Сохранение прозрачных слоев ImgView32 в PNG

У меня проблемы с сохранением слоя ImgView32 в качестве прозрачного PNG. Я использую код из этого вопроса, чтобы сделать сохранение. Однако изображение сохраняется с белым фоном.

Вот как я инициализирую свой ImgView32, создаю на нем слой и затем рисую на нем линию:

procedure TputLine.FormCreate(Sender: TObject);
var
  P: TPoint;
  W, H: Single;
  imwidth: integer;
  imheight: integer;
begin
  imwidth := Iv1.Width;
  imheight := Iv1.Height;
  with iv1 do
  begin
    Selection := nil;
    Layers.Clear;
    Scale := 1;
    Scaled := True;
    Bitmap.DrawMode := dmTransparent;
    Bitmap.SetSize(imwidth, imheight);
    Bitmap.Canvas.Pen.Width := 4;
  end;
  BL := TBitmapLayer.Create(iv1.Layers);
  try
    BL.Bitmap.DrawMode := dmTransparent;
    BL.Bitmap.SetSize(imwidth,imheight);
    BL.Bitmap.Canvas.Pen.Width := penwidth;
    BL.Bitmap.Canvas.Pen.Color := pencolor;
    BL.Location := GR32.FloatRect(0, 0, imwidth, imheight);
    BL.Scaled := False;
  except
    BL.Free;
    raise;
  end;
end;

Итак, iv1 - это имя моего ImgView32. Затем я рисую линию, используя этот код:

var 
  bm32:TBitmapLayer;
  ...
begin
  bm32:=(iv1.Layers[0] as TBitmapLayer).Bitmap;
  bm32.canvas.pen.color:=clwhite;
  bm32.canvas.brush.color:=clwhite;
  bm32.canvas.rectangle(0,0,bm32.width-1, bm32.height-1);

  bm32.canvas.Pen.Color:=WinColor(ColorPickerGTK1.SelectedColor);
  bm32.canvas.brush.color:=clWhite;
  bm32.Canvas.Pen.Width:=3;
  bm32.Canvas.MoveTo(0,bm32.Height);
  bm32.Canvas.LineTo(0+150,bm32.Height-250);
end;

Если я использую clWhite32 для вышеуказанного кода при рисовании прямоугольника, то при сохранении PNG фон imgView становится черным... Так что я действительно не понимаю проблему.

Я делаю экономию так:

procedure TputLine.Button2Click(Sender: TObject);
var
  myLay:TBitmapLayer;
begin
  mylay := iv1.Layers.Items[0] as TBitmapLayer;
  SavePNGTransparentX(mylay.Bitmap);
end;

и актуальный код сохранения (по ссылке, описанной выше)

procedure TPutLine.SavePNGTransparentX(bm32:TBitmap32);
var
  Y: Integer;
  X: Integer;
  Png: TPortableNetworkGraphic32;

  function IsWhite(Color32: TColor32): Boolean;
  begin
    Result:= (TColor32Entry(Color32).B = 255) and
             (TColor32Entry(Color32).G = 255) and
             (TColor32Entry(Color32).R = 255);
  end;

begin
    bm32.ResetAlpha;
    for Y := 0 to bm32.Height-1 do
      for X := 0 to bm32.Width-1 do
      begin
        if IsWhite(bm32.Pixel[X, Y]) then
          bm32.Pixel[X,Y]:=Color32(255,255,255,0);
      end;
    Png:= TPortableNetworkGraphic32.Create;
    Png.Assign(bm32);
    Png.SaveToFile('C:\ThisShouldBeTransparent.png');
    Png.Free;
end;

Я не понимаю, почему это не сохраняет слой как прозрачный PNG. Как я могу это исправить? Любая идея приветствуется.

Вы можете повторить мою проблему, используя приведенный выше код. Он использует GR32_PNG и GR32_PortableNetworkGraphic. Вам нужно только добавить элемент управления TImgView32 в форму и добавить код, указанный выше.

1 ответ

Решение

Причина проблемы, кажется, двоякая.

  • Во-первых, когда вы звоните Png.Assign(bm32); в блоке GR32_PNGон пытается выяснить, в каком наименьшем формате будет храниться изображение. Если изображение имеет менее 256 различных цветов, оно создает формат палитры, и в зависимости от того, сколько цветов оно находит, глубина в битах может стать 1, 2, 4 или 8. Насколько мне известно, только изображения с TrueColor и Alpha могут быть сохранены в качестве переменной прозрачности png изображений.
  • Во-вторых, вы рисуете только одним цветом, что вызывает вышеуказанную проблему. Это, конечно, не ваша вина, IMO вышеупомянутый анализ должен быть в состоянии обойти.

TPortableNetworkGraphic32 класс имеет два свойства, BitDepth а также ColorType который контролирует формат png изображение, которое было бы полезно, если бы они были настраиваемыми! Попытка установить их в коде как:

Png.BitDepth := 8;
Png.ColorType := ctTrueColorAlpha;

приводит к исключениям

EPngError с сообщением "Битовая глубина не может быть указана напрямую!
EPngError с сообщением 'Color Type может быть еще не указан напрямую!

Из формулировки можно предположить дальнейшее развитие в будущем.

Лечение

Чтобы обойти описанный выше анализ изображения, вы можете изменить строку 459 в GR32_PNG.pas,

procedure TPortableNetworkGraphic32.AssignPropertiesFromBitmap32()
var
  ...
begin
  ...
   IsPalette := True; // <--- change to False
  ...

Это позаботится об анализе глубины цвета и предотвратит создание палитры, если менее 256 цветов. После этого взлома вы можете использовать SavePNGTransparentX() процедура для сохранения TBitmap32 к .png файл и сохранение прозрачности.

Затем есть еще одно изменение, которое вас может заинтересовать в отношении SavePNGTransparentX() процедура. Как вы уже видели, он требует, чтобы фон вашей поверхности рисования был белым, потому что он специально устанавливает Alpha канал на ноль для всех белых пикселей. TBitmapLayer однако инициализируется со всеми пикселями (RGBA) как всеми нулями, поэтому цветовые компоненты каждого пикселя составляют черный цвет (который не виден, потому что альфа-канал равен нулю). Поэтому вам необходимо заполнить слой для рисования белым, что делает его непрозрачным, что снова покрывает все нижние слои (под слоем для рисования).

Чтобы исправить это, вы можете

  • удалить инициализацию слоя рисования для всего белого
  • изменить IsWhite функция в IsBlack функция
  • изменить назначение прозрачных пикселей

Код станет

procedure TForm8.SavePNGTransparentX(bm32:TBitmap32);
var
  Y: Integer;
  X: Integer;
  Png: TPortableNetworkGraphic32;

  function IsBlack(Color32: TColor32): Boolean;
  begin
    Result:= (TColor32Entry(Color32).B = 0) and
             (TColor32Entry(Color32).G = 0) and
             (TColor32Entry(Color32).R = 0);
  end;

  function IsWhite(Color32: TColor32): Boolean;
  begin
    Result:= (TColor32Entry(Color32).B = 255) and
             (TColor32Entry(Color32).G = 255) and
             (TColor32Entry(Color32).R = 255);
  end;

begin
    bm32.ResetAlpha;
    for Y := 0 to bm32.Height-1 do
      for X := 0 to bm32.Width-1 do
      begin
//        if IsWhite(bm32.Pixel[X, Y]) then
//          bm32.Pixel[X,Y]:=Color32(255,255,255,  0);
        if IsBlack(bm32.Pixel[X, Y]) then
          bm32.Pixel[X,Y]:=Color32(  0,  0,  0,  0);
      end;

    Png:= TPortableNetworkGraphic32.Create;
    Png.Assign(bm32);
    Png.SaveToFile('C:\tmp\imgs\ThisShouldBeTransparent3.png');
    Png.Free;
end;

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

Вот несколько изображений. Сначала ImageView с битовой картой, загруженной на нижний слой:

Затем я рисую синий крест на BL слой (см. ваш код), используя в основном код в вашем Button1Click() но без белого прямоугольника.

Тогда я сохраняю BL слой к .png и просмотрите его с помощью Windows Explorer "Preview":

Другие вопросы по тегам