A Simple C# Image Process API with Fluent Interface
哈囉大家,話說這一陣子寫程式數量實在太多,寫得很膩,所以跑來寫篇技術短文,稍微轉移一下注意力。(疑,會有幫助嗎?)
這次分享一下自製的圖形處理庫,實作成 Fluent API,本來自己的程式庫用的不是 Fluent 介面,而是一般的 static method(s),但是過去一段時間被一些圖片處理循序呼叫組合搞得很煩,想說既然這樣一不做二不休來個改版,而且這次還包含了解決自己長久以來的疑惑「控制 .NET 圖形處理後存檔 JPEG 的品質問題」,這次重寫也證明了我原來的想法是錯的,並非無解。
- 做成 Fluent API
流利的 API? 哈哈,這名字聽起來就蠻爽的,做起來才會開心啊,如今我們早已習慣這種形式的 API,舉凡前端 jQuery、後端 LINQ、諸多 ORM framework 中都很容易見到這樣的設計,Dino 自己還蠻喜歡的,還是秉持著先構想用戶端使用介面的精神(用戶當然還是我自己 XD),習慣上還是先假想一下,API 具體來說我想要做成什麼樣子
ImageProcess // 開啟圖片處理 .Handle( Stream | Bitmap | FilePath ) // 圖片處理 .Crop( X, Y, Width, Height ) .Resize( Percentage, Side.Width | Side.Height ) .Resize( Length, Side.Width | Side.Height ) .Overlay( Bitmap | ImageProcess | FilePath, Placer ) .Opacity( 0 ~ 1 ) // 品質控制 .Format( ImageFormat.Jpeg, ... ) .Quality( 0 ~ 100 ) // 記憶與還原點 .Store( Name ) .Fetch( Name ) // 存檔 .Save( Stream | FilePath )
這是將第一階段我想做好的 API 全部列表,實際上還陸續增加了些。
身為作者,當然是覺得介面應該還不會太囉唆啦,有建議的話在麻煩提供給我進行改善喔 XD
下面這個例子,是一般情況下可能會用到的呼叫方式,這樣的程式碼就可以完成一次縮圖的處理。
ImageProcess .Handle( Stream | Bitmap | FilePath ) .Resize( Length, Side.Width | Side.Height ) .Save( Stream | FilePath )
- 控制 .NET 圖形處理後存檔 JPEG 的品質問題
過去不知道,誤認壓縮品質為這是無法控制的 .NET 程式庫結果,事實上,在網路上也大多是看到
InterpolationMode.HighQualityBicubic
設定的相關文章,更加惱怒的是,網路上充斥著抄襲的部落格文章,實在無恥!啊,講遠了,快回來啊,這點改善也可以肉眼就發現,卻是相當有限的。如果你的縮圖尺寸事實上不大,那麼差不多可以忽略品質問題,因為圖形小,視覺的問題不會那麼明顯,可是如果是大一點的圖,像是 Width 大於 400px,那麼 JPEG 的壓縮存檔可能會破壞掉圖像的品質,肉眼就看得很清楚,我過去就是不知道該怎麼調整 JPEG 存檔壓縮率,所以誤以為無法調整。
所幸耐著性子找到 參考文章 終於知道調整的地方在哪
(在這之前,我實在無法在 MSDN 上找到類似的例子,可能找文章的方向錯誤吧)
public static void SaveAsQualityFactor(Bitmap bitmap, Stream stream, ImageFormat format = null, long? quality = null) { if (format == null) { format = ImageFormat.Jpeg; } if (quality.HasValue) { var codec = ImageCodecInfo .GetImageEncoders() .FirstOrDefault(x => x.FormatDescription.Equals(format.ToString(), StringComparison.CurrentCultureIgnoreCase)); if (codec != null) { //Set the parameters for defining the quality of the thumbnail... here it is set to 100% var encoderParameters = new EncoderParameters(1); encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality.Value); bitmap.Save(stream, codec, encoderParameters); } else { bitmap.Save(stream, format); } } else { bitmap.Save(stream, format); } }
那些關鍵程式行顯示出,藉由對於 codec 的細部參數控制,你可以設定出無損格式的 JPEG 存檔,當然檔案一定會隨之變大,所以我實務上僅在關鍵圖片上以幾乎無損方式存檔(理論上吧 XD),其他小縮圖則不考慮這樣處理
那麼就動手來做吧,咻一下程式就可以寫完了,程式碼有點長,直接由文末的附件下載就好了
本來想說這篇文簡單寫一下就好,沒想到還是多花了些時間,感覺有需要時候的再來探討程式碼,現在就先跳轉到用 LINQPad 來測試 ImageProcess 類別:
var stamp = ImageProcess // 讀檔 .Handle( "浮水印圖檔.png" ) // Debug 資訊 .TakeBitmap( x => x.Dump() ) // 縮小比例 12% .Resize( 0.12, Side.Width ) // 透明度 80% .Opacity( 0.8 ) // Debug 資訊 .TakeBitmap( x => x.Dump() ); ImageProcess // 讀檔 .Handle( "原圖.jpg" ) // Debug 資訊 .Size( x => x.Dump() ) // 圖寬縮為 461px, 圖高依計算比例縮小 .Resize( 461, Side.Width ) // Debug 資訊 .Size( x => x.Dump() ) // 灰階化 .GrayScale() // 記憶起來 .Store( "A" ) // 四角落浮水印 .Overlay( stamp, (s1, s2) => new Point(10, 10) ) .Overlay( stamp, (s1, s2) => new Point(s1.Width - s2.Width - 10, s1.Height - s2.Height - 10) ) .Overlay( stamp, (s1, s2) => new Point(s1.Width - s2.Width - 10, 10) ) .Overlay( stamp, (s1, s2) => new Point(10, s1.Height - s2.Height - 10) ) // 100% 無損格式 .Quality( 100 ) // JPEG .Format( ImageFormat.Jpeg ) // 存檔 .Save( "合成輸出.jpg" ) // PNG .Format( ImageFormat.Png ) // 存檔 .Save( "合成輸出.png" ) // Debug 資訊 .TakeBitmap( x => x.Dump() ) // 提取記憶點 .Fetch( "A" ) // Debug 資訊 .TakeBitmap( x => x.Dump() );[Gist] ImageProcess.cs
沒有留言:
張貼留言