Home > Back-end >  Fast crop .png images in Delphi
Fast crop .png images in Delphi

Time:08-08

I don't know what to do here anymore, so I hope that somebody can help me.

I'm using Delphi 10.4 and Windows 10.

Basically, my problem is that cutting a part of the .png image with transparent background is to slow. I use scanline.

I have one background image (back.bmp) that is drawn on the form. That image can be also a .png (with no transparency) if that can help to solve this.

From the second image (frontsigns.bmp) I cat different parts and need to draw them to that background.

Old version of this program used .bmp as second image (with no transparent background) so that was very fast.

procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
frontsigns : TBitmap;
begin

frontsigns := TBitmap.Create;
frontsigns.LoadFromFile('E:\frontsigns.bmp');

frmMain.Canvas.CopyRect(Rect(0,0,302,869), frontsigns.Canvas, Rect(0, yStartPos, 302, yEndPos)); // yStartPos and yEndPos are variables
end;

This draw part of the second image (303x870 px) on the background in the 0.415 ms. That is OK (probably can't be faster).

Now I need to use a second image with transparent backgrounds, so I use .png. Because I cut and draw different parts of the second image on the background my idea is that I use temp background image and draw part of the .png on that temp image and after that I draw it on the form.

Here is the code.

procedure TfrmMain.btnDrawBMPClick(Sender: TObject);
var
background, tmpbackground : TBitmap;
frontsigns, CroppedPng : TPngImage;
begin

background := TBitmap.Create;
background.LoadFromFile('E:\back.bmp');
frontsigns := TPngImage.Create;
frontsigns.LoadFromFile('E:\frontsigns.png');
tmpbackground := TBitmap.Create(303, 870);

tmpbackground.Canvas.CopyRect(Rect(0, 0, 302, 869), background.Canvas, Rect(0, 0, 302, 869));
CropPng(frontsigns, 0, yStartPos, 302, yEndPos, CroppedPng); // yStartPos and yEndPos are variables
tmpbackground.Canvas.Draw(0, 0, CroppedPng);

end;

This draw part of the second image (303x870 px) on the background in the 13.5 ms!!!!!!!

Reason is slow scanline I think. I should write here that frontsigns.png has only fully transparent background. There are not any semi-transparent pixels.

Here is my code for cropping .png images.

const
    ColorTabMax = 10;
    ColorTab : array[0..ColorTabMax-1] of TColor =
        (ClBlack, ClMaroon, ClRed, ClWebDarkOrange, ClYellow, ClGreen, ClBlue, ClPurple, ClGray, ClWhite);

procedure CropPng(Source: TPngImage; Left, Top, Width, Height: Integer; out Target : TPngImage);

function ColorToTriple(Color: TColor): TRGBTriple;
begin
    Color := ColorToRGB(Color);
    Result.rgbtBlue := Color shr 16 and $FF;
    Result.rgbtGreen := Color shr 8 and $FF;
    Result.rgbtRed := Color and $FF;
end;

var
X, Y : Integer;
Bitmap : TBitmap;
BitmapLine : PRGBLine;
AlphaLineA, AlphaLineB : pngImage.PByteArray;
begin
if (Source.Width < (Left   Width)) or (Source.Height < (Top   Height)) then
    raise Exception.Create('Invalid position/size');

Bitmap := TBitmap.Create;
try
Bitmap.Width := Width;
Bitmap.Height := Height;
Bitmap.PixelFormat := pf24bit;

for Y := 0 to Bitmap.Height - 1 do
begin
    BitmapLine := Bitmap.Scanline[Y];
    for X := 0 to Bitmap.Width - 1 do
        BitmapLine^[X] := ColorToTriple(Source.Pixels[Left   X, Top   Y]);
end;

Target := TPngImage.Create;
Target.Assign(Bitmap);

if Source.Header.ColorType in [COLOR_GRAYSCALEALPHA, COLOR_RGBALPHA] then
begin
    Target.CreateAlpha;
    for Y := 0 to Target.Height - 1 do
    begin
        AlphaLineA := Source.AlphaScanline[Top   Y];
        AlphaLineB := Target.AlphaScanline[Y];
        for X := 0 to Target.Width - 1 do
        AlphaLineB^[X] := AlphaLineA^[X   Left];
    end;
end;
finally
    Bitmap.Free;
end;
end;

I'm open for any ideas here. Can I make scanline works fatser? I don't have semi-transparent pixels so maybe I don't need to do all this.

I've tried with 32bit .bmp images with alpha channel, but haven't made it work with alphablend function.

I'me even open for third party libraries if there is no otehr option.

enter image description here enter image description here enter image description here

Thanks.....

CodePudding user response:

In library PngComponents unit PngFunctions offers procedure SlicePNG, which allows to split a TPngImage into separate parts of equal size. As this has to be done only once it may significantly reduce the drawing time.

CodePudding user response:

The problem with your approach is that you are reading your source image by accessing individual pixels using Source.Pixels and not using ScanLine

BitmapLine^[X] := ColorToTriple(Source.Pixels[Left   X, Top   Y]);

If you want to benefit properly by using ScanLine make sure you use ScanLine for both source and target images.

Also since your source and target images are both TPngImage you probably don't even need to create the temporary TBitmap.

And if color palettes of your PNG's match then you don't even need to do any color decoding/encoding but instead just copy data directly from one image to another. Of course you do need to make sure that color palette in your PNG's match each other in advance.

I remember reading about a tool that modifies a PNG's palette information to match with other files some years ago. Unfortunately I don't remember its name. I do remember reading about it in an article about creating of PNG based image atlases for games.

  • Related