Вращение без потерь изображения JPG с Image.Save и EncoderParameters завершается неудачей - PullRequest
6 голосов
/ 12 марта 2019

Я должен вращать изображения JPG без потерь в .net (90 ° | 180 ° | 270 °).В следующих статьях показано, как это сделать:

Примеры кажутся довольно простыми;однако мне не повезло заставить это работать.Мои исходные данные поступают в виде массива (различные файлы JPG, с камеры из Интернета и т. Д.), Поэтому я хочу вернуть повернутые изображения также в виде байтового массива.Вот (упрощенный) код:

Image image;
using (var ms = new MemoryStream(originalImageData)) {
    image = System.Drawing.Image.FromStream(ms);
}

// If I don't copy the image into a new bitmap, every try to save the image fails with a general GDI+ exception. This seems to be another bug of GDI+.
var bmp = new Bitmap(image);    

// Creating the parameters for saving
var encParameters = new EncoderParameters(1);            
encParameters.Param[0] = new EncoderParameter(Encoder.Transformation, (long)EncoderValue.TransformRotate90);              
using (var ms = new MemoryStream()) {                
    // Now saving the image, what fails always with an ArgumentException from GDI+
    // There is no difference, if I try to save to a file or to a stream.
    bmp.Save(ms, GetJpgEncoderInfo(), encParameters);
    return ms.ToArray();
}

Я всегда получаю ArgumentException из GDI + без какой-либо полезной информации:

Операция завершилась с последним исключением [ArgumentException].
Источник: System.Drawing

Я перепробовал очень много вещей, но так и не заработал.Основной код кажется правильным, так как, если я изменю EncoderParameter на Encoder.Quality, код будет работать нормально:

encParameters.Param[0] = new EncoderParameter(Encoder.Quality, 50L);

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

https://social.msdn.microsoft.com/Forums/vstudio/en-US/de74ec2e-643d-41c7-9d04-254642a9775c/imagesave-quotparameter-is-not-validquot-in-windows-7?forum=netfxbcl

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

У кого-нибудь есть подсказка, что я делаю не так, или, если это действительно ошибка, как мне ее обойти?

Обратите внимание, что у меня естьсделать преобразование без потерь (насколько позволяет размер в пикселях).Поэтому Image.RotateFlip не вариант.

Версия Windows - 10.0.17763, .Net - 4.7.2

1 Ответ

4 голосов
/ 22 марта 2019
using (var ms = new MemoryStream(originalImageData)) {
    image = System.Drawing.Image.FromStream(ms);
}

Это корень всего зла и первая попытка провалилась.Это нарушает правило, указанное в разделе «Примечания» документации , . Вы должны держать поток открытым в течение всего времени жизни образа .Нарушение правила не вызывает постоянных проблем, обратите внимание, что вызов Save () не удался, но конструктор Bitmap (изображения) завершился успешно.GDI + несколько ленив, у вас есть очень хорошее доказательство того, что кодек JPEG действительно пытается избежать повторного сжатия изображения.Но это не может работать, необработанные данные в потоке больше не доступны, так как поток был удален.Исключение является паршивым, потому что нативный код GDI + не знает bean-компонентов о MemoryStream.Исправить это просто, просто переместите закрывающую скобку} после вызова Save ().

Оттуда все пошло не так, как обычно, вызванное новым объектом bmp.Ни image, ни bmp объекты не располагаются.Это быстро занимает адресное пространство, сборщик мусора не может запускаться достаточно часто, чтобы избежать проблем, поскольку данные для растрового изображения хранятся в неуправляемой памяти.Теперь вызов Save () завершается ошибкой, когда MemoryStream больше не может выделять память.

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

Чтобы решить проблемы, избавьтесь от обходного пути для растрового изображения, поскольку это вызывает повторное сжатие JPEG.Технически вы все еще можете столкнуться с проблемами, когда изображения большие, страдающие от фрагментации адресного пространства в 32-битном процессе.Следите за счетчиком памяти «Private bytes» для процесса, в идеале он должен быть ниже гигабайта.Если нет, используйте «Проект»> «Свойства»> вкладку «Сборка» и снимите флажок «Предпочитать 32-разрядную версию».

...