mirror of
https://github.com/storytold/photocraft.git
synced 2026-10-19 02:05:19 +02:00
* CMYK documents display and export through their embedded CMYK profile A thread-local CmykSpace (photocraft_color::convert::with_cmyk_space) replaces the built-in coated CMYK while the CPU compositor, the GPU compositor's uploads and the PSD merged composite convert a document's CMYK pixels; GPU residents track the profile they were converted with. Refs #46 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * EXR/HDR are linear: tag imports linear sRGB, linearise exports Refs #46 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Colour-managed canvas: document profile -> monitor profile on GPU and CPU paths * engine display_color: CanvasDisplay per (document profile, mode, monitor), identity fast path when they match (sRGB on sRGB costs nothing); linear composites are stored sRGB-encoded in the 8-bit texture. canvas_lut now always carries the CM transform, with Proof Colors / Gamut Warning / 32-bit preview on top. * GPU canvas applies the display LUT whenever the transform isn't the identity (also for filter/proxy previews); the CPU canvas and Navigator transform the 8-bit composite. * Edit > Color Settings > Monitor Profile (auto|built-in|.icc). macOS 'auto' reads the main display's ICC profile at launch via osascript/AppKit (no unsafe); elsewhere sRGB. * Tests: P3 (hand-computed), Adobe RGB, ProPhoto, Gray Gamma 2.2, synthetic CMYK, linear, monitor setting; GPU+CPU canvas test via kittest (skips without Rgba32Float). Fixes #46 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * io: tag EXR/HDR linear in import_flat (adapt to the shared image_to_document) #65 moved flat decoding into image_to_document(name, img) so camera raws can reuse it; the linear-EXR tagging needs the file bytes for format detection, so it now runs in import_flat. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
77 lines
3.2 KiB
Rust
77 lines
3.2 KiB
Rust
//! Colour-managed canvas display cost on a 36 MP document (#46).
|
||
//!
|
||
//! ```sh
|
||
//! cargo run --release -p photocraft-engine --example bench_display -- [--size 7360x4912] [--reps 5]
|
||
//! ```
|
||
//!
|
||
//! Times, per profile: the composite (shared by every path), the CPU canvas conversion before
|
||
//! (`Buffer::to_rgba8`, no colour management) and after (`CanvasDisplay::to_rgba8`), the
|
||
//! per-frame display lookup (`display_signature`, what the GPU canvas checks each frame) and
|
||
//! building the 33³ GPU display LUT (once per profile/monitor change).
|
||
|
||
use std::time::Instant;
|
||
|
||
use photocraft_cms::Builtin;
|
||
use photocraft_color::{Color, ColorMode, SampleType};
|
||
use photocraft_doc::{Document, Size};
|
||
use photocraft_engine::Session;
|
||
|
||
fn arg(args: &[String], name: &str) -> Option<String> {
|
||
args.iter().position(|a| a == name).and_then(|i| args.get(i + 1).cloned())
|
||
}
|
||
|
||
fn median(mut v: Vec<f64>) -> f64 {
|
||
v.sort_by(|a, b| a.total_cmp(b));
|
||
v[v.len() / 2]
|
||
}
|
||
|
||
fn time(reps: usize, mut f: impl FnMut()) -> f64 {
|
||
median(
|
||
(0..reps)
|
||
.map(|_| {
|
||
let t = Instant::now();
|
||
f();
|
||
t.elapsed().as_secs_f64() * 1000.0
|
||
})
|
||
.collect(),
|
||
)
|
||
}
|
||
|
||
fn main() {
|
||
let args: Vec<String> = std::env::args().collect();
|
||
let (w, h) =
|
||
arg(&args, "--size").and_then(|s| s.split_once('x').map(|(a, b)| (a.parse().unwrap_or(7360), b.parse().unwrap_or(4912)))).unwrap_or((7360, 4912));
|
||
let reps: usize = arg(&args, "--reps").and_then(|v| v.parse().ok()).unwrap_or(5).max(1);
|
||
println!("document {w}×{h} ({:.1} MP), median of {reps}", (w * h) as f64 / 1e6);
|
||
println!("{:<22} {:>10} {:>14} {:>14} {:>12} {:>10}", "profile", "composite", "CPU before", "CPU after", "per frame", "LUT build");
|
||
for (name, profile, depth) in [
|
||
("sRGB (untagged)", None, SampleType::U8),
|
||
("Display P3", Some(Builtin::DisplayP3), SampleType::U8),
|
||
("ProPhoto", Some(Builtin::ProPhotoCompat), SampleType::U16),
|
||
("linear sRGB (EXR)", Some(Builtin::LinearSrgb), SampleType::F32),
|
||
] {
|
||
let mut d = Document::with_background("b", Size::new(w, h), ColorMode::Rgb, depth, Color::rgb(0.7, 0.4, 0.2));
|
||
d.icc_profile = profile.map(|b| b.profile().to_bytes());
|
||
let s = Session::new();
|
||
let mut buf = None;
|
||
let composite = time(reps, || buf = Some(photocraft_compose::flatten(&d)));
|
||
let buf = buf.expect("composited");
|
||
let before = time(reps, || {
|
||
std::hint::black_box(buf.to_rgba8());
|
||
});
|
||
let display = s.color.canvas_display(&d).expect("display");
|
||
let after = time(reps, || {
|
||
let d = s.color.canvas_display(&d).expect("display");
|
||
std::hint::black_box(if d.is_identity() { buf.to_rgba8() } else { d.to_rgba8(&buf) });
|
||
});
|
||
let frame = time(reps * 200, || {
|
||
std::hint::black_box(s.color.display_signature(&d));
|
||
});
|
||
let lut = time(reps, || {
|
||
std::hint::black_box(s.color.canvas_lut(&d, 33).expect("lut"));
|
||
});
|
||
let tag = if display.is_identity() { " (identity)" } else { "" };
|
||
println!("{:<22} {composite:>8.1}ms {before:>12.1}ms {after:>12.1}ms {:>10.1}µs {lut:>8.1}ms{tag}", name, frame * 1000.0);
|
||
}
|
||
}
|