0

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.....

brane
  • 41
  • 6
  • Check Graphics32 open source library, `TBitmap32` has a `Draw` method and it easily handles bitmap transparency (`DrawMode` and `CombineMode` properties of source bitmap). I have used it in past and have seen 10-100x increase in speed over standard TBitmap. – BrakNicku Aug 09 '22 at 16:54

3 Answers3

1

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.

Uwe Raabe
  • 45,288
  • 3
  • 82
  • 130
  • Hi Uve, Thank you for the suggestion. I've checked SlicePNG procedure and it accessing individual pixels using: BitmapLine[X] := ColorToTriple(JoinedPNG.Pixels[X + OffsetX, Y + OffsetY]); As SilverWarior pointed I'll try using scanline. I'll post some results soon. – brane Aug 09 '22 at 06:23
0

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.

AmigoJack
  • 5,234
  • 1
  • 15
  • 31
SilverWarior
  • 7,372
  • 2
  • 16
  • 22
0

Here is my current progress thank you to the SilverWariors answer. I've just implemented first tip for now.

I was using information from:

https://delphi.cjcsoft.net/viewthread.php?tid=48996

https://en.wikipedia.org/wiki/BMP_file_format

I've replaced:

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

with:

BitmapLine^[X] := GetPixel(source, Left + X, Top + Y);

GetPixel function is bellow.

function GetPixel(Source: TPngImage; X, Y: Integer): TRGBTriple;
var
LineSource : pngImage.PByteArray;
begin
LineSource := Source.Scanline[y];
// Get blue value - stored in lowest order byte in a TColor
Result.rgbtBlue := PByteArray(LineSource)^[(x*3)+0];

// Get Green value - second lowest byte in TColor
Result.rgbtGreen := PByteArray(LineSource)^[(x*3)+1];

// Get Red value - third lowest byte in TColor
Result.rgbtRed := PByteArray(LineSource)^[(x*3)+2];
end;

I'm not sure why the color order is like this and not like in the article on the link above. Maybe because .png file is 32bit.

With this change I've decreased time from 13.5 ms to 6.44 ms. That is great, but I think it can be even much better.

Here is where is I see potential improvement.

Now I scan every line two times. One for the RGB colors and one for for ALPA information.

AlphaLineA := Source.AlphaScanline[Top + Y];

I think that I can get ALPHA info from scanline if I scanline returns all four bytes in a 32bit image. I'm I correct? Maybe something like:

PByteArray(LineSource)^[(x*3)+3];

Another idea is that I can directly write to the final background. Now I cut part of the .png image and draw it on the background at the end. I must use draw because transparency that .png image that I got as result of croping original image will be lost if I use CopyRect.

But If I draw pixels directly to the background (that has ALPHA 255) that would be much faster. Maybe I can avoid that because the background is 32bit .bmp (it can be 32bit .png) without any transparency (ALPHA is 255 for all bits). Also ALPHA for .png that I'm cutting of can be only 255 (not transparent) and 0 (fully transparent).

I'm not sure how I can accomplish this.

brane
  • 41
  • 6