pdf-lib icon indicating copy to clipboard operation
pdf-lib copied to clipboard

Issue with Jpeg's Orientation Exif when embedding in pdf

Open AliceLoeser opened this issue 2 years ago • 16 comments

What were you trying to do?

I am trying to create a pdf file from multiple jpeg files.

How did you attempt to do it?

We allow the user to upload jpeg, png, pdf files in our application. They can then select those files to "join" them, which creates a pdf. This works fine, except when the jpeg has an Orientation exif, it is not taken into account by the pdf-lib, and they see the jpeg as being rotated when they create the pdf.

  const newPdf = await PDFDocument.create()
  const appendNextCheckedDocument = async (newPdf, documentToAppend, previewURL) => {
    const documentBytes = await (await fetch(previewURL)).arrayBuffer()

    if (documentToAppend.content_type.match('image/png') || documentToAppend.content_type.match('image/jpeg')) {
      const image = await (documentToAppend.content_type.match('image/png')
        ? newPdf.embedPng(documentBytes)
        : newPdf.embedJpg(documentBytes))
      const page = newPdf.addPage([image.width, image.height])
      page.drawImage(image, { x: 0, y: 0, width: image.width, height: image.height })
    }
  }

This works well with files that to not have an orientation exif.

What actually happened?

What happened is that the generated pdf contains image that look like they are rotated (they are not rotated, it is just that the Orientation exif is not taken into account, and they look rotated to the user).

You can find a user report here, unfortunately it is in French: https://community.doctolib.com/t5/logiciel-m%C3%A9dical/bug-import-des-photos/m-p/72835

You can see that the user imports pictures taken from his phone on the left side of the screenshot (so, with orientation exif since they took it in portrait mode), but when the pdf is created (right side of the screenshot) the pictures seem rotated.

What did you expect to happen?

I would expect the orientation exif to be taken into account, so that the jpeg is rotated accordingly when appending it to a pdf file. Or, if I had an option that I could pass as a parameter, I could extract the exif orientation metadata and pass it to embedJpg.

How can we reproduce the issue?

You can use embedJpg with a file that has an orientation exif metadata. To get that you can take a picture with your phone and orient the screen while doing it, or you can use some examples here: https://github.com/recurser/exif-orientation-examples

Version

1.17.1

What environment are you running pdf-lib in?

React Native

Checklist

  • [X] My report includes a Short, Self Contained, Correct (Compilable) Example.
  • [X] I have attached all PDFs, images, and other files needed to run my SSCCE.

Additional Notes

Thank you!

AliceLoeser avatar Jul 26 '22 08:07 AliceLoeser

I have the same issue. I've tried to embed an image which was rotated using windows media rotate option. The embed returns wrong values for width/ height, but also the image is displayed as before rotation.

enoh-barbu avatar Aug 01 '22 19:08 enoh-barbu

Until a native solution is implemented, you can download the image and use imagemagick's auto-orient before embedding into the PDF.

sobrinho avatar Aug 23 '22 12:08 sobrinho

Until a native solution is implemented, you can download the image and use imagemagick's auto-orient before embedding into the PDF.

Can we ask this for our customers? :)

enoh-barbu avatar Aug 23 '22 13:08 enoh-barbu

convert -auto-orient -strip original.jpg transformed.jpg

Then you can use the transformed.jpg in the script, no need to ask the customer! ;)

sobrinho avatar Aug 23 '22 14:08 sobrinho

convert? this looks like a CLI execution. I'm using the library on the client side

enoh-barbu avatar Aug 23 '22 14:08 enoh-barbu

Oh, I see, I thought you were in back-end mode.

Alright, let me try to find something for you in the front-end side.

sobrinho avatar Aug 23 '22 14:08 sobrinho

I already made some investigations, not that easy :) Thanks though

enoh-barbu avatar Aug 23 '22 14:08 enoh-barbu

Didn't try but can you check if a package like this would do the job for you? https://github.com/onurzorluer/exif-auto-rotate

sobrinho avatar Aug 23 '22 14:08 sobrinho

hmm, looking nice, I'll give a try

enoh-barbu avatar Aug 23 '22 14:08 enoh-barbu

Oh even that one: https://jsfiddle.net/orotemo/obvna6qn/1/

You don't even need a 3rd package, apparently.

sobrinho avatar Aug 23 '22 14:08 sobrinho

Oh even that one: https://jsfiddle.net/orotemo/obvna6qn/1/

You don't even need a 3rd package, apparently.

This one is working only in the browser, it's using css transform on the image, lol

enoh-barbu avatar Aug 23 '22 14:08 enoh-barbu

That's a reference for reading the image, you can use it to detect the rotation and then use the canvas API to rotate it.

I'm not giving you the copy/paste code, just the right path! :)

sobrinho avatar Aug 23 '22 14:08 sobrinho

it's not working that easly, I've tried all possible solutions, even with canvas api, still seeking for something else. but anyway, I would still be interested in having a solution from the library itself.

enoh-barbu avatar Aug 23 '22 14:08 enoh-barbu

How did you try?

If you can read the image EXIF, get the rotation nad then use the canvas API to render the image, rotate it and convert it back to a base64 string, you should be able to embed it.

sobrinho avatar Aug 23 '22 20:08 sobrinho

found solution. it solved my cases. this thread and discussion helped me to find correct way. thank you guys https://github.com/Hopding/pdf-lib/issues/1329#issuecomment-1285534542

ArkanRomanPango avatar Oct 20 '22 13:10 ArkanRomanPango

There is another solution that doesn't require adding any additional libraries. You can get just the image's EXIF orientation from the array buffer using this snippet:

https://stackoverflow.com/a/32490603

Although, I would remove the check to skip images that are not JPEG because modern PNGs can also contain EXIF metadata. Then determine the rotation for each of the 8 possible orientations. But before you draw the image and rotate you need to correct for mirrored images using scale and translate:

    if (orientationCorrection.mirrored === 'x') {
      // scale - flips the image horizontally but keeps it in the same quadrant
      // translate - moves the coordinate system origin to the bottom right and flips the direction of the x-axis
      page.pushOperators(pushGraphicsState(), scale(-1, 1), translate(-pageWidth, 0));
    } else if (orientationCorrection.mirrored === 'y') {
      // scale - flips the image vertically but keeps it in the same quadrant
      // translate - moves the coordinate system origin to the top left and flips the direction of the y-axis
      page.pushOperators(pushGraphicsState(), scale(1, -1), translate(0, -pageHeight));
    }
    ...
    page.drawImage(...);
    orientationCorrection.mirrored && page.pushOperators(popGraphicsState());

After you translate and rotate, your image will be in a new quadrant so you'll have to shift it back to where you want it.

ancheetah avatar Mar 13 '24 23:03 ancheetah