调整大小后围绕图像的幽灵轮廓

时间:2010-12-21 05:19:25

标签: c# .net image resize scale

我正在一个销售手工制作珠宝的网站上工作,我正在完成图像编辑器,但它表现得并不是很正确。

基本上,用户上传的图像将保存为源,然后将调整大小以适合用户的屏幕并保存为临时图像。然后,用户将转到一个屏幕,该屏幕将允许他们裁剪图像,然后将其保存到最终版本。

所有这些都很好,除了最终版本有3个错误。首先是图像底部的一些黑色水平线。第二个是边缘之后的各种轮廓。我认为这是因为我降低了质量,但即使是100%它仍然会出现......最后,我注意到裁剪后的图像总是比我指定的低几个像素......

无论如何,我希望有人在使用C#编辑图像方面有经验,可以看看代码,看看我可能走错了路。

哦,顺便说一句,这是在ASP.NET MVC应用程序中。

以下是代码:

public class ImageProvider {
    private readonly ProductProvider ProductProvider = null;

    private readonly EncoderParameters HighQualityEncoder = new EncoderParameters();
    private readonly ImageCodecInfo JpegCodecInfo = ImageCodecInfo.GetImageEncoders().Single(
        c => (c.MimeType == "image/jpeg"));

    private readonly string Path = HttpContext.Current.Server.MapPath("~/Resources/Images/Products");
    private readonly short[][] Dimensions = new short[3][] {
        new short[2] { 640, 480 },
        new short[2] { 240, 0 },
        new short[2] { 80, 60 }
    }

    public ImageProvider(ProductProvider ProductProvider) {
        this.ProductProvider = ProductProvider;

        HighQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
    }

    public void Crop(string FileName, Image Image, Crop Crop) {
        using (Bitmap Source = new Bitmap(Image)) {
            using (Bitmap Target = new Bitmap(Crop.Width, Crop.Height)) {
                using (Graphics Graphics = Graphics.FromImage(Target)) {
                    Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    Graphics.SmoothingMode = SmoothingMode.HighQuality;
                    Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    Graphics.CompositingQuality = CompositingQuality.HighQuality;

                    Graphics.DrawImage(Source, new Rectangle(0, 0, Target.Width, Target.Height), new Rectangle(Crop.Left, Crop.Top, Crop.Width, Crop.Height), GraphicsUnit.Pixel);
                }

                Target.Save(FileName, JpegCodecInfo, HighQualityEncoder);
            }
        }
    }

    public void CropAndResize(Product Product, Crop Crop) {
        using (Image Source = Image.FromFile(String.Format("{0}/{1}.source", Path, Product.ProductId))) {
            using (Image Temp = Image.FromFile(String.Format("{0}/{1}.temp", Path, Product.ProductId))) {
                float Percent = ((float)Source.Width / (float)Temp.Width);

                short Width = (short)(Temp.Width * Percent);
                short Height = (short)(Temp.Height * Percent);

                Crop.Height = (short)(Crop.Height * Percent);
                Crop.Left = (short)(Crop.Left * Percent);
                Crop.Top = (short)(Crop.Top * Percent);
                Crop.Width = (short)(Crop.Width * Percent);

                Img Img = new Img();

                this.ProductProvider.AddImageAndSave(Product, Img);

                this.Crop(String.Format("{0}/{1}.cropped", Path, Img.ImageId), Source, Crop);

                using (Image Cropped = Image.FromFile(String.Format("{0}/{1}.cropped", Path, Img.ImageId))) {
                    this.Resize(this.Dimensions[0], String.Format("{0}/{1}-L.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
                    this.Resize(this.Dimensions[1], String.Format("{0}/{1}-T.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
                    this.Resize(this.Dimensions[2], String.Format("{0}/{1}-S.jpg", Path, Img.ImageId), Cropped, HighQualityEncoder);
                }
            }
        }

        this.Purge(Product);
    }

    public void QueueFor( Product Product, HttpPostedFileBase PostedFile) {
        using (Image Image = Image.FromStream(PostedFile.InputStream)) {
            this.Resize(new short[2] {
                1152,
                0
            }, String.Format("{0}/{1}.temp", Path, Product.ProductId), Image, HighQualityEncoder);
        }

        PostedFile.SaveAs(String.Format("{0}/{1}.source", Path, Product.ProductId));
    }

    private void Purge(Product Product) {
        string Source = String.Format("{0}/{1}.source", Path, Product.ProductId);
        string Temp = String.Format("{0}/{1}.temp", Path, Product.ProductId);

        if (File.Exists(Source)) {
            File.Delete(Source);
        }
        if (File.Exists(Temp)) {
            File.Delete(Temp);
        }

        foreach (Img Img in Product.Imgs) {
            string Cropped = String.Format("{0}/{1}.cropped", Path, Img.ImageId);

            if (File.Exists(Cropped)) {
                File.Delete(Cropped);
            }
        }
    }

    public void Resize( short[] Dimensions, string FileName, Image Image, EncoderParameters EncoderParameters) {
        if (Dimensions[1] == 0) {
            Dimensions[1] = (short)(Image.Height / ((float)Image.Width / (float)Dimensions[0]));
        }

        using (Bitmap Bitmap = new Bitmap(Dimensions[0], Dimensions[1])) {
            using (Graphics Graphics = Graphics.FromImage(Bitmap)) {
                Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                Graphics.SmoothingMode = SmoothingMode.HighQuality;
                Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                Graphics.CompositingQuality = CompositingQuality.HighQuality;

                Graphics.DrawImage(Image, 0, 0, Dimensions[0], Dimensions[1]);
            };

            Bitmap.Save(FileName, JpegCodecInfo, EncoderParameters);
        }
    }
}

以下是其中一张图片:

http://i.stack.imgur.com/GJX97.jpg

更新

所以,我发布后,我坐下来阅读了MSDN 2小时,验证了我的代码。据我所知,我在执行操作时使用了最高质量的设置。

无论哪种方式,我最终清理并简化了代码。我考虑了源文件的需要,并决定删除它,因为它要求我做额外的工作来找出基于临时文件的裁剪尺寸。所以,它已经消失了。

此外,在精简的某个地方,黑线消失了,所以我只能假设宽高比问题应该已经纠正为@StefanE所说的。

另外,正如@VinayC所说,重新分级器为高度产生了479的值(我仍然没有得到如何,但无论如何......),但是当我切换时似乎已经纠正了一直使用System.Drawing.Size和System.Drawing.Rectangle类,而不是使用我自己的基本上做同样事情的类。

所以,这是更新的代码。剩下的两个bug bug仍然存在,所以我在图像周围有一个“重影”轮廓(见第二个附件),我可以缩小到调整大小,因为它显示在第一个-size没有发生裁剪。 第二个错误是裁剪版本总是位于y轴的下方,而不是我作为裁剪器传递的版本。我估计它比我说的好5%-8%,所以不确定那个(位置不应该改变......除非我从jQuery传入一个错误的数字,必须检查在...)。

(我的第二个错误最终导致我发送错误的值,裁剪div基于主内容div获得其相对位置,而不是图像容器div。现在全部修复。)

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;

namespace Website.Models.Providers {
    public class ImageProvider {
        private readonly ProductProvider ProductProvider = null;

        private readonly EncoderParameters DefaultQualityEncoder = new EncoderParameters();
        private readonly EncoderParameters HighQualityEncoder = new EncoderParameters();
        private readonly ImageCodecInfo JpegCodecInfo = ImageCodecInfo.GetImageEncoders().Single(
            c =>
                (c.MimeType == "image/jpeg"));
        private readonly Size[] Sizes = new Size[3] {
            new Size(640, 0),
            new Size(240, 0),
            new Size(80, 0)
        };

        private readonly string Path = HttpContext.Current.Server.MapPath("~/Resources/Images/Products");

        public ImageProvider(
            ProductProvider ProductProvider) {
            this.ProductProvider = ProductProvider;

            this.DefaultQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 80L);
            this.HighQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
        }

        public void Crop(
            string FileName,
            Image Image,
            Crop Crop) {
            using (Bitmap Source = new Bitmap(Image)) {
                using (Bitmap Target = new Bitmap(Crop.Width, Crop.Height)) {
                    using (Graphics Graphics = Graphics.FromImage(Target)) {
                        Graphics.CompositingMode = CompositingMode.SourceCopy;
                        Graphics.CompositingQuality = CompositingQuality.HighQuality;
                        Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                        Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                        Graphics.SmoothingMode = SmoothingMode.HighQuality;

                        Graphics.DrawImage(Source, new Rectangle(0, 0, Target.Width, Target.Height), new Rectangle(Crop.Left, Crop.Top, Crop.Width, Crop.Height), GraphicsUnit.Pixel);
                    };

                    Target.Save(FileName, JpegCodecInfo, HighQualityEncoder);
                };
            };
        }

        public void CropAndResize(
            Product Product,
            Crop Crop) {
            using (Image Temp = Image.FromFile(String.Format("{0}/{1}.temp", Path, Product.ProductId))) {
                Img Img = new Img();

                this.ProductProvider.AddImageAndSave(Product, Img);

                this.Crop(String.Format("{0}/{1}.cropped", Path, Img.ImageId), Temp, Crop);

                using (Image Cropped = Image.FromFile(String.Format("{0}/{1}.cropped", Path, Img.ImageId))) {
                    this.Resize(String.Format("{0}/{1}-L.jpg", Path, Img.ImageId), this.Sizes[0], Cropped, HighQualityEncoder);
                    this.Resize(String.Format("{0}/{1}-T.jpg", Path, Img.ImageId), this.Sizes[1], Cropped, HighQualityEncoder);
                    this.Resize(String.Format("{0}/{1}-S.jpg", Path, Img.ImageId), this.Sizes[2], Cropped, HighQualityEncoder);
                };
            };

            this.Purge(Product);
        }

        public void QueueFor(
            Product Product,
            Size Size,
            HttpPostedFileBase PostedFile) {
            using (Image Image = Image.FromStream(PostedFile.InputStream)) {
                this.Resize(String.Format("{0}/{1}.temp", Path, Product.ProductId), Size, Image, HighQualityEncoder);
            };
        }

        private void Purge(
            Product Product) {
            string Temp = String.Format("{0}/{1}.temp", Path, Product.ProductId);

            if (File.Exists(Temp)) {
                File.Delete(Temp);
            };

            foreach (Img Img in Product.Imgs) {
                string Cropped = String.Format("{0}/{1}.cropped", Path, Img.ImageId);

                if (File.Exists(Cropped)) {
                    File.Delete(Cropped);
                };
            };
        }

        public void Resize(
            string FileName,
            Size Size,
            Image Image,
            EncoderParameters EncoderParameters) {
            if (Size.Height == 0) {
                Size.Height = (int)(Image.Height / ((float)Image.Width / (float)Size.Width));
            };

            using (Bitmap Bitmap = new Bitmap(Size.Width, Size.Height)) {
                using (Graphics Graphics = Graphics.FromImage(Bitmap)) {
                    Graphics.CompositingMode = CompositingMode.SourceCopy;
                    Graphics.CompositingQuality = CompositingQuality.HighQuality;
                    Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    Graphics.SmoothingMode = SmoothingMode.HighQuality;

                    Graphics.DrawImage(Image, new Rectangle(0, 0, Size.Width, Size.Height));
                };

                Bitmap.Save(FileName, JpegCodecInfo, EncoderParameters);
            };
        }
    }
}

此图像显示红色箭头指向的“重影”轮廓。

alt text

3 个答案:

答案 0 :(得分:6)

关于底部的黑线是因为你没有处理宽高比。

double aspectRatio = imageWidth/imageHeight;
double boxRatio = maxWidth/maxHeight;
double scaleFactor = 0;
if (boxRatio > aspectRatio)
 //Use height, since that is the most restrictive dimension of box.
 scaleFactor = maxHeight / imageHeight;
else
 scaleFactor = maxWidth / imageWidth;

double newWidth = imageWidth * scaleFactor;
double newHeight = imageHeight * scaleFactor;

来源:http://nathanaeljones.com/163/20-image-resizing-pitfalls/

答案 1 :(得分:2)

我想在您的输入/计算中,您可能提供比实际存在更大的源矩形 - 例如,源图像大小为640 x 480但在缩放(DrawImage调用)时,它将以640 x 479的形式传递。其他方式也可能是目标矩形小于目标位图 - 但看起来你的代码似乎不太可能。麻烦射击的最佳方法是放置断点并检查源图像大小并比较它将源矩形大小(裁剪参数)

答案 2 :(得分:1)

好消息,大家好!我修好了,我不得不说解决方案很简单,它让我咕噜咕噜地叹了口气。显然,在Resize方法中我正在做方式比调整图像大小所需的工作更多。整个using (Graphics ...)就是问题所在。你可以做using (Bitmap Bitmap = new Bitmap(SOURCE_IMAGE, NEW_SIZE))而且有效。

干净简单,这让我想知道为什么网上的教程(以及我在其他项目中一直用到的代码)强制在不需要时使用Graphics类?

所以,如果没有进一步的到期日,这是我的代码的最终版本,适用于任何可能发现它有用的人。请注意,CropAndResizeQueueForPurge方法专门用于处理我的域模型,但是这整个类的CropResize方法最终可以很容易地适应任何其他应用程序。

享受:

public class ImageProvider {
    private readonly ProductProvider ProductProvider = null;

    private readonly EncoderParameters DefaultQualityEncoder = new EncoderParameters();
    private readonly EncoderParameters HighQualityEncoder = new EncoderParameters();
    private readonly ImageCodecInfo JpegCodecInfo = ImageCodecInfo.GetImageEncoders().Single(
        c =>
            (c.MimeType == "image/jpeg"));
    private readonly Size[] Sizes = new Size[3] {
        new Size(640, 0),
        new Size(280, 0),
        new Size(80, 0)
    };

    private readonly string Path = HttpContext.Current.Server.MapPath("~/Resources/Images/Products");

    public ImageProvider(
        ProductProvider ProductProvider) {
        this.ProductProvider = ProductProvider;

        this.DefaultQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 90L);
        this.HighQualityEncoder.Param[0] = new EncoderParameter(Encoder.Quality, 100L);
    }

    private void Crop(
        string FileName,
        Image Image,
        Crop Crop) {
        using (Bitmap Source = new Bitmap(Image)) {
            Source.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);

            using (Bitmap Target = new Bitmap(Crop.Width, Crop.Height, Image.PixelFormat)) {
                Target.SetResolution(Image.HorizontalResolution, Image.VerticalResolution);

                using (Graphics Graphics = Graphics.FromImage(Target)) {
                    Graphics.CompositingMode = CompositingMode.SourceCopy;
                    Graphics.CompositingQuality = CompositingQuality.HighQuality;
                    Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    Graphics.PageUnit = GraphicsUnit.Pixel;
                    Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
                    Graphics.SmoothingMode = SmoothingMode.HighQuality;

                    Graphics.DrawImage(Source, new Rectangle(0, 0, Target.Width, Target.Height), new Rectangle(Crop.Left, Crop.Top, Crop.Width, Crop.Height), GraphicsUnit.Pixel);
                };

                Target.Save(FileName, JpegCodecInfo, HighQualityEncoder);
            };
        };
    }

    public void CropAndResize(
        Product Product,
        Crop Crop) {
        using (Image Temp = Image.FromFile(String.Format("{0}/{1}.temp", Path, Product.ProductId))) {
            Img Img = new Img();

            this.ProductProvider.AddImageAndSave(Product, Img);

            this.Crop(String.Format("{0}/{1}.cropped", Path, Img.ImageId), Temp, Crop);

            using (Image Cropped = Image.FromFile(String.Format("{0}/{1}.cropped", Path, Img.ImageId))) {
                this.Resize(String.Format("{0}/{1}-L.jpg", Path, Img.ImageId), this.Sizes[0], Cropped, DefaultQualityEncoder);
                this.Resize(String.Format("{0}/{1}-T.jpg", Path, Img.ImageId), this.Sizes[1], Cropped, DefaultQualityEncoder);
                this.Resize(String.Format("{0}/{1}-S.jpg", Path, Img.ImageId), this.Sizes[2], Cropped, DefaultQualityEncoder);
            };
        };

        this.Purge(Product);
    }

    public void QueueFor(
        Product Product,
        Size Size,
        HttpPostedFileBase PostedFile) {
        using (Image Image = Image.FromStream(PostedFile.InputStream)) {
            this.Resize(String.Format("{0}/{1}.temp", Path, Product.ProductId), Size, Image, HighQualityEncoder);
        };
    }

    private void Purge(
        Product Product) {
        string Temp = String.Format("{0}/{1}.temp", Path, Product.ProductId);

        if (File.Exists(Temp)) {
            File.Delete(Temp);
        };

        foreach (Img Img in Product.Imgs) {
            string Cropped = String.Format("{0}/{1}.cropped", Path, Img.ImageId);

            if (File.Exists(Cropped)) {
                File.Delete(Cropped);
            };
        };
    }

    private void Resize(
        string FileName,
        Size Size,
        Image Image,
        EncoderParameters EncoderParameters) {
        if (Size.Height == 0) {
            Size.Height = (int)(Image.Height / ((float)Image.Width / (float)Size.Width));
        };

        using (Bitmap Bitmap = new Bitmap(Image, Size)) {
            Bitmap.Save(FileName, JpegCodecInfo, EncoderParameters);
        };
    }
}