PDFをページごとに画像変換(PNG形式)するツール

木曜日 , 3, 2月 2022 1 Comment

指定されたPDFをページごとに分解、指定解像度の画像ファイル(PNGファイル)に変換したのち、指定ディレクトリに保存するWindows用プログラムを作成したので公開します。
ひとつのPDFから下図のように複数のPNG形式の画像ファイルに変換することができます。
※2022/02/03 14:00 GUI対応版を公開しました

PNGファイルに変換されたPDFの例

では、本当にそのまま画像化されているのか変換元のPDFをAcrobat Readerで確認してみます。
Acrobat Readerで変換元のPDFを表示したときは下図のように表示されます。
改ページがずれて次ページに少しだけはみ出てしまったパターンもそのまま画像化されていることが確認できます。

Acrobat Readerで変換元PDFファイルを表示
プログラムの流れとしては、以前記事にした『Microsoft OCRを用いてPDFからテキストを抽出する』とほぼ同じで、OCRエンジンに流していた画像を解像度指定でPNG形式に変換できるようにしました。

ソースコード

ソースコードからビルドする際の注意事項

このプログラムはOCRを実現するためWinRT APIを使用しています。

NuGetなどでMicrosoft.Windows.SDK.Contractsをインストールしてください。

Microsoft.Windows.SDK.Contractsをインストールするためにプロジェクトの形式をPackageReferenceに移行する必要があるかも知れません。

移行の手順は『packages.config から PackageReference への移行』に詳しく記載されています。

移行が行えない環境の場合は、UwpDesktopなどWinRT呼び出しのために必要なパッケージをプロジェクトに追加することで解決できる可能性があります。

//
// PDFをページごとに画像変換(PNG形式)に変換するツール
//
// This software is distributed under the license of NYSL.
// http://www.kmonos.net/nysl/
// 
//
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;
using Windows.Data.Pdf;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using BitmapFrame = System.Windows.Media.Imaging.BitmapFrame;

namespace PDF2PNG
{
    public class Program
    {

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args">
        /// 0: PDF
        /// 1: 生成した画像ファイルの保存先(省略可。省略時PDFファイル名+タイムスタンプでフォルダ作成)
        /// 2: DPI(省略可。省略時300)
        /// </param>
        /// <returns></returns>
        public static int Main(string[] args)
        {
            if (args.Length < 1)
            {
                Console.Out.WriteLine("[使い方]");
                Console.Out.WriteLine("PDF2PNG 変換元PDFファイルのパス [PNGファイルの保存先フォルダ] [DPI]\n");
                Console.Out.WriteLine("・PNGファイルの保存先フォルダ省略時、変換元PDFファイルの存在するフォルダにPNG保存用フォルダを作成して使用");
                Console.Out.WriteLine(" 生成されるフォルダ名: 《変換元PDFファイル名から拡張子を取り除いたもの》_《タイムスタンプYYYYMMDDHHMMSS形式》");
                Console.Out.WriteLine("・DPI省略時、デフォルト値として300を使用");
                return 1;
            }

            return _mainasync(args).Result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        private static async Task<int> _mainasync(string[] args)
        {
            // PDFファイルの場所
            string pdfpath = args[0];
            if (!System.IO.File.Exists(pdfpath))
            {
                Console.Error.WriteLine($"指定されたPDFファイルが存在しません(PDF={pdfpath})");
                return 1;
            }
            // PNGファイルの保存先
            string savepath = (args.Length >= 2) ? args[1] : 
                System.IO.Path.Combine(System.IO.Path.GetDirectoryName(pdfpath), $"{System.IO.Path.GetFileNameWithoutExtension(pdfpath)}_{DateTime.Now:yyyyMMddHHmmss}");
            if (!System.IO.Directory.Exists(savepath)) {
                if (!System.IO.Directory.CreateDirectory(savepath).Exists)
                {
                    Console.Error.WriteLine($"保存先ディレクトリの作成に失敗しました(PATH={savepath})");
                    return 1;
                }
            }
            // DPI
            int dpi;
            if (!int.TryParse((args.Length >= 3) ? args[2] : "300", out dpi))
            {
                Console.Error.WriteLine("DPIは数値を設定してください");
                return 1;
            }
            return await new Program().Perform(pdfpath, savepath, dpi);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdfpath"></param>
        /// <param name="saveFolderPath"></param>
        /// <param name="dpi"></param>
        /// <returns></returns>
        public async Task<int> Perform(string pdfpath, string saveFolderPath, int dpi = 300)
        {
            // PNGファイルの基本ファイル名を決定
            var basename = System.IO.Path.GetFileNameWithoutExtension(pdfpath);

            // PDFを読み込み、ページごとに画像に変換
            using (var instream = new MemoryStream(System.IO.File.ReadAllBytes(pdfpath)))
            using (var inrastream = instream.AsRandomAccessStream())
            {
                // 変数pdfdocは処理のあいだ開いていないとビットマップへのレンダリングに失敗する
                var pdfdoc = await PdfDocument.LoadFromStreamAsync(inrastream);

                for (uint pageIndex = 0; pageIndex < pdfdoc.PageCount; pageIndex++)
                {
                    // ファイル名を決定
                    var imageFilePath = System.IO.Path.Combine(saveFolderPath, $"{basename}_{pageIndex + 1:0000}.png");

                    using (var pdfpage = pdfdoc.GetPage(pageIndex))
                    using (var outstream = new MemoryStream())
                    using (var outratream = outstream.AsRandomAccessStream())
                    {
                        // ページの内容をBitmapにレンダリング
                        var renderOptions = new Windows.Data.Pdf.PdfPageRenderOptions();
                        // 解像度などを指定のものに変換
                        renderOptions.DestinationWidth = (uint)((pdfpage.Size.Width / (96d / 72d)) * (dpi / 72d));
                        renderOptions.DestinationHeight = (uint)((pdfpage.Size.Height / (96d / 72d)) * (dpi / 72d));
                        await pdfpage.RenderToStreamAsync(outratream, renderOptions);

                        // Bitmapの内容をPNGに変換
                        using (var image = System.Drawing.Image.FromStream(outstream))
                        {
                            SavePng(ConvertToBitmapSource(image as Bitmap), imageFilePath);
                        }
                    }
                }
            }

            return 0;
        }

        /// <summary>
        /// BitmapをBitmapSourceに変換する
        /// </summary>
        /// <param name="bmp"></param>
        /// <returns></returns>
        private static System.Windows.Media.Imaging.BitmapSource ConvertToBitmapSource(Bitmap bmp)
        {
            using (var dest = new Bitmap(bmp.Width, bmp.Height))
            using (var g = Graphics.FromImage(dest))
            {
                g.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
                var hbitmap = dest.GetHbitmap();
                BitmapSource bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                    BitmapSizeOptions.FromWidthAndHeight(bmp.Width, bmp.Height));
                DeleteObject(hbitmap);

                return bitmapSource;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bitmapSource"></param>
        /// <param name="pngFilePath"></param>
        /// <returns></returns>
        private static bool SavePng(BitmapSource bitmapSource, string pngFilePath)
        {
            using (var outstream = new System.IO.FileStream(pngFilePath, System.IO.FileMode.Create))
            {
                var encoder = new PngBitmapEncoder();

                encoder.Frames.Add(BitmapFrame.Create(bitmapSource));
                encoder.Save(outstream);
                outstream.Flush();
            }

            return true;
        }
    }
}

ビルド

※ソースコードから実行プログラムを作成するしたい場合、以下の手順で行うことができます。



  1. ダウンロードしたソース一式を展開しお使いのVisual Studioに読み込みます。

    ※Visual Studioをお持ちでない場合、こちらからCommunity版が無償で入手可能です。

  2. Visual Studio中のNuGetの復元を行い必要なファイルをダウンロードします。2022-01-24-1_thumb1
  3. ビルドします。

使い方

実行例

PDF2PNGは三種類の方法で実行することができます。

コマンドから使用する

  • WindowsのコマンドプロンプトやPowerShellなどから下記のコマンドを投入します。

PDF2PNG.exe <変換元PDFのパス>  [PNGファイルの保存先フォルダ] [DPI]

  • 第1パラメータで指定されたPDFを読み込み、ページごとに分解し指定された第3パラメータで指定されたDPI(解像度)を使いPNG形式の画像ファイルに変換します。
    変換した画像ファイルは第2パラメータで指定されたフォルダに保存されます。
    PNGファイルの命名規約は下記のとおりです。

《変換元のPDFファイル名から拡張子を除いたもの》_《ページ番号(前ゼロ4桁)》.png

プログラムの実行時に第2パラメータの保存先フォルダを省略した場合、PDFファイルの存在するフォルダにPDFファイル名+タイムスタンプでフォルダを作成し、作成したフォルダに画像を保存します。
第3パラメータのDPIを省略した場合、デフォルト値として300を使用します。

エクスプローラ上でドラッグアンドドロップする

  • エクスプローラ上で変換したいPDFをPDF2PNG.exeにドラッグアンドドロップします。
    この場合DPIは300固定、PDFの存在するフォルダに画像保存用のフォルダが新たに作成されPNGファイルが格納されます。

エクスプローラの「送る」を使用する

  • エクスプローラの「送る」にPDF2PNG.exeを項目追加します。
    変換したいPDFを選択し、右クリックメニューから[送る]-[PDF2PNG.exe]を選択クリックします。
    この場合DPIは300固定、PDFの存在するフォルダに画像保存用のフォルダが新たに作成されPNGファイルが格納されます。

    ※「送る」への追加方法は、NEC LAVIE公式サイトの『Windows 10で「送る」メニューに項目を追加する方法』に詳しく紹介されています。(NEC以外のパソコンでも使えるテクニックです)

更新履歴

    • 2022/02/03 Ver.1.0 初版公開

注意事項

    • PDFの品質・再現度は、マイクロソフト社の提供するAP(Windows.Data.Pdf)に依存しています。
      特に印刷・デザイン業務などでお使いの方は業務で使用するに十分な品質・再現度が確保されているか事前に十分にご確認の上ご使用ください。
One thought on “ : PDFをページごとに画像変換(PNG形式)するツール”
  • […] 『PDFをページごとに画像変換(PNG形式)するツール』にGUIをつけてみました。基本的な操作はコマンド版と同じで、変換元PDF、変換後の画像ファイル保存先、DPI(解像度)を指定し実行ボタンを押すと処理が始まります。画像ファイルに変換後、指定フォルダの内容をGUIアプリケーションの画面下部に一覧表示します。 […]

  • PDFをページごとに画像変換(PNG形式)するツールをGUIにしてみる – RIALAB. へ返信する コメントをキャンセル

    メールアドレスが公開されることはありません。 が付いている欄は必須項目です

    このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください