context_clip.go

Functions

Functions

func Clip

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.

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
}