TJpegImage: внутреннее растровое изображение не обновляется после применения сжатия JPEG - PullRequest
1 голос
/ 17 марта 2019

Я хочу преобразовать BMP в JPG, сжать этот JPG и вернуть обратно сжатый JPG в исходный BMP.Однако, это не назначит сжатое изображение BMP.Я всегда получаю оригинальное изображение в BMP.

Код ниже.Чтобы увидеть сжатие, я установил CompressionQuality = 1. Это разрушит изображение.

function CompressBmp2RAM(InOutBMP: TBitmap): Integer;
VAR
   Stream: TMemoryStream;
   Jpg: TJPEGImage;
begin
 Jpg:= TJPEGImage.Create;
 Stream:= TMemoryStream.Create;
 TRY
   Jpg.Assign(InOutBMP);
   Jpg.CompressionQuality:= 1;  // highest compression, lowest quality 
   Jpg.Compress;
   Jpg.SaveToStream(Stream);
   //Stream.SaveToFile('c:\out.jpg'); <---- this gives the correct (heavily compressed) image
   Result:= tmpQStream.Size;
   InOutBMP.Assign(Jpg);
   //InOutBMP.SaveToFile('c:\out.bmp'); <---- this gives the uncompressed image
 FINALLY
   FreeAndNil(Stream);
   FreeAndNil(Jpg);
 END;
end;

Я нашел решение, но я все еще хочу знать, почему InOutBMP.Assign (Jpg) в приведенном выше коде не будет работать.

   ...
   Stream.Position:= 0;
   Jpg.LoadFromStream(Stream);
   InOutBMP.Assign(Jpg);
   ...

Мне кажется, это ошибка.JPG не знает, что данные были повторно сжаты, поэтому внутреннее растровое изображение никогда не обновляется.Должен быть какой-то внутренний флаг «DirtyData» (или «HasChanged»).

Итак, что является официальным решением для этого?Наличие JPG для перезагрузки ITS OWN DATA из внешнего источника данных (потока) кажется скорее хаком / временным исправлением ошибки.

PS: Jpg.DIBNeeded не поможет.

1 Ответ

4 голосов
/ 18 марта 2019

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

TJpegImage хранит внутреннюю TBitmap для представления. Когда вы вызываете DIBNeeded, это растровое изображение создается на основе данных изображения Jpeg.

GetBitmap (частная функция, выполняющая работу для DIBNeeded) сначала проверит, назначено ли растровое изображение, и не будет повторять процесс, если он есть. Так что простой вызов DIBNeeded в вашем случае не сработает, так как вы гарантированно уже имеете этот кэшированный битовый массив.

Метод FreeBitmap освободит внутреннее растровое изображение, после чего вызов DIBNeeded снова создаст новое. Поэтому я думаю, что вам нужна следующая последовательность:

Jpg.Compress; // Make sure the Jpeg compressed image data is up to date
Jpg.FreeBitmap; // Clear the internal cached bitmap
Jpg.DIBNeeded; // Optional, get a new bitmap. Will happen when you assign to TBitmap.

Я также упоминал JpegNeeded и раньше, но это будет аналогично DIBNeeded: проверьте, есть ли данные, если нет, позвоните Compress. Поэтому вам нужно вызвать Compress, как и вы, чтобы вызвать это сжатие.

PS: TBitmap (и форматы файлов, аналогичные bmp), на самом деле не знает этот тип сжатия, поэтому, присвоив его обратно растровому изображению, вы уменьшите изображение качество , но не изображение размер . Некоторые форматы растровых изображений, включая PNG, сжимаются с использованием (среди прочего) кодирования длины прогона (RLE), что означает что-то вроде расходования всего четырех байтов за выражение «А теперь, 54 раза больше пикселя этого цвета!» . Этот тип сжатия не очень хорошо работает на изображениях с большим количеством артефактов в формате jpeg (зернистый / размытый побочный эффект сжатия), поэтому PNG-версия сжатого Jpg может быть больше, чем оригинальная PNG-версия, даже если качество оригинала также лучше. Это особенно верно для изображений с большими областями одного цвета, таких как снимки экрана и некоторые иллюстрации.

...