Ogeon / palette

A Rust library for linear color calculations and conversion
Apache License 2.0
751 stars 60 forks source link

CMYK #6

Open flosse opened 8 years ago

flosse commented 8 years ago

It would be cool to be able to convert to an from CMYK :)

Ogeon commented 8 years ago

I have been holding off on device specific color spaces because of the hairy business around different devices. The values of CMYK depends on printers and the colors of their ink, which makes it particularly tricky, but I think I saw some "standard" formula somewhere that may be "good enough". Same for CMY. It's definitely worth investigating, since they are both very common. :)

Ogeon commented 8 years ago

Looks like there's no way to implement accurate CMYK without a color profile, since it depends on paper color, ink, etc. The only possibility without implementing color profile support is the naive implementation, where

black = 1 - max(red, green, blue)
cyan = (1 - red - black) / (1 - black), or 0 if black is 1
magenta = (1 - green - black) / (1 - black), or 0 if black is 1
yellow = (1 - blue - black) / (1 - black), or 0 if black is 1 

red = 1 - min(1, cyan * (1 - black) + black)
green = 1 - min(1, magenta * (1 - black) + black)
blue = 1 - min(1, yellow * (1 - black) + black) 

Some people may prefer to define their colors this way, so it may still be useful as an input interface, but the colors won't be accurate.

flosse commented 8 years ago

hm... I think something like black = 1 - max(red, green, blue) is not the solution we want. But having a stuct that can hold CMYK values is useful. More general, a generic type that can hold n different channels could allow to define custom spaces. Let's say you want to print something special, then you'd define the colors c, m, y, k but also e.g. two spot colors custom0 and custom1. And transforming from color space A to color space B should only be implemented if it is definite. In all other cases I'd offer a convert function where you can pass in your custom function. That would look like this (pseudocode):

let my_rgb = Rgb::new(0.5, 0.3, 0.7);
let my_foo: FooColor = my_rgb.convert::<FooColor>(|channels|{
  let a = some_calc(channels[0], channels[2]);
  let b = some_cals(channels[1], channels[2]);
  FooColor::new(a,b)
});

Then you can also offer official standard profiles and just do s.th. like

let my_cmyk = my_rgb.convert::<Cmyk>(Cmyk::Profiles::ISOUncoded_v2);

What do you think?

Ogeon commented 8 years ago

Yea, something like that is probably the way to go. The main problem with implementing the naive variant is that it can be mistaken for the accurate variant, so I feel like it should be avoided. I'll postpone this for the time being, until a concrete solution comes up, but it looks like some form of color profile support is desirable. A more generic conversion interface can still be independently added. I'll open a couple of issues...

Ogeon commented 2 months ago

I think this should be straight forward enough with ICC profiles off the table. Palette's support should mainly be for the naive version (e.g. Cmyk<Srgb> and similar) or as a semantic value container (could be Cmyk<Any> or a different marker type). The documentation just need to be clear enough on regarding how the naive variant is not suitable for printing.

flosse commented 2 months ago

Oh wow, eight years have already passed... I (almost) can't remember which project I needed CMYK support for back then :rofl: But it's still good to know that there are now new perspectives :wink: Thank you! :+1:

Ogeon commented 2 months ago

Ah, yes, I have a better idea of what I'm building now. But other things have also been calling for more attention along the way, so this one had to be patient. 😅