diff --git a/src/exporter.rs b/src/exporter.rs index dee90b6..3a103bb 100644 --- a/src/exporter.rs +++ b/src/exporter.rs @@ -171,10 +171,12 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result Result<(), String> { let ext = settings.mask_format.extension(); 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)) .map_err(|e| format!("Failed writing {}: {e}", path.display()))?; Ok(()) @@ -335,6 +338,35 @@ fn raster_image_from_pixmap( Ok(img) } +fn raster_mask_image_from_pixmap( + pixmap: &Pixmap, + settings: &UiSettings, +) -> Result { + 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) { let mut width = settings.export_width.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 ); - for c in 0..=g.cols { - let x = g.left() + c as f32 * g.cell; - let _ = writeln!( - s, - "", - g.top(), - g.top() + g.rows as f32 * g.cell, - GRID.0, - GRID.1, - GRID.2 - ); - } - for r in 0..=g.rows { - let y = g.top() + r as f32 * g.cell; - let _ = writeln!( - s, - "", - g.left(), - g.left() + g.cols as f32 * g.cell, - GRID.0, - GRID.1, - GRID.2 - ); + if settings.export_show_grid { + for c in 0..=g.cols { + let x = g.left() + c as f32 * g.cell; + let _ = writeln!( + s, + "", + g.top(), + g.top() + g.rows as f32 * g.cell, + GRID.0, + GRID.1, + GRID.2 + ); + } + for r in 0..=g.rows { + let y = g.top() + r as f32 * g.cell; + let _ = writeln!( + s, + "", + g.left(), + g.left() + g.cols as f32 * g.cell, + GRID.0, + GRID.1, + GRID.2 + ); + } } 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)) { + 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 { - let x = g.left() + c as f32 * g.cell; - draw_line( - pixmap, - x, - g.top(), - x, - g.top() + g.rows as f32 * g.cell, - 1.0, - color, - DoorStyle::Solid, - ); + let x = (left + c as f32 * g.cell).round(); + let Some(rect) = Rect::from_xywh(x, top, 1.0, height) else { + continue; + }; + pixmap.fill_rect(rect, &paint, Transform::identity(), None); } for r in 0..=g.rows { - let y = g.top() + r as f32 * g.cell; - draw_line( - pixmap, - g.left(), - y, - g.left() + g.cols as f32 * g.cell, - y, - 1.0, - color, - DoorStyle::Solid, - ); + let y = (top + r as f32 * g.cell).round(); + let Some(rect) = Rect::from_xywh(left, y, width, 1.0) else { + continue; + }; + pixmap.fill_rect(rect, &paint, Transform::identity(), None); } } @@ -742,7 +772,12 @@ fn draw_wall_segment( width: f32, color: (u8, u8, u8, u8), ) { + let width = width.round().max(1.0); 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 x = x1.min(x2); let y0 = y1.min(y2); @@ -764,6 +799,14 @@ fn draw_wall_segment( 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( out: &mut String, g: &ExportGeometry, diff --git a/src/ui.rs b/src/ui.rs index a04cd9f..f7df825 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -111,6 +111,7 @@ pub struct UiSettings { pub export_width: u32, pub export_height: u32, pub allow_export_aspect_change: bool, + pub export_show_grid: bool, active_tab: Tab, } @@ -138,6 +139,7 @@ impl Default for UiSettings { export_width: 1920, export_height: 1080, allow_export_aspect_change: false, + export_show_grid: true, 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() { ui.add_space(8.0); ui.checkbox(