diff --git a/Ext/SVGIconImageList/Image32/source/Img32.Draw.pas b/Ext/SVGIconImageList/Image32/source/Img32.Draw.pas index 24f3b1a..252d396 100644 --- a/Ext/SVGIconImageList/Image32/source/Img32.Draw.pas +++ b/Ext/SVGIconImageList/Image32/source/Img32.Draw.pas @@ -59,6 +59,17 @@ TCustomRenderer = class {$IFDEF ABSTRACT_CLASSES} abstract {$ENDIF} // RenderProc: x & y refer to pixel coords in the destination image and // where x1 is the start (and left) and x2 is the end of the render procedure RenderProc(x1, x2, y: integer; alpha: PByte); virtual; abstract; + // RenderProcSkip: is called for every skipped line block if + // SupportsRenderProcSkip=True and the Rasterize() function skips scanlines. + procedure RenderProcSkip(const skippedRect: TRect); virtual; + // SetClipRect is called by the Rasterize() function with the + // rasterization clipRect. The default implementation does nothing. + procedure SetClipRect(const clipRect: TRect); virtual; + // If SupportsRenderProcSkip returns True the Rasterize() function + // will call RenderProcSkip() for every scanline where it didn't have + // anything to rasterize. + function SupportsRenderProcSkip: Boolean; virtual; + property ImgWidth: integer read fImgWidth; property ImgHeight: integer read fImgHeight; property ImgBase: Pointer read fImgBase; @@ -93,14 +104,27 @@ TAliasedColorRenderer = class(TCustomColorRenderer) constructor Create(color: TColor32 = clNone32); end; - // TCustomColorRendererCache is used to not create ColorRenderer + // TMaskRenderer masks all pixels inside the clipRect area + // where the alpha[]-array is zero. + TMaskRenderer = class(TCustomRenderer) + private + fClipRect: TRect; + protected + procedure SetClipRect(const clipRect: TRect); override; + procedure RenderProc(x1, x2, y: integer; alpha: PByte); override; + procedure RenderProcSkip(const skippedRect: TRect); override; + function SupportsRenderProcSkip: Boolean; override; + 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 +132,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 +266,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 +278,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 +297,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 +329,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 +364,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 @@ -1237,16 +1264,28 @@ procedure Rasterize(const paths: TPathsD; const clipRec: TRect; scanlines: TArrayOfScanline; fragments: PFragment; scanline: PScanline; + skippedScanlines: integer; + skipRenderer: boolean; // FPC generates wrong code if "count" isn't NativeInt FillByteBuffer: procedure(byteBuffer: PByte; windingAccum: PDouble; count: nativeint); begin // See also https://nothings.org/gamedev/rasterize/ if not assigned(renderer) then Exit; + renderer.SetClipRect(clipRec); + skipRenderer := renderer.SupportsRenderProcSkip; + Types.IntersectRect(clipRec2, clipRec, GetBounds(paths)); - if IsEmptyRect(clipRec2) then Exit; + if IsEmptyRect(clipRec2) then + begin + if skipRenderer then renderer.RenderProcSkip(clipRec); + Exit; + end; - 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 @@ -1280,18 +1319,37 @@ procedure Rasterize(const paths: TPathsD; const clipRec: TRect; {$ENDIF} FillByteBuffer := FillByteBufferNegative; else + if skipRenderer then renderer.RenderProcSkip(clipRec); Exit; end; + // Notify the renderer about the parts at the top + // that we didn't touch. + if skipRenderer and (clipRec2.Top > clipRec.Top) then + begin + renderer.RenderProcSkip(Rect(clipRec.Left, clipRec.Top, + clipRec.Right, clipRec2.Top - 1)); + end; + + skippedScanlines := 0; scanline := @scanlines[0]; for i := 0 to high(scanlines) do begin if scanline.fragCnt = 0 then begin inc(scanline); + if skipRenderer then inc(skippedScanlines); Continue; end; + // If we have skipped some scanlines, we must notify the renderer. + if skipRenderer and (skippedScanlines > 0) then + begin + renderer.RenderProcSkip(Rect(clipRec.Left, clipRec2.Top + i - skippedScanlines, + clipRec.Right, clipRec2.Top + i - 1)); + skippedScanlines := 0; + end; + // process each scanline to fill the winding count accumulation buffer ProcessScanlineFragments(scanline^, fragments, windingAccum); // it's faster to process only the modified sub-array of windingAccum @@ -1310,12 +1368,34 @@ procedure Rasterize(const paths: TPathsD; const clipRec: TRect; inc(scanline); end; + + // Notify the renderer about the last skipped scanlines + if skipRenderer then + begin + clipRec2.Bottom := clipRec2.top + High(scanlines) - skippedScanlines; + if clipRec2.Bottom < clipRec.Bottom then + begin + renderer.RenderProcSkip(Rect(clipRec.Left, clipRec2.Bottom + 1, + clipRec.Right, clipRec.Bottom)); + end; + end; finally // cleanup and deallocate memory FreeMem(fragments); 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 @@ -1362,6 +1442,24 @@ function TCustomRenderer.GetDstPixel(x, y: integer): Pointer; Result := fCurrLinePtr; inc(PByte(Result), x * fPixelSize); end; +// ------------------------------------------------------------------------------ + +procedure TCustomRenderer.SetClipRect(const clipRect: TRect); +begin + // default: do nothing +end; +// ------------------------------------------------------------------------------ + +procedure TCustomRenderer.RenderProcSkip(const skippedRect: TRect); +begin + // default: do nothing +end; +// ------------------------------------------------------------------------------ + +function TCustomRenderer.SupportsRenderProcSkip: Boolean; +begin + Result := False; +end; // ------------------------------------------------------------------------------ // TCustomColorRenderer @@ -1549,25 +1647,121 @@ procedure TAliasedColorRenderer.RenderProc(x1, x2, y: integer; alpha: PByte); end; // ------------------------------------------------------------------------------ -// TCustomColorRendererCache +// TMaskRenderer // ------------------------------------------------------------------------------ -constructor TCustomColorRendererCache.Create; +procedure TMaskRenderer.SetClipRect(const clipRect: TRect); +begin + fClipRect := clipRect; + // clipping to the image size + if fClipRect.Left < 0 then fClipRect.Left := 0; + if fClipRect.Top < 0 then fClipRect.Top := 0; + if fClipRect.Right > fImgWidth then fClipRect.Right := fImgWidth; + if fClipRect.Bottom > fImgHeight then fClipRect.Bottom := fImgHeight; +end; +// ------------------------------------------------------------------------------ + +procedure TMaskRenderer.RenderProc(x1, x2, y: integer; alpha: PByte); +var + p: PColor32; + i: integer; +begin + // CopyBlend excludes ClipRect.Right/Bottom, so we also + // need to exclude it. + if (y < fClipRect.Top) or (y >= fClipRect.Bottom) then Exit; + if x2 >= fClipRect.Right then x2 := fClipRect.Right - 1; + + if x1 < fClipRect.Left then + begin + inc(alpha, fClipRect.Left - x1); + x1 := fClipRect.Left; + end; + + p := GetDstPixel(fClipRect.Left, y); + + // Clear the area before x1 (inside OutsideBounds) + FillChar(p^, (x1 - fClipRect.Left) * SizeOf(TColor32), 0); + inc(p, x1 - fClipRect.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^, (fClipRect.Right - (x2 + 1)) * SizeOf(TColor32), 0); +end; +// ------------------------------------------------------------------------------ + +procedure TMaskRenderer.RenderProcSkip(const skippedRect: TRect); +var + i, h, w: integer; + p: PColor32; + r: TRect; +begin + r := skippedRect; + if r.Left < fClipRect.Left then r.Left := fClipRect.Left; + if r.Top < fClipRect.Top then r.Top := fClipRect.Top; + // CopyBlend excludes ClipRect.Right/Bottom, so we also + // need to exclude it. + if r.Right >= fClipRect.Right then r.Right := fClipRect.Right - 1; + if r.Bottom >= fClipRect.Bottom then r.Bottom := fClipRect.Bottom - 1; + + if r.Right < r.Left then Exit; + if r.Bottom < r.Top then Exit; + + w := r.Right - r.Left + 1; + h := r.Bottom - r.Top + 1; + p := GetDstPixel(r.Left, r.Top); + if w = fImgWidth then + FillChar(p^, w * h * SizeOf(TColor32), 0) + else + begin + for i := 1 to h do + begin + FillChar(p^, w * SizeOf(TColor32), 0); + inc(p, fImgWidth); + end; + end; +end; + +// ------------------------------------------------------------------------------ +function TMaskRenderer.SupportsRenderProcSkip: Boolean; +begin + Result := True; +end; + +// ------------------------------------------------------------------------------ +// TCustomRendererCache +// ------------------------------------------------------------------------------ + +constructor TCustomRendererCache.Create; begin inherited Create; fColorRenderer := TColorRenderer.Create; fAliasedColorRenderer := TAliasedColorRenderer.Create; + fMaskRenderer := TMaskRenderer.Create; 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 +2394,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 +2449,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 +2477,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 +2493,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 +2503,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 +2536,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 +2547,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 +2575,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 +2612,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 +2653,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 +2667,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 +2676,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 +2689,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 +2698,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 +2722,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 +2748,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 +2779,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/Ext/SVGIconImageList/Image32/source/Img32.Extra.pas b/Ext/SVGIconImageList/Image32/source/Img32.Extra.pas index 1380658..b0e7f62 100644 --- a/Ext/SVGIconImageList/Image32/source/Img32.Extra.pas +++ b/Ext/SVGIconImageList/Image32/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,86 @@ 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; 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 + else + renderer := rendererCache.MaskRenderer; 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/Ext/SVGIconImageList/Image32/source/Img32.SVG.Reader.pas b/Ext/SVGIconImageList/Image32/source/Img32.SVG.Reader.pas index 5cbb824..7316ca4 100644 --- a/Ext/SVGIconImageList/Image32/source/Img32.SVG.Reader.pas +++ b/Ext/SVGIconImageList/Image32/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/Ext/SVGIconImageList/Image32/source/Img32.Vector.pas b/Ext/SVGIconImageList/Image32/source/Img32.Vector.pas index 85caa15..bcc8a20 100644 --- a/Ext/SVGIconImageList/Image32/source/Img32.Vector.pas +++ b/Ext/SVGIconImageList/Image32/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; @@ -1915,7 +1977,7 @@ procedure ConcatPaths(var dstPath: TPathD; const paths: TPathsD); pathLen := Length(paths[i]); if pathLen > 0 then begin - // Skip the start-point if is matches the previous path's end-point + // Skip the start-point if it matches the previous path's end-point if (i > 0) and PointsEqual(paths[i][0], paths[i -1][high(paths[i -1])]) then dec(pathLen); inc(len, pathLen); @@ -1931,14 +1993,19 @@ procedure ConcatPaths(var dstPath: TPathD; const paths: TPathsD); if pathLen > 0 then begin offset := 0; - // Skip the start-point if is matches the previous path's end-point + // Skip the start-point if it matches the previous path's end-point if (i > 0) and PointsEqual(paths[i][0], paths[i -1][high(paths[i -1])]) then begin dec(pathLen); offset := 1; end; - Move(paths[i][offset], dstPath[len], pathLen * SizeOf(TPointD)); - inc(len, pathLen); + // Skip if we have a path with only one point and that point also matches + // the previous path's end-point. + if pathLen > 0 then + begin + Move(paths[i][offset], dstPath[len], pathLen * SizeOf(TPointD)); + inc(len, pathLen); + end; end; end; end; diff --git a/Ext/SVGIconImageList/Image32/source/Img32.pas b/Ext/SVGIconImageList/Image32/source/Img32.pas index d39a94a..5d49f35 100644 --- a/Ext/SVGIconImageList/Image32/source/Img32.pas +++ b/Ext/SVGIconImageList/Image32/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; diff --git a/Ext/SVGIconImageList/Source/FMX.SVGIconImageList.pas b/Ext/SVGIconImageList/Source/FMX.SVGIconImageList.pas index 79cb256..5673af6 100644 --- a/Ext/SVGIconImageList/Source/FMX.SVGIconImageList.pas +++ b/Ext/SVGIconImageList/Source/FMX.SVGIconImageList.pas @@ -47,7 +47,7 @@ interface ; const - SVGIconImageListVersion = '4.1.8'; + SVGIconImageListVersion = '4.1.9'; DEFAULT_SIZE = 32; ZOOM_DEFAULT = 100; SVG_INHERIT_COLOR = TAlphaColors.Null; diff --git a/Ext/SVGIconImageList/Source/SVGIconImageListBase.pas b/Ext/SVGIconImageList/Source/SVGIconImageListBase.pas index a9f0694..28d634d 100644 --- a/Ext/SVGIconImageList/Source/SVGIconImageListBase.pas +++ b/Ext/SVGIconImageList/Source/SVGIconImageListBase.pas @@ -48,7 +48,7 @@ interface SvgInterfaces; const - SVGIconImageListVersion = '4.1.8'; + SVGIconImageListVersion = '4.1.9'; DEFAULT_SIZE = 16; type diff --git a/Ext/StyledComponents/source/Vcl.ButtonStylesAttributes.pas b/Ext/StyledComponents/source/Vcl.ButtonStylesAttributes.pas index 9cba957..cf566e8 100644 --- a/Ext/StyledComponents/source/Vcl.ButtonStylesAttributes.pas +++ b/Ext/StyledComponents/source/Vcl.ButtonStylesAttributes.pas @@ -1534,42 +1534,45 @@ function GetRoundedCornersPath(ARectangle: TGPRectF; const d0 = 0.0001; var - LPath : TGPGraphicsPath; l, t, w, h, d : Single; begin - LPath := TGPGraphicsPath.Create; - l := ARectangle.X; - t := ARectangle.Y; - w := ARectangle.Width; - h := ARectangle.Height; - d := ARadius / 2; - d := Min(d, Min(ARectangle.Width, ARectangle.Height)); - // topleft - if rcTopLeft in ARoundedCorners then - LPath.AddArc(l, t, d, d, 180, 90) - else - LPath.AddArc(l, t, d0, d0, 180, 90); + Result := TGPGraphicsPath.Create; + try + l := ARectangle.X; + t := ARectangle.Y; + w := ARectangle.Width; + h := ARectangle.Height; + d := ARadius / 2; + d := Min(d, Min(ARectangle.Width, ARectangle.Height)); + // topleft + if rcTopLeft in ARoundedCorners then + Result.AddArc(l, t, d, d, 180, 90) + else + Result.AddArc(l, t, d0, d0, 180, 90); - // topright - if rcTopRight in ARoundedCorners then - LPath.AddArc(l + w - d, t, d, d, 270, 90) - else - LPath.AddArc(l + w - d0, t, d0, d0, 270, 90); + // topright + if rcTopRight in ARoundedCorners then + Result.AddArc(l + w - d, t, d, d, 270, 90) + else + Result.AddArc(l + w - d0, t, d0, d0, 270, 90); - // bottomright - if rcBottomRight in ARoundedCorners then - LPath.AddArc(l + w - d, t + h - d, d, d, 0, 90) - else - LPath.AddArc(l + w - d0, t + h - d0, d0, d0, 0, 90); + // bottomright + if rcBottomRight in ARoundedCorners then + Result.AddArc(l + w - d, t + h - d, d, d, 0, 90) + else + Result.AddArc(l + w - d0, t + h - d0, d0, d0, 0, 90); - // bottomleft - if rcBottomLeft in ARoundedCorners then - LPath.AddArc(l, t + h - d, d, d, 90, 90) - else - LPath.AddArc(l, t + h - d0, d0, d0, 90, 90); + // bottomleft + if rcBottomLeft in ARoundedCorners then + Result.AddArc(l, t + h - d, d, d, 90, 90) + else + Result.AddArc(l, t + h - d0, d0, d0, 90, 90); - LPath.CloseFigure(); - result := LPath; + Result.CloseFigure(); + except + FreeAndNil(Result); + raise; + end end; function GPColor(AColor: TColor): TGPColor; @@ -2217,6 +2220,8 @@ procedure CanvasDrawShape(const ACanvas: TCanvas; ARect: TRect; begin LGraphics := nil; LPen := nil; + LBrush := nil; + LPath := nil; try X := ARect.Left; Y := ARect.Top; @@ -2272,6 +2277,8 @@ procedure CanvasDrawShape(const ACanvas: TCanvas; ARect: TRect; finally LGraphics.Free; LPen.Free; + LBrush.Free; + LPath.Free; end; end; {$else} diff --git a/Ext/StyledComponents/source/Vcl.StyledTaskDialogFormUnit.pas b/Ext/StyledComponents/source/Vcl.StyledTaskDialogFormUnit.pas index 608cf48..f12844b 100644 --- a/Ext/StyledComponents/source/Vcl.StyledTaskDialogFormUnit.pas +++ b/Ext/StyledComponents/source/Vcl.StyledTaskDialogFormUnit.pas @@ -724,7 +724,7 @@ procedure TStyledTaskDialogForm.UpdateButtonsSize; procedure UpdateButtonSize(const AButton: TStyledButton); begin - AButton.Width := FButtonsWidth; + AButton.Width := Round(FButtonsWidth * ScaleFactor); end; begin diff --git a/README.htm b/README.htm index dbce06a..1050d5d 100644 --- a/README.htm +++ b/README.htm @@ -30,7 +30,7 @@ }
Latest Version 3.2.2 - 27 Aug 2024
+Latest Version 3.2.3 - 14 Sep 2024
A collection of extensions tools for SVG files, integrated into Microsoft Windows Explorer (Vista, 7, 8, 10 and 11):
A Preview handler which allows you to see the SVG image and text without open it, in the “Preview Panel”.
@@ -89,6 +89,11 @@14 Sep 2024: ver. 3.2.3
+27 Aug 2024: ver. 3.2.2