Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RenderTargetBitmap renders image of a wrong size

My task is to show the user a thumbnail of each page of his XPS document. I need all the images to be small, so I render them with dpi set to 72.0 (I have googled that size of an A4 sheet with dpi 72.0 is 635x896). Basically, I do the following:

        List<BitmapImage> thumbnails = new List<BitmapImage>();
        documentPaginator.ComputePageCount();
        int pageCount = documentPaginator.PageCount;
        for (int i = 0; i < pageCount; i++)
        {
            DocumentPage documentPage = documentPaginator.GetPage(i);
            bool isLandscape = documentPage.Size.Width > documentPage.Size.Height;
            Visual pageVisual = documentPage.Visual;
            //I want all the documents to be less or equals to A4
            //private const double A4_SHEET_WIDTH = 635;
            //private const double A4_SHEET_HEIGHT = 896;
            //A4 sheet size in px, considering 72 dpi
            RenderTargetBitmap targetBitmap = new RenderTargetBitmap(
            (int)(System.Math.Min(documentPage.Size.Width, A4_SHEET_WIDTH)),
            (int)(System.Math.Min(documentPage.Size.Height, A4_SHEET_HEIGHT)),
            72.0, 72.0,
            PixelFormats.Pbgra32);
            targetBitmap.Render(pageVisual);
            BitmapFrame frame = BitmapFrame.Create(targetBitmap);
            PngBitmapEncoder encoder = new PngBitmapEncoder();
            encoder.Frames.Add(frame);
            BitmapImage image = new BitmapImage();
            using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
            {
                encoder.Save(ms);
                image.BeginInit();
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.StreamSource = ms;
                if (isLandscape)
                {
                    image.Rotation = Rotation.Rotate270;
                }
                image.EndInit();
            }
            thumbnails.Add(image);
        }

But when I render a document page (A4) its size is actually 846x1194 instead of the one I expected. I tried to make the dpi lower (48.0) and the image's size has grown even bigger (I guess, I just do not quite undrestand what dpi is and how it affects the document). I tried to make dpi=96.0, and the size went smaller. I set one of images from the collection of instances of the class BitmapImage generated by the code above as source for the Image control (I am creating a WPF application) and in case dpi is set to 96.0 my program looks like this:

Thumbnail displayed incorrectly
As you can see, part of the page is not shown at all, it does not fit inside the Image control, even though the size of the control is set to 635x896 that's why according to the code above, the image must be displayed correctly and all the text must fit.
What result do I expect in a nutshell: I am trying to create thumbnails of the pages of a document, but I want them to be smaller, relative to some number (I am sorry, I am not quite sure how do I say such stuff in English, basically if the document's page width is 1200 px I want it to be 1200/n, where n is the "some number" that I mentioned earlier), but if the shrinked image's size is still bigger than 635x896 I want the size to be 635x896.
Thanks in advance. And also I am sorry for my bad English.

like image 466
Cracker Avatar asked Oct 30 '12 17:10

Cracker


1 Answers

First of all, DPI means Dots Per Inch, or pixel per inch. In the case of rendering an A4 page - which is 21 by 29.7 cm - to a bitmap at 72 DPI, you would end up with a bitmap of the following size:

  • Width = 21 cm / (2.54 cm/inch) * 72 pixel/inch = 595 pixel
  • Height = 29.7 cm / (2.54 cm/inch) * 72 pixel/inch = 842 pixel

Besides this you shouldn't care too much about DPI, with one exception: WPF rendering is done at 96 DPI. This means that an A4-sized page of your document is rendered to a 794 x 1123 bitmap. As a reminder:

  • Width = 21 cm / (2.54 cm/inch) * 96 pixel/inch = 794 pixel
  • Height = 29.7 cm / (2.54 cm/inch) * 96 pixel/inch = 1123 pixel

Hence, 794 x 1123 should be the size of your RenderTargetBitmap when it contains a page that is exactly A4. If the page size is less than A4, the bitmap should be smaller. If on the other hand the page is larger than A4, it should be scaled down to 794 x 1123. And that is the trick. Instead of rendering the page visual directly into the RenderTargetBitmap, you can put the visual into a ContainerVisual with a ScaleTransform as shown below.

for (int i = 0; i < paginator.PageCount; i++)
{
    DocumentPage page = paginator.GetPage(i);
    double width = page.Size.Width;
    double height = page.Size.Height;
    double maxWidth = Math.Round(21.0 / 2.54 * 96.0); // A4 width in pixels at 96 dpi
    double maxHeight = Math.Round(29.7 / 2.54 * 96.0); // A4 height in pixels at 96 dpi
    double scale = 1.0;
    scale = Math.Min(scale, maxWidth / width);
    scale = Math.Min(scale, maxHeight / height);

    ContainerVisual containerVisual = new ContainerVisual();
    containerVisual.Transform = new ScaleTransform(scale, scale);
    containerVisual.Children.Add(page.Visual);

    RenderTargetBitmap bitmap = new RenderTargetBitmap(
        (int)(width * scale), (int)(height * scale), 96, 96, PixelFormats.Default);

    bitmap.Render(containerVisual);

    ...
}
like image 148
Clemens Avatar answered Nov 15 '22 00:11

Clemens