mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
Color: freeform gradients keep CMYK inks when expanded, meshed or edited
Object > Expand and Object > Create Gradient Mesh mixed a freeform gradient's points in display RGB and turned the result back into the points' model with the profile-free formula. CMYK inks changed: points of C50 M20 Y0 K10 gave mesh points of C45 M24 Y0 K22. RGB points were stored as display sRGB, so with a working RGB space other than sRGB the mesh showed other colors. Points colored along CMYK stops, a point paint.freeform.splitLine added between CMYK points and a point paint.freeform.addPoint added without a color came out as RGB. Points that share a color model now mix in it: the C, M, Y and K values mix as inks, and Gray, RGB and Lab components mix as they are. Points in different models still mix in display RGB. Object > Expand and Object > Create Gradient Mesh store the result in the working RGB space, and in a CMYK document they store it as CMYK through the color settings, as paint.setFill stores an RGB color. Freeform::recolor and split_line mix with the new Color::lerp_keep_model, which vectorcraft_doc::blend::lerp_color now calls. Docs: docs/mcp.md, docs/target-app-parity.md.
This commit is contained in:
@@ -318,7 +318,7 @@ fn expand_fills(d: &mut Document, l: &Node, to: GradientTo) -> Option<Node> {
|
||||
/// fill's opacity, blend mode and effects other than geometry effects ([`fill_effects`]).
|
||||
fn gradient_art(d: &mut Document, l: &Node, path: &PathData, bounds: Rect, f: &FillLayer, g: &GradientPaint, to: GradientTo) -> Option<Node> {
|
||||
let content = if g.gradient.kind == GradientKind::Freeform {
|
||||
let m = GradientMesh::for_path_with(path, FREEFORM_MESH, FREEFORM_MESH, &*gradient_sampler(g, bounds))?;
|
||||
let m = GradientMesh::for_path_with(path, FREEFORM_MESH, FREEFORM_MESH, &*gradient_sampler(g, bounds, d.color_mode.model()))?;
|
||||
vec![Node::new(d.alloc_id(), NodeKind::Mesh(m))]
|
||||
} else {
|
||||
let frame = Frame::new(g, bounds);
|
||||
@@ -720,29 +720,24 @@ fn tile_art(d: &mut Document, l: &Node, f: &FillLayer, tiles: &[Node]) -> Option
|
||||
|
||||
/// The colour and opacity gradient fill `g` paints at a document point of an object whose box is
|
||||
/// `bounds`, as the canvas draws it (the end colours continue past the ends). Colours stay in the
|
||||
/// colour model their stops (or freeform points) share; display RGB where models differ.
|
||||
pub(crate) fn gradient_sampler(g: &GradientPaint, bounds: Rect) -> Box<dyn Fn(Point) -> (Color, f32) + '_> {
|
||||
/// color model their stops share; display RGB where models differ. Freeform points mix in the
|
||||
/// model they share ([`vectorcraft_color::Freeform::model_field`]); where their models differ, the
|
||||
/// RGB color they mix to is stored in `doc_model` (the document's model) the way `paint.setFill`
|
||||
/// stores an RGB color ([`super::paint::stored_color`]).
|
||||
pub(crate) fn gradient_sampler(g: &GradientPaint, bounds: Rect, doc_model: Model) -> Box<dyn Fn(Point) -> (Color, f32) + '_> {
|
||||
if g.gradient.kind == GradientKind::Freeform {
|
||||
let b = bounds.abs();
|
||||
let f = g.freeform_on(b);
|
||||
let model = shared_model(f.points.iter().map(|p| p.color));
|
||||
let field = f.field(spread_scale(b));
|
||||
let field = g.freeform_on(b).model_field(spread_scale(b));
|
||||
let store_in = (field.model().is_none() && doc_model != Model::Rgb).then_some(doc_model);
|
||||
return Box::new(move |p| {
|
||||
let ([r, g, b], a) = field.sample(p);
|
||||
let c = Color::rgb(r, g, b);
|
||||
(model.map_or(c, |m| c.in_model(m)), a)
|
||||
let (c, a) = field.sample(p);
|
||||
(super::paint::stored_color(store_in, c), a)
|
||||
});
|
||||
}
|
||||
let geom = g.resolve(bounds);
|
||||
Box::new(move |p| g.gradient.sample_with(geom.param_at(g.gradient.kind, p) as f32, lerp_color))
|
||||
}
|
||||
|
||||
/// The colour model all of `colors` share.
|
||||
fn shared_model(mut colors: impl Iterator<Item = Color>) -> Option<Model> {
|
||||
let m = colors.next()?.model();
|
||||
colors.all(|c| c.model() == m).then_some(m)
|
||||
}
|
||||
|
||||
/// Where the colour changes along a gradient between parameters `t0` and `t1`: both ends, every
|
||||
/// stop between them (in its own colour, so coincident stops keep a sharp edge) and every midpoint
|
||||
/// off the centre, as (parameter, colour, opacity) in order, with more between them where they
|
||||
|
||||
@@ -1394,6 +1394,7 @@ fn mesh_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut out = vec![];
|
||||
let mut index = None;
|
||||
let mut refused = None;
|
||||
let model = d.color_mode.model();
|
||||
for id in &roots {
|
||||
let Some(n) = d.node(*id).cloned() else { continue };
|
||||
let m = if let NodeKind::Image(im) = &n.kind {
|
||||
@@ -1413,7 +1414,7 @@ fn mesh_create(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
match n.appearance.fill_paint() {
|
||||
// The points take the colours the gradient paints there.
|
||||
Paint::Gradient(g) => {
|
||||
path.bounds().and_then(|b| GradientMesh::for_path_with(&path, rows, cols, &*gradient_sampler(&g, b))).map(|mut m| {
|
||||
path.bounds().and_then(|b| GradientMesh::for_path_with(&path, rows, cols, &*gradient_sampler(&g, b, model))).map(|mut m| {
|
||||
m.highlight(app, highlight);
|
||||
m
|
||||
})
|
||||
|
||||
@@ -301,7 +301,7 @@ fn new_color_model(s: &Session, p: &Value) -> Option<vectorcraft_color::cms::Mod
|
||||
/// `c` in `model` ([`new_color_model`]) when it is an RGB color, converted through the color
|
||||
/// settings (the working spaces and their intent) as Document Color Mode converts it. Other colors
|
||||
/// are returned as given.
|
||||
fn stored_color(model: Option<vectorcraft_color::cms::Model>, c: Color) -> Color {
|
||||
pub(crate) fn stored_color(model: Option<vectorcraft_color::cms::Model>, c: Color) -> Color {
|
||||
match (c, model) {
|
||||
(Color::Rgb { .. }, Some(m)) => {
|
||||
let cms = vectorcraft_color::cms::active();
|
||||
|
||||
Reference in New Issue
Block a user