added specific resolution output for rasterized image formats
This commit is contained in:
+40
-4
@@ -2,7 +2,7 @@ use std::collections::HashSet;
|
|||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use image::{DynamicImage, ImageFormat, RgbaImage};
|
use image::{DynamicImage, ImageFormat, RgbaImage, imageops::FilterType};
|
||||||
use rfd::FileDialog;
|
use rfd::FileDialog;
|
||||||
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
use tiny_skia::{FillRule, Paint, PathBuilder, Pixmap, Rect, Stroke, StrokeDash, Transform};
|
||||||
|
|
||||||
@@ -105,9 +105,7 @@ pub fn export_with_dialog(
|
|||||||
}
|
}
|
||||||
ExportFormat::Png | ExportFormat::Jpeg | ExportFormat::Webp => {
|
ExportFormat::Png | ExportFormat::Jpeg | ExportFormat::Webp => {
|
||||||
let pixmap = render_pixmap(layout, settings)?;
|
let pixmap = render_pixmap(layout, settings)?;
|
||||||
let rgba = RgbaImage::from_raw(pixmap.width(), pixmap.height(), pixmap.data().to_vec())
|
let img = raster_image_from_pixmap(&pixmap, settings)?;
|
||||||
.ok_or_else(|| "Failed to build image buffer".to_string())?;
|
|
||||||
let img = DynamicImage::ImageRgba8(rgba);
|
|
||||||
let format = match settings.export_format {
|
let format = match settings.export_format {
|
||||||
ExportFormat::Png => ImageFormat::Png,
|
ExportFormat::Png => ImageFormat::Png,
|
||||||
ExportFormat::Jpeg => ImageFormat::Jpeg,
|
ExportFormat::Jpeg => ImageFormat::Jpeg,
|
||||||
@@ -216,6 +214,44 @@ fn render_pixmap(layout: &DungeonLayout, settings: &UiSettings) -> Result<Pixmap
|
|||||||
Ok(pixmap)
|
Ok(pixmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn raster_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_target_size(settings);
|
||||||
|
if img.width() != target.0 || img.height() != target.1 {
|
||||||
|
img = img.resize_exact(target.0, target.1, FilterType::Lanczos3);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if !settings.allow_export_aspect_change {
|
||||||
|
let cols = settings.cols.max(1) as f32;
|
||||||
|
let rows = settings.rows.max(1) as f32;
|
||||||
|
let ratio = cols / rows;
|
||||||
|
height = ((width as f32) / ratio).round() as u32;
|
||||||
|
height = height.clamp(1, 10_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if width == 0 {
|
||||||
|
width = 1;
|
||||||
|
}
|
||||||
|
if height == 0 {
|
||||||
|
height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
(width, height)
|
||||||
|
}
|
||||||
|
|
||||||
fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
fn build_svg(layout: &DungeonLayout, settings: &UiSettings) -> String {
|
||||||
let g = ExportGeometry::new(settings.cols, settings.rows);
|
let g = ExportGeometry::new(settings.cols, settings.rows);
|
||||||
let wall_w = (g.cell / 5.0).max(1.0);
|
let wall_w = (g.cell / 5.0).max(1.0);
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ impl ExportFormat {
|
|||||||
ExportFormat::Svg => ".svg",
|
ExportFormat::Svg => ".svg",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_raster(self) -> bool {
|
||||||
|
!matches!(self, ExportFormat::Svg)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
@@ -67,6 +71,9 @@ pub struct UiSettings {
|
|||||||
pub allow_middle_corridor_doors: bool,
|
pub allow_middle_corridor_doors: bool,
|
||||||
pub colorblind_mode: bool,
|
pub colorblind_mode: bool,
|
||||||
pub export_format: ExportFormat,
|
pub export_format: ExportFormat,
|
||||||
|
pub export_width: u32,
|
||||||
|
pub export_height: u32,
|
||||||
|
pub allow_export_aspect_change: bool,
|
||||||
active_tab: Tab,
|
active_tab: Tab,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +95,9 @@ impl Default for UiSettings {
|
|||||||
allow_middle_corridor_doors: false,
|
allow_middle_corridor_doors: false,
|
||||||
colorblind_mode: false,
|
colorblind_mode: false,
|
||||||
export_format: ExportFormat::Png,
|
export_format: ExportFormat::Png,
|
||||||
|
export_width: 1920,
|
||||||
|
export_height: 1080,
|
||||||
|
allow_export_aspect_change: false,
|
||||||
active_tab: Tab::Generate,
|
active_tab: Tab::Generate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,6 +214,63 @@ fn draw_generate_tab(ui: &mut egui::Ui, settings: &mut UiSettings, result: &mut
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if settings.export_format.is_raster() {
|
||||||
|
ui.add_space(8.0);
|
||||||
|
ui.checkbox(
|
||||||
|
&mut settings.allow_export_aspect_change,
|
||||||
|
"Allow aspect ratio change",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut width_changed = false;
|
||||||
|
let mut height_changed = false;
|
||||||
|
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.label("Resolution Width");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
width_changed |= ui
|
||||||
|
.add(egui::Slider::new(&mut settings.export_width, 1..=10_000).show_value(false))
|
||||||
|
.changed();
|
||||||
|
width_changed |= ui
|
||||||
|
.add(
|
||||||
|
egui::DragValue::new(&mut settings.export_width)
|
||||||
|
.speed(1.0)
|
||||||
|
.range(1..=10_000),
|
||||||
|
)
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
|
||||||
|
ui.add_space(6.0);
|
||||||
|
ui.label("Resolution Height");
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
height_changed |= ui
|
||||||
|
.add(
|
||||||
|
egui::Slider::new(&mut settings.export_height, 1..=10_000).show_value(false),
|
||||||
|
)
|
||||||
|
.changed();
|
||||||
|
height_changed |= ui
|
||||||
|
.add(
|
||||||
|
egui::DragValue::new(&mut settings.export_height)
|
||||||
|
.speed(1.0)
|
||||||
|
.range(1..=10_000),
|
||||||
|
)
|
||||||
|
.changed();
|
||||||
|
});
|
||||||
|
|
||||||
|
if !settings.allow_export_aspect_change {
|
||||||
|
let cols = settings.cols.max(1) as f32;
|
||||||
|
let rows = settings.rows.max(1) as f32;
|
||||||
|
let ratio = cols / rows;
|
||||||
|
|
||||||
|
if width_changed {
|
||||||
|
let h = ((settings.export_width as f32) / ratio).round() as u32;
|
||||||
|
settings.export_height = h.clamp(1, 10_000);
|
||||||
|
} else if height_changed {
|
||||||
|
let w = ((settings.export_height as f32) * ratio).round() as u32;
|
||||||
|
settings.export_width = w.clamp(1, 10_000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ui.add_space(6.0);
|
ui.add_space(6.0);
|
||||||
if ui.button("Export Image").clicked() {
|
if ui.button("Export Image").clicked() {
|
||||||
result.export_clicked = true;
|
result.export_clicked = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user