Delphi, GR32 + PngObject: преобразование в Bitmap32 не работает должным образом - PullRequest
5 голосов
/ 21 ноября 2011

Я использую GR32 для рисования нескольких полупрозрачных изображений PNG. До сих пор я использовал следующий метод:

  png:= TPNGObject.Create;
  png.LoadFromFile(...);
  PaintBox321.Buffer.Canvas.Draw(120, 20, png);

однако я хотел перейти на метод, предложенный на веб-сайте GR32 (http://graphics32.org/wiki/FAQ/ImageFormatRelated):

  tmp:= TBitmap32.Create;
  LoadPNGintoBitmap32(tmp, ..., foo);
  tmp.DrawMode:= dmBlend;
  PaintBox321.Buffer.Draw(Rect(20, 20, 20+ tmp.Width, 20+tmp.Height),
   tmp.ClipRect, tmp);

Хотя первый метод работает отлично, второй - который должен дать тот же результат - вызывает очень странную проблему с альфа-каналом, см. Изображение (которое также показывает сравнение с тем же изображением, «упорядоченным» в Paint.NET - оба фон и значок были открыты на слоях редактора). Изображение показывает, что Bitmap32 загружен или нарисован неправильно. Любые советы?

Problem with TBitmap32 alpha channel

- добавлено 22 ноября

Я обнаружил, что речь идет не о рисовании, а о загрузке PNG в BMP32. Сохранение из BMP32 в PNG приводит к появлению неправильного, «отбеленного» (слева) изображения PNG.

Ответы [ 2 ]

9 голосов
/ 21 ноября 2011

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

Сначала прозрачность:

Это код из оригинального LoadPNGintoBitmap32, критические части отмечены комментариями:

 PNGObject := TPngObject.Create;
 PNGObject.LoadFromStream(srcStream);

 destBitmap.Assign(PNGObject);  // <--- paint to destBitmap's canvas with transparency (!)
 destBitmap.ResetAlpha;         

 case PNGObject.TransparencyMode of  // <--- the following code sets the transparency again for the TBitmap32
 { ... }

Внутренне destBitmap.Assign делает то же, что и вы в предыдущем подходе:изображение PNG рисует себя на холсте.Эта операция учитывает альфа-канал PNG.Но в этом нет необходимости, поскольку альфа-канал назначается пикселям TBitmap32 на втором шаге!

Теперь измените код следующим образом, критические части снова помечаются комментариями:

 PNGObject := TPngObject.Create;
 PNGObject.LoadFromStream(srcStream);

 PNGObject.RemoveTransparency;  // <--- paint PNG without any transparency...
 destBitmap.Assign(PNGObject);  // <--- ...here
 destBitmap.ResetAlpha;

 srcStream.Position:=0;
 PNGObject.LoadFromStream(srcStream); // <--- read the image again to get the alpha channel back

 case PNGObject.TransparencyMode of   // <--- this is ok now, the alpha channel now only exists in the TBitmap32
 { ... }

Приведенное выше решение неэффективно, поскольку оно считывает изображение дважды.Но это показывает, почему ваш второй подход создает более прозрачное изображение.

И для серости: в исходном коде есть еще одна проблема: destBitmap.Assign сначала заполняет фон clWhite32, затем рисует изображениепрозрачно на это.А потом LoadPNGintoBitmap32 приходит и добавляет еще один слой прозрачности поверх него.

1 голос
/ 29 октября 2016

Проблема может заключаться в том, что PNG неправильно преобразован в TBitmap32, теряя информацию о прозрачности при передаче.Это частый случай с PNG-изображениями с палитрой.В противном случае вам не пришлось бы использовать «Bitmap.DrawMode: = dmTransparent» и «OuterColor».Если бы информация о прозрачности из PNG была бы правильно передана в TBitmpa32, DrawMode: = dmBlend сработал бы, не устанавливая OuterColor.

Самое важное, как вы загрузили PNG в TBitmap32.TPngImage из модуля Vcl.Imaging.pngimage (реализованный в Delphi XE2 и более поздних версиях) может рисовать прозрачно на растровых изображениях, сохраняя то, что было на этих растровых изображениях, комбинируя цвета с использованием альфа-слоя PNG и т. Д., Но не позволяет легко преобразовывать различныеформаты прозрачности PNG (включая палитру) в альфа-компонент каждого пикселя TBitmap32.После того, как TPngImage нарисовал изображение, вы получаете объединенный RGB для каждого пикселя, но альфа-компонент не переносится в целевое растровое изображение.

Доступны вспомогательные процедуры, которые пытаются загрузить PNG в TBitmap32 с прозрачностью, но у них есть недостатки:

(1) «LoadPNGintoBitmap32» из http://graphics32.org/wiki/FAQ/ImageFormatRelated - он применяет прозрачность дважды, поэтому изображения с альфа-значениями, отличными от 0 или 255, будут выглядеть иначе, чем в других программах.(наиболее заметно на полупрозрачных изображениях со стеклянными эффектами).Этот код сначала применяет альфа к RGB, а затем устанавливает альфа как отдельный слой, поэтому при рисовании альфа будет применена снова.Вы можете найти более подробную информацию по этому вопросу здесь: Delphi, GR32 + PngObject: преобразование в Bitmap32 не работает должным образом .Кроме того, он неправильно конвертирует прозрачность из палитровых изображений в альфа-слой TBitmap32.Они вручную устанавливают альфа-прозрачность для пикселей определенного цвета выходного растрового изображения (отрисованного в RGB), делая это до рендеринга в RGB, поэтому фактическая прозрачность теряется, как на образце изображения, когда все белые пиксели прозрачны.

(2) «LoadBitmap32FromPNG» из библиотеки gr32ex: https://code.google.com/archive/p/gr32ex/ - немного отличающаяся реализация того же алгоритма, что и (1), и имеет те же проблемы, что и (1).

Итак, решения следующие:

  1. Не использовать TBitmap32;используйте Vcl.Imaging.pngimage.TPngImage для рисования непосредственно на целевом растровом изображении (экране и т. д.) - это наиболее совместимый способ, который корректно работает с различными форматами PNG.
  2. Использование вспомогательной маршрутизации для передачи информации о прозрачности изVcl.Imaging.pngimage.TPngImage to TBitmap32.
  3. Используйте библиотеку GR32 PNG, которая может загружать PNG-файл в TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/, так как теперь у вас есть вся информация по этому вопросу, вы можете получитьправильное решение для вас.

Как загрузить альфа-слой за один проход

Генрих Ульбрихт сделал хорошее предложение удалить слой прозрачности перед нанесением краски, а затем снова прочитать изображение.Чтобы избежать двойной загрузки изображения, вы можете сохранить альфа-слой перед вызовом PNGObject.RemoveTransparency.Вот код, который правильно применяет альфа-слой и загружает изображение только один раз.К сожалению, он не работает с палитры изображений.Если вы знаете, как правильно заполнить альфа-слой TBitmap32 из любого изображения с палитрой, без эффектов, описанных в Прозрачный Png to TBitmap32 , пожалуйста, дайте мне знать.

procedure LoadPNGintoBitmap32(DstBitmap: TBitmap32; SrcStream: TStream; out AlphaChannelUsed: Boolean);
var
  PNGObject: TPngImage;
  PixelPtr: PColor32;
  AlphaPtr: PByte;
  SaveAlpha: PByte;
  I, AlphaSize: Integer;
begin
  AlphaChannelUsed := False;
  PNGObject := TPngImage.Create;
  try
    PNGObject.LoadFromStream(SrcStream);
    AlphaPtr := PByte(PNGObject.AlphaScanline[0]);
    if Assigned(AlphaPtr) then
    begin
      AlphaSize := PNGObject.Width * PNGObject.Height;
      if AlphaSize <= 0 then raise Exception.Create('PNG files with zero dimensions are not supported to be loaded to TBitmap32');
      GetMem(SaveAlpha, AlphaSize);
      try
        Move(AlphaPtr^, SaveAlpha^, AlphaSize);
        PNGObject.RemoveTransparency;
        DstBitmap.Assign(PNGObject);
        DstBitmap.ResetAlpha;
        PixelPtr := PColor32(@DstBitmap.Bits[0]);
        AlphaPtr := SaveAlpha;
        for I := 0 to AlphaSize-1 do
        begin
          PixelPtr^ := (PixelPtr^ and $00FFFFFF) or (TColor32(AlphaPtr^) shl 24);
          Inc(PixelPtr);
          Inc(AlphaPtr);
        end;
      finally
        FreeMem(SaveAlpha, AlphaSize);
      end;
      AlphaChannelUsed := True;
    end else
    if PNGObject.TransparencyMode = ptmNone then
    begin
      DstBitmap.Assign(PNGObject);
    end else
    begin
      raise Exception.Create('Paletted PNG images are not supported in LoadPNGintoBitmap32, transparency cannot be stored to TBitmap32');
    end;
  finally
    FreeAndNil(PNGObject);
  end;
end;
...