Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Introduction

The basic principles for rendering in Mako are straightforward, and are demonstrated in two of the simple examples (render and separatedrender) that ship with the Mako SDK. For example, rendering to RGB from a PDF or other input file involves these steps:

This is all you need for a simple rendering, for example to create a thumbnail.

Color management in Mako

Even for the simple example described above, Mako uses the CMS (color management system) to control how colors are interpreted and subsequently represented in the output.

The above example defaults to creating RGB output as no output colorspace is declared. To create 300dpi, CMYK output, pass in a DPI value and colorspace. For example:

Code Block
languagecpp
titleRender to CMYK @300dpi
renderedImage = renderer->render(pageContent, 300, IDOMColorSpaceDeviceCMYK::create(jawsMako));

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:

Code Block
languagecpp
titleCreating a CMYK colorspace from an external profile
String iccProfile = L"WebCoatedFOGRA28.icc";
IDOMColorSpaceICCBasedPtr iccBasedColorSpace = IDOMColorSpaceICCBased::create(
					jawsMako, IDOMICCProfile::create(jawsMako, IInputStream::createFromFile(jawsMako, iccProfile)),
					IDOMColorSpaceDeviceCMYK::create(jawsMako));

Then use this colorspace for rendering:

Code Block
languagecpp
titleCall the renderer
renderedImage = renderer->render(pageContent, 300, 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

ColorspaceDefault profile in Mako
Grayscale1DeviceGraysGray*
RGB3DeviceRGBsRGB
CMYK4DeviceCMYKSWOP 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 colorspace 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 colorspace 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

This is a simplified diagram of the color conversion that occurs inside the renderer.

Configuring the CMS

Mako provides an 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.

Code Block
languagecpp
titleGet the Color Manager
#include <edl/icolormanager.h>
...
IColorManagerPtr cmm = IColorManager::get(jawsMako);

Intercept spaces - The profile associated with one of these intercept spaces can retrieved or set with these methods:

ColorspaceGetSet
DeviceGrayIColorManager::getDeviceGrayIntercept()IColorManager::setDeviceGrayIntercept()
DeviceRGBIColorManager::getDeviceRGBIntercept()IColorManager::setDeviceRGBIntercept()
DeviceCMYKIColorManager::getDeviceCMYKIntercept()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.

Code Block
languagecpp
titleConfigure gray to black mapping
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.

Code Block
languagecpp
titleGet or set rendering intent
PValue riValue;
pageContent->getFirstChild()->getProperty("RenderingIntent", riValue);
pageContent->getFirstChild()->setProperty("RenderingIntent", eAbsoluteColorimetric);

The PValue returned is an integer. An enumeration is available for setting the value.

ValueSettingDescription
0ePerceptualPerceptual rendering intent
1eRelativeColorimetricRelative colorimetric rendering intent
2eSaturationSaturation rendering intent
3eAbsoluteColorimetricAbsolute colorimetric rendering intent


Code Block
languagecpp
titleGet or set black point compensation
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.

ValueSettingDescription
0eBPCDefaultDefault behavior
1eBPCOnUse black point compensation if applicable during color conversion
2eBPCOffDo not use black point compensation during color conversion

Converting colors


Visiting every node on the page

As already mentioned, to control rendering intent or black point compensation it is necessary to update every node on the page. This sounds daunting but actually Mako provides a way to do this, known as a custom transform. Custom transforms are described here. The advantage of this approach is that the code can make a decision based on the node type and its properties. For example:

Code Block
languagecpp
titleCustom transform example for updating rendering intents
linenumberstrue
collapsetrue
#pragma once

#include <edl/idomnode.h>
#include <edl/idombrush.h>

#include <jawsmako/jawsmako.h>
#include <jawsmako/transforms.h>
#include "customtransform.h"

namespace CUpdateRenderingIntents {

	using namespace JawsMako;
	using namespace EDL;

	// A transform implementation that will modify the DOM to explicitly set the rendering intents.
	class TransformImplementation : public ICustomTransform::IImplementation {
	public:
		TransformImplementation(const IJawsMakoPtr& jawsMako)
			: m_jawsMako(jawsMako) { }

		virtual IDOMNodePtr transformPath(IImplementation* genericImplementation, const IDOMPathNodePtr& path, bool& changed, const CTransformState& state)
		{
			// Now, a path can potentially contain both a fill and a stroke, however it is
			// unlikely that we will see a fill and a stroke with different colour spaces.

			// Determine the intent we should use for the brushes contained herein.
			// Start off with what the PDF says we should use.
			eRenderingIntent intent = state.renderingIntent;
			eRenderingIntent useIntent = intent;

			// Now check to see if this is to be overridden
			IDOMBrushPtr brush;
			if (path->getStroke(brush) && brush)
			{
				determineIntentForBrush(brush, useIntent);
			}
			if (path->getFill(brush) && brush)
			{
				determineIntentForBrush(brush, useIntent);
			}

			// Ok - does it differ?

			if (useIntent != intent)
			{
				// Yes. Override the intent
				// For rendering intents, node properties are used.
				PValue intentValue((int32)useIntent);
				path->setProperty("RenderingIntent", intentValue);
				changed = true;
			}

			// Also check inside any brushes, for example for colors inside tiling patterns etc.
			bool didSomething = false;
			IDOMNodePtr transformed = genericImplementation->transformPath(NULL, path, didSomething, state);
			changed |= didSomething;
			return transformed;
		}

	protected:
		void determineIntentForBrush(const IDOMBrushPtr& brush, eRenderingIntent& intent)
		{
			// Depends on brush type
			switch (brush->getBrushType())
			{
			case IDOMBrush::eSolidColor:
			{
				IDOMSolidColorBrushPtr solid = edlobj2IDOMSolidColorBrush(brush);

				// Get the colour
				IDOMColorPtr colour;
				solid->getColor(colour);

				// Get the colour space from that
				IDOMColorSpacePtr space;
				colour->getColorSpace(space);

				// And make a decision based on that
				determineIntentForColorSpace(space, intent);
			}
			break;

			case IDOMBrush::eImage:
			{
				IDOMImageBrushPtr imageBrush = edlobj2IDOMImageBrush(brush);
				IDOMImagePtr image;
				imageBrush->getImageSource(image);

				// Get the colour space from a frame
				IImageFramePtr frame;
				image->getFirstImageFrame(m_jawsMako, frame);

				// Get the colour space
				IDOMColorSpacePtr space;
				frame->getColorSpace(space);

				// And make a decision based on that
				determineIntentForColorSpace(space, intent);
			}
			break;

			case IDOMBrush::eMasked:
			{
				IDOMMaskedBrushPtr masked = edlobj2IDOMMaskedBrush(brush);

				// Get the brush that is masked by the image
				IDOMBrushPtr subBrush;
				masked->getBrush(subBrush);

				// Recurse on that
				determineIntentForBrush(subBrush, intent);
			}
			break;

			// Not relevant
			case IDOMBrush::eSoftMask:
				break;

			case IDOMBrush::eTilingPattern:
			{
				IDOMTilingPatternBrushPtr tiling = edlobj2IDOMTilingPatternBrush(brush);

				int32 paintType = 0;
				if (tiling->getPaintType(paintType) && paintType == 2)
				{
					// The pattern has a colour
					IDOMColorPtr colour;
					tiling->getPatternColor(colour);

					IDOMColorSpacePtr space;
					colour->getColorSpace(space);

					// And make a decision based on that
					determineIntentForColorSpace(space, intent);
				}
			}
			break;

			case IDOMBrush::eType1ShadingPattern:
			case IDOMBrush::eType2ShadingPattern:
			case IDOMBrush::eType3ShadingPattern:
			case IDOMBrush::eType4567ShadingPattern:
			{
				IDOMShadingPatternBrushPtr shade = edlobj2IDOMShadingPatternBrush(brush);

				// Get the colour space
				IDOMColorSpacePtr space;
				shade->getColorSpace(space);

				// And make a decision based on that
				determineIntentForColorSpace(space, intent);
			}
			break;

			// No colour at all
			case IDOMBrush::eNull:

				// These are found in XPS, but not in PDF. Ignore for now.
			case IDOMBrush::eLinearGradient:
			case IDOMBrush::eRadialGradient:
			case IDOMBrush::eVisual:
				break;

			default:
				// Not reached
				break;
			}

			// Done
		}

		void determineIntentForColorSpace(const IDOMColorSpacePtr& space, eRenderingIntent& intent)
		{
			// Depends on colour space type
			switch (space->getColorSpaceType())
			{
			case IDOMColorSpace::eDeviceRGB:
				// Untagged RGB. We will use the HP-supplied sRGB colour space.
				// And therefore use perceptual
				intent = ePerceptual;
				break;

			case IDOMColorSpace::eDeviceGray:
				// Untagged gray. Use Perceptual
				intent = ePerceptual;
				break;

			case IDOMColorSpace::eDeviceCMYK:
				// Untagged CMYK. Use relative colorimetric.
				intent = eRelativeColorimetric;
				break;

				// Colour spaces usually found in XPS and not occuring in PDF.
				// However, they are backed by profiles, so treat them as such.
			case IDOMColorSpace::esRGB:
			case IDOMColorSpace::esGray:
			case IDOMColorSpace::escRGB:
				intent = ePerceptual;
				break;

			case IDOMColorSpace::eICCBased:
			{
				// Ok. What flavour?
				switch (space->getNumComponents())
				{
				case 1:
					// Gray. Perceptual.
					intent = ePerceptual;
					break;

				case 3:
					// RGB. Perceptual
					intent = ePerceptual;
					break;

				case 4:
					// CMYK - Use relative colorimetric
					intent = eRelativeColorimetric;
					break;
				default: ;
				}
			}
			break;

			case IDOMColorSpace::eIndexed:
			{
				IDOMColorSpaceIndexedPtr indexed = edlobj2IDOMColorSpaceIndexed(space);

				// Recurse on the underlying space
				determineIntentForColorSpace(indexed->getUnderlyingColorSpace(), intent);
			}
			break;

			case IDOMColorSpace::eDeviceN:
				// Unclear what to do about this. Perhaps we should look at
				// the alternate? For now, use the PDF intent.
				break;

			case IDOMColorSpace::eLAB:
				// Use embedded intent
				break;

			default:
				// Not reached
				break;
			}
		}

	private:
		IJawsMakoPtr m_jawsMako;
	};
}

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 with have a fill and a stroke color. When we access the path's fill or stroke color, there's a method which allows us to get its corresponding color space.

Code Block
languagecpp
linenumberstrue
const auto brush = pathNode->getFill();

if (brush && brush->getBrushType() == IDOMBrush::eSolidColor)
{
    const auto solidColorBrush = edlobj2IDOMSolidColorBrush(brush);

    // If it has, we can get it's color space. Similar accessors exist for other brush types.
    const auto colorSpace = solidColorBrush->getColor()->getColorSpace();

    // Do something with the color space...
}


Glossary

Color Management System (CMS)

A color management system converts color values from one device, such as a scanner, to color values from a different device, such as a printer, so that the colors reproduced by the printer correspond to those scanned. Where an exact match cannot be achieved, the color management system calculates color values that reproduce the colors of the original as accurately as possible.

Gamut

The entire range of colors available on a particular device such as a monitor or printer. A monitor, which displays RGB signals, typically has a greater color gamut than a printer, which uses CMYK inks.