export: add Fit mode to image export too

ImageExportSettings gains `fit: ExportFitMode` (default Letterbox) + a "Fit"
dropdown in advanced image settings; the image render path uses it instead of the
hardcoded Letterbox. Image export supports a resolution override, so the mode
matters when the override aspect differs from the document.
This commit is contained in:
Skyler Lehmkuhl 2026-06-26 14:39:18 -04:00
parent fffcf0679c
commit 5bc117c518
3 changed files with 19 additions and 3 deletions

View File

@ -524,11 +524,14 @@ pub struct ImageExportSettings {
/// When false, the image is composited onto an opaque background before encoding.
/// Only meaningful for formats that support alpha (PNG, WebP).
pub allow_transparency: bool,
/// How the document is fit into the output frame when aspect ratios differ (default Letterbox).
#[serde(default)]
pub fit: ExportFitMode,
}
impl Default for ImageExportSettings {
fn default() -> Self {
Self { format: ImageFormat::Png, time: 0.0, width: None, height: None, quality: 90, allow_transparency: false }
Self { format: ImageFormat::Png, time: 0.0, width: None, height: None, quality: 90, allow_transparency: false, fit: ExportFitMode::Letterbox }
}
}

View File

@ -378,6 +378,19 @@ impl ExportDialog {
if changed_h { self.image_settings.height = if h == 0 { None } else { Some(h) }; }
ui.weak("(0 = document size)");
});
// Fit mode — how the document maps into the output frame when aspect ratios differ.
ui.horizontal(|ui| {
use lightningbeam_core::export::ExportFitMode;
ui.label("Fit:");
egui::ComboBox::from_id_salt("image_fit_mode")
.selected_text(self.image_settings.fit.name())
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.image_settings.fit, ExportFitMode::Letterbox, ExportFitMode::Letterbox.name());
ui.selectable_value(&mut self.image_settings.fit, ExportFitMode::Crop, ExportFitMode::Crop.name());
ui.selectable_value(&mut self.image_settings.fit, ExportFitMode::Stretch, ExportFitMode::Stretch.name());
});
});
}
/// Render advanced audio settings (sample rate, channels, bit depth, bitrate, time range)

View File

@ -598,6 +598,7 @@ impl ExportOrchestrator {
// ── First call: render the frame to the GPU output texture ────────
let w = state.width;
let h = state.height;
let fit = state.settings.fit;
if state.gpu_resources.is_none() {
state.gpu_resources = Some(video_exporter::ExportGpuResources::new(device, w, h));
@ -632,8 +633,7 @@ impl ExportOrchestrator {
state.settings.allow_transparency,
raster_store,
true, // image export composites on the shared device
// Image export renders at the document's own size, so the fit transform is identity.
lightningbeam_core::export::ExportFitMode::Letterbox,
fit,
)?;
queue.submit(Some(encoder.finish()));