Search code examples
pdfpdf-generationpdfboxmask

Creating a transparency group or setting graphics state soft mask with PDFBox


I have a grayscale image that serves as a soft mask and I want to use it on a group of PDF objects (images or paths).

The mask and the objects do not necessarily use the same transformation matrix, and there might be more than one object to mask, so that excludes the possibility of using the SMask attribute of the ImageXObject dictionary.

So after reading some of the PDF specification, it looks like I should do the following: create a transparency group with the objects to mask, then draw it with the soft mask set on the graphics state.

Will that work? How can I achieve this, preferably with PDFBox?


Here's an example. I have these two images: the mask and another image.

mask image

  • The mask image is 200x200. It is drawn with the matrix [[4 0 100] [0 4 100]].
  • The image is 400x300. It is drawn with the matrix [[2 0 100] [0 2 150]].
  • Additionally, a 400x400 black square is drawn below the image with no transform matrix.

So a transparency group is created with the image and the square, then it's drawn with the mask image. Here's the expected result:

result

Rather ugly as far as the effect goes, but that's just an example.


Solution

  • As far as I can see establishing an extended graphics state soft mask is a very manual task in PDFBox. You can do so as follows:

    try (   PDDocument document = new PDDocument()  ) {
        final PDImageXObject image = RETRIEVE PHOTO IMAGE;
        final PDImageXObject mask = RETRIEVE MASK IMAGE;
    
        PDTransparencyGroupAttributes transparencyGroupAttributes = new PDTransparencyGroupAttributes();
        transparencyGroupAttributes.getCOSObject().setItem(COSName.CS, COSName.DEVICEGRAY);
    
        PDTransparencyGroup transparencyGroup = new PDTransparencyGroup(document);
        transparencyGroup.setBBox(PDRectangle.A4);
        transparencyGroup.setResources(new PDResources());
        transparencyGroup.getCOSObject().setItem(COSName.GROUP, transparencyGroupAttributes);
        try (   PDFormContentStream canvas = new PDFormContentStream(transparencyGroup)   ) {
            canvas.drawImage(mask, new Matrix(400, 0, 0, 400, 100, 100));
        }
    
        COSDictionary softMaskDictionary = new COSDictionary();
        softMaskDictionary.setItem(COSName.S, COSName.LUMINOSITY);
        softMaskDictionary.setItem(COSName.G, transparencyGroup);
    
        PDExtendedGraphicsState extendedGraphicsState = new PDExtendedGraphicsState();
        extendedGraphicsState.getCOSObject().setItem(COSName.SMASK, softMaskDictionary);
    
        PDPage page = new PDPage(PDRectangle.A4);
        document.addPage(page);
        try (   PDPageContentStream canvas = new PDPageContentStream(document, page)   ) {
            canvas.saveGraphicsState();
            canvas.setGraphicsStateParameters(extendedGraphicsState);
            canvas.setNonStrokingColor(Color.BLACK);
            canvas.addRect(100, 100, 400, 400);
            canvas.fill();
            canvas.drawImage(image, new Matrix(400, 0, 0, 300, 100, 150));
            canvas.restoreGraphicsState();
        }
    
        document.save(new File(RESULT_FOLDER, "SoftMaskedImageAndRectangle.pdf"));
    }
    

    The result:

    screen shot

    If I were you, though, I would not use a bitmap image for the soft mask but instead a PDF gradient. The result most likely will be much less pixelated.