Enjoyed your tutorial blog!
On the Amiga we would have done this using the Blitter, and it would have been very fast. Sadly missed: a GPU way ahead of its time.
I'm glad it's working. My blog would be to try to decompose a problem and see which bits can be simplified. It's not always obvious, unfortunately.
Although the flood-fill is the obvious route, there are as many cons as pros. If you wanted to do smart colouring you would have to do an "enhanced" floodfill - that could take a while, so you'd have to have animated pens etc to disguise it.
I hope this demonstrates the importance of off-line tools. Anything that can be pre-computed should be. AppGameKit will probably not be the tool to do that. AppGameKit is not a tool-maker - it's a delivery system for simple games. The PaintBook program uses the highly optimised flood-fills in Windows to do the heavy-lifting off-line. In the case of PIC1.png it does 94 flood fills, works out the bounding boxes, saves the 94 generated PNG files into a zip file for each sprite image and datafile in about 3 seconds.
Here's the flood-filling function:
function TForm_main.DoFills: boolean;
var x,y : integer;
cv : TCanvas;
begin
result := true;
fillsdone := 0;
fillchar(minYIndex,sizeof(MinYIndex),#0);
cursor := crHourGlass;
cv := bmp.Canvas;
cv.Brush.Style := bsSolid;
for y := 0 to pHeight-1 do
begin
for x := 0 to pWidth-1 do
begin
if cv.Pixels[x,y]=cBackground then
begin
inc(fillsdone);
cv.Brush.Color := TColor(fillsdone);
cv.FloodFill(x,y,cEdge,fsBorder);
end;
end;
end;
image2.Picture.Bitmap.Assign(bmp);
cursor := crDefault;
rep('There are '+inttostr(fillsdone)+' regions');
end;
That uses the canvas Pixels property, which is actually very slow, though good for proof-of-concept trials. Using that, the calculation of the bounds took minutes. We then had to turn to the Scanlines property, which is very fast. The downside is that we need to know the pixels format, which can be variable. To normalise this, we ensure that the loaded picture is blitted to 24-bit pixel format.
The Scanlines are actually pointers to a structure, but we can fool them into being a pointer array and simply index them. In this code segment, fillsdone is the number of filled segements. The colours have already been computed as above to lie in the range 1..255. So we can scan this rather strange image and extract the bounding box. If we are computing minima we must start the comparison value with an impossibly large number.
The procedure Fillchar() fills any thing with what ever byte value you wish, in this case 0 (#0).
The rather poorly named MinYIndex is an array of the following type:
type
TYminmax = record
iLeft,iTop,iWidth,iHeight: integer;
end;
The TRGBTriple is a system-defined 24-bit structure with RGB bytes.
Here's how we compute the bounds for each region
procedure TForm_main.GetMaxIndex;
var x, y : integer;
idx :integer;
I : Integer;
C : tcolor;
cval : TRGBTriple;
row : PRGBTripleArray;
begin
rep('Computing bounds');
fillchar(MinYIndex,sizeof(MinYIndex), #0);
for I := low(Minyindex) to High(minYindex) do
begin
minyindex[I].iLeft := MAXINT;
minyindex[I].iTop := MAXINT;
end;
for idx := 1 to fillsdone do
begin
cval.rgbtRed := idx;
for y :=0 to pheight-1 do
begin
row := bmp.ScanLine[Y];
for x := 0 to pwidth-1 do
begin
C :=row[x].rgbtRed;
if C=cval.rgbtRed then
begin
if X<MinYIndex[idx].iLeft then MinYIndex[idx].iLeft := X;
if Y<MinYIndex[idx].iTop then MinYIndex[idx].iTop := Y;
if X>MinYIndex[idx].iWidth then MinYIndex[idx].iWidth := X;
if Y>MinYIndex[idx].iheight then MinYIndex[idx].iHeight := Y;
end;
end;
end;
end;
for i := 1 to fillsdone do
begin
MinYIndex[I].iWidth := MinYIndex[I].iWidth - MinYIndex[I].iLeft + 1;
MinYIndex[I].iHeight := MinYIndex[I].iHeight - MinYIndex[I].iTop;
end;
end;
Using the Scanlines is very fast. Obviously, it's not as fast as using a DirectX surface, but we're talking microseconds.
I won't go into detail on how we make the subimages apart from to say we put them in a bitmap, having created the bitmap with rectangular bounds computed above. Effectively, we read the scanline values in the primary image and poke them into the scanlines of the subimage, setting them to black or white.
Making a PNG from a bitmap in Delphi? Here's how hard it is:
procedure TForm_main.BitmapFileToPNG(const inbmp: TBitmap;Dest: String;trans:boolean = true);
var
PNG: TPNGImage;
begin
PNG := TPNGImage.Create;
png.CompressionLevel := 9;
{In case something goes wrong, free both Bitmap and PNG}
try
PNG.Assign(inbmp); //Convert data into png
if trans then PNG.TransparentColor := clBlack;
PNG.SaveToFile(Dest);
finally
PNG.Free;
end
end;
Before you hit the metal, make the hammer...
End of sermon.
Onwards and sometimes upwards