С# Измененные изображения имеют черные границы

У меня проблема с масштабированием изображения в .NET. Я использую стандартный тип Graphics для изменения размеров изображений, как в этом примере:

public static Image Scale(Image sourceImage, int destWidth, int destHeight)
{
        Bitmap toReturn = new Bitmap(sourceImage, destWidth, destHeight);

        toReturn.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

        using (Graphics graphics = Graphics.FromImage(toReturn))
        {
            graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
            graphics.DrawImage(sourceImage, 0, 0, destWidth, destHeight);
        }
        return toReturn;
    }

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

Почему они появляются и что я могу сделать, чтобы они исчезли?

Результат вывода:

sample output

Ответ 1

Try:

graphic.CompositingMode = CompositingMode.SourceCopy;

Ответ 2

Это может быть вызвано неправильной интерполяцией пикселей вокруг краев. Я бы назвал это ошибкой.

Здесь решение, хотя:

graphics.CompositingMode = CompositingMode.SourceCopy;
graphics.PixelOffsetMode = PixelOffsetMode.Half;
graphics.InterpolationMode = InterpolationMode.NearestNeighbor;

// Draw your image here.

graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;

// Draw it again.

Что это значит, сначала рисуем "фон" с правильно заполненными краями, а затем снова рисуем с интерполяцией. Если вам не нужна интерполяция, то это необязательно.

Ответ 3

Реальное решение - использовать перегрузку DrawImage, которая позволяет передать объект ImageAttributes.

В экземпляре ImageAttributes вызовите следующий метод, прежде чем передать его в DrawImage:

using (var ia = new ImageAttributes())
{
    ia.SetWrapMode(WrapMode.TileFlipXY);
    aGraphic.DrawImage(..., ia);
}

См. также этот ответ

Ответ 4

Проблема заключается в том, что ваш растровый образ toReturn по умолчанию имеет черный фон. Копирование нового изображения поверх него делает черные или серые границы.

Решение состоит в том, чтобы удалить черный фон по умолчанию, вызвав:

toReturn.MakeTransparent();

Так как после этой строки вы будете рисовать новое изображение без какого-либо цвета фона, границы исчезнут.

Ответ 5

Это потому, что выборка была взята из краев фотографии.

Ответ 6

Как для вас работает следующая работа? Это код, который я использовал, чтобы сделать то же самое. Основное отличие, которое я замечаю, это то, что я не использую SetResolution (и я принимаю квадратный ввод и вывод, поскольку это было для меня).

/// <summary>
/// Resizes a square image
/// </summary>
/// <param name="OriginalImage">Image to resize</param>
/// <param name="Size">Width and height of new image</param>
/// <returns>A scaled version of the image</returns>
internal static Image ResizeImage( Image OriginalImage, int Size )
{
    Image finalImage = new Bitmap( Size, Size );

    Graphics graphic = Graphics.FromImage( finalImage );

    graphic.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
    graphic.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
    graphic.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;

    Rectangle rectangle = new Rectangle( 0, 0, Size, Size );

    graphic.DrawImage( OriginalImage, rectangle );

    return finalImage;
}

Ответ 7

Это происходит из-за сглаживания (смешивания с фоном) по краям при рисовании изображения.

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

Ответ 8

Ни один из них не работал у меня.

Однако изменение формата с

System.Drawing.Imaging.PixelFormat.Format24bppRgb

to

System.Drawing.Imaging.PixelFormat.Format32bppArgb 

решил проблему

using (System.Drawing.Bitmap newImage = new System.Drawing.Bitmap(newWidth, newHeight,
                // System.Drawing.Imaging.PixelFormat.Format24bppRgb // OMG bug
                    System.Drawing.Imaging.PixelFormat.Format32bppArgb 
                ))
            {

Ответ 9

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

Есть три причины для артефактов, которые вы видите:

  • Значение по умолчанию Graphics.PixelOffsetMode приводит к неправильной выборке пикселей, что приводит к небольшому искажению изображения, особенно по краям.
  • InterpolationMode.HighQualityBicubic выборки пикселей за пределами края изображения, которые по умолчанию прозрачны. Эти прозрачные пиксели смешиваются с крайними пикселями сэмплером, что приводит к полупрозрачным краям.
  • При сохранении полупрозрачного изображения в формате, который не поддерживает прозрачность (например, JPEG), прозрачные значения заменяются черным.

Все это добавляет к получерным (т.е. серым) ребрам.

Правильный способ удаления этих артефактов - использовать PixelOffsetMode.Half и использовать объект ImageAttributes для указания кроссировки края, чтобы сэмплер HighQualityBicubic имел что-то отличное от прозрачных пикселей для образца.

Есть еще несколько проблем с кодом, который вы опубликовали:

Используемый конструктор Bitmap инициализирует новый Bitmap путем изменения размера исходного изображения, поэтому вы выполняете операцию изменения размера дважды. Для создания пустого холста следует использовать перегрузку конструктора с нужными размерами.

Помните, что класс Bitmap представляет неуправляемую копию изображения в памяти. Он должен быть удален таким образом, чтобы GDI + можно было сообщить, чтобы освободить эту память, когда вы закончите с ней. Я предполагаю, что вы делаете это в коде, который получает return Image, но я указываю это на случай, если кто-то другой заимствует этот код.

Параметр CompositingQuality.HighQuality, используемый в вашем коде, не будет иметь визуального эффекта, если вы правильно настроите другие настройки и значительно повредите производительность значительно в сочетании со значением по умолчанию CompositingMode.SourceOver. Вы можете опустить параметр CompositingQuality и установить CompositingMode.SourceCopy, чтобы получить те же результаты с лучшей производительностью.

Параметр SmoothingMode, используемый в вашем коде, вообще не влияет на DrawImage(), поэтому его можно удалить.

Подробнее о параметрах класса Graphics и их влиянии на качество и производительность изображения можно узнать здесь: http://photosauce.net/blog/post/image-scaling-with-gdi-part-3-drawimage-and-the-settings-that-affect-it

Пересмотренный код должен выглядеть примерно так:

public static Image Scale(Image sourceImage, int destWidth, int destHeight)
{
    var toReturn = new Bitmap(destWidth, destHeight);

    using (var graphics = Graphics.FromImage(toReturn))
    using (var attributes = new ImageAttributes())
    {
        toReturn.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);

        attributes.SetWrapMode(WrapMode.TileFlipXY);

        graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        graphics.PixelOffsetMode = PixelOffsetMode.Half;
        graphics.CompositingMode = CompositingMode.SourceCopy;
        graphics.DrawImage(sourceImage, Rectangle.FromLTRB(0, 0, destWidth, destHeight), 0, 0, sourceImage.Width, sourceImage.Height, GraphicsUnit.Pixel, attributes);
    }

    return toReturn;
}