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. using System.Text.RegularExpressions; using System.Security.Cryptography; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Collections.ObjectModel; using System.Runtime.InteropServices; // for Stream conv. using System.Runtime.InteropServices.WindowsRuntime; using Windows.Storage.Streams; //using System.IO.WindowsRuntimeStreamExtensions; // //using Codeplex.Data; // DynamicJson // UWP-APIの使用 using Windows.Data.Pdf; // PDFiumの追加 using PdfiumViewer; using static CSRender.RenderPDF; //https://proself2.screen.co.jp/public/OcyIQAHPksNAnE4Bs3BxIPQApnAEmSCIBOxsCZ946Uur //Screen8080 //1_A4縦_2×1_両面_可変長レコード.pdf // TOshiba: //#region アセンブリ Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime // C:\UserData\GIT_JSH\WinRT\CSRender\CSRender\bin\Debug\Windows.winmd // WindowsRuntime 1.3 //#endregion // MacBookPro: // WindowsRuntime 1.4 //CSRender, Version=1.1.0.0, Culture=neutral, PublicKeyToken=null // mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine) // System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine) // Windows, Version=255.255.255.255, Culture=neutral, PublicKeyToken=null, ContentType=WindowsRuntime,Ver(255.255.255.255),VerComp(SameMachine) // System.Runtime.WindowsRuntime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine) // System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a,Ver(4.0.0.0),VerComp(SameMachine) // System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine) // System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089,Ver(4.0.0.0),VerComp(SameMachine) // clrVer=4.0.30319.42000 // 参考: zoomパレメータなど // https://www.syncfusion.com/kb/8767/how-to-print-pdf-documents-in-xamarin-forms-platform //var DisplayInformation = Windows.Graphics.Display.DisplayInformation.GetForCurrentView(); //var dpi = DisplayInformation.LogicalDpi / 96; // https://elesynd.blogspot.com/2018/11/hDpiForm.html //Program.csでDPIAwareする //FormクラスのAutoScaleModeをUIを使って"Dpi"に変更する //それでもズレるコントロールは、FontをUIを使って明示的に指定してみる // https://qiita.com/felis_silv/items/efee4b1a397b0b95100a // スケーリング grph.FromImage(bmp)後にスケール namespace CSRender { #pragma warning disable IDE1006 // 小文字のメソッド含む static public class Check { //https://gist.github.com/retorillo/4e0c4a3cf4c7096e05ac static public bool bDump = false; static public bool bThDump = false; const int LOGPIXELSX = 88; const int LOGPIXELSY = 90; // // const int HORZSIZE = 4;//物理画面の幅・高さ(ミリメートル単位) const int VERTSIZE = 5; const int HORZRES = 8; //画面の幅・高さ(ピクセル単位) const int VERZRES = 10; // const int RASTERCAPS = 38; // 返却値のマスク //1 (RC_BITBLT) ビットマップの転送 //2 (RC_BANDING) バンド処理のサポートが必要 //4 (RC_SCALING) スケーリング //8 (RC_BITMAP64) 64KB より大きいビットマップ //0x0080 (RC_DI_BITMAP) SetDIBits 関数と GetDIBits 関数 //0x0100 (RC_PALETTE) デバイスはパレットベースのデバイスである //0x0200 (RC_DIBTODEV) SetDIBitsToDevice 関数 //0x0800 (RC_STRETCHBLT) StretchBlt 関数 //0x1000 (RC_FLOODFILL) 塗りつぶし //0x2000 (RC_STRETCHDIB) StretchDIBits 関数 const int SCALINGFACTORX = 114;//x 軸・ y 軸のスケーリングファクター const int SCALINGFACTORY = 115; [DllImport("user32.dll")] extern static bool SetProcessDPIAware(); [DllImport("user32.dll")] extern static IntPtr GetWindowDC(IntPtr hwnd); [DllImport("gdi32.dll")] extern static int GetDeviceCaps(IntPtr hdc, int index); [DllImport("user32.dll")] extern static int ReleaseDC(IntPtr hwnd, IntPtr hdc); public static int GetDPI() { //return 96; SetProcessDPIAware();// もしくはdpiAwareをマニフェストで設定すればよい。 var dc = GetWindowDC(IntPtr.Zero); var dpi = GetDeviceCaps(dc, LOGPIXELSX); if ( bDump ) { Console.WriteLine("DpiX: {0}", GetDeviceCaps(dc, LOGPIXELSX)); Console.WriteLine("DpiY: {0}", GetDeviceCaps(dc, LOGPIXELSY)); Console.WriteLine("SCALINGFACTORYX: {0}", GetDeviceCaps(dc, SCALINGFACTORX)); Console.WriteLine("SCALINGFACTORY: {0}", GetDeviceCaps(dc, SCALINGFACTORY)); //if ( false ) {// これは倍率で変更されない // int width = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width; // 幅(pixel) // int height = System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height; // 高さ(pixel) // // 取得したスクリーンのサイズをコンソールに出力する // string message = string.Format("This screen size is {0} x {1} (pixel)", width, height); // System.Console.WriteLine(message); //} //if (false){// コンソールアプリでは呼び出せない // var di = Windows.Graphics.Display.DisplayInformation.GetForCurrentView(); // Console.WriteLine($@"di.LogicalDpi: {di.LogicalDpi}"); // Console.WriteLine($@"di.RawDpiX: {di.RawDpiX}"); // Console.WriteLine($@"di.ResoSacle: {di.ResolutionScale.ToString()}"); // Console.WriteLine($@"di.StereoEnabled: {di.StereoEnabled}"); // Console.WriteLine($@"di: {di.ToString()}"); //} } ReleaseDC(IntPtr.Zero, dc); return dpi;// 96;// dpi; } } public static class RenderPDF { /// /// Rendering Condition /// [DataContract] public class RenderConditionParams { [DataMember(Order = 0)] public double Dpi = 72.0; [DataMember(Order = 1)] public string BoxType = "Crop"; [DataMember(Order = 2)] public string ImageType = "JPEG"; [DataMember(Order = 3)] public int JpegQ = 91; // Method non } // Verbose public static void setEcho(bool b) { bEcho = b; } public static bool bEcho = false; public static void echo(params object[] args) { if (!bEcho) return; var s = ""; foreach (object a in args) { s += a.ToString(); } Console.WriteLine(s); } // 事前チェック BashHash public static bool RenderPdfInitCheck( Stream stream /* PDFストリーム */) { Windows.Storage.Streams.InMemoryRandomAccessStream rmStrageStream=null; {// Stream->Memstream -> byte array -> IBuffer -> InMemmory...Stream() var ms = new MemoryStream(); stream.CopyTo(ms); var bAray = ms.ToArray(); IBuffer ib = bAray.AsBuffer(); rmStrageStream = new Windows.Storage.Streams.InMemoryRandomAccessStream(); //await s.WriteAsync(ib); async Task wa() => await rmStrageStream.WriteAsync(ib); var wa_ret = wa().Result; } async Task LoadS() => await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(rmStrageStream); var pdfDoc = LoadS().Result; if (pdfDoc == null) { Console.WriteLine("error: get PdfDocument failed"); return false; } // var memStream = new MemoryStream(); // memStream.AsInputStream(); // WindowsRuntimeStreamExtensions. // var ras = WindowsRuntimeStreamExtensions.AsRandomAccessStream(stream); //https://blog.ch3cooh.jp/entry/20131207/1386342000 //https://csharp.hotexamples.com/site/file?hash=0xe951daae5be57f9f35507c45eb09a1085819b0a42b846aaa211ac138a2d1b5af&fullName=src/Nutrition/Nutrition.WP/WPOCRService.cs&project=SamirHafez/Nutrition //なんだけどな。。。→ .NetFrameworkにAsRandomAccessStream(x)が存在しない。.Net Coreのみみたい。 // exeと同一場所に"baseRender.jpg" string otPath = Path.Combine(Directory.GetParent(Assembly.GetEntryAssembly().Location).FullName,"baseRender.jpg"); bool bSaveImage = false;// Debug時にtrueにする Console.WriteLine($"mode=Hash base check"); var taskList = new List>(); var hashValue = ""; var pm = new RenderConditionParams(); var ret = RenderPage( doc: pdfDoc, page: pdfDoc.GetPage(0), otPath: otPath, pm:pm, bSaveImage: bSaveImage, bHash: true, otHashCode: ref hashValue ); Console.WriteLine($@"Hash={hashValue},Host={Environment.MachineName},{Environment.OSVersion}"); return true;// true:OK, false:NG } // Sync version( no async -> Pallale Task ) ************************************************************************************** /// /// PDFのレンダリング /// /// /// /// レンダリングの条件 /// /// /// /// 未使用,常にtrueで使用 /// /// public static int RenderPdfDoc( string pdfPath, string inDir = "", RenderConditionParams pm = null, string inPageRange = "1-*", string inMode = "pageBitMap", bool bSaveImage = true, //ハッシュ値のみ計算するときにfalseにする bool bHash = false, UtHash.HashData inHashData = null, bool bPDFium = false ) { if (pm == null) { pm = new RenderConditionParams(); } var otDir = inDir; var otBaseName = Path.GetFileName(pdfPath);//*.pdf var otExtention = pm.ImageType.ToLower(); if (bSaveImage && otDir == "") { // 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); } // PDFファイルを読み込む async Task GetStrageFilePath(string path) => await Windows.Storage.StorageFile.GetFileFromPathAsync(path); var file = GetStrageFilePath(pdfPath).Result; async Task Load(Windows.Storage.StorageFile path) => await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(file); var pdfDoc = Load(file).Result; // 2つを合わせることができそう //async Task GetLoadFromPath(string path) //{ // var a1 = await Windows.Storage.StorageFile.GetFileFromPathAsync(path); // var a2 = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(a1); // return a2; //}; //var pdfDoc2 = GetLoadFromPath(pdfPath).Result; if (pdfDoc == null) { Console.WriteLine("error: get PdfDocument failed"); return -1; } var n = pdfDoc.PageCount; inMode = "pageBitMap"; //Console.WriteLine($"mode={inMode},bPDFium={bPDFium}"); var taskList = new List>(); uint[] rNo = makePageRange(inPageRange, pdfDoc.PageCount); if (inHashData != null) { // ハッシュ対象の最大ページ数の設定必須。 inHashData.SetFile(pdfPath, (int)pdfDoc.PageCount); } // PDFiumViewer PdfiumViewer.PdfDocument docG = null; if (bPDFium) { docG = PdfiumViewer.PdfDocument.Load(pdfPath); } //Console.WriteLine($@"dpi={pm.Dpi}"); /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値) ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; Parallel.ForEach(rNo, options, i => { //Console.WriteLine($"*****{i}*****/{rNo.Length}"); var hashValue = ""; var ret = RenderPage( doc: pdfDoc, page: pdfDoc.GetPage((uint)(i - 1)), otPath: Path.Combine(otDir, $"{otBaseName}.{i}.{otExtention}"), pm: pm, bSaveImage: bSaveImage, // ファイル保存 bHash: bHash, otHashCode: ref hashValue, docG : docG ); if (inHashData != null) { lock (inHashData) { inHashData.AddHashCode(pdfPath, (int)i, hashValue); } } }); if ( docG != null) docG.Dispose(); return (int)pdfDoc.PageCount;// ページ数を返却します //return 0;// success. } /// /// pdfのページを画像に出力する /// /// PdfDocument /// ページ番号 /// 出力ファイル名 /// 正常:0 public static int RenderPage( // Need: Windows.Data.Pdf.PdfDocument doc, // Need GetPage() only. (Total page number); PdfPage page, // Target Page Data(page.index:0-x) string otPath, ref string otHashCode, // Optional: RenderConditionParams pm = null, bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする bool bHash = false, PdfiumViewer.PdfDocument docG = null // ){ if( pm == null) { pm = new RenderConditionParams(); } double dpiWin = Check.GetDPI(); //96.0;/* , dpiPDF = 72.0*/ //double resoScale = (96.0 / dpiWin); double resoScale = (pm.Dpi / 96.0); //double resoScale = (inDpi / dpiWin); var bDump = Check.bDump; var bThDump = Check.bThDump; 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 }; var boxDic = new Dictionary() { // Init ["Media"] = page.Dimensions.MediaBox, ["Trim"] = page.Dimensions.TrimBox, ["Bleed"] = page.Dimensions.BleedBox, ["Crop"] = page.Dimensions.CropBox, ["Art"] = page.Dimensions.ArtBox }; //Dump: if ( bDump){ Console.WriteLine($@"PageZoom={page.PreferredZoom}"); Console.WriteLine($@"PageRotate={page.Rotation.ToString()}"); Console.WriteLine($"{"Size",-5}=({page.Size.Width,8:f2},{page.Size.Height,8:f2})"); foreach (var b in boxDic.Keys) { var r = boxDic[b]; Console.WriteLine($"{b,-5}=({r.X,8:f2},{r.Y,8:f2}) W({r.Width,8:f2},{r.Height,8:f2})"); Console.WriteLine($"{b,-5}=L {r.Left,-8:f2} R {r.Right,-8:f2}) T {r.Top,-8:f2} B {r.Bottom,-8:f2})"); } } opt.SourceRect = boxDic.ContainsKey(pm.BoxType) ? boxDic[pm.BoxType] : boxDic["Crop"];// Cannot find -> "Crop"; //Console.WriteLine($"Select {inBoxType} box."); // 何も変倍しないとどうなる opt.DestinationWidth = (uint)(opt.SourceRect.Width * resoScale + 0.5); opt.DestinationHeight = (uint)(opt.SourceRect.Height * resoScale + 0.5); // 150%の時は * 0.5358を設定するとよい解像度になるが、この数値の計算方法がわからない //opt.DestinationWidth = (uint)(opt.SourceRect.Width * 0.5358/*resoScale*/ + 0.5); //opt.DestinationHeight = (uint)(opt.SourceRect.Height * 0.5358/*resoScale*/ + 0.5); if (bDump) { Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},width(org)={page.Size.Width}->{opt.DestinationWidth}"); Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}"); } using (var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream()) { if (docG == null) { async Task RenderToStream(PdfPage p) => await p.RenderToStreamAsync(memStrm, opt); RenderToStream(page).Wait(); } else { PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi); System.Drawing.Image img = docG.Render((int)(page.Index), (float)pm.Dpi, (float)pm.Dpi, flg); img.Save(memStrm.AsStream(), System.Drawing.Imaging.ImageFormat.Jpeg); // [注意] PDFuim用に直接 AsStream()に書き込んでいるので、AsStream().Flush()しないとだめです } async Task FlushX() => await memStrm.FlushAsync(); FlushX().Wait(); var bmp = new System.Drawing.Bitmap(memStrm.AsStream()); if (bDump) { Console.WriteLine($"bmp=w:{bmp.Size.Width},h:{bmp.Size.Height}"); } //Console.WriteLine($"OrgReso({bmp.HorizontalResolution},{bmp.VerticalResolution})"); if (bHash) { var h =GetHashValue(memStrm); //Console.WriteLine($"HashString:{h}"); otHashCode = h; } var th = Thread.CurrentThread.ManagedThreadId; if ( !bSaveImage ) { //ファイル保存しない.ハッシュ計算のみ if ( bThDump ) { Console.WriteLine($"ot[th{th},{(page.Index + 1)}/{doc.PageCount}]=,{Path.GetFileName(otPath)},hash:{otHashCode}"); } return 0; } var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers. bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi); if (bThDump) { Console.WriteLine($"ot[th{th},{(page.Index + 1)}/{doc.PageCount}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})"); } // imEnc.SaveImage(bmp, otPath); // bmp.Dispose(); //memStrm.Seek(0); } return 0;//success } // Sync version( 比較モード(/FC ) ************************************************************************************** /// /// レンダリング(比較モード) /// /// /// /// /// レンダリング条件 /// /// /// 未使用常にtrueで利用する /// /// /// 一致した場合は0、不一致の場合は!0を返却する public static int RenderPdfDocCompare( string pdfPath, string refPdfPath, string resultDataPath, string inDir = "", RenderConditionParams pm = null, string inPageRange = "1-*", string inMode = "pageBitMap", bool bHash = false, UtHash.HashData inHashDataTgt = null, UtHash.HashData inHashDataRef = null, bool bPDFium = false // 比較結果を返す ページ番号とメッセージ ) { var bDump = Check.bDump; var bThDump = Check.bThDump; if(bDump) Console.WriteLine("RenderPdfDocCompare & diff Image! & non Para"); if (pm == null) pm = new RenderConditionParams(); var otDir = inDir;// 今は未使用 var otBaseName = Path.GetFileName(pdfPath);//xxx.pdf var otExtention = pm.ImageType.ToLower(); if (otDir == "") { // 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); } // PDFファイルを読み込む // 以下のコードで良いみたい。 // [変更待ち] var file2 = Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath).GetAwaiter().GetResult(); async Task GetStrageFilePath(string path) => await Windows.Storage.StorageFile.GetFileFromPathAsync(path); var file = GetStrageFilePath(pdfPath).Result; async Task Load(Windows.Storage.StorageFile path) => await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(path); var pdfDoc = Load(file).Result; if (pdfDoc == null) { Console.WriteLine("error: get PdfDocument failed"); return -1; } // ref var fileRef = GetStrageFilePath(refPdfPath).Result; var pdfDocRef = Load(fileRef).Result; if (pdfDocRef == null) { Console.WriteLine("error: get PdfDocument(ref) failed"); return -1; } var n = pdfDoc.PageCount; if ( n != pdfDocRef.PageCount) { Console.WriteLine($"error: differ page length:{n}:{pdfDocRef.PageCount}"); return -1; } uint[] rNo = makePageRange(inPageRange, n); int nRetAll = 0;// 一致(def) // ハッシュ情報の確認 /* - 双方のハッシュ値が存在するか確認 - tgtののみなら、refのrender - refのみなら、tgtのみrender - とう感じ */ UtHash.HashFile tgtHf=null; if (inHashDataTgt!=null) { var f = Path.GetFileName(pdfPath); if (inHashDataTgt.Files.ContainsKey(f)) { tgtHf = inHashDataTgt.Files[f]; } } UtHash.HashFile refHf = null; if (inHashDataRef != null) { var f = Path.GetFileName(refPdfPath); if ( inHashDataRef.Files.ContainsKey(f) ) { refHf = inHashDataRef.Files[f]; } } Console.WriteLine($@"UsingHashFile:tgt({(tgtHf==null?0:1)}),ref({(refHf == null ? 0 : 1)})"); // tgtHf,refHfを構築完了 var fcResultMsg = new SortedDictionary();// 比較結果を返す ページ番号とメッセージ // PDFiumViewer PdfiumViewer.PdfDocument docG = null; PdfiumViewer.PdfDocument docGRef = null; if (bPDFium) { docG = PdfiumViewer.PdfDocument.Load(pdfPath); docGRef = PdfiumViewer.PdfDocument.Load(refPdfPath); } /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値) ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; Parallel.ForEach(rNo, options, i => { Windows.Storage.Streams.InMemoryRandomAccessStream tgtStrm = null, refStrm = null; string tgtHash = "", refHash = ""; // ターゲット レンダー InMemoryRandomAccessStream RendPageTGT() { var strm = RenderPageStream(doc:pdfDoc,page:pdfDoc.GetPage((uint)(i-1)), pm:pm,docG:docG); return strm; } // リファレンス レンダー InMemoryRandomAccessStream RendPageREF() { var strm = RenderPageStream(doc:pdfDocRef,page:pdfDocRef.GetPage((uint)(i-1)), pm:pm,docG:docGRef); return strm; } //tgtStrm = RendPageTGT(); if (tgtHf == null) { tgtStrm = RendPageTGT(); tgtHash = GetHashValue(tgtStrm); } else { lock (inHashDataTgt) { tgtHash = tgtHf.GetHashValue((int)(i - 1)); } } // リファレンス if (refHf == null) { refStrm = RendPageREF(); refHash = GetHashValue(refStrm); } else { lock (inHashDataRef) { refHash = refHf.GetHashValue((int)(i - 1)); } } // compare stream; int nRet = 1;//異なる(def). var dateStr = DateTime.Now.ToString(); //Console.WriteLine($@"tgt={i}:{tgtHash}"); //Console.WriteLine($@"ref={i}:{refHash}"); if (tgtHash != refHash) { nRet = 1; nRetAll = nRet;//全体の不一致設定 echo($"{i}:NotMatch:{i},tgt[{tgtHash}],ref[{refHash}]"); //2020年3月2日 15:23:55:[@Difference]:.diff.jpg lock (fcResultMsg) { fcResultMsg.Add((int)i, $@"{dateStr}:[@Difference]:{pdfPath}.diff.jpg"); } //if ( true && tgtStrm != null && bSave) { { echo("***** save diff image"); // diffのjpegを書き出してみる(tgt) if (tgtStrm == null) tgtStrm = RendPageTGT(); var otPath = Path.Combine(otDir, $"{otBaseName}.{i}.tgt.{otExtention}"); var bmp = new System.Drawing.Bitmap(tgtStrm.AsStream()); var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers. bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi); imEnc.SaveImage(bmp, otPath); bmp.Dispose(); } //if (true && refStrm != null && bSave) { { // diffのjpegを書き出してみる(ref) if (refStrm == null) refStrm = RendPageREF(); var otPath = Path.Combine(otDir, $"{otBaseName}.{i}.ref.{otExtention}"); var bmp = new System.Drawing.Bitmap(refStrm.AsStream()); var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers. bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi); imEnc.SaveImage(bmp, otPath); bmp.Dispose(); } } else { nRet = 0;// match //Console.WriteLine($"{i}:Match:{i}"); // 2019年12月18日 19:31:38:[OK]:fname.pdf.1.png lock (fcResultMsg) { fcResultMsg.Add((int)i, $@"{dateStr}:[OK]:{Path.GetFileName(pdfPath)}.{i}.png"); } } // dispose: if (refStrm != null) refStrm.Dispose(); if (tgtStrm != null) tgtStrm.Dispose(); }); if ( docG != null) docG.Dispose(); if (docGRef != null) docGRef.Dispose(); // まとめて表示 foreach ( var v in fcResultMsg){ Console.WriteLine("@CMP@"+v.Value); } if (resultDataPath != "") { // まとめて書き出し var resCont =($"{pdfPath}\n"); foreach (var v in fcResultMsg) { resCont += ("@CMP@" + v.Value) + "\n"; } File.AppendAllText(resultDataPath, resCont); } return nRetAll;// success. } /// /// 比較モード /// /// PdfDocument /// ページ番号 /// 出力ファイル名 /// 正常:0 public static Windows.Storage.Streams.InMemoryRandomAccessStream RenderPageStream( // Need: Windows.Data.Pdf.PdfDocument doc, // Need GetPage() only. (Total page number); PdfPage page, // Target Page Data(page.index:0-x) //string otPath, // Optional: RenderConditionParams pm = null, PdfiumViewer.PdfDocument docG = null // ) { var bDump = Check.bDump; var bThDump = Check.bThDump; if (pm == null) pm = new RenderConditionParams(); double dpiWin = Check.GetDPI(); //96.0;/* , dpiPDF = 72.0*/ double resoScale = (pm.Dpi / 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 }; // //async Task PreparePage(PdfPage p) => await p.PreparePageAsync();//ページの完成を待つ //PreparePage(page).Wait(); //page.PreparePageAsync var boxDic = new Dictionary() { // Init ["Media"] = page.Dimensions.MediaBox, ["Trim"] = page.Dimensions.TrimBox, ["Bleed"] = page.Dimensions.BleedBox, ["Crop"] = page.Dimensions.CropBox, ["Art"] = page.Dimensions.ArtBox }; opt.SourceRect = boxDic.ContainsKey(pm.BoxType) ? boxDic[pm.BoxType] : boxDic["Crop"];// Cannot find -> "Crop"; opt.DestinationWidth = (uint)(opt.SourceRect.Width * resoScale + 0.5); opt.DestinationHeight = (uint)(opt.SourceRect.Height * resoScale + 0.5); if (bDump) Console.WriteLine($"dpi={pm.Dpi},scale={resoScale},height(org)={page.Size.Height}->{opt.DestinationHeight}"); var memStrm = new Windows.Storage.Streams.InMemoryRandomAccessStream(); if(true) { if (docG == null) { async Task RenderToStream(PdfPage p) => await p.RenderToStreamAsync(memStrm, opt); RenderToStream(page).Wait(); } else { PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi); System.Drawing.Image img = docG.Render((int)(page.Index), (float)pm.Dpi, (float)pm.Dpi, flg); img.Save(memStrm.AsStream(), System.Drawing.Imaging.ImageFormat.Jpeg); // [注意] PDFuim用に直接 AsStream()に書き込んでいるので、AsStream().Flush()しないとだめです memStrm.AsStream().Flush(); } async Task FlushX() => await memStrm.FlushAsync(); //var dmy =FlushX().Result; FlushX().Wait(); } //page.Dispose(); return memStrm;// } /// /// ページ1から始まるページ範囲の配列を返す /// /// /// /// private static uint[] makePageRange(string pageRange = "1", uint endPage = 100) { var range = new SortedSet(); var vec = pageRange.Split(',');//カンマで分割 var reRange = new Regex($@"([\d]+)[\s]*\-[\s]*([\d]*|\*)");// n-m or n- or n-* var reOne = new Regex($@"([\d]+)"); // n bool isRange(uint x) => (0 < x && x <= endPage); // 範囲チェック void swap(ref T a, ref T b) { T tmp = a; a = b; b = tmp; } foreach (var s in vec) { //範囲指定 var m = reRange.Match(s); if (m.Success) { uint st = uint.Parse(m.Groups[1].Value); uint en = 0; if (!isRange(st)) continue;//開始値が範囲外で無効 uint.TryParse(m.Groups[2].Value, out en); if (!isRange(en)) en = endPage;// 最終値が無効なため最終ページ if (st > en) swap(ref st, ref en); // 範囲登録 foreach (var g in Enumerable.Range((int)st, (int)(en - st + 1))) range.Add((uint)g); continue; } //ページ指定 m = reOne.Match(s); if (m.Success) { var n = uint.Parse(m.Groups[1].Value); if (isRange(n)) range.Add(n); } } return range.ToArray(); } public static string GetHashValue(Windows.Storage.Streams.InMemoryRandomAccessStream strm) { var alg = new SHA256CryptoServiceProvider(); strm.Seek(0); var bin = alg.ComputeHash(strm.AsStream()); alg.Clear(); // バイト配列をUTF8エンコードで文字列化 var hashedText = new StringBuilder(); foreach (var b in bin) { hashedText.AppendFormat("{0:X2}", b); } return hashedText.ToString(); } } /// /// ファイルのハッシュ値のIO /// namespace UtHash // https://qiita.com/Akasaki/items/dee137b24aea4b7e2bcb // http://mokake.hatenablog.com/entry/2017/09/28/234433 // https://lifetime-engineer.com/csharp-create-json-indent/ // DataMemberでOrderを指定することで順序を確定。 // JsonReaderWriterFactory.CreateJsonWriterでindent=trueにすることでjsonの可視性向上 { [DataContract] public class HashHead { public bool Dirty = false; // 書き換えられたらTrue 保存対象外 [DataMember(Order = 0)] public string HashBaseName; // 保存時にベース名を設定する [DataMember(Order=1)] public string HashFilePath; [DataMember(Order=2)] public string Dpi; [DataMember(Order=3)] public string Box; [DataMember(Order=4)] public string ImType; [DataMember(Order=5)] public string JQ; // メソッド public HashHead() { SetRenderInfo("0", "Bx", "IMx", "xx"); } public HashHead SetRenderInfo(string dpi, string box, string imType, string jq) { Dpi = dpi; Box = box; ImType = imType; JQ = jq; return this; } public string GetRenderInfoStr() { return $@"{Dpi}_{Box}_{ImType}_{JQ}"; } } [DataContract] public class HashFile { [DataMember(Order=0)] public DateTime UpdateTime; // ファイルの更新日 [DataMember(Order=1)] public string UpdateTimeStr; // ファイルの更新日(Json目視用) [DataMember(Order=2)] public List PageHashCode = new List(); // ページ毎のハッシュ値の配列 // メソッド public string GetHashValue(int i) { // 範囲チェック if ( !(0 <= i && i < PageHashCode.Count) ){ return ""; } return PageHashCode[i]; } } [DataContract] public class HashData { [DataMember(Order=0)] public HashHead Head = new HashHead(); [DataMember(Order=1)] public SortedDictionary Files = new SortedDictionary(); // メソッド static public string GetHashFileName(string baseName, string dpi, string box, string imType, string jq) { var h = new HashHead(); var f = baseName + "_" + h.SetRenderInfo(dpi, box, imType, jq).GetRenderInfoStr() + ".json"; return f; } static public HashData load(string pdfPath/*or Dir*/, string baseName, string dpi, string box, string imType, string jq, ref string otPath) { var hashPath = ""; if (Path.GetExtension(pdfPath).ToLower() == ".pdf") { var paPath = Directory.GetParent(Path.GetFullPath(pdfPath)).ToString(); var hashFName = GetHashFileName(baseName, dpi, box, imType, jq); hashPath = Path.Combine(paPath, hashFName); } else if (File.GetAttributes(pdfPath).HasFlag(FileAttributes.Directory)) { var hashFName = GetHashFileName(baseName, dpi, box, imType, jq); hashPath = Path.Combine(pdfPath, hashFName); } //Console.WriteLine($@"hashPath={hashPath}"); if (otPath != null) { otPath = hashPath; } var hd = HashData.load(hashPath); //ファイルの存在はこっちに任す return hd; } static public HashData load(string hashPath) { if (!File.Exists(hashPath)) { echo("not find load hash data file"); return null; } DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData)); using (var rd = new FileStream(hashPath, FileMode.Open,FileAccess.Read,FileShare.ReadWrite)) { var a = (HashData)serializer.ReadObject(rd); a.Head.Dirty = false;// clear return a; } } static public HashData loadFromString(string s) { DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(HashData)); using (var rd = new MemoryStream( Encoding.UTF8.GetBytes(s) )) { var a = (HashData)serializer.ReadObject(rd); a.Head.Dirty = false;// clear return a; } } public override string ToString() { //文字列に書き出す using (var m = new MemoryStream()) using (var writer = JsonReaderWriterFactory.CreateJsonWriter(m, Encoding.UTF8, true, true, " ")) { var serializer = new DataContractJsonSerializer(typeof(HashData)); serializer.WriteObject(writer, this); var x= Encoding.UTF8.GetString(m.ToArray());// 文字列に変換 return x; } } public bool save(string path) { // JSONに変換するデータを作る。 // 再保存の抑制 //this.Head.Dirtyがfalseなら更新されていないことになる。 //またかつ保存パスが同じ this.Head.HashFilePath = path; echo($@"HashData.save:{path}"); this.Head.HashFilePath = Path.GetFullPath(path); this.Head.HashBaseName = this.Head.GetRenderInfoStr(); try { using (var fs = new FileStream(path, FileMode.Create)) using (var writer = JsonReaderWriterFactory.CreateJsonWriter(fs, Encoding.UTF8, true, true, " ")) { var serializer = new DataContractJsonSerializer(typeof(HashData)); serializer.WriteObject(writer, this); return true; } } catch (Exception e) { Console.WriteLine($@"error Save json anything error:{e}"); return false; } } public bool SetFile(string pathPDF, int pageNum) { var fi = new FileInfo(pathPDF); var fName = fi.Name; HashFile hf; if (!Files.ContainsKey(fName)) { this.Head.Dirty = true;// 変更あり hf = new HashFile() { UpdateTime = fi.LastWriteTimeUtc, UpdateTimeStr= fi.LastWriteTime.ToString(), PageHashCode = new List(pageNum) }; // Console.WriteLine($@"update={hf.UpdateTime}"); for (var i = 0; i < pageNum; i++) { hf.PageHashCode.Add(""); } Files.Add(fName,hf); } else { // 既に存在、pageNumがかわらないことだけチェック var pageNumOrg = Files[fName].PageHashCode.Count; if (Files[fName].UpdateTime.ToString() != fi.LastWriteTimeUtc.ToString()) { // 日付が一致しなければ更新(ToStringで比較しないとだめです this.Head.Dirty = true;// 変更あり Files[fName].UpdateTime = fi.LastWriteTimeUtc; Files[fName].UpdateTimeStr = fi.LastWriteTime.ToString(); Files[fName].PageHashCode = new List(pageNum); for (var i = 0; i < pageNum; i++) { Files[fName].PageHashCode.Add(""); } } // ありえないけど、同一日付でページ数が異なる if (pageNumOrg != pageNum) { Console.WriteLine($@"Mismatch pageNum:setP:{pageNum},DbP:{Files[fName].PageHashCode.Count}"); this.Head.Dirty = true;// 変更あり return false; // Page数が一致しない } } return true; } public bool AddHashCode(string pathPDF, int pageNo/*1開始*/, string hashCode) { //Console.WriteLine("call addHashCode:" + pathPDF + ":" + pageNo); var fi = new FileInfo(pathPDF); var fName = fi.Name; var n = pageNo - 1;//0始まり if (!Files.ContainsKey(fName)) { this.Head.Dirty = true;// 変更あり return false; } if (Files[fName].PageHashCode[n] != hashCode) { this.Head.Dirty = true;// 変更あり Files[fName].PageHashCode[n] = hashCode; } return true; } public string GetHashCode(string pathPDF, int pageNo/*1開始*/) { var fi = new FileInfo(pathPDF); var fName = fi.Name; var n = pageNo - 1;//0始まり if (!Files.ContainsKey(fName)) { return ""; } var v = Files[fName].PageHashCode; return (n < v.Count) ? v[n] : ""; } } } namespace UtImage // Image保存のユーティリティー { // ref:https://water2litter.net/rum/post/cs_pdf_wpf/ //using EncType = System.Drawing.Imaging.Encoder;// 別名 //using EncParamType = System.Drawing.Imaging.EncoderParameter;//別名 /// /// BitMapのSaveに使うImage Encodeパラメータの設定値の生成と 画像保存 /// var enc = new UtImage.Enc("jpeg"); /// //enc.SetImageType("PNG"); // 後で変更できる /// //enc.JJpegQuality = 100; JpegのQuarty変更 /// enc.SaveImage(BitMap,path); /// // enc.SaveImage(BitMap,path,"PNG"); /// /// public class Enc { // SEE:https://dobon.net/vb/dotnet/graphics/encoderparameters.html public Enc(string imageType = "jpeg") { SetImageType(imageType); } public long JpegQuality { get; set; } = 91;// 指定しないときの値は91と一致する private string ImageType = "jpeg"; public Enc 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; } /// /// サポートするEncodeパラメータ一覧 /// 現状Errorが発生して取得できない /// /// 対象のBitMap 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 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. private 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; } private System.Drawing.Imaging.ImageCodecInfo GetInfo(string it = null) { if (it == null) {// SetImageType()で指定したものを使う it = ImageType; } return GetEncoderInfo($"image/{it}"); } //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) { var encs = System.Drawing.Imaging.ImageCodecInfo.GetImageEncoders(); //指定されたMimeTypeを探して見つかれば返す foreach (System.Drawing.Imaging.ImageCodecInfo enc in encs) { if (enc.MimeType == mineType) return enc; } return null; } } } }