drunkenfist
drunkenfist

Reputation: 3036

Replacing images with same resource in PDFBox

I have a pdf containing 2 blank images. I need to replace both the images with 2 separate images using PDFBox. The problem is, both the blank images appear to have the same resource. So, if I replace one, the other one is replaced with the same image as well.

I followed this example and tried overriding the processOperator() method and replaced the images based on the imageHeight. However, it still ends up replacing both the images with the same image. This is my code thus far:

protected void processOperator( PDFOperator operator, List arguments ) throws IOException
    {
        String operation = operator.getOperation();
        if( INVOKE_OPERATOR.equals(operation) )
        {
            COSName objectName = (COSName)arguments.get( 0 );
            Map<String, PDXObject> xobjects = getResources().getXObjects();
            PDXObject xobject = (PDXObject)xobjects.get( objectName.getName() );
            if( xobject instanceof PDXObjectImage )
            {
                PDXObjectImage blankImage = (PDXObjectImage)xobject;
                int imageWidth = blankImage.getWidth();
                int imageHeight = blankImage.getHeight();

                System.out.println("Image width >>> "+imageWidth+" height >>>> "+imageHeight);

                // Check if it is blank image 1 based on height
                if(imageHeight < 480){
                    File logo = new File("abc.jpg");
                    BufferedImage bufferedImage = ImageIO.read(logo);
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    ImageIO.write( bufferedImage, "jpg", baos );
                    baos.flush();
                    byte[] logoImageInBytes = baos.toByteArray();
                    baos.close();

                    // label will be used to replace the blank image
                    label = logoImageInBytes;
                }

                BufferedImage img = ImageIO.read(new ByteArrayInputStream(label));

                BufferedImage resizedImage = Scalr.resize(img, Scalr.Method.BALANCED, Scalr.Mode.FIT_EXACT, img.getWidth(), img.getHeight());
                ByteArrayOutputStream baos = new ByteArrayOutputStream();                       
                ImageIO.write(resizedImage, "jpg", baos);                       

                // Replace empty image in template with the image generated from shipping label byte array
                PDXObjectImage validImage = new PDJpeg(doc, new ByteArrayInputStream(baos.toByteArray()));
                blankImage.getCOSStream().replaceWithStream(validImage.getCOSStream());
            }

Now, when I remove the if block which checks if (imageHeight < 480), it prints the imageHeight as 30 and 470 for the blank images. However, when I add the if block, it prints the imageHeight as 480 and 1500 and never goes inside the if block because of which both the blank images end up getting replaced by the same image.

What's going on here? I'm new to PDFBox, so I am unsure if my code is correct.

Upvotes: 2

Views: 2697

Answers (2)

mkl
mkl

Reputation: 95918

While first thinking about a generic way to actually replace the existing Image by the new Images, I agree with @TilmanHausherr that a more simple solution would be to simply add an extra content stream with two images in the size / position you need covering the existing Image.

This approach is easier to implement (even generically) and less error-prone than actual replacement.

In a generic solution we do not have the Image positions beforehand. To determine them, we can use this helper class (which essentially is a rip-off of the PDFBox example PrintImageLocations):

public class ImageLocator extends PDFStreamEngine
{
    private static final String INVOKE_OPERATOR = "Do";

    public ImageLocator() throws IOException
    {
        super(ResourceLoader.loadProperties("org/apache/pdfbox/resources/PDFTextStripper.properties", true));
    }

    public List<ImageLocation> getLocations()
    {
        return new ArrayList<ImageLocation>(locations);
    }

    protected void processOperator(PDFOperator operator, List<COSBase> arguments) throws IOException
    {
        String operation = operator.getOperation();
        if (INVOKE_OPERATOR.equals(operation))
        {
            COSName objectName = (COSName) arguments.get(0);
            Map<String, PDXObject> xobjects = getResources().getXObjects();
            PDXObject xobject = (PDXObject) xobjects.get(objectName.getName());
            if (xobject instanceof PDXObjectImage)
            {
                PDXObjectImage image = (PDXObjectImage) xobject;
                PDPage page = getCurrentPage();
                Matrix matrix = getGraphicsState().getCurrentTransformationMatrix();

                locations.add(new ImageLocation(page, matrix, image));
            }
            else if (xobject instanceof PDXObjectForm)
            {
                // save the graphics state
                getGraphicsStack().push((PDGraphicsState) getGraphicsState().clone());
                PDPage page = getCurrentPage();

                PDXObjectForm form = (PDXObjectForm) xobject;
                COSStream invoke = (COSStream) form.getCOSObject();
                PDResources pdResources = form.getResources();
                if (pdResources == null)
                {
                    pdResources = page.findResources();
                }
                // if there is an optional form matrix, we have to
                // map the form space to the user space
                Matrix matrix = form.getMatrix();
                if (matrix != null)
                {
                    Matrix xobjectCTM = matrix.multiply(getGraphicsState().getCurrentTransformationMatrix());
                    getGraphicsState().setCurrentTransformationMatrix(xobjectCTM);
                }
                processSubStream(page, pdResources, invoke);

                // restore the graphics state
                setGraphicsState((PDGraphicsState) getGraphicsStack().pop());
            }
        }
        else
        {
            super.processOperator(operator, arguments);
        }
    }

    public class ImageLocation
    {
        public ImageLocation(PDPage page, Matrix matrix, PDXObjectImage image)
        {
            this.page = page;
            this.matrix = matrix;
            this.image = image;
        }

        public PDPage getPage()
        {
            return page;
        }

        public Matrix getMatrix()
        {
            return matrix;
        }

        public PDXObjectImage getImage()
        {
            return image;
        }

        final PDPage page;
        final Matrix matrix;
        final PDXObjectImage image;
    }

    final List<ImageLocation> locations = new ArrayList<ImageLocation>();
}

(ImageLocator.java)

In contrast to the example class this helper stores the locations in a list instead of printing them.

We now can cover existing images using code like this:

try (   InputStream resource = getClass().getResourceAsStream("sample.pdf");
        InputStream left = getClass().getResourceAsStream("left.png");
        InputStream right = getClass().getResourceAsStream("right.png");
        PDDocument document = PDDocument.load(resource) )
{
    if (document.isEncrypted())
    {
        document.decrypt("");
    }

    PDJpeg leftImage = new PDJpeg(document, ImageIO.read(left));
    PDJpeg rightImage = new PDJpeg(document, ImageIO.read(right));

    // Locate images
    ImageLocator locator = new ImageLocator();
    List<?> allPages = document.getDocumentCatalog().getAllPages();
    for (int i = 0; i < allPages.size(); i++)
    {
        PDPage page = (PDPage) allPages.get(i);
        locator.processStream(page, page.findResources(), page.getContents().getStream());
    }

    // cover images
    for (ImageLocation location : locator.getLocations())
    {
        // Decide on a replacement
        PDRectangle cropBox = location.getPage().findCropBox();
        float center = (cropBox.getLowerLeftX() + cropBox.getUpperRightX()) / 2.0f;
        PDJpeg image = location.getMatrix().getXPosition() < center ? leftImage : rightImage;

        AffineTransform transform = location.getMatrix().createAffineTransform();

        PDPageContentStream content = new PDPageContentStream(document, location.getPage(), true, false, true);
        content.drawXObject(image, transform);
        content.close();
    }

    document.save(new File(RESULT_FOLDER, "sample-changed.pdf"));
}

(OverwriteImage)

This sample covers all images on the left half of their respective page with left.png and all others with right.png.

Upvotes: 1

duffy356
duffy356

Reputation: 3718

I have no implementation or example, but I want to illustrate you a possible way to do what you want by the following steps:

  1. Since you need 2 Images (lets tell them imageA and imageB) in the pdf instead of 1 (which is the blank one). You have to add both of them to the pdf.
  2. save the file temporary - optional, it could work without rewriting the pdf
  3. reopen the file - optional, if you don't need step 2, you also don't need this step
  4. Then replace the blank image with imageA or imageB
  5. Remove the blank image from the pdf
  6. Save the pdf

Upvotes: 0

Related Questions