using System; using System.IO; using System.Collections.Generic; // List<> using System.Linq; // for Enumration using System.Text; using System.Threading; using System.Threading.Tasks; 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 // PDFiumの追加 using PdfiumViewer; // 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. using static CSRender.RenderPDF; 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; [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, 88/*LOGPIXELSX*/); 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 } /* * 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 = ""; foreach (object a in args) { s += a.ToString(); } Console.WriteLine(s); } /// /// 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, int nPageThreadNum = 4 ) { if (pm == null) { pm = new RenderConditionParams(); } var otDir = inDir; var otBaseName = Path.GetFileName(pdfPath);//*.pdf var otExtention = pm.ImageType.ToLower(); 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); } // 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); if (inHashData != null) { 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); } } }); echo("End Para"); docG.Dispose(); } return pageCount;// ページ数を返却します } /// /// pdfのページを画像に出力する /// /// PdfDocument /// ページ番号 /// 出力ファイル名 /// 正常:0 public static int RenderPage( PdfiumViewer.PdfDocument docG, int index, string otPath, ref string otHashCode, // Optional: RenderConditionParams pm = null, bool bSaveImage = true,// ファイル保存有無。ハッシュ値計算のみのときにfalseにする bool bHash = false ){ var bDump = Check.bDump; var bThDump = Check.bThDump; if ( pm == null ) pm = new RenderConditionParams(); 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) { 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 } // 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, int nPageThreadNum = 4 // 比較結果を返す ページ番号とメッセージ ) { var bDump = Check.bDump; var bThDump = Check.bThDump; if(bDump) echo("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 == "") { Console.WriteLine("Error:No output dir"); return -1;// error } if (!Directory.Exists(otDir)) Directory.CreateDirectory(otDir); // PDFファイルを読み込む by PDFium. var docG = PdfiumViewer.PdfDocument.Load(pdfPath); var docGRef = PdfiumViewer.PdfDocument.Load(refPdfPath); var pageCount = docG.PageCount; uint[] rNo = makePageRange(inPageRange, (uint)pageCount); int nRetAll = 0;// トータルの不一致ページ数 // ハッシュ情報の確認 /* - 双方のハッシュ値が存在するか確認 - 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();// 比較結果を返す ページ番号とメッセージ /////並行処理するスレッド数を指定(2-4ぐらいが穏便な数値) ParallelOptions options = new ParallelOptions { MaxDegreeOfParallelism = 4 }; Parallel.ForEach(rNo, options, i => { MemoryStream tgtStrm = null, refStrm = null; string tgtHash = "", refHash = ""; // ターゲット レンダラ定義 MemoryStream RendPageTGT() { var strm = RenderPageStream(docG:docG,index:(int)i-1, pm:pm); return strm; } // リファレンス レンダラ定義 MemoryStream RendPageREF() { var strm = RenderPageStream(docG:docGRef, index:(int)i-1, pm:pm); return strm; } // ターゲット処理 if (tgtHf == null) { tgtStrm = RendPageTGT();// Render tgtHash = GetHashValue(tgtStrm); } else { lock (inHashDataTgt) { tgtHash = tgtHf.GetHashValue((int)(i - 1)); } } // リファレンス処理 if (refHf == null) { refStrm = RendPageREF();// Render refHash = GetHashValue(refStrm); } else { lock (inHashDataRef) { refHash = refHf.GetHashValue((int)(i - 1)); } } // compare stream; // int nRet = 1;//異なる(diff). var dateStr = DateTime.Now.ToString(); //Console.WriteLine($@"tgt={i}:{tgtHash}"); //Console.WriteLine($@"ref={i}:{refHash}"); if (tgtHash != refHash) { // 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]:{Path.GetFileName(pdfPath)}.{i}"); } 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(); } // 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 { // match //nRet = 0; lock (fcResultMsg) { 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(); }); 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. 不一致数を返す } /// /// 比較モード /// /// ドキュメントハンドル /// ページ番号(0-) /// 正常:0 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(); 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; } /// /// ページ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(MemoryStream strm) { var alg = new SHA256CryptoServiceProvider(); strm.Seek(0, SeekOrigin.Begin); var bin = alg.ComputeHash(strm); 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; } // 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が発生して取得できない /// /// 対象の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; } } } }