Technical Article

Soft Masks and Transparency Flattening in Delphi

PDF Library for Delphi authors both kinds of PDF soft mask from Delphi. SetImageSoftMaskEx attaches a mask image to a target image and can write the /Matte entry that pre-multiplied artwork needs. NewSoftMaskGraphicsState builds an alpha or luminosity mask from captured content and returns a graphics state you apply to subsequent drawing. And when a consumer cannot handle transparency at all, FlattenTransparency renders the visual result and replaces each page with it

Soft masks are how PDF expresses a gradient fade, a vignette, a photo blended into a background, and every other effect where opacity varies across a region. Constant transparency is a single number in the graphics state; a soft mask is a whole function of position, and until you can author one, an entire category of design cannot be reproduced

The two mask types, and what each one reads

An alpha mask takes its values from the alpha channel of the masking content. A luminosity mask takes them from the brightness of the masking content, so a white area is fully opaque and a black area fully transparent. Luminosity is the one designers reach for, because painting a greyscale gradient is a natural way to describe a fade

Form soft masks are built from captured content, and several details are fixed rather than optional. The captured content becomes an isolated transparency group in DeviceRGB, so the mask evaluates in a known colour space. The backdrop colour must therefore have three components, matching that group. The transfer function accepts /Identity or a valid function object, letting you reshape the mask curve without repainting the artwork

Diagram comparing luminosity and alpha soft masks feeding NewSoftMaskGraphicsState, with a reminder that the graphics state persists until it is cleared
Luminosity masks read brightness while alpha masks read coverage, and NewSoftMaskGraphicsState wraps either one in a persistent graphics state
uses
  PDFlibrary;

var
  Lib: TPDFlib;
  MaskCapture, MaskGS, PhotoID: Integer;
begin
  Lib := TPDFlib.Create;
  try
    Lib.LoadFromFile('brochure.pdf', '');

    // Page 4 holds a greyscale gradient painted for use as a mask
    MaskCapture := Lib.CapturePage(4);

    MaskGS := Lib.NewSoftMaskGraphicsState(MaskCapture,
      PDF_SOFT_MASK_LUMINOSITY, '0 0 0', '/Identity');

    Lib.SelectPage(1);
    Lib.SetGraphicsState(MaskGS);
    PhotoID := Lib.AddImageFromFile('hero.jpg', 0);
    Lib.SelectImage(PhotoID);
    Lib.DrawImage(40, 700, 515, 260);        // fades out under the mask

    // ExtGState settings persist: end the scope explicitly
    Lib.SetGraphicsState(Lib.NewSoftMaskGraphicsState(0,
      PDF_SOFT_MASK_NONE, '', ''));

    Lib.SaveToFile('brochure-masked.pdf');
  finally
    Lib.Free;
  end;
end;

That last step is not decoration. Graphics state parameters persist until they are changed, and applying an ordinary graphics state afterwards does not clear an active soft mask. Without an explicit /SMask /None, everything drawn after the masked artwork inherits the mask, which is one of the more baffling bugs to diagnose visually

Image masks and the Matte entry

For image-level masking, the mask image is normalised to DeviceGray first, so a mask supplied in any colour space behaves predictably. The optional /Matte entry, one to four components depending on the image's colour space, tells a consumer that the image data is pre-multiplied against that colour, which prevents the dark halo that appears when pre-multiplied artwork is composited as if it were straight alpha

Diagram showing a missing Matte entry producing dark halos while a declared Matte composites cleanly, beside PDF 1.4 and PDF/A and PDF/X guardrails
A declared /Matte prevents the dark halo of pre-multiplied artwork, while PDF/A-1 and PDF/X-1 files are refused before any object changes

Two guardrails matter here. PDF/A-1 and PDF/X-1 forbid transparency, and the call refuses before modifying any object rather than producing a file that fails validation later. And a successful soft mask requires PDF 1.4, which the library sets automatically — transparency did not exist before that version, and a 1.3 header on a file containing soft masks is simply false

Repeated states are reused: the same captured form, mask type, backdrop and transfer function signature yield the same graphics state object rather than a new one per call. On a document that masks a hundred images identically, that is ninety-nine objects that never get written

Why flatten, and what flattening actually does

Transparency is still the most common source of "it looks right on screen and wrong on the press". Older RIPs, some regulatory submission systems and PDF/X-1a all require a file with no live transparency at all

Flattening here is deliberately literal: each page in the requested range is rendered at a chosen DPI against an opaque background, and the page content and resources are replaced by that single image. Because the renderer produces the final composited result, this covers everything at once — transparency groups, blend modes, soft masks, patterns and nested forms — with no per-construct approximation logic to get wrong. Peak raster memory stays bounded to one page

Pipeline diagram of FlattenTransparency rendering each requested page at 300 DPI on an opaque background and saving a flat press-ready PDF 1.3 file
Each requested page renders once at the chosen DPI and returns as a single opaque image saved into a truthful PDF 1.3 file
// Flatten pages 1-8 at 300 DPI on white, then save as PDF 1.3
Lib.FlattenTransparency('1-8', 300, $FFFFFF, 0);
Lib.SaveToFile('press-ready.pdf');

The trade is equally literal: the output is an image, so text is no longer selectable or searchable and vector edges are resampled at the chosen DPI. Flattening is a delivery step, not an editing step. Keep the transparent original and flatten a copy

Rotated pages, user units and the page boxes

A rotated page needs care that a naive implementation skips. After rendering, the visual width and height are written back to the MediaBox, the user unit is folded into the effective DPI and the new page size, and the /Rotate entry, the user unit and the auxiliary page boxes are removed

Skipping that produces a file where the flattened image is correct but a viewer rotates it again, or scales it by a user unit that no longer applies. Removing the boxes rather than recomputing them is the right call, because a TrimBox describing the pre-flattening geometry is worse than no TrimBox at all

Annotations and form fields are rendered into the static image by default and the interactive objects removed, which is what a print-ready deliverable wants. If you keep the interactive objects instead, the renderer's annotation pass is switched off so nothing is drawn twice

The version check that scans everything

Saving as PDF 1.3 requires that no transparency remains anywhere, and the pre-save check examines every indirect object, not merely the ones reachable from pages. That is stricter than it first appears and it is correct: an orphaned soft mask left behind by an earlier edit is still in the file, and a consumer that walks objects rather than pages will find it

To make the downgrade achievable, a mark-and-sweep pass runs by default once every target page has been successfully replaced, releasing the unreachable objects. The consequence to plan for is that image, captured page and graphics state identifiers obtained before that sweep may no longer be valid afterwards. Re-acquire the resource identifiers you need before continuing to edit, or disable the sweep and accept that the version cannot then be lowered

Two companions complete the picture. Vector artwork that uses shadings and gradients rather than masks is covered in vector graphics, shaders and gradients, and it is often the better route when the effect can be expressed as a shading. And if the reason you are flattening is a colour requirement rather than a transparency one, the conversion path in recolouring a document to another colour space preserves live text where flattening would not

Soft mask authoring, flattening and the rendering engine behind them ship in one library for Delphi, C++Builder and Free Pascal; the complete feature list is on the PDF Library for Delphi page