如何在.NET中一步完成图像裁剪和调整大小

10
我有一张图像文件,想使用System.Drawing类同时进行裁剪和调整大小。
我正在尝试基于这篇文章的思路:http://www.schnieds.com/2011/07/image-upload-crop-and-resize-with.html 我能够分别进行裁剪和调整大小,但是当我尝试将两个过程结合起来时,我得到了一些奇怪的输出。
以下是我的尝试内容:
using (System.Drawing.Bitmap _bitmap = new System.Drawing.Bitmap(w, h))
{
    _bitmap.SetResolution(img.HorizontalResolution, img.VerticalResolution);
    using (Graphics _graphic = Graphics.FromImage(_bitmap))
    {
        _graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        _graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        _graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        _graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

        //Code used to crop
        _graphic.DrawImage(img, 0, 0, w, h);
        _graphic.DrawImage(img, new Rectangle(0, 0, w, h), x, y, w, h, GraphicsUnit.Pixel);

        //Code I used to resize
        _graphic.DrawImage(img, 0, 0, img.Width, img.Height);
        _graphic.DrawImage(img, new Rectangle(0, 0, W_FixedSize, H_FixedSize), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);



       //continued...
    }
}
在以上的代码中...有两个已被注释的部分...一个是用于裁剪,另一个是用于调整大小。
对于裁剪,我传递了正确的坐标和要裁剪的图像的宽度/高度到crop(x, y, w, h)函数中。
我希望根据我的参数进行裁剪,并基于W_FixedSize和H_FixedSize参数绘制图像。

你可以查看 http://imager.codeplex.com 的源代码(大约100行),或将其用作dll。 - Omu
5个回答

9

我正在使用我自己写的这个类:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace Studio.Utilities
{
    public class ImageResizer
    {
        public void ResizeImage(string origFileLocation, string newFileLocation, string origFileName, string newFileName, int newWidth, int maxHeight, bool resizeIfWider)
        {
            System.Drawing.Image FullSizeImage = System.Drawing.Image.FromFile(origFileLocation + origFileName);
            // Ensure the generated thumbnail is not being used by rotating it 360 degrees
            FullSizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);
            FullSizeImage.RotateFlip(System.Drawing.RotateFlipType.Rotate180FlipNone);

            if (resizeIfWider)
            {
                if (FullSizeImage.Width <= newWidth)
                {
                    //newWidth = FullSizeImage.Width;
                }
            }

            int newHeight = FullSizeImage.Height * newWidth / FullSizeImage.Width;
            if (newHeight > maxHeight) // Height resize if necessary
            {
                //newWidth = FullSizeImage.Width * maxHeight / FullSizeImage.Height;
                newHeight = maxHeight;
            }
            newHeight = maxHeight;
            // Create the new image with the sizes we've calculated
            System.Drawing.Image NewImage = FullSizeImage.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero);
            FullSizeImage.Dispose();
            NewImage.Save(newFileLocation + newFileName);
        }
        public void ResizeImageAndRatio(string origFileLocation, string newFileLocation, string origFileName, string newFileName, int newWidth, int newHeight, bool resizeIfWider)
        {

            System.Drawing.Image initImage = System.Drawing.Image.FromFile(origFileLocation + origFileName);
            int templateWidth = newWidth;
            int templateHeight = newHeight;
                double templateRate = double.Parse(templateWidth.ToString()) / templateHeight;
                double initRate = double.Parse(initImage.Width.ToString()) / initImage.Height;
                if (templateRate == initRate)
                {

                    System.Drawing.Image templateImage = new System.Drawing.Bitmap(templateWidth, templateHeight);
                    System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage);
                    templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
                    templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    templateG.Clear(Color.White);
                    templateG.DrawImage(initImage, new System.Drawing.Rectangle(0, 0, templateWidth, templateHeight), new System.Drawing.Rectangle(0, 0, initImage.Width, initImage.Height), System.Drawing.GraphicsUnit.Pixel);
                    templateImage.Save(newFileLocation + newFileName, System.Drawing.Imaging.ImageFormat.Jpeg);
                }

                else
                {

                    System.Drawing.Image pickedImage = null;
                    System.Drawing.Graphics pickedG = null;


                    Rectangle fromR = new Rectangle(0, 0, 0, 0);
                    Rectangle toR = new Rectangle(0, 0, 0, 0);


                    if (templateRate > initRate)
                    {

                        pickedImage = new System.Drawing.Bitmap(initImage.Width, int.Parse(Math.Floor(initImage.Width / templateRate).ToString()));
                        pickedG = System.Drawing.Graphics.FromImage(pickedImage);


                        fromR.X = 0;
                        fromR.Y = int.Parse(Math.Floor((initImage.Height - initImage.Width / templateRate) / 2).ToString());
                        fromR.Width = initImage.Width;
                        fromR.Height = int.Parse(Math.Floor(initImage.Width / templateRate).ToString());


                        toR.X = 0;
                        toR.Y = 0;
                        toR.Width = initImage.Width;
                        toR.Height = int.Parse(Math.Floor(initImage.Width / templateRate).ToString());
                    }

                    else
                    {
                        pickedImage = new System.Drawing.Bitmap(int.Parse(Math.Floor(initImage.Height * templateRate).ToString()), initImage.Height);
                        pickedG = System.Drawing.Graphics.FromImage(pickedImage);

                        fromR.X = int.Parse(Math.Floor((initImage.Width - initImage.Height * templateRate) / 2).ToString());
                        fromR.Y = 0;
                        fromR.Width = int.Parse(Math.Floor(initImage.Height * templateRate).ToString());
                        fromR.Height = initImage.Height;

                        toR.X = 0;
                        toR.Y = 0;
                        toR.Width = int.Parse(Math.Floor(initImage.Height * templateRate).ToString());
                        toR.Height = initImage.Height;
                    }


                    pickedG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                    pickedG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;


                    pickedG.DrawImage(initImage, toR, fromR, System.Drawing.GraphicsUnit.Pixel);


                    System.Drawing.Image templateImage = new System.Drawing.Bitmap(templateWidth, templateHeight);
                    System.Drawing.Graphics templateG = System.Drawing.Graphics.FromImage(templateImage);
                    templateG.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
                    templateG.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
                    templateG.Clear(Color.White);
                    templateG.DrawImage(pickedImage, new System.Drawing.Rectangle(0, 0, templateWidth, templateHeight), new System.Drawing.Rectangle(0, 0, pickedImage.Width, pickedImage.Height), System.Drawing.GraphicsUnit.Pixel);
                    templateImage.Save(newFileLocation + newFileName, System.Drawing.Imaging.ImageFormat.Jpeg);


                    templateG.Dispose();
                    templateImage.Dispose();

                    pickedG.Dispose();
                    pickedImage.Dispose();
                }
                initImage.Dispose();
            }

    }
}

非常感谢,伙计!我将其转换为图像扩展方法并使用了using语句,这样我就可以创建一个端点来返回图像流以进行预览。如果有人想看看,让我知道。 - JohnnyFun
1
谢谢,这正是我所需要的。 - Sagi

7

看起来你应该可以通过一次DrawImage调用来进行裁剪和调整大小。

_graphic.DrawImage(img,
   new Rectangle(/*..cropped rect..*/),
   new Rectangle(/*..new size..*/),
   GraphicsUnit.Pixel);

2
我认为你把矩形搞反了,MSDN页面上说顺序应该是:图像、目标矩形、源矩形、图形单元。 - That Chuck Guy
实际上,这将在正确的调整大小的空间中绘制所需裁剪区域的图像……另外,我还得到了一个与裁剪区域相同高度和宽度的大黑色正方形,与裁剪区域一起被绘制出来。 - stephen776

6

所有回答中都忽略了一个问题,由于GDI的一个bug,生成的图片周围会有一个50%透明度的1像素边框。

为了正确地裁剪和调整大小,需要将以下设置应用于图形对象:

        g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
        g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

然后你需要创建一个ImageAttributes实例来修复边框bug:

ImageAttributes ia = new ImageAttributes();
ia.SetWrapMode(WrapMode.TileFlipXY);

然后,在调用DrawImage时,将ia作为最后一个参数传递。

如果您处理任何PNG、TIFF或ICO图像并将它们转换为不支持透明度的格式,则还需要在调用DrawImage之前调用g.Clear(bgcolor)。

如果要编码为jpeg格式,请确保设置质量参数,并在此之后处理EncoderParameters对象。

您从中读取的位图实例将锁定底层文件,直到被处理。如果使用FromStream方法,则必须保持流处于打开状态,直到Bitmap实例被处理。一种好的方法是将流克隆到MemoryStream实例中,并将其分配给Bitmap.Tag属性。

我在我的博客上列出了更完整的GDI+裁剪和缩放错误列表,以避免

通常我会推荐人们使用我的imageresizing.net库,因为它旨在在网站上安全运行,并提供最佳性能。1行代码,用户误差极小。

我下载了Schnieds的示例项目,我不得不说这是一种(不必要的)复杂的做法。非破坏性编辑实际上要简单得多,就像这篇文章中所示。它很容易与Uploadify组合使用,尽管我在博客中没有涵盖。

此外,在上传过程中重新编码图像对jpeg和png文件来说非常破坏性。验证是好的,但仅在验证后处理实例,不要重新编码它。Schnieds的示例还通过未处理的Bitmap实例泄漏内存——在高负载服务器上运行它会很快崩溃。


感谢您提供详细的答案。我会查看所有提到的内容,希望能够想出更简洁的解决方案。 - stephen776
如果Bitmap实例被包装在using语句中,为什么会通过它泄漏内存? - stephen776
你能指出Schnied使用哪种方法创建位图而不使用using吗?我找不到它。我正在尝试修改他的示例以适应您的建议并修复内存泄漏问题。 - stephen776
在MediaController.cs文件中,方法ProcessUploadedImage中,他创建了一个名为'workingImage'的Image实例,将其重新编码为byte[]数组,然后假装它不存在。我可以建议您不要以他的示例代码为基础吗?它非常低效且没有做任何“正确”的事情。 - Lilith River
请问您能否更具体些,说明是哪里不正确?除了少数的内存泄漏和需要修复的图像处理方面的一些小改动外,还有什么问题吗?这个示例是我发现的最完整的之一,可以允许用户上传、裁剪和调整单张照片(在我的实现中,这些操作将在 S3 上存储)。 - stephen776
显示剩余5条评论

4

修复了问题...

我在实例化一个新的位图对象时,将被裁剪区域的宽度和高度传入语句中。

通过创建具有所需固定大小的调整后图像的位图对象进行了更正...

using (System.Drawing.Bitmap _bitmap = new System.Drawing.Bitmap(W_FixedSize, H_FixedSize))
{
    _bitmap.SetResolution(img.HorizontalResolution, img.VerticalResolution);
    using (Graphics _graphic = Graphics.FromImage(_bitmap))
    {
        _graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
        _graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
        _graphic.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
        _graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;

        //Code used to crop
        _graphic.DrawImage(img, 0, 0, w, h);
        _graphic.DrawImage(img, new Rectangle(0, 0, w, h), x, y, w, h, GraphicsUnit.Pixel);

        //Code I used to resize
        _graphic.DrawImage(img, 0, 0, img.Width, img.Height);
        _graphic.DrawImage(img, new Rectangle(0, 0, W_FixedSize, H_FixedSize), 0, 0, img.Width, img.Height, GraphicsUnit.Pixel);



       //continued...
    }
}

非常感谢。这段代码可用于裁剪图像而不保持纵横比,并根据给定的高度和宽度准确裁剪图像。 - shrawan_lakhe

0

用于客户端的 http://jcrop.org/

http://jcrop.org/demos/basic

   public static class ImageHelper
{
    public static byte[] CropImage(byte[] content, int x, int y, int width, int height)
    {
        using (MemoryStream stream = new MemoryStream(content))
        {
            return CropImage(stream, x, y, width, height);
        }
    }

    public static byte[] CropImage(Stream content, int x, int y, int width, int height)
    {
        //Parsing stream to bitmap
        using (Bitmap sourceBitmap = new Bitmap(content))
        {
            //Get new dimensions
            double sourceWidth = Convert.ToDouble(sourceBitmap.Size.Width);
            double sourceHeight = Convert.ToDouble(sourceBitmap.Size.Height);
            Rectangle cropRect = new Rectangle(x, y, width, height);

            //Creating new bitmap with valid dimensions
            using (Bitmap newBitMap = new Bitmap(cropRect.Width, cropRect.Height))
            {
                using (Graphics g = Graphics.FromImage(newBitMap))
                {
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    g.CompositingQuality = CompositingQuality.HighQuality;

                    g.DrawImage(sourceBitmap, new Rectangle(0, 0, newBitMap.Width, newBitMap.Height), cropRect, GraphicsUnit.Pixel);

                    return GetBitmapBytes(newBitMap);
                }
            }
        }
    }

    public static byte[] GetBitmapBytes(Bitmap source)
    {
        //Settings to increase quality of the image
        ImageCodecInfo codec = ImageCodecInfo.GetImageEncoders()[4];
        EncoderParameters parameters = new EncoderParameters(1);
        parameters.Param[0] = new EncoderParameter(Encoder.Quality, 100L);

        //Temporary stream to save the bitmap
        using (MemoryStream tmpStream = new MemoryStream())
        {
            source.Save(tmpStream, codec, parameters);

            //Get image bytes from temporary stream
            byte[] result = new byte[tmpStream.Length];
            tmpStream.Seek(0, SeekOrigin.Begin);
            tmpStream.Read(result, 0, (int)tmpStream.Length);

            return result;
        }
    }
    public static Image Resize(Image current, int maxWidth, int maxHeight)
    {
        int width, height;
        #region reckon size 
        if (current.Width > current.Height)
        {
            width = maxWidth;
            height = Convert.ToInt32(current.Height * maxHeight / (double)current.Width);
        }
        else
        {
            width = Convert.ToInt32(current.Width * maxWidth / (double)current.Height);
            height = maxHeight;
        }
        #endregion

        #region get resized bitmap 
        var canvas = new Bitmap(width, height);

        using (var graphics = Graphics.FromImage(canvas))
        {
            graphics.CompositingQuality = CompositingQuality.HighSpeed;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.CompositingMode = CompositingMode.SourceCopy;
            graphics.DrawImage(current, 0, 0, width, height);
        }

        return canvas;
        #endregion
    }

    public static Image byteArrayToImage(byte[] byteArrayIn)
    {
        MemoryStream ms = new MemoryStream(byteArrayIn);
        Image returnImage = Image.FromStream(ms);
        return returnImage;
    }
    public static byte[] imageToByteArray(Image image)
    {
        using (var ms = new MemoryStream())
        {
            image.Save(ms, image.RawFormat);
            return ms.ToArray();
        }
    }
}

在控制器中可以这样使用

 int cropPointX = Convert.ToInt32(model.imgX1);
                int cropPointY = Convert.ToInt32(model.imgY1);
                int imageCropWidth = Convert.ToInt32(model.imgWidth);
                int imageCropHeight = Convert.ToInt32(model.imgHeight);

                byte[] imageBytes = ConvertToBytes(model.ProductImage);
                byte[] croppedImage;
                if (cropPointX > 0 || cropPointY > 0 || imageCropWidth > 0 || imageCropHeight > 0)
                {
                    croppedImage = CropImage(imageBytes, cropPointX, cropPointY, imageCropWidth, imageCropHeight);
                }
                else
                {
                    croppedImage = imageBytes;
                }
                Stream stream = new MemoryStream(croppedImage);
                Image img = Image.FromStream(stream, true, true);
                if (img.Height > 522 || img.Width > 522)
                {
                    img = Resize(img, 522, 522);
                }


byte[] imageBytes = (byte[])(new ImageConverter()).ConvertTo(img, typeof(byte[]));

在 imageBytes 中,您将获得裁剪和调整大小后的图像。您可以将图像存储在数据库或文件夹中的任何位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接