refactor(raw): separate DNG metadata and opcode parsing

This commit is contained in:
Dylan M. Taylor
2026-10-28 03:24:18 +00:00
parent c1766c016b
commit 2c7d98b5e2
4 changed files with 246 additions and 167 deletions
+50 -42
View File
@@ -284,48 +284,8 @@ pub fn write_dng(raw: &RawImage, opts: &DngWriteOptions) -> Result<Vec<u8>> {
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<Vec<u8>> {
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 }
+140 -103
View File
@@ -253,6 +253,9 @@ impl Rd<'_> {
self.p += 8;
Some(v)
}
fn finite_f64(&mut self) -> Option<f64> {
self.f64().filter(|v| v.is_finite())
}
fn f32(&mut self) -> Option<f32> {
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<Opcode> {
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<Opcode> {
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<const N: usize>(reader: &mut Rd<'_>, max: usize, mut parse: impl FnMut(&mut Rd<'_>) -> Option<[f64; N]>) -> Option<Vec<[f64; N]>> {
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<Opcode> {
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::<Option<_>>()?;
let rects = (0..nr).map(|_| Some([r.u32()?, r.u32()?, r.u32()?, r.u32()?])).collect::<Option<_>>()?;
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<Opcode> {
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::<Option<_>>()?;
let rects = (0..rect_count).map(|_| Some([reader.u32()?, reader.u32()?, reader.u32()?, reader.u32()?])).collect::<Option<_>>()?;
Some(Opcode::FixBadPixelsList { bayer_phase, points, rects })
}
fn parse_area_opcode(id: u32, reader: &mut Rd<'_>) -> Option<Opcode> {
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::<Option<_>>()? }
Some(Opcode::MapTable { area, table: (0..count).map(|_| reader.u16()).collect::<Option<_>>()? })
}
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::<Option<_>>()? }
Some(Opcode::MapPolynomial { area, coefficients: (0..=degree).map(|_| reader.finite_f64()).collect::<Option<_>>()? })
}
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::<Option<_>>()?;
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<f32> = (0..n).map(|_| r.f32()).collect::<Option<_>>()?;
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<Opcode> {
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::<Option<_>>()?;
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<Opcode> {
let count = reader.u32()? as usize;
if count * 4 > reader.remaining() {
return None;
}
let values: Vec<f32> = (0..count).map(|_| reader.f32()).collect::<Option<_>>()?;
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<Item = (
.flat_map(move |y| (l..r.max(l)).step_by(a.col_pitch.max(1) as usize).flat_map(move |x| (p0..p1.max(p0)).map(move |p| (x, y, p))))
}
fn gain_at(points_v: u32, points_h: u32, spacing: [f64; 2], origin: [f64; 2], map_planes: u32, gains: &[f32], rv: f64, rh: f64, plane: usize) -> 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;
}
}
+1 -1
View File
@@ -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};
+55 -21
View File
@@ -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<u8>) {
boundary_bands: (bool, bool),
}
fn constant_band_planes(config: ConstantBandPlaneConfig<'_>) -> (usize, Vec<u8>) {
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<Vec<u8>>; 4] = if levels == 0 {
let mut encoded: [Vec<Vec<u8>>; 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<u8> {
/// 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<u8> {
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
}