PDFファイルをjpeg の画像ファイルに変換するアプリ作ってみた
ダウンロード
PDFtoJPEG.zip
ギットハブ
github.com
農林水産省 トマトの種類と見分け方
http://www.maff.go.jp/j/pr/aff/1708/pdf/1708_04.pdf
ここからダウンロードしたPDFファイルをアプリにドラッグアンドドロップ で
最初のページだけが表示される
保存ボタンを押すと全てのページがjpeg ファイルとして保存される、場所は元のPDFファイルと同じフォルダ
保存処理完了したところ
2ページのPDFだったので、できあがったjpeg ファイルは2つ
1708_04_1.jpg
1708_04_2.jpg
作成されたjpeg ファイル2つ
作成されるファイル名
ファイル名は元のPDFファイル名+_+連番になる。連番は総ページ数の桁を0埋め
1708_04.pdfが元のPDFファイル名でページ総数が2なら作成されるのは
1708_04_1.jpg 1708_04_2.jpg
元のPDFのページ総数が30なら2桁なので
元のPDFファイル名_01.jpg
元のPDFファイル名_02.jpg
…
元のPDFファイル名_30.jpg
になる
dpiの指定
といっても画像のサイズの指定に使うだけで、保存されるjpg画像のdpiはWindows 基本の96dpi
DPI
数字のボタンで切り替わる。jpeg 変換はこの表示状態でされる。PDFファイルによるけど、だいたい縦ピクセル が2000前後になるDPIが良さそう
文字はきれいに拡大されるけど画像の場合
PDFに埋め込まれている画像の解像度が低いとガタガタになる
変換処理時間
環境は
OS:Windows 10
CPU:Ryzen 5 2400G
メモリ:DDR4 2666
dpiを高くして画像が大きくなるほど時間がかかる
高dpi > 低dpi
別のアプリ
PDF-XChange Viewer
色々細かい設定ができる素晴らしいアプリ
今まではこのアプリでjpgに変換していた
2ページのPDFファイルの変換時間
300dpiでの変換時間は4秒、600dpiでの変換時間は12秒
今回作ったアプリでは
300dpiでの変換時間は2秒、600dpiでの変換時間は6秒
約半分の処理時間にできた
仕様
jpeg の品質は85固定
保存されるjpeg のdpiは96固定
表示できるのは最初のページだけ
処理の途中で中止はできない、終わるまでひたすら待つ
処理の進捗プログレスバー なし、終わるまでひたすら待つ
シングルスレッド
保存先に同名ファイルがあっても問答無用で上書き
<Window x Class ="PDFtoJPEG.MainWindow"
xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns local ="clr-namespace:PDFtoJPEG"
mc Ignorable ="d"
Title ="MainWindow" Height ="800" Width ="600" >
<Window Resources>
<Style TargetType ="Button" >
<Setter Property ="Margin" Value ="10,5,0,5" />
</Style>
<Style TargetType ="TextBlock" >
<Setter Property ="Margin" Value ="10,5,0,5" />
<Setter Property ="VerticalAlignment" Value ="Center" />
</Style>
</Window Resources>
<Grid>
<Grid RowDefinitions>
<RowDefinition Height ="auto" />
<RowDefinition/>
</Grid RowDefinitions>
<StackPanel Orientation ="Horizontal" >
<Button x Name ="ButtonSave" Content ="保存" Click ="ButtonSave_Click" />
<Button Name ="ButtonDpi96" Content ="96" Click ="ButtonDpi96_Click" />
<Button Name ="ButtonDpi150" Content ="150" Click ="ButtonDpi150_Click" />
<Button Name ="ButtonDpi300" Content ="300" Click ="ButtonDpi300_Click" />
<Button Name ="ButtonDpi600" Content ="600" Click ="ButtonDpi600_Click" />
<TextBlock Name ="tbDpi" Text ="今のdpi" />
<TextBlock Name ="tbHeight" Text ="縦ピクセル" />
<TextBlock Name ="tbPageCount" Text ="ページ総数" />
</StackPanel>
<ScrollViewer Grid Row ="1" HorizontalScrollBarVisibility ="Auto" VerticalScrollBarVisibility ="Auto" >
<Image Name ="MyImage" Stretch ="None" UseLayoutRounding ="True" />
</ScrollViewer>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using Windows.Data.Pdf;
namespace PDFtoJPEG
{
< summary >
</ summary >
public partial class MainWindow : Window
{
private PdfDocument MyPdfDocument;
private string MyPdfDirectory;
private string MyPdfName;
private double MyDpi;
public MainWindow()
{
InitializeComponent();
this .Title = "PDFtoJPEG" ;
this .AllowDrop = true ;
this .Drop += MainWindow_Drop;
}
private void MainWindow_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false ) { return ; }
string [] filePath = (string [])e.Data.GetData(DataFormats.FileDrop);
LoadPdf(filePath[0 ]);
}
private async void LoadPdf(string filePath)
{
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(filePath);
try
{
MyPdfDocument = await PdfDocument.LoadFromFileAsync(file);
MyPdfDirectory = System.IO.Path.GetDirectoryName(filePath);
MyPdfName = System.IO.Path.GetFileNameWithoutExtension(filePath);
MyDpi = 96 ;
DisplayImage(0 , 96 );
tbPageCount.Text = $"ページ数 : {MyPdfDocument.PageCount.ToString()}" ;
}
catch (Exception)
{ }
}
private async void DisplayImage(int pageIndex, double dpi)
{
if (MyPdfDocument == null ) { return ; }
MyDpi = dpi;
using (PdfPage page = MyPdfDocument.GetPage((uint )pageIndex))
{
double h = page.Size.Height;
var options = new PdfPageRenderOptions();
options.DestinationHeight = (uint )Math.Round(page.Size.Height * (dpi / 96.0 ), MidpointRounding.AwayFromZero);
tbDpi.Text = $"dpi : {dpi.ToString()}" ;
tbHeight.Text = $"縦ピクセル : {options.DestinationHeight.ToString()}" ;
using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
{
await page.RenderToStreamAsync(stream, options);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream.AsStream();
image.EndInit();
MyImage.Source = image;
MyImage.Width = image.PixelWidth;
MyImage.Height = image.PixelHeight;
}
}
}
private void ButtonDpi96_Click(object sender, RoutedEventArgs e)
{
DisplayImage(0 , 96 );
}
private void ButtonDpi150_Click(object sender, RoutedEventArgs e)
{
DisplayImage(0 , 150 );
}
private void ButtonDpi300_Click(object sender, RoutedEventArgs e)
{
DisplayImage(0 , 300 );
}
private void ButtonDpi600_Click(object sender, RoutedEventArgs e)
{
DisplayImage(0 , 600 );
}
< summary >
</ summary >
< param name ="pdfDocument" > </ param >
< param name ="dpi" > </ param >
< param name ="directory" > </ param >
< param name ="fileName" > </ param >
< param name ="pageIndex" > </ param >
< param name ="quality" > </ param >
< param name ="keta" > </ param >
< returns ></ returns >
private async Task SaveSub2(PdfDocument pdfDocument, double dpi, string directory, string fileName, int pageIndex, int quality, int keta)
{
using (PdfPage page = pdfDocument.GetPage((uint )pageIndex))
{
var options = new PdfPageRenderOptions();
options.DestinationHeight = (uint )Math.Round(page.Size.Height * (dpi / 96.0 ), MidpointRounding.AwayFromZero);
using (var stream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
{
await page.RenderToStreamAsync(stream, options);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream.AsStream();
image.EndInit();
JpegBitmapEncoder encoder = new JpegBitmapEncoder();
encoder.QualityLevel = quality;
encoder.Frames.Add(BitmapFrame.Create(image));
pageIndex++;
string renban = pageIndex.ToString("d" + keta);
using (var fileStream = new FileStream(
System.IO.Path.Combine(directory, fileName) + "_" + renban + ".jpg" , FileMode.Create, FileAccess.Write))
{
encoder.Save(fileStream);
}
}
}
}
private async void ButtonSave_Click(object sender, RoutedEventArgs e)
{
if (MyPdfDocument == null ) { return ; }
this .IsEnabled = false ;
try
{
int keta = MyPdfDocument.PageCount.ToString().Length;
var MyTasks = new List<Task>();
for (int i = 0 ; i < MyPdfDocument.PageCount; i++)
{
MyTasks.Add(SaveSub2(MyPdfDocument, MyDpi, MyPdfDirectory, MyPdfName, i, 85 , keta));
}
for (int i = 0 ; i < MyTasks.Count; i++)
{
await MyTasks[i];
}
MessageBox.Show("処理完了" );
}
catch (Exception ex) { MessageBox.Show($"なんかエラー出たわ \n {ex.Message} \n {ex.ToString()}" ); }
finally { this .IsEnabled = true ; }
}
}
}
なんとか書いてみたけど例によってあんまりわかっていないし、残念なのはシングルスレッドの処理になってしまったこと
各タスク実行のところのforをParallel.forに書き換えてみたけど変化なし
awaitとかTaskとかがまだわからん、マルチスレッドに対応できれば最低でも今の4倍速くなるんだけどねえ
そもそもPDF-XChange Viewerでの変換をもっと速くできないかなあと思って、今使っているのが4年前のバージョンなので、新しいのならマルチスレッドに対応しているかもと思ってダウンロードしたら、めんどくさいインストール仕様に変更されていたのでWPFでできないものかとググったのが始まりだっただけに残念、それでも2倍速くなったから、そこは満足
今気づいたのが、セキュリティとかで保護されているPDFファイルも問答無用で画像に変換できる
たとえば
www.asus.com
より引用、ダウンロードした日本語マニュアルPDF
パスワードセキュリティのあるPDFファイル
これだと
PDF-XChange Viewerではエクスポートの項目が、グレーアウトで画像に変換できないけど、今回のアプリなら
うれしい誤算、個人で使う分には問題ないと思う
参照したところ
water2litter.net
blog.hiros-dot.net
qiita.com
www.atmarkit.co.jp
関連記事
続きは2日後の
gogowaten.hatenablog.com