using System; using System.IO; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Media.Imaging; using System.Reflection; // Assembly. // UWP-APIの使用 using Windows.Data.Pdf; // ref:https://water2litter.net/rum/post/cs_pdf_wpf/ using EncType = System.Drawing.Imaging.Encoder;// 別名 using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名 namespace ImageParams { using EncType = System.Drawing.Imaging.Encoder;// 別名 using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名 /// /// BitMapのSaveに使うImage Encodeパラメータの設定値の生成. /// var enc = new ImageParams.EncData("jpeg"); /// //enc.SetImageType("PNG"); /// bitmap.Save(xxx, enc.GetInfo(),enc.GetParams()); /// /// public class EncData { // SEE:https://dobon.net/vb/dotnet/graphics/encoderparameters.html private string ImageType = "jpeg"; public EncData(string imageType = "jpeg") { SetImageType(imageType); } public long JpegQuality { get; set; } = 91;// 指定しないときの値は91と一致する /// /// イメージパラメータを設定する こんな感じで /// /// /// public EncData SetImageType(string imageType) { imageType = imageType.ToLower(); if (imageType == "jpeg" || imageType == "jpg") { ImageType = "jpeg"; } else if (imageType == "png") { ImageType = "png"; } else if (imageType == "tif" || imageType == "tiff") { ImageType = "tiff"; } else { ImageType = imageType;// 小文字を設定 } return this; } public int SaveImage(System.Drawing.Bitmap bitmap, string path, string imageType = null) { var p = GetParams(); string imType = imageType?.ToLower(); if (p != null) { bitmap.Save(path, GetInfo(imType), GetParams());// imageTypeがnullならSetImageType()のものが使われる } else { // パラメータが空の場合 var imageFormat = new System.Drawing.Imaging.ImageFormat(GetInfo(imType).FormatID); bitmap.Save(path, imageFormat); } return 0; } public System.Drawing.Imaging.EncoderParameters GetParams() { var encList = new List(); //encList.Clear(); // 今はJPEGパラメータだけ encList.Add(new System.Drawing.Imaging.EncoderParameter(System.Drawing.Imaging.Encoder.Quality, JpegQuality)); //... // 結合 var eps = new System.Drawing.Imaging.EncoderParameters(encList.Count); for (var i = 0; i < encList.Count; i++) eps.Param[i] = encList[i]; return eps; } public System.Drawing.Imaging.ImageCodecInfo GetInfo(string it=null) { if ( it != null) { var ici = GetEncoderInfo($"image/{it}"); return ici; } else { var ici = GetEncoderInfo($"image/{ImageType}"); return ici; } } public static void GetSupportedParameters(System.Drawing.Bitmap bitmap1=null) { // https://docs.microsoft.com/ja-jp/dotnet/framework/winforms/advanced/how-to-determine-the-parameters-supported-by-an-encoder // 現状Errorが発生して取得できない try { if (bitmap1 == null) { bitmap1 = new System.Drawing.Bitmap(100, 100); var destBitmapData = bitmap1.LockBits( new System.Drawing.Rectangle(0, 0, 100, 100), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap1.PixelFormat ); bitmap1.UnlockBits(destBitmapData); } var jpgEncoder = GetEncoderInfo(System.Drawing.Imaging.ImageFormat.Tiff); var paramList = bitmap1.GetEncoderParameterList(jpgEncoder.Clsid); var encParams = paramList.Param; for (int i = 0; i < encParams.Length; i++) { Console.WriteLine("Param " + i + " holds " + encParams[i].NumberOfValues + " items of type " + encParams[i].ValueType + "\r\n" + "Guid category: " + encParams[i].Encoder.Guid + "\r\n"); } } catch (Exception e) { Console.WriteLine($"Ignore error ={e.ToString()}"); } } // inner method. //ImageFormatで指定されたImageCodecInfoを探して返す private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(System.Drawing.Imaging.ImageFormat f) { var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders(); foreach (var enc in encs) { if (enc.FormatID == f.Guid) { return enc; } } return null; } private static System.Drawing.Imaging.ImageCodecInfo GetEncoderInfo(string mineType) { //GDI+ に組み込まれたイメージ エンコーダに関する情報をすべて取得 System.Drawing.Imaging.ImageCodecInfo[] encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders(); //指定されたMimeTypeを探して見つかれば返す foreach (System.Drawing.Imaging.ImageCodecInfo enc in encs) { if (enc.MimeType == mineType) { return enc; } } return null; } } } namespace CSRender { class Program { static string outuptImageDir = "";// /O static string dpi = "72.0";// /D #if non static string boxSelect = "C"; //B static string pageRange = ""; //P static string inputList = ""; //L static string offset = ""; //OFFSET static string overprint = ""; //OP static string tempDir = ""; //T static string fileUpdate = ""; //U #endif static string mode = "page"; //MODE static string imageType = "JPG"; //JPG or /PNG static string jpegQ = "91"; //JPEGQ static void DispHelp() { var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名 string msg = $"{pgName} [/] \n" +$"* Render of PDF file\n" +$"\t/F : pdfPath(pdfファイル名) /Fは省略可能\n" +$"\t/O : 出力ディレクトリ\n" +$"\t/D <解像度> : 解像度指定 9 - 300dpi\n" + $"\t/B : ボックス指定 x:M/B/T/A/C(default) ex. /BM (MediaBox/BleedBox/TrimBox/ArtBox/CropBox(*unsupport)\n" +$"\t/P : ページの範囲を指定する(省略時は全ページ)(*unsupport)\n" +$"\t\t連続した範囲を指定する場合は、ハイフン('-')を用いる\n" +$"\t\t複数のページ範囲を指定する場合は、カンマ(',')を用いる\n" +$"\t/L : 入力PDFファイルリスト(*unsupport)\n" +$"\t/OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport)\n" +$"\t/OP <0 or 1> : オーバープリントのOn/Off (省略時は1)(*unsupport allready 1)\n" +$"\t/T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need)\n" +$"\t/U <0 or 1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport)\n" +$"\t/H or /? : Help\n" +$"\t[Type of Image format]\n" +$"\t/JPG: JPEGファイル出力\n" +$"\t/PNG: PNGファイル出力\n" +$"\t/TIF: TIFファイル出力\n" +$"\t/GIF: GIFファイル出力\n" +$"\t/BMP: BMPファイル出力\n" +$"\t[JPEG Param]\n" +$"\t/JPEGQ : Jpegの品質指定1-100(default=91)\n" +$"\t[for TEST]\n" +$"\t/M : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n" + "\n"; Console.Write(msg); } /// /// PDFレンダラーメイン /// /// /// static int Main(string[] args) { var pdfPath=""; //static async Task Main(string[] args) {// async Task Main:おまじない Mainでawaitが書けるようになる C# 7.1からじゃないと書けない var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) while (qu.Count > 0) { // 引数のパース qu.Dequeue()で次の要素を取得する var wd = qu.Dequeue(); // 取り出し var wdi = wd.ToUpper(); // 大文字で比較 if (wdi == "/?" || wdi == "/H") { DispHelp(); return -1; } else if (wdi == "/F") { pdfPath = (qu.Count > 0) ? qu.Dequeue() :"";// next word. } else if (wdi == "/O") { outuptImageDir = (qu.Count > 0) ? qu.Dequeue() :"";// next word. } else if (wdi == "/D") { dpi = (qu.Count > 0) ? qu.Dequeue():"";// next word. if ( !double.TryParse(dpi, out double dmy)) { Console.WriteLine($"解像度が不正です:/D {dpi}"); DispHelp(); return -1; } } else if (wdi == "/JPG" || wdi == "/JPEG") { imageType = "JPG"; } else if (wdi == "/PNG") { imageType = "PNG"; } else if (wdi == "/TIF") { imageType = "TIFF"; } else if (wdi == "/GIF") { imageType = "GIF"; } else if (wdi == "/BMP") { imageType = "BMP"; } else if (wdi == "/JPEGQ") { jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word. if (!int.TryParse(jpegQ, out int dmy)) { Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {jpegQ}"); DispHelp(); return -1; } if ( !(0 < dmy && dmy <= 100) ) { Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}"); return -1; } } else if (wdi == "/M") { mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word. } else { // 引数をファイル名として取得する if (pdfPath == "") { pdfPath = wd; } } } if (pdfPath=="") { Console.WriteLine("pdfファイルが指定されてません"); DispHelp(); return -1; } if ( !File.Exists(pdfPath) ) { Console.WriteLine($"ファイルが存在しません:{pdfPath}"); return -1; } var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う var otDir = ""; if ( outuptImageDir == "" ) { // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); } var ret = RenderPdfDocAsync( pdfPath :Path.GetFullPath(pdfPath), inDir :otDir, inDpi :double.Parse(dpi), imageType:imageType, inJpegQ :int.Parse(jpegQ) ).Result;// スレッドの終了まで「待つ」 watch.Stop(); Console.WriteLine( $"result={ret},time={ watch.ElapsedMilliseconds/1000.0 }[sec]"); return ret;//success. } public static async Task RenderPdfDocAsync(string pdfPath, string inDir = "", double inDpi = 9.0, string imageType = "JPG", int inJpegQ = 0) { var otDir = inDir; var otBaseName = Path.GetFileName(pdfPath);//*.pdf var otExtention = imageType.ToLower(); { // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); } // PDFファイルを読み込む var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath); var pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file); if (pdfDoc == null) { Console.WriteLine("error: get PdfDocument failed"); return -1; } var n = pdfDoc.PageCount; if (mode == "page" || mode == "pageBitMap" || mode == "pageBitMapImage") { if (mode == "page") { mode = "pageBitMap"; } Console.WriteLine($"mode={mode}"); var taskList = new List>(); for (int i = 0; i < n; i++) { taskList.Add( RenderPageAync( doc :pdfDoc, page :pdfDoc.GetPage((uint)i), nPage :i, otPath :Path.Combine(otDir, $"{otBaseName}.{i + 1}.{otExtention}"), inDpi :inDpi, encName :imageType, mode :mode, inJpegQ :inJpegQ ) ); // docを渡す } await Task.WhenAll(taskList);// すべてのタスクを待つ。//int[] arrayInt = } else if (mode == "org") { // 並列で動く唯一のもの(Test) var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(otDir); Console.WriteLine("mode=org"); var taskList = new List>(); for (int i = 0; i < n; i++) { taskList.Add(RenderPageOrgASync(pdfDoc.GetPage((uint)i), dir)); // docを渡す } await Task.WhenAll(taskList);// すべてのタスクを待つ。//int[] arrayInt = } return 0;// success. } /// /// pdfのページを画像に出力する /// /// PdfDocument /// ページ番号 /// 出力ファイル名 /// 正常:0 public static async Task RenderPageAync(PdfDocument doc, PdfPage page,int nPage, string otPath, double inDpi=72.0, string encName="JPG",string mode = "pageBitMap", int inJpegQ = 91) { //おまじない このダミーのawait入れないとスレッドにならない var dir = await Windows.Storage.StorageFolder.GetFolderFromPathAsync(Directory.GetParent(otPath).FullName); var th = Thread.CurrentThread.ManagedThreadId; // パラメータ const double dpiWin = 96.0;/* , dpiPDF = 72.0*/ double resoScale = (inDpi / dpiWin); var opt = new PdfPageRenderOptions() { // SEE:https://github.com/microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/cs/Scenario1_Render.xaml.cs //BitmapEncoderId = Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,// JPEG IsIgnoringHighContrast = false }; opt.DestinationHeight = (uint)(page.Size.Height * resoScale + 0.5); opt.DestinationWidth = (uint)(page.Size.Width * resoScale + 0.5); //Console.WriteLine($"dpi={inDpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}"); using (var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream()) { await page.RenderToStreamAsync(memStrm,opt); await memStrm.FlushAsync(); if (mode == "pageBitMap") { var bmp = new System.Drawing.Bitmap(memStrm.AsStream()); //Console.WriteLine($"OrgReso({bmp.HorizontalResolution},{bmp.VerticalResolution})"); var imEnc = new ImageParams.EncData(encName); imEnc.JpegQuality = inJpegQ; bmp.SetResolution((float)inDpi, (float)inDpi); Console.WriteLine($"ot[{Thread.CurrentThread.ManagedThreadId}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})"); imEnc.SaveImage(bmp, otPath); bmp.Dispose(); memStrm.Seek(0); } else if (mode == "pageBitMapImage") { var image = new BitmapImage(); image.BeginInit(); image.CacheOption = BitmapCacheOption.OnLoad; image.StreamSource = memStrm.AsStream(); image.EndInit(); BitmapEncoder enc = null; switch (encName) { case "JPG": enc = new JpegBitmapEncoder(); break; case "PNG": enc = new PngBitmapEncoder(); break; case "TIF": case "TIFF": enc = new TiffBitmapEncoder(); break; case "GIF": enc = new GifBitmapEncoder(); break; case "BMP": enc = new BmpBitmapEncoder(); break; default: Console.WriteLine("failed encode"); return -2; } enc.Frames.Add(BitmapFrame.Create(image)); Console.WriteLine($"ot[{Thread.CurrentThread.ManagedThreadId}]={otPath}"); using (var fs = new FileStream(otPath, System.IO.FileMode.Create)) { enc.Save(fs); } } } return 0;//success } /// /// PDF Render サンプル /// ref: https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html#0x66958305801896177d9d64dbfb6e64ebd307d702f05b801316d258b19a8d91d6-70,,84, /// * コード量は少ないが、以下ができない /// - Built-in のEncodeIdしか指定できない(パラメータが渡せない) /// - 遅い:PDFレンダリングと同時にファイル保存するから? /// /// /// /// // ref: https://csharp.hotexamples.com/examples/-/PdfPageRenderOptions/-/php-pdfpagerenderoptions-class-examples.html#0x66958305801896177d9d64dbfb6e64ebd307d702f05b801316d258b19a8d91d6-70,,84, private static async Task RenderPageOrgASync(Windows.Data.Pdf.PdfPage page, Windows.Storage.StorageFolder output) { var pagePath = string.Format("{0}.jpeg", page.Index); var pageFile = await output.CreateFileAsync(pagePath, Windows.Storage.CreationCollisionOption.ReplaceExisting); using (var imageStream = await pageFile.OpenAsync(Windows.Storage.FileAccessMode.ReadWrite)) { await page.RenderToStreamAsync(imageStream); var renderOpt = new PdfPageRenderOptions() { BitmapEncoderId = Windows.Graphics.Imaging.BitmapEncoder.JpegEncoderId,// JPEG IsIgnoringHighContrast = false }; await page.RenderToStreamAsync(imageStream, renderOpt); await imageStream.FlushAsync(); } Console.WriteLine($">{Thread.CurrentThread.ManagedThreadId} save {pagePath}"); return 0; } } }