mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-14 22:42:46 +00:00
Preserve CFA anchor in shared raw metadata
This commit is contained in:
10 files changed
+44
-15
No files matched your search
@@ -721,7 +721,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
let scale_bits = if info.compression == 1 && out_bits < 14 && strip_len >= (w * h * 2) as u64 { 14 } else { out_bits };
|
||||
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => Cfa::bayer_static("RGGB"),
|
||||
};
|
||||
let default_black = if scale_bits >= 14 { 512.0 } else { 128.0 };
|
||||
|
||||
@@ -410,9 +410,13 @@ fn decode_ifd(bytes: &[u8], tiff: &Tiff, pixels: &Ifd, main: &Ifd, mode: Mode, f
|
||||
// sensor origin (the crop to the active area shifts it back), so move the anchor out by the area's offset
|
||||
// (DNGs that LightCraft wrote before its writer followed the spec carry the sensor-origin pattern; they are marked
|
||||
// by a bare "LightCraft" Software tag and keep their original reading)
|
||||
let at_origin = Cfa { width: cols, height: rows, pattern };
|
||||
let mut at_origin = Cfa { width: cols, height: rows, pattern, origin_x: 0, origin_y: 0 };
|
||||
let legacy_writer = [raw, ifd0].iter().any(|i| i.string(t::SOFTWARE).as_deref().map(str::trim) == Some("LightCraft"));
|
||||
Some(if legacy_writer { at_origin } else { at_origin.shifted(cols - active_area.x % cols, rows - active_area.y % rows) })
|
||||
if !legacy_writer {
|
||||
at_origin.origin_x = active_area.x;
|
||||
at_origin.origin_y = active_area.y;
|
||||
}
|
||||
Some(at_origin)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
@@ -78,7 +78,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
let (w, h) = (info.width as usize, info.height as usize);
|
||||
let n = w.checked_mul(h).filter(|n| *n > 0 && *n <= limits.samples()).ok_or(RawError::Limit("image too large"))?;
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => return Err(RawError::Unsupported("Erf raw without a 2x2 CFAPattern".into())),
|
||||
};
|
||||
let mn = maker_note(bytes, &tiff);
|
||||
|
||||
@@ -68,7 +68,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
let (w, h) = (info.width as usize, info.height as usize);
|
||||
let n = w.checked_mul(h).filter(|n| *n > 0 && *n <= limits.samples()).ok_or(RawError::Limit("image too large"))?;
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => return Err(RawError::Unsupported("Kodak raw without a 2x2 CFAPattern".into())),
|
||||
};
|
||||
let curve: Vec<u16> = raw
|
||||
|
||||
+14
-5
@@ -785,6 +785,11 @@ pub struct Cfa {
|
||||
pub height: usize,
|
||||
/// `width × height` colour indices, row-major, anchored at image pixel (0, 0).
|
||||
pub pattern: Vec<u8>,
|
||||
/// The sensor-space coordinate where this periodic pattern is anchored.
|
||||
#[serde(default)]
|
||||
pub origin_x: usize,
|
||||
#[serde(default)]
|
||||
pub origin_y: usize,
|
||||
}
|
||||
|
||||
impl Cfa {
|
||||
@@ -801,13 +806,13 @@ impl Cfa {
|
||||
})
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
(p.len() == 4).then_some(Cfa { width: 2, height: 2, pattern: p })
|
||||
(p.len() == 4).then_some(Cfa { width: 2, height: 2, pattern: p, origin_x: 0, origin_y: 0 })
|
||||
}
|
||||
/// A 2×2 Bayer layout named in code (`"RGGB"`, `"BGGR"`, `"GRBG"`, `"GBRG"`). Only for literal
|
||||
/// names (tested below); anything else falls back to RGGB instead of failing.
|
||||
#[cfg_attr(not(feature = "crw"), allow(dead_code))]
|
||||
pub(crate) fn bayer_static(s: &'static str) -> Cfa {
|
||||
Cfa::bayer(s).unwrap_or(Cfa { width: 2, height: 2, pattern: vec![0, 1, 1, 2] })
|
||||
Cfa::bayer(s).unwrap_or(Cfa { width: 2, height: 2, pattern: vec![0, 1, 1, 2], origin_x: 0, origin_y: 0 })
|
||||
}
|
||||
/// The Fujifilm X-Trans 6×6 layout (as commonly documented), anchored at (0, 0).
|
||||
pub fn xtrans() -> Cfa {
|
||||
@@ -822,16 +827,20 @@ impl Cfa {
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
Cfa { width: 6, height: 6, pattern }
|
||||
Cfa { width: 6, height: 6, pattern, origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
#[inline]
|
||||
pub fn color_at(&self, x: usize, y: usize) -> u8 {
|
||||
self.pattern[(y % self.height) * self.width + (x % self.width)]
|
||||
let relative = |v: usize, origin: usize, period: usize| {
|
||||
let (v, origin) = (v % period, origin % period);
|
||||
if v >= origin { v - origin } else { period - (origin - v) }
|
||||
};
|
||||
self.pattern[relative(y, self.origin_y, self.height) * self.width + relative(x, self.origin_x, self.width)]
|
||||
}
|
||||
/// The same pattern anchored at `(dx, dy)` of the current anchor (e.g. after cropping to an active area).
|
||||
pub fn shifted(&self, dx: usize, dy: usize) -> Cfa {
|
||||
let pattern = (0..self.height).flat_map(|y| (0..self.width).map(move |x| (x, y))).map(|(x, y)| self.color_at(x + dx, y + dy)).collect();
|
||||
Cfa { width: self.width, height: self.height, pattern }
|
||||
Cfa { width: self.width, height: self.height, pattern, origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
pub fn is_bayer(&self) -> bool {
|
||||
if self.width != 2 || self.height != 2 {
|
||||
|
||||
@@ -70,7 +70,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
let (w, h) = (info.width as usize, info.height as usize);
|
||||
let n = w.checked_mul(h).filter(|n| *n > 0 && *n <= limits.samples()).ok_or(RawError::Limit("image too large"))?;
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => return Err(RawError::Unsupported("Mef raw without a 2x2 CFAPattern".into())),
|
||||
};
|
||||
let preview = rgb_preview(&tiff, bytes);
|
||||
|
||||
@@ -281,7 +281,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
.filter(|v| v.len() >= 2 && v[0] > 0.1 && v[1] > 0.1 && v[0] < 10.0 && v[1] < 10.0)
|
||||
.map(|v| [v[0] as f32, 1.0, v[1] as f32]);
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => Cfa::bayer_static("RGGB"),
|
||||
};
|
||||
let model = ifd0.string(t::MODEL).unwrap_or_default();
|
||||
|
||||
@@ -195,7 +195,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
None => dark_trimmed(samples, w, hgt, black_point.as_deref().map(|v| v.iter().sum::<f64>() / 4.0), white),
|
||||
};
|
||||
let cfa = match (ifd0.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), ifd0.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
// Every body states its layout in the Exif `CFAPattern`, read from the first sample of the stored array, not
|
||||
// of the image area: the *ist D (origin 19, 11) and the 645D (80, 59) only fit unshifted. The 12-bit 10 MP
|
||||
// bodies and everything from the K-3 on say RGGB; the K-7 / K-5 / 645D generation says BGGR. Files without the
|
||||
|
||||
@@ -125,7 +125,9 @@ fn camera_name(b: &[u8]) -> String {
|
||||
/// The CFA: X-Trans from the (reversed) layout record, else RGGB.
|
||||
fn cfa(h: &Header) -> Cfa {
|
||||
match record(h, XTRANS_LAYOUT) {
|
||||
Some(l) if l.len() == 36 && l.iter().all(|&c| c <= 2) => Cfa { width: 6, height: 6, pattern: l.iter().rev().copied().collect() },
|
||||
Some(l) if l.len() == 36 && l.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 6, height: 6, pattern: l.iter().rev().copied().collect(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => Cfa::bayer_static("RGGB"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,9 @@ pub(crate) fn decode(bytes: &[u8], mode: Mode, limits: &Limits) -> Result<RawIma
|
||||
.filter(|&(l, tp, r, b)| l < r && tp < b && r <= w && b <= h)
|
||||
.map_or(Rect::new(0, 0, w, h), |(l, tp, r, b)| Rect::new(l, tp, r - l, b - tp));
|
||||
let cfa = match (raw.u64s(t::CFA_REPEAT_PATTERN_DIM).as_deref(), raw.bytes(t::CFA_PATTERN_EP)) {
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => Cfa { width: 2, height: 2, pattern: p.to_vec() },
|
||||
(Some([2, 2]), Some(p)) if p.len() == 4 && p.iter().all(|&c| c <= 2) => {
|
||||
Cfa { width: 2, height: 2, pattern: p.to_vec(), origin_x: 0, origin_y: 0 }
|
||||
}
|
||||
_ => return Err(RawError::Unsupported("Samsung raw without a 2x2 CFAPattern".into())),
|
||||
};
|
||||
// saturation levels and black levels (R, G1, G2, B), in the file's own words
|
||||
|
||||
Reference in new issue
Block a user