diff --git a/crates/engine/src/camera_preview.rs b/crates/engine/src/camera_preview.rs index 84dc7ef69..e01116b45 100644 --- a/crates/engine/src/camera_preview.rs +++ b/crates/engine/src/camera_preview.rs @@ -1937,6 +1937,7 @@ mod tests { color: ColorData::default(), wb_multipliers: Some([1.0; 3]), linearized: true, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: lightcraft_meta::Metadata::default(), lens_metadata: Default::default(), @@ -2326,6 +2327,7 @@ mod tests { color, wb_multipliers: Some([2.0, 1.0, 1.5]), linearized: false, + pixel_aspect: 1.0, opcodes: Default::default(), metadata: lightcraft_raw::Metadata { make: Some(make.to_owned()), model: Some(model.to_owned()), ..Default::default() }, lens_metadata: Default::default(), @@ -3540,6 +3542,7 @@ mod tests { color: ColorData::default(), wb_multipliers: Some([1.0; 3]), linearized: true, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: lightcraft_meta::Metadata { make: Some("SONY".into()), model: Some("ILCE-7M4".into()), ..Default::default() }, lens_metadata: Default::default(), diff --git a/crates/engine/src/files.rs b/crates/engine/src/files.rs index 5fd6537e5..e68632ef7 100644 --- a/crates/engine/src/files.rs +++ b/crates/engine/src/files.rs @@ -159,10 +159,8 @@ pub fn probe_bytes(name: &str, bytes: &[u8]) -> Result { } Err(Err(e)) => return Err(e), }; - let (mut w, mut h) = (raw.crop.width.max(1) as u32, raw.crop.height.max(1) as u32); - if w <= 1 || h <= 1 { - (w, h) = (raw.active_area.width as u32, raw.active_area.height as u32); - } + let (w, h) = raw.developed_size(); + let (mut w, mut h) = (w.max(1) as u32, h.max(1) as u32); if raw.orientation.swaps_axes() { std::mem::swap(&mut w, &mut h); } diff --git a/crates/engine/src/lens_profiles.rs b/crates/engine/src/lens_profiles.rs index 1894f7189..6338b3b40 100644 --- a/crates/engine/src/lens_profiles.rs +++ b/crates/engine/src/lens_profiles.rs @@ -64,8 +64,7 @@ fn frame_crop_factor(camera: Option, focal: f64, focal_35mm: Option) - pub fn for_raw(raw: &lightcraft_raw::RawInfo, meta: impl FnOnce() -> lightcraft_meta::Metadata) -> Option { crate::files::embedded_lens(raw).or_else(|| { let db = database()?; - let c = raw.crop.clipped(raw.active_area.width, raw.active_area.height); - let (w, h) = if c.width == 0 || c.height == 0 { (raw.active_area.width, raw.active_area.height) } else { (c.width, c.height) }; + let (w, h) = raw.developed_size(); lookup(db, &meta(), w as f64, h as f64, raw.orientation) }) } diff --git a/crates/engine/src/tests_xmp.rs b/crates/engine/src/tests_xmp.rs index 11871c90f..c5f2b05c0 100644 --- a/crates/engine/src/tests_xmp.rs +++ b/crates/engine/src/tests_xmp.rs @@ -584,6 +584,7 @@ pub(crate) fn synthetic_dng_of(w: usize, h: usize, cfa: &str, data: Vec, xm }, wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/merge/src/frame.rs b/crates/merge/src/frame.rs index cb74e23ed..1316bfd13 100644 --- a/crates/merge/src/frame.rs +++ b/crates/merge/src/frame.rs @@ -309,6 +309,7 @@ mod tests { linearized: false, opcodes: OpcodeLists::default(), lens_metadata: Default::default(), + pixel_aspect: 1.0, metadata: Metadata::default(), } } diff --git a/crates/merge/src/output.rs b/crates/merge/src/output.rs index 6cb06c966..2d0a523f4 100644 --- a/crates/merge/src/output.rs +++ b/crates/merge/src/output.rs @@ -112,6 +112,7 @@ pub fn write_linear_dng( color: cd, wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists { list3: lens.to_vec(), ..Default::default() }, metadata: meta, lens_metadata: Default::default(), diff --git a/crates/raw/examples/rawinfo.rs b/crates/raw/examples/rawinfo.rs index 78624a0de..23e540b96 100644 --- a/crates/raw/examples/rawinfo.rs +++ b/crates/raw/examples/rawinfo.rs @@ -228,6 +228,7 @@ mod tests { opcodes: OpcodeLists::default(), metadata: Default::default(), lens_metadata: Default::default(), + pixel_aspect: 1.0, }; let path = dir.path("valid.dng"); std::fs::write(&path, write_dng(&raw, &DngWriteOptions::default()).expect("encode generated fixture")).expect("write fixture"); diff --git a/crates/raw/src/binned.rs b/crates/raw/src/binned.rs index 5eb99422b..db725a2ec 100644 --- a/crates/raw/src/binned.rs +++ b/crates/raw/src/binned.rs @@ -24,7 +24,7 @@ impl RawImage { /// and B; Bayer: `k` even, so that every block has the same layout)? pub fn can_bin(&self, k: usize) -> bool { let Some(cfa) = &self.cfa else { return false }; - if k < 2 || self.cpp != 1 || !cfa.valid() || (cfa.is_bayer() && !k.is_multiple_of(2)) { + if k < 2 || self.cpp != 1 || (self.pixel_aspect - 1.0).abs() >= 0.01 || !cfa.valid() || (cfa.is_bayer() && !k.is_multiple_of(2)) { return false; } // every window position relative to the pattern @@ -163,6 +163,7 @@ mod tests { color: ColorData::default(), wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: Metadata::default(), lens_metadata: Default::default(), diff --git a/crates/raw/src/color.rs b/crates/raw/src/color.rs index 12c509190..2caafcfde 100644 --- a/crates/raw/src/color.rs +++ b/crates/raw/src/color.rs @@ -287,6 +287,7 @@ mod tests { color, wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: Default::default(), metadata: Default::default(), lens_metadata: Default::default(), diff --git a/crates/raw/src/dng.rs b/crates/raw/src/dng.rs index b076c8bc3..e9fc0999d 100644 --- a/crates/raw/src/dng.rs +++ b/crates/raw/src/dng.rs @@ -338,6 +338,7 @@ pub(crate) fn decode_as(bytes: &[u8], mode: Mode, format: RawFormat) -> Result Result) -> f32 { + match scale { + Some([h, v]) if *h > 0.0 && *v > 0.0 && (1.5..=4.0).contains(&(v / h)) => (v / h) as f32, + _ => 1.0, + } +} + #[cfg(test)] mod tests { + #[test] + fn default_scale_makes_tall_pixels() { + assert_eq!(default_scale_aspect(Some(&[1.0, 2.0])), 2.0); + assert_eq!(default_scale_aspect(Some(&[1.0, 1.0])), 1.0); + assert_eq!(default_scale_aspect(Some(&[2.0, 1.0])), 1.0); + assert_eq!(default_scale_aspect(Some(&[1.0, 1.2])), 1.0); + assert_eq!(default_scale_aspect(None), 1.0); + } + use super::*; /// Store `image` the way DNG 1.7 interleaves it: row `r` of the image is written to stored row diff --git a/crates/raw/src/dngwrite.rs b/crates/raw/src/dngwrite.rs index 815a33790..d7ba42776 100644 --- a/crates/raw/src/dngwrite.rs +++ b/crates/raw/src/dngwrite.rs @@ -538,6 +538,7 @@ pub fn write_rendered_dng( color, wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: crate::OpcodeLists::default(), metadata: metadata.clone(), lens_metadata: Default::default(), diff --git a/crates/raw/src/lib.rs b/crates/raw/src/lib.rs index faa28c593..202983331 100644 --- a/crates/raw/src/lib.rs +++ b/crates/raw/src/lib.rs @@ -435,13 +435,27 @@ pub struct RawInfo { /// Lens-correction tables the file records, decoded but not necessarily applied (see [`crate::lens_metadata`]). #[serde(default)] pub lens_metadata: LensMetadata, + /// See [`RawImage::pixel_aspect`]. + #[serde(default = "square_pixels")] + pub pixel_aspect: f32, +} + +fn square_pixels() -> f32 { + 1.0 +} + +/// Rows of a picture `height` rows tall once its pixels (taller than wide by `aspect`) are made square. +pub fn stretched_height(height: usize, aspect: f32) -> usize { + if (aspect - 1.0).abs() < 0.01 { height } else { ((height as f32 * aspect).round() as usize).max(1) } } impl RawInfo { - /// Size of the developed image before orientation: the default crop, else the active area. + /// Size of the developed image before orientation: the default crop, else the active area, with the rows + /// stretched for non-square pixels. pub fn developed_size(&self) -> (usize, usize) { let c = self.crop.clipped(self.active_area.width, self.active_area.height); - if c.width > 1 && c.height > 1 { (c.width, c.height) } else { (self.active_area.width, self.active_area.height) } + let (w, h) = if c.width > 1 && c.height > 1 { (c.width, c.height) } else { (self.active_area.width, self.active_area.height) }; + (w, stretched_height(h, self.pixel_aspect)) } } @@ -666,6 +680,10 @@ pub struct RawImage { pub wb_multipliers: Option<[f32; 3]>, /// Whether a `LinearizationTable` was applied to `data`. pub linearized: bool, + /// Height of a sensor pixel over its width (1.0 for square pixels). The D1X and D1H write 2.0: their frame is + /// 4028 x 1324 and the picture is 3:2 only once the rows are doubled. [`Self::develop`] stretches the rows by + /// this factor. + pub pixel_aspect: f32, pub opcodes: OpcodeLists, pub metadata: Metadata, /// Lens-correction tables the file records, decoded but not necessarily applied (see [`crate::lens_metadata`]); only @@ -704,6 +722,7 @@ impl RawImage { opcodes: self.opcodes.clone(), metadata: self.metadata.clone(), lens_metadata: self.lens_metadata.clone(), + pixel_aspect: self.pixel_aspect, } } @@ -724,6 +743,7 @@ impl RawImage { opcodes: self.opcodes, metadata: self.metadata, lens_metadata: self.lens_metadata, + pixel_aspect: self.pixel_aspect, } } @@ -827,10 +847,13 @@ impl RawImage { pub fn finish_demosaiced(&self, mut rgb: Rgb32f) -> Rgb32f { opcodes::apply_list3(&self.opcodes.list3, &mut rgb); let c = self.develop_crop(rgb.width, rgb.height); - if c.x == 0 && c.y == 0 && c.width == rgb.width && c.height == rgb.height { + let rgb = + if c.x == 0 && c.y == 0 && c.width == rgb.width && c.height == rgb.height { rgb } else { rgb.into_crop(c.x, c.y, c.width, c.height) }; + let h = stretched_height(rgb.height, self.pixel_aspect); + if h == rgb.height { return rgb; } - rgb.into_crop(c.x, c.y, c.width, c.height) + lightcraft_raster::resample::resize(&rgb, rgb.width, h, lightcraft_raster::resample::Filter::Mitchell) } /// The part of a `width × height` demosaiced picture (the active area) that [`Self::develop`] keeps: the default @@ -889,6 +912,7 @@ mod tests { color: ColorData::default(), wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: Metadata::default(), lens_metadata: Default::default(), @@ -906,6 +930,17 @@ mod tests { // a crop that pokes out is clipped to the picture whole.crop = Rect::new(6, 4, 10, 10); assert_eq!(whole.develop_crop(8, 6), Rect::new(6, 4, 2, 2)); + + // tall pixels (D1X: 2.0) are stretched to square ones: the rows double, `developed_size` says so + let mut tall = raw.clone(); + tall.pixel_aspect = 2.0; + let d = tall.develop(Method::Bilinear).unwrap(); + assert_eq!((d.width, d.height), (5, 8)); + assert_eq!(tall.info().developed_size(), (5, 8)); + assert_eq!(raw.info().developed_size(), (5, 4)); + assert!(!tall.can_bin(2)); + assert_eq!(stretched_height(1324, 2.0), 2648); + assert_eq!(stretched_height(1324, 1.0), 1324); } /// A maker-note crop (`PEF` AOC / `ORF` ImageProcessing) with an origin of `u64::MAX` used to @@ -933,6 +968,7 @@ mod tests { opcodes: OpcodeLists::default(), metadata: Metadata::default(), lens_metadata: Default::default(), + pixel_aspect: 1.0, }; assert!(raw.validate().is_err(), "`x + width` wrapped: the area is outside the image"); assert!(raw.normalized().is_err()); diff --git a/crates/raw/src/saturation.rs b/crates/raw/src/saturation.rs index 261d5023b..16768b16d 100644 --- a/crates/raw/src/saturation.rs +++ b/crates/raw/src/saturation.rs @@ -135,6 +135,7 @@ mod tests { color: ColorData::default(), wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: Metadata::default(), lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/arw.rs b/crates/raw/src/vendor/arw.rs index fddf47bb9..a662799ae 100644 --- a/crates/raw/src/vendor/arw.rs +++ b/crates/raw/src/vendor/arw.rs @@ -810,6 +810,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { // makes a neutral surface magenta. WB edits remain relative to this as-shot RGB. wb_multipliers: if linear_rgb { Some([1.0; 3]) } else { wb }, linearized: false, + pixel_aspect: 1.0, // only validated components become opcodes (see `arw_lens`, and `arw_lens_rm6` for the ILCE-7RM6) opcodes: OpcodeLists { list3: warp.into_iter().chain(rm6).collect(), ..Default::default() }, metadata, diff --git a/crates/raw/src/vendor/cr2.rs b/crates/raw/src/vendor/cr2.rs index f6bc1798a..3fe173e09 100644 --- a/crates/raw/src/vendor/cr2.rs +++ b/crates/raw/src/vendor/cr2.rs @@ -249,6 +249,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/cr2/sraw.rs b/crates/raw/src/vendor/cr2/sraw.rs index e1b2988e9..6a37d627e 100644 --- a/crates/raw/src/vendor/cr2/sraw.rs +++ b/crates/raw/src/vendor/cr2/sraw.rs @@ -391,6 +391,7 @@ pub(super) fn decode(bytes: &[u8], tiff: &Tiff, raw: &Ifd, mode: Mode) -> Result // the camera's white balance is already applied, as in Sony's linear YCbCr wb_multipliers: Some([1.0; 3]), linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/cr3.rs b/crates/raw/src/vendor/cr3.rs index 2c37c99d3..3df33424d 100644 --- a/crates/raw/src/vendor/cr3.rs +++ b/crates/raw/src/vendor/cr3.rs @@ -253,6 +253,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: levels.wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/erf.rs b/crates/raw/src/vendor/erf.rs index 7f98ca121..b9861ef38 100644 --- a/crates/raw/src/vendor/erf.rs +++ b/crates/raw/src/vendor/erf.rs @@ -124,6 +124,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), + pixel_aspect: 1.0, }; img.validate_for(mode)?; Ok(img) diff --git a/crates/raw/src/vendor/kodak.rs b/crates/raw/src/vendor/kodak.rs index 61c238740..1e972873e 100644 --- a/crates/raw/src/vendor/kodak.rs +++ b/crates/raw/src/vendor/kodak.rs @@ -115,6 +115,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/mef.rs b/crates/raw/src/vendor/mef.rs index a879f59f7..207bc8871 100644 --- a/crates/raw/src/vendor/mef.rs +++ b/crates/raw/src/vendor/mef.rs @@ -122,6 +122,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), + pixel_aspect: 1.0, }; img.validate_for(mode)?; Ok(img) diff --git a/crates/raw/src/vendor/nef.rs b/crates/raw/src/vendor/nef.rs index f73183072..5076c6df9 100644 --- a/crates/raw/src/vendor/nef.rs +++ b/crates/raw/src/vendor/nef.rs @@ -175,6 +175,16 @@ fn measured_body(model: &str, w: usize, h: usize, bits: u32) -> Option<&'static MEASURED_BODIES.iter().find(|b| b.model == model.trim() && b.size == (w, h) && b.bits == bits) } +/// Pixel height over width from the raw IFD's resolution tags: the D1X and D1H write 600 x 300 per inch for a +/// 4028 x 1324 frame, rows twice as tall as the columns are wide. Only ratios from 1.5 to 4 count (a frame +/// squashed 3:1 or 2:1), the rest is rounding or a stray tag. +pub(crate) fn pixel_aspect_of(raw: &Ifd) -> f32 { + match (raw.f64(t::X_RESOLUTION), raw.f64(t::Y_RESOLUTION)) { + (Some(x), Some(y)) if x > 0.0 && y > 0.0 && (1.5..=4.0).contains(&(x / y)) => (x / y).round() as f32, + _ => 1.0, + } +} + fn raw_ifd(tiff: &Tiff) -> Option<&Ifd> { tiff.all_ifds() .into_iter() @@ -268,9 +278,10 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { Some(&MeasuredBody { crop: (x, y, cw, ch), .. }) if x + cw <= active_w && y + ch <= h => Rect::new(x, y, cw, ch), _ => default_crop(mn.as_ref(), active_w, h), }; + let pixel_aspect = pixel_aspect_of(raw); let mut metadata = lightcraft_meta::from_tiff(&tiff); metadata.width = Some(crop.width as u32); - metadata.height = Some(crop.height as u32); + metadata.height = Some(crate::stretched_height(crop.height, pixel_aspect) as u32); let img = RawImage { format: RawFormat::Nef, width: w, @@ -290,6 +301,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect, // Nikon's in-file distortion correction, about the optical centre (which follows the sensor shift) opcodes: OpcodeLists { list3: super::nef_lens::distortion(&model, raw, Rect::new(0, 0, active_w, h), crop).into_iter().collect(), diff --git a/crates/raw/src/vendor/orf.rs b/crates/raw/src/vendor/orf.rs index 6672c8655..92caeec26 100644 --- a/crates/raw/src/vendor/orf.rs +++ b/crates/raw/src/vendor/orf.rs @@ -334,6 +334,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/pef.rs b/crates/raw/src/vendor/pef.rs index de805d688..9b7d92bc7 100644 --- a/crates/raw/src/vendor/pef.rs +++ b/crates/raw/src/vendor/pef.rs @@ -335,6 +335,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/raf.rs b/crates/raw/src/vendor/raf.rs index 0ff69cd19..42ed29ce8 100644 --- a/crates/raw/src/vendor/raf.rs +++ b/crates/raw/src/vendor/raf.rs @@ -212,6 +212,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists { list3: super::raf_lens::corrections(&camera_name(bytes), &ifd, active), ..Default::default() }, metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/rw2.rs b/crates/raw/src/vendor/rw2.rs index d883bd3d1..7ebee5916 100644 --- a/crates/raw/src/vendor/rw2.rs +++ b/crates/raw/src/vendor/rw2.rs @@ -614,6 +614,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists { list3: distortion(ifd0, active).into_iter().collect(), ..Default::default() }, metadata, lens_metadata: Default::default(), diff --git a/crates/raw/src/vendor/srw.rs b/crates/raw/src/vendor/srw.rs index 7d5a70aaf..b22faeec0 100644 --- a/crates/raw/src/vendor/srw.rs +++ b/crates/raw/src/vendor/srw.rs @@ -184,6 +184,7 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode) -> Result { color: ColorData::default(), wb_multipliers: wb, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/tests/dng.rs b/crates/raw/tests/dng.rs index 921e8900a..dddafd5d0 100644 --- a/crates/raw/tests/dng.rs +++ b/crates/raw/tests/dng.rs @@ -59,6 +59,7 @@ fn synthetic(w: usize, h: usize, cfa: Option, cpp: usize) -> RawImage { color, wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata, lens_metadata: Default::default(), diff --git a/crates/raw/tests/lossy_dng.rs b/crates/raw/tests/lossy_dng.rs index d2022b382..3b7480ff2 100644 --- a/crates/raw/tests/lossy_dng.rs +++ b/crates/raw/tests/lossy_dng.rs @@ -38,6 +38,7 @@ fn mosaic(w: usize, h: usize) -> RawImage { opcodes: OpcodeLists::default(), metadata: Default::default(), lens_metadata: Default::default(), + pixel_aspect: 1.0, } } diff --git a/crates/raw/tests/robust.rs b/crates/raw/tests/robust.rs index e08c86474..323149fda 100644 --- a/crates/raw/tests/robust.rs +++ b/crates/raw/tests/robust.rs @@ -24,6 +24,7 @@ fn sample(comp: DngCompression) -> Vec { color: ColorData::default(), wb_multipliers: None, linearized: false, + pixel_aspect: 1.0, opcodes: OpcodeLists::default(), metadata: Default::default(), lens_metadata: Default::default(), @@ -165,6 +166,7 @@ fn non_finite_black_levels_and_samples_develop() { opcodes: OpcodeLists::default(), metadata: Default::default(), lens_metadata: Default::default(), + pixel_aspect: 1.0, }; for img in [raw.clone(), lightcraft_raw::decode(&lightcraft_raw::write_dng(&raw, &DngWriteOptions::default()).unwrap()).unwrap()] { let rgb = img.develop(Method::Ahd).unwrap();