sungaila / PDFtoImage

A .NET library to render PDF files into images.
https://www.sungaila.de/PDFtoImage/
MIT License
152 stars 15 forks source link

changing width or height does not preserve the aspect ratio of the image #22

Closed be5her closed 1 year ago

be5her commented 1 year ago

when using the function PDFtoImage.Conversion.ToImage and setting either width or height not both the aspect ration of the output image is broken

be5her commented 1 year ago

Code Example

Original image: width: 546, height: 340 image.pdf

using var stream = await FileSystem.OpenAppPackageFileAsync("image.pdf");
var ms = new MemoryStream();
stream.CopyTo(ms);
byte[] ReportPDFByteArray = ms.ToArray();

var result = PDFtoImage.Conversion.ToImage(InputPDFbyteArr, width:200);
var encodedData = result.Encode(SkiaSharp.SKEncodedImageFormat.Png, 100);
MemoryStream ImageMs= new();
encodedData.SaveTo(ImageMs);
var imageResp =  ImageMs.ToArray();

await File.WriteAllBytesAsync(_pdfFilePath, imageResp);

output image: width: 200, height: 255

image

sungaila commented 1 year ago

Hi @be5her,

you are correct, there is no option to keep the aspect ratio right now. I'll add an optional property to keep the aspect ration (rounded to whole pixels).

In the meantime you can use this workaround:

public static SKBitmap ToImageScaled(byte[] pdfAsByteArray, string? password = null, int page = 0, int dpi = 300, int? width = null, int? height = null, bool withAnnotations = false, bool withFormFill = false)
{
    var size = PDFtoImage.Conversion.GetPageSize(pdfAsByteArray, page, password);

    if (width == null && height != null)
    {
        width = (int)Math.Round((size.Width / size.Height) * height.Value);
    }

    if (width != null && height == null)
    {
        height = (int)Math.Round((size.Height / size.Width) * width.Value);
    }

    return PDFtoImage.Conversion.ToImage(pdfAsByteArray, password, page, dpi, width, height, withAnnotations, withFormFill);
}
hosamyousof commented 1 year ago

Hi @sungaila,

Thanks for your help.

The equations to calculate the width and the height are not correct:

They should be:


       if (width == null && height != null)
    {
        width = (int)Math.Round((size.Width * height.Value) / size.Height);
    }

        if (width != null && height == null)
    {
        height = (int)Math.Round((size.Height * width.Value) / size.Width);
    }

I appreciate your support and it would be great if you publish an update to fix this.

sungaila commented 1 year ago

Hello @hosamyousof,

yes, I have mixed up width and height in my original comment (it's edited now). Just want to add that both of our equations are correct:

// mine
width = (int)Math.Round((size.Width / size.Height) * height.Value);

$$width = {actualWidth \over actualHeight} * desiredHeight$$

$$width = {actualWidth {1 \over actualHeight}} desiredHeight$$

$$width = {{actualWidth * desiredHeight} \over actualHeight}$$

// yours
width = (int)Math.Round((size.Width * height.Value) / size.Height);
sungaila commented 1 year ago

@be5her @hosamyousof The PDFtoImage 2.2.0 release includes the optional parameter withAspectRatio for this use case. Could you please give it a try and tell me if it works properly?

be5her commented 1 year ago

hi @sungaila I treid it with the the optional parameter withAspectRatio set to true and it worked perfectly, However I still think that the default pehavier is a bit weired, because it neither keeps the original length nor does it maintains the aspect ratio, It seems to change it to an arbitrary length, is there a particular reason for that??

sungaila commented 1 year ago

The default value is set to false so that other projects do not break when updating to 2.2.0.

The arbitrary length is defined by the PDF itself. For example: you have a PDF in the ISO A4 paper format. That's 8.24 in in width and 11.69 in in height.

To translate this into pixels you have to multiply with 72 dpi (dots per inch). So your width becomes 593 px ≈ 8.24 in * 72 dpi and height becomes 842 px ≈ 11.69 in * 72 dpi.

If you call PDFtoImage.Conversion.ToImage(InputPDFbyteArr, dpi: 72); you will get 593 px in width and 842 px in height.

But if you call PDFtoImage.Conversion.ToImage(InputPDFbyteArr, width: 200); on this PDF, the result will be 200 px * 842 px.

However, if you call PDFtoImage.Conversion.ToImage(InputPDFbyteArr); instead, it will upscale from 72 dpi to 300 dpi (optional parameter defaults to 300) and result in 2,470 px * 3,508 px.

I can see how this is confusing because this behavior isn't documented properly. Imho resizing one dimension only (without keeping the aspect ratio) is a very rare use case anyway. And yes, it should use the aspect ratio by default but I cannot change this compatibility reasons (sadly).

Thanks for helping out on this project @be5her and @hosamyousof! :-)