Vb.netでPDFをTiffに変換したい

タグの編集
投稿者 kazu  (社会人) 投稿日時 2021/7/29 14:08:28
現在、社内で有料ソフトを使用して、PDFファイルをtiffファイルに変換していますが、ソフトを使用せずに、VB.net上でPDF読込→Tiffで出力することは可能でしょうか。
当方、画像に関するコードを作成すること自体が、滅多になく、ネットで検索しても、有料ソフトを使うという記事が圧倒的に多く、自作でコード作成可能かどうかご教授お願いします。
投稿者 (削除されました)  () 投稿日時 2021/7/30 12:02:16
(削除されました)
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/7/30 13:15:26
WinRT に PDF 表示機能があります。
https://docs.microsoft.com/ja-jp/uwp/api/windows.data.pdf?view=winrt-18362

NuGet で「Microsoft.Windows.SDK.Contracts」を参照した上で、
「Imports System.IO」
「Imports Windows.Data.Pdf」
を記述しておきます。

(1) PdfDocument を LoadFromStreamAsync または LoadFromFileAsync メソッドで生成
(2) PdfDocument の GetPage メソッドで PdfPage を取得(Integer ではなく UInteger を指定すること)
(3) 保存先の Stream を用意して(FileStream でも MemoryStream でも良い)から、
 AsRandomAccessStream 拡張メソッドで、IRandomAccessStream を準備する
(4) PdfPage の RenderToStreamAsync で、(3) の保存先に画像として保存される
 画像形式として tiff を指定する場合には、RenderToStreamAsync メソッドの引数で
 PdfPageRenderOptions の BitmapEncoderId に TIFF の Encoder Id を指定します。
Dim opt As New PdfPageRenderOptions() With {.BitmapEncoderId = Guid.Parse("0131be10-2001-4c5f-a9b0-cc88fab64ce8")}

TIFF 以外の WIC ID については下記をご覧ください。
https://docs.microsoft.com/en-us/windows/win32/wic/-wic-guids-clsids

この方法だと、1 ページごとに別々の画像に保存していくことになります。
各ページの画像を 1 枚のマルチページ TIFF に収める場合は、下記が参考になるでしょう。
https://dobon.net/vb/dotnet/graphics/createmultitiff.html


実装例を載せておきます。
Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim tif As String = "D:\example.tiff"
    Dim pdf As String = "D:\example.pdf"

    Using pdfFile As New FileStream(pdf, FileMode.Open, FileAccess.Read),
          pdfStream = pdfFile.AsRandomAccessStream()
        Dim pdfDoc = Await PdfDocument.LoadFromStreamAsync(pdfStream)
        Dim tiffICI As ImageCodecInfo = ImageCodecInfo.GetImageEncoders().First(Function(enc) enc.MimeType = "image/tiff")
        Dim opt As New PdfPageRenderOptions() With {.BitmapEncoderId = Guid.Parse(WICTiffEncoder)}
        Using page = pdfDoc.GetPage(0UI),
            tifFile = File.OpenWrite(tif),
            tifStream = tifFile.AsRandomAccessStream()
            Await page.RenderToStreamAsync(tifStream, opt)
            Await tifStream.FlushAsync()
        End Using
        Using tiffImage As New Bitmap(New MemoryStream(File.ReadAllBytes(tif)))
            '最初のページは MultiFrame で Save する 
            Dim epMultiFrame As New EncoderParameters() With {.Param = {New EncoderParameter(Encoder.SaveFlag, CLng(EncoderValue.MultiFrame))}}
            tiffImage.Save(tif, tiffICI, epMultiFrame)

            For p = 1UI To pdfDoc.PageCount - 1UI
                Using page = pdfDoc.GetPage(p),
                      pageMemory = New MemoryStream(),
                      tifStream = pageMemory.AsRandomAccessStream()
                    Await page.RenderToStreamAsync(tifStream, opt)
                    Await tifStream.FlushAsync()
                    Using imgStream = tifStream.AsStreamForRead(),
                          pageImage = Image.FromStream(imgStream)

                        '2ページ目以降は FrameDimensionPage を SaveAdd する 
                        Dim epFrameDimensionPage As New EncoderParameters() With {.Param = {New EncoderParameter(Encoder.SaveFlag, CLng(EncoderValue.FrameDimensionPage))}}
                        tiffImage.SaveAdd(pageImage, epFrameDimensionPage)
                    End Using
                End Using
            Next

            ' 全ページを追加したら、Flush を SaveAdd する 
            Dim epFlush As New EncoderParameters() With {.Param = {New EncoderParameter(Encoder.SaveFlag, CLng(EncoderValue.Flush))}}
            tiffImage.SaveAdd(epFlush)
        End Using
    End Using
    Process.Start(tif)
End Sub
投稿者 snowmansnow  (社会人) 投稿日時 2021/7/31 20:05:16

 るきお様、魔界の仮面弁士様、kazu様
 横から申し訳ございません。

 魔界の仮面弁士様のコードを、
 フォームアプリケーションで、試してみようと思いました。
 
 NuGet で「Microsoft.Windows.SDK.Contracts」を参照した上で、
 「Imports System.IO」
 「Imports Windows.Data.Pdf」
 を記述しておきます。
 と記載がございましたので、
 ソリューショネクスプローラの
  参照のNugetパッケージの管理でインストールしたつもりです。

 フォームにボタンを設置して、
 魔界の仮面弁士様のコードをコピペしてみました。
 
 Imports Windows.Data.Pdfのところで
 「Importsステートメントは不要です」と緑の波線が出て、
 pdfFile.AsRandomAccessStream()などのところに赤の波線が出て、
 「FileStreamのメンバーではありません」とか
 PdfDocumentなどのところに赤の波線が出て
 「宣言されてません」などとなってしまいます。

 NuGet で「Microsoft.Windows.SDK.Contracts」を参照した上~が出来てないのかなぁ?
 と思いますが、困って、
  https://www.nuget.org/packages/Microsoft.Windows.SDK.Contracts/10.0.19041.1で
 Download packageでdlしたもの
 microsoft.windows.sdk.contracts.10.0.19041.1.nupkg
 も、参照できません。
 初心者の質問で、横から申し訳ございませんが、宜しかったら教えて頂きたいです。



 

投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/8/2 10:17:27
済みません。説明が不足していましたね。

というか、定数宣言も漏れていますね…。
投稿時に整理した時にマジックナンバーに戻したつもりだったのですが、見落としていたようで。
Private Const WICTiffEncoder As String = "0131be10-2001-4c5f-a9b0-cc88fab64ce8"



> 参照のNugetパッケージの管理でインストールしたつもりです。
今回の対象 OS は Windows 10 Version 1803 (Build 17134) 以降となります。

また、packages.config ではなく PackageReference を使うことになるため、
Visual Studio 2017  バージョン 15.7 以降または
Visual Studio 2019  バージョン 16.0 以降が必要です。
Visual Studio 2022  以降でももちろん  OK ですが、上記未満だと動作しません。

1. [ツール]-[オプション] ダイアログを開く。
2. オプションダイアログから [NuGet パッケージ マネージャー]-[全般]で、既定のパッケージ管理形式を【PackageRerence】に設定する
3. この状態で新規に Windows Forms アプリケーションを作成。ターゲット フレームワークとしては
 「.NET Framework 4.6.1 以上 4.8 以下」または「.NET Core 3.0 / 3.1」「.NET 5」「.NET 6 Preview」とします。
4. [ツール]-[NuGet パッケージ マネージャー]-[ソリューションの NuGet パッケージの管理]で、nuget.org から【Microsoft.Windows.SDK.Contracts】を検索する。
5. この時、右側ペインのバージョン一覧から OS 環境に沿ったものを選択する。
 •10.0.19041.1    → Win10 version 2004 (Build 19041) 以降
 •10.0.18362.2005 → Win10 version 1903 (Build 18362) / 1909 (Build 18363)
 •10.0.17763.1000 → Win10 version 1809 (Build 17763)
 •10.0.17134.1000 → Win10 version 1803 (Build 17134)
6. Form1 に Button1 を貼って、下記のコードを記載。(前回貼ったコードから少し修正してあります)
https://gist.github.com/Benshi/2693d42eb6fbd5ec9601d14845783bd0
投稿者 kazu  (社会人) 投稿日時 2021/8/2 15:24:25
魔界の仮面弁士様

ご教授いただき、ありがとうございます。
早速、今から試してみます。

また、結果、ご報告させていただきます。
投稿者 (削除されました)  () 投稿日時 2021/8/2 16:26:16
(削除されました)
投稿者 kazu  (社会人) 投稿日時 2021/8/2 16:31:58
魔界の仮面弁士様

この度はご教授頂き、ありがとうございました。
ご教授いただいた方法で、無事にPDFをTiffに変換することができました。
大変助かりました。

後は、読込及び、変換後の保存先を指定する箇所を指定できる様にするのみになりました。
こちらに関しては、他のプロジェクトでも実施しているので、そちらを流用すればOKかと思います。

誠にありがとうございました。
投稿者 snowmansnow  (社会人) 投稿日時 2021/8/2 21:49:40

 魔界の仮面弁士様、kazu様
 横から申し訳ございませんでした。
 
  やっと、ソリューションエクスプローラの参照に、
  Microsoft.Windows.SDK.Contractsが表示されるようになりました。
  
  マルチtiffもシングルtiffも作成できるのを確認できて嬉しいです。
  kazu様、魔界の仮面弁士様のおかげで勉強になりました。

  kazu様が、まだお返事してなかったのに気づきませんで、質問してごめんなさい。
  今後は気を付けます。

  また宜しくお願い致します。

投稿者 kazu  (社会人) 投稿日時 2021/8/3 09:13:49
魔界の仮面弁士様

解決後、一つ問題が発生したため、追記でご教授願いたく。
ご教授頂いたコードで、常に解像度を水平・垂直共に300dpi、ビットの深さを8に固定することは可能でしょうか。

,
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/8/3 11:09:49
> snowmansnow さん
フィードバックが無いため、結局、何が原因だったのかのは分かりませんが、
恐らくは PackageReference ではなく packages.config が使われていたか、
ターゲットフレームワークのバージョンが古すぎたのが原因だと想像します。

従来の packages.config モードだとプロジェクトごとにファイルが保持されるので
ディスク容量を圧迫しやすく、ソース管理(Git、Subversion 等)との相性も良くありません。
packages.config の開発は既に終了しているので、新規開発では PackageReference に
切り替えていくことをお奨めします。
https://docs.microsoft.com/ja-jp/nuget/consume-packages/migrate-packages-config-to-package-reference


> kazu  さん
先のコードだと恐らく、96dpi、LZW 圧縮、深度 32bit になりますね。

画像化する際の PdfPageRenderOptions で指定できる範囲には無いので、
PDF を PNG か TIFF で出力した後で、GDI+ 側で処理してみてください。

先ほどの GitHub Gist を更新して、300dpi と 8bit カラー対応のコードにしておきました。
dpi と物理解像度は別なので、幅と高さは適宜調整してください。
投稿者 kazu  (社会人) 投稿日時 2021/8/3 13:03:45
魔界の仮面弁士様

追加質問のご教授ありがとうございます。
高さと幅は希望する値に設定して、出力したのですが、解像度は300dpi なったのですが、 8bitとなるはずの箇所が、出力したTiffファイルのプロパティを確認したところ、32bitとなっており、また、うまく圧縮できていないのか、サイズが1.53 MBとかなり大きくなってしまっています。
何か対応策はございますでしょうか。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2021/8/3 13:19:44
> 8bitとなるはずの箇所が、出力したTiffファイルのプロパティを確認したところ、32bitとなっており
追加訂正しました。

変更前
Dim opt As New PdfPageRenderOptions() With {.BitmapEncoderId = Guid.Parse(WICTiffEncoder)}

変更後
Dim opt As New PdfPageRenderOptions() 'With {.BitmapEncoderId = Guid.Parse(WICTiffEncoder)} 



> うまく圧縮できていないのか
圧縮方式を変更してみて、どれを試してみてもまだ大きすぎるのであれば、
8bitカラーではなく、8bitグレースケールを選択してみるのはどうでしょうか。
https://dobon.net/vb/dotnet/graphics/grayscale.html
投稿者 kazu  (社会人) 投稿日時 2021/8/3 14:21:23
魔界の仮面弁士様

度重なるご教授有難うございました。
無事に8bit、圧縮されて250KB程の解像度300のTiffファイルが出来ました。
画像処理に関して全く携わっていなかった為、度重なる???にご回答頂き有難うございました。
投稿者 snowmansnow  (社会人) 投稿日時 2021/8/3 18:07:50

 魔界の仮面弁士様、kazu様
  
  修正で動きましたので、その理由を入れずに申し訳ございません。
  魔界の仮面弁士様の、御指示通り、

  恐らくは PackageReference ではなく packages.config が使われていたか~

  の所をPackageReferenceに修正して、
  一旦Microsoft.Windows.SDK.Contractsをアンインストールして、インストールし直しました。
  バージョンは、最新版の安定版で再インストール致しました。
  
  すると、ソリューションエクスプローラの参照に、
  Microsoft.Windows.SDK.Contractsが表示されるようになりました。

  その結果、緑の波線や、赤の波線は消えて、動くようになりました。

  以上のような状況でございます

  るきお様、魔界の仮面弁士様、kazu様
  皆さん、投稿も気を付けますので、またお願いします