context_clip.go
Functions
func Clip
func (c *Context) Clip() {
if c.clipStack == nil {
c.initClipStack()
}
// Path elements are in user-space; clip stack operates in device-space.
// Transform through deviceMatrix to get device coordinates.
devicePath := c.deviceSpacePath()
clipVerbs, clipCoords := convertPathToClipVerbs(devicePath)
// Push the path as a clip region
_ = c.clipStack.PushPath(clipVerbs, clipCoords, true) // anti-aliased by default
// Store the device-space path for GPU depth clipping (GPU-CLIP-003a).
// The GPU DepthClipPipeline fan-tessellates this path at draw time.
c.gpuClipPath = devicePath.Clone()
// Clear the path
c.path.Clear()
}
func ClipPreserve
ClipPreserve sets the current path as the clipping region but keeps the path.
This is like Clip() but doesn't clear the path, allowing you to both clip
and then fill/stroke the same path.
func (c *Context) ClipPreserve() {
if c.clipStack == nil {
c.initClipStack()
}
// Path elements are in user-space; clip stack operates in device-space.
devicePath := c.deviceSpacePath()
clipVerbs, clipCoords := convertPathToClipVerbs(devicePath)
// Push the path as a clip region
_ = c.clipStack.PushPath(clipVerbs, clipCoords, true) // anti-aliased by default
// Store the device-space path for GPU depth clipping (GPU-CLIP-003a).
c.gpuClipPath = devicePath.Clone()
// Path is preserved
}
func ClipRect
ClipRect sets a rectangular clipping region.
This is a faster alternative to creating a rectangular path and calling Clip().
The clip region is intersected with any existing clip regions.
func (c *Context) ClipRect(x, y, w, h float64) {
if c.clipStack == nil {
c.initClipStack()
}
// Transform the rectangle corners to device coordinates.
tm := c.totalMatrix()
p1 := tm.TransformPoint(Pt(x, y))
p2 := tm.TransformPoint(Pt(x+w, y+h))
// Create clip rectangle in device coordinates
rect := clip.NewRect(
math.Min(p1.X, p2.X),
math.Min(p1.Y, p2.Y),
math.Abs(p2.X-p1.X),
math.Abs(p2.Y-p1.Y),
)
c.clipStack.PushRect(rect)
}
func ClipRoundRect
ClipRoundRect sets a rounded rectangle clipping region.
The rectangle is defined by (x, y, w, h) in user coordinates and the
corners are rounded with the given radius. The radius is clamped to
min(w, h)/2. If radius is zero, this is equivalent to ClipRect.
On GPU, this uses a two-level clip strategy:
- Scissor rect (hardware, free) for the bounding box
- Analytic SDF in the fragment shader for the rounded corners
On CPU, the SDF is evaluated per-pixel during coverage computation.
func (c *Context) ClipRoundRect(x, y, w, h, radius float64) {
if radius <= 0 {
c.ClipRect(x, y, w, h)
return
}
if c.clipStack == nil {
c.initClipStack()
}
// Transform the rectangle corners to device coordinates.
tm := c.totalMatrix()
p1 := tm.TransformPoint(Pt(x, y))
p2 := tm.TransformPoint(Pt(x+w, y+h))
// Create clip rectangle in device coordinates.
devX := math.Min(p1.X, p2.X)
devY := math.Min(p1.Y, p2.Y)
devW := math.Abs(p2.X - p1.X)
devH := math.Abs(p2.Y - p1.Y)
// Scale radius by the total transform scale factor.
scaledRadius := radius * tm.ScaleFactor()
// Clamp to half the smaller dimension.
maxRadius := math.Min(devW, devH) / 2
if scaledRadius > maxRadius {
scaledRadius = maxRadius
}
rect := clip.NewRect(devX, devY, devW, devH)
c.clipStack.PushRRect(rect, scaledRadius)
}
func ResetClip
ResetClip removes all clipping regions, restoring the full canvas as drawable.
func (c *Context) ResetClip() {
if c.clipStack == nil {
return
}
// Reset to physical pixel bounds (clip stack operates in device-space).
bounds := clip.NewRect(0, 0, float64(c.pixmap.Width()), float64(c.pixmap.Height()))
c.clipStack.Reset(bounds)
c.gpuClipPath = nil
}
Clip sets the current path as the clipping region and clears the path.
Subsequent drawing operations will be clipped to this region.
The clip region is intersected with any existing clip regions.