fixed masks on export and added option to have the grid or not

This commit is contained in:
grimsace
2026-03-12 08:50:32 -05:00
parent 2f91824d72
commit b1779c4a25
2 changed files with 108 additions and 58 deletions
+101 -58
View File
@@ -171,10 +171,12 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?; Pixmap::new(g.width, g.height).ok_or_else(|| "Failed to allocate canvas".to_string())?;
fill_bg(&mut pixmap, BG); fill_bg(&mut pixmap, BG);
let wall_w = (g.cell / 5.0).max(1.0); let wall_w = snap_even_width((g.cell / 5.0).max(1.0));
let door_w = (g.cell / 10.0).max(1.0); let door_w = (g.cell / 10.0).max(1.0).round();
draw_grid(&mut pixmap, &g); if settings.export_show_grid {
draw_grid(&mut pixmap, &g);
}
for &(x, y) in &scene.corridor_cells { for &(x, y) in &scene.corridor_cells {
fill_rect_cell(&mut pixmap, &g, x, y, CORRIDOR); fill_rect_cell(&mut pixmap, &g, x, y, CORRIDOR);
@@ -247,7 +249,7 @@ fn export_composite_masks(
let mut floors = Pixmap::new(g.width, g.height) let mut floors = Pixmap::new(g.width, g.height)
.ok_or_else(|| "Failed to allocate floors mask canvas".to_string())?; .ok_or_else(|| "Failed to allocate floors mask canvas".to_string())?;
fill_bg(&mut floors, BG); fill_bg(&mut floors, BLACK);
for &(x, y) in &scene.corridor_cells { for &(x, y) in &scene.corridor_cells {
fill_rect_cell(&mut floors, &g, x, y, WHITE); fill_rect_cell(&mut floors, &g, x, y, WHITE);
} }
@@ -257,7 +259,7 @@ fn export_composite_masks(
let mut walls = Pixmap::new(g.width, g.height) let mut walls = Pixmap::new(g.width, g.height)
.ok_or_else(|| "Failed to allocate walls mask canvas".to_string())?; .ok_or_else(|| "Failed to allocate walls mask canvas".to_string())?;
fill_bg(&mut walls, BG); fill_bg(&mut walls, BLACK);
draw_cell_walls( draw_cell_walls(
&mut walls, &mut walls,
&g, &g,
@@ -279,20 +281,21 @@ fn export_composite_masks(
let mut doors = Pixmap::new(g.width, g.height) let mut doors = Pixmap::new(g.width, g.height)
.ok_or_else(|| "Failed to allocate doors mask canvas".to_string())?; .ok_or_else(|| "Failed to allocate doors mask canvas".to_string())?;
fill_bg(&mut doors, BG); fill_bg(&mut doors, BLACK);
for door in &layout.doors { for door in &layout.doors {
draw_door(&mut doors, &g, door, door_w, WHITE, DoorStyle::Solid); draw_door(&mut doors, &g, door, door_w, WHITE, DoorStyle::Solid);
} }
let mut grid = Pixmap::new(g.width, g.height)
.ok_or_else(|| "Failed to allocate grid mask canvas".to_string())?;
fill_bg(&mut grid, BG);
draw_grid_color(&mut grid, &g, WHITE);
save_mask_image(folder, "floors_mask", &floors, settings)?; save_mask_image(folder, "floors_mask", &floors, settings)?;
save_mask_image(folder, "walls_mask", &walls, settings)?; save_mask_image(folder, "walls_mask", &walls, settings)?;
save_mask_image(folder, "doors_mask", &doors, settings)?; save_mask_image(folder, "doors_mask", &doors, settings)?;
save_mask_image(folder, "grid_mask", &grid, settings)?; if settings.export_show_grid {
let mut grid = Pixmap::new(g.width, g.height)
.ok_or_else(|| "Failed to allocate grid mask canvas".to_string())?;
fill_bg(&mut grid, BLACK);
draw_grid_color(&mut grid, &g, WHITE);
save_mask_image(folder, "grid_mask", &grid, settings)?;
}
Ok(()) Ok(())
} }
@@ -305,7 +308,7 @@ fn save_mask_image(
) -> Result<(), String> { ) -> Result<(), String> {
let ext = settings.mask_format.extension(); let ext = settings.mask_format.extension();
let path = folder.join(format!("{stem}.{ext}")); let path = folder.join(format!("{stem}.{ext}"));
let img = raster_image_from_pixmap(pixmap, settings)?; let img = raster_mask_image_from_pixmap(pixmap, settings)?;
img.save_with_format(&path, mask_image_format(settings.mask_format)) img.save_with_format(&path, mask_image_format(settings.mask_format))
.map_err(|e| format!("Failed writing {}: {e}", path.display()))?; .map_err(|e| format!("Failed writing {}: {e}", path.display()))?;
Ok(()) Ok(())
@@ -335,6 +338,35 @@ fn raster_image_from_pixmap(
Ok(img) Ok(img)
} }
fn raster_mask_image_from_pixmap(
pixmap: &Pixmap,
settings: &UiSettings,
) -> Result<DynamicImage, String> {
let rgba = RgbaImage::from_raw(pixmap.width(), pixmap.height(), pixmap.data().to_vec())
.ok_or_else(|| "Failed to build image buffer".to_string())?;
let mut img = DynamicImage::ImageRgba8(rgba);
let target = raster_mask_target_size(settings, pixmap.width(), pixmap.height());
if img.width() != target.0 || img.height() != target.1 {
img = img.resize_exact(target.0, target.1, FilterType::Nearest);
}
let mut out = img.to_rgba8();
for pixel in out.pixels_mut() {
let on = pixel[0] > 127 || pixel[1] > 127 || pixel[2] > 127;
let v = if on { 255 } else { 0 };
*pixel = image::Rgba([v, v, v, 255]);
}
Ok(DynamicImage::ImageRgba8(out))
}
fn raster_mask_target_size(settings: &UiSettings, src_w: u32, src_h: u32) -> (u32, u32) {
let (target_w, _target_h) = raster_target_size(settings);
let scale = ((target_w as f32) / (src_w as f32)).round().max(1.0) as u32;
(src_w.saturating_mul(scale), src_h.saturating_mul(scale))
}
fn raster_target_size(settings: &UiSettings) -> (u32, u32) { fn raster_target_size(settings: &UiSettings) -> (u32, u32) {
let mut width = settings.export_width.clamp(1, 10_000); let mut width = settings.export_width.clamp(1, 10_000);
let mut height = settings.export_height.clamp(1, 10_000); let mut height = settings.export_height.clamp(1, 10_000);
@@ -374,29 +406,31 @@ fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
BG.0, BG.1, BG.2 BG.0, BG.1, BG.2
); );
for c in 0..=g.cols { if settings.export_show_grid {
let x = g.left() + c as f32 * g.cell; for c in 0..=g.cols {
let _ = writeln!( let x = g.left() + c as f32 * g.cell;
s, let _ = writeln!(
"<line x1='{x}' y1='{}' x2='{x}' y2='{}' stroke='rgb({},{},{})' stroke-width='1'/>", s,
g.top(), "<line x1='{x}' y1='{}' x2='{x}' y2='{}' stroke='rgb({},{},{})' stroke-width='1'/>",
g.top() + g.rows as f32 * g.cell, g.top(),
GRID.0, g.top() + g.rows as f32 * g.cell,
GRID.1, GRID.0,
GRID.2 GRID.1,
); GRID.2
} );
for r in 0..=g.rows { }
let y = g.top() + r as f32 * g.cell; for r in 0..=g.rows {
let _ = writeln!( let y = g.top() + r as f32 * g.cell;
s, let _ = writeln!(
"<line x1='{}' y1='{y}' x2='{}' y2='{y}' stroke='rgb({},{},{})' stroke-width='1'/>", s,
g.left(), "<line x1='{}' y1='{y}' x2='{}' y2='{y}' stroke='rgb({},{},{})' stroke-width='1'/>",
g.left() + g.cols as f32 * g.cell, g.left(),
GRID.0, g.left() + g.cols as f32 * g.cell,
GRID.1, GRID.0,
GRID.2 GRID.1,
); GRID.2
);
}
} }
let corridor_cells = corridor_cells(layout, settings.cols, settings.rows); let corridor_cells = corridor_cells(layout, settings.cols, settings.rows);
@@ -540,31 +574,27 @@ fn draw_grid(pixmap: &mut Pixmap, g: &ExportGeometry) {
} }
fn draw_grid_color(pixmap: &mut Pixmap, g: &ExportGeometry, color: (u8, u8, u8, u8)) { fn draw_grid_color(pixmap: &mut Pixmap, g: &ExportGeometry, color: (u8, u8, u8, u8)) {
let left = g.left();
let top = g.top();
let width = g.cols as f32 * g.cell;
let height = g.rows as f32 * g.cell;
let mut paint = Paint::default();
paint.set_color_rgba8(color.0, color.1, color.2, color.3);
for c in 0..=g.cols { for c in 0..=g.cols {
let x = g.left() + c as f32 * g.cell; let x = (left + c as f32 * g.cell).round();
draw_line( let Some(rect) = Rect::from_xywh(x, top, 1.0, height) else {
pixmap, continue;
x, };
g.top(), pixmap.fill_rect(rect, &paint, Transform::identity(), None);
x,
g.top() + g.rows as f32 * g.cell,
1.0,
color,
DoorStyle::Solid,
);
} }
for r in 0..=g.rows { for r in 0..=g.rows {
let y = g.top() + r as f32 * g.cell; let y = (top + r as f32 * g.cell).round();
draw_line( let Some(rect) = Rect::from_xywh(left, y, width, 1.0) else {
pixmap, continue;
g.left(), };
y, pixmap.fill_rect(rect, &paint, Transform::identity(), None);
g.left() + g.cols as f32 * g.cell,
y,
1.0,
color,
DoorStyle::Solid,
);
} }
} }
@@ -742,7 +772,12 @@ fn draw_wall_segment(
width: f32, width: f32,
color: (u8, u8, u8, u8), color: (u8, u8, u8, u8),
) { ) {
let width = width.round().max(1.0);
let half = width * 0.5; let half = width * 0.5;
let x1 = x1.round();
let y1 = y1.round();
let x2 = x2.round();
let y2 = y2.round();
let rect = if (x1 - x2).abs() <= f32::EPSILON { let rect = if (x1 - x2).abs() <= f32::EPSILON {
let x = x1.min(x2); let x = x1.min(x2);
let y0 = y1.min(y2); let y0 = y1.min(y2);
@@ -764,6 +799,14 @@ fn draw_wall_segment(
pixmap.fill_rect(rect, &paint, Transform::identity(), None); pixmap.fill_rect(rect, &paint, Transform::identity(), None);
} }
fn snap_even_width(value: f32) -> f32 {
let mut w = value.round().max(1.0);
if (w as u32) % 2 == 1 {
w += 1.0;
}
w
}
fn append_svg_walls( fn append_svg_walls(
out: &mut String, out: &mut String,
g: &ExportGeometry, g: &ExportGeometry,
+7
View File
@@ -111,6 +111,7 @@ pub struct UiSettings {
pub export_width: u32, pub export_width: u32,
pub export_height: u32, pub export_height: u32,
pub allow_export_aspect_change: bool, pub allow_export_aspect_change: bool,
pub export_show_grid: bool,
active_tab: Tab, active_tab: Tab,
} }
@@ -138,6 +139,7 @@ impl Default for UiSettings {
export_width: 1920, export_width: 1920,
export_height: 1080, export_height: 1080,
allow_export_aspect_change: false, allow_export_aspect_change: false,
export_show_grid: true,
active_tab: Tab::Generate, active_tab: Tab::Generate,
} }
} }
@@ -273,6 +275,11 @@ fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut
}); });
} }
ui.add_space(6.0);
result.settings_changed |= ui
.checkbox(&mut settings.export_show_grid, "Include Grid In Export")
.changed();
if settings.export_format.supports_resolution() { if settings.export_format.supports_resolution() {
ui.add_space(8.0); ui.add_space(8.0);
ui.checkbox( ui.checkbox(