Read DCP camera profiles: embedded in a DNG, or a .dcp beside the app

The first half of D20. dr-decode now finds a camera profile's HueSatMap
and LookTable in the order camera-profiles.md §4 gives: the profile a
DNG embeds, then a .dcp in the profiles directory whose
UniqueCameraModel names the body, then none. A .dcp brings its own
matrices, since its tables were measured against its forward matrix.

The HueSatMap is blended for the frame's colour temperature with the
same mired weight the matrices use, once per decode, and the result
rides on RawImage as profile_tables beside color_matrix, so every path
that renders a decoded file gets the same profile without a setter to
forget. Nothing applies the tables yet.

A profile whose embed policy allows copying can be written back out as
a .dcp (rawler's TIFF writer with the RC magic patched in), which is how
the library's 6D CR2s will get the Adobe Standard their DNGs carry. The
table type lives in dr-types because decode, pipeline and GPU all need
its layout. Tests read the library's 6D DNG when it is present.
This commit is contained in:
2026-10-02 22:38:06 -04:00
parent 05ac2416c6
commit f6a3f3f4e2
16 changed files with 1017 additions and 35 deletions
+30 -2
View File
@@ -16,6 +16,7 @@
//! second decoder can be put behind them without changing any of them
//! (FR-RAW-2). [`Rawler`] is the one that ships; [`default`] hands it out.
pub mod dcp;
mod decoder;
mod error;
mod locate;
@@ -137,8 +138,17 @@ pub struct RawImage {
/// carry on: calibrations and the as-shot neutral. `None` for a body the
/// decoder has no matrix for.
pub profile: Option<profile::CameraProfile>,
/// The body, as rawler cleans the names: what `Make`/`Model` say and what
/// the base-curve database matches on.
/// TRACES: FR-DEV-3e
/// The camera profile's HueSatMap and LookTable, resolved for this frame
/// (D20): embedded in the DNG, or from a matched `.dcp`. `None` renders
/// through the matrix alone.
///
/// Carried with the image, as `color_matrix` is, so that every path that
/// renders a decoded file renders it through the same profile without
/// having to be told — see camera-profiles.md §3.
pub profile_tables: Option<std::sync::Arc<dr_types::ProfileTables>>,
/// The body, as rawler cleans the names: what `Make`/`Model` say, and
/// what a `.dcp`'s `UniqueCameraModel` is matched against.
pub make: String,
pub model: String,
}
@@ -571,6 +581,23 @@ fn decode_unguarded(bytes: &[u8]) -> Result<RawImage, DecodeError> {
// is now structural, because there is only one interpolated matrix and
// both callers ask the same object for it.
let profile = profile::CameraProfile::extract(&image, &dng);
// TRACES: FR-DEV-3e
// The tables, and — where a `.dcp` supplies them — the matrices they were
// built against, which then stand in for the file's (D20).
let root = decoder
.ifd(rawler::decoders::WellKnownIFD::Root)
.ok()
.flatten();
let dcp::Resolved {
profile,
tables: profile_tables,
..
} = dcp::resolve(
profile,
root.as_deref(),
&image.camera.clean_make,
&image.camera.clean_model,
);
let color_matrix = profile.as_ref().and_then(|p| p.cam_to_srgb());
let wb_coeffs = sane_wb(
image.wb_coeffs,
@@ -661,6 +688,7 @@ fn decode_unguarded(bytes: &[u8]) -> Result<RawImage, DecodeError> {
color_matrix,
samples_per_pixel,
profile,
profile_tables,
make: image.camera.clean_make.clone(),
model: image.camera.clean_model.clone(),
})