Color management
Color management in Mako
Even for the simple example described in Rendering 101, Mako uses the CMS (color management system) to control how colors are interpreted and subsequently represented in the output.
That example defaults to creating RGB output, as no output color space is declared. For example, to create 300dpi, 8bpc, CMYK output, pass in a DPI value, and color space:
IDOMImagePtr renderedImage = renderer->render(pageContent, 300, 8, IDOMColorSpaceDeviceCMYK::create(jawsMako));
var renderedImage = renderer.render(pageContent, 300, 8, IDOMColorSpaceDeviceCMYK.create(jawsMako.getFactory())
DeviceCMYK
represents the default CMYK color space, by default a SWOP profile. See Intercept color spaces below. You can load an alternative profile from disk and use that. For example:
String iccProfile = L"WebCoatedFOGRA28.icc";
IDOMColorSpaceICCBasedPtr iccBasedColorSpace = IDOMColorSpaceICCBased::create(
jawsMako, IDOMICCProfile::create(jawsMako, IInputStream::createFromFile(jawsMako, iccProfile)),
IDOMColorSpaceDeviceCMYK::create(jawsMako));
string iccProfile = "WebCoatedFOGRA28.icc";
var iccBasedColorSpace = IDOMColorSpaceICCBased.create(
jawsMako, IDOMICCProfile.create(jawsMako, IInputStream.createFromFile(jawsMako, iccProfile)),
IDOMColorSpaceDeviceCMYK.create(jawsMako));
Then use this color space for rendering:
Call the renderer
IDOMImagePtr renderedImage = renderer->render(pageContent, 300, 8, iccBasedColorSpace);
var renderedImage = renderer.render(pageContent, 300, 8, iccBasedColorSpace);
Device color spaces
Device color spaces directly specify colors or shades of gray that the output device is to produce. They provide a variety of color specification methods, including grayscale, RGB (red-green-blue), and CMYK (cyan-magenta-yellow-black), corresponding to the color space families DeviceGray, DeviceRGB, and DeviceCMYK. Since each of these families consists of just a single color space with no parameters, they are often loosely referred to as the DeviceGray, DeviceRGB, and DeviceCMYK color spaces.
CIE-based color spaces
CIE-based color spaces are based on an international standard for color specification created by the Commission Internationale de l’Éclairage (International Commission on Illumination). These spaces specify colors in a way that is independent of the characteristics of any particular output device. Color space families in this category include CalGray, CalRGB, Lab, and ICCBased. Individual color spaces within these families are specified by means of dictionaries containing the parameter values needed to define the space.
Intercept color spaces
These are the color spaces that will be used for conversion whenever the color manager is asked to convert from or to DeviceGray, DeviceRGB, or DeviceCMYK, respectively.
Color model | # colorants | Colorspace | Default profile in Mako |
---|---|---|---|
Grayscale | 1 | DeviceGray | sGray* |
RGB | 2 | DeviceRGB | sRGB |
CMYK | 4 | DeviceCMYK | SWOP ICC |
* sGray is a grayscale equivalent of sRGB, adopting the same gamma curve as sRGB
Overriding the intercept defaults
You may want to set a different profile to be used for the intercept. For example, a way to speed up processing is to set the DeviceCMYK intercept color space to the same space used for rendering, as there will be no need to carry out an intermediate conversion. Or you may want to specify a different profile for RGB or grayscale processing. Load the profile using the technique in the code snippet above (Creating a CMYK color space from an external profile) and use the color manager's setIntercept() method. How to do this is described in "Configuring the CMS", below.
Conversion of color spaces during rendering
A simplified diagram of the color conversion that occurs inside the renderer follows:
![](../__attachments/54886447/ColorFlowChart.png?inst-v=e730af54-a027-4b4f-96be-4116dc0ba3e3)
Configuring the CMS
Mako provides a class, IColorManager
, to control the CMS (or CMM, Color Management Module). It offers various methods to control how colors are transformed between color spaces, create profiles, retrieve built-in profiles, or convert colors.
There is only one instance of the color manager. The singleton can be retrieved with the get()
method.
Get the Color Manager
#include <edl/icolormanager.h>
...
IColorManagerPtr cmm = IColorManager::get(jawsMako);
var cmm = IColorManager.get(jawsMako.getFactory());
Intercept spaces - The profile associated with one of these intercept spaces can be retrieved or set with these methods:
DeviceGray |
CPP
|
CPP
|
---|---|---|
DeviceRGB |
CPP
|
CPP
|
DeviceCMYK |
CPP
|
CPP
|
Configuring DeviceGray to DeviceCMYK
Another commonly used setting is to configure the CMM to map DeviceGray to DeviceCMYK without color conversion. This is required to obtain correct results for many cases.
Configure gray to black mapping
cmm->setMapDeviceGrayToCMYKBlack(true);
cmm.setMapDeviceGrayToCMYKBlack(true);
Controlling rendering intent and black point compensation
In general there is no need to explicitly set these for rendering. PDF defines a default rendering intent of Relative Colorimetric, and a black point compensation setting of "default" (meaning it's left up to the CMS).
These settings are properties of every node in the page tree. You can get or set these values to influence how the object is rendered:
Get or set rendering intent
// Get example
PValue riValue;
pageContent->getFirstChild()->getProperty("RenderingIntent", riValue);
// Set example
pageContent->getFirstChild()->setProperty("RenderingIntent", eAbsoluteColorimetric);
// Get example
PValue riValue;
pageContent.getFirstChild().getProperty("RenderingIntent", ref riValue);
// Set example
pageContent.getFirstChild().setProperty("RenderingIntent", eAbsoluteColorimetric);
The PValue
returned is an integer. An enumeration is available for setting the value:
| ePerceptual | Perceptual rendering intent |
| eRelativeColorimetric | Relative colorimetric rendering intent |
| eSaturation | Saturation rendering intent |
| eAbsoluteColorimetric | Absolute colorimetric rendering intent |
Get or set black point compensation
// Get
PValue bpcValue;
pageContent->getFirstChild()->getProperty("BlackPointCompensation", bpcValue);
pageContent->getFirstChild()->setProperty("BlackPointCompensation", eBPCOn);
PValue bpcValue;
pageContent->getFirstChild()->getProperty("BlackPointCompensation", bpcValue);
pageContent->getFirstChild()->setProperty("BlackPointCompensation", eBPCOn);
The PValue
returned is an integer. An enumeration is available for setting the value.
Value | Setting | Description |
---|---|---|
0 | eBPCDefault | Default behavior: Black point compensation will be applied when ICC Version 4 or greater profiles are used and the intent is perceptual or saturation. |
1 | eBPCOn | Use black point compensation if applicable during color conversion: Black point compensation will be applied for all but absolute rendering intents. |
2 | eBPCOff | Do not use black point compensation during color conversion: Black point compensation wll not be used. |
Setting the BPC property influences rendering or other operations that require color management, but this property is not persisted (saved) to PDF 1.x. It is saved to PDF 2.0.
Visiting every node on the page
As already mentioned, to control rendering intent or black point compensation, it is necessary to update every marking node on the page. This sounds daunting, but actually Mako provides a way to do this, known as a custom transform. This C++ example shows how this is done, while custom transforms are described more generally elsewhere in this documentation. The advantage of this approach is that the code can make a decision based on the node type and its properties.
Accessing color spaces through the DOM
Sometimes, it's useful to inspect the color space that a DOM node is using.
For example, a path node will have a fill or stroke color. When we access the path's fill or stroke, there's a method that allows us to get its corresponding color space.
const auto brush = pathNode->getFill();
if (brush && brush->getBrushType() == IDOMBrush::eSolidColor)
{
const auto solidColorBrush = edlobj2IDOMSolidColorBrush(brush);
// If it has, we can get its color space. Similar accessors exist for other brush types.
const auto colorSpace = solidColorBrush->getColor()->getColorSpace();
// Do something with the color space...
}
var brush = pathNode.getFill();
if (brush && brush.getBrushType() == IDOMBrush.eSolidColor)
{
var solidColorBrush = edlobj2IDOMSolidColorBrush(brush);
// If it has, we can get its color space. Similar accessors exist for other brush types.
var colorSpace = solidColorBrush.getColor().getColorSpace();
// Do something with the color space...
}