From 3f6303ec751cc4656bfade47920b1390b0f3d917 Mon Sep 17 00:00:00 2001 From: Olly Date: Thu, 12 Oct 2023 01:50:54 +0100 Subject: [PATCH] Image: Line,Ellipse,Circle antialiased (with thickness) methods --- Source/Simba.lpi | 32 +++- Source/image/clearpixelaa.inc | 6 + Source/image/drawellipseaa.inc | 155 ++++++++++++++++++ Source/image/drawlineaa.inc | 64 ++++++++ Source/image/drawpixelaa.inc | 26 +++ Source/{ => image}/simba.image.pas | 77 +++++++++ Source/{ => image}/simba.image_lazbridge.pas | 0 Source/{ => image}/simba.image_textdrawer.pas | 0 .../simbaclasses/simba.import_class_image.pas | 19 +++ 9 files changed, 371 insertions(+), 8 deletions(-) create mode 100644 Source/image/clearpixelaa.inc create mode 100644 Source/image/drawellipseaa.inc create mode 100644 Source/image/drawlineaa.inc create mode 100644 Source/image/drawpixelaa.inc rename Source/{ => image}/simba.image.pas (97%) rename Source/{ => image}/simba.image_lazbridge.pas (100%) rename Source/{ => image}/simba.image_textdrawer.pas (100%) diff --git a/Source/Simba.lpi b/Source/Simba.lpi index 5e2c49f16..ba0d59a1f 100644 --- a/Source/Simba.lpi +++ b/Source/Simba.lpi @@ -48,7 +48,7 @@ - + @@ -93,7 +93,7 @@ - + @@ -141,7 +141,7 @@ - + @@ -184,7 +184,7 @@ - + @@ -226,7 +226,7 @@ - + @@ -274,7 +274,7 @@ - + @@ -340,7 +340,7 @@ - + @@ -1025,6 +1025,22 @@ + + + + + + + + + + + + + + + + @@ -1035,7 +1051,7 @@ - + diff --git a/Source/image/clearpixelaa.inc b/Source/image/clearpixelaa.inc new file mode 100644 index 000000000..ae5826b3c --- /dev/null +++ b/Source/image/clearpixelaa.inc @@ -0,0 +1,6 @@ +procedure PixelProc(const X, Y: Integer; const Alpha: Byte); inline; +begin + if (X >= 0) and (Y >= 0) and (X < FWidth) and (Y < FHeight) then + FData[Y*FWidth+X].A := 0; +end; + diff --git a/Source/image/drawellipseaa.inc b/Source/image/drawellipseaa.inc new file mode 100644 index 000000000..01bf8678e --- /dev/null +++ b/Source/image/drawellipseaa.inc @@ -0,0 +1,155 @@ +// https://zingl.github.io/bresenham.js + +procedure DrawEllipseAA(x0, y0, x1, y1: Integer; Thickness: Single); +var + a,b,b1: Integer; + a2,b2: Single; + dx,dy: Single; + err: Single; + dx2,dy2,e2,ed: Single; + i: Single; + Alpha: Byte; +begin + a := Abs(x1 - x0); + b := Abs(y1 - y0); + b1 := b and 1; + a2 := a-2*Thickness; + b2 := b-2*Thickness; + dx := 4*(a-1)*b*b; + dy := 4*(b1-1)*a*a; + + i := a+b2; + err := b1*a*a; + + if ((Thickness-1) * (2*b-Thickness) > a*a) then + b2 := Sqrt(a*(b-a)*i*a2) / (a-Thickness); + + if ((Thickness-1) * (2*a-Thickness) > b*b) then + begin + a2 := Sqrt(b*(a-b)*i*b2) / (b-Thickness); + Thickness := (a-a2) / 2; + end; + + if (a = 0) or (b = 0) then + Exit; + + if (x0 > x1) then + begin + x0 := x1; + x1 += a; + end; + + if (y0 > y1) then + y0 := y1; + + if (b2 <= 0) then + Thickness := a; + + e2 := Thickness - Floor(Thickness); + Thickness := x0+Thickness-e2; + dx2 := 4*(a2+2*e2-1)*b2*b2; + dy2 := 4*(b1-1)*a2*a2; + e2 := dx2*e2; + y0 += (b+1) shr 1; + y1 := y0-b1; + a := 8*a*a; + b1 := 8*b*b; + a2 := 8*a2*a2; + b2 := 8*b2*b2; + + repeat + while True do + begin + if (err < 0) or (x0 > x1) then + begin + i := x0; + Break; + end; + + i := Min(dx,dy); + ed := Max(dx,dy); + + if ((y0 = y1+1) and (2*err > dx) and (a > b1)) then + ed := a/4 + else + ed += 2*ed*i*i/(4*ed*ed+i*i+1)+1; + i := 255*err/ed; + + Alpha := Byte(Round(i)); + PixelProc(x0,y0, Alpha); + PixelProc(x0,y1, Alpha); + PixelProc(x1,y0, Alpha); + PixelProc(x1,y1, Alpha); + + if (err+dy+a < dx) then + begin + i := x0+1; + Break; + end; + + x0 += 1; + x1 -= 1; + err -= dx; + dx -= b1; + end; + + while (i < Thickness) and (2*i <= x0+x1) do + begin + PixelProc(Round(i), y0, 0); + PixelProc(Round(x0+x1-i), y0, 0); + PixelProc(Round(i), y1, 0); + PixelProc(Round(x0+x1-i), y1, 0); + + i += 1.0; + end; + + while ((e2 > 0) and (x0+x1 >= 2*Thickness)) do + begin + i := Min(dx2,dy2); + ed := Max(dx2,dy2); + + if (y0 = y1+1) and (2*e2 > dx2) and (a2 > b2) then + ed := a2/4 + else + ed += 2*ed*i*i/(4*ed*ed+i*i); + + Alpha := Byte(Round(255-255*e2/ed)); + PixelProc(Round(Thickness), y0, Alpha); + PixelProc(Round(x0+x1-Thickness), y0, Alpha); + PixelProc(Round(Thickness), y1, Alpha); + PixelProc(Round(x0+x1-Thickness), y1, Alpha); + + if (e2+dy2+a2 < dx2) then + Break; + + Thickness += 1; + e2 -= dx2; + dx2 -= b2; + end; + + dy2 += a2; + e2 += dy2; + y0 += 1; + y1 -= 1; + dy += a; + err += dy; + until (x0 >= x1); + + while (y0-y1 <= b) do + begin + Alpha := Byte(Round(255*4*err/b1)); + + PixelProc(x0, y0, Alpha); + PixelProc(x1, y0, Alpha); + + y0 += 1; + + PixelProc(x0, y1, Alpha); + PixelProc(x1, y1, Alpha); + + y1 -= 1; + dy += a; + err += dy; + end; +end; + diff --git a/Source/image/drawlineaa.inc b/Source/image/drawlineaa.inc new file mode 100644 index 000000000..7451c25b9 --- /dev/null +++ b/Source/image/drawlineaa.inc @@ -0,0 +1,64 @@ +// https://zingl.github.io/bresenham.js + +procedure DrawLineAA(x0, y0, x1, y1: Integer; Thickness: Single); +var + dx, dy, err: Integer; + e2, x2, y2: Integer; + ed: Single; + sx, sy: Integer; +begin + dx := Abs(x1 - x0); + dy := Abs(y1 - y0); + + if (x0 < x1) then sx := 1 else sx := -1; + if (y0 < y1) then sy := 1 else sy := -1; + + err := dx-dy; + if (dx+dy = 0) then + ed := 1 + else + ed := Sqrt(Double(dx*dx) + Double(dy*dy)); + + Thickness := (Thickness + 1) / 2; + while True do + begin + PixelProc(x0, y0, Round(Max(0, 255 * (Abs(err-dx+dy)/ed-Thickness+1)))); + + e2 := err; + x2 := x0; + if (2*e2 >= -dx) then + begin + e2 += dy; + y2 := y0; + while (e2 < ed*Thickness) and ((y1 <> y2) or (dx > dy)) do + begin + y2 += sy; + PixelProc(x0, y2, Round(Max(0, 255 * (Abs(e2)/ed-Thickness+1)))); + e2 += dx; + end; + if (x0 = x1) then + Break; + + e2 := err; + err -= dy; + x0 += sx; + end; + + if (2*e2 <= dy) then + begin + e2 := dx-e2; + while (e2 < ed*Thickness) and ((x1 <> x2) or (dx < dy)) do + begin + x2 += sx; + PixelProc(x2, y0, Round(Max(0, 255 * (Abs(e2)/ed-Thickness+1)))); + e2 += dy; + end; + if (y0 = y1) then + Break; + + err += dx; + y0 += sy; + end; + end; +end; + diff --git a/Source/image/drawpixelaa.inc b/Source/image/drawpixelaa.inc new file mode 100644 index 000000000..ceda0ac5b --- /dev/null +++ b/Source/image/drawpixelaa.inc @@ -0,0 +1,26 @@ +procedure PixelProc(const X, Y: Integer; const Alpha: Byte); inline; +var + Pixel: PColorBGRA; + APlus1, APlus1Inv: UInt32; +begin + if (X >= 0) and (Y >= 0) and (X < FWidth) and (Y < FHeight) then + begin + Pixel := @FData[Y*FWidth+X]; + + if (Alpha > 0) then + begin + if (Pixel^.A > 0) then // dont blend again + Exit; + + APlus1 := Alpha + 1; + APlus1Inv := 255 - Alpha + 1; + + Pixel^.R := (BGRA.R * APlus1Inv) shr 8 + Pixel^.R * APlus1 shr 8; + Pixel^.G := (BGRA.G * APlus1Inv) shr 8 + Pixel^.G * APlus1 shr 8; + Pixel^.B := (BGRA.B * APlus1Inv) shr 8 + Pixel^.B * APlus1 shr 8; + Pixel^.A := Alpha; + end else + Pixel^ := BGRA; + end; +end; + diff --git a/Source/simba.image.pas b/Source/image/simba.image.pas similarity index 97% rename from Source/simba.image.pas rename to Source/image/simba.image.pas index 90ffb19e1..5020a4044 100644 --- a/Source/simba.image.pas +++ b/Source/image/simba.image.pas @@ -214,6 +214,10 @@ TSimbaImage = class(TSimbaBaseClass) function MatchTemplate(Template: TSimbaImage; Formula: ETMFormula): TSingleMatrix; function MatchTemplateMask(Template: TSimbaImage; Formula: ETMFormula): TSingleMatrix; + + procedure DrawLineAA(Start, Stop: TPoint; Color: TColor; Thickness: Single = 1.5); + procedure DrawEllipseAA(ACenter: TPoint; XRadius, YRadius: Integer; Color: TColor; Thickness: Single = 1.5); + procedure DrawCircleAA(ACenter: TPoint; Radius: Integer; Color: TColor; Thickness: Single = 1.5); end; TSimbaImageArray = array of TSimbaImage; @@ -2409,6 +2413,79 @@ function TSimbaImage.MatchTemplateMask(Template: TSimbaImage; Formula: ETMFormul Result := simba.matchtemplate.MatchTemplateMask(Self.ToMatrixBGR(), Template.ToMatrixBGR(), Formula); end; +procedure TSimbaImage.DrawLineAA(Start, Stop: TPoint; Color: TColor; Thickness: Single); + + procedure DoClearAlpha; + {$i clearpixelaa.inc} + {$i drawlineaa.inc} + begin + DrawLineAA( + Start.X, Start.Y, + Stop.X, Stop.Y, + Thickness + ); + end; + + procedure DoDraw; + var + BGRA: TColorBGRA; + + {$i drawpixelaa.inc} + {$i drawlineaa.inc} + begin + BGRA := Color.ToBGRA(); + + DrawLineAA( + Start.X, Start.Y, + Stop.X, Stop.Y, + Thickness + ); + end; + +begin + DoClearAlpha(); + DoDraw(); +end; + +procedure TSimbaImage.DrawEllipseAA(ACenter: TPoint; XRadius, YRadius: Integer; Color: TColor; Thickness: Single); + + procedure DoClearAlpha; + {$i clearpixelaa.inc} + {$i drawellipseaa.inc} + begin + DrawEllipseAA( + ACenter.X - XRadius, ACenter.Y - YRadius, + ACenter.X + XRadius, ACenter.Y + YRadius, + Thickness + ); + end; + + procedure DoDraw; + var + BGRA: TColorBGRA; + + {$i drawpixelaa.inc} + {$i drawellipseaa.inc} + begin + BGRA := Color.ToBGRA(); + + DrawEllipseAA( + ACenter.X - XRadius, ACenter.Y - YRadius, + ACenter.X + XRadius, ACenter.Y + YRadius, + Thickness + ); + end; + +begin + DoClearAlpha(); + DoDraw(); +end; + +procedure TSimbaImage.DrawCircleAA(ACenter: TPoint; Radius: Integer; Color: TColor; Thickness: Single); +begin + DrawEllipseAA(ACenter, Radius, Radius, Color, Thickness); +end; + constructor TSimbaImage.Create; begin inherited Create(); diff --git a/Source/simba.image_lazbridge.pas b/Source/image/simba.image_lazbridge.pas similarity index 100% rename from Source/simba.image_lazbridge.pas rename to Source/image/simba.image_lazbridge.pas diff --git a/Source/simba.image_textdrawer.pas b/Source/image/simba.image_textdrawer.pas similarity index 100% rename from Source/simba.image_textdrawer.pas rename to Source/image/simba.image_textdrawer.pas diff --git a/Source/script/imports/simbaclasses/simba.import_class_image.pas b/Source/script/imports/simbaclasses/simba.import_class_image.pas index 76f6ac6bb..f8e765a71 100644 --- a/Source/script/imports/simbaclasses/simba.import_class_image.pas +++ b/Source/script/imports/simbaclasses/simba.import_class_image.pas @@ -1203,6 +1203,21 @@ procedure _LapeImage_RowPtrs(const Params: PParamArray; const Result: Pointer); TSimbaImageRowPtrs(Result^) := PSimbaImage(Params^[0])^.RowPtrs(); end; +procedure _LapeImage_DrawLineAA(const Params: PParamArray); LAPE_WRAPPER_CALLING_CONV +begin + PSimbaImage(Params^[0])^.DrawLineAA(PPoint(Params^[1])^, PPoint(Params^[2])^, PColor(Params^[3])^, PSingle(Params^[4])^); +end; + +procedure _LapeImage_DrawEllipseAA(const Params: PParamArray); LAPE_WRAPPER_CALLING_CONV +begin + PSimbaImage(Params^[0])^.DrawEllipseAA(PPoint(Params^[1])^, Pinteger(Params^[2])^, PInteger(Params^[3])^, PColor(Params^[4])^, PSingle(Params^[5])^); +end; + +procedure _LapeImage_DrawCircleAA(const Params: PParamArray); LAPE_WRAPPER_CALLING_CONV +begin + PSimbaImage(Params^[0])^.DrawCircleAA(PPoint(Params^[1])^, Pinteger(Params^[2])^, PColor(Params^[3])^, PSingle(Params^[4])^); +end; + (* TImage.Finder ~~~~~~~~~~~~~~~~~~ @@ -1406,6 +1421,10 @@ procedure ImportSimbaImage(Compiler: TSimbaScript_Compiler); addGlobalFunc('procedure TImage.SaveUnfreedImages(Directory: String); static;', @_LapeImage_SaveUnfreedImages); addGlobalFunc('procedure TImage.FreeOnTerminate(Value: Boolean);', @_LapeImage_FreeOnTerminate); + addGlobalFunc('procedure TImage.DrawLineAA(Start, Stop: TPoint; Color: TColor; Thickness: Single = 1.5);', @_LapeImage_DrawLineAA); + addGlobalFunc('procedure TImage.DrawEllipseAA(ACenter: TPoint; XRadius, YRadius: Integer; Color: TColor; Thickness: Single = 1.5);', @_LapeImage_DrawEllipseAA); + addGlobalFunc('procedure TImage.DrawCircleAA(ACenter: TPoint; Radius: Integer; Color: TColor; Thickness: Single = 1.5);', @_LapeImage_DrawCircleAA); + ImportingSection := ''; end; end;