From 1a225102624f5d187e70b8de7d4c0101345ef737 Mon Sep 17 00:00:00 2001 From: Andreas Hausladen Date: Sat, 7 Sep 2024 14:21:29 +0200 Subject: [PATCH 1/2] Introduction of the TMaskRenderer that allows EraseOutsidePaths to directly modify the target image instead of drawing on a new image and then blending that with the target image. EraseOutsidePaths now also skips the path-masking if the path is a rectangle. It then uses up to 4 FillRect calls for the uncovered areas. --- source/Img32.Draw.pas | 192 ++++++++++++++++++++++-------------- source/Img32.Extra.pas | 86 ++++++++++++---- source/Img32.SVG.Reader.pas | 24 ++--- source/Img32.Vector.pas | 62 ++++++++++++ 4 files changed, 257 insertions(+), 107 deletions(-) diff --git a/source/Img32.Draw.pas b/source/Img32.Draw.pas index 24f3b1a..c0c7748 100644 --- a/source/Img32.Draw.pas +++ b/source/Img32.Draw.pas @@ -93,14 +93,27 @@ TAliasedColorRenderer = class(TCustomColorRenderer) constructor Create(color: TColor32 = clNone32); end; - // TCustomColorRendererCache is used to not create ColorRenderer + // TMaskRenderer masks all pixels inside the OutsideBounds area + // where the alpha[]-array is zero. It is a "negative EraseRenderer". + TMaskRenderer = class(TCustomRenderer) + private + fOutsideBounds: TRect; + protected + procedure RenderProc(x1, x2, y: integer; alpha: PByte); override; + public + constructor Create(const outsideBounds: TRect); + procedure SetOutsideBounds(const outsideBounds: TRect); + end; + + // TCustomRendererCache is used to not create Renderer // objects for every DrawPolygon/DrawLine function call. The color // of the TCustomColorRenderer will be changed by the DrawPolygon/ // DrawLine method. - TCustomColorRendererCache = class(TObject) + TCustomRendererCache = class(TObject) private fColorRenderer: TColorRenderer; fAliasedColorRenderer: TAliasedColorRenderer; + fMaskRenderer: TMaskRenderer; public constructor Create; destructor Destroy; override; @@ -108,6 +121,7 @@ TCustomColorRendererCache = class(TObject) property ColorRenderer: TColorRenderer read fColorRenderer; property AliasedColorRenderer: TAliasedColorRenderer read fAliasedColorRenderer; + property MaskRenderer: TMaskRenderer read fMaskRenderer; end; TEraseRenderer = class(TCustomRenderer) @@ -241,7 +255,7 @@ TBarycentricRenderer = class(TCustomRenderer) miterLimit: double = 2); overload; procedure DrawLine(img: TImage32; const line: TPathD; lineWidth: double; color: TColor32; - rendererCache: TCustomColorRendererCache; + rendererCache: TCustomRendererCache; endStyle: TEndStyle; joinStyle: TJoinStyle = jsAuto; miterLimit: double = 2); overload; procedure DrawLine(img: TImage32; @@ -253,7 +267,7 @@ TBarycentricRenderer = class(TCustomRenderer) endStyle: TEndStyle; joinStyle: TJoinStyle = jsAuto; miterLimit: double = 2); overload; procedure DrawLine(img: TImage32; const lines: TPathsD; - lineWidth: double; color: TColor32; rendererCache: TCustomColorRendererCache; + lineWidth: double; color: TColor32; rendererCache: TCustomRendererCache; endStyle: TEndStyle; joinStyle: TJoinStyle = jsAuto; miterLimit: double = 2); overload; procedure DrawLine(img: TImage32; const lines: TPathsD; @@ -272,12 +286,12 @@ TBarycentricRenderer = class(TCustomRenderer) dashPattern: TArrayOfDouble; patternOffset: PDouble; lineWidth: double; color: TColor32; endStyle: TEndStyle; joinStyle: TJoinStyle = jsAuto; - rendererCache: TCustomColorRendererCache = nil); overload; + rendererCache: TCustomRendererCache = nil); overload; procedure DrawDashedLine(img: TImage32; const lines: TPathsD; dashPattern: TArrayOfDouble; patternOffset: PDouble; lineWidth: double; color: TColor32; endStyle: TEndStyle; joinStyle: TJoinStyle = jsAuto; - rendererCache: TCustomColorRendererCache = nil); overload; + rendererCache: TCustomRendererCache = nil); overload; procedure DrawDashedLine(img: TImage32; const line: TPathD; dashPattern: TArrayOfDouble; patternOffset: PDouble; lineWidth: double; renderer: TCustomRenderer; endStyle: TEndStyle; @@ -304,7 +318,7 @@ TBarycentricRenderer = class(TCustomRenderer) fillRule: TFillRule; color: TColor32); overload; procedure DrawPolygon(img: TImage32; const polygons: TPathsD; fillRule: TFillRule; color: TColor32; - rendererCache: TCustomColorRendererCache); overload; + rendererCache: TCustomRendererCache); overload; procedure DrawPolygon(img: TImage32; const polygons: TPathsD; fillRule: TFillRule; renderer: TCustomRenderer); overload; @@ -339,7 +353,9 @@ TBarycentricRenderer = class(TCustomRenderer) const mask: TArrayOfByte; color: TColor32 = clBlack32); procedure Rasterize(const paths: TPathsD; - const clipRec: TRect; fillRule: TFillRule; renderer: TCustomRenderer); + const clipRec: TRect; fillRule: TFillRule; renderer: TCustomRenderer); overload; + procedure Rasterize(img: TImage32; const paths: TPathsD; + const clipRec: TRect; fillRule: TFillRule; renderer: TCustomRenderer); overload; implementation @@ -1246,7 +1262,10 @@ procedure Rasterize(const paths: TPathsD; const clipRec: TRect; Types.IntersectRect(clipRec2, clipRec, GetBounds(paths)); if IsEmptyRect(clipRec2) then Exit; - paths2 := TranslatePath(paths, -clipRec2.Left, -clipRec2.Top); + if (clipRec2.Left = 0) and (clipRec2.Top = 0) then + paths2 := paths + else + paths2 := TranslatePath(paths, -clipRec2.Left, -clipRec2.Top); // Delphi's Round() function is *much* faster than Trunc(), // and even a little faster than Trunc() above (except @@ -1316,6 +1335,17 @@ procedure Rasterize(const paths: TPathsD; const clipRec: TRect; FreeMem(byteBuffer); end; end; +// ------------------------------------------------------------------------------ + +procedure Rasterize(img: TImage32; const paths: TPathsD; + const clipRec: TRect; fillRule: TFillRule; renderer: TCustomRenderer); +begin + if renderer.Initialize(img) then + begin + Rasterize(paths, clipRec, fillRule, renderer); + renderer.NotifyChange; + end; +end; // ------------------------------------------------------------------------------ // TAbstractRenderer @@ -1549,25 +1579,82 @@ procedure TAliasedColorRenderer.RenderProc(x1, x2, y: integer; alpha: PByte); end; // ------------------------------------------------------------------------------ -// TCustomColorRendererCache +// TMaskRenderer +// ------------------------------------------------------------------------------ + +constructor TMaskRenderer.Create(const outsideBounds: TRect); +begin + inherited Create; + fOutsideBounds := outsideBounds; +end; + +procedure TMaskRenderer.SetOutsideBounds(const outsideBounds: TRect); +begin + fOutsideBounds := outsideBounds; +end; + +procedure TMaskRenderer.RenderProc(x1, x2, y: integer; alpha: PByte); +var + p: PColor32; + i: integer; +begin + // clip to fOutsideBounds + if (y < fOutsideBounds.Top) or (y > fOutsideBounds.Bottom) then Exit; + + if x1 < fOutsideBounds.Left then + begin + inc(alpha, fOutsideBounds.Left - x1); + x1 := fOutsideBounds.Left; + end; + if x2 > fOutsideBounds.Right then + x2 := fOutsideBounds.Right; + + p := GetDstPixel(fOutsideBounds.Left, y); + + // Clear the area before x1 (inside OutsideBounds) + FillChar(p^, (x1 - fOutsideBounds.Left) * SizeOf(TColor32), 0); + inc(p, x1 - fOutsideBounds.Left); + + // Fill the area between x1 and x2 + for i := x1 to x2 do + begin + if p^ <> 0 then + begin + if Ord(alpha^) = 0 then + p^ := 0 + else if Ord(alpha^) <> 255 then + p^ := BlendMask(p^, Ord(alpha^) shl 24); + end; + inc(p); + inc(alpha); + end; + + // Clear the area after x2 (inside OutsideBounds) + FillChar(p^, (fOutsideBounds.Right - x2) * SizeOf(TColor32), 0); +end; + +// ------------------------------------------------------------------------------ +// TCustomRendererCache // ------------------------------------------------------------------------------ -constructor TCustomColorRendererCache.Create; +constructor TCustomRendererCache.Create; begin inherited Create; fColorRenderer := TColorRenderer.Create; fAliasedColorRenderer := TAliasedColorRenderer.Create; + fMaskRenderer := TMaskRenderer.Create(Rect(0, 0, 0, 0)); end; // ------------------------------------------------------------------------------ -destructor TCustomColorRendererCache.Destroy; +destructor TCustomRendererCache.Destroy; begin fColorRenderer.Free; fAliasedColorRenderer.Free; + fMaskRenderer.Free; end; // ------------------------------------------------------------------------------ -function TCustomColorRendererCache.GetColorRenderer(color: TColor32): TColorRenderer; +function TCustomRendererCache.GetColorRenderer(color: TColor32): TColorRenderer; begin Result := fColorRenderer; Result.SetColor(color); @@ -2200,7 +2287,7 @@ procedure DrawLine(img: TImage32; const line: TPathD; lineWidth: double; // ------------------------------------------------------------------------------ procedure DrawLine(img: TImage32; const line: TPathD; lineWidth: double; - color: TColor32; rendererCache: TCustomColorRendererCache; + color: TColor32; rendererCache: TCustomRendererCache; endStyle: TEndStyle; joinStyle: TJoinStyle; miterLimit: double); var lines: TPathsD; @@ -2255,7 +2342,7 @@ procedure DrawLine(img: TImage32; const lines: TPathsD; // ------------------------------------------------------------------------------ procedure DrawLine(img: TImage32; const lines: TPathsD; - lineWidth: double; color: TColor32; rendererCache: TCustomColorRendererCache; + lineWidth: double; color: TColor32; rendererCache: TCustomRendererCache; endStyle: TEndStyle; joinStyle: TJoinStyle; miterLimit: double); var cr: TCustomColorRenderer; @@ -2283,11 +2370,7 @@ procedure DrawLine(img: TImage32; const lines: TPathsD; if (not assigned(lines)) or (not assigned(renderer)) then exit; if (lineWidth < MinStrokeWidth) then lineWidth := MinStrokeWidth; lines2 := RoughOutline(lines, lineWidth, joinStyle, endStyle, miterLimit); - if renderer.Initialize(img) then - begin - Rasterize(lines2, img.bounds, frNonZero, renderer); - renderer.NotifyChange; - end; + Rasterize(img, lines2, img.bounds, frNonZero, renderer); end; // ------------------------------------------------------------------------------ @@ -2303,11 +2386,7 @@ procedure DrawInvertedLine(img: TImage32; lines2 := RoughOutline(lines, lineWidth, joinStyle, endStyle, 2); ir := TInverseRenderer.Create; try - if ir.Initialize(img) then - begin - Rasterize(lines2, img.bounds, frNonZero, ir); - ir.NotifyChange; - end; + Rasterize(img, lines2, img.bounds, frNonZero, ir); finally ir.free; end; @@ -2317,7 +2396,7 @@ procedure DrawInvertedLine(img: TImage32; procedure DrawDashedLine(img: TImage32; const line: TPathD; dashPattern: TArrayOfDouble; patternOffset: PDouble; lineWidth: double; color: TColor32; endStyle: TEndStyle; joinStyle: TJoinStyle; - rendererCache: TCustomColorRendererCache); + rendererCache: TCustomRendererCache); var lines: TPathsD; cr: TColorRenderer; @@ -2350,11 +2429,7 @@ procedure DrawDashedLine(img: TImage32; const line: TPathD; cr := TColorRenderer.Create(color) else cr := rendererCache.GetColorRenderer(color); try - if cr.Initialize(img) then - begin - Rasterize(lines, img.bounds, frNonZero, cr); - cr.NotifyChange; - end; + Rasterize(img, lines, img.bounds, frNonZero, cr); finally if rendererCache = nil then cr.free; @@ -2365,7 +2440,7 @@ procedure DrawDashedLine(img: TImage32; const line: TPathD; procedure DrawDashedLine(img: TImage32; const lines: TPathsD; dashPattern: TArrayOfDouble; patternOffset: PDouble; lineWidth: double; color: TColor32; endStyle: TEndStyle; joinStyle: TJoinStyle; - rendererCache: TCustomColorRendererCache); + rendererCache: TCustomRendererCache); var i: integer; begin @@ -2393,11 +2468,7 @@ procedure DrawDashedLine(img: TImage32; const line: TPathD; lines := GetDashedPath(line, endStyle = esPolygon, dashPattern, patternOffset); if Length(lines) = 0 then Exit; lines := RoughOutline(lines, lineWidth, joinStyle, endStyle); - if renderer.Initialize(img) then - begin - Rasterize(lines, img.bounds, frNonZero, renderer); - renderer.NotifyChange; - end; + Rasterize(img, lines, img.bounds, frNonZero, renderer); end; // ------------------------------------------------------------------------------ @@ -2434,11 +2505,7 @@ procedure DrawInvertedDashedLine(img: TImage32; lines := RoughOutline(lines, lineWidth, joinStyle, endStyle); renderer := TInverseRenderer.Create; try - if renderer.Initialize(img) then - begin - Rasterize(lines, img.bounds, frNonZero, renderer); - renderer.NotifyChange; - end; + Rasterize(img, lines, img.bounds, frNonZero, renderer); finally renderer.Free; end; @@ -2479,11 +2546,7 @@ procedure DrawPolygon(img: TImage32; const polygon: TPathD; if (not assigned(polygon)) or (not assigned(renderer)) then exit; setLength(polygons, 1); polygons[0] := polygon; - if renderer.Initialize(img) then - begin - Rasterize(polygons, img.Bounds, fillRule, renderer); - renderer.NotifyChange; - end; + Rasterize(img, polygons, img.Bounds, fillRule, renderer); end; // ------------------------------------------------------------------------------ @@ -2497,11 +2560,7 @@ procedure DrawPolygon(img: TImage32; const polygons: TPathsD; cr := TColorRenderer.Create(color) else cr := TAliasedColorRenderer.Create(color); try - if cr.Initialize(img) then - begin - Rasterize(polygons, img.bounds, fillRule, cr); - cr.NotifyChange; - end; + Rasterize(img, polygons, img.bounds, fillRule, cr); finally cr.free; end; @@ -2510,7 +2569,7 @@ procedure DrawPolygon(img: TImage32; const polygons: TPathsD; procedure DrawPolygon(img: TImage32; const polygons: TPathsD; fillRule: TFillRule; color: TColor32; - rendererCache: TCustomColorRendererCache); + rendererCache: TCustomRendererCache); var cr: TCustomColorRenderer; begin @@ -2523,11 +2582,7 @@ procedure DrawPolygon(img: TImage32; const polygons: TPathsD; cr := rendererCache.ColorRenderer else cr := rendererCache.AliasedColorRenderer; cr.SetColor(color); - if cr.Initialize(img) then - begin - Rasterize(polygons, img.bounds, fillRule, cr); - cr.NotifyChange; - end; + Rasterize(img, polygons, img.bounds, fillRule, cr); end; end; // ------------------------------------------------------------------------------ @@ -2536,11 +2591,7 @@ procedure DrawPolygon(img: TImage32; const polygons: TPathsD; fillRule: TFillRule; renderer: TCustomRenderer); begin if (not assigned(polygons)) or (not assigned(renderer)) then exit; - if renderer.Initialize(img) then - begin - Rasterize(polygons, img.bounds, fillRule, renderer); - renderer.NotifyChange; - end; + Rasterize(img, polygons, img.bounds, fillRule, renderer); end; // ------------------------------------------------------------------------------ @@ -2564,11 +2615,7 @@ procedure DrawInvertedPolygon(img: TImage32; const polygons: TPathsD; if not assigned(polygons) then exit; cr := TInverseRenderer.Create; try - if cr.Initialize(img) then - begin - Rasterize(polygons, img.bounds, fillRule, cr); - cr.NotifyChange; - end; + Rasterize(img, polygons, img.bounds, fillRule, cr); finally cr.free; end; @@ -2594,8 +2641,7 @@ procedure DrawPolygon_ClearType(img: TImage32; const polygons: TPathsD; tmpPolygons := ScalePath(tmpPolygons, 3, 1); cr := TColorRenderer.Create(clBlack32); try - if cr.Initialize(tmpImg) then - Rasterize(tmpPolygons, tmpImg.bounds, fillRule, cr); + Rasterize(tmpImg, tmpPolygons, tmpImg.bounds, fillRule, cr); finally cr.Free; end; @@ -2626,11 +2672,7 @@ procedure ErasePolygon(img: TImage32; const polygons: TPathsD; begin er := TEraseRenderer.Create; try - if er.Initialize(img) then - begin - Rasterize(polygons, img.bounds, fillRule, er); - er.NotifyChange; - end; + Rasterize(img, polygons, img.bounds, fillRule, er); finally er.Free; end; diff --git a/source/Img32.Extra.pas b/source/Img32.Extra.pas index 1380658..342ae0b 100644 --- a/source/Img32.Extra.pas +++ b/source/Img32.Extra.pas @@ -108,7 +108,7 @@ procedure EraseOutsidePath(img: TImage32; const path: TPathD; fillRule: TFillRule; const outsideBounds: TRect); procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD; fillRule: TFillRule; const outsideBounds: TRect; - rendererCache: TCustomColorRendererCache = nil); overload; + rendererCache: TCustomRendererCache = nil); overload; procedure Draw3D(img: TImage32; const polygon: TPathD; fillRule: TFillRule; height, blurRadius: double; @@ -938,43 +938,89 @@ procedure EraseInsidePaths(img: TImage32; const paths: TPathsD; fillRule: TFillR end; //------------------------------------------------------------------------------ +procedure EraseOutsideRect(img: TImage32; const r, outsideBounds: TRect); +begin + // Fill the parts, that are in outsideBounds but not in r with zeros + + // whole top block + if r.Top > outsideBounds.Top then + img.FillRect(Rect(outsideBounds.Left, outsideBounds.Top, outsideBounds.Right, r.Top - 1), 0); + // whole bottom block + if r.Bottom < outsideBounds.Bottom then + img.FillRect(Rect(outsideBounds.Left, r.Bottom + 1, outsideBounds.Right, outsideBounds.Bottom), 0); + + // remaining left block + if r.Left > outsideBounds.Left then + img.FillRect(Rect(outsideBounds.Left, r.Top, r.Left - 1, r.Bottom), 0); + // remaining right block + if r.Right < outsideBounds.Right then + img.FillRect(Rect(r.Right + 1, r.Top, outsideBounds.Right, r.Bottom), 0); +end; +//------------------------------------------------------------------------------ + procedure EraseOutsidePath(img: TImage32; const path: TPathD; fillRule: TFillRule; const outsideBounds: TRect); var - mask: TImage32; - p: TPathD; - w,h: integer; + w, h: integer; + renderer: TMaskRenderer; + r: TRect; + polygons: TPathsD; begin if not assigned(path) then Exit; - RectWidthHeight(outsideBounds, w,h); - mask := TImage32.Create(w, h); + RectWidthHeight(outsideBounds, w, h); + if (w <= 0) or (h <= 0) then Exit; + + // We can skip the costly polygon rasterization if the path is + // a rectangle + if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(path, r) then + begin + EraseOutsideRect(img, r, outsideBounds); + Exit; + end; + + renderer := TMaskRenderer.Create(outsideBounds); try - p := TranslatePath(path, -outsideBounds.Left, -outsideBounds.top); - DrawPolygon(mask, p, fillRule, clBlack32); - img.CopyBlend(mask, mask.Bounds, outsideBounds, BlendMaskLine); + SetLength(polygons, 1); + polygons[0] := path; + Rasterize(img, polygons, outsideBounds, fillRule, renderer); finally - mask.Free; + renderer.Free; end; end; //------------------------------------------------------------------------------ procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD; fillRule: TFillRule; const outsideBounds: TRect; - rendererCache: TCustomColorRendererCache); + rendererCache: TCustomRendererCache); var - mask: TImage32; - pp: TPathsD; - w,h: integer; + w, h: integer; + renderer: TMaskRenderer; + r: TRect; begin if not assigned(paths) then Exit; - RectWidthHeight(outsideBounds, w,h); - mask := TImage32.Create(w, h); + RectWidthHeight(outsideBounds, w, h); + if (w <= 0) or (h <= 0) then Exit; + + // We can skip the costly polygon rasterization if the path is + // a rectangle. + if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(paths, r) then + begin + EraseOutsideRect(img, r, outsideBounds); + Exit; + end; + + if rendererCache = nil then + renderer := TMaskRenderer.Create(outsideBounds) + else + begin + renderer := rendererCache.MaskRenderer; + renderer.SetOutsideBounds(outsideBounds); + end; try - pp := TranslatePath(paths, -outsideBounds.Left, -outsideBounds.top); - DrawPolygon(mask, pp, fillRule, clBlack32, rendererCache); - img.CopyBlend(mask, mask.Bounds, outsideBounds, BlendMaskLine); + Rasterize(img, paths, outsideBounds, fillRule, renderer); finally - mask.Free; + if rendererCache = nil then + renderer.Free; end; end; //------------------------------------------------------------------------------ diff --git a/source/Img32.SVG.Reader.pas b/source/Img32.SVG.Reader.pas index 5cbb824..7316ca4 100644 --- a/source/Img32.SVG.Reader.pas +++ b/source/Img32.SVG.Reader.pas @@ -120,7 +120,7 @@ TSvgReader = class fClassStyles : TClassStylesList; fLinGradRenderer : TLinearGradientRenderer; fRadGradRenderer : TSvgRadialGradientRenderer; - fCustomColorRendererCache: TCustomColorRendererCache; + fCustomRendererCache: TCustomRendererCache; fRootElement : TSvgElement; fFontCache : TFontCache; fUsePropScale : Boolean; @@ -1016,9 +1016,9 @@ procedure TGroupElement.Draw(image: TImage32; drawDat: TDrawData); begin if fDrawData.fillRule = frNegative then EraseOutsidePaths(tmpImg, clipPaths, frNonZero, clipRec, - fReader.fCustomColorRendererCache) else + fReader.fCustomRendererCache) else EraseOutsidePaths(tmpImg, clipPaths, fDrawData.fillRule, clipRec, - fReader.fCustomColorRendererCache); + fReader.fCustomRendererCache); end; image.CopyBlend(tmpImg, clipRec, clipRec, BlendToAlphaLine); finally @@ -2122,7 +2122,7 @@ procedure TFeGaussElement.Apply; // FastGaussianBlur is a very good approximation and also very much faster. // Empirically stdDev * PI/4 more closely emulates other renderers. - FastGaussianBlur(dstImg, dstRec, Ceil(stdDev * PI/4 * ParentFilterEl.fScale)); + FastGaussianBlur(dstImg, dstRec, Ceil(stdDev * (PI/4) * ParentFilterEl.fScale)); end; //------------------------------------------------------------------------------ @@ -2395,9 +2395,9 @@ procedure TShapeElement.Draw(image: TImage32; drawDat: TDrawData); begin if fDrawData.fillRule = frNegative then EraseOutsidePaths(img, clipPaths, frNonZero, clipRec2, - fReader.fCustomColorRendererCache) else + fReader.fCustomRendererCache) else EraseOutsidePaths(img, clipPaths, fDrawData.fillRule, clipRec2, - fReader.fCustomColorRendererCache); + fReader.fCustomRendererCache); end; if usingTempImage and (img <> image) then @@ -2540,14 +2540,14 @@ procedure TShapeElement.DrawFilled(img: TImage32; drawDat: TDrawData); else if drawDat.fillColor = clInvalid then begin DrawPolygon(img, fillPaths, drawDat.fillRule, clBlack32, - fReader.fCustomColorRendererCache); + fReader.fCustomRendererCache); end else with drawDat do begin DrawPolygon(img, fillPaths, fillRule, MergeColorAndOpacity(fillColor, fillOpacity), - fReader.fCustomColorRendererCache); + fReader.fCustomRendererCache); end; end; //------------------------------------------------------------------------------ @@ -2629,7 +2629,7 @@ procedure TShapeElement.DrawStroke(img: TImage32; strokePaths := MatrixApply(drawPathsO, drawDat.matrix); DrawDashedLine(img, strokePaths, dashArray, @dashOffset, sw * scale, strokeClr, endStyle, jsAuto, - fReader.fCustomColorRendererCache); + fReader.fCustomRendererCache); Exit; end; strokePaths := RoughOutline(drawPathsO, sw, joinStyle, endStyle, lim); @@ -2662,7 +2662,7 @@ procedure TShapeElement.DrawStroke(img: TImage32; end; end else begin - DrawPolygon(img, strokePaths, frNonZero, strokeClr, fReader.fCustomColorRendererCache); + DrawPolygon(img, strokePaths, frNonZero, strokeClr, fReader.fCustomRendererCache); end; end; @@ -4902,7 +4902,7 @@ constructor TSvgReader.Create; fClassStyles := TClassStylesList.Create; fLinGradRenderer := TLinearGradientRenderer.Create; fRadGradRenderer := TSvgRadialGradientRenderer.Create; - fCustomColorRendererCache := TCustomColorRendererCache.Create; + fCustomRendererCache := TCustomRendererCache.Create; fIdList := TStringList.Create; fIdList.Duplicates := dupIgnore; fIdList.CaseSensitive := false; @@ -4925,7 +4925,7 @@ destructor TSvgReader.Destroy; fLinGradRenderer.Free; fRadGradRenderer.Free; - fCustomColorRendererCache.Free; + fCustomRendererCache.Free; FreeAndNil(fFontCache); fSimpleDrawList.Free; diff --git a/source/Img32.Vector.pas b/source/Img32.Vector.pas index 85caa15..28d8d75 100644 --- a/source/Img32.Vector.pas +++ b/source/Img32.Vector.pas @@ -275,6 +275,11 @@ interface function IsClockwise(const path: TPathD): Boolean; + // IsSimpleRectanglePath returns true if the specified path has only one polygon + // with 4 points that describe a rectangle. + function IsSimpleRectanglePath(const paths: TPathsD; var R: TRect): Boolean; overload; + function IsSimpleRectanglePath(const path: TPathD; var R: TRect): Boolean; overload; + function Area(const path: TPathD): Double; overload; function RectsEqual(const rec1, rec2: TRect): Boolean; @@ -791,6 +796,63 @@ function IsClockwise(const path: TPathD): Boolean; end; //------------------------------------------------------------------------------ +function IsSimpleRectanglePath(const path: TPathD; var R: TRect): Boolean; +type + TLastMatch = (lmX, lmY); +var + i: Integer; + lastMatch: TLastMatch; +begin + Result := False; + // If we have a single path with 4 points, it could be a rectangle + if Length(path) = 4 then + begin + // For a rectangle the X and Y coordinates of the points alternate + // in being equal + if path[0].X = path[3].X then + lastMatch := lmX + else if path[0].Y = path[3].Y then + lastMatch := lmY + else + Exit; + + R.Left := Trunc(path[0].X); + R.Top := Trunc(path[0].Y); + R.Right := Ceil(path[0].X); + R.Bottom := Ceil(path[0].Y); + for i := 1 to 3 do + begin + case lastMatch of + lmY: // now the X-coordinates must be equal + begin + if path[i].X <> path[i - 1].X then Exit; + lastMatch := lmX; + R.Top := Min(R.Top, Trunc(path[i].Y)); + R.Bottom := Max(R.Bottom, Ceil(path[i].Y)); + end; + lmX: // now the Y-coordinates must be equal + begin + if path[i].Y <> path[i - 1].Y then Exit; + lastMatch := lmY; + R.Left := Min(R.Left, Trunc(path[i].X)); + R.Right := Max(R.Right, Ceil(path[i].X)); + end; + end; + end; + Result := True; + end; +end; + +//------------------------------------------------------------------------------ +function IsSimpleRectanglePath(const paths: TPathsD; var R: TRect): Boolean; +begin + if (Length(paths) = 1) and (Length(paths[0]) = 4) then + Result := IsSimpleRectanglePath(paths[0], r) + else + Result := False; +end; +//------------------------------------------------------------------------------ + function Area(const path: TPathD): Double; var i, j, highI: Integer; From 6dc31a1ac2adc88764ab2594cf0d61c839a8f8c5 Mon Sep 17 00:00:00 2001 From: Andreas Hausladen Date: Sat, 7 Sep 2024 14:22:09 +0200 Subject: [PATCH 2/2] Some micro optimizations for BlendMaskLine and ReduceOpacity --- source/Img32.pas | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/source/Img32.pas b/source/Img32.pas index d39a94a..5d49f35 100644 --- a/source/Img32.pas +++ b/source/Img32.pas @@ -1269,6 +1269,8 @@ function BlendMask(bgColor, alphaMask: TColor32): TColor32; {$RANGECHECKS OFF} // negative array index is used procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint); +label + SkipNone32; var a: byte; begin @@ -1283,6 +1285,13 @@ procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint); // common values. while width < 0 do begin + // MulTable[0, fgA] -> 0, if bgColor is already 0 => skip + while PStaticARGBArray(bgColor)[width].Color = 0 do + begin +SkipNone32: + inc(width); + if width = 0 then exit; + end; a := PStaticARGBArray(bgColor)[width].A; // MulTable[0, fgA] -> 0 => replace color with 0 while a = 0 do @@ -1290,6 +1299,8 @@ procedure BlendMaskLine(bgColor, alphaMask: PColor32; width: nativeint); PStaticColor32Array(bgColor)[width] := 0; inc(width); if width = 0 then exit; + if PStaticARGBArray(bgColor)[width].Color = 0 then + goto SkipNone32; a := PStaticARGBArray(bgColor)[width].A; end; // MulTable[255, fgA] -> fgA => replace alpha with fgA @@ -3707,12 +3718,15 @@ procedure TImage32.ReduceOpacity(opacity: Byte); var i: Integer; c: PARGB; + a: Byte; begin if opacity = 255 then Exit; c := PARGB(PixelBase); for i := 0 to Width * Height -1 do begin - c.A := MulTable[c.A, opacity]; + a := c.A; + if a <> 0 then + c.A := MulTable[a, opacity]; inc(c); end; Changed; @@ -3723,19 +3737,24 @@ procedure TImage32.ReduceOpacity(opacity: Byte; rec: TRect); var i,j, rw: Integer; c: PARGB; + a: Byte; + lineOffsetInBytes: integer; begin Types.IntersectRect(rec, rec, bounds); if IsEmptyRect(rec) then Exit; rw := RectWidth(rec); c := @Pixels[rec.Top * Width + rec.Left]; - for i := rec.Top to rec.Bottom -1 do + lineOffsetInBytes := (Width - rw) * SizeOf(TARGB); + for i := rec.Top to rec.Bottom - 1 do begin for j := 1 to rw do begin - c.A := MulTable[c.A, opacity]; + a := c.A; + if a <> 0 then + c.A := MulTable[a, opacity]; inc(c); end; - inc(c, Width - rw); + inc(PByte(c), lineOffsetInBytes); end; Changed; end;