2016-09-13 29 views
0

我有加載一個全尺寸的文件和種植它,它調整到所要求的尺寸和質量要求按需調整大小/作物圖像的服務。Graphics.DrawImage產生一些「噪音」,一個關鍵線附近的圖像邊緣

public static byte[] Resize(Image sourceImage, int? targetWidth, int? targetHeight, int quality); 

但是我正在面臨的問題是,對於特定尺寸的輸出圖像包含沿邊緣一些微弱關鍵線的形式的一些「噪音」。 你可以在這裏如何看一個例子圖像邊緣應 enter image description here

這裏實際上是如何返回[images here]

它增加了一個關鍵行只對某些維度對,誤差是一致的,並且獨立於質量(1-100)通過。

以下是調整大小代碼,這裏也簡單的操場https://github.com/gromag/ImageResizeTest

有什麼建議?

using System; 
using System.Collections.Generic; 
using System.Drawing.Drawing2D; 
using System.Drawing; 
using System.Drawing.Imaging; 
using System.IO; 

namespace grom.lib.graphics 
{ 
    public class ImageResizer 
    { 
    /// <summary> 
    /// A quick lookup for getting image encoders 
    /// </summary> 
    private static Dictionary<string, ImageCodecInfo> encoders = null; 

    /// <summary> 
    /// A quick lookup for getting image encoders 
    /// </summary> 
    public static Dictionary<string, ImageCodecInfo> Encoders 
    { 
     //get accessor that creates the dictionary on demand 
     get 
     { 
      //if the quick lookup isn't initialised, initialise it 
      if (encoders == null) 
      { 
       encoders = new Dictionary<string, ImageCodecInfo>(); 
      } 

      //if there are no codecs, try loading them 
      if (encoders.Count == 0) 
      { 
       //get all the codecs 
       foreach (ImageCodecInfo codec in ImageCodecInfo.GetImageEncoders()) 
       { 
        //add each codec to the quick lookup 
        encoders.Add(codec.MimeType.ToLower(), codec); 
       } 
      } 

      //return the lookup 
      return encoders; 
     } 
    } 

    /// <summary> 
    /// ************************************************************* 
    /// Resizes or crops an images to the requested width and height. 
    /// ************************************************************* 
    /// If any dimension is not passed, the function will calculate the missed dimensions 
    /// If both dimensions are not passed, the function will return a *copy* of the original image 
    /// If any of the requested dimensions exceeds the original one's, this function will return null 
    /// If dimensions' ratio does not match the original ratio, clipping will occur. 
    /// </summary> 
    /// <param name="sourceImage"></param> 
    /// <param name="targetWidth"></param> 
    /// <param name="targetHeight"></param> 
    /// <returns>A bitmap, you will **need** to dispose of such image</returns> 
    public static byte[] Resize(Image sourceImage, int? targetWidth, int? targetHeight, int quality) 
    { 
     //w, h source width and height 
     int w = sourceImage.Size.Width; 
     int h = sourceImage.Size.Height; 
     //wt, ht requested width and height 
     int wt = 1; 
     int ht = 1; 

     //The new image would exceed the max boundary of the source image 
     if (targetWidth > w || targetHeight > h) return null; 

     if (targetWidth == null && targetHeight == null) 
     { 
      wt = w; 
      ht = h; 
     } 

     var sourceRatio = (double)w/(double)h; 

     //if no target width expressed then 
     //if w = sourceRatio * h 
     //then 
     //wt = sourceRatio * ht 
     wt = (int)(targetWidth ?? sourceRatio * ht); 

     //if no target height expressed then 
     //if h = w/sourceRatio 
     //then 
     //ht = wt/sourceRatio 
     ht = (int)(targetHeight ?? wt/sourceRatio); 

     var targetRatio = (double)wt/(double)ht; 

     #region ***Clipping explaination in visual terms 
     //Clip applied to original image before scaling 
     // If proportions are as follow: 
     // 
     //   target 2:1    source 1:1 
     //  ___________    _______________ 
     //  |   |   |    | 
     //  |___________|   |    | 
     //        |    | 
     //        |    | 
     //        |_______________| 
     // Then we will clip as follows: 
     // 
     //    clip to source 
     //    _______________ 
     //    |_ _ _ _ _ _ _ _| 
     //    |    | 
     //    |    | 
     //    |_ _ _ _ _ _ _ _| 
     //    |_______________| 

     // or vertical clip instead if proportions are as follow: 
     // 
     //   target 1:2    source 1:1 
     //  ___      _______________ 
     //  | |     |    | 
     //  | |     |    | 
     //  |___|     |    | 
     //        |    | 
     //        |_______________| 
     // Then we will clip as follows: 
     // 
     //    clip to source 
     //    _______________ 
     //    | !  ! | 
     //    | !  ! | 
     //    | !  ! | 
     //    | !  ! | 
     //    |____!_____!____| 
     #endregion 
     Rectangle clip; 

     if (targetRatio >= sourceRatio) 
     { 
      //The image requested is more elungated than the original one 
      //therefore we clip the height 
      //targetRatio = wt/ht 
      //ht = wt/targetRatio 
      //hClip = w/targetRatio 
      var hClip = (int)Math.Ceiling((double) w/(double)targetRatio); 

      //Rectangle pars are: x, y, width, height 
      clip = new Rectangle(0, (h - hClip)/2, w, hClip); 
     } 
     else 
     { 
      //The image requested is more stretched in height than the original one 
      //therefore we clip the width 
      //targetRatio = wt/ht 
      //wt = targetRatio * ht 
      //hClip = targetRatio * h 
      var wClip = (int)Math.Ceiling((double)h * (double)targetRatio); 

      //Rectangle pars are: x, y, width, height 
      clip = new Rectangle((w - wClip)/2, 0, wClip, h); 
     } 


     var targetImage = new Bitmap(wt, ht); 

     using (var g = Graphics.FromImage(targetImage)) 
     { 
      g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality; 
      g.SmoothingMode = SmoothingMode.HighQuality; 
      g.InterpolationMode = InterpolationMode.HighQualityBicubic; 


      var targetRectangle = new Rectangle(0, 0, wt, ht); 

      g.DrawImage(sourceImage, targetRectangle, clip, GraphicsUnit.Pixel); 
     } 

     var bytes = ImageToByteArray(targetImage, ImageFormat.Jpeg, quality); 

     targetImage.Dispose(); 

     return bytes; 
    } 
    /// <summary> 
    /// Given a System.Drawing.Image it will return the corresponding byte array given 
    /// a format. 
    /// </summary> 
    /// <param name="imageIn"></param> 
    /// <param name="format"></param> 
    /// <returns></returns> 
    public static byte[] ImageToByteArray(System.Drawing.Image imageIn, ImageFormat format, int quality) 
    { 
     //create an encoder parameter for the image quality 
     EncoderParameter qualityParam = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, quality); 
     //get the jpeg codec 
     ImageCodecInfo jpegCodec = GetEncoderInfo("image/jpeg"); 

     //create a collection of all parameters that we will pass to the encoder 
     EncoderParameters encoderParams = new EncoderParameters(1); 
     //set the quality parameter for the codec 
     encoderParams.Param[0] = qualityParam; 
     //save the image using the codec and the parameters 

     var ms = new MemoryStream(); 
     imageIn.Save(ms, jpegCodec, encoderParams); 
     return ms.ToArray(); 
    } 
    /// <summary> 
    /// Converts a byte array to System.Drawing.Image. 
    /// IMPORTANT: You must dispose of the returned image. 
    /// </summary> 
    /// <param name="byteArrayIn"></param> 
    /// <returns>Returns an image of type System.Drawing.Image, you will have to take care of disposing it</returns> 
    public static Image ByteArrayToImage(byte[] byteArrayIn) 
    { 
     var ms = new MemoryStream(byteArrayIn); 
     var returnImage = Image.FromStream(ms); 
     return returnImage; 
    } 

    /// <summary> 
    /// Returns the image codec with the given mime type 
    /// </summary> 
    public static ImageCodecInfo GetEncoderInfo(string mimeType) 
    { 
     //do a case insensitive search for the mime type 
     string lookupKey = mimeType.ToLower(); 

     //the codec to return, default to null 
     ImageCodecInfo foundCodec = null; 

     //if we have the encoder, get it to return 
     if (Encoders.ContainsKey(lookupKey)) 
     { 
      //pull the codec from the lookup 
      foundCodec = Encoders[lookupKey]; 
     } 

     return foundCodec; 
    } 
} 

}

編輯:上面上傳了實際圖像的特寫截圖

圖片,我在這裏將另一對夫婦的實際來源和輸出圖像的例子。

來源:在403x305(關鍵線旁邊的左邊緣)

Source file

輸出:

Output at 403x305

+2

這是一般由Graphics.Interpolation屬性設置產生的僞影。更高質量的內插器通過查看多個像素來對圖像進行重新採樣。邊緣的問題在於它耗盡了要使用的像素。它在圖像的左側顯得更加明顯,但在這種情況下您不覺得它令人討厭,因爲它非常黑暗。它*額外*發音,因爲你的源圖像已經有這個神器。 InterpolationMode.NearestNeighbor不會生成該工件,但您可能不喜歡圖像其餘部分的結果。 –

回答

2

的問題已經從源圖像開始。如果您在Photoshop(或其他編輯器)中仔細觀察它,您會注意到這些工件位於源圖像中,但非常暗淡。縮放圖像時,源像素不會複製到其新位置,而是插值。這意味着在某些情況下,顏色可能會發生相當劇烈的變化。我稍微改變了顏色級別以清楚表明。

Color artifacts

這裏是在圖像的頂部的變焦:

Color artifacts zoomed in


現在的事情是,你顯然希望儘量減少不良源圖像得到的效果他們的顏色發生了變化,從而使壓縮僞影更加突出。你應該嘗試玩InterpolationMode,也許還有一些其他的質量設置。

另一個想法是使用ImageMagick嘗試。 Here's a .NET wrapper圍繞它。

+0

我認爲源圖像上的工件是因爲我確實上傳了Photoshop頁面的屏幕截圖,而不是實際圖像,我猜剪切工具添加了一些噪音,我上傳了另外幾幅圖像,這次是原件。我會看看InterpolationMode。 –

+0

@GiuseppeRomagnuolo這也可以解釋它。但是,正如Hans Passant所說,插值模式可能會產生問題,並會導致圖像發生這種變化。 – hankide

+0

@GiuseppeRomagnuolo隨着你發佈的新圖像例子,很明顯插值是問題。 – hankide