Recent

Author Topic: Cannot save transparent png  (Read 7304 times)

dvhx

  • New Member
  • *
  • Posts: 17
Cannot save transparent png
« on: August 30, 2014, 01:38:07 pm »
Hi

I am trying to save transparent png image (I want to set certain pixels fully transparent). In the atteched demo, there are 4 different versions I tried, nothing work. 

Code: [Select]
procedure TForm1.Button1Click(Sender: TObject);
var
  img: TLazIntfImage;
  png: TPortableNetworkGraphic;
  c: TFPColor = (Red: $FFFF; Green: $0000; Blue: $0000; Alpha: $7777);
begin
  // this is attempt to save png image with some transparent pixels
  // uses IntfGraphics, Graphics, FPimage

  // version 1 - not working, output is black
  img := TLazIntfImage.Create(100, 100);
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;
  img.SaveToFile('v1.png');
  img.Free;

  // version 2 - various attempts to make transparency work, output is funchsia
  png := TPortableNetworkGraphic.Create;
  png.Width := 100;
  png.Height := 100;
  png.Transparent := True;
  png.TransparentMode := tmFixed;
  png.TransparentColor := clFuchsia;
  png.Canvas.Brush.Color := clFuchsia;
  png.Canvas.FillRect(0, 0, 100, 100);
  png.Masked := True;
  png.Mask(clFuchsia);
  png.SaveToFile('v2.png');

  // version 3 - using TLazIntfImage, output is 4 pixels on black
  img := png.CreateIntfImage;
  if img.HasTransparency then
    ShowMessage('has transparency = ' + BoolToStr(img.HasTransparency, True)); // sais false
  img.FillPixels(colTransparent);
  img.Colors[10, 10] := colRed;
  img.Colors[11, 10] := colGreen;
  img.Colors[12, 10] := colBlue;
  img.Colors[14, 10] := colTransparent; // pixel [14,10] should be transparent
  img.Colors[15, 10] := c;              // pixel [15,10] should be 50% transparent red
  img.SaveToFile('v3.png');
  png.LoadFromIntfImage(img);
  png.SaveToFile('v3b.png');

  // version 4 - another attempt using mask, not working
  img.Masked[20, 10] := True;
  img.Masked[21, 10] := False;
  img.Mask(colRed, False);
  img.SaveToFile('v4.png');
  png.LoadFromIntfImage(img);
  png.SaveToFile('v4b.png');

  img.Free;
  png.Free;
end;

My current workaround is that I created fully transparent png image of size 1000x1000 in GIMP and when I want to save transparent png I shrink it to desired size and then only copy pixels from source bitmap I want to be opaque, the rest will remain transparent:

Code: [Select]
png.LoadFromFile('transparent.png');
png.Width := 32;
png.Height := 32;
png.Pixels[10,10] := clRed;
png.SaveToFile('new.png');

This works but it is retarded. Thanks for any advice on how to do it properly. Thanks.

minesadorada

  • Sr. Member
  • ****
  • Posts: 452
  • Retired
Re: Cannot save transparent png
« Reply #1 on: August 30, 2014, 06:12:58 pm »
This works but it is retarded.

What exactly do you mean by 'retarded'?
GPL Apps: Health MonitorRetro Ski Run
OnlinePackageManager Components: LazAutoUpdate, LongTimer, PoweredBy, ScrollText, PlaySound, CryptINI

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Cannot save transparent png
« Reply #2 on: August 30, 2014, 06:52:52 pm »
I tracked your 1st method. You filled it with red color and it turned out black. Fixed it this way:
Code: [Select]
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);

Then it crashed because there was no memory allocated, fixed that too:
Code: [Select]
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;

Then the produced image was red with two pixels (green and black). It did not have an alpha channel. Fixed that by using your 3rd method:
Code: [Select]
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;

  png := TPortableNetworkGraphic.Create;
  png.LoadFromIntfImage(img);
  png.SaveToFile('v1-3b.png');

Maybe not the best way, I'm not sure.

skalogryz

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 2770
    • havefunsoft.com
Re: Cannot save transparent png
« Reply #3 on: August 30, 2014, 07:25:11 pm »
I am trying to save transparent png image (I want to set certain pixels fully transparent). In the atteched demo, there are 4 different versions I tried, nothing work. 
actually, I've experienced exactly the same problem.
My PNG consisted of non transparent and fully transparent pixels (alpha channel is either $FF or $00).
Whenever I saved it, it looses all the transparency information.

But, if I create pixels non-fully transparent (i.e. use $FE or $01) - works like a charm.
(I'm using ScanLine method to fill the PNG)

I presume, there's a bug in the saving code, that tries to be smart and detect a type of PNG it should save.
Didn't have enough time time to investigate.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Cannot save transparent png
« Reply #4 on: August 30, 2014, 09:06:45 pm »
img.SaveToFile chooses an image writer based on the extension. In this case the writer is TFPWriterPNG.

TFPWriterPNG by default does not generate an alpha channel. Its DetermineHeader procedure adds an alpha channel based on FUseAlpha:
Code: [Select]
procedure TFPWriterPNG.DetermineHeader (var AHeader : THeaderChunk);
...
    if FUseAlpha then
      c := CountAlphas
    else
      c := 0;
..

Using the overloaded version of img.SaveToFile:
Code: [Select]
var
..
  pngWriter: TLazWriterPNG;
..
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;

  pngWriter := TLazWriterPNG.create;
  pngWriter.UseAlpha := true; //<----- needed to get an alpha channel
  img.SaveToFile('v0.png', pngWriter);
  pngWriter.Free;
handles transparency correctly.

dvhx

  • New Member
  • *
  • Posts: 17
Re: Cannot save transparent png
« Reply #5 on: September 10, 2014, 09:13:28 am »
Thanks for replies but proposed solutions are not complete as the black color (colBlack) for some reason will be transparent in saved png:

Code: [Select]
  // version 5 - not working because black will be transparent
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;
  img.Colors[12, 10] := colBlack; // this will be transparent
  png := TPortableNetworkGraphic.Create;
  png.LoadFromIntfImage(img);
  png.SaveToFile('v5.png');

  // version 6 - UseAlpha:=true, not working because black will be transparent
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;
  img.Colors[12, 10] := colBlack; // this will be transparent
  pngWriter := TLazWriterPNG.create;
  pngWriter.UseAlpha := true; // <----- needed to get an alpha channel
  img.SaveToFile('v6.png', pngWriter);
  pngWriter.Free;

  // version 7 - workaround using almost black, works, but well, it's not black color
  img := TLazIntfImage.Create(100, 100, [riqfRGB, riqfAlpha]);
  img.CreateData;
  img.FillPixels(colRed);
  img.Colors[10, 10] := colGreen;
  img.Colors[11, 10] := colTransparent;
  img.Colors[12, 12] := FPColor(0,0,0,$FFFF); // this is real black, will be transparent
  img.Colors[13, 13] := FPColor(1,1,1,$FFFF); // this is almost black, will be transparent, because FPColor use 16 bit per channel, but png only 8, so $0001 is reduced to $00
  img.Colors[14, 14] := FPColor($00FF,$00FF,$00FF,$FFFF); // this is almost black, will be transparent, because FPColor use 16 bit per channel, but png only 8, so $00FF is reduced to $00
  img.Colors[15, 15] := FPColor($0100,$0100,$0100,$FFFF); // this is almost black, will be almost black, $01FF will be reduced to $01 which will become RGB(1,1,1)
  pngWriter := TLazWriterPNG.create;
  pngWriter.UseAlpha := true;
  img.SaveToFile('v7.png', pngWriter);
  pngWriter.Free;

Any ideas on how to make it work with black color too?

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Cannot save transparent png
« Reply #6 on: September 10, 2014, 07:28:09 pm »
I don't know who wrote it, but the code and indenting picks on the eye.

Here the "end;" refers to previous begin... not to this "if"
Code: [Select]
            if FtransparentColor.alpha < a then
              FtransparentColor := c;
            end;

Then he is using simple while-loops with number counter down, that could be done with a for-loop.

Anyway, for what i can see function should result:
1 if picture is non-transparent
2 if there are some pixels with full transparency, like true/false transparency
3 if there are some semi-transparent pixels

I don't see any blatant error. Can you debug if your picture uses palette or not?

Oh, obvious hack to get it return 3 is to put 1 pixel with alpha value anything between 1..254

edit: I was observing AHeader.ColorType value, which will remain 0 when c=2 (the whole header is fillchar()'d to 0 before calling this function). And then +2 if not grayscale. Basically if it's not WordSized, image format ends up
Code: [Select]
              FFmtColor := @ColorDataColorB;
              FByteWidth := 3;
              //CFmt := cfBGR24;

... which again leads on FUsetRNS that is supposed to handle the transparency from there.
« Last Edit: September 10, 2014, 07:57:00 pm by User137 »

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Cannot save transparent png
« Reply #7 on: September 11, 2014, 12:05:42 am »
By mistake I removed my previous post instead of modifying it. Here it is again.
---------------------
The key (and the bug) is in TFPWriterPNG.DetermineHeader
It uses the first color that has Alpha = alphaTransparent as the SingleTransparentColor. I followed the path I want in reverse:

DetermineColorFormat should use ColorDataColorAW/ColorDataColorAB ===>
  colortype must be 6  ===>
    FGrayScale should be false,
    FUseAlpha should be true,
    and CountAlphas should return 3 ===> that did not happen

I forced CountAlphas to return 3 and the problem disappeared. I believe CountAlphas is buggy.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: Cannot save transparent png
« Reply #8 on: September 11, 2014, 12:32:25 am »
Basically if it's not WordSized, image format ends up
Code: [Select]
              FFmtColor := @ColorDataColorB;
              FByteWidth := 3;
              //CFmt := cfBGR24;

... which again leads on FUsetRNS that is supposed to handle the transparency from there.
Which is not the correct format. To support alpha, I believe it should be 6 for colored images:
Code: [Select]
        6 : if FWordSized then
              begin
              FFmtColor := @ColorDataColorAW;
              FByteWidth := 8;
              //CFmt := cfABGR64
              end
            else
              begin
              FFmtColor := @ColorDataColorAB;
              FByteWidth := 4;
              //CFmt := cfABGR32;
              end;
or 4 for gray scale images.

User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: Cannot save transparent png
« Reply #9 on: September 11, 2014, 01:22:38 am »
Point was that FUsetRNS is used only when CountAlphas return 2. It's different kind of transparency where pixel is either fully transparent or fully opaque. There could be bug in dealing with that system.

 

TinyPortal © 2005-2018