mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-15 03:39:50 +00:00
Color management: documents behind the front one draw through their own profiles
With Window › Arrange › Tile or floating document windows, every document other than the front one was drawn through the profiles the front document is assigned, since the active color settings are process-wide. - cms::with_active puts other settings in place of the active ones on one thread, fast paths included, and puts them back after (also on a panic). cms::for_document gives the context a document's assigned profiles make, the same one each time for the same profiles and working settings. - Each pane renders its document's art through colormgmt::doc_cms, on the render worker and on the UI thread alike, and draws its paper, artboard colors and transparency grid through the same settings. - Loading or removing a profile redraws the documents assigned it, the ones behind the front one included. - The ink and separation tables keep the tables of up to four color settings, so documents with different CMYK profiles side by side do not rebuild them on each redraw. Placed documents inside a document behind the front one still draw through the front document's profiles.
This commit is contained in:
+47
-19
@@ -239,19 +239,29 @@ fn inks(c: &Color) -> [f32; 4] {
|
||||
let key = c.key();
|
||||
let cms = cms::active();
|
||||
SEPARATED.with(|s| {
|
||||
let (owner, seen) = &mut *s.borrow_mut();
|
||||
let kept = &mut *s.borrow_mut();
|
||||
let i = match kept.iter().position(|(owner, _)| Arc::ptr_eq(owner, &cms)) {
|
||||
Some(i) => i,
|
||||
None => {
|
||||
if kept.len() >= KEPT_TABLES {
|
||||
kept.remove(0);
|
||||
}
|
||||
kept.push((cms.clone(), HashMap::new()));
|
||||
kept.len() - 1
|
||||
}
|
||||
};
|
||||
let seen = &mut kept[i].1;
|
||||
// Large enough for the many colours of gradient meshes and blends, which otherwise cleared
|
||||
// it over and over and separated every colour through the profile again.
|
||||
if !owner.as_ref().is_some_and(|o| Arc::ptr_eq(o, &cms)) || seen.len() >= 1 << 16 {
|
||||
if seen.len() >= 1 << 16 {
|
||||
seen.clear();
|
||||
*owner = Some(cms.clone());
|
||||
}
|
||||
*seen.entry(key).or_insert_with(|| cms.to_cmyk(c, cms.settings().intent))
|
||||
})
|
||||
}
|
||||
|
||||
/// Separated colours, for the colour settings they were separated with.
|
||||
type Separated = (Option<Arc<Cms>>, HashMap<[u32; 5], [f32; 4]>);
|
||||
/// Separated colours, for each of the colour settings they were separated with, the newest last.
|
||||
type Separated = Vec<(Arc<Cms>, HashMap<[u32; 5], [f32; 4]>)>;
|
||||
|
||||
thread_local! {
|
||||
static SEPARATED: RefCell<Separated> = RefCell::default();
|
||||
@@ -276,31 +286,37 @@ impl InkLut {
|
||||
}
|
||||
}
|
||||
|
||||
/// A table built for the colour settings it was built with, and its generation (see [`cached`]).
|
||||
type Cached<T> = Mutex<Option<(Arc<Cms>, Arc<T>, u64)>>;
|
||||
/// Tables built for the colour settings they were built with, and their generations, the newest
|
||||
/// last (see [`cached`]).
|
||||
type Cached<T> = Mutex<Vec<(Arc<Cms>, Arc<T>, u64)>>;
|
||||
|
||||
/// How many tables each [`Cached`] keeps: documents drawn side by side through their own profiles
|
||||
/// don't make them again and again.
|
||||
const KEPT_TABLES: usize = 4;
|
||||
|
||||
/// The last table generation handed out: images made with a table are cached under its
|
||||
/// generation (see [`crate::images`]), and none is ever reused.
|
||||
static GENERATION: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
static DISPLAY: Cached<InkLut> = Mutex::new(None);
|
||||
static DISPLAY: Cached<InkLut> = Mutex::new(Vec::new());
|
||||
/// Screen colours → [`Ink::Cmy`] and [`Ink::K`] values (placed images).
|
||||
static SEPARATE: Cached<[ProofLut; 2]> = Mutex::new(None);
|
||||
static SEPARATE: Cached<[ProofLut; 2]> = Mutex::new(Vec::new());
|
||||
|
||||
/// The table in `slot` for the active colour settings, built by `build` when they changed, and
|
||||
/// its generation.
|
||||
/// The table in `slot` for the active colour settings, built by `build` when none is kept for
|
||||
/// them, and its generation.
|
||||
fn cached<T>(slot: &Cached<T>, build: impl FnOnce(&Cms) -> T) -> (Arc<T>, u64) {
|
||||
let cms = cms::active();
|
||||
let mut s = slot.lock().unwrap_or_else(|e| e.into_inner());
|
||||
match &*s {
|
||||
Some((owner, t, g)) if Arc::ptr_eq(owner, &cms) => (t.clone(), *g),
|
||||
_ => {
|
||||
let t = Arc::new(build(&cms));
|
||||
let g = GENERATION.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
*s = Some((cms, t.clone(), g));
|
||||
(t, g)
|
||||
}
|
||||
if let Some((_, t, g)) = s.iter().find(|(owner, _, _)| Arc::ptr_eq(owner, &cms)) {
|
||||
return (t.clone(), *g);
|
||||
}
|
||||
let t = Arc::new(build(&cms));
|
||||
let g = GENERATION.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if s.len() >= KEPT_TABLES {
|
||||
s.remove(0);
|
||||
}
|
||||
s.push((cms, t.clone(), g));
|
||||
(t, g)
|
||||
}
|
||||
|
||||
/// [`compose`] puts at least this many pixels in each band but the last.
|
||||
@@ -585,6 +601,18 @@ mod tests {
|
||||
assert_eq!(pm.data()[0].r, 204, "source pixels remain unchanged");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tables_of_two_color_settings_are_kept_side_by_side() {
|
||||
// Two documents drawn in turn through their own settings each keep their tables.
|
||||
let settings = || Arc::new(Cms::new(&cms::ColorSettings::default()).unwrap());
|
||||
let (a, b) = (settings(), settings());
|
||||
let tables = |c: &Arc<Cms>| cms::with_active(c.clone(), || (display_lut().1, separation().1));
|
||||
let (first_a, first_b) = (tables(&a), tables(&b));
|
||||
assert_ne!(first_a, first_b, "each settings get their own tables");
|
||||
assert_eq!(tables(&a), first_a, "the first settings' tables are not made again");
|
||||
assert_eq!(tables(&b), first_b, "the second settings' tables are not made again");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_ink_table_matches_the_profile() {
|
||||
let cms = cms::active();
|
||||
|
||||
Reference in New Issue
Block a user