Extracting pages from a PDF document and saving them as separate image files, C# edition

Today's Little Program extracts the pages from a PDF document and saves them as separate image files. Why? Because I needed to do that.

I'll start with the PDF Document sample program and change it so that instead of displaying pages on the screen, it saves them to disk.

Take the C# sample and make these changes to Scenario1_Render.xaml.cs:

private async void ViewPage()
    rootPage.NotifyUser("", NotifyType.StatusMessage);

    uint pageNumber;
    if (!uint.TryParse(PageNumberBox.Text, out pageNumber) ||
        (pageNumber < 1) || (pageNumber > pdfDocument.PageCount))
        rootPage.NotifyUser("Invalid page number.", NotifyType.ErrorMessage);

    // New: Ask the user for the output file.
    var save = new FileSavePicker();
    save.FileTypeChoices["PNG image"] = new[] { ".png" };
    var outfile = await save.PickSaveFileAsync();
    if (outfile == null) return;

    Output.Source = null;
    ProgressControl.Visibility = Visibility.Visible;

    // Convert from 1-based page number to 0-based page index.
    uint pageIndex = pageNumber - 1;

    using (PdfPage page = pdfDocument.GetPage(pageIndex))
    using (var transaction = await outfile.OpenTransactedWriteAsync())
        await page.RenderToStreamAsync(transaction.Stream);
    ProgressControl.Visibility = Visibility.Collapsed;

Actually, I kind of gutted the program and replaced it with my own stuff. The only interesting parts that remain from the original program are the Load­Document method (not shown here) which loads the PDF file into the pdfDocument variable, and the part that obtains the PdfPage from the user-specified page number.

We ask for the output file, obtain a write stream to that file, and ask the page to render into that stream. The default options generate a bitmap in PNG format whose size matches the declared Size of the page.

The PNG format was fine for my purposes, but the size wasn't. WinRT view pixels are 1/96 of an inch, so the resulting bitmap was rendered as if printed to a 96 DPI printer. That's the resolution of a first-generation fax machine, which isn't all that great. I wanted 192 DPI, so I needed to render the image at double-size.

    using (PdfPage page = pdfDocument.GetPage(pageIndex))
    using (var transaction = await outfile.OpenTransactedWriteAsync())
        var options = new PdfPageRenderOptions();
        options.DestinationHeight = (uint)(page.Size.Height * 2);
        options.DestinationWidth = (uint)(page.Size.Width * 2);
        await page.RenderToStreamAsync(transaction.Stream, options);

(If I had wanted to change the file format, I'd have set the options.Bitmap­Encoder­Id to something like Bitmap­Encoder.Jpeg­Encoder­Id.)

I didn't have a large document to convert, so changing the page number and clicking the (now-mislabeled) "View" button a dozen times wasn't that big of a deal.

For the rest of the week, I'm going to be translating this program into C++/CX (twice) and JavaScript (twice).


Yes, twice. You'll see.

And then there will be a bonus.

I can sense your anticipation.

Comments (11)
  1. pc says:

    If Raymond can sense our anticipation now, from however long ago he wrote and queued up this blog entry, then all this talk about not having a time machine has just been to direct our attention away from the fact that his psychic powers include being able to read what our minds will be thinking in the future!

    On a more serious note, it's nice to see that Windows has all this PDF handling functionality built in for programmatic use; I only wish that more of it was exposed in the default PDF reading software. At least Windows finally has Print to PDF built in.

  2. God, this async stuff is majorly annoying when you're forced to do it. E.g. when you try to do that with C++/Cx and it won't let you call await right away.
    I'm actually interested how you're solving that tomorrow :)

  3. Smithers says:

    Convert pages of a PDF into individual image files? I remember doing that once; let's see if I've still got the script handy...

    convert -density 450 MLP_CCG_Comprehensive_Rules_3-3.pdf -gravity center -extent 3300x4650 -density 0 -scene 1 input\page.png

    Ah yes, good ol' imagemagick. Complete with extracting the right resolution and cropping the white border. The reason the results are sent to the 'input' directory is that they were used as the input to some further processing (adding covers and pairing pages in the correct order to print as a booklet).

    1. DWalker07 says:

      I wonder what the odds are that "Convert" does about the same as what Raymond listed above?

      Running a compiled program is one thing; seeing how it's done is something else entirely. Once you compile code that does this work, you could probably run one command that doesn't even need parameters..... I don't know what your example is trying to show!

      1. Joshua says:

        Imagemagic is a library that happens to provide a command line interface too.

      2. 640k says:

        It's showing MS reinventing the wheel, NIH as usual. Apparently more than twice in this case.

        1. xcomcmdr says:

          Not really.
          First, it's just a Little Program, no need to take offense. Talking about "MS reinventing the wheel" for what is only a Little Program in a blog post is quite ridiculous.

          Secondly, in the most recent versons of ImageMagick, "convert" isn't installed by default anymore / doesn't exist. :p

          1. Smithers says:

            s/convert/magick/ then if you're on IM7. I think it still takes all of these arguments.

            If his answer to "why?" really is "because I needed to do that," then Raymond is reinventing the wheel (MS aren't, this is a Little Program, not a product).

            But I suspect the answer is really "because I needed to do that and wanted to play around with a such-and-such API," which is a valid reason to code just about anything.

  4. cheong00 says:

    Am I the only one found it disturbing the nowhere in the documentation for .RenderToStreamAsync() seems to mention the rendered stream is in PNG format?

    The first overload that accepts IRandomAccessStream as parameter simply says "The stream of data, which represents a Portable Document Format (PDF) page's content." and make you think it may mean the function will render the serialized structure of page to stream.

    The second overload that accepts both IRandomAccessStream and PdfPageRenderOptions specifies it's an image, and the type of image can be selected by PdfPageRenderOptions.BitmapEncoderId , but kind of make you think there may be some difference than the first overload.

    1. "Render" means "convert to bitmap" in this context, as in other graphics APIs. This API isn't designed to give you any tools to search, create, or work with PDF as a document, it's purely a convenience API for showing pages. Aside from on-screen viewing, the primary use case is probably generating thumbnails.

  5. Medinoc says:

    This PDF API is UWP-only? Is there no way to use it from desktop apps?

Comments are closed.

Skip to main content