delphi convert gif to png with transparency - delphi

I am trying to convert a gif to png, that's easy, but the problem is the result image is not transparent, also I would like to have in the png image the alpha channel.
This is my code:
procedure TForm1.Button1Click(Sender: TObject);
var
png: TPngImage;
p : TPicture;
begin
p := TPicture.Create;
p.LoadFromFile('C:\temp\php.gif');
png := TPngImage.CreateBlank(COLOR_RGB , 8, p.Width, p.Height);
png.Canvas.Draw(0,0, p.Graphic);
png.SaveToFile('C:\Windows\Temp\test.png');
end;
The new picture has the background black, should be transparent.
If I try to add the ALPHA in the constructor, is 100% transparent.
png := TPngImage.CreateBlank(COLOR_RGBALPHA , 8, p.Width, p.Height);

Since Delphi XE 2 GDI+ is supported, which offers real easy to use options for conversions.
You just need to create TGPImage providing the image file to load and save this image with the wished encoder, found by the desired mime type.
uses Winapi.GDIPAPI, Winapi.GDIPOBJ, Winapi.GDIPUTIL;
procedure TForm8.Button1Click(Sender: TObject);
var
encoderClsid: TGUID;
stat: TStatus;
IMG: TGPImage;
begin
IMG := TGPImage.Create('C:\temp\transparent.gif');
try
GetEncoderClsid('image/png', encoderClsid);
stat := IMG.Save('C:\temp\transparent.png', encoderClsid, nil);
finally
IMG.Free;
end;
if (stat = Ok) then
Showmessage('Success');
end;
examples for the mime types:
image/bmp
image/jpeg
image/gif
image/tiff
image/png

Just by drawing GIF image on PNG canvas will not move transparency information from GIF image to PNG.
You will have to do it yourself.
ForceAlphaChannel procedure will create alpha channel for any PNG image based on given TransparentColor.
procedure ForceAlphaChannel(Image: TPngImage; BitTransparency: Boolean; TransparentColor: TColor; Amount: Byte);
var
Temp: TPngImage;
x, y: Integer;
Line: VCL.Imaging.PngImage.pByteArray;
PixColor: TColor;
begin
Temp := TPngImage.CreateBlank(COLOR_RGBALPHA, 8, Image.Width, Image.Height);
try
for y := 0 to Image.Height - 1 do
begin
Line := Temp.AlphaScanline[y];
for x := 0 to Image.Width - 1 do
begin
PixColor := Image.Pixels[x, y];
Temp.Pixels[x, y] := PixColor;
if BitTransparency and (PixColor = TransparentColor) then Line^[x] := 0
else Line^[x] := Amount;
end;
end;
Image.Assign(Temp);
finally
Temp.Free;
end;
end;
If you add call to ForceAlphaChannel after you have drawn GIF image you will get transparency based on transparent color you define.
procedure TForm1.Button1Click(Sender: TObject);
var
png: TPngImage;
p : TPicture;
TransparentColor: TColor;
begin
p := TPicture.Create;
p.LoadFromFile('C:\temp\php.gif');
TransparentColor := clFuchsia;
png := TPngImage.CreateBlank(COLOR_RGB , 8, p.Width, p.Height);
// set png background color to same color that will be used for setting transparency
png.Canvas.Brush.Color := TransparentColor;
png.Canvas.FillRect(rect(0, 0 , p.Width, p.Height));
png.Canvas.Draw(0, 0, p.Graphic);
ForceAlphaChannel(png, true, TransparentColor, 255);
png.SaveToFile('C:\Windows\Temp\test.png');
end;

For older/new Delphi versions (in newer version - change TPngObject to TPngImage).
If you need to save every frame of (animated) GIF into PNG (works for non-animated GIFS also):
The first variant code is compatible with the newer pngimage Version 1.56+ (which supports the CreateBlank constructor)
procedure TForm1.Button1Click(Sender: TObject);
var
Gif: TGifImage;
Png: TPngObject; // for new Delphi versions use "TPngImage"
Bmp: TBitmap;
TransparentColor, Pixel: TColor;
I, X, Y: Integer;
AlphaScanline: pByteArray;
IsTransparent: Boolean;
ColorType: Cardinal;
begin
Gif := TGifImage.Create;
try
Gif.LoadFromFile('C:\Downloads\ajax-loader.gif');
for I := 0 to Gif.Images.Count - 1 do
begin
IsTransparent := Gif.Images[I].Transparent;
TransparentColor := Gif.Images[I].GraphicControlExtension.TransparentColor;
Bmp := Gif.Images[I].Bitmap;
if IsTransparent then
ColorType := COLOR_RGBALPHA
else
ColorType := COLOR_RGB;
Png := TPngObject.CreateBlank(ColorType, 8, Bmp.Width, Bmp.Height); // for new Delphi versions use "TPngImage"
try
AlphaScanline := nil;
for Y := 0 to Bmp.Height - 1 do
begin
if IsTransparent then
AlphaScanline := Png.AlphaScanline[Y];
for X := 0 to Bmp.Width - 1 do
begin
Pixel := Bmp.Canvas.Pixels[X, Y];
Png.Pixels[X, Y] := Pixel;
if IsTransparent then
begin
if (Pixel = TransparentColor) then
AlphaScanline^[X] := 0
else
AlphaScanline^[X] := 255;
end;
end;
end;
Png.SaveToFile(Format('%d.png', [I]));
finally
Png.Free;
end;
end;
finally
Gif.Free;
end;
end;
For old pngimage version before 1.56 which do not support TPngObject.CreateBlank:
procedure TForm1.Button2Click(Sender: TObject);
var
Gif: TGifImage;
Png: TPngObject; // for new Delphi versions use "TPngImage"
Bmp: TBitmap;
TransparentColor, Pixel: TColor;
I, X, Y: Integer;
AlphaScanline: pByteArray;
IsTransparent: Boolean;
begin
Gif := TGifImage.Create;
try
Gif.LoadFromFile('C:\Downloads\ajax-loader.gif');
for I := 0 to Gif.Images.Count - 1 do
begin
IsTransparent := Gif.Images[I].Transparent;
Png := TPngObject.Create; // for new Delphi versions use "TPngImage"
try
if IsTransparent then
begin
Bmp := TBitmap.Create;
Bmp.Assign(Gif.Images[I].Bitmap);
Bmp.PixelFormat := pf24bit;
Png.Assign(Bmp);
Png.CreateAlpha;
TransparentColor := Gif.Images[I].GraphicControlExtension.TransparentColor;
for Y := 0 to Bmp.Height - 1 do
begin
AlphaScanline := Png.AlphaScanline[Y];
for X := 0 to Bmp.Width - 1 do
begin
Pixel := Png.Pixels[X, Y];
if (Pixel = TransparentColor) then
AlphaScanline^[X] := 0;
end;
end;
Bmp.Free;
end
else
Png.Assign(Gif.Images[I].Bitmap);
Png.SaveToFile(Format('%d.png', [I]));
finally
Png.Free;
end;
end;
finally
Gif.Free;
end;
end;

Related

Delphi drawing part of png file on another image

I am using this function to draw a png over a TImage on a specific location:
procedure TForm1.PlacePNG(nam: string; px, py: Integer);
var
vPic: TPicture;
vSrc: TGraphic;
begin
vPic := TPicture.Create;
try
vPic.LoadFromFile(Nam);
vSrc := vPic.Graphic;
Image1.Canvas.Draw(px, py, vSrc);
finally
vPic.Free;
end;
end;
My question: what is the best way to do this with part of the png file, without losing its transparency?
This is an interesting question!
Of course, drawing the entire PNG is trivial:
procedure TForm1.FormCreate(Sender: TObject);
var
bg, fg: TPngImage;
begin
bg := TPngImage.Create;
try
bg.LoadFromFile('K:\bg.png');
fg := TPngImage.Create;
try
fg.LoadFromFile('K:\fg.png');
Image1.Picture.Graphic := bg;
Image2.Picture.Graphic := fg;
fg.Draw(bg.Canvas, Rect(0, 0, fg.Width, fg.Height));
Image3.Picture.Graphic := bg;
finally
fg.Free;
end;
finally
bg.Free;
end;
end;
To draw only a part, one possible solution is to obtain the images as 32-bpp RGBA bitmaps and then use the Windows API, specifically, the AlphaBlend function:
procedure TForm1.FormCreate(Sender: TObject);
var
bg, fg: TPngImage;
bgbm, fgbm: TBitmap;
BlendFunction: TBlendFunction;
begin
// Load background PNG
bg := TPngImage.Create;
try
bg.LoadFromFile('K:\bg.png');
// Load foreground PNG
fg := TPngImage.Create;
try
fg.LoadFromFile('K:\fg.png');
// Preview background and foreground
Image1.Picture.Graphic := bg;
Image2.Picture.Graphic := fg;
// Create background BMP
bgbm := TBitmap.Create;
try
bgbm.Assign(bg);
// Create foreground BMP
fgbm := TBitmap.Create;
try
fgbm.Assign(fg);
// Blend PART OF foreground BMP onto background BMP
BlendFunction.BlendOp := AC_SRC_OVER;
BlendFunction.BlendFlags := 0;
BlendFunction.SourceConstantAlpha := 255;
BlendFunction.AlphaFormat := AC_SRC_ALPHA;
if not Winapi.Windows.AlphaBlend(
bgbm.Canvas.Handle,
100,
100,
200,
200,
fgbm.Canvas.Handle,
200,
200,
200,
200,
BlendFunction
) then
RaiseLastOSError;
// Preview result
Image3.Picture.Graphic := bgbm;
finally
fgbm.Free;
end;
finally
bgbm.Free;
end;
finally
fg.Free;
end;
finally
bg.Free;
end;
end;

delphi change canvas pixel color

i need to convert all pixels of a canvas
found this function after a quick search in google
but dont work correct , but it seems must work good!!
function RGBBitmapTo1Bit(OriginalBitmap : TBitmap) : TBitmap;
var
x, y : integer;
begin
result := TBitmap.Create;
result.width := OriginalBitmap.width;
result.height := OriginalBitmap.height;
for x := 1 to OriginalBitmap.width do
for y := 1 to OriginalBitmap.height do
begin
result.Canvas.Pixels[x, y] := clBlack;
end;
end;
this function dont make any change on the file
for example i used like this
procedure TForm1.Button2Click(Sender: TObject);
var
imgf : TBitmap;
begin
if od1.Execute then
begin
imgf := TBitmap.Create;
imgf.LoadFromFile(od1.FileName);
RGBBitmapTo1Bit(imgf);
imgf.SaveToFile(ExtractFilePath(od1.FileName)+'test.bmp');
end;
but the output and input files are the same!!!
how can i assign a color to a pixel rightly!?
Your code has three problems with it:
Pixels are 0-indexed in both dimensions, so you need to change your loops accordingly.
for x := 0 to OriginalBitmap.width-1 do
for y := 0 to OriginalBitmap.height-1 do
your function DOES NOT modify the original TBitmap, it allocates and modifies a new TBitmap and then returns that to the caller, but the caller is ignoring that new bitmap, expecting the original TBitmap to have been modified instead. You are saving the original TBitmap to file, which is why you don't see any of the pixels changed.
You are leaking memory for both TBitmap objects;
Try this instead:
function RGBBitmapTo1Bit(OriginalBitmap : TBitmap) : TBitmap;
var
x, y : integer;
begin
Result := TBitmap.Create;
Result.Width := OriginalBitmap.Width;
Result.Height := OriginalBitmap.Height;
for x := 0 to OriginalBitmap.Width-1 do
for y := 0 to OriginalBitmap.Height-1 do
begin
Result.Canvas.Pixels[x, y] := clBlack;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
imgf : TBitmap;
imgf2 : TBitmap;
begin
if od1.Execute then
begin
imgf := TBitmap.Create;
try
imgf.LoadFromFile(od1.FileName);
imgf2 := RGBBitmapTo1Bit(imgf);
try
imgf2.SaveToFile(ExtractFilePath(od1.FileName)+'test.bmp');
finally
imgf2.Free;
end;
finally
imgf.Free;
end;
end;
end;
Or this:
procedure RGBBitmapTo1Bit(OriginalBitmap : TBitmap);
var
x, y : integer;
begin
for x := 0 to OriginalBitmap.Width-1 do
for y := 0 to OriginalBitmap.Height-1 do
begin
OriginalBitmap.Canvas.Pixels[x, y] := clBlack;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
imgf : TBitmap;
begin
if od1.Execute then
begin
imgf := TBitmap.Create;
try
imgf.LoadFromFile(od1.FileName);
RGBBitmapTo1Bit(imgf);
imgf.SaveToFile(ExtractFilePath(od1.FileName)+'test.bmp');
finally
imgf.Free;
end;
end;
end;
That being said, RGBBitmapTo1Bit() is slow in both versions. A faster version would be more like this:
function RGBBitmapTo1Bit(OriginalBitmap : TBitmap) : TBitmap;
begin
Result := TBitmap.Create;
Result.Width := OriginalBitmap.Width;
Result.Height := OriginalBitmap.Height;
Result.Canvas.Brush.Color := clBlack;
Result.Canvas.FillRect(Rect(0, 0, Result.Width, Result.Height));
end;
function RGBBitmapTo1Bit(OriginalBitmap : TBitmap) : TBitmap;
begin
Result := TBitmap.Create;
Result.Canvas.Brush.Color := clBlack;
Result.Width := OriginalBitmap.Width;
Result.Height := OriginalBitmap.Height;
end;
Or:
procedure RGBBitmapTo1Bit(OriginalBitmap : TBitmap);
begin
OriginalBitmap.Canvas.Brush.Color := clBlack;
OriginalBitmap.Canvas.FillRect(Rect(0, 0, OriginalBitmap.Width, OriginalBitmap.Height));
end;
It also does not do what its name suggests - convert a bitmap to 1bit. To do that, you have to set the TBitmap.PixelFormat property instead:
function RGBBitmapTo1Bit(OriginalBitmap : TBitmap) : TBitmap;
begin
Result := TBitmap.Create;
Result.PixelFormat := pf1Bit;
Result.Canvas.Brush.Color := clBlack;
Result.Width := OriginalBitmap.Width;
Result.Height := OriginalBitmap.Height;
end;
Or:
procedure RGBBitmapTo1Bit(OriginalBitmap : TBitmap);
begin
OriginalBitmap.PixelFormat := pf1Bit;
OriginalBitmap.Canvas.Brush.Color := clBlack;
OriginalBitmap.Canvas.FillRect(Rect(0, 0, OriginalBitmap.Width, OriginalBitmap.Height));
end;

crop and align inserted BMP in Delphi

I want to crop and align the inserted BMP from the clipboard.
I'm trying for 2 days but still nothing workable...
procedure TForm1.act1Execute(Sender: TObject);
var
BMP : TBitmap;
begin
BMP := TBitmap.Create;
BMP.Assign(Clipboard);
BMP.SetSize(400,200);
Img1.picture.Graphic := BMP;
BMP.Free;
end;
procedure TForm1.act1Update(Sender: TObject);
begin
(Sender as TAction).Enabled := Clipboard.HasFormat(CF_BITMAP);
end;
end.
If I understand you right, you need to center the bitmap in the Image control?
It's simple - set the Img1.Center := True
To crop the bitmap you need code like this:
procedure CropBitmap(Bmp: TBitmap; const CropRect: TRect);
var
CropBmp: TBitmap;
begin
CropBmp := TBitmap.Create;
try
CropBmp.Width := CropRect.Right - CropRect.Left;
CropBmp.Height := CropRect.Bottom - CropRect.Top;
CropBmp.Canvas.CopyRect(
Rect(0, 0, CropBmp.Width, CropBmp.Height),
Bmp.Canvas,
CropRect
);
Bmp.Assign(CropBmp);
finally
CropBmp.Free;
end;
end;

Delphi How to draw 2d array of TColor on TCanvas quickly?

I have a 2-dimensional array of TColor. And also I have a TCanvas. How can I draw this color map on canvas faster than with a for cycle?
For Example:
type
T2DAr = array of array of TColor;
var
ar: T2DAr;
Form1: TForm; // mainform
function main;
var x, y: integer;
begin
{filling array with colors as a 10x10}
for x := 0 to length(ar)-1 do
for y := 0 to length(ar[x])-1 do
Form1.Canvas.Pixels[x, y] := ar[x, y];
end;
This way works too slowly. I need a faster algorithm.
This has been answered many times. The answer is: use scanlines instead of the terribly slow Pixels property. Example:
function CreateBitmapReallyFast: TBitmap;
const
WHITE: TRGBTriple = (rgbtBlue: 255; rgbtGreen: 255; rgbtRed: 255);
BLACK: TRGBTriple = (rgbtBlue: 0; rgbtGreen: 0; rgbtRed: 0);
var
y: Integer;
scanline: PRGBTriple;
x: Integer;
begin
result := TBitmap.Create;
result.SetSize(1920, 1080);
result.PixelFormat := pf24bit;
for y := 0 to result.Height - 1 do
begin
scanline := result.ScanLine[y];
for x := 0 to result.Width - 1 do
begin
if odd(x) then
scanline^ := WHITE
else
scanline^ := BLACK;
inc(scanline);
end;
end;
end;
Even cooler:
with scanline^ do
begin
rgbtBlue := Random(255);
rgbtGreen := Random(255);
rgbtRed := Random(255);
end;
To try it:
procedure TForm1.FormPaint(Sender: TObject);
var
bm: TBitmap;
begin
bm := CreateBitmapReallyFast;
try
Canvas.Draw(0, 0, bm);
finally
bm.Free;
end;
end;
Of course, if you have a (packed) array of TRGBTriple or TRGBQuad, and the pixel format of the bitmap is the same, you can simply Move the data in memory from the array to the bitmap's scanlines.

Need to figure which pixels are (completely) transparent

Given a Delphi TPicture containing some TGraphic descendant, I need to figure pixel color and opacity.
I think I have to have different implementations for each class, and I think I've got TPngImage covered. Is there any support for transparency in 32bit Bitmaps?
Can I address the problem in a more general way than the following?:
procedure GetPixelColorAndTransparency(const Picture: TPicture; X, Y:
Integer; out Color: TColor; out Opacity: Byte);
var
Bmp: TBitmap;
begin
if Picture.Graphic is TPngImage then
begin
Opacity := (TPngImage(Picture.Graphic).AlphaScanline[Y]^)[X];
Color := TPngImage(Picture.Graphic).Pixels[ X, Y ];
end
else
if Picture.Graphic is TBitmap then
begin
Color := Picture.Bitmap.Canvas.Pixels[ X, Y ];
Opacity := 255;
end
else
begin
Bmp := TBitmap.Create;
try
Bmp.Assign(Picture.Graphic);
Color := Bmp.Canvas.Pixels[ X, Y ];
Opacity := 255;
finally
Bmp.Free;
end;
end;
end;
How about something like this:
procedure GetPixelColorAndTransparency(const Picture: TPicture; X, Y: Integer; out Color: TColor; out Opacity: Byte);
type
PRGBQuadArray = ^TRGBQuadArray;
TRGBQuadArray = array [Integer] of TRGBQuad;
var
Bmp: TBitmap;
begin
if Picture.Graphic is TPngImage then
begin
with TPngImage(Picture.Graphic) do begin
Opacity := AlphaScanline[Y]^[X];
Color := Pixels[X, Y];
end;
end
else if Picture.Graphic is TBitmap then
begin
with Picture.Bitmap do begin
Color := Canvas.Pixels[X, Y];
if PixelFormat = pf32Bit then begin
Opacity := PRGBQuadArray(Scanline[Y])^[X].rgbReserved;
end
else if Color = TranparentColor then begin
Opacity := 0;
end
else begin
Opacity := 255;
end;
end;
end else
begin
Bmp := TBitmap.Create;
try
Bmp.Assign(Picture.Graphic);
Color := Bmp.Canvas.Pixels[X, Y];
if Color = Bmp.TranparentColor then begin
Opacity := 0;
end else begin
Opacity := 255;
end;
finally
Bmp.Free;
end;
end;
end;
It is not optimized but simple to understand:
procedure GetPixelColorAndTransparency(const Picture: TPicture; X, Y:
Integer; out Color: TColor; out Opacity: Byte);
var
Bmp: TBitmap;
Color32: Cardinal;
begin
Bmp := TBitmap.Create;
try
Bmp.Assign(Picture.Graphic);
Color32 := Bmp.Canvas.Pixels[ X, Y ];
Color := Color32 and $00FFFFFF;
Opacity := Color32 shr 24;
finally
Bmp.Free;
end;
end;

Resources