I load a png file into a TPNGImage, and then display it in a TImage. No problem. Then I create a TBitmap and assign it to the TPNGImage, and display it in a TImage. No problem.
Then I create a second TPNGImage and assign it to the TBitmap. In this case if I display it in a TImage, it has lost it's transparency. If I set the TBitmap.Transparent to True, before assigning to the PNGImage, the PNGImage is mostly transparent, but there is a small area where the transparency was lost, showing in black.
var
Bmp: TBitmap;
PngImage: TPNGImage;
PngImage2: TPNGImage;
begin
PngImage := TPNGImage.Create;
try
PngImage.LoadFromFile(FILE_NAME);
Image1.Picture.Assign(PngImage);
Bmp := TBitmap.Create;
try
Bmp.Assign(PngImage);
Image2.Picture.Assign(Bmp);
PngImage2 := TPNGImage.Create;
try
//Bmp.Transparent := True;
PngImage2.Assign(Bmp);
Image3.Picture.Assign(PngImage2);
finally
PngImage2.Free;
end;
finally
Bmp.Free;
end;
finally
PngImage.Free;
end;
end;
Result without setting Bitmap.Transparent to True:
Result when I set Bitmap.Transparent to True: There is a small bit of black in the Image
How can I assign the PNGImage to the Bitmap without losing any transparency?
I can only speculate... But PNGs support partial transparency, and 24-bits bitmaps don't. And since the leftover "black" pixels aren't black (they are $000101), I suspect those pixels are semi-transparent in the original png. That, or some antialising effect was applied when converting to bitmap. But I believe semi-transparency is more likely.
I never really worked with 32 bits bitmap, but maybe they could be used to preserve the transparency. (They do have an alpha channel...). But I suspect it might be more tricky than just Bitmap.Assign.
I can confirm what #SertacAkyuz said in the comments. There is no problem with:
Bmp.Assign(PngImage);
The result is a 32bit image that preserves the alpha channel. The problem is with
PngImage2.Assign(Bmp);
Where the alpha is lost.
I found this library to help with the conversion:
https://github.com/graphics32/GR32PNG
Related
After going through several other related questions I couldn't come up with a working code for this, so please spare the "duplicate question" tags.
Given a PNG image with either per-pixel alpha channel or single-color transparency, I need code to draw it onto a TBitmap32 which already contains an image (some drawing goes on before the PNG part). So let's say my TBitmap32 is 200x200, I do some drawing on it, then I want to ~insert a smaller transparent PNG image on top of its current content, transparently according to the PNG's alpha channel data or single-color alpha.
Uses pngimage, GR32;
procedure TForm1.Button1Click(Sender: TObject);
Var b: TBitmap;
b32: TBitmap32;
p: TPngImage;
begin
b := TBitmap.Create;
b32 := TBitmap32.Create;
p := TPngImage.Create;
// 50x50 PNG
p.LoadFromFile('z:\test2.png');
b.Width := 200;
b.Height := 200;
b32.Width := 200;
b32.Height := 200;
// some drawing happens on the b32~
// insert code here to draw the png onto the b32, on top of
// what's already drawn, and at specific coordinates i.e 10,10
/////////////////////////////
b32.DrawTo(b.Canvas.Handle,0,0);
Canvas.Draw(0,0,b);
p.Free;
b32.Free;
b.Free;
end;
Original PNG:
Results so far:
There are two ways of working with transparent PNG files:
Load them into intermediary TBitmap32 bitmaps and then manipulate these TBitmap32 bitmaps.
Use TPngImage.Draw (implemented in Vcl.Imaging.pngimage with Delphi XE2 and later) directly on target Canvas, as you have pointed out.
The second way is preferable when it comes to transparency, because the code that you may find to load a PNG into TBitmap32 may work incorrectly. Here are the two examples of the incorrect code that is used most frequently:
(1) “LoadPNGintoBitmap32” from http://graphics32.org/wiki/FAQ/ImageFormatRelated
- it applies the transparency twice, so the images with alpha values other than 0 or 255 will look differently than in other software (most noticeable on translucent images with glass effects). This code will first apply alpha to RGB and then sets alpha, so when you pain, alpha will be applied again. You can find more information on this issue here: Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected
. Besides that, it doesn't convert correctly transparency from paletted images into the alpha layer of TBitmap32, for example, all white pixels become transparent.
(2) “LoadBitmap32FromPNG” from gr32ex library: https://code.google.com/archive/p/gr32ex/
- a slightly different implementation of the same algorithm as (1), and has the same issues as (1).
If you still prefer using TBitmap32, make the following sequence of steps:
Make sure your code correctly converts PNG to TBitmap32.
Do not use TBitmap32 with transparent images to draw directly on HDC, Canvas or TBitmap. Use dmBlend and DrawTo or BlockTransfer() to draw on another TBitmap32. For example, to draw transparently on a TBitmap, create an intermediary cache TBitmap32:
Copy the image from TBitmap to the cache TBitmap32;
Apply your transparent image to the cache TBitmap32 using DrawTo or BlockTransfer(), avoid using Canvas or HDC to mix two images because they lose alpha layer information;
Copy the image back from the cache TBitmap32 to your TBitmap.
Problem
I am trying to copy 32x32 tiles from a TBitmap into a TPaintbox which is my map editor, but I cannot seem to get the transparency working correctly.
See the image below:
Note: For the purpose of the demo and testing, I have placed a TImage underneath the TPaintbox which would help see if the transparency is working or not.
As you can see, regular tiles draw correctly, but the tiles that should be transparent are drawn with a white background.
I am now using proper classes to manage my maps and tiles, and below is two ways I have tried drawing:
CopyRect:
procedure TMap.DrawTile(Tileset: TBitmap; MapX, MapY, TileX, TileY: Integer;
MapCanvas: TCanvas);
begin
if TileIsFree(MapX, MapY) then
begin
MapCanvas.CopyRect(
Rect(MapX, MapY, MapX + fTileWidth, MapY + fTileHeight),
Tileset.Canvas,
Rect(TileX, TileY, TileX + fTileWidth, TileY + fTileHeight));
end;
end;
BitBlt
procedure TMap.DrawTile(Tileset: TBitmap; MapX, MapY, TileX, TileY: Integer;
MapCanvas: TCanvas);
begin
if TileIsFree(MapX, MapY) then
begin
BitBlt(
MapCanvas.Handle,
MapX,
MapY,
fTileWidth,
fTileHeight,
Tileset.Canvas.Handle,
TileX,
TileY,
SRCCOPY);
end;
end;
I have tried using bitmap and png image formats for the tileset (left image on the screenshot). The only difference between bitmap and png is that CopyRect struggles to draw even a few tiles when it is a png, but BitBlt manages to draw without any obvious drawbacks.
Anyway, how do I copy/draw part of a TBitmap onto a TPaintbox without losing transparency, or in my case without also copying the white background?
Update 1
Following on from some of the comments below I have tried calling the AlphaBlend function but this still leaves undesirable results (note the blue colors around the transparent areas):
procedure TMap.DrawTile(Tileset: Graphics.TBitmap; MapX, MapY, TileX, TileY: Integer;
MapCanvas: TCanvas);
var
BlendFn: TBlendFunction;
begin
if TileIsFree(MapX, MapY) then
begin
BlendFn.BlendOp := AC_SRC_OVER;
BlendFn.BlendFlags := 0;
BlendFn.SourceConstantAlpha := 255;
BlendFn.AlphaFormat := AC_SRC_ALPHA;
AlphaBlend(
MapCanvas.Handle,
MapX,
MapY,
fTileWidth,
fTileHeight,
Tileset.Canvas.Handle,
TileX,
TileY,
fTileWidth,
fTileHeight,
BlendFn);
end;
end;
Thanks.
There are 3 popular ways to work with transparent bitmaps, first two use standard Delphi tools and the third one requires a third-party library:
If you use one of the two standard methods, don’t use BitBlt or CopyRect. Use the Draw method of the transparent image holder to draw on destination canvas.
Keep your transparent bitmaps in a TImageList and use TImageList.Draw to paint directly on destination canvas (don’t paint on intermediary bitmaps since you will lose transparency here). To add images to an image list at design time, right click and choose Image List Editor. Images in the list may be bitmaps, icons, PNG, GIF and JPEG images: any image type that TImage supports. ImageLists also support 32-bit format, so alpha blended bitmaps and PNG files work properly. You can also load images at run time. If your bitmaps are stored in a non-transparent form but there is a transparent color, you can use the TImageList.AddMasked(Bitmap: TBitmap; MaskColor: TColor) method. You can either pass the transparent color yourself in the second parameter or clDefault to let the imagelist take the bottom-left pixel's color.
Keep your images in PNG files or resources and load them into a Vcl.Imaging.pngimage.TpngImage, then call TpngImage.Draw to paint your PNG directly on destination canvas. As above, don’t paint on intermediary bitmaps since you will lose transparency here.
Use TBitmap32 from GR32, a third-party library. In this case, Do not use TBitmap32 with transparent images to draw directly on HDC, Canvas or TBitmap. Use dmBlend and DrawTo or BlockTransfer() to draw on another TBitmap32. For example, to draw transparently on a TBitmap, create an intermediary cache TBitmap32: (1) Copy the image from TBitmap to the cache TBitmap32; (2) Apply your transparent image to the cache TBitmap32 using DrawTo or BlockTransfer(), avoid using Canvas or HDC to mix two images because they lose alpha layer information; (3) Copy the image back from the cache TBitmap32 to your TBitmap.
I have a PNG of a watermark that makes use of transparency that I load at run time from a .res file. It comes through as entirely black (due to mishandling the transparency).
RS := TResourceStream.Create(hInstance, 'WATERMARK', RT_RCDATA);
Bitmap := TBitmap.Create(768, 960);
Bitmap.LoadFromStream(RS);
Image1.Bitmap := Bitmap;
Image1.WrapMode := TImageWrapMode.iwStretch;
I am using the same code with a bitmap that I have dramatically reduced in size in the mean time, but I would rather be using the PNG with the transparency and resolution. Is there anyway to make the TBitmap in Firemonkey correctly handle the transparency of the PNG?
Everything I have read on this topic involves using VCL classes or properties that are unavailable in firemonkey
I'm using GR32 for drawing multiple semi-transparent PNG images.
So far I've been using the following method:
png:= TPNGObject.Create;
png.LoadFromFile(...);
PaintBox321.Buffer.Canvas.Draw(120, 20, png);
however I wanted to switch to the method proposed on GR32 website (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);
While the first method works perfectly fine, the second - which should give the same result - causes very strange problem with alpha channel, see the image (which also shows comparison to the same image "arranged" in Paint.NET - both background and icon were opened on the layers of the editor). The image depicts that the Bitmap32 is loaded or drawn inproperly. Any tips?
-- added 22 Nov
I've found out that it is not about drawing, it's about loading PNG to BMP32. Saving back from BMP32 to PNG generates the incorrect, "whitened" (the one on the left) PNG image.
The reason seems to be that the transparency is applied two times to the image when loaded with LoadPNGintoBitmap32, giving it a more transparent and greyish look (more on this later).
First the transparency:
This is code from the original LoadPNGintoBitmap32, the critical parts are marked with comments:
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
{ ... }
The destBitmap.Assign internally does the same as you in your previous approach: it let's the PNG image paint itself to its canvas. This operation respects the alpha channel of the PNG. But this is not necessary, since the alpha channel is assigned to TBitmap32's pixels in a second step!
Now change the code as follows, critical parts are again marked with comments:
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
{ ... }
The above solution is inefficient because it reads the image twice. But it shows why your second approach produces a more transparent image.
And for the greyishness: There is one more problem in the original code: destBitmap.Assign first fills the background with clWhite32, then paints the image transparently onto it. And then LoadPNGintoBitmap32 comes and adds another layer of transparency on top of it.
The problem may be that the PNG has incorrectly converted to TBitmap32, losing the transparency information in transit. It is a common case with paletted PNG images. Otherwise, you would not had to use „Bitmap.DrawMode := dmTransparent” and the „OuterColor”. If the transparencry information from PNG would have correctly transferred to TBitmpa32, DrawMode := dmBlend would have worked, without the need to set the OuterColor.
What matters most is how did you load a PNG into the TBitmap32. The TPngImage from Vcl.Imaging.pngimage unit (implemented in Delphi XE2 and later) can draw transparently on bitmaps, preserving what was on that bitmaps, combining colors using the PNG alpha layer, etc, but it doesn’t allow to easily convert various formats of PNG transparency (including paletted) into the alpha component of each pixel of TBitmap32. Once TPngImage have drawn an image, you get the combined RGB for each pixel, but the alpha component is not transferred to the target bitmap.
There are helper routines available that try to load a PNG into a TBitmap32 with transparency, but they have drawbacks:
(1) “LoadPNGintoBitmap32” from http://graphics32.org/wiki/FAQ/ImageFormatRelated
- it applies the transparency twice, so the images with alpha values other than 0 or 255 will look differently than in other software (most noticeable on translucent images with glass effects). This code will first apply alpha to RGB and then sets alpha as a separate layer, so when you paint, alpha will be applied again. You can find more information on this issue here: Delphi, GR32 + PngObject: converting to Bitmap32 doesn't work as expected
. Besides that, it doesn't convert correctly transparency from paletted images into the alpha layer of TBitmap32. They manually set alpha transparency for the pixels of a certain color of the output bitmap (rendered to RGB) rather doing that before rendering to RGB, so the actual transparency is lost as on your sample image when all white pixels are transparent.
(2) “LoadBitmap32FromPNG” from gr32ex library: https://code.google.com/archive/p/gr32ex/
- a slightly different implementation of the same algorithm as (1), and has the same issues as (1).
So, the solutions are:
Do not use TBitmap32; use Vcl.Imaging.pngimage.TPngImage do draw directly on target bitmap (screen, etc.) – this is the most compatible way that deals correctly with various PNG formats.
Use a helper routing to transfer transparency information from Vcl.Imaging.pngimage.TPngImage to TBitmap32.
Use the GR32 PNG library that can natively load a PNG into TBitmap32 https://sourceforge.net/projects/gr32pnglibrary/
Since you now have all the information on this issue, you may get the right solution for you.
How to load the alpha layer in one pass
Heinrich Ulbricht made a nice suggestion to remove the transparency layer before paining and then to read the image again. To avoid loading the image twice, you can save the alpha layer before calling PNGObject.RemoveTransparency. Here is the code that correctly applies the alpha layer and loads the image only once. Unfortunately, it does not work with paletted images. If you know how to correctly fill the alpha layer of TBitmap32 from any paletted image, without the effects described at Transparent Png to TBitmap32 please let me know.
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;
HI! Could you please tell me how to resize a .png image. Or better give an example. I've been searching for the answer for a long time and it seems that nobody knows how to resize a .png image and keep its transparency. :(
The original author of the PNGImage component (the basis of the Delphi native component) had a forum where he, and others, posted code snippets on how to do things using the PNGImage component.
Before the forum was taken down I grabbed a copy of all of the code snippets and placed them on the CodeGear Code Central website.
Most if not all of these work with native PNG images and do maintain the Alpha channel.
Here is the complete list of examples included in the package:
Smooth rotates a PNG object
Resizes a TPNGObject using a smooth algorithm
Slice one PNG into several smaller ones
Saves an image as either a bitmap or a png.
Sample chunk descendant
Read all tEXt-Chunks and write values into a TStrings object
Display a message box with information extracted from the PNG File
Finds and cuts a block from a PNG image
This method converts the png into a jpeg object
This method converts the png into a bmp object
Overlay one PNG over another
This makes the image half transparent
Flips a png image vertically and saves back
Draws a png image over the desktop
Here is the link: CodeCentral PNG Methods
You can use Windows Imaging Component (WIC) in Delphi 2010. You can load your PNG image in a TWICImage class, then extract IWICBitmapScaler interface from its handle.
Using IWICBitmapScaler, you can scale down or up the image.
WIC is available on Windows Vista, and Windows 7. For Windows XP, you have to install an update before using it.
There are versions of PngImage (Portable Network Graphics Delphi)
That allow to do it via simple StretchDraw.
I have version which allows to do it - 1.564 (31 July 2006)
And version which not allows - 1.4361 (8 March 2003)
To perform it I used:
heart.png - with transparency and I managed to resize it and save with transparency.
empty.png - the pure transparent png. it was used as blank sheet to put my image on it.
I have checked it via such code:
procedure TForm1.Button1Click(Sender: TObject);
var pic_empty, pic_stamp, pic_result :TPicture;
r:TRect;
png : TPNGObject;
begin
pic_stamp := TPicture.Create;
pic_stamp.LoadFromFile('c:\heart.png');
pic_stamp.Graphic.Transparent := True;
pic_empty := TPicture.Create;
pic_empty.LoadFromFile('c:\empty.png');
pic_empty.Graphic.Transparent := True;
r.Left := 0;
r.Top := 0;
r.Right := r.Left + 100;
r.Bottom := r.Top + 100;
pic_result := tpicture.Create;
pic_result.Bitmap.Assign(pic_empty.Graphic);
pic_result.Graphic.Transparent := True;
pic_result.Bitmap.Canvas.StretchDraw(r,pic_stamp.Graphic);
pic_result.Bitmap.Width :=100;
pic_result.Bitmap.Height:=100;
png := TPNGObject.Create;
png.Assign(pic_result.Bitmap);
png.SaveToFile('c:\result.png');
png.Free;
pic_result.Free;
pic_empty.Free;
pic_stamp.free;
end;
Delphi-7, win7 x64
Unfortunately, the vcldeveloper did not give an example in his answer.
Here is a small example.
var
vImage: TWICImage;
vScaler: IWICBitmapScaler;
begin
...
vImage := TWICImage.Create;
try
...
vImage.ImagingFactory.CreateBitmapScaler(vScaler);
vScaler.Initialize(vImage.Handle, NewWidth, NewHeight, WICBitmapInterpolationModeFant);
vImage.Handle := IWICBitmap(vScaler);
...
finally
vImage.Free;
end
More examples can be found in the Delphi source.
I must confess that I don't have any experience in programmatically play with png.
Anyway, you will find some libs here. Except the native Delphi PNG support, I think you'll find there all the existing libs (native to delphi of course).
If nothing helps you there, consider playing with ImageMagick if possible...That's the Swiss army knife of the image manipulation and all It can do is feasible in command line