Add sk_paint_compute_fast_bounds C API#271
Merged
Merged
Conversation
… C API Expose the public SkPaint::computeFastBounds and SkPaint::canComputeFastBounds through the C API so SkiaSharp can compute the conservative device-space bounds a paint produces for a given source rect (accounting for path effect, stroke, mask filter and image filter), enabling quick-reject culling alongside the already-bound sk_canvas_quick_reject. This uses the public SkPaint API rather than the private SkMaskFilterBase::computeFastBounds (which has been an internal implementation detail since 2018). SkPaint::computeFastBounds composes the mask filter bounds internally, so it is a strict superset of the per-mask-filter method. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4ed1644 to
32957b9
Compare
mattleibow
added a commit
to mono/SkiaSharp
that referenced
this pull request
Jun 29, 2026
Add SKPaint.GetFastBounds() for quick-reject culling (#4271) Context: mono/skia#271 Submodule: mono/skia@b16789e...280ec21 Expose Skia's public SkPaint::computeFastBounds / canComputeFastBounds as a single Try-style method: bool SKPaint.GetFastBounds(SKRect bounds, out SKRect fastBounds) It returns the conservative device-space rectangle a paint can draw into for a given source rect, composing the paint's path effect, stroke inflation, mask filter and image filter. Paired with the already-bound SKCanvas.QuickReject it completes the quick-reject culling pattern Skia documents in its own headers: if (paint.GetFastBounds(path.Bounds, out var fast) && canvas.QuickReject(fast)) return; // skip the draw One method instead of a Can* property plus a Compute* method: Skia's contract is that canComputeFastBounds() is a guard that must be checked before calling computeFastBounds() - the result is unreliable for paints whose image filter or path effect cannot report bounds (the header says "Only call this if canComputeFastBounds() returned true"). Folding that guard into the bool return makes the API impossible to misuse and matches the existing out-bool bounds family - SKPath.GetBounds, SKPath.GetTightBounds, SKCanvas.GetLocalClipBounds - none of which has a paired Can* property (there is no CanGetTightBounds). GetFastBounds returns false (and SKRect.Empty) when the paint cannot compute fast bounds. Public SkPaint API, not SkMaskFilter: This began as a binding of SkMaskFilter::computeFastBounds, but that method has been private since 2018 (mono/skia 80747ef, "move the guts of SkMaskFilter.h into SkMaskFilterBase.h") and binding it would require the internal SkMaskFilterBase.h. SkPaint::computeFastBounds is the public entry point, has stayed public for 8 years despite a "(to be made private)" doc note, and composes the mask-filter bounds internally, so it is a strict superset of the per-mask-filter method. SkImageFilter::computeFastBounds likewise stayed public, reinforcing SkPaint as the intended API. The skia-discuss thread EFbFlfJjNxE ("How to get the path bound including border?") also recommends computeFastBounds as the way to get a shape's bounds including blur/shadow. * C API (mono/skia#271): sk_paint_compute_fast_bounds, sk_paint_can_compute_fast_bounds; regenerated SkiaApi.generated.cs. * Bump externals/skia b16789ec35 -> 280ec21ada to pick up the C API. * Tests (SKPaintTest): blur outset 3x sigma, stroke (round join/cap) outset strokeWidth/2, stroke+blur composition, fill no-op returns source, source rect not mutated, returns true for simple paints, managed/native parity. * Gallery sample PaintFastBoundsSample and PaintFastBoundsBenchmark (quick-reject culling vs. unconditional draw). Co-authored-by: Matthew Leibowitz <mattleibow@live.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds two C API functions wrapping the public
SkPaintquick-reject API:sk_paint_compute_fast_boundsreturns the conservative device-space bounds a paint can draw into for a given source rect, composing the paint's path effect, stroke radius, mask filter and image filter. This lets SkiaSharp exposeSKPaint.ComputeFastBounds/SKPaint.CanComputeFastBounds, completing the documented quick-reject pattern alongside the existingsk_canvas_quick_reject.Consumer PR: mono/SkiaSharp#4271.
Why
SkPaintand notSkMaskFilter?This work originally targeted
SkMaskFilter::computeFastBounds, but that method is private — and has been since 2018.9efd9a048acomputeFastBoundsadded to the publicSkMaskFilter, for quick-rejecting shadow/blur draws.80747ef591(Mike Reed)SkMaskFilter.hintoSkMaskFilterBase.h" — made it private. Pure encapsulation (empty bug field); part of making all effect types opaque handles, with implementation methods moved to internal*Baseclasses.Binding the private method would require the C shim to
#include "src/core/SkMaskFilterBase.h"and callas_MFB()— fragile private coupling. The publicSkPaint::computeFastBoundscalls the mask-filter bounds internally (doComputeFastBounds), so it is a strict superset and the canonical entry point.SkPaint::computeFastBoundsdoes carry a(to be made private)doc note (added 2018-01-03,2823f9f06c1), but it has remained public and unchanged for ~8 years with no move to privatize it.Community threads on groups.google.com/g/skia-discuss confirm
computeFastBoundsis the recommended way to get visual bounds including blur/shadow/stroke (e.g. "How to get the path bound including border?", 2024-01-28).Implementation
computeFastBoundsreturns a reference that is eitherorig(the ultra-fast fill-with-no-effects case) or*storage; assigning it into*storageis correct and safe for both (self-assignment of a trivially-copyableSkRectin the latter case). Uses only the already-included publicSkPaint.h— no private headers.Testing
Verified via the consumer PR: native rebuilt from source (macOS arm64), symbols exported, and the full managed test suite passes (5743 passed, 0 failed). Behavior covered: blur outsets by 3×sigma, stroke outsets by strokeWidth/2, stroke+blur composition, fill no-op, and managed/native parity.