Cannot save transparent png lazarus / 39-Graphics ---------------------------------------------------------------------- dvhx · 2014-08-30 11:38 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 · 2014-08-30 16:12 Quote from: dusan.halicky on August 30, 2014, 01:38:07 pmThis works but it is retarded. What exactly do you mean by 'retarded'? ---------------------------------------------------------------------- engkin · 2014-08-30 16:52 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 · 2014-08-30 17:25 Quote from: dusan.halicky on August 30, 2014, 01:38:07 pmI 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 · 2014-08-30 19:06 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 · 2014-09-10 07:13 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 · 2014-09-10 17:28 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. ---------------------------------------------------------------------- engkin · 2014-09-10 22:05 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 · 2014-09-10 22:32 Quote from: User137 on September 10, 2014, 07:28:09 pmBasically 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 · 2014-09-10 23:22 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.