[cairo] Subtractive API, part 0
ecir hana
ecir.hana at gmail.com
Sun Jan 24 17:54:57 PST 2010
> Are you to a point where you could start capturing the above as
> concretely as an API proposal for cairo? That also might help me
> understand better what I'm missing.
Hello,
here's a first try, thanks in advance for your feedback! It has at
least one issue but as they say: "release early, release often".
* CMYK
If I understood it correctly, this could be fairly simple (color,
pattern, image):
void cairo_set_source_cmyk(cairo_t *cr, double cyan, double magenta,
double yellow, double black);
cairo_pattern_t* cairo_pattern_create_cmyk(double cyan, double
magenta, double yellow, double black);
void cairo_pattern_add_color_stop_cmyk(cairo_pattern_t *pattern,
double offset, double cyan, double magenta, double yellow, double
black);
CAIRO_FORMAT_CMYK32 in cairo_format_t
* DeviceN
This is a bit worse... I wanted to add as few new functions as
possible and to break none of the current ones. I also did not try to
come up with API which was overly generic, rather I took the advice
from Mike Pall (of LuaJIT) when he said "I think it's mandatory to
write some standard usage examples before designing and implementing
an API" and wrote a few examples (see bellow). Also, it would be great
if one could use the same sequence of operators to draw, say, to the
new meta surface, replay it on screen for zooming and panning and
finally output it as print-ready PDF.
First, instead of creating a new generic color space and assign it to
every surface as its property, I thought it might be simpler just to
create a completely new kind of object:
cairo_devicen_t* cairo_devicen_create();
cairo_devicen_destroy(cairo_devicen_t *devicen);
void cairo_devicen_add_spot_color_cmyk(cairo_devicen_t *devicen, const
char *name, double fallback_cyan, double fallback_magenta, double
fallback_yellow, double fallback_black);
This way DeviceN becomes separate from the rest of API so it would
stay out of way if one does not want to use it and it might be
possible in the future to provide other kinds of fallbacks (e.g.
cairo_devicen_add_spot_color_srgb_icc()). It could be also possible to
create many fallbacks for a given name so one could specify a fallback
for RGB, CMYK, ... It somewhat looks like Cairo pattern API. On
fallbacks, please read bellow for more.
Then, we could write something similar as for CMYK, passing the
DeviceN as additional argument:
void cairo_set_source_devicen(cairo_t *cr, cairo_devicen_t *devicen, ...);
cairo_pattern_t* cairo_pattern_create_devicen(cairo_devicen_t *devicen, ...);
void cairo_pattern_add_color_stop_devicen(cairo_pattern_t *pattern,
cairo_devicen_t *devicen, double offset, ...);
CAIRO_FORMAT_DEVICEN in cairo_format_t
"..." means tint values for each colorant of the DeviceN (vararg doubles).
There is one (or more?) problem, thought. It is sometimes useful to
create multi-channel images, e.g. a pixmap with just two channels,
so-called "duotone" image. It needs the possibility of creating a
Cairo surface out of such image and to assign a spot color to each
channel (so that the image pixel values become DeviceN tint values).
Currently, there is no way how to create such image surfaces.
"cairo_image_surface_create_for_data()" would need to know how many
bits a pixel contains and how many channels there are and PNG
("cairo_image_surface_create_from_png()") has predefined set of
allowed image types. We can assume that the images would have either 8
or 16 bit color depth. That could go to cairo_format_t as
CAIRO_FORMAT_DEVICEN8 and CAIRO_FORMAT_DEVICE16. Then, if we knew how
long "*data" is it would be possible to calculate the number of
channels. But even if we knew all this there is still the need to
define the spot colors for each channel.
Or maybe, somehow, possibly, there could be two functions:
cairo_surface_set_colorant_names(cairo_surface_t *surface, const char **names);
cairo_devicen_get_colorant_names(cairo_devicen_t *devicen);
and not care what kind of data and image surface is built on, just
force it to be something and hope you know what you are doing.. kind
of like C cast.
But I'm affraid this is all crap... Help!
Anyway...
Thoughts?
Appendix A, Examples:
* CMYK
** rectangle
cairo_set_source_cmyk(cr, 0.0, 0.0, 0.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 10.0, 10.0);
cairo_fill(cr);
** pattern
lp = cairo_pattern_create_linear(0.0, 0.0, 1.0, 1.0);
cairo_pattern_add_color_stop_cmyk(lp, 0.0, 0.0, 0.0, 0.0, 0.0);
cairo_pattern_add_color_stop_cmyk(lp, 1.0, 1.0, 1.0, 1.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 10.0, 10.0);
cairo_set_source(cr, lp);
cairo_fill(cr);
** image
cairo_surface_t *im =
cairo_image_surface_create_from_jpeg("image.jpg"); // PNG does not
support CMYK
cairo_set_source_surface(cr, im, 0.0, 0.0);
cairo_paint(cr);
cairo_devicen_destroy(dn);
* 2 spot colors
** rectangle
dn = cairo_devicen_create()
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorA", 0.1, 0.2, 0.3, 0.4);
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorB", 0.6, 0.7, 0.9, 0.9);
cairo_set_source_devicen(cr, dn, 1.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 10.0, 10.0);
cairo_fill(cr);
cairo_devicen_destroy(dn);
** pattern
dn = cairo_devicen_create()
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorA", 0.1, 0.2, 0.3, 0.4);
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorB", 0.6, 0.7, 0.9, 0.9);
lp = cairo_pattern_create_linear(0.0, 0.0, 1.0, 1.0);
cairo_pattern_add_color_stop_devicen(lp, dn, 0.0, 0.0, 0.0);
cairo_pattern_add_color_stop_devicen(lp, dn, 1.0, 1.0, 1.0);
cairo_rectangle(cr, 0.0, 0.0, 10.0, 10.0);
cairo_set_source(cr, lp);
cairo_fill(cr);
cairo_devicen_destroy(dn);
** image
dn = cairo_devicen_create()
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorA", 0.1, 0.2, 0.3, 0.4);
cairo_devicen_add_spot_color_cmyk(dn, "SpotColorB", 0.6, 0.7, 0.9, 0.9);
cairo_surface_t *im = cairo_image_surface_create_for_data(data,
CAIRO_FORMAT_DEVICEN8, 100, 100, cairo_format_stride_for_width(100));
cairo_surface_set_colorant_names({"SpotColorB", "SpotColorB"}); // ???
FIXME: here it breaks!
cairo_set_source_surface(cr, im, 0.0, 0.0);
cairo_paint(cr);
cairo_devicen_destroy(dn);
Appendix B, DeviceN like CMYK (just for fun):
dn = cairo_devicen_create()
cairo_devicen_add_spot_color_cmyk(dn, "Cyan", 1.0, 0.0, 0.0, 0.0);
cairo_devicen_add_spot_color_cmyk(dn, "Magenta", 0.0, 1.0, 0.0, 0.0);
cairo_devicen_add_spot_color_cmyk(dn, "Yellow", 0.0, 0.0, 1.0, 0.0);
cairo_devicen_add_spot_color_cmyk(dn, "Black", 0.0, 0.0, 0.0, 1.0);
cairo_devicen_destroy(dn);
Appendix C, Fallbacks:
In my previous post I wrote that if a color is not available on the
output device there should be a fallback mapping (tintTranform &
sampled function) which assigns a tint of the spot color to a mix of
device's colors values. It turns out, it is actually rather easy to
generate the whole mapping if CMYK of 100% spot color is known, so the
table (i.e. samples) could be generated on the fly and the user would
need to provide just that one CMYK. Small Python example:
--- cmyk.py - begin ---
class cmyk(object):
def __init__(self, c, m, y, k):
self.tints = c, m, y, k
def __add__(self, other):
return cmyk(*(a + b - a*b for a, b in zip(self.tints, other.tints)))
def __mul__(self, intensity):
return cmyk(*(tint * intensity for tint in self.tints))
def __str__(self):
return '%.2f %.2f %.2f %.2f' % self.tints
def to_ascii(self):
return '%03d %03d %03d %03d' % tuple(round(tint * 255) for
tint in self.tints)
##print cmyk(1, 1, 0, 0) + cmyk(0, 0, 1, 1)
##print cmyk(0.89, 0.43, 0, 0) + cmyk(0.03, 0.89, 0, 0)
##print cmyk(1, 1, 0, 0) * 0.5
a = cmyk(0.89, 0.43, 0, 0)
b = cmyk(0.03, 0.89, 0, 0)
# Generate 1D mapping from one spot color to CMYK
##SIZE = 255
##y = []
##for j in range(SIZE):
## y.append((a * (j / (SIZE - 1.0))).to_ascii())
# Generate 2D mapping from two spot colors to CMYK
SIZE = 45
y = []
for j in range(SIZE):
x = []
for i in range(SIZE):
x.append((a * (i / (SIZE - 1.0)) + b * (j / (SIZE - 1.0))).to_ascii())
y.append(x)
for x in y:
print x
--- cmyk.py - end ---
More information about the cairo
mailing list