Closed tomwiel closed 2 months ago
I might have found something. The challenge is, fOutsideBounds around the mask-shape must be erased,
similar to EraseOutsideRect(), but also for polygons. TMaskRenderer.RenderProc() is called only for each y in mask-shapes.
The remaining y (lines) in fOutsideBounds need to be erased too.
If polygon-rendering runs from top to bottom, something like:
In TMaskRenderer.RenderProc():
for i := fLastY+1 to y-1 do begin
dst := GetDstPixel(fOutsideBounds.Left, i);
FillChar(dst^, OutsideBoundsW * SizeOf(TColor32), 0);
end;
fLastY := y;
in EraseOutsidePaths():
Rasterize(img, paths, vOutsideBounds, fillRule, renderer);
if renderer.fLastY < vOutsideBounds.bottom-1 then begin
img.FillRect( Rect( vOutsideBounds.Left, renderer.fLastY+1, vOutsideBounds.Right, vOutsideBounds.Bottom), 0);
end;
But still not complete for a solution, because shapes can have multiple x-ranges per y.
Hi Tom. I'll have a look when I can, but without a simpler example I suspect this won't be a quick fix 😉.
Yes, the example is inefficient for bug finding, but I've found the reason: The requirement is that all non-shape pixels inside of TMaskRenderer.fOutsideBounds need to be erased (0). TMaskRenderer.RenderProc() does not fulfill this, because it's called only for visible fill-ranges (interior) of shape. TMaskRenderer.RenderProc() already implements erasing of non-shape pixels, but not completely for all cases.
Example: fOutsideBounds correctly includes the whole clip-path. Due to the zoomed view in the example, only subregions of the clip-path are inside of TImage32. This causes fOutsideBounds to contain more empty lines (around subregions). fOutsideBounds (still large from whole clip-path) is correct, but erase is not complete.
For example, non-processed (bug) bottom half under green subregion:
The other potential issue is (not in the example): Complex clip-shapes can contain inner gaps which need erasing (0).
My quick fix is this (without TMaskRenderer):
procedure EraseOutsidePaths(img: TImage32; const paths: TPathsD;
fillRule: TFillRule; const outsideBounds: TRect;
rendererCache: TCustomRendererCache);
var
w, h: integer;
vOutsideBounds, r: TRect;
mask: TImage32;
pp: TPathsD;
begin
if not assigned(paths) then Exit;
Types.IntersectRect( vOutsideBounds, outsideBounds, img.Bounds);
RectWidthHeight(vOutsideBounds, w, h);
if (w <= 0) or (h <= 0) then Exit;
if (fillRule in [frEvenOdd, frNonZero]) and IsSimpleRectanglePath(paths, r) then
begin
EraseOutsideRect(img, r, vOutsideBounds);
Exit;
end;
mask := TImage32.Create(w, h);
try
pp := TranslatePath(paths, -vOutsideBounds.Left, -vOutsideBounds.top);
DrawPolygon(mask, pp, fillRule, clBlack32, rendererCache);
img.CopyBlend(mask, mask.Bounds, vOutsideBounds, BlendMaskLine);
finally
mask.Free;
end;
end;
Should be fixed with my new PR.
I actually used the car2.svg for checking if the TMaskRenderer worked. Unfortunately I had my eyes only on the mask for the wheel not the other parts of the car. And my other example SVG file had a mask, that has the same dimensions as outsideBounds
, so it had no untouched clipping region.
As you already found out, the problem with the TMaskRenderer was that it only worked for scanlines for which RenderProc was called. All other scanlines in the clipping region were not filled with zeros.
The fix introduces the new method TMaskRenderer.RenderSkipProc
which is called for all scanlines that Rasterize skips, including the scanlines from cliprec.top
to cliprec2.top
and the scanlines from cliprec2.bottom
to cliprec.bottom
. The last block is combined with the last skipped scanlines, to make only one call to RenderSkipProc instead of 2.
I made a PNG comparison with the old EraseOutsidePaths code (DrawPolygon+CopyBlend(BlendMask)
) and the new code to also get the clipping boundaries correct. CopyBlend excludes the Right/Bottom pixels whereas TMaskRenderer included them. But not anymore.
Very useful. Thank you!
Sometimes a wrong effect results with new EraseOutsidePaths(). I was not able to find the reason and it's difficult to reproduce.
Here is one example: Svg101.dpr.
1) Modify TSvgReader.DrawImage() just for testing:
2) In EraseOutsidePaths(img, ....), in 2nd line, also add a constraint to prevent later AV:
Types.IntersectRect( vOutsideBounds, outsideBounds, img.bounds); .....
After this line, use vOutsideBounds instead of outsideBounds. Note: This constraint is probably not the reason of the reported artifact. The same constraint in old EraseOutsidePaths() would not produce artifacts.3) After building, start the app and