From 2c7d98b5e24c71d4df2fd24f090dc0877c4d69c8 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 27 Oct 2026 12:46:49 +0000 Subject: [PATCH] refactor(raw): separate DNG metadata and opcode parsing --- crates/raw/src/dngwrite.rs | 92 +++++----- crates/raw/src/opcodes.rs | 243 +++++++++++++++----------- crates/raw/src/tiffraw.rs | 2 +- crates/raw/tests/it/crx_properties.rs | 76 +++++--- 4 files changed, 246 insertions(+), 167 deletions(-) diff --git a/crates/raw/src/dngwrite.rs b/crates/raw/src/dngwrite.rs index fae6015b6..bdafb5865 100644 --- a/crates/raw/src/dngwrite.rs +++ b/crates/raw/src/dngwrite.rs @@ -284,48 +284,8 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { if let Some(v) = c.baseline_sharpness { ifd.set(t::BASELINE_SHARPNESS, rat_vec(&[v])); } - // the source's own profile look travels with its data (conversions, smart previews, merges) - let p = &c.profile; - let table_tags = |ifd: &mut IfdBuilder, table: &crate::profile::HsvTable, dims: u16, enc: u16| { - ifd.set(dims, Value::Long(vec![table.hue_divisions as u32, table.sat_divisions as u32, table.val_divisions as u32])); - if table.srgb_value { - ifd.set(enc, Value::Long(vec![1])); - } - }; - let flat = |table: &crate::profile::HsvTable| Value::Float(table.data.iter().flatten().copied().collect()); - match &p.hue_sat_map { - [Some(a), b] => { - table_tags(&mut ifd, a, t::PROFILE_HUE_SAT_MAP_DIMS, t::PROFILE_HUE_SAT_MAP_ENCODING); - ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_1, flat(a)); - if let Some(b) = - b.as_ref().filter(|b| (b.hue_divisions, b.sat_divisions, b.val_divisions) == (a.hue_divisions, a.sat_divisions, a.val_divisions)) - { - ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_2, flat(b)); - } - } - [None, Some(b)] => { - table_tags(&mut ifd, b, t::PROFILE_HUE_SAT_MAP_DIMS, t::PROFILE_HUE_SAT_MAP_ENCODING); - ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_1, flat(b)); - } - [None, None] => {} - } - if let Some(l) = &p.look_table { - table_tags(&mut ifd, l, t::PROFILE_LOOK_TABLE_DIMS, t::PROFILE_LOOK_TABLE_ENCODING); - ifd.set(t::PROFILE_LOOK_TABLE_DATA, flat(l)); - } - if let Some(curve) = &p.tone_curve { - ifd.set(t::PROFILE_TONE_CURVE, Value::Float(curve.points.iter().flatten().copied().collect())); - } - if let Some(map) = &p.gain_table_map { - // the raw IFD is IFD 0 here, where both tag versions are valid - let (version2, bytes) = map.to_bytes(opts.order); - ifd.set(if version2 { t::PROFILE_GAIN_TABLE_MAP_2 } else { t::PROFILE_GAIN_TABLE_MAP }, Value::Undefined(bytes)); - } - for (list, tag) in [(&raw.opcodes.list1, t::OPCODE_LIST_1), (&raw.opcodes.list2, t::OPCODE_LIST_2), (&raw.opcodes.list3, t::OPCODE_LIST_3)] { - if !list.is_empty() { - ifd.set(tag, Value::Undefined(opcodes::write_list(list))); - } - } + write_profile_tags(&mut ifd, raw, opts.order); + write_opcode_tags(&mut ifd, raw); match (&raw.data, opts.compression) { (RawData::F32(v), DngCompression::Deflate { tile, half }) => { @@ -495,6 +455,54 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result> { Ok(TiffWriter::new(opts.order, false).write(&[ifd])?) } +fn write_profile_tags(ifd: &mut IfdBuilder, raw: &RawImage, order: ByteOrder) { + // The source's own profile look travels with its data (conversions, smart previews, merges). + let profile = &raw.color.profile; + let table_tags = |ifd: &mut IfdBuilder, table: &crate::profile::HsvTable, dims: u16, enc: u16| { + ifd.set(dims, Value::Long(vec![table.hue_divisions as u32, table.sat_divisions as u32, table.val_divisions as u32])); + if table.srgb_value { + ifd.set(enc, Value::Long(vec![1])); + } + }; + let flat = |table: &crate::profile::HsvTable| Value::Float(table.data.iter().flatten().copied().collect()); + match &profile.hue_sat_map { + [Some(first), second] => { + table_tags(ifd, first, t::PROFILE_HUE_SAT_MAP_DIMS, t::PROFILE_HUE_SAT_MAP_ENCODING); + ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_1, flat(first)); + if let Some(second) = second.as_ref().filter(|second| { + (second.hue_divisions, second.sat_divisions, second.val_divisions) == (first.hue_divisions, first.sat_divisions, first.val_divisions) + }) { + ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_2, flat(second)); + } + } + [None, Some(table)] => { + table_tags(ifd, table, t::PROFILE_HUE_SAT_MAP_DIMS, t::PROFILE_HUE_SAT_MAP_ENCODING); + ifd.set(t::PROFILE_HUE_SAT_MAP_DATA_1, flat(table)); + } + [None, None] => {} + } + if let Some(table) = &profile.look_table { + table_tags(ifd, table, t::PROFILE_LOOK_TABLE_DIMS, t::PROFILE_LOOK_TABLE_ENCODING); + ifd.set(t::PROFILE_LOOK_TABLE_DATA, flat(table)); + } + if let Some(curve) = &profile.tone_curve { + ifd.set(t::PROFILE_TONE_CURVE, Value::Float(curve.points.iter().flatten().copied().collect())); + } + if let Some(map) = &profile.gain_table_map { + // The raw IFD is IFD 0 here, where both tag versions are valid. + let (version2, bytes) = map.to_bytes(order); + ifd.set(if version2 { t::PROFILE_GAIN_TABLE_MAP_2 } else { t::PROFILE_GAIN_TABLE_MAP }, Value::Undefined(bytes)); + } +} + +fn write_opcode_tags(ifd: &mut IfdBuilder, raw: &RawImage) { + for (list, tag) in [(&raw.opcodes.list1, t::OPCODE_LIST_1), (&raw.opcodes.list2, t::OPCODE_LIST_2), (&raw.opcodes.list3, t::OPCODE_LIST_3)] { + if !list.is_empty() { + ifd.set(tag, Value::Undefined(opcodes::write_list(list))); + } + } +} + /// Exif `LightSource` code of a white point the rendered DNG writer knows (D65 = 21, D50 = 23). fn illuminant_of(white: lightcraft_color::Xy) -> u16 { if (white.x - lightcraft_color::D50.x).abs() < 1e-3 && (white.y - lightcraft_color::D50.y).abs() < 1e-3 { 23 } else { 21 } diff --git a/crates/raw/src/opcodes.rs b/crates/raw/src/opcodes.rs index df85e2703..f7750aaac 100644 --- a/crates/raw/src/opcodes.rs +++ b/crates/raw/src/opcodes.rs @@ -253,6 +253,9 @@ impl Rd<'_> { self.p += 8; Some(v) } + fn finite_f64(&mut self) -> Option { + self.f64().filter(|v| v.is_finite()) + } fn f32(&mut self) -> Option { let v = f32::from_be_bytes(self.d.get(self.p..self.p + 4)?.try_into().ok()?); self.p += 4; @@ -276,109 +279,126 @@ impl Rd<'_> { } fn parse_one(id: u32, flags: u32, params: &[u8]) -> Option { - let mut r = Rd { d: params, p: 0 }; - let f = |r: &mut Rd| r.f64().filter(|v| v.is_finite()); - Some(match id { + let mut reader = Rd { d: params, p: 0 }; + let parsed = match id { + 1 | 2 | 3 | 14 => parse_warp(id, flags, &mut reader)?, + 4 => Opcode::FixBadPixelsConstant { constant: reader.u32()?, bayer_phase: reader.u32()? }, + 5 => parse_bad_pixel_list(&mut reader)?, + 6 => Opcode::TrimBounds { top: reader.u32()?, left: reader.u32()?, bottom: reader.u32()?, right: reader.u32()? }, + 7..=13 => parse_area_opcode(id, &mut reader)?, + _ => Opcode::Unknown { id, flags, params: params.to_vec() }, + }; + Some(parsed) +} + +fn parse_warp(id: u32, flags: u32, reader: &mut Rd<'_>) -> Option { + match id { 1 => { - let n = r.u32()? as usize; - if n == 0 || n > 4 { - return None; - } - let mut planes = Vec::with_capacity(n); - for _ in 0..n { - planes.push([f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?]); - } - Opcode::WarpRectilinear { planes, center: [f(&mut r)?, f(&mut r)?] } + let planes = parse_planes(reader, 4, |r| { + Some([r.finite_f64()?, r.finite_f64()?, r.finite_f64()?, r.finite_f64()?, r.finite_f64()?, r.finite_f64()?]) + })?; + Some(Opcode::WarpRectilinear { planes, center: [reader.finite_f64()?, reader.finite_f64()?] }) } 2 => { - let n = r.u32()? as usize; - if n == 0 || n > 4 { - return None; - } - let mut planes = Vec::with_capacity(n); - for _ in 0..n { - planes.push([f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?]); - } - Opcode::WarpFisheye { planes, center: [f(&mut r)?, f(&mut r)?] } + let planes = parse_planes(reader, 4, |r| Some([r.finite_f64()?, r.finite_f64()?, r.finite_f64()?, r.finite_f64()?]))?; + Some(Opcode::WarpFisheye { planes, center: [reader.finite_f64()?, reader.finite_f64()?] }) } - 14 => { - let n = r.u32()? as usize; - // N sets of 19 doubles, then the centre (2 doubles) and reciprocalRadial (LONG); nothing else - if n == 0 || n > WR2_MAX_PLANES || params.len() != 4 + n * WR2_LEN * 8 + 16 + 4 { - return None; - } - let mut planes = Vec::with_capacity(n); - for _ in 0..n { - let mut k = [0.0; WR2_LEN]; - for v in k.iter_mut() { - *v = f(&mut r).filter(|v| v.abs() <= WR2_MAX_COEF)?; - } - // the spec requires 0 <= min_valid_radius < max_valid_radius <= 1 - if !(k[17] >= 0.0 && k[17] < k[18] && k[18] <= 1.0) { - return None; - } - planes.push(k); - } - let center = [f(&mut r).filter(|v| v.abs() <= 4.0)?, f(&mut r).filter(|v| v.abs() <= 4.0)?]; - Opcode::WarpRectilinear2 { planes, center, reciprocal: r.u32()? != 0, optional: flags & 1 != 0 } + 3 => Some(Opcode::FixVignetteRadial { + k: [reader.finite_f64()?, reader.finite_f64()?, reader.finite_f64()?, reader.finite_f64()?, reader.finite_f64()?], + center: [reader.finite_f64()?, reader.finite_f64()?], + }), + 14 => parse_warp_rectilinear2(flags, reader), + _ => None, + } +} + +fn parse_planes(reader: &mut Rd<'_>, max: usize, mut parse: impl FnMut(&mut Rd<'_>) -> Option<[f64; N]>) -> Option> { + let count = reader.u32()? as usize; + if count == 0 || count > max { + return None; + } + (0..count).map(|_| parse(reader)).collect() +} + +fn parse_warp_rectilinear2(flags: u32, reader: &mut Rd<'_>) -> Option { + let count = reader.u32()? as usize; + // N sets of 19 doubles, then the centre (2 doubles) and reciprocalRadial (LONG); nothing else. + if count == 0 || count > WR2_MAX_PLANES || reader.remaining() != count * WR2_LEN * 8 + 16 + 4 { + return None; + } + let mut planes = Vec::with_capacity(count); + for _ in 0..count { + let mut coefficients = [0.0; WR2_LEN]; + for value in &mut coefficients { + *value = reader.finite_f64().filter(|value| value.abs() <= WR2_MAX_COEF)?; } - 3 => Opcode::FixVignetteRadial { k: [f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?, f(&mut r)?], center: [f(&mut r)?, f(&mut r)?] }, - 4 => Opcode::FixBadPixelsConstant { constant: r.u32()?, bayer_phase: r.u32()? }, - 5 => { - let bayer_phase = r.u32()?; - let (np, nr) = (r.u32()? as usize, r.u32()? as usize); - if np.saturating_mul(8).saturating_add(nr.saturating_mul(16)) > r.remaining() { - return None; - } - let points = (0..np).map(|_| Some((r.u32()?, r.u32()?))).collect::>()?; - let rects = (0..nr).map(|_| Some([r.u32()?, r.u32()?, r.u32()?, r.u32()?])).collect::>()?; - Opcode::FixBadPixelsList { bayer_phase, points, rects } + if !(coefficients[17] >= 0.0 && coefficients[17] < coefficients[18] && coefficients[18] <= 1.0) { + return None; } - 6 => Opcode::TrimBounds { top: r.u32()?, left: r.u32()?, bottom: r.u32()?, right: r.u32()? }, + planes.push(coefficients); + } + let center = [reader.finite_f64().filter(|value| value.abs() <= 4.0)?, reader.finite_f64().filter(|value| value.abs() <= 4.0)?]; + Some(Opcode::WarpRectilinear2 { planes, center, reciprocal: reader.u32()? != 0, optional: flags & 1 != 0 }) +} + +fn parse_bad_pixel_list(reader: &mut Rd<'_>) -> Option { + let bayer_phase = reader.u32()?; + let (point_count, rect_count) = (reader.u32()? as usize, reader.u32()? as usize); + if point_count.saturating_mul(8).saturating_add(rect_count.saturating_mul(16)) > reader.remaining() { + return None; + } + let points = (0..point_count).map(|_| Some((reader.u32()?, reader.u32()?))).collect::>()?; + let rects = (0..rect_count).map(|_| Some([reader.u32()?, reader.u32()?, reader.u32()?, reader.u32()?])).collect::>()?; + Some(Opcode::FixBadPixelsList { bayer_phase, points, rects }) +} + +fn parse_area_opcode(id: u32, reader: &mut Rd<'_>) -> Option { + let area = reader.area()?; + match id { 7 => { - let area = r.area()?; - let n = r.u32()? as usize; - if n == 0 || n > 65536 || n * 2 > r.remaining() { + let count = reader.u32()? as usize; + if count == 0 || count > 65536 || count * 2 > reader.remaining() { return None; } - Opcode::MapTable { area, table: (0..n).map(|_| r.u16()).collect::>()? } + Some(Opcode::MapTable { area, table: (0..count).map(|_| reader.u16()).collect::>()? }) } 8 => { - let area = r.area()?; - let deg = r.u32()? as usize; - if deg > 8 { + let degree = reader.u32()? as usize; + if degree > 8 { return None; } - Opcode::MapPolynomial { area, coefficients: (0..=deg).map(|_| f(&mut r)).collect::>()? } + Some(Opcode::MapPolynomial { area, coefficients: (0..=degree).map(|_| reader.finite_f64()).collect::>()? }) } - 9 => { - let area = r.area()?; - let (pv, ph) = (r.u32()?, r.u32()?); - let spacing = [f(&mut r)?, f(&mut r)?]; - let origin = [f(&mut r)?, f(&mut r)?]; - let mp = r.u32()?; - let n = (pv as usize).checked_mul(ph as usize)?.checked_mul(mp as usize)?; - if n == 0 || n * 4 > r.remaining() { - return None; - } - let gains = (0..n).map(|_| r.f32()).collect::>()?; - Opcode::GainMap { area, points_v: pv, points_h: ph, spacing, origin, map_planes: mp, gains } - } - 10..=13 => { - let area = r.area()?; - let n = r.u32()? as usize; - if n * 4 > r.remaining() { - return None; - } - let v: Vec = (0..n).map(|_| r.f32()).collect::>()?; - match id { - 10 => Opcode::DeltaPerRow { area, deltas: v }, - 11 => Opcode::DeltaPerColumn { area, deltas: v }, - 12 => Opcode::ScalePerRow { area, scales: v }, - _ => Opcode::ScalePerColumn { area, scales: v }, - } - } - _ => Opcode::Unknown { id, flags, params: params.to_vec() }, + 9 => parse_gain_map(area, reader), + 10..=13 => parse_delta_scale(id, area, reader), + _ => None, + } +} + +fn parse_gain_map(area: Area, reader: &mut Rd<'_>) -> Option { + let (points_v, points_h) = (reader.u32()?, reader.u32()?); + let spacing = [reader.finite_f64()?, reader.finite_f64()?]; + let origin = [reader.finite_f64()?, reader.finite_f64()?]; + let map_planes = reader.u32()?; + let count = (points_v as usize).checked_mul(points_h as usize)?.checked_mul(map_planes as usize)?; + if count == 0 || count * 4 > reader.remaining() { + return None; + } + let gains = (0..count).map(|_| reader.f32()).collect::>()?; + Some(Opcode::GainMap { area, points_v, points_h, spacing, origin, map_planes, gains }) +} + +fn parse_delta_scale(id: u32, area: Area, reader: &mut Rd<'_>) -> Option { + let count = reader.u32()? as usize; + if count * 4 > reader.remaining() { + return None; + } + let values: Vec = (0..count).map(|_| reader.f32()).collect::>()?; + Some(match id { + 10 => Opcode::DeltaPerRow { area, deltas: values }, + 11 => Opcode::DeltaPerColumn { area, deltas: values }, + 12 => Opcode::ScalePerRow { area, scales: values }, + _ => Opcode::ScalePerColumn { area, scales: values }, }) } @@ -508,19 +528,29 @@ fn area_iter(a: &Area, w: usize, h: usize, cpp: usize) -> impl Iterator f32 { - let (pv, ph) = (points_v as usize, points_h as usize); - let mp = map_planes as usize; - let p = plane.min(mp - 1); - let fy = if spacing[0] > 0.0 { ((rv - origin[0]) / spacing[0]).clamp(0.0, (pv - 1) as f64) } else { 0.0 }; - let fx = if spacing[1] > 0.0 { ((rh - origin[1]) / spacing[1]).clamp(0.0, (ph - 1) as f64) } else { 0.0 }; - let (y0, x0) = (fy.floor() as usize, fx.floor() as usize); - let (y1, x1) = ((y0 + 1).min(pv - 1), (x0 + 1).min(ph - 1)); - let (ty, tx) = ((fy - y0 as f64) as f32, (fx - x0 as f64) as f32); - let g = |y: usize, x: usize| gains[(y * ph + x) * mp + p]; - let top = g(y0, x0) + (g(y0, x1) - g(y0, x0)) * tx; - let bot = g(y1, x0) + (g(y1, x1) - g(y1, x0)) * tx; - top + (bot - top) * ty +struct GainMapGrid<'a> { + points: [usize; 2], + spacing: [f64; 2], + origin: [f64; 2], + planes: usize, + gains: &'a [f32], +} + +impl GainMapGrid<'_> { + fn sample_at(&self, position: [f64; 2], plane: usize) -> f32 { + let [pv, ph] = self.points; + let mp = self.planes; + let p = plane.min(mp - 1); + let fy = if self.spacing[0] > 0.0 { ((position[0] - self.origin[0]) / self.spacing[0]).clamp(0.0, (pv - 1) as f64) } else { 0.0 }; + let fx = if self.spacing[1] > 0.0 { ((position[1] - self.origin[1]) / self.spacing[1]).clamp(0.0, (ph - 1) as f64) } else { 0.0 }; + let (y0, x0) = (fy.floor() as usize, fx.floor() as usize); + let (y1, x1) = ((y0 + 1).min(pv - 1), (x0 + 1).min(ph - 1)); + let (ty, tx) = ((fy - y0 as f64) as f32, (fx - x0 as f64) as f32); + let g = |y: usize, x: usize| self.gains[(y * ph + x) * mp + p]; + let top = g(y0, x0) + (g(y0, x1) - g(y0, x0)) * tx; + let bot = g(y1, x0) + (g(y1, x1) - g(y1, x0)) * tx; + top + (bot - top) * ty + } } /// Radius normaliser: distance from the centre to the farthest image corner. @@ -556,10 +586,17 @@ pub fn apply_list(list: &[Opcode], buf: &mut [f32], w: usize, h: usize, cpp: usi } } Opcode::GainMap { area, points_v, points_h, spacing, origin, map_planes, gains } => { + let grid = GainMapGrid { + points: [*points_v as usize, *points_h as usize], + spacing: *spacing, + origin: *origin, + planes: *map_planes as usize, + gains, + }; for (x, y, p) in area_iter(area, w, h, cpp) { let rv = y as f64 / h as f64; let rh = x as f64 / w as f64; - let g = gain_at(*points_v, *points_h, *spacing, *origin, *map_planes, gains, rv, rh, p - area.plane as usize); + let g = grid.sample_at([rv, rh], p - area.plane as usize); buf[(y * w + x) * cpp + p] *= g; } } diff --git a/crates/raw/src/tiffraw.rs b/crates/raw/src/tiffraw.rs index 57878b881..b6a1971ce 100644 --- a/crates/raw/src/tiffraw.rs +++ b/crates/raw/src/tiffraw.rs @@ -1,6 +1,6 @@ //! Reading the pixel data of a TIFF IFD (strips or tiles; uncompressed, lossless/lossy JPEG, Deflate, JPEG XL), in parallel. -use crate::unpack::*; +use crate::unpack::{f16_to_f32, f24_to_f32, inflate, read_u16s, undo_diff_u16, undo_float_predictor, unpack_lsb, unpack_msb, unpack_words32_msb}; use crate::{MAX_SAMPLES, RawData, RawError, Result, ljpeg}; use lightcraft_tiff::image::{Chunk, ImageInfo, chunk_bytes}; use lightcraft_tiff::{ByteOrder, tags::compression as comp}; diff --git a/crates/raw/tests/it/crx_properties.rs b/crates/raw/tests/it/crx_properties.rs index 804970de4..ec61aa3b8 100644 --- a/crates/raw/tests/it/crx_properties.rs +++ b/crates/raw/tests/it/crx_properties.rs @@ -632,20 +632,23 @@ fn lossless_file((width, height): (usize, usize), values: &[i32]) -> Fixture { /// Constant-coefficient planes for one tile: LL3 flat at `ll3`, band `mid_band.0` flat at `mid_band.1`, /// everything else zero. Encoded at the given level count with the given quant/gain/base parameters. -fn constant_band_planes( - plane_w: usize, - plane_h: usize, +struct ConstantBandPlaneConfig<'a> { + plane_size: (usize, usize), levels: u8, ll3: i32, mid_band: Option<(usize, i32)>, quant: u8, gains: [u32; 10], bases: [u32; 10], - qp: Option<&[i32]>, + qp: Option<&'a [i32]>, qp_padding: usize, - before: bool, - after: bool, -) -> (usize, Vec) { + boundary_bands: (bool, bool), +} + +fn constant_band_planes(config: ConstantBandPlaneConfig<'_>) -> (usize, Vec) { + let ConstantBandPlaneConfig { plane_size: (plane_w, plane_h), levels, ll3, mid_band, quant, gains, bases, qp, qp_padding, boundary_bands } = + config; + let (before, after) = boundary_bands; let planes: [Vec>; 4] = if levels == 0 { let mut encoded: [Vec>; 4] = Default::default(); for bands in encoded.iter_mut() { @@ -681,7 +684,18 @@ fn constant_band_planes( /// A 16×16 three-level C-RAW file built from constant bands at the given uniform quant value. fn constant_bands_file(ll3: i32, mid_band: Option<(usize, i32)>, quant: u8) -> Fixture { - let (header_size, sample) = constant_band_planes(8, 8, 3, ll3, mid_band, quant, [0; 10], [0; 10], None, 0, false, false); + let (header_size, sample) = constant_band_planes(ConstantBandPlaneConfig { + plane_size: (8, 8), + levels: 3, + ll3, + mid_band, + quant, + gains: [0; 10], + bases: [0; 10], + qp: None, + qp_padding: 0, + boundary_bands: (false, false), + }); let coding = cmp1(0x100, 16, 16, 16, 16, 3, header_size as u32); Fixture { file: wrap(&coding, 16, 16, &sample, None), expected: Vec::new() } } @@ -697,7 +711,18 @@ fn v200_mapped_file( bases: [u32; 10], ) -> Fixture { assert_eq!(map.len(), (width / 16) * (height / 4)); - let (header_size, sample) = constant_band_planes(width / 2, height / 2, 3, ll3, mid_band, 4, gains, bases, Some(map), 3, false, false); + let (header_size, sample) = constant_band_planes(ConstantBandPlaneConfig { + plane_size: (width / 2, height / 2), + levels: 3, + ll3, + mid_band, + quant: 4, + gains, + bases, + qp: Some(map), + qp_padding: 3, + boundary_bands: (false, false), + }); let coding = cmp1(0x200, width as u32, height as u32, width as u32, height as u32, 3, header_size as u32); Fixture { file: wrap(&coding, width as u32, height as u32, &sample, None), expected: Vec::new() } } @@ -811,20 +836,18 @@ proptest! { } *b = if i < 4 { base + 1 } else { base }; } - let (header_size, sample) = constant_band_planes( - width / 2, - height / 2, - 3, + let (header_size, sample) = constant_band_planes(ConstantBandPlaneConfig { + plane_size: (width / 2, height / 2), + levels: 3, ll3, - Some((4, mid)), - 4, + mid_band: Some((4, mid)), + quant: 4, gains, bases, - Some(&qp_values[..map_w * map_h]), - 3, - false, - false, - ); + qp: Some(&qp_values[..map_w * map_h]), + qp_padding: 3, + boundary_bands: (false, false), + }); let coding = cmp1(0x200, width as u32, height as u32, width as u32, height as u32, 3, header_size as u32); let file = wrap(&coding, width as u32, height as u32, &sample, None); let image = decode(&file).unwrap(); @@ -879,7 +902,18 @@ fn lossless_sample(value: i32) -> Vec { /// Raw sample bytes of a constant-band 16×16 C-RAW fixture. fn constant_bands_sample(ll3: i32, mid_band: Option<(usize, i32)>, quant: u8) -> Vec { - let (_, sample) = constant_band_planes(8, 8, 3, ll3, mid_band, quant, [0; 10], [0; 10], None, 0, false, false); + let (_, sample) = constant_band_planes(ConstantBandPlaneConfig { + plane_size: (8, 8), + levels: 3, + ll3, + mid_band, + quant, + gains: [0; 10], + bases: [0; 10], + qp: None, + qp_padding: 0, + boundary_bands: (false, false), + }); sample }