Graphics.DrawImage会产生一些“噪音”,即图像边缘附近的keyline

时间:2016-09-13 10:56:42

标签: c# .net image-processing gdi

我有一项服务,可以根据需要调整大小/裁剪图片,方法是加载完整尺寸的文件并裁剪,调整尺寸并将其调整到所要求的尺寸和要求的质量。

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

然而,我所面临的问题是,对于某些尺寸,输出图像包含一些沿着边缘呈微弱keyline形式的“噪声”。 您可以在此处查看图像边缘应如何的示例 enter image description here

以及实际上如何返回[images here]

它仅为某些维度添加了一个keyline,错误是一致的,并且与通过的质量(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;
    } 
}

}

修改

上面上传的图片是实际图片的特写截图,我在这里添加了另外几个实际源图像和输出图像的例子。

来源:

Source file

403x305的输出(左边缘的keyline):

Output at 403x305

1 个答案:

答案 0 :(得分:2)

问题已经从源图像开始了。如果你在Photoshop(或其他编辑器)中仔细查看它,你会注意到这些工件在源图像中但更加微弱。缩放图像时,源像素不仅会复制到新位置,还会进行插值。这意味着在某些情况下,颜色可能会发生巨大变化。我稍微改变了颜色水平以使其清晰。

Color artifacts

这是放大图像顶部的部分:

Color artifacts zoomed in

现在的问题是,你显然希望尽量减少不良源图像的颜色变化的影响,从而使压缩工件更加突出。您应该尝试使用InterpolationMode以及其他一些质量设置。

另一个想法是尝试使用ImageMagick。 Here's a .NET wrapper围绕它。