Призрачный контур вокруг изображения после изменения размера - PullRequest
1 голос
/ 21 декабря 2010

Я работаю над сайтом, который будет продавать ювелирные изделия ручной работы, и я заканчиваю редактор изображений, но он ведет себя не совсем правильно.

Обычно пользователь загружает изображение, которое будет сохранено в качестве источника, а затем оно будет изменено в соответствии с экраном пользователя и сохранено в качестве временного. Затем пользователь перейдет к экрану, который позволит им обрезать изображение, а затем сохранить его в окончательной версии.

Все это прекрасно работает, за исключением того, что в финальной версии есть 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

UPDATE

Итак, я сел и прочитал MSDN примерно через 2 часа после публикации, проверив код, который у меня был. Насколько я могу судить, я использую максимально возможную настройку качества при выполнении манипуляции.

В любом случае, я закончил тем, что немного очистил и оптимизировал код. Я подумал о необходимости исходного файла и решил удалить его, потому что он требовал от меня дополнительной работы, чтобы выяснить размеры обрезки, основанные на временном файле. Итак, оно ушло.

Кроме того, где-то вдоль обтекания исчезла черная линия, поэтому я могу только предположить, что проблемы с соотношением сторон должны были быть исправлены, как указано @StefanE.

Кроме того, как сказал @VinayC, ресайзер генерировал значение высоты 479 (которое я до сих пор не понимаю, как, но что угодно ...), но это, похоже, было исправлено, когда я переключился чтобы использовать классы System.Drawing.Size и System.Drawing.Rectangle на всем пути, вместо использования моих собственных классов, которые по сути делают то же самое.

Итак, вот обновленный код. Оставшиеся две ошибки ошибка все еще не устранена, поэтому у меня есть контур "поблескивания" вокруг изображения (см. Второе вложение), который я могу сузить до изменения размера, потому что оно отображается в первой размер, где обрезка не произошла. И вторая ошибка в том, что обрезанные версии всегда располагаются ниже по оси 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 ]

6 голосов
/ 21 декабря 2010

Что касается черных линий внизу, это потому, что вы не обрабатываете соотношение сторон.

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/

2 голосов
/ 21 декабря 2010

Я предполагаю, что при вводе / расчете вы, вероятно, предоставляете исходный прямоугольник большего размера, чем фактически существует - например, размер исходного изображения 640 x 480, но при масштабировании (вызов DrawImage) он принимается как 640 x 479. Также возможен другой способ, что целевой прямоугольник меньше целевого растрового изображения - но это выглядит маловероятным, если взглянуть на ваш код. Лучший способ устранения неполадок - это установить точку останова, проверить размер исходного изображения и сравнить его с исходным размером прямоугольника (параметр кадрирования)

1 голос
/ 23 декабря 2010

Хорошие новости, все! Я исправил это, и я должен сказать, что решение было настолько простым, что просто заставило меня хрюкать и тяжело вздыхать. К счастью, в методе Resize я выполнял способ * на 1003 * больше работы, чем было необходимо для изменения размера изображения. Вся using (Graphics ...) была проблема. Вы можете просто сделать using (Bitmap Bitmap = new Bitmap(SOURCE_IMAGE, NEW_SIZE)), и это работает.

Чисто и просто, и меня удивляет, почему учебники в Интернете (и код, который я до сих пор использовал в других проектах) заставляют использовать класс Graphics, когда он не нужен?

Итак, без дополнительной информации, вот окончательная версия моего кода для тех, кто может найти его полезным. Имейте в виду, что методы CropAndResize, QueueFor и Purge специализированы для работы с моей моделью предметной области, но методы Crop и Resize, о которых в конечном итоге говорит весь этот класс, могут быть легко адаптированы к любым другим применение.

Наслаждайтесь:

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);
        };
    }
}
...