Skip to main content
Skip table of contents

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:

CPP
IDOMImagePtr renderedImage = renderer->render(pageContent, 300, 8, IDOMColorSpaceDeviceCMYK::create(jawsMako));
C#
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:

CPP
String iccProfile = L"WebCoatedFOGRA28.icc";
IDOMColorSpaceICCBasedPtr iccBasedColorSpace = IDOMColorSpaceICCBased::create(
                    jawsMako, IDOMICCProfile::create(jawsMako, IInputStream::createFromFile(jawsMako, iccProfile)),
                    IDOMColorSpaceDeviceCMYK::create(jawsMako));
C#
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
CPP
IDOMImagePtr renderedImage = renderer->render(pageContent, 300, 8, iccBasedColorSpace);
C#
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:

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

CPP
#include <edl/icolormanager.h>
...
IColorManagerPtr cmm = IColorManager::get(jawsMako);
C#
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
IColorManager::getDeviceGrayIntercept()
CPP
IColorManager::setDeviceGrayIntercept()

DeviceRGB

CPP
IColorManager::getDeviceRGBIntercept()
CPP
IColorManager::setDeviceRGBIntercept()

DeviceCMYK

CPP
IColorManager::getDeviceCMYKIntercept()
CPP
IColorManager::setDeviceCMYKIntercept()

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

CPP
cmm->setMapDeviceGrayToCMYKBlack(true);
C#
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

CPP
// Get example
PValue riValue;
pageContent->getFirstChild()->getProperty("RenderingIntent", riValue);

// Set example
pageContent->getFirstChild()->setProperty("RenderingIntent", eAbsoluteColorimetric);
C#
// 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:

0

ePerceptual

Perceptual rendering intent

1

eRelativeColorimetric

Relative colorimetric rendering intent

2

eSaturation

Saturation rendering intent

3

eAbsoluteColorimetric

Absolute colorimetric rendering intent


Get or set black point compensation

CPP
// Get
PValue bpcValue;
pageContent->getFirstChild()->getProperty("BlackPointCompensation", bpcValue);
pageContent->getFirstChild()->setProperty("BlackPointCompensation", eBPCOn);
C#
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.

CPP
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...
}
C#
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...
}

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.