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
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
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
// 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