diff --git a/CSRender/CSRender.cs b/CSRender/CSRender.cs index 02d33d2ed946d0889c22eca0ef0ad0c12ed55344..25f739e3b718c7400b0d2d64260c5533453d437e 100644 --- a/CSRender/CSRender.cs +++ b/CSRender/CSRender.cs @@ -1,81 +1,35 @@ using System; using System.IO; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; // List<> +using System.Linq; // for Enumration + 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.Text.RegularExpressions; +using System.Security.Cryptography; // for Hash using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Collections.ObjectModel; +using System.Runtime.InteropServices; // for DLL import -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; +// no need +//using System.Windows; // No need +//using System.Windows.Media.Imaging; // no need +// for Stream conv. +//using System.Runtime.InteropServices.WindowsRuntime; +//using Windows.Storage.Streams; +//using System.Reflection; // for Assembly. -// 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)後にスケール +using static CSRender.RenderPDF; namespace CSRender { -#pragma warning disable IDE1006 // 小文字のメソッド含む - +#pragma warning disable IDE1006 // 小文字のメソッドを許可 static public class Check { //https://gist.github.com/retorillo/4e0c4a3cf4c7096e05ac @@ -83,31 +37,7 @@ namespace CSRender { 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")] + [DllImport("user32.dll")] extern static bool SetProcessDPIAware(); [DllImport("user32.dll")] extern static IntPtr GetWindowDC(IntPtr hwnd); @@ -116,38 +46,17 @@ namespace CSRender { [DllImport("user32.dll")] extern static int ReleaseDC(IntPtr hwnd, IntPtr hdc); - public static int GetDPI() { + 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()}"); - //} - } + var dpi = GetDeviceCaps(dc, 88/*LOGPIXELSX*/); ReleaseDC(IntPtr.Zero, dc); return dpi;// 96;// dpi; } } + public static class RenderPDF { /// @@ -167,11 +76,11 @@ namespace CSRender { // Method non } - // Verbose - public static void setEcho(bool b) { - bEcho = b; - } + /* + * Console Echo関係 + */ public static bool bEcho = false; + public static void setEcho(bool b) {bEcho = b;} public static void echo(params object[] args) { if (!bEcho) return; var s = ""; @@ -181,59 +90,6 @@ namespace CSRender { 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のレンダリング /// @@ -255,7 +111,8 @@ namespace CSRender { bool bSaveImage = true, //ハッシュ値のみ計算するときにfalseにする bool bHash = false, UtHash.HashData inHashData = null, - bool bPDFium = false + bool bPDFium = false, + int nPageThreadNum = 4 ) { if (pm == null) { pm = new RenderConditionParams(); @@ -263,79 +120,54 @@ namespace CSRender { 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 (bSaveImage) { + if ( otDir == "" ) { + // 出力ディレクトリが空→元PDFの同一フォルダにIMGフォルダを作成する + otDir = Path.Combine(Directory.GetParent(pdfPath).FullName, "IMG"); + } if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); + echo("ouptput dir=",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}"); + // Open pdf by PDFium. + int pageCount = 0; + using(var docG = PdfiumViewer.PdfDocument.Load(pdfPath)){ + pageCount = docG.PageCount; + inMode = "pageBitMap"; + var taskList = new List>(); + uint[] rNo = makePageRange(inPageRange, (uint)pageCount); - /////並行処理するスレッド数を指定(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); + echo("Create Hash"); + // ハッシュ対象の最大ページ数の設定必須。 + inHashData.SetFile(pdfPath, (int)pageCount); + } + + echo($"Page.ParallelOptions:Thread={nPageThreadNum},Pages={rNo.Length}"); + /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値) + ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = nPageThreadNum }; + Parallel.ForEach(rNo, options, i => { + //Console.WriteLine($"*****{i}*****/{rNo.Length}"); + var hashValue = ""; + var ret = RenderPage( + docG: docG, + index: (int)i-1, + otPath: Path.Combine(otDir, $"{otBaseName}.{i}.{otExtention}"), + pm: pm, + bSaveImage: bSaveImage, // ファイル保存 + bHash: bHash, + otHashCode: ref hashValue + ); + if (inHashData != null) { + lock (inHashData) { + inHashData.AddHashCode(pdfPath, (int)i, hashValue); + } } - } - }); - if ( docG != null) + }); + echo("End Para"); docG.Dispose(); - - return (int)pdfDoc.PageCount;// ページ数を返却します - //return 0;// success. + } + return pageCount;// ページ数を返却します } /// /// pdfのページを画像に出力する @@ -345,113 +177,82 @@ namespace CSRender { /// 出力ファイル名 /// 正常: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) + PdfiumViewer.PdfDocument docG, + int index, string otPath, ref string otHashCode, // Optional: - RenderConditionParams pm = null, - bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする - bool bHash = false, - PdfiumViewer.PdfDocument docG = null // + RenderConditionParams pm = null, + bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする + bool bHash = false ){ - 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; + if ( pm == null ) + pm = new RenderConditionParams(); - 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); + using (var memStrm = new MemoryStream()) { + PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi); + using(var img = docG.Render(index, (float)pm.Dpi, (float)pm.Dpi, flg)){ + img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Bmp); + //img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Jpeg); + /* + // ImageFormatJPEGの時 + dpi = 350,TIFF + 2691.777 = 44分51.777秒 + 10335ファイルで20G + ーー + ImageFormat.Bmpに変更 + result=0,time=2850.955[sec] + 10335ファイルで12.2G + ーー + pagePara=2 + para=4 + ImageFormat.Bmp + result=0,time=2824.644[sec] + + 10335ファイルで12.2G + ーー + pagePara=2 + para=8 + ImageFormat.Bmp + result=0,time=2828.631[sec] => 47分 + 10335ファイルで12.2G + + */ + // [注意] PDFuim用Flush()必要しないとだめです + memStrm.Flush(); + img.Dispose(); + } + var bmp = new System.Drawing.Bitmap(memStrm); + if (bDump) { + echo($"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) { + echo($"ot[th{th},{(index + 1)}/{docG.PageCount}]=,{Path.GetFileName(otPath)},hash:{otHashCode}"); + } + return 0; + } + // image encodeを作成 + var imEnc = new UtImage.Enc(pm.ImageType) { JpegQuality = pm.JpegQ };// Init membrers. + bmp.SetResolution((float)pm.Dpi, (float)pm.Dpi); // imageクラスでもPropertyTagX(Y)Resolutionで設定できそう if (bThDump) { - Console.WriteLine($"ot[th{th},{(page.Index + 1)}/{doc.PageCount}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})"); - } - // - imEnc.SaveImage(bmp, otPath); - // - bmp.Dispose(); + echo($"ot[th{th},{(index + 1)}/{docG.PageCount}]={Path.GetFileName(otPath)}({Directory.GetParent(otPath)})"); + } + // + imEnc.SaveImage(bmp, otPath); + // + bmp.Dispose(); //memStrm.Seek(0); + memStrm.Dispose(); } return 0;//success } @@ -482,62 +283,36 @@ namespace CSRender { bool bHash = false, UtHash.HashData inHashDataTgt = null, UtHash.HashData inHashDataRef = null, - bool bPDFium = false + bool bPDFium = false, + int nPageThreadNum = 4 // 比較結果を返す ページ番号とメッセージ ) { - - var bDump = Check.bDump; var bThDump = Check.bThDump; if(bDump) - Console.WriteLine("RenderPdfDocCompare & diff Image! & non Para"); + echo("RenderPdfDocCompare & diff Image! & non Para"); if (pm == null) pm = new RenderConditionParams(); - - var otDir = inDir;// 今は未使用 + 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); + Console.WriteLine("Error:No output dir"); + return -1;// error } - // PDFファイルを読み込む - - - - // 以下のコードで良いみたい。 - // [変更待ち] var file2 = Windows.Storage.StorageFile.GetFileFromPathAsync(pdfPath).GetAwaiter().GetResult(); + if (!Directory.Exists(otDir)) + Directory.CreateDirectory(otDir); - 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; - } + // PDFファイルを読み込む by PDFium. + var docG = PdfiumViewer.PdfDocument.Load(pdfPath); + var docGRef = PdfiumViewer.PdfDocument.Load(refPdfPath); - // ref - var fileRef = GetStrageFilePath(refPdfPath).Result; - var pdfDocRef = Load(fileRef).Result; - if (pdfDocRef == null) { - Console.WriteLine("error: get PdfDocument(ref) failed"); - return -1; - } + var pageCount = docG.PageCount; - var n = pdfDoc.PageCount; - if ( n != pdfDocRef.PageCount) { - Console.WriteLine($"error: differ page length:{n}:{pdfDocRef.PageCount}"); - return -1; - } - uint[] rNo = makePageRange(inPageRange, n); + uint[] rNo = makePageRange(inPageRange, (uint)pageCount); - int nRetAll = 0;// 一致(def) + int nRetAll = 0;// トータルの不一致ページ数 // ハッシュ情報の確認 /* @@ -565,43 +340,34 @@ namespace CSRender { 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; + MemoryStream tgtStrm = null, refStrm = null; string tgtHash = "", refHash = ""; - // ターゲット レンダー - InMemoryRandomAccessStream RendPageTGT() { - var strm = RenderPageStream(doc:pdfDoc,page:pdfDoc.GetPage((uint)(i-1)), pm:pm,docG:docG); + // ターゲット レンダラ定義 + MemoryStream RendPageTGT() { + var strm = RenderPageStream(docG:docG,index:(int)i-1, pm:pm); return strm; } - // リファレンス レンダー - InMemoryRandomAccessStream RendPageREF() { - var strm = RenderPageStream(doc:pdfDocRef,page:pdfDocRef.GetPage((uint)(i-1)), pm:pm,docG:docGRef); + // リファレンス レンダラ定義 + MemoryStream RendPageREF() { + var strm = RenderPageStream(docG:docGRef, index:(int)i-1, pm:pm); return strm; } - //tgtStrm = RendPageTGT(); + // ターゲット処理 if (tgtHf == null) { - tgtStrm = RendPageTGT(); + tgtStrm = RendPageTGT();// Render tgtHash = GetHashValue(tgtStrm); } else { lock (inHashDataTgt) { tgtHash = tgtHf.GetHashValue((int)(i - 1)); } } - // リファレンス + // リファレンス処理 if (refHf == null) { - refStrm = RendPageREF(); + refStrm = RendPageREF();// Render refHash = GetHashValue(refStrm); } else { lock (inHashDataRef) { @@ -609,52 +375,49 @@ namespace CSRender { } } // compare stream; - int nRet = 1;//異なる(def). + // int nRet = 1;//異なる(diff). var dateStr = DateTime.Now.ToString(); //Console.WriteLine($@"tgt={i}:{tgtHash}"); //Console.WriteLine($@"ref={i}:{refHash}"); if (tgtHash != refHash) { - nRet = 1; - nRetAll = nRet;//全体の不一致設定 + // no match + // 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"); + fcResultMsg.Add((int)i, $@"{dateStr}:[@Difference]:{Path.GetFileName(pdfPath)}.{i}"); } - - //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()); + echo("***** save diff image"); + // diffのjpegを書き出してみる(tgt) + if (tgtStrm == null) + tgtStrm = RendPageTGT(); + var otPath = Path.Combine(otDir, $"{otBaseName}.{i}.tgt.{otExtention}"); + using(var bmp = new System.Drawing.Bitmap(tgtStrm)){ 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()); + // diffのjpegを書き出してみる(ref) + if (refStrm == null) + refStrm = RendPageREF(); + otPath = Path.Combine(otDir, $"{otBaseName}.{i}.ref.{otExtention}"); + using(var bmp = new System.Drawing.Bitmap(refStrm)){ 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 + // match + //nRet = 0; lock (fcResultMsg) { - fcResultMsg.Add((int)i, $@"{dateStr}:[OK]:{Path.GetFileName(pdfPath)}.{i}.png"); - } - } + fcResultMsg.Add((int)i, $@"{dateStr}:[OK]:{Path.GetFileName(pdfPath)}.{i}"); + // Ex 2019年12月18日 19:31:38:[OK]:fname.pdf.1.png + } + } // dispose: if (refStrm != null) refStrm.Dispose(); if (tgtStrm != null) tgtStrm.Dispose(); @@ -676,75 +439,35 @@ namespace CSRender { } File.AppendAllText(resultDataPath, resCont); } - return nRetAll;// success. + return nRetAll;// success. 不一致数を返す } /// /// 比較モード /// - /// PdfDocument - /// ページ番号 - /// 出力ファイル名 + /// ドキュメントハンドル + /// ページ番号(0-) /// 正常: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 // + public static MemoryStream RenderPageStream( + PdfiumViewer.PdfDocument docG, + int index, + // Optional: + RenderConditionParams pm = null ) { + var memStrm = new MemoryStream(); + 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;// + PdfRenderFlags flg = (PdfRenderFlags.ForPrinting | PdfRenderFlags.CorrectFromDpi); + System.Drawing.Image img = docG.Render(index, (float)pm.Dpi, (float)pm.Dpi, flg); + img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Bmp); + //img.Save(memStrm, System.Drawing.Imaging.ImageFormat.Jpeg); + // [注意] PDFuim用にFlush()必要 + memStrm.Flush(); + img.Dispose(); + return memStrm; } /// @@ -787,10 +510,10 @@ namespace CSRender { } return range.ToArray(); } - public static string GetHashValue(Windows.Storage.Streams.InMemoryRandomAccessStream strm) { + public static string GetHashValue(MemoryStream strm) { var alg = new SHA256CryptoServiceProvider(); - strm.Seek(0); - var bin = alg.ComputeHash(strm.AsStream()); + strm.Seek(0, SeekOrigin.Begin); + var bin = alg.ComputeHash(strm); alg.Clear(); // バイト配列をUTF8エンコードで文字列化 var hashedText = new StringBuilder(); @@ -840,9 +563,7 @@ namespace CSRender { } [DataContract] - public class HashFile - { - + public class HashFile { [DataMember(Order=0)] public DateTime UpdateTime; // ファイルの更新日 [DataMember(Order=1)] @@ -859,14 +580,11 @@ namespace CSRender { } } [DataContract] - public class HashData - { + 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(); @@ -890,7 +608,6 @@ namespace CSRender { var hd = HashData.load(hashPath); //ファイルの存在はこっちに任す return hd; } - static public HashData load(string hashPath) { if (!File.Exists(hashPath)) { echo("not find load hash data file"); @@ -921,7 +638,6 @@ namespace CSRender { return x; } } - public bool save(string path) { // JSONに変換するデータを作る。 // 再保存の抑制 @@ -954,9 +670,7 @@ namespace CSRender { UpdateTimeStr= fi.LastWriteTime.ToString(), PageHashCode = new List(pageNum) }; - // Console.WriteLine($@"update={hf.UpdateTime}"); - for (var i = 0; i < pageNum; i++) { hf.PageHashCode.Add(""); } @@ -985,7 +699,6 @@ namespace CSRender { } 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始まり @@ -993,7 +706,6 @@ namespace CSRender { this.Head.Dirty = true;// 変更あり return false; } - if (Files[fName].PageHashCode[n] != hashCode) { this.Head.Dirty = true;// 変更あり Files[fName].PageHashCode[n] = hashCode; @@ -1060,6 +772,21 @@ namespace CSRender { } return 0; } + // Image版 + public int SaveImage(System.Drawing.Image image, string path, string imageType = null) + { + var p = GetParams(); + string imType = imageType?.ToLower(); + if (p != null) { + image.Save(path, GetInfo(imType), GetParams());// imageTypeがnullならSetImageType()のものが使われる + } + else + {// パラメータが空の場合 + var imageFormat = new System.Drawing.Imaging.ImageFormat(GetInfo(imType).FormatID); + image.Save(path, imageFormat); + } + return 0; + } /// /// サポートするEncodeパラメータ一覧 /// 現状Errorが発生して取得できない diff --git a/CSRender/CSRender.csproj b/CSRender/CSRender.csproj index 8a1dbe64e90d3fc94f69d064317a5e1dfcca21e8..0ae266d7bc8d1fc1f4700ce7c74f32ff1a0eb6ab 100644 --- a/CSRender/CSRender.csproj +++ b/CSRender/CSRender.csproj @@ -44,7 +44,7 @@ false - AnyCPU + x64 pdbonly true ..\bin\Release\ @@ -76,7 +76,6 @@ - False diff --git a/CSRender/Program.cs b/CSRender/Program.cs index 74eb522ae0f03539b197e352b3db70c095dee879..8b671066720e3e47d210eed7a35a03aeb9e709c4 100644 --- a/CSRender/Program.cs +++ b/CSRender/Program.cs @@ -1,24 +1,19 @@ 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.Collections.Generic; // For List<> +using System.Linq; // For Enumration using System.Reflection; // Assembly. using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using System.Runtime.InteropServices; - using System.Runtime.Serialization; -using System.ServiceModel; // WCF +using System.ServiceModel; // for WCF using System.Diagnostics; // for Process +// no need +//using System.Runtime.InteropServices; // 作成クラス using CSRender; @@ -37,14 +32,16 @@ namespace CSRenderMain { [DataMember] public string pdfPath = ""; //対象ファイル [DataMember] - public string pdfPath2 = ""; //比較ファイル + public string pdfPathRef = ""; //比較ファイル [DataMember] public string outuptImageDir = ""; // /O [DataMember] public string dpi = "72.0"; // /D [DataMember] - public int para = 4; // 並行数 - [DataMember] + public int para = 4; // 並行数(プロセス数) + [DataMember] + public int paraPage = 4; // 並行数(page処理スレッド) + [DataMember] public string boxSelect = "Crop"; // /B [DataMember] public string pageRange = "1-*"; // /P @@ -76,141 +73,113 @@ namespace CSRenderMain { public ParamData Clone() { return (ParamData)MemberwiseClone(); } - } + } public class Program { - static void DispHelp() { - var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名 - var pgNameFullPath = Assembly.GetExecutingAssembly().Location;// プログラム名 - Console.WriteLine("pageNameF="+pgNameFullPath); + static void DispHelpNoArg() { + var asmName = Assembly.GetExecutingAssembly().GetName(); + var pgName = asmName.Name;// "アセンブリ名" + var v1 = asmName.Version; // "1.0.0.0" +// 逐語的文字列リテラル $@, ダブルクウォートは2つでエスケープされる +// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + Console.Write( $@" +Version={ v1} +{pgName} [/] :Rendering(to JPEG,TIFF,...) +{pgName} /MkHash [/] :Make Imaged hash keys +{pgName} /FC [/] :Compare files - string msg = - $"{pgName} [/] \n" - + $"* Render of PDF file. available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and [Render Options]\n" - + $"\tPDFの画像化は[Basic Rendering]。\n" - + $"\t比較は/MkHash([Make Hash command])後に、/FC([Compare command])で高速実行できます。\n" - + $"\n" - + $"[Basic Rendering] 基本的なレンダリング\n" - + $"\t{pgName} [/] \n" - + $"\t/F : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能\n" - + $"\t/O : 出力ディレクトリ。省略時は\"IMG\"フォルダ\n" - + $"\n" - + $"[Render Options] レンダリングオプション\n" - + $"\t/D <解像度> : 解像度指定 9 - 300dpi(default=72dpi)\n" - + $"\t/JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG)\n" - + $"\t/JPEGQ : Jpegの品質指定1-100(default=91)\n" - + $"\n" - + $"\t/P : ページの範囲を指定する(省略時は全ページ)\n" - + $"\t\t連続した範囲を指定する場合は、ハイフン('-')を用いる。終了側を省略すると最終pageまで。\n" - + $"\t\t複数のページを指定する場合は、カンマ(',')を用いる\n" - + $"\t\tEx. /P \"1,2,30-100\" //1,2pages and 30-100pages.\n" - + $"\t[Unsupport] 未対応↓\n" - + $"\t/L : 入力PDFファイルリスト(*unsupport)\n" - + $"\t/T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need)\n" - + $"\t/OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1])\n" - + $"\t/U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1])\n" - + $"\t/OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport)\n" - + $"\t/PDFium <0or1>: GoogoleのPDFiumViewerエンジンを使用する(default=1>\n" - + $"\t【未】/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox\n" +Render of PDF file.available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and[Render Options] + PDFの画像化は[Basic Rendering]。 + 比較は/MkHash([Make Hash command]) 後に/FC([Compare command]) で高速実行できます。 +For more information,see /H /? or /? +"); +// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + } + static void DispHelpDetail() { + var asmName = Assembly.GetExecutingAssembly().GetName(); + var pgName = asmName.Name;// "アセンブリ名" + var v1 = asmName.Version; // "1.0.0.0" + DispHelpNoArg(); +// 逐語的文字列リテラル $@, ダブルクウォートは2つでエスケープされる +// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + Console.Write($@" +[Basic Rendering] 基本的なレンダリング + {pgName} [/] + /F : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能 + /O : 出力ディレクトリ。省略時は""IMG""フォルダ - + $"\n" - + $"[Make Hash command] 比較用ハッシュ値作成コマンド\n" - + $"\t{pgName} /MkHash ...... \n" - + $"\t/MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること\n" - + $"\t[HASHファイル作成]\n" +[Render Options] レンダリングオプション + /D <解像度> : 解像度指定 9 - 300dpi(default=72dpi) + /JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG) + /JPEGQ : Jpegの品質指定1-100(default=91) - + $"\n" - + $"[Compare command] 比較コマンド\n" - + $"\t{pgName} /FC ...... \n" - + $"\t/FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です\n" - + $"\t 事前に/MkHashを実行しておくことで高速に処理できる\n" - + $"\n" - + $"\t/RESULT : 比較結果を格納するファイルパス\n" - + $"\t\t/FCコマンドを指定すると一致したら0,不一致なら1を返却するようになる\n" - + $"\t\tは、,<[OK] or [@Difference]>の行で構成される\n" - - + $"\n" - + $"[ELSE ] その他のオプション\n" - + $"\t/PDFium <0|1>:PDFiumライブラリを使う,デフォルト=1\n" - + $"\t/NoExeSepa :実行分離しない(遅い)\n" - + $"\t/para <並行数>:並行数を指定(デフォルト4)\n" - + $"\t内部コマンド:/SubExe :実行分離,PDF単位で別Processで処理\n" - + $"\n" - + $"/H or /? : This help\n" + /P : ページの範囲を指定する(省略時は全ページ) + 連続した範囲を指定する場合は、ハイフン('-')を用いる。終了側を省略すると最終pageまで。 + 複数のページを指定する場合は、カンマ(',')を用いる + Ex. /P ""1,2,30-100"" //1,2pages and 30-100pages. + [Unsupport] 未対応↓ + /L : 入力PDFファイルリスト(*unsupport) + /T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need) + /OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1]) + /U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1]) + /OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport) + /PDFium <0or1>: GoogoleのPDFiumViewerエンジンを使用する(default=1> + 【未】/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox +[Make Hash command] 比較用ハッシュ値作成コマンド + {pgName} /MkHash ...... + /MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること + [HASHファイル作成] - /* PDFと同じフォルダに - data.<出力条件>.jsonファイルを作成する - 出力条件:+++ - data.72_BT_JPG_Q34.json - 既に存在したら、それを読んでから、追記(もしくは書き換える>。 - */ +[Compare command] 比較コマンド + {pgName} /FC ...... + /FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です + 事前に/MkHashを実行しておくことで高速に処理できる + /RESULT : 比較結果を格納するファイルパス + /FCコマンドを指定すると一致したら0,不一致なら1を返却するようになる + は、,<[OK] or [@Difference]>の行で構成される - // Remove TEST - //+$"\t[for TEST]\n" - //+$"\t/M Sync(default) or ASync\n" - //+$"\t/M : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n" - + "\n"; +[ELSE ] その他のオプション + /NoExeSepa :実行分離しない(遅い) + /Para <プロセス並行数>:本Exeの並行数を指定(デフォルト4) + /ParaPage <ページ処理スレッド数>:ページ処理のスレッド数を指定(デフォルト4) - Console.Write(msg); +/H or /? : This help"); +// HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH } -#if COMENT -// 設計の見直し - -* ハッシュ作成のみのコマンド - → /MkHashをつけると ハッシュファイルのみ作成。ただし、ディレクトリ指定に限る - → CSRender /MkHash <各種Renderパラメータ> - -* 単純なRenderを維持。それに同時にハッシュファイルのOn/Off - → /MkHashをつけなければよい。 - → CSRender <各種Renderパラメータ> - -* 比較モードは、Autoハッシュで、存在しなければ最初に作成。Target/Refともに。比較しながら作成してよし - Manualでハッシュなしで、遅いけどできるようにする。ハッシュ動作の検証用に - → /FCをつけると、デフォルトでハッシュ優先で検査(Auto相当)、ハッシュ検査を無効にしたければ - → /NoHash追加でハッシュを無視してTarget/Refとも再計算。ハッシュを使いたければ、事前に/MkHashで - 作成しておけばよい。 -* ヘルプは <単純なRender> <ハッシュ作成> <比較>にわける - * <単純なレンダラ>: 1ファイルおよびディレクトリの比較。 Renderingパラメータの一覧(項目分ける)まで. - * <ハッシュ作成>: /MkHashで高速比較のための前準備。ディレクトリモードでハッシュファイルのみを作成する。Renderingパラメータは合わせること - * <比較>:/FCコマンドで比較の説明 - -* ここにAsiccDocを埋められないの? → 情報なし* -// 遅くなるので -* 比較モード時の差異があったとき、Diffを出す上限値をLimitDiffNumがほしい(デフォルトは各PDFで1pageとしたい) -* 比較NGになって、フルでDIFF画像を出したいときはLimitDiffを解除すべき。単独ファイル指定のとき。 - -// 設計の見直し(END) -#endif /// - /// PDF renderer main + /// PDF render main /// /// - /// + /// 0 正常終了 static int Main(string[] args) { - - // 引数の保持 - var pm = new ParamData(); - - bool bDirMode = false; // ディレクト指定の場合 - - //var pdfPathLst = new List(); + var argsStr = String.Join(@" ", args); + var pm = new ParamData(); // 引数の保持 + bool bDirMode = false; // ディレクト指定の場合 string[] pdfPathLst = null; string[] pdfPathLst2 = null; var pdfPathLstBoth = new List(); var pdfPathLstNoBoth = new List(); - //ハッシュ関係 string hashBaseName = "RenderHash"; - CSRender.Check.GetDPI(); + + // No CSRender.Check.GetDPI(); + + // 未使用 + //{// ベースハッシュ値の計算テスト + // var streamPDF = ResData.GetBaseHashPDF(); + // RenderPDF.RenderPdfInitCheck(streamPDF); + //} - //{// ベースハッシュ値の計算テスト - // // 一旦使用しない - // var streamPDF = ResData.GetBaseHashPDF(); - // RenderPDF.RenderPdfInitCheck(streamPDF); - //} + var logicalProcessNum = Environment.ProcessorCount; - var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) + if ( logicalProcessNum >= 10 ) {// 元々 4(exe)+4(page)なので+2 = プロセス数が10以上の時にexe数を増やす + pm.para = logicalProcessNum - pm.paraPage; + } + /* + * ↓引数解析 + * */ + var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) while (qu.Count > 0) { // 引数のパース var wd = qu.Dequeue(); // 取り出しqu.Dequeue()で次の要素を取得する @@ -227,8 +196,8 @@ namespace CSRenderMain { ["/BB"] = "Bleed", ["/BC"] = "Crop", ["/BA"] = "Art" }; - if (isOpt("/?", "/H")) { - DispHelp(); + if (isOpt("/?", "/H","/Help")) { + DispHelpDetail(); return -1; } else if (isOpt("/F")) { pm.pdfPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word. @@ -238,17 +207,24 @@ namespace CSRenderMain { pm.dpi = (qu.Count > 0) ? qu.Dequeue() : "";// next word. if (!double.TryParse(pm.dpi, out double dmy)) { Console.WriteLine($"解像度が不正です:/D {pm.dpi}"); - DispHelp(); + DispHelpNoArg(); return -1; } } else if (isOpt("/Para")) { string paraNum = (qu.Count > 0) ? qu.Dequeue() : "";// next word. if (!int.TryParse(paraNum, out pm.para)) { Console.WriteLine($"並行数が不正です:/para {paraNum}"); - DispHelp(); + DispHelpNoArg(); return -1; } - } else if (isOpt("/P")) { + } else if (isOpt("/ParaPage")) { + string paraNum = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + if (!int.TryParse(paraNum, out pm.paraPage)){ + Console.WriteLine($"並行数(ページスレッド数が不正です:/paraPage {paraNum}"); + DispHelpNoArg(); + return -1; + } + } else if (isOpt("/P")) { pm.pageRange = (qu.Count > 0) ? qu.Dequeue() : "";// next word. } else if (isOpt("/JPG", "/JPEG")) { pm.imageType = "JPG"; @@ -264,7 +240,7 @@ namespace CSRenderMain { pm.jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word. if (!int.TryParse(pm.jpegQ, out int dmy)) { Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {pm.jpegQ}"); - DispHelp(); + DispHelpNoArg(); return -1; } if (!(0 < dmy && dmy <= 100)) { @@ -323,13 +299,14 @@ namespace CSRenderMain { pm.pdfPath = wd; continue; } - if (pm.bFC && (pm.pdfPath2 == "")) { - pm.pdfPath2 = wd;// 比較時のみ取得 + if (pm.bFC && (pm.pdfPathRef == "")) { + pm.pdfPathRef = wd;// 比較時のみ取得 continue; } } } // ↑引数解析終わり + setEcho(pm.bVerbose); if (pm.bVerbose) echo("Varbose Mode!"); @@ -339,6 +316,7 @@ namespace CSRenderMain { if (!bSubExe) { /// サーバー側 echo("++MainProcess start"); + Console.WriteLine($"LogicalProcNum={logicalProcessNum},ProcessParallelTasks={pm.para},PageParallelTasks={pm.paraPage}"); } else { // SubExe側の動作に差し替える var sp = pm.subExe.Split(':'); @@ -352,7 +330,7 @@ namespace CSRenderMain { if (pm.pdfPath == "") { Console.WriteLine("pdfファイルが指定されてません"); - DispHelp(); + DispHelpNoArg(); return -1; } if (!(File.Exists(pm.pdfPath) || Directory.Exists(pm.pdfPath))) { @@ -371,15 +349,15 @@ namespace CSRenderMain { // Directoryが指定されたのでファイルリストアップ bDirMode = true; pdfPathLst = System.IO.Directory.GetFiles(pm.pdfPath, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); - pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); // 配列書き換え(ファイル名のみにする - // var enumLst = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない) - //foreach ( var f in pdfPathLst) { - // Console.WriteLine($@"path1={f}"); - //} + pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); + // 配列書き換え(ファイル名のみにする + // var enumLst = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない) + //foreach ( var f in pdfPathLst) { + // Console.WriteLine($@"path1={f}"); + //} //Console.WriteLine($@"path1.Len={pdfPathLst.Count()}"); } - var resultData = new SortedDictionary(); if ((!pm.bFC) && bDir) { @@ -389,22 +367,22 @@ namespace CSRenderMain { } } if (pm.bFC) {// 比較モード時 - if (pm.pdfPath2 == "") { + if (pm.pdfPathRef == "") { Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません"); return -1; } - if (!(File.Exists(pm.pdfPath2) || Directory.Exists(pm.pdfPath2))) { - Console.WriteLine($"比較ファイルが存在しません:{pm.pdfPath2}"); + if (!(File.Exists(pm.pdfPathRef) || Directory.Exists(pm.pdfPathRef))) { + Console.WriteLine($"比較ファイルが存在しません:{pm.pdfPathRef}"); return -1; } - bool bDir2 = File.GetAttributes(pm.pdfPath2).HasFlag(FileAttributes.Directory); + bool bDir2 = File.GetAttributes(pm.pdfPathRef).HasFlag(FileAttributes.Directory); if (bDir2) { if (!bDir) { - Console.WriteLine($"比較対象はファイルパスでないといけません:{pm.pdfPath2}"); + Console.WriteLine($"比較対象はファイルパスでないといけません:{pm.pdfPathRef}"); return -1; } // 2つ目のファイルリストアップ - pdfPathLst2 = System.IO.Directory.GetFiles(pm.pdfPath2, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); + pdfPathLst2 = System.IO.Directory.GetFiles(pm.pdfPathRef, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); pdfPathLst2 = Array.ConvertAll(pdfPathLst2, f => Path.GetFileName(f));// 配列の書き換え // 共通のファイルを見つける。Lstの要素がLst2に含まれているかどうか @@ -412,13 +390,13 @@ namespace CSRenderMain { if (pdfPathLst2.Contains(f)) { pdfPathLstBoth.Add(f); } else { - pdfPathLstNoBoth.Add(f);// LstがLst2に含まれていない + pdfPathLstNoBoth.Add("tgt:"+f);// LstがLst2に含まれていない } } // Lst2のみファイルをNoBothに登録 foreach (var f in pdfPathLst2) { if (!pdfPathLstBoth.Contains(f)) { - pdfPathLstNoBoth.Add(f); + pdfPathLstNoBoth.Add("ref:"+f); } } // pdfPathBoth,pdfPathNoBothが作成済み @@ -433,12 +411,18 @@ namespace CSRenderMain { var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う var otDir = pm.outuptImageDir; if (otDir == "") { - // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する - otDir = Path.Combine(Directory.GetParent(pm.pdfPath).FullName, "IMG"); - } - - if (!Directory.Exists(otDir) /* && (!bMkHash)*/ ) { // MkHashのとき不要→必要 - Directory.CreateDirectory(otDir); + if ( bDir ) { + var suffix = pm.bFC ? ".DiffImage": ".IMG"; + otDir = Path.Combine( Directory.GetParent(pm.pdfPath).FullName, Path.GetFileName( pm.pdfPath )+suffix); + echo($"****Output dir={otDir} ******\n"); + if (!Directory.Exists(otDir) && (!pm.bMkHash) ) { // + Directory.CreateDirectory(otDir); + } + } else { + // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する + otDir = Path.Combine(Directory.GetParent(pm.pdfPath).FullName, "IMG"); + } + pm.outuptImageDir = otDir;// pmに戻す } var otHashPath = ""; var otHashPath2 = ""; @@ -450,10 +434,10 @@ namespace CSRenderMain { //比較モード // ハッシュデータの読み込み echo($@"pre pdfPath={pm.pdfPath}"); - echo($@"pre pdfPath2={pm.pdfPath2}"); + echo($@"pre pdfPathRef={pm.pdfPathRef}"); var hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); - var hashDataRef = HashData.load(pm.pdfPath2, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); + var hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); //Console.WriteLine(""); if (pm.resultPath != "") {// 初期化 @@ -462,14 +446,15 @@ namespace CSRenderMain { ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(pm.pdfPath), - refPdfPath: Path.GetFullPath(pm.pdfPath2), + refPdfPath: Path.GetFullPath(pm.pdfPathRef), resultDataPath: pm.resultPath, inDir : otDir, pm : rdCond, inPageRange : pm.pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef, - bPDFium : pm.bPDFium + bPDFium : pm.bPDFium, + nPageThreadNum: pm.paraPage ); } else { // Sync version. @@ -488,8 +473,9 @@ namespace CSRenderMain { inPageRange : pm.pageRange, bSaveImage : pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。 bHash : pm.bHash, - inHashData : hashDataTgt, - bPDFium : pm.bPDFium + inHashData : pm.bMkHash ? hashDataTgt : null, //ハッシュコマンドモードのみDataを渡す + bPDFium : pm.bPDFium, + nPageThreadNum : pm.paraPage ); if (pm.bMkHash) { /// ここでMain側に通信データを返せばよい @@ -503,10 +489,10 @@ namespace CSRenderMain { //表示のみ まだやめておく: } } - // retはページ数です。 + // retはページ数を返す } } else { - // 対象がディレクトリ + // 対象がディレクトリの場合 echo("For Directory mode"); HashData hashDataTgt = null, hashDataRef = null; hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); @@ -520,23 +506,16 @@ namespace CSRenderMain { } if (pm.bFC) { // リファレンス側のハッシュの読み込み - hashDataRef = HashData.load(pm.pdfPath2, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); + hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); if (hashDataRef == null) { // 空のハッシュデータを作成 hashDataRef = new HashData(); hashDataRef.Head.SetRenderInfo(pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ); } } - // ハッシュデータが適切に作成済みがどうかを確認。もしハッシュデータが存在しない場合は - // ハッシュデータを渡してはいけない。ハッシュのFilesで確認するか? - // ハッシュデータの作成プロセスをもっと簡単にすべき echo($@"Start render:Count={pdfPathLstBoth.Count}"); //Console.WriteLine("");[ var count = pdfPathLstBoth.Count; - - //ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 6 }; - //Parallel.For(0,pdfPathLstBoth.Count, options, index => { - //逆に遅くなる UWPコール(render)は対応していない? if (pm.bExeSepa &&(!pm.bFC)) { // FCモードは除外します。 var tokenSource = new CancellationTokenSource(); ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = pm.para }; @@ -544,12 +523,12 @@ namespace CSRenderMain { var loopResult = Parallel.For(0,pdfPathLstBoth.Count, options, (index,lpState) => { //for (var index = 0; index < pdfPathLstBoth.Count; index++) { var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); - var tgtID = $@"{index}/{pdfPathLstBoth.Count}"; + var tgtID = $@"{index+1}/{pdfPathLstBoth.Count}"; Console.WriteLine($@"**Remain({count})****tgt({tgtID})={tgt}"); if (pm.bFC) { //比較モード - var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]); + var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); } else { // Sync version(Paralles). var svrData = new xChangeWCFPipe.XData(); var pmClone = pm.Clone(); @@ -583,7 +562,7 @@ namespace CSRenderMain { if ( retProc < 1 ) { //PDFページ数以外が返った時 ret = -1; - Console.WriteLine( $@"Warning ::Cannot get PDFPage:{retProc}"); + Console.WriteLine( $@"Warning ::Cannot get PDFPage:{retProc},tgt={tgt},{argsStr}"); } else { ;// 初期値 ret=0; } @@ -613,13 +592,13 @@ namespace CSRenderMain { proc.Dispose(); svrHost.Close(); svrData = null; - Console.WriteLine($@"End tgt({tgtID})"); + echo($@"End tgt({tgtID})"); } //GC.Collect();// これでメモリリークが解決した Paraの中ではやめておく count--; }); if ( !loopResult.IsCompleted ) { - Console.WriteLine("中断"); + Console.WriteLine("Abort!"); pm.bMkHash = false;// Hash値保存抑制 } ////tokenSource.Cancel();// 処理のキャンセル @@ -627,8 +606,90 @@ namespace CSRenderMain { // pm.bMkHash = false;// Hash値保存抑制 //} //}; // Non Para Block - } else { // NoExeSepa + echo($"********** コマンド終了*****{argsStr} "); + } else if (pm.bExeSepa &&(pm.bFC)) { // セパのFCモード + /* + refのハッシュが存在しなければWCFで作成 + tgtのハッシュは強制的にWCFで作成 + */ + echo($"****************セパのFCモード******{argsStr}************"); + hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); + if ( hashDataRef == null ) { + var myExePath = Assembly.GetExecutingAssembly().Location; + var proc = Ut.Ut.DoCmd( + cmdAndArgs: new string[] { myExePath, "/MkHash",pm.pdfPathRef,"/D",pm.dpi,$"/{pm.imageType }",$"/JPEGQ",pm.jpegQ } + , bEcho: true + , bSameConsole: true + ); + proc.WaitForExit(); + hashDataRef = HashData.load(pm.pdfPathRef, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); + if ( hashDataRef != null ) { + echo($" OK hash ref"); + } + } else { + echo($" OK hash ref(already exist)"); + } + if (true){ + var myExePath = Assembly.GetExecutingAssembly().Location; + var proc = Ut.Ut.DoCmd( + cmdAndArgs: new string[] { myExePath, "/MkHash",pm.pdfPath,"/D",pm.dpi, $"/{pm.imageType }",$"/JPEGQ",pm.jpegQ } + , bEcho: true + , bSameConsole: true + ); + proc.WaitForExit(); + hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); + if ( hashDataTgt != null ) { + echo($" OK hash tgt"); + } + } + echo($@"****************FC開始*****{argsStr}**************"); + if (pm.resultPath != "") {// 初期化 + File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); + } + int noMatchPageNum=0; + int noMatchFileNum=0; + for (var index = 0; index < pdfPathLstBoth.Count; index++) { + var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); + Console.WriteLine($@"tgt={index+1}/{pdfPathLstBoth.Count}:{tgt}"); + var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); + ret = RenderPDF.RenderPdfDocCompare( + pdfPath: Path.GetFullPath(tgt), + refPdfPath: Path.GetFullPath(reff), + resultDataPath: pm.resultPath, + inDir: otDir, + pm: rdCond, + inPageRange: pm.pageRange, + inHashDataTgt: hashDataTgt, + inHashDataRef: hashDataRef, + bPDFium: pm.bPDFium, + nPageThreadNum:pm.paraPage + ); + if ( ret > 0 ) { + // retは不一致ページ数 + noMatchFileNum++; + noMatchPageNum+= ret; + } + }; + watch.Stop(); + + var SummaryResult = $"\n[結果]\n"; + SummaryResult += $"不整合ファイル群={pdfPathLstNoBoth.Count()}\n"; + foreach (var f in pdfPathLstNoBoth) { + SummaryResult += $"\tWarning [no match]={f}\n"; + } + SummaryResult += $"NGファイル数={noMatchFileNum}\n"; + SummaryResult += $"NGページ数 ={noMatchPageNum}\n"; + SummaryResult += $"Result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]"; + Console.WriteLine(SummaryResult);// 結果標準出力 + if (pm.resultPath != "") {// ファイル出力の先頭に追記 + var rData = File.ReadAllText(pm.resultPath); + SummaryResult += "\n[詳細] *find @Difference\n"; + File.WriteAllText(Path.GetFullPath(pm.resultPath), (SummaryResult + rData)); + } + echo($"****************[終了]セパのFCモード***{argsStr}***************"); + } else { // NoExeSepa + echo($@"****************NoExeSepa*****{argsStr}**************"); if (pm.resultPath != "") {// 初期化 File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); } @@ -637,7 +698,7 @@ namespace CSRenderMain { Console.WriteLine($@"tgt={index+1}/{pdfPathLstBoth.Count}:{tgt}"); if (pm.bFC) { //比較モード - var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]); + var reff = Path.Combine(pm.pdfPathRef, pdfPathLstBoth[index]); //Console.WriteLine(""); ret = RenderPDF.RenderPdfDocCompare( pdfPath: Path.GetFullPath(tgt), @@ -648,8 +709,10 @@ namespace CSRenderMain { inPageRange: pm.pageRange, inHashDataTgt: hashDataTgt, inHashDataRef: hashDataRef, - bPDFium: pm.bPDFium + bPDFium: pm.bPDFium, + nPageThreadNum:pm.paraPage ); + } else { // Sync version(Paralles). ret = RenderPDF.RenderPdfDoc( pdfPath: Path.GetFullPath(tgt), @@ -659,7 +722,8 @@ namespace CSRenderMain { bSaveImage: pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。 bHash: pm.bHash,// bHash,dumy inHashData: pm.bMkHash ? hashDataTgt : null, //ハッシュコマンドモードのみDataを渡す - bPDFium: pm.bPDFium + bPDFium: pm.bPDFium, + nPageThreadNum: pm.paraPage ); } count--; @@ -672,16 +736,6 @@ namespace CSRenderMain { } watch.Stop(); Console.WriteLine($"result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]"); -#if COMMNET - Assembly assm = Assembly.GetExecutingAssembly(); - Console.WriteLine(assm.FullName); - // 参照しているすべてのアセンブリを取得し、表示する - foreach (AssemblyName refassm in assm.GetReferencedAssemblies()) { - Console.WriteLine($@" {refassm.FullName},Ver({refassm.Version}),VerComp({refassm.VersionCompatibility})" ); - } - string clrVersion = System.Environment.Version.ToString(); - Console.WriteLine($@"clrVer={clrVersion}"); -#endif return ret;//success. } @@ -861,22 +915,6 @@ namespace xChangeWCFPipeGEN _service = oInstance; // デリゲート登録 //callDelegate = () => { Console.WriteLine("svr:Delegate func called"); } -#if false - { - // インターフェースを求める - // AddServiceEndpoint()にわたすIXDataはXData.Interfaces()から求められるが - // インタフェースは一つとは限らないのでやめておく。 - Type interfType = null; - Type type = _service.GetType(); - Type[] interfaces = type.GetInterfaces(); - Console.WriteLine($@"GetInterfaces***********************************"); - foreach (Type t in interfaces) { - Console.WriteLine(t.ToString()); - interfType = t; - }; - Console.WriteLine($@"End of GetInterfaces***********************************"); - } -#endif _serviceHost = new ServiceHost(_service, new Uri(uri)); try { _serviceHost.AddServiceEndpoint(_baseIf/*typeof(Ti)*/, new NetNamedPipeBinding(), pipeAddress); @@ -902,14 +940,9 @@ namespace xChangeWCFPipeGEN static public Ti makeCLT(string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") { const string pipeBase = "net.pipe://localhost"; var address = pipeBase + "/" + pipeName + "/" + pipeAddress; - //try { var factory = (new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress(address))); Ti toMain = factory.CreateChannel(); return toMain; - //} catch (Exception e) { - // Console.WriteLine("makeCLT failed:" + e.ToString()); - // return null; - //} } } } @@ -997,6 +1030,7 @@ namespace Ut /// /// ProcessID static int GetCurrentProcessId() { return Process.GetCurrentProcess().Id; } + /// /// 外部プログラムを起動する /// @@ -1008,7 +1042,8 @@ namespace Ut /// public static Process DoCmd(IEnumerable cmdAndArgs, bool bSameConsole = true, bool bEcho = true, bool bWait = false, int nWaitLimitMSec = -1) { var cmdName = cmdAndArgs.First();//[0]; - var argStr = makeCmdLine(cmdAndArgs.Skip(1));// 先頭を除外してコピー new ArraySegment(cmdAndArgs.ToArray(), 1, cmdAndArgs.Count()-1) + var argStr = makeCmdLine(cmdAndArgs.Skip(1)); + // 先頭を除外してコピー new ArraySegment(cmdAndArgs.ToArray(), 1, cmdAndArgs.Count()-1) //Console.WriteLine($@"DoCmd:{cmdName}:args[{argStr}]"); //using (var p = new Process()) { var p = new Process(); diff --git a/CSRender/Properties/AssemblyInfo.cs b/CSRender/Properties/AssemblyInfo.cs index be2612ba35565598868b9a404d73a9f7695e8e6f..fe94bcfe91d4c0718494aa0b7fd8b64e61a1171d 100644 --- a/CSRender/Properties/AssemblyInfo.cs +++ b/CSRender/Properties/AssemblyInfo.cs @@ -1,4 +1,5 @@ -using System.Reflection; +using System.Resources; +using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -10,7 +11,7 @@ using System.Runtime.InteropServices; [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("CSRender")] -[assembly: AssemblyCopyright("Copyright © 2020")] +[assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,9 +33,11 @@ using System.Runtime.InteropServices; // すべての値を指定するか、次を使用してビルド番号とリビジョン番号を既定に設定できます // 既定値にすることができます: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.3.0.5")] -[assembly: AssemblyFileVersion("1.3.0.5")] +[assembly: AssemblyVersion("1.3.1.0")] +[assembly: AssemblyFileVersion("1.3.1.0")] +[assembly: NeutralResourcesLanguage("ja")] +// rev:1.3.1.x:2021/10/05 :不要コード削除(Windows.Data.pdf) // rev:1.3.0.5:2021/09/14 :/Paraで並行プロセス最大値の設定追加 // rev:1.3.0.4:2021/04/29 :Helpの記述抜け "/Result"の対応 // rev:1.3.0.3:2020/11/22 :/Result <比較結果> を実装。diff画像を出力 diff --git a/CSRender/Program_Scrap.cs b/CSRender/Scrap1/Program_Scrap.cs similarity index 100% rename from CSRender/Program_Scrap.cs rename to CSRender/Scrap1/Program_Scrap.cs diff --git a/CSRender/Program_Scrap2.cs b/CSRender/Scrap1/Program_Scrap2.cs similarity index 100% rename from CSRender/Program_Scrap2.cs rename to CSRender/Scrap1/Program_Scrap2.cs diff --git a/CSRender/Program_Scrap3.cs b/CSRender/Scrap1/Program_Scrap3.cs similarity index 100% rename from CSRender/Program_Scrap3.cs rename to CSRender/Scrap1/Program_Scrap3.cs diff --git a/CSRender/Program_Scrap4_leakOK.cs b/CSRender/Scrap1/Program_Scrap4_leakOK.cs similarity index 100% rename from CSRender/Program_Scrap4_leakOK.cs rename to CSRender/Scrap1/Program_Scrap4_leakOK.cs diff --git a/CSRender/Program_mg0607.cs b/CSRender/Scrap1/Program_mg0607.cs similarity index 100% rename from CSRender/Program_mg0607.cs rename to CSRender/Scrap1/Program_mg0607.cs diff --git a/CSRender/Scrap2/CSRender_full.cs b/CSRender/Scrap2/CSRender_full.cs new file mode 100644 index 0000000000000000000000000000000000000000..02d33d2ed946d0889c22eca0ef0ad0c12ed55344 --- /dev/null +++ b/CSRender/Scrap2/CSRender_full.cs @@ -0,0 +1,1132 @@ +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; + } + } + + } +} diff --git a/CSRender/Scrap2/Program_full.cs b/CSRender/Scrap2/Program_full.cs new file mode 100644 index 0000000000000000000000000000000000000000..74eb522ae0f03539b197e352b3db70c095dee879 --- /dev/null +++ b/CSRender/Scrap2/Program_full.cs @@ -0,0 +1,1046 @@ +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.Threading; +using System.Threading.Tasks; + +using System.Runtime.InteropServices; + +using System.Runtime.Serialization; +using System.ServiceModel; // WCF +using System.Diagnostics; // for Process + + +// 作成クラス +using CSRender; +using CSRender.UtHash; +using static CSRender.RenderPDF; + +// *重要* デスクトップアプリで UWP Api を呼び出す プロジェクトの設定方法が記載されている +// https://docs.microsoft.com/ja-jp/windows/apps/desktop/modernize/desktop-to-uwp-enhance +// 参照はwindows. winmd :C:\Program Files (x86) \Windows Kits\10\UnionMetadata\/ファサード + + +namespace CSRenderMain { + [DataContract] + //[Serializable()] + public class ParamData { + [DataMember] + public string pdfPath = ""; //対象ファイル + [DataMember] + public string pdfPath2 = ""; //比較ファイル + [DataMember] + public string outuptImageDir = ""; // /O + [DataMember] + public string dpi = "72.0"; // /D + [DataMember] + public int para = 4; // 並行数 + [DataMember] + public string boxSelect = "Crop"; // /B + [DataMember] + public string pageRange = "1-*"; // /P + [DataMember] + public string mode = "page"; // /MODE + [DataMember] + public string imageType = "JPG"; // /JPG or /PNG + [DataMember] + public string jpegQ = "91"; // JPEGQ + [DataMember] + public bool bHash = false; // ハッシュ値を生成する + [DataMember] + public bool bMkHash = false; // ハッシュファイルを作成する + [DataMember] + public bool bFC = false; + [DataMember] + public string resultPath = ""; + [DataMember] + public bool bPDFium = true; + // + [DataMember] + public bool bExeSepa = true;// 実行分離 + [DataMember] + public string subExe = null;// "Sub"が指定されたら、処理をExe分離する。内部コマンド + // データはWCFの通信データを使う。引数はPipeName:PortAddressNameとする + [DataMember] + public bool bVerbose = false;// 詳細デバッグ + + public ParamData Clone() { + return (ParamData)MemberwiseClone(); + } + } + public class Program { + static void DispHelp() { + var pgName = Path.GetFileName(Path.GetFileName(Assembly.GetExecutingAssembly().Location));// プログラム名 + var pgNameFullPath = Assembly.GetExecutingAssembly().Location;// プログラム名 + Console.WriteLine("pageNameF="+pgNameFullPath); + + string msg = + $"{pgName} [/] \n" + + $"* Render of PDF file. available 3 command mode:[Basic Rendering] [Make Hash command] [Compare command], and [Render Options]\n" + + $"\tPDFの画像化は[Basic Rendering]。\n" + + $"\t比較は/MkHash([Make Hash command])後に、/FC([Compare command])で高速実行できます。\n" + + $"\n" + + $"[Basic Rendering] 基本的なレンダリング\n" + + $"\t{pgName} [/] \n" + + $"\t/F : pdfPath(pdfファイル名|ディレクトリ) /Fは省略可能\n" + + $"\t/O : 出力ディレクトリ。省略時は\"IMG\"フォルダ\n" + + $"\n" + + $"[Render Options] レンダリングオプション\n" + + $"\t/D <解像度> : 解像度指定 9 - 300dpi(default=72dpi)\n" + + $"\t/JPG,/JPEG,/PNG,/TIF,/TIFF,/GIF,/BMP: Select one output format.(default=/JPG)\n" + + $"\t/JPEGQ : Jpegの品質指定1-100(default=91)\n" + + $"\n" + + $"\t/P : ページの範囲を指定する(省略時は全ページ)\n" + + $"\t\t連続した範囲を指定する場合は、ハイフン('-')を用いる。終了側を省略すると最終pageまで。\n" + + $"\t\t複数のページを指定する場合は、カンマ(',')を用いる\n" + + $"\t\tEx. /P \"1,2,30-100\" //1,2pages and 30-100pages.\n" + + $"\t[Unsupport] 未対応↓\n" + + $"\t/L : 入力PDFファイルリスト(*unsupport)\n" + + $"\t/T : テンポラリフォルダを指定(省略時は出力先フォルダと同じ(*unsupport no need)\n" + + $"\t/OP <0|1> : オーバープリントのOn/Off (省略時は1)(*unsupport allways on[1])\n" + + $"\t/U <0|1> : 同名上書き設定 0:上書きしない 1:上書き(*unsupport allways overwrite[1])\n" + + $"\t/OFFSET : ミリ単位でオフセットを指定する(省略時は共に0mm)(*unsupport)\n" + + $"\t/PDFium <0or1>: GoogoleのPDFiumViewerエンジンを使用する(default=1>\n" + + $"\t【未】/BM,/BT,/BA,/BA,/BC: Select one box.(default=/BC:CrobBox): Boxies:MediaBox/BleedBox/TrimBox/ArtBox/CropBox\n" + + + $"\n" + + $"[Make Hash command] 比較用ハッシュ値作成コマンド\n" + + $"\t{pgName} /MkHash ...... \n" + + $"\t/MKHash : ハッシュ値を出力する。前記の[Render Options]を指定すること\n" + + $"\t[HASHファイル作成]\n" + + + $"\n" + + $"[Compare command] 比較コマンド\n" + + $"\t{pgName} /FC ...... \n" + + $"\t/FC : 2つのPDFを比較する。前記の[Render Options]を指定すること。無名引数が2つ必要です\n" + + $"\t 事前に/MkHashを実行しておくことで高速に処理できる\n" + + $"\n" + + $"\t/RESULT : 比較結果を格納するファイルパス\n" + + $"\t\t/FCコマンドを指定すると一致したら0,不一致なら1を返却するようになる\n" + + $"\t\tは、,<[OK] or [@Difference]>の行で構成される\n" + + + $"\n" + + $"[ELSE ] その他のオプション\n" + + $"\t/PDFium <0|1>:PDFiumライブラリを使う,デフォルト=1\n" + + $"\t/NoExeSepa :実行分離しない(遅い)\n" + + $"\t/para <並行数>:並行数を指定(デフォルト4)\n" + + $"\t内部コマンド:/SubExe :実行分離,PDF単位で別Processで処理\n" + + $"\n" + + $"/H or /? : This help\n" + + + /* PDFと同じフォルダに + data.<出力条件>.jsonファイルを作成する + 出力条件:+++ + data.72_BT_JPG_Q34.json + 既に存在したら、それを読んでから、追記(もしくは書き換える>。 + */ + + // Remove TEST + //+$"\t[for TEST]\n" + //+$"\t/M Sync(default) or ASync\n" + //+$"\t/M : pageBitMap(default), pageBitMapImage, page(same as pageBitMap), org(orginal source)\n" + + "\n"; + + Console.Write(msg); + } +#if COMENT +// 設計の見直し + +* ハッシュ作成のみのコマンド + → /MkHashをつけると ハッシュファイルのみ作成。ただし、ディレクトリ指定に限る + → CSRender /MkHash <各種Renderパラメータ> + +* 単純なRenderを維持。それに同時にハッシュファイルのOn/Off + → /MkHashをつけなければよい。 + → CSRender <各種Renderパラメータ> + +* 比較モードは、Autoハッシュで、存在しなければ最初に作成。Target/Refともに。比較しながら作成してよし + Manualでハッシュなしで、遅いけどできるようにする。ハッシュ動作の検証用に + → /FCをつけると、デフォルトでハッシュ優先で検査(Auto相当)、ハッシュ検査を無効にしたければ + → /NoHash追加でハッシュを無視してTarget/Refとも再計算。ハッシュを使いたければ、事前に/MkHashで + 作成しておけばよい。 +* ヘルプは <単純なRender> <ハッシュ作成> <比較>にわける + * <単純なレンダラ>: 1ファイルおよびディレクトリの比較。 Renderingパラメータの一覧(項目分ける)まで. + * <ハッシュ作成>: /MkHashで高速比較のための前準備。ディレクトリモードでハッシュファイルのみを作成する。Renderingパラメータは合わせること + * <比較>:/FCコマンドで比較の説明 + +* ここにAsiccDocを埋められないの? → 情報なし* +// 遅くなるので +* 比較モード時の差異があったとき、Diffを出す上限値をLimitDiffNumがほしい(デフォルトは各PDFで1pageとしたい) +* 比較NGになって、フルでDIFF画像を出したいときはLimitDiffを解除すべき。単独ファイル指定のとき。 + +// 設計の見直し(END) +#endif + + /// + /// PDF renderer main + /// + /// + /// + static int Main(string[] args) { + + // 引数の保持 + var pm = new ParamData(); + + bool bDirMode = false; // ディレクト指定の場合 + + //var pdfPathLst = new List(); + string[] pdfPathLst = null; + string[] pdfPathLst2 = null; + var pdfPathLstBoth = new List(); + var pdfPathLstNoBoth = new List(); + + //ハッシュ関係 + string hashBaseName = "RenderHash"; + CSRender.Check.GetDPI(); + + //{// ベースハッシュ値の計算テスト + // // 一旦使用しない + // var streamPDF = ResData.GetBaseHashPDF(); + // RenderPDF.RenderPdfInitCheck(streamPDF); + //} + + var qu = new Queue(args); // 引数をQueに登録 (qu.Enqueue(a)でも可能) + while (qu.Count > 0) { + // 引数のパース + var wd = qu.Dequeue(); // 取り出しqu.Dequeue()で次の要素を取得する + if (wd.First() == '-') { + // 先頭-(ハイフンもオプション扱いにする)→"/"に置換 + wd = Regex.Replace(wd, @"^\-", "/"); + } + //大文字小文字無視でオプションチェック + var eIgnoreCase = StringComparer.OrdinalIgnoreCase; + // オプションチェックローカル関数 + bool isOpt(params string[] opts) => opts.Contains(wd, eIgnoreCase); + // ボックスオプション辞書 + var BoxSelOptDic = new Dictionary(eIgnoreCase) { ["/BM"] = "Media", ["/BT"] = "Trim", + ["/BB"] = "Bleed", ["/BC"] = "Crop", + ["/BA"] = "Art" + }; + if (isOpt("/?", "/H")) { + DispHelp(); + return -1; + } else if (isOpt("/F")) { + pm.pdfPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + } else if (isOpt("/O")) { + pm.outuptImageDir = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + } else if (isOpt("/D")) { + pm.dpi = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + if (!double.TryParse(pm.dpi, out double dmy)) { + Console.WriteLine($"解像度が不正です:/D {pm.dpi}"); + DispHelp(); + return -1; + } + } else if (isOpt("/Para")) { + string paraNum = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + if (!int.TryParse(paraNum, out pm.para)) { + Console.WriteLine($"並行数が不正です:/para {paraNum}"); + DispHelp(); + return -1; + } + } else if (isOpt("/P")) { + pm.pageRange = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + } else if (isOpt("/JPG", "/JPEG")) { + pm.imageType = "JPG"; + } else if (isOpt("/PNG")) { + pm.imageType = "PNG"; + } else if (isOpt("/TIF", "/TIFF")) { + pm.imageType = "TIFF"; + } else if (isOpt("/GIF", "/GIFF")) { + pm.imageType = "GIF"; + } else if (isOpt("/BMP")) { + pm.imageType = "BMP"; + } else if (isOpt("/JPEGQ")) { + pm.jpegQ = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + if (!int.TryParse(pm.jpegQ, out int dmy)) { + Console.WriteLine($"JPEG Qualityが不正です:/JPEGQ {pm.jpegQ}"); + DispHelp(); + return -1; + } + if (!(0 < dmy && dmy <= 100)) { + Console.WriteLine($"JPEG Qualityが不正です(not 1-100):/JPEGQ {dmy}"); + return -1; + } + } else if (isOpt("/M")) { + pm.mode = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + } else if (BoxSelOptDic.ContainsKey(wd)) { + pm.boxSelect = BoxSelOptDic[wd];// "/BT" -> "Trim",... + } else if (isOpt("/HASH")) { + pm.bHash = true; + } else if (isOpt("/MKHASH")) { + pm.bMkHash = true; + pm.bHash = true; + } else if (isOpt("/FC")) { + pm.bFC = true; + } else if (isOpt("/PDFium")) { + pm.bPDFium = true; + var flgStr = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + if (!int.TryParse(flgStr, out int dmy)) { + Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}"); + return -1; + } + if (dmy == 0) { + pm.bPDFium = false; + } else if (dmy == 1) { + pm.bPDFium = true; + } else { + Console.WriteLine($"PDFiumフラグが不正です:/PDFium {flgStr}"); + return -1; + } + } else if (isOpt("/Verbose")) { + var arg = (qu.Count > 0) ? qu.Dequeue() : "True";// next word. + string[] sel = {"True","1","ON"}; + pm.bVerbose = sel.Contains(arg, eIgnoreCase); + + + } else if (isOpt("/SubExe")) { + pm.subExe = (qu.Count > 0) ? qu.Dequeue() : null;// next word. + var sp = pm.subExe.Split(':'); + if (sp.Length < 2 ) { + Console.WriteLine($"SubExe指定は\":\"区切りが必要: {pm.subExe}"); + return -1; + } + } else if (isOpt("/NoExeSepa")) { + pm.bExeSepa = false; + } else if (isOpt("/RESULT")) { + pm.resultPath = (qu.Count > 0) ? qu.Dequeue() : "";// next word. + } else if (wd.First() == '/') { + // 処理の無いオプションを明示的に無視する + Console.WriteLine($"Warning::Ignore opt:{wd}"); + } else { + // 引数をファイル名として取得する + if (pm.pdfPath == "") { + pm.pdfPath = wd; + continue; + } + if (pm.bFC && (pm.pdfPath2 == "")) { + pm.pdfPath2 = wd;// 比較時のみ取得 + continue; + } + } + } + // ↑引数解析終わり + setEcho(pm.bVerbose); + if (pm.bVerbose) + echo("Varbose Mode!"); + // + bool bSubExe = (pm.subExe != null);// SubExeで起動されている場合。 + xChangeWCFPipe.IXData cltSrv = null; + if (!bSubExe) { + /// サーバー側 + echo("++MainProcess start"); + } else { + // SubExe側の動作に差し替える + var sp = pm.subExe.Split(':'); + var pipeName = sp[0]; var pipeAddr = sp[1]; + cltSrv = xChangeWCFPipeGEN.CLT_T.makeCLT(pipeName:pipeName,pipeAddress:pipeAddr); + Ut.Ut.SetAutoSelfKillByMainProcEnd(); // 起動元が終了した場合に自身を終了させる + pm = cltSrv.GetParam(); // パラメータを書き換える + setEcho(pm.bVerbose); + echo("++SubProcess start:" + pm.subExe); + } + + if (pm.pdfPath == "") { + Console.WriteLine("pdfファイルが指定されてません"); + DispHelp(); + return -1; + } + if (!(File.Exists(pm.pdfPath) || Directory.Exists(pm.pdfPath))) { + // ファイルもしくはディレクトも見つからない場合 + Console.WriteLine($"ファイルが存在しません:{pm.pdfPath}"); + return -1; + } + var rdCond = new RenderPDF.RenderConditionParams { + Dpi = double.Parse(pm.dpi), + ImageType = pm.imageType, + BoxType = pm.boxSelect, + JpegQ = int.Parse(pm.jpegQ) + }; + bool bDir = File.GetAttributes(pm.pdfPath).HasFlag(FileAttributes.Directory); + if (bDir) { + // Directoryが指定されたのでファイルリストアップ + bDirMode = true; + pdfPathLst = System.IO.Directory.GetFiles(pm.pdfPath, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); + pdfPathLst = Array.ConvertAll(pdfPathLst, f => Path.GetFileName(f)); // 配列書き換え(ファイル名のみにする + // var enumLst = pdfPathLst.Select(f => Path.GetFileName(f)); LINQ式に置き換えることも可能(返り値は配列ではない) + //foreach ( var f in pdfPathLst) { + // Console.WriteLine($@"path1={f}"); + //} + //Console.WriteLine($@"path1.Len={pdfPathLst.Count()}"); + } + + + var resultData = new SortedDictionary(); + + if ((!pm.bFC) && bDir) { + // 単純レンダリング時 かつ ディレクト時に 対象リストに追加 + foreach (var f in pdfPathLst) { + pdfPathLstBoth.Add(f); + } + } + if (pm.bFC) {// 比較モード時 + if (pm.pdfPath2 == "") { + Console.WriteLine("比較モードで2つ目のpdfファイルが指定されてません"); + return -1; + } + if (!(File.Exists(pm.pdfPath2) || Directory.Exists(pm.pdfPath2))) { + Console.WriteLine($"比較ファイルが存在しません:{pm.pdfPath2}"); + return -1; + } + bool bDir2 = File.GetAttributes(pm.pdfPath2).HasFlag(FileAttributes.Directory); + if (bDir2) { + if (!bDir) { + Console.WriteLine($"比較対象はファイルパスでないといけません:{pm.pdfPath2}"); + return -1; + } + // 2つ目のファイルリストアップ + pdfPathLst2 = System.IO.Directory.GetFiles(pm.pdfPath2, "*.pdf"/*, System.IO.SearchOption.AllDirectories*/); + pdfPathLst2 = Array.ConvertAll(pdfPathLst2, f => Path.GetFileName(f));// 配列の書き換え + + // 共通のファイルを見つける。Lstの要素がLst2に含まれているかどうか + foreach (var f in pdfPathLst) { + if (pdfPathLst2.Contains(f)) { + pdfPathLstBoth.Add(f); + } else { + pdfPathLstNoBoth.Add(f);// LstがLst2に含まれていない + } + } + // Lst2のみファイルをNoBothに登録 + foreach (var f in pdfPathLst2) { + if (!pdfPathLstBoth.Contains(f)) { + pdfPathLstNoBoth.Add(f); + } + } + // pdfPathBoth,pdfPathNoBothが作成済み + if (pdfPathLstNoBoth.Count() != 0) { + Console.WriteLine($@"不一致のファイル={pdfPathLstNoBoth.Count()}"); + foreach (var f in pdfPathLstNoBoth) { + Console.WriteLine($@"warning [no match]={f}"); + } + } + } + } + var watch = System.Diagnostics.Stopwatch.StartNew(); // 時間の生成と計測開始を同時に行う + var otDir = pm.outuptImageDir; + if (otDir == "") { + // 出力ディレクトリの作成 // 元PDFの同一フォルダにIMGフォルダを作成する + otDir = Path.Combine(Directory.GetParent(pm.pdfPath).FullName, "IMG"); + } + + if (!Directory.Exists(otDir) /* && (!bMkHash)*/ ) { // MkHashのとき不要→必要 + Directory.CreateDirectory(otDir); + } + var otHashPath = ""; + var otHashPath2 = ""; + int ret = -1; + //Console.WriteLine($@"Use bPDFium={pm.bPDFium}"); + + if (!bDirMode) { + if (pm.bFC) { + //比較モード + // ハッシュデータの読み込み + echo($@"pre pdfPath={pm.pdfPath}"); + echo($@"pre pdfPath2={pm.pdfPath2}"); + + var hashDataTgt = HashData.load(pm.pdfPath, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath); + var hashDataRef = HashData.load(pm.pdfPath2, hashBaseName, pm.dpi, pm.boxSelect, pm.imageType, pm.jpegQ, ref otHashPath2); + //Console.WriteLine(""); + + if (pm.resultPath != "") {// 初期化 + File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); + } + + ret = RenderPDF.RenderPdfDocCompare( + pdfPath: Path.GetFullPath(pm.pdfPath), + refPdfPath: Path.GetFullPath(pm.pdfPath2), + resultDataPath: pm.resultPath, + inDir : otDir, + pm : rdCond, + inPageRange : pm.pageRange, + inHashDataTgt: hashDataTgt, + inHashDataRef: hashDataRef, + bPDFium : pm.bPDFium + ); + } else { + // Sync version. + // Console.WriteLine(""); + // シングル指定では既存のハッシュファイルを読みださない → 単独名のハッシュファイル");[ + var count = pdfPathLstBoth.Count; + + //ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 6 }; + //Parallel.For(0,pdfPathLstBoth.Count, options, index => { + //逆に遅くなる UWPコール(render)は対応していない? + if (pm.bExeSepa &&(!pm.bFC)) { // FCモードは除外します。 + var tokenSource = new CancellationTokenSource(); + ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = pm.para }; + ret = 0;//Success + var loopResult = Parallel.For(0,pdfPathLstBoth.Count, options, (index,lpState) => { + //for (var index = 0; index < pdfPathLstBoth.Count; index++) { + var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); + var tgtID = $@"{index}/{pdfPathLstBoth.Count}"; + + Console.WriteLine($@"**Remain({count})****tgt({tgtID})={tgt}"); + if (pm.bFC) { + //比較モード + var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]); + } else { // Sync version(Paralles). + var svrData = new xChangeWCFPipe.XData(); + var pmClone = pm.Clone(); + pmClone.pdfPath = tgt;// 引数のターゲットのみを書き換える + svrData.SetParam(pmClone); + string guidStr = Guid.NewGuid().ToString("N"); + string pipeName = $"PN_{guidStr}"; + string pipeAddress = $"P_{index}_{guidStr}"; + + var svrHost = xChangeWCFPipeGEN.SVR_T.makeSVR( + baseIf : typeof(xChangeWCFPipe.IXData), + obj : svrData, + pipeName : pipeName, + pipeAddress : pipeAddress + ); + if ( svrHost == null) { + Console.WriteLine("WCF cannot make"); + //tokenSource.Cancel();// 処理のキャンセル( throwされる) + lpState.Stop(); + return; + } + var myExePath = Assembly.GetExecutingAssembly().Location; + var proc = Ut.Ut.DoCmd( + cmdAndArgs: new string[] { myExePath, "/SubExe",$"{pipeName}:{pipeAddress}" } + , bEcho: false + , bSameConsole: true + ); + proc.WaitForExit(); + var retProc = proc.ExitCode; + echo($@"retProc={retProc}"); + if ( retProc < 1 ) { + //PDFページ数以外が返った時 + ret = -1; + Console.WriteLine( $@"Warning ::Cannot get PDFPage:{retProc}"); + } else { + ;// 初期値 ret=0; + } + //while (!proc.HasExited) { + // System.Threading.Thread.Sleep(2 * 1000); + //} + var tmpH = svrData.GetHashData(); + if (tmpH == null) { + echo("tmpH is null"); + } + if ( (hashDataTgt!=null)&&(tmpH!=null)) { + lock(hashDataTgt) { + //hashDataTgt.Files += tmpH.Files; + if ( tmpH.Files.Count() < 1 ) {// 中身が空なら警告 + Console.WriteLine($@"Warning tempF.Count={tmpH.Files.Count()}"); + } + var merged = hashDataTgt.Files + .Concat(tmpH.Files.Where(pair => + !hashDataTgt.Files.ContainsKey(pair.Key)) + ).ToDictionary( + pair => pair.Key, + pair => pair.Value + ); + hashDataTgt.Files = new SortedDictionary(merged);// hashを合成した。 + } + } + proc.Dispose(); + svrHost.Close(); + svrData = null; + Console.WriteLine($@"End tgt({tgtID})"); + } + //GC.Collect();// これでメモリリークが解決した Paraの中ではやめておく + count--; + }); + if ( !loopResult.IsCompleted ) { + Console.WriteLine("中断"); + pm.bMkHash = false;// Hash値保存抑制 + } + ////tokenSource.Cancel();// 処理のキャンセル + //if ( tokenSource.IsCancellationRequested ) { + // pm.bMkHash = false;// Hash値保存抑制 + //} + //}; // Non Para Block + } else { // NoExeSepa + + if (pm.resultPath != "") {// 初期化 + File.WriteAllText(Path.GetFullPath(pm.resultPath), ""); + } + for (var index = 0; index < pdfPathLstBoth.Count; index++) { + var tgt = Path.Combine(pm.pdfPath, pdfPathLstBoth[index]); + Console.WriteLine($@"tgt={index+1}/{pdfPathLstBoth.Count}:{tgt}"); + if (pm.bFC) { + //比較モード + var reff = Path.Combine(pm.pdfPath2, pdfPathLstBoth[index]); + //Console.WriteLine(""); + ret = RenderPDF.RenderPdfDocCompare( + pdfPath: Path.GetFullPath(tgt), + refPdfPath: Path.GetFullPath(reff), + resultDataPath: pm.resultPath, + inDir: otDir, + pm: rdCond, + inPageRange: pm.pageRange, + inHashDataTgt: hashDataTgt, + inHashDataRef: hashDataRef, + bPDFium: pm.bPDFium + ); + } else { // Sync version(Paralles). + ret = RenderPDF.RenderPdfDoc( + pdfPath: Path.GetFullPath(tgt), + inDir: otDir, + pm: rdCond, + inPageRange: pm.pageRange, + bSaveImage: pm.bMkHash ? false : true,// ハッシュ値生成ではイメージ保存しない。 + bHash: pm.bHash,// bHash,dumy + inHashData: pm.bMkHash ? hashDataTgt : null, //ハッシュコマンドモードのみDataを渡す + bPDFium: pm.bPDFium + ); + } + count--; + GC.Collect();// これでメモリリークが解決した + }; + } + if (pm.bMkHash) { + hashDataTgt.save(otHashPath);//ハッシュモードで保存する(Dutyチェックもいるでしょう + } + } + watch.Stop(); + Console.WriteLine($"result={ret},time={ watch.ElapsedMilliseconds / 1000.0 }[sec]"); +#if COMMNET + Assembly assm = Assembly.GetExecutingAssembly(); + Console.WriteLine(assm.FullName); + // 参照しているすべてのアセンブリを取得し、表示する + foreach (AssemblyName refassm in assm.GetReferencedAssemblies()) { + Console.WriteLine($@" {refassm.FullName},Ver({refassm.Version}),VerComp({refassm.VersionCompatibility})" ); + } + string clrVersion = System.Environment.Version.ToString(); + Console.WriteLine($@"clrVer={clrVersion}"); +#endif + return ret;//success. + } + + // リソースからの取得 + public static class ResData { + public static Stream GetBaseHashPDF(string resName = "CSRender.RES.BaseHash.pdf") { + System.Reflection.Assembly asm = System.Reflection.Assembly.GetExecutingAssembly(); + var sel = from x in asm.GetManifestResourceNames() select resName; + if (sel.Count() == 1) { + return asm.GetManifestResourceStream(sel.First()); + } + return null; + } + } + + } +} +/// +/// WCF通信モジュールのラッパクラス。 +/// 2つのExe間をWCFパイプモードで通信を行う +/// +namespace xChangeWCFPipe +{ + /// シリアライズ可能にすること + /// + [ServiceContract] + public interface IXData { + [OperationContract(IsOneWay = true)] + void Execute(); + + [OperationContract] + bool SetMessage(string msg); + + [OperationContract] + bool SetProgress(int per);//0-100. + + [OperationContract] + HashData GetHashData(); + + [OperationContract] + void SetHashData(HashData d); + + [OperationContract] + CSRenderMain.ParamData GetParam(); + //CSRenderMain.Program.ParamData GetParam(); + } + // ServiceContract https://tnakamura.hatenablog.com/entry/20080606/1220023868 + // https://devlights.hatenablog.com/entry/20111023/p2 + + /// + /// 通信用複雑データ。スカラー型(int,double,,,.)以外は[DataCOntract]属性を + /// つけて、通知用Interfaceの引数や、返値で利用する + /// + [DataContract] + public class DataContainer + { + [DataMember] + public CSRenderMain.ParamData pm; + [DataMember] + public HashData hdata; + } +} + +namespace xChangeWCFPipe +{ + /// + /// Main(サーバー)側 データ定義 + /// + [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)] + public class XData : IXData + { + private readonly Object LockObj = new object();// 排他制御用 + public delegate void callFunc(); + public callFunc callDelegate; + public XData() { + _dc.pm = new CSRenderMain.ParamData(); + } + public void Execute() { + lock (LockObj) { + callDelegate(); // 実装はデリゲートします + } + } + public bool SetMessage(string msg) { + lock (LockObj) { + Console.WriteLine($"SVR:called setMessage({msg}) from CLT"); + _msg = msg; + } + return true; + } + public bool SetProgress(int per) {//0-100. + lock (LockObj) { + Console.WriteLine($"SVR:called setProgress({per}) from CLT"); + _progress = per; + } + return true; + } + + public void SetHashData(HashData h) { + lock (LockObj) { + _dc.hdata = h; + } + } + public HashData GetHashData() { + lock (LockObj) { + return _dc.hdata; + } + } + public CSRenderMain.ParamData GetParam() { + lock (LockObj) { + return _dc.pm; + } + } + // クライアントに公開しなくてもよいI/F + public void SetParam(CSRenderMain.ParamData pm) { + lock (LockObj) { + _dc.pm = pm; + } + } + // private data + private string _msg = "init msg"; + private int _progress = 0;// 0-100 + private DataContainer _dc = new DataContainer(); + } +} + +namespace xChangeWCFPipeGEN +{ + /// + /// Main(サーバー)側 データ定義 + /// + //[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)] + //public class XData : IXData + //{ + // ... + //} + /// + /// Main(サーバー)側クラス + /// + /// + public class SVR_T + { + // - static - + + /// + /// + /// + /// 公開するインターフェースクラス + /// 実装インスタンス(baseIfを含むこと) + /// + /// + /// + static public SVR_T makeSVR(Type baseIf, object obj/*XData*/ , string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") { + + Type oType = obj.GetType(); + bool hasBaseIf = oType.GetInterfaces().Any(t => (t == baseIf) ); + if ( !hasBaseIf) { + Console.WriteLine($@"Error:'{oType.FullName}' have not the interface '{baseIf.FullName}'");//A have not the interface B. + return null; + } + return (new SVR_T(baseIf, obj, pipeName, pipeAddress)); + } + // - public I/F - + public void Close() { + echo($"Close Service:{_service != null},{_serviceHost != null}"); + _service = null; + _serviceHost.Close(); + } + // - private - + private object _service = null;// 要らないかも + private Type _baseIf = null; + private ServiceHost _serviceHost = null; + private SVR_T() { } + private SVR_T(Type baseIf, object oInstance, string pipeName, string pipeAddress) { + const string pipeBase = "net.pipe://localhost"; + var uri = pipeBase + "/" + pipeName; + _baseIf = baseIf; + _service = oInstance; + // デリゲート登録 + //callDelegate = () => { Console.WriteLine("svr:Delegate func called"); } +#if false + { + // インターフェースを求める + // AddServiceEndpoint()にわたすIXDataはXData.Interfaces()から求められるが + // インタフェースは一つとは限らないのでやめておく。 + Type interfType = null; + Type type = _service.GetType(); + Type[] interfaces = type.GetInterfaces(); + Console.WriteLine($@"GetInterfaces***********************************"); + foreach (Type t in interfaces) { + Console.WriteLine(t.ToString()); + interfType = t; + }; + Console.WriteLine($@"End of GetInterfaces***********************************"); + } +#endif + _serviceHost = new ServiceHost(_service, new Uri(uri)); + try { + _serviceHost.AddServiceEndpoint(_baseIf/*typeof(Ti)*/, new NetNamedPipeBinding(), pipeAddress); + _serviceHost.Open(); + } catch (AddressAlreadyInUseException) { + Console.WriteLine("既にサービスは起動しています。"); + } catch (Exception e) { + Console.WriteLine(e.ToString()); + } + } + } + /// + /// Sub(クライアント)側クラス + /// + public class CLT_T + { + /// + /// Sub側の初期化処理。Main側とpipeName,pipeAddressを同じにすること + /// + /// + /// + /// + static public Ti makeCLT(string pipeName = "PDFormstudioESorGS", string pipeAddress = "SubModuleAddress") { + const string pipeBase = "net.pipe://localhost"; + var address = pipeBase + "/" + pipeName + "/" + pipeAddress; + //try { + var factory = (new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress(address))); + Ti toMain = factory.CreateChannel(); + return toMain; + //} catch (Exception e) { + // Console.WriteLine("makeCLT failed:" + e.ToString()); + // return null; + //} + } + } +} + +namespace Ut +{ + public class Ut + { + // Util + // -------------------------------------------------------------- + /// + /// 親プロセスが終了したら、自身を終了させる + /// + /// + public static void SetAutoSelfKillByMainProcEnd(bool bEnable = true) { + if (_SingletonProc != null) + return;//一度しか呼び出せない + if (bEnable == false) + return; + const string fname = "SetAutoSelfKillByMainProcEnd"; + // 自動的に親のプロセスがいなくなったら自動的にキルモードを設定する + // メインプロセスの終了チェック + // 親プロセスIDでProcessハンドルを取得、そのExitedイベントに自身の終了関数を設定(Environment.Exit()) + int paProcID = GetParentProcessId(); + var paProc = Process.GetProcessById(paProcID); + paProc.EnableRaisingEvents = true; + //Console.WriteLine($@"{fname}:ParrentProcessName ={paProc.ProcessName}({paProcID})"); + paProc.Exited += new EventHandler( + (object s, EventArgs a) => { + Console.WriteLine($@"{fname}:Exited Event!!!!"); + var ss = s as Process; + Console.WriteLine($@"Sure ProcName is {ss.ProcessName}({ss.Id}) {ss.StartInfo.Arguments}"); ; + System.Threading.Thread.Sleep(10 * 1000); + ss.Close(); + ss.Dispose(); + Console.WriteLine($@"{fname}:Exited Event!!!!(afer10sec)"); + //Environment.Exit(-1); + }); + // + _SingletonProc = paProc; + } + private static System.Diagnostics.Process _SingletonProc = null; + /// + /// コマンドライン引数複数個をエンコードして、スペースで結合 + /// + /// string[] コマンドライン引数 + /// コマンドライン文字列(Escaped) + public static string makeCmdLine(IEnumerable args) { + if (args == null) + throw new ArgumentNullException("args"); + string EscapeCmdLineArg(string v) { + if (string.IsNullOrEmpty(v)) return ""; + var containsSpace = v.IndexOfAny(new[] { ' ', '\t' }) != -1; + v = ReCommandLineEscapePattern.Replace(v, @"$1\$&");//「\…\"」をエスケープ.「"」直前の「\」の数を 2倍+1 + if (containsSpace) { + v = "\"" + ReLastBackSlashPattern.Replace(v, "$1$1") + "\""; + } + return v; + } + return string.Join(" ", args.Select(v => EscapeCmdLineArg(v))); + } + private static Regex ReCommandLineEscapePattern = new Regex("(\\\\*)\""); + private static Regex ReLastBackSlashPattern = new Regex(@"(\\+)$"); + + /// + /// 親のプロセスIDを取得する + /// + /// 親ProcessID + static int GetParentProcessId() { + var myProcId = GetCurrentProcessId(); + var query = string.Format($@"SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {myProcId}"); + //クエリから結果を取得 + using (var search = new System.Management.ManagementObjectSearcher(@"root\CIMV2", query)) + using (var results = search.Get().GetEnumerator()) { + if (!results.MoveNext()) + throw new ApplicationException("Couldn't Get ParrentProcessId."); + var queryResult = results.Current; + //親プロセスのPIDを取得 + uint pa = (uint)(queryResult["ParentProcessId"]); + return (int)(pa); + } + } + /// + /// 自身のプロセスIDを取得する + /// + /// ProcessID + static int GetCurrentProcessId() { return Process.GetCurrentProcess().Id; } + /// + /// 外部プログラムを起動する + /// + /// 引数文字列の配列 + /// true:コンソールを呼び出しと共有 false:別Console Windows + /// 標準出力(未実装) + /// コマンドが終了するまで待つ(false:未実装,常に待つ) + /// タイムアウト時間ms(未実装) + /// + public static Process DoCmd(IEnumerable cmdAndArgs, bool bSameConsole = true, bool bEcho = true, bool bWait = false, int nWaitLimitMSec = -1) { + var cmdName = cmdAndArgs.First();//[0]; + var argStr = makeCmdLine(cmdAndArgs.Skip(1));// 先頭を除外してコピー new ArraySegment(cmdAndArgs.ToArray(), 1, cmdAndArgs.Count()-1) + //Console.WriteLine($@"DoCmd:{cmdName}:args[{argStr}]"); + //using (var p = new Process()) { + var p = new Process(); + { + var info = p.StartInfo; + p.StartInfo.FileName = cmdName; + p.StartInfo.Arguments = argStr; + if (bSameConsole) { + //Console.WriteLine("Same windows mode"); + p.StartInfo.UseShellExecute = false; // Shell経由で実行しない(必須) true: 別ウインドウ。false:コンソールを共有 + p.StartInfo.CreateNoWindow = false; // ウィンドウを作成しない + } else { + //Console.WriteLine("New windows mode"); + p.StartInfo.UseShellExecute = true; + p.StartInfo.CreateNoWindow = true; + } + p.Start();// 実行 + if (bWait == false) { + return p; + } + if (nWaitLimitMSec == -1) { + p.WaitForExit();// タイムアウト無し + } else { + bool bExit = p.WaitForExit(nWaitLimitMSec); + if (!bExit) {// 処理の終了を待つ + Console.WriteLine("処理が終了しないので強制終了"); + p.Kill(); // 強制終了する + return p; + } + } + return p; + } + } + } +} \ No newline at end of file diff --git a/bin/Release/CSRender.exe b/bin/Release/CSRender.exe index 36dd24cbf49a0e0e8eebf3d767277569398ac7a1..620d841505545f45132fb8a918713f5b8b78bb3b 100644 Binary files a/bin/Release/CSRender.exe and b/bin/Release/CSRender.exe differ diff --git a/bin/Release/CSRender.exe.config b/bin/Release/CSRender.exe.config index 5ee0d86ba06651f5c62a3cbb75951202bc64a1bb..45437954e08a2d113d4b04d9d9f51e30f4676ab9 100644 --- a/bin/Release/CSRender.exe.config +++ b/bin/Release/CSRender.exe.config @@ -1,326 +1,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + +