1

I'm working with PDFBox and trying to rotate an image and have it position correctly on the screen. The design editor I'm using outputs the following information about images that may be useful.

Image bounding box top-left coords (I'm using the bottom left coords to better suit PDFBox coord space.)

Image rotation in degrees

Image width & height

The translation appears to be off.

// Rotation
AffineTransform rotation = new AffineTransform();
rotation.rotate(Math.toRadians(360 - element.getAngle()),
    element.getLeft() + scaledWidth/2,
    adjustedYPos + scaledHeight/2);
    stream.transform(new Matrix(rotation));

// Position & scale
AffineTransform mat = new AffineTransform(scaledWidth,
        0,
        0,
        scaledHeight,
        element.getLeft(),
        adjustedYPos);

// Draw the final image
stream.drawImage(pdfImage, new Matrix(mat));

Rotations are based on the center of the image as an anchor point.

Liam
  • 135
  • 11
  • And the unwanted result of your code is...? Ok, I assume it is not positioned correctly, but is it totally off? Or just a bit? – mkl Nov 20 '17 at 05:13
  • The images are near the desired location and they have the correct rotation. The issue appears to be related to the fact the bounding boxes. When a user creates a design, the output json file stores the final bounding box of the image in whatever rotation it is. I can get within the ballpark as far as positioning goes but because we don't have the original bounding box before the user started rotating the image we can't rebuild the process they took when trying to position the image on our PDF. – Liam Nov 20 '17 at 06:16
  • But the bounding box your data refer to is (as usual) the minimal one, isn't it? In that case it should be simple math to determine all data. And how do you determine `adjustedYPos`? – mkl Nov 20 '17 at 08:51
  • Yes The minimal is correct. Well to calculate it would be easy if I could access the four corner positions of the image. But as far as I'm aware there's no way for me to do so? Unless there's something I've overlooked. adjustedYPos = page.getMediaBox().getHeight() - (element.getTop() + scaledHeight) – Liam Nov 20 '17 at 09:41
  • Did my answer help? – mkl Nov 24 '17 at 15:19
  • Hey mkl, thanks for the in depth response. I'll take a look at this tomorrow afternoon and see if I can resolve my issue. Will respond after doing so. – Liam Nov 26 '17 at 10:22

1 Answers1

4

You can correctly position images using code like this:

void placeImage(PDDocument document, PDPage page, PDImageXObject image, float bbLowerLeftX, float bbLowerLeftY, float width, float height, float angle) throws IOException {
    try (   PDPageContentStream contentStream = new PDPageContentStream(document, page, AppendMode.APPEND, true, true)   ) {
        float bbWidth = (float)(Math.abs(Math.sin(angle))*height + Math.abs(Math.cos(angle))*width);
        float bbHeight = (float)(Math.abs(Math.sin(angle))*width + Math.abs(Math.cos(angle))*height);
        contentStream.transform(Matrix.getTranslateInstance((bbLowerLeftX + .5f*bbWidth), (bbLowerLeftY + .5f*bbHeight)));
        contentStream.transform(Matrix.getRotateInstance(angle, 0, 0));
        contentStream.drawImage(image, -.5f*width, -.5f*height, width, height);
    }
}

(PlaceRotatedImage utility method)

This method accepts coordinates as they are meaningful in the context of PDF, i.e. coordinate values and dimensions according to the default user space coordinate system of the given page (y values increasing upwards, the origin arbitrary but fairly fairly often in the lower left), (bounding) box given by lower left corner, angles as in math in counterclockwise radians...

If you need the parameters differently, you can fairly easily adapt the method, though. If you e.g. get the upper left corner of the bounding box instead of the lower left, you can simply subtract the bounding box height determined in the method as bbHeight to calculate the lower left y coordinate used here.

You can use this method like this:

PDPage page = ...;

PDRectangle mediaBox = page.getMediaBox();
float bbLowerLeftX = 50;
float bbLowerLeftY = 100;

try (   PDPageContentStream contentStream = new PDPageContentStream(document, page)   ) {
    contentStream.moveTo(bbLowerLeftX, mediaBox.getLowerLeftY());
    contentStream.lineTo(bbLowerLeftX, mediaBox.getUpperRightY());
    contentStream.moveTo(mediaBox.getLowerLeftX(), bbLowerLeftY);
    contentStream.lineTo(mediaBox.getUpperRightX(), bbLowerLeftY);
    contentStream.stroke();
}

PDImageXObject image = PDImageXObject.createFromByteArray(document, IOUtils.toByteArray(resource), "Image");
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, image.getWidth(), image.getHeight(), (float)(Math.PI/4));
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, .5f*image.getWidth(), .5f*image.getHeight(), 0);
placeImage(document, page, image, bbLowerLeftX, bbLowerLeftY, .25f*image.getWidth(), .25f*image.getHeight(), (float)(9*Math.PI/8));

(PlaceRotatedImage test testPlaceByBoundingBox)

This code draws the left and bottom lines corresponding to the left and bottom side of the given lower left bounding box coordinates and draws an image at different magnifications and angles with the constant given lower left bounding box corner.

The result looks like this:

screenshot


You can find more information on the calculation of the bounding box sizes in these answers:

mkl
  • 90,588
  • 15
  • 125
  • 265
  • Hey mkl. I looked at this today, sorry it took some time. It turns out the position data I'm referring too isn't actually the bounding box top,left but the rotated images top,left. This made things easier than expected. I was able to slightly modify the provided implementation and everything is now working as expected. I'll mark your answer as correct because it answers my original question but also only required minor modification to work with the data as it actually ended up. – Liam Nov 27 '17 at 04:25
  • Ah, so the main issue was in the documentation of the inputs you receive... ;) – mkl Nov 27 '17 at 05:13