Skip to content

Add sk_paint_compute_fast_bounds C API#271

Merged
mattleibow merged 1 commit into
skiasharpfrom
dev/bind-maskfilter-computefastbounds
Jun 29, 2026
Merged

Add sk_paint_compute_fast_bounds C API#271
mattleibow merged 1 commit into
skiasharpfrom
dev/bind-maskfilter-computefastbounds

Conversation

@mattleibow

@mattleibow mattleibow commented Jun 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds two C API functions wrapping the public SkPaint quick-reject API:

bool sk_paint_can_compute_fast_bounds(const sk_paint_t* cpaint);
void sk_paint_compute_fast_bounds(const sk_paint_t* cpaint, const sk_rect_t* orig, sk_rect_t* storage);

sk_paint_compute_fast_bounds returns 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 expose SKPaint.ComputeFastBounds / SKPaint.CanComputeFastBounds, completing the documented quick-reject pattern alongside the existing sk_canvas_quick_reject.

Consumer PR: mono/SkiaSharp#4271.

Why SkPaint and not SkMaskFilter?

This work originally targeted SkMaskFilter::computeFastBounds, but that method is private — and has been since 2018.

When Commit What
2012-01-30 9efd9a048a computeFastBounds added to the public SkMaskFilter, for quick-rejecting shadow/blur draws.
2018-01-23 80747ef591 (Mike Reed) "move the guts of SkMaskFilter.h into SkMaskFilterBase.h" — made it private. Pure encapsulation (empty bug field); part of making all effect types opaque handles, with implementation methods moved to internal *Base classes.

Binding the private method would require the C shim to #include "src/core/SkMaskFilterBase.h" and call as_MFB() — fragile private coupling. The public SkPaint::computeFastBounds calls the mask-filter bounds internally (doComputeFastBounds), so it is a strict superset and the canonical entry point. SkPaint::computeFastBounds does 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 computeFastBounds is 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

bool sk_paint_can_compute_fast_bounds(const sk_paint_t* cpaint) {
    return AsPaint(cpaint)->canComputeFastBounds();
}

void sk_paint_compute_fast_bounds(const sk_paint_t* cpaint, const sk_rect_t* orig, sk_rect_t* storage) {
    *AsRect(storage) = AsPaint(cpaint)->computeFastBounds(*AsRect(orig), AsRect(storage));
}

computeFastBounds returns a reference that is either orig (the ultra-fast fill-with-no-effects case) or *storage; assigning it into *storage is correct and safe for both (self-assignment of a trivially-copyable SkRect in the latter case). Uses only the already-included public SkPaint.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.

… 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>
@mattleibow mattleibow force-pushed the dev/bind-maskfilter-computefastbounds branch from 4ed1644 to 32957b9 Compare June 28, 2026 11:13
@mattleibow mattleibow changed the title Add sk_maskfilter_compute_fast_bounds C API Add sk_paint_compute_fast_bounds C API Jun 28, 2026

@mattleibow mattleibow left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mattleibow mattleibow merged commit 280ec21 into skiasharp Jun 29, 2026
1 check passed
@mattleibow mattleibow deleted the dev/bind-maskfilter-computefastbounds branch June 29, 2026 15:15
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant