Эффективность изменения изображения: System.Drawing vs System.Windows.Media

У меня есть ситуация, когда мне нужно изменить размер большого количества изображений. Эти изображения хранятся как .jpg файлы в файловой системе в настоящее время, но я ожидаю, что просто будет байтом [] в памяти позже в проекте. Размер исходного изображения является переменным, но выход должен быть 3 разных заранее определенных размера. Соотношение сторон должно быть сохранено, добавив исходное изображение в пробел (то есть, очень высокое изображение будет изменено в соответствии с размером квадратного целевого изображения с большими областями белого слева и справа).

Сначала я построил проект, ориентированный на .NET 2.0, и используя классы System.Drawing для выполнения загрузки/изменения размера/сохранения. Соответствующий код включает:

original = Image.FromFile(inputFile); //NOTE: Reused for each of the 3 target sizes
Bitmap resized = new Bitmap(size, size);
//Draw the image to a new image of the intended size
Graphics g = Graphics.FromImage(resized);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.Clear(Color.White);
g.DrawImage(original, center - width / 2f, center - height / 2f, width, height);
g.Dispose();
//Save the new image to the output path
resized.Save(outputFile, ImageFormat.Jpeg);

Я хотел перенести этот проект на .NET 3.5, поэтому попробовал использовать классы System.Windows.Media для выполнения той же функции. Я получил его работу, однако производительность ужасна; время обработки изображения составляет примерно 50 раз. Подавляющее большинство времени тратится на загрузку изображения. Соответствующий код включает:

BitmapImage original = new BitmapImage(); //Again, reused for each of the 3 target sizes
original.BeginInit();
original.StreamSource = new MemoryStream(imageData); //imageData is a byte[] of the data loaded from a FileStream
original.CreateOptions = BitmapCreateOptions.None;
original.CacheOption = BitmapCacheOption.Default;
original.EndInit(); //Here where the vast majority of the time is spent
original.Freeze();

// Target Rect for the resize operation
Rect rect = new Rect(center - width / 2d, center - height / 2d, width, height);

// Create a DrawingVisual/Context to render with
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
    drawingContext.DrawImage(original, rect);
}

// Use RenderTargetBitmap to resize the original image
RenderTargetBitmap resizedImage = new RenderTargetBitmap(
    size, size,                         // Resized dimensions
    96, 96,                             // Default DPI values
    PixelFormats.Default);              // Default pixel format
resizedImage.Render(drawingVisual);

// Encode the image using the original format and save the modified image
SaveImageData(resizedImage, outputFile);

Я делаю что-то не так здесь, чтобы уделить столько времени? Я пробовал просто использовать конструктор в BitmapImage, который принимает URI, такая же проблема с производительностью. Кто-нибудь сделал что-нибудь подобное раньше, узнайте, есть ли более эффективный способ сделать это? Или мне еще нужно будет использовать System.Drawing? Спасибо!

Ответ 1

И после ввода всего этого мне пришло в голову, что я могу загрузить символы из MS для классов System.Windows.Media и проделать туда, где он был медленным. Немедленно нашел причину и решение. Входные изображения были сохранены с помощью цветового профиля, и он пытался загрузить этот цветовой профиль (из файловой системы) каждого изображения. Переключившись с BitmapCreateOptions.None на BitmapCreateOptions.IgnoreColorProfile в приведенном выше коде, он больше не делает этого и выполняет так же быстро, как это делал System.Drawing.

Надеюсь, это поможет кому-то еще, кто сталкивается с этой проблемой!

Ответ 2

Вы, кажется, делаете это с трудом. Вы можете позволить WPF выполнить эту работу для вас, просто установив DecodePixelHeight и DecodePixelWidth. Это приведет к изменению размера во время загрузки изображения:

BitmapImage resizedImage = new BitmapImage
{
  StreamSource = new MemoryStream(imageData),
  CreateOptions = BitmapCreateOptions.IgnoreColorProfile,
  DecodePixelHeight = height,
  DecodePixelWidth = width,
}
resizedImage.BeginInit();  // Needed only so we can call EndInit()
resizedImage.EndInit();    // This does the actual loading and resizing

imageSaveImageData(resizedImage, outputFile);

Я также включил решение IgnoreColorProfile, которое вы нашли в моем коде.

Обновление. Я перечитал ваш вопрос и понял, почему вы используете DrawingVisual, так это то, что вам нужно пробелы вокруг вашего изображения, чтобы сделать его квадратным. DecodePixelHeight и DecodePixelWidth не достигнут этой цели, поэтому мое решение не отвечает на ваш вопрос.

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

Ответ 3

Я думаю, что это на странице System.Drawing в MSDN могло бы быть актуальным:

Пространство имен System.Drawing обеспечивает доступ к функциональным возможностям GDI + базовой графики. Более расширенные функции предоставляются в пространствах имен System.Drawing.Drawing2D, System.Drawing.Imaging и System.Drawing.Text. Класс Graphics предоставляет методы для рисования на устройстве отображения. Классы, такие как Rectangle и Point, инкапсулируют примитивы GDI+. Класс Pen используется для рисования линий и кривых, а классы, полученные из абстрактного класса Brush, используются для заполнения интерьеров фигур.

Используя System.Drawing, вы ближе к фактической базовой графической функциональности, чем если бы вы проходили через System.Windows.Media, который:

Определяет объекты, которые позволяют интегрировать мультимедийные материалы, включая чертежи, текст и аудио/видеоконтент в приложениях Windows Presentation Foundation (WPF).

System.Drawing по-прежнему поддерживается, поэтому я буду придерживаться этого.

Ответ 4

Я нашел интересную ситуацию в вашем коде. Удалите using из следующей строки:

using(DrawingContext drawingContext = drawingVisual.RenderOpen())

Я не уверен, почему это ускоряет код, но вы можете попробовать.