Потеря прозрачности в System.Drawing.Image при использовании ImageResizer для изменения размера

Для моего текущего приложения WPF мне нужно уменьшить масштаб некоторых System.Drawing.Image (объектов, которые я загружаю из файлов PNG (некоторые из них с прозрачным фоном). Я пробовал несколько подходов для изменения размера изображений, и все они работали нормально с точки зрения после этого изображение меньшего размера.Но, к сожалению, все они делают изображения менее прозрачными.

Моя последняя попытка состояла в том, чтобы использовать внешнюю библиотеку ImageResizer, чтобы выполнить работу, поскольку я ожидал, что она легко справится с этой проблемой, но у меня все еще есть та же проблема: исходное изображение отображается с прозрачным фоном; Отображаемое изображение с измененным размером отображается с черным фоном.

Вот мой код для использования библиотеки ImageResizer:

ImageResizer.Instructions inst = new ImageResizer.Instructions("width=" + newWidth.ToString() + ";height=" + newHeight.ToString() + ";format=png;mode=max");
ImageResizer.ImageJob job = new ImageResizer.ImageJob(originalImage, typeof(System.Drawing.Bitmap), inst);
job.Build();
return job.Result as System.Drawing.Image;

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

return originalImage.GetThumbnailImage(newWidth, newHeight, null, IntPtr.Zero); // Transparency gets lost

return new System.Drawing.Bitmap(originalImage, new System.Drawing.Size(newWidth, newHeight));  // Transparency gets lost

Любые идеи о том, что мне нужно сделать, чтобы сохранить прозрачность при изменении размера?

С Уважением

Ральф


person Ralf    schedule 16.09.2014    source источник


Ответы (2)


Несмотря на то, что вы используете WPF, вы работаете с System.Drawing.Image, поэтому вы можете сделать это:

    public static Bitmap ResizeImage(Image imgToResize, int newHeight)
    {
        int sourceWidth = imgToResize.Width;
        int sourceHeight = imgToResize.Height;

        float nPercentH = ((float)newHeight / (float)sourceHeight);

        int destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
        int destHeight = newHeight;

        Bitmap b = new Bitmap(destWidth, destHeight);
        using (Graphics g = Graphics.FromImage((Image)b))
        {
            g.SmoothingMode = SmoothingMode.HighQuality;
            g.InterpolationMode = InterpolationMode.HighQualityBicubic;
            g.PixelOffsetMode = PixelOffsetMode.HighQuality;
            g.DrawImage(imgToResize, 0, 0, destWidth, destHeight);
        }

        return b;
    }

После этого обязательно сохраните его с помощью кодировщика PNG:

    public static System.Drawing.Imaging.ImageCodecInfo GetEncoder(System.Drawing.Imaging.ImageFormat format)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
        foreach (ImageCodecInfo codec in codecs)
        {
            if (codec.FormatID == format.Guid)
            {
                return codec;
            }
        }
        return null;
    }

а потом

    codec = GetEncoder(ImageFormat.Png);
    newBitmap.Save(newFile, codec, null);

(Обратите внимание, что я использую стандартные библиотеки классов .Net, а не стороннюю библиотеку; надеюсь, это нормально.)

Обновить

Кстати, поскольку вы работаете в WPF, почему бы не использовать манипуляции с изображениями?

public static class BitmapHelper
{
    public static void SaveToPng(this BitmapSource bitmap, string fileName)
    {
        var encoder = new PngBitmapEncoder();
        SaveUsingEncoder(bitmap, fileName, encoder);
    }

    public static void SaveUsingEncoder(this BitmapSource bitmap, string fileName, BitmapEncoder encoder)
    {
        BitmapFrame frame = BitmapFrame.Create(bitmap);
        encoder.Frames.Add(frame);

        using (var stream = File.Create(fileName))
        {
            encoder.Save(stream);
        }
    }

    public static void ImageLoadResizeAndSave(string inFile, string outFile, int newPixelHeight)
    {
        BitmapImage image = new BitmapImage();
        image.BeginInit();
        image.UriSource = new Uri(inFile);
        image.EndInit();

        var newImage = BitmapHelper.ResizeImageToHeight(image, newPixelHeight);

        BitmapHelper.SaveToPng(newImage, outFile);
    }

    /// <summary>
    /// Resize the image to have the selected height, keeping the width proportionate.
    /// </summary>
    /// <param name="imgToResize"></param>
    /// <param name="newHeight"></param>
    /// <returns></returns>
    public static BitmapSource ResizeImageToHeight(BitmapSource imgToResize, int newPixelHeight)
    {
        double sourceWidth = imgToResize.PixelWidth;
        double sourceHeight = imgToResize.PixelHeight;

        var nPercentH = ((double)newPixelHeight / sourceHeight);

        double destWidth = Math.Max((int)Math.Round(sourceWidth * nPercentH), 1); // Just in case;
        double destHeight = newPixelHeight;

        var bitmap = new TransformedBitmap(imgToResize, new ScaleTransform(destWidth / imgToResize.PixelWidth, destHeight / imgToResize.PixelHeight));

        return bitmap;
    }
}

Может быть, вы теряете прозрачные пленки при преобразовании изображений из старого формата в формат WPF?

person dbc    schedule 16.09.2014
comment
Хотя вопрос был направлен на использование System.Drawing.Image, я думаю, что это все еще правильный ответ: простое использование класса BitmapImage, который кажется более подходящим для WPF, решило мою проблему с потерянной прозрачностью. Кроме того, я заметил, что BitmapImage имеет два полезных свойства, называемых DecodePixelWidth и DecodePixelHeight, которые еще больше упрощают изменение размера в моем случае. - person Ralf; 17.09.2014

ImageResizer всегда сохраняет прозрачность.

Вы теряете прозрачность во время кодирования (или отображения) изображения, что происходит после того, как вы перехватываете управление у ImageResizer. Вместо передачи typeof(System.Drawing.Bitmap) передайте выходной путь или выходной поток.

var i = new Instructions(){ Width = newWidth,Height = newHeight, OutputFormat= OutputFormat.Png, Mode= FitMode.Max};
new ImageJob(originalImage, "output.png", i).Build();

ImageResizer не может контролировать, как кодируется изображение, если вместо этого вы берете из него необработанное растровое изображение.


Вы не указали, как вы используете результаты, что очень важно. Если вы не записываете их на диск или в поток, а вместо этого отображаете их, значит, вы ищете проблему не в том месте. Вполне вероятно, что код, отвечающий за наложение результатов на поверхность дисплея, не обрабатывает изображение как 32-битное изображение и вместо этого игнорирует альфа-канал.

person Lilith River    schedule 16.09.2014
comment
Я полагаю, вы правы. Я удалил свой ответ, вероятно, это не решило бы проблему Ральфа. Кроме того, я вижу, что вы являетесь автором ImageResizer, так что вы, вероятно, знаете, о чем говорите :) - person Bedford; 16.09.2014
comment
Должен признать, что вы были правы в том, что прозрачность, скорее всего, была потеряна где-то до или после использования ImageResizer. Поэтому я попытался использовать MemoryStream для параметров source и dest, чтобы избежать использования System.Drawing.Bitmap перед окончательным отображением изображения. К сожалению, у меня это не сработало, так как Result остался пустым (Null). Каким-то образом я, должно быть, неправильно использовал ImageResizer. Однако я решил попробовать предложение dbc и использовал класс BitmapImage. Это решило проблему для меня, и ImageResizer больше не нужен. - person Ralf; 17.09.2014