Rendering Separations with MuPDF

Robin Watts·April 30, 2025

MuPDFCRendering
MuPDF rendering separations

MuPDF is famous for its high quality antialiased output targeted for screens, but it can render to other colorspaces too, including full color separations, with overprint support.

This article will walk you through how to achieve this.

Hold on, what are separations?

Put simply, separations, or “spot colors” are ‘extra’ inks specified within the PDF file, over and above the base “process” colorants.

While most graphics are generally defined as a mixture of RGB or CMYK colorants, sometimes files make use of specific extra colors. These can be used for a variety of reasons. Often brands might have a particular color associated with it (Coca Cola red, or Cadbury purple for example), so their artwork will mention this color explicitly rather than relying on mixing the process colorants to produce it.

In some cases, it’s not possible to generate the exact color/effect required by mixing colorants; consider metallic or foil effects where the reflectiveness is key to their appearance.

Finally, consider cases where artwork has to be ‘cut out’, perhaps by a laser cutter. By encoding the cutting plane as an additional separation, the file can be self-contained, and the possibilities for mis-registration of the artwork are reduced.

Separations are a powerful mechanism with a huge range of uses across professional printing.

Viewing separations

But if a file uses a custom ink, how can we view it on screen?

Typically, most separations include an equivalent color, so when rendering the file to view it on a screen, you’ll get a reasonable approximation of what the fully featured print would look like.

This simulation is not always trivial to achieve, however, due in part to the use of “overprint”.

Normally in a PDF file, if you display a graphical object on top of another one, the bottom one is completely obscured. Only the color values from the top one show through.

Overprint is a mechanism whereby this rule can be varied. Without going into too much detail, it’s possible to set up PDF files so multiple objects can be layered on top of one another and individual inks from the different objects can show through.

For most files, whether the strict overprinting rules are followed will make little to no difference to the final appearance - most PDF software ignores overprint by default. For some files, proper overprint support will make a huge difference to the correctness of the final image.

For such files, even if you are rendering to a simple RGB preview, you need to at least ‘simulate’ the effects of overprint to get a decent result.

MuPDF allows users to choose how overprint is handled. It can be ignored for simplicity, handled in full for proper output, or simulated (by rendering internally with overprint, and then converting down to simple colorspaces at the end).

From the command line

The mutool draw command can produce output in a range of bitmap formats. These different formats allow for a range of colorspaces. Most commonly we produce RGB output as either PNG, PPM, PAM, and PNM. Greyscale renderings can be produced as PNG, PGM, PAM, and PNM. CMYK output can be produced in PAM.

In addition, we support a couple of dithered output formats, where we render as normal internally, and dither down to produce either monochrome (as PBM), or 1-bit-per-component CMYK (as PKM).

Finally, we have support for the Photoshop bitmap format (PSD). This is a swiss-army-knife format that can cope with any format we throw at it; RGB, greyscale, CMYK, and even CMYK with additional spot planes. It can even cope with an optional alpha plane.

To generate a rendered image we’d use a command such as:

mutool draw -o <output> -c <colorspace> -O <overprint> <input>

Obviously, <output> is the filename to produce. The suffix for this determines the file format used; in the rare occasion where the file suffix differs from the format produced, the format can be passed explicitly using the -F flag.

The <colorspace> is often (usually) implied by the file format, but for formats that can accommodate multiple different colorspaces (such as PNG which can cope with both RGB and greyscale), the -c flag allows it to be explicitly set (e.g. gray, rgb, rgba, cmyk, cmyka, etc).

Attempting to select a colorspace that’s incompatible with the selected format will give an error.

The <overprint> option controls how overprint is handled; this is an integer value from 0 to 2. 0 means “ignore overprint”, 1 (the default) means “simulate overprint” (i.e. render to separations internally, and convert down to the target colorspace at the end), and 2 means “perform full spot color rendering”. Attempting to select full spot color rendering will only work if the output format selected can cope - currently just PSD format.

Viewing PSD files

For users that don’t have access to Photoshop, PSD files can be converted to other formats using common tools, such as ImageMagick.

While there are a large number of applications that claim to support PSD format, many of them are restricted to RGB (or even CMYK) files. If anyone finds a free tool that allows PSD files with spot colors to be manipulated, please let me know!

MuPDF can itself be used to view the PSD files it produces, of course, but often it’s useful to be able to examine the separations in a file in detail, including being able to toggle them on and off “live”. For this reason, we have written a simple web viewer. It can be found at https://ghostscript.com/~robin/pamview.html. We hope you find it useful!

Using separations in the MuPDF API

For the following explanation, we’re going to assume a basic level of familiarity with calling the MuPDF C API. For more information on this see the included documentation, and also MuPDF Explored.

Separations are represented within MuPDF by an fz_separations object.

You can create your own fz_separations object using:

fz_separations *fz_new_separations(fz_context *ctx, int controllable);

Ignore controllable for now, and pass 0. This is a historical thing that no longer applies; it may be removed in later versions.

You now own that object, and should free it when you’re finished by calling:

void fz_drop_separations(fz_context *ctx, fz_separations *sep);

Creating your own separations is relatively rare though; mostly people will want to find the separations used on a given PDF page. This can be done by calling:

fz_separations *fz_page_separations(fz_context *ctx, fz_page *page);

Again, you now own that returned object; remember to drop it when you are done.

Each fz_separations object is basically a list of separations, with their behaviours, names, and equivalent colours. The most basic operation is therefore to find out how many such separations you have:

int fz_count_separations(fz_context *ctx, const fz_separations *sep);

For each separation you can then ask for its name (generally only used for displaying in a UI):

const char *fz_separation_name(fz_context *ctx, const fz_separations *sep, int index);

Or its equivalent color in a given colorspace:

void fz_separation_equivalent(fz_context *ctx, const fz_separations *seps, int index, fz_colorspace *dst_cs, float *dst_color, fz_colorspace *proof, fz_color_params color_params);

Color scientists may wish to provide a ‘proofing’ profile, but mere mortals can safely just pass NULL here. Similarly, fz_default_color_params is a good option for the final parameter.

The behaviour for each separation can be read individually:

fz_separation_behavior fz_separation_current_behavior(fz_context *ctx, const fz_separations *sep, int separation);

And can be set:

void fz_set_separation_behavior(fz_context *ctx, fz_separations *sep, int separation, fz_separation_behavior behavior);

The behaviours determine what happens when this separation is encountered during rendering:

  • FZ_SEPARATION_COMPOSITE will render the separation using the equivalent mix of process colors (i.e. overprint is ignored, and a dedicated spot plane is not produced).
  • FZ_SEPARATION_SPOT will render the separation to its own dedicated ‘spot’ plane.
  • FZ_SEPARATION_DISABLED will cause the separation to be completely ignored during rendering.

The fz_separations object returned by fz_page_separations() has the behaviour for all separations set to FZ_SEPARATION_COMPOSITE.

Rendering with separations

So, putting this all together, we can get the separations on a page:

fz_separations *seps = fz_page_separations(ctx, page);

then we set all those to be FZ_SEPARATION_SPOT:

int i, n = fz_count_separations(ctx, seps);

for (i = 0; i < n; i++)

fz_set_separation_behaviour(ctx, seps, i,

FZ_SEPARATION_SPOT);

Then we can render the page image. The simplest way to do this is to use the helper function:

fz_pixmap *fz_new_pixmap_from_page_with_separations(fz_context *ctx, fz_page *page, fz_matrix ctm, fz_colorspace *cs, fz_separations *seps, int alpha);

In almost all cases, you’ll want to use a CMYK colorspace (e.g. fz_device_cmyk(ctx)) as cs here.

This (and its sister functions fz_new_pixmap_from_display_list_with_separations, fz_new_pixmap_from_page_number_with_separations, fz_new_pixmap_from_page_contents_with_separations) behaves just like fz_new_pixmap_from_page (and its sister functions), with the sole difference that the fz_pixmap which is created has the given separations structure attached. This causes the rendering to include all the appropriate extra colorant information.

Alternatively, you can of course create the fz_pixmap with the separations structure yourself, and use whatever rendering call you prefer.

Saving the data out

The fz_pixmap can then be written as a PSD using:

void fz_write_pixmap_as_psd(fz_context *ctx, fz_output *out, const fz_pixmap *pixmap);

Accessing the rendered data programmatically

Alternatively, it’s a simple matter to access the raw pixel data within a fz_pixmap.

A fz_pixmap *pix represents a 2d plane of pix->w wide, by pix->h high pixels. The colorants for a given pixel (x,y) within this range can be found as unsigned 8 bit values at:

&pix->samples[pix->stride * y + pix->n * x]

The bytes for each pixel are ordered as follows:

<Process colors><Spots><Alpha>

Typically:

C M Y K Spot0 Spot1…. Alpha

There are pix->n bytes in total.

The last pix->a bytes are alphas (currently only 0 or 1 bytes of alpha are supported).

There are pix->s bytes of spot data (pix->s being 0 indicates no spots).

Thus there are (pix->n - pix->s - pix->a) bytes of process colorants.

Alternatively, the number of process colorants can be found by:

(pix->cs == NULL) ? 1 : fz_colorspace_n(pix->cs)

Conclusion

Hopefully this has given you enough information to generate output from MuPDF with spot colors. If you have any questions, please don’t hesitate to visit our Discord server and ask. Or commercial customers can contact the devs directly via our commercial support email address!