.netでPDFページを画像化する時の解像度指定 への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 マナ  (社会人) 投稿日時 2022/7/25 11:18:51
魔界の仮面弁士様、いつもご回答ありがとうございます。
とりあえず、96という数字はマジックナンバー的に考えて、
しばらく様子を見ようと思います。
ありがとうございました。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/7/22 17:42:21
>>  「600 * page.Size.Width / 72」
ごめんなさい。この投稿は私の勘違いでした。
PdfPageRenderOptions に渡す値は
 DestinationWidth = 「page.Size.Width * dpi / 96」
 DestinationHeight = 「page.Size.Height * dpi / 96」
で求められそうです。※ UInt32 に丸める必要あり


>> /MediaBox[0.0 0.0 595.276 841.89]
>> page.Dimensions.MediaBox
>>  → new Rect(0.0, 0.0, 595.276, 841.89)
これはデバッグ時のミスで、実際は
 new Rect(0.0, 0.0, 793.70135498046875, 1122.52001953125)
でした。申し訳ありません。


>> /ArtBox[53.9492 23.8076 538.19 814.538]
>>  page.Dimensions.ArtBox
>>   → new Rect(71.932266235351562, 31.743467330932617, 645.6544189453125, 1054.3072509765625)
こちらは (96/72) 倍のルールに合致していますね。

 53.9492 * (96 / 72) = 71.9322666666
 23.8076 * (96 / 72) = 31.7434666666
 (538.190 - 53.9492) * (96 / 72) = 645.6544
 (814.538 - 23.8076) * (96 / 72) = 1054.3072


> MediaBox[ 0 0 595.32 841.92]のような記載(弁士様が提示の値に近い値)があるのですが、
> このPDFのpage.Size.Dimensions.MediaBoxの値を見てみると{0,0,793.76,1122.56}に
「595.32」「841.92」が、A4 サイズを示していることは間違いないはずです。

PDF の仕様上、それが points 単位(1/72インチ単位)なのは確実なので、
595.32 pt だと、72 で割って 8.268333333 in。
841.92 pt だと、72 で割って 11.69333333 in。

つまり 210.0156666 mm × 11.69333333 mm 相当であることを示しており、
A4 用紙 210.0 mm × 297.0 mm(8.26772 in × 11.6929 in) に合致します。


しかし、それを PdfPage クラスを通じて読み取ると、元の値の (96/72) 倍の値となり、
.Dimensions.MediaBox 等から返される値も 4 / 3 倍になるようですね。
 595.32 * (96 / 72) = 793.76
 1122.56 * (96 / 72) = 1496.746666666


72 という値の根拠は、PDF 側のルール(1/72 インチ単位)によるもの。
96 という単位は、Windows におけるDIUs 単位(1/96 インチ単位)とすれば、意味としては通ります。
https://docs.microsoft.com/ja-jp/xamarin/xamarin-forms/creating-mobile-apps-xamarin-forms/summaries/chapter05#pixels-points-dps-dips-and-dius


今の所、.Size や .Dimensions の単位系について言及された公式資料は発見できていません。

OS の何かの設定(画面の DPI 設定とか?)や、PDF ファイル依存の何かによって
異なる単位系になることがあるか否かと問われると…現状は心当たりがありません。
何か追加情報が得られるまでは、とりあえず固定値と思っておいて良いのではないでしょうか。


PdfPage クラスのソースコードを読み取ることが出来れば良いのだけれどなぁ。
投稿者 マナ  (社会人) 投稿日時 2022/7/22 11:27:26
魔界の仮面弁士様、いつもご回答ありがとうございます。
非常に勉強になります。

> という換算式です。そして、PdfPage 側のサイズは「1/72 インチ単位」。
>
> つまり、幅に関しては
>  inch = 「page.Size.Width / 72」

これなんですけど、page.Size.WidthのpageはPdfPageのインスタンスですよね?
実は、私の環境でA4縦のPDFのpage.Size.Widthを見てみると793.760009765625で、
どうもこの計算と合わないのです。
確かに、こちらにあるA4縦のPDFをテキストエディターで開いてみると、
MediaBox[ 0 0 595.32 841.92]のような記載(弁士様が提示の値に近い値)があるのですが、
このPDFのpage.Size.Dimensions.MediaBoxの値を見てみると{0,0,793.76,1122.56}になっていました。

ちょうど、英文のレターサイズのPDF(8.5×11インチ)のものがあったので
これのpage.Size.Widthを見てみると816でした。
よくよく考えると、どうやら私の環境では72ではなく96で計算すると合うようです。
これは何かのプロパティの設定が必要なのでしょうか?
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/7/21 18:01:15
> そもそも PDF 文書側には、「解像度」という情報がありません。
「210mm」のデータを 600 dpi で出力したいという話なら、
 210[mm] * (5 / 127)[inch / mm] * 600[dot / inch] = 4960.6299[dot]
という換算式です。そして、PdfPage 側のサイズは「1/72 インチ単位」。

つまり、幅に関しては
 inch = 「page.Size.Width / 72」
で求まるので、あとは 600dpi で出力したい場合のピクセル数は
 600 * page.Size.Width / 72
で求められるのでは無いでしょうか?
Size で都合が悪い場合は Dimensions から求めるということで。


> pdf をメモ帳等で開いてみると、境界ボックスが /MediaBox や /CropBox などとして
> 座標情報が記録されていることを確認できるかと思います。
> 実際の Dimensions プロパティの内容と比較してみてください。

手元の PDF ファイルを開いてみると、
/Rotate 0
/MediaBox[0.0 0.0 595.276 841.89]
/ArtBox[53.9492 23.8076 538.19 814.538]
などと記録されていました。(PDF 単位 なので、72 で割るとインチになる値)

実際に読み込ませてみたところ
 page.Dimensions.MediaBox
  → new Rect(0.0, 0.0, 595.276, 841.89)
 page.Dimensions.ArtBox
  → new Rect(71.932266235351562, 31.743467330932617, 645.6544189453125, 1054.3072509765625)
として読み込まれました。


> 内部値が 595.276 だったり 595.320 だったりといった差が生まれることがあります。
> 595.276 × 25.4 ÷ 72 ≒ 210.000144 mm
> 595.320 × 25.4 ÷ 72 ≒ 210.015666 mm

上記の誤差はあれど、600 dpi として出力させるとなると、先ほどの
 「600 * page.Size.Width / 72」
の算出式により
 600 * 595.276 / 72 = 4960.6333
 600 * 595.320 / 72 = 4961.0000
となるので、
>> DestinationWidth = 4961, DestinationHeight = 7016 とすることで
の値に合致した値が算出されますね。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/7/21 17:31:10
PdfPageRenderOptions の SourceRect に描画元の領域を指定し、
Destination{Width|Height} を指定することで切り出します。
https://qiita.com/shotasakamoto/items/f5a011dffd4e9117e4ea

PdfPageRenderOptions.DestinationHeight
PdfPageRenderOptions.DestinationWidth
の単位は デバイス依存ピクセル (DIPs) なので、
実際の出力先(プリンター、画面、画像ファイルなど)が求める解像度に応じて
算出したピクセル値をセットします。


ページのサイズは、各 PdfPage の Size プロパティ (Windows.Foundation.Size クラス) から
得られますので、DestinationWidth に対して dpi に応じた値を乗算して UInt32 化して渡します。
https://docs.microsoft.com/ja-jp/uwp/api/windows.data.pdf.pdfpage.size?view=winrt-22621


あるいはトンボ付き PDF などに備えて、Dimensions プロパティから
境界ボックス「MediaBox」「CropBox」「BleedBox」「TrimBox」「ArtBox」のそれぞれを
Rect クラスとして得る方法もあります;マナさんが実際に試されたのはこの方法ですよね。

※境界ボックスの意味については下記を参照。
https://www.antenna.co.jp/pdf/reference/pdf-point.html

pdf をメモ帳等で開いてみると、境界ボックスが /MediaBox や /CropBox などとして
座標情報が記録されていることを確認できるかと思います。
実際の Dimensions プロパティの内容と比較してみてください。


> プログラム側でPDFのサイズが分からないと

上記で述べた通り、ページのインチ数なら得られます。


> この96dpiというのがどの環境でも適用できるのかが分からず、

その値は PDF 側の解像度ではなく、画像出力時の解像度にあたるものかと。
恐らくは Windows 系なので 96 を採用したということで。


そもそも PDF 文書側には、「解像度」という情報がありません。

PDF で使われるすべての座標やサイズは、
1/72 インチを 1 単位とした座標系で記録される仕様です。
これはあくまでもインチというデバイス非依存の長さ情報であって、
ドットやピクセルといったデバイス依存の長さ情報ではありません。

デバイス依存であるということは、出力系(プリンターなのか、画面なのかなど)によって
1 インチあたりの物理サイズ、いわゆる dpi が決まることになります。
拡大縮小表示する場合もそうですよね。


強いて言えば、ページ内に埋め込まれた画像データにおいては、
DPI 情報が埋め込まれていることもあります(DPI を含まない事もあります)。
ただしこれは、ドキュメントを光学スキャナなどで撮影して画像ファイル化した際に、
元資料のサイズを表すための「参考値」に使われる程度のものであり、
PDF ページの解像度を示しているわけではありません。


また、A4 サイズ(短辺 210.00mm) の PDF ファイルを作ったとしても、出力ソフトによって
内部値が 595.276 だったり 595.320 だったりといった差が生まれることがあります。

とはいえ、インチやミリメートルに換算すると
 595.276 ÷ 72 ≒ 8.2677222 in
 595.320 ÷ 72 ≒ 8.2683333 in
 
 595.276 × 25.4 ÷ 72 ≒ 210.000144 mm
 595.320 × 25.4 ÷ 72 ≒ 210.015666 mm
という値ですので、精度的には無視できる程度でしょうけれどね。
投稿者 マナ  (社会人) 投稿日時 2022/7/21 12:22:11
魔界の仮面弁士様、ご回答ありがとうございます。
あと、私の言葉が足りないようで、申し訳ございません。

dpi⇔pixel換算は分かるのですが、その場合、処理中のPDFページのサイズ
(mmやインチの寸法)を知る方法はありますか?
対象とするPDFはA4縦だけとは限らず、プログラム側でPDFのサイズが分からないと
dpi⇔pixel換算ができないと思いました。

PdfPageRenderOptionsに何も指定しない場合、必ず96dpi換算が行われているというのであれば、
処理しようとするPDFのページサイズを知らなくとも、先程のC#のブログを参考に
任意のdpiの画像のピクセルを計算することができるのですが、この96dpiというのが
どの環境でも適用できるのかが分からず、今回の質問をさせて頂いた次第です。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/7/21 10:52:25
> 例えばA4縦のPDFを600dpi指定で
> 4961×7016ピクセルのpng画像を作成できていました。

A4 とは、短辺が 210mm、長辺が297mmです。

国際インチにおける1インチは正確に25.4 mmと定められているので、
 210mm = 1050 / 127 インチ(約 8.2677 インチ)
 297mm = 1485 / 127 インチ(約11.6929 インチ)
ということになります。これを 600 dpi で表すと、
 210mm * 600 dpi = 630000 / 127 dots (約4960.62992 ドット) ≒ 4961 ピクセル
 297mm * 600 dpi = 891000 / 127 dots (約7015.74803 ドット) ≒ 7016 ピクセル
として求められる計算です。

> どうやって指定dpiのサイズを計算すればいいか分かりません。

96 dpi であれば、
 210mm * 96 dpi = 100800 / 127 dots (約793.70079 ドット) ≒ 794 ピクセル
 297mm * 96 dpi = 142560 / 127 dots (約1122.51969 ドット) ≒ 1123 ピクセル
ですし、72 dpi であれば、
 210mm * 96 dpi = 75600 / 127 dots (約595.27559 ドット) ≒ 595 ピクセル
 297mm * 96 dpi = 106920 / 127 dots (約841.88976 ドット) ≒ 842 ピクセル
となります。
投稿者 魔界の仮面弁士  (社会人) 投稿日時 2022/7/21 10:33:43
> 96.0という数値は何を意味しているのでしょうか?
Windows においては、100% 設定のディスプレイ環境において
1 論理インチが 96dot となります。96 dot per inch すなわち 96dpi。

https://ascii.jp/elem/000/004/038/4038068/
https://ja.wikipedia.org/wiki/Dpi
投稿者 マナ  (社会人) 投稿日時 2022/7/21 10:12:30
こんにちは!
現在作成しているツールでは、PDFのページを画像化(今の所24ビットpng)するために、
別途Ghostscriptをインストールし、VBからこれを呼び出すようなことをしています。
ただ、以下の問題があります。

・Ghostscriptのインストールが必要。
・PDFに組み込まれたフォント名表記によってはGhostscript側でエラーとなる。
 (cmapのフォント名定義の修正が必要)
・Ghostscriptのセキュリティの問題でバージョン変更があるが、
 バグのあるバージョンもあったりするので、管理が大変。

そこで調べてみると、こちらの掲示板にPDFの画像化に関する質問と回答がありました。

■Vb.netでPDFをTiffに変換したい
 http://rucio.cloudapp.net/ThreadDetail.aspx?ThreadId=30630

こちらを参考に、画像化に成功しました。
ただ、解像度の指定方法が分かりませんでした。
Ghostscriptでは解像度指定があり、例えばA4縦のPDFを600dpi指定で
4961×7016ピクセルのpng画像を作成できていました。

とりあえずは、PdfPageのRenderToStreamAsyncに渡すPdfPageRenderOptionsで
DestinationWidth = 4961, DestinationHeight = 7016 とすることで
直接サイズ指定を行うことにより、目的の画像を取得できました。

ただ、扱うPDFはA4縦とは限らない(B5やレターサイズ、あるいは横向き)
ので、どうやって指定dpiのサイズを計算すればいいか分かりません。

色々と調べると、以下のブログで、C#ですが、200dpiの指定を行う方法が記載されていました。

■C#でPDFを表示する(WPF)
 https://water2litter.net/rye/post/c_pdf_render/

//実際のコード
PdfPageRenderOptions renderOptions = new PdfPageRenderOptions();
renderOptions.DestinationWidth = (uint)Math.Round(page.Dimensions.ArtBox.Width / 96.0 * 200.0);

おそらく上記の200.0というのがdpiを示しているものと思いますが、
その隣の96.0という数値は何を意味しているのでしょうか?
この値は固定にして、これをそのまま参考にしてもいいのでしょうか?