mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-28 20:17:18 -06:00
refactor(raw): separate DNG metadata and opcode parsing
This commit is contained in:
+50
-42
@@ -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
@@ -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,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};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user