context.go

Functions Structs

Functions

func AntiAlias

AntiAlias returns whether anti-aliasing is enabled for geometry rendering.

func (c *Context) AntiAlias() bool {
	return c.antiAlias
}

func AppendPath

AppendPath appends the elements of p to the current path without clearing it.

This allows combining multiple sub-paths before a single Fill or Stroke call.

Note: path coordinates are copied as-is (not transformed by the current matrix).

Use DrawPath for transform-aware path rendering.

func (c *Context) AppendPath(p *Path) {
	if p != nil {
		c.path.Append(p)
	}
}

func BeginGPUFrame

BeginGPUFrame resets per-context GPU frame state so the next render pass

uses LoadOpClear. Call this on persistent contexts before re-rendering

to the same view — without it, frameRendered=true from the previous frame

causes LoadOpLoad, preserving stale content.

 

Not needed for one-shot contexts (NewContext + Close per frame).

Not needed when the view changes between frames (auto-reset on view change).

func (c *Context) BeginGPUFrame() {
	if rc := c.gpuCtxOps(); rc != nil {
		rc.BeginFrame()
	}
}

func Clear

Clear resets the entire context to transparent (zero alpha).

To fill with a specific background color, use [ClearWithColor].

func (c *Context) Clear() {
	c.pixmap.Clear(Transparent)
}

func ClearDash

ClearDash removes the dash pattern, returning to solid lines.

func (c *Context) ClearDash() {
	if c.paint.Stroke != nil {
		c.paint.Stroke.Dash = nil
	}
}

func ClearPath

ClearPath clears the current path.

func (c *Context) ClearPath() {
	c.path.Clear()
}

func ClearWithColor

ClearWithColor fills the entire context with the specified color.

This is the recommended way to set a background color before drawing.

func (c *Context) ClearWithColor(col RGBA) {
	c.pixmap.Clear(col)
}

func Close

Close releases resources associated with the Context.

After Close, the Context should not be used.

Close is idempotent - multiple calls are safe.

Implements io.Closer.

 

Close flushes any pending GPU accelerator operations to ensure all

queued draw commands are rendered before releasing context state.

Note: Close does NOT shut down the global GPU accelerator itself,

since it may be shared by other contexts. To release GPU resources

at application shutdown, call [CloseAccelerator].

func (c *Context) Close() error {
	if c.closed {
		return nil
	}
	c.closed = true

	// Flush pending GPU operations so queued shapes are not lost.
	c.flushGPUAccelerator()

	// Close per-context GPU render context if it was created.
	if c.gpuCtx != nil {
		type gpuCtxCloser interface {
			Close()
		}
		if closer, ok := c.gpuCtx.(gpuCtxCloser); ok {
			closer.Close()
		}
		c.gpuCtx = nil
	}

	// Clear path to release memory
	c.ClearPath()

	// Clear state stack
	c.stack = nil
	c.clipStackDepth = nil
	c.maskStack = nil
	c.mask = nil
	c.gpuClipPath = nil

	return nil
}

func ClosePath

ClosePath closes the current subpath.

func (c *Context) ClosePath() {
	c.path.Close()
}

func CreateSharedEncoder

CreateSharedEncoder creates a command encoder for single-command-buffer

frames (ADR-017). Multiple gg.Contexts record render passes into this

encoder via SetSharedEncoder. Call SubmitSharedEncoder after all contexts

have flushed to submit in one GPU call.

Returns a zero-value CommandEncoder (IsNil() == true) if GPU is not available.

func (c *Context) CreateSharedEncoder() gpucontext.CommandEncoder {
	rc := c.gpuCtxOps()
	if rc == nil {
		return gpucontext.CommandEncoder{}
	}
	type encoderCreator interface {
		CreateEncoder() gpucontext.CommandEncoder
	}
	if ec, ok := rc.(encoderCreator); ok {
		return ec.CreateEncoder()
	}
	return gpucontext.CommandEncoder{}
}

func CubicTo

CubicTo adds a cubic Bezier curve to the current path.

func (c *Context) CubicTo(c1x, c1y, c2x, c2y, x, y float64) {
	cp1 := c.matrix.TransformPoint(Pt(c1x, c1y))
	cp2 := c.matrix.TransformPoint(Pt(c2x, c2y))
	p := c.matrix.TransformPoint(Pt(x, y))
	c.path.CubicTo(cp1.X, cp1.Y, cp2.X, cp2.Y, p.X, p.Y)
}

func DeviceScale

DeviceScale returns the device scale factor (physical pixels per logical pixel).

Default is 1.0. On Retina/HiDPI displays, typical values are 2.0 or 3.0.

func (c *Context) DeviceScale() float64 {
	return c.deviceScale
}

func DrawArc

DrawArc draws a circular arc.

func (c *Context) DrawArc(x, y, r, angle1, angle2 float64) {
	// Transform center point
	center := c.matrix.TransformPoint(Pt(x, y))

	// Create arc in world space
	const twoPi = 2 * math.Pi
	for angle2 < angle1 {
		angle2 += twoPi
	}

	const maxAngle = math.Pi / 2
	numSegments := int(math.Ceil((angle2 - angle1) / maxAngle))
	angleStep := (angle2 - angle1) / float64(numSegments)

	for i := 0; i < numSegments; i++ {
		a1 := angle1 + float64(i)*angleStep
		a2 := a1 + angleStep
		c.arcSegment(center.X, center.Y, r, a1, a2)
	}
}

func DrawCircle

DrawCircle draws a circle.

func (c *Context) DrawCircle(x, y, r float64) {
	const k = 0.5522847498307936
	offset := r * k

	c.MoveTo(x+r, y)
	c.CubicTo(x+r, y+offset, x+offset, y+r, x, y+r)
	c.CubicTo(x-offset, y+r, x-r, y+offset, x-r, y)
	c.CubicTo(x-r, y-offset, x-offset, y-r, x, y-r)
	c.CubicTo(x+offset, y-r, x+r, y-offset, x+r, y)
	c.ClosePath()
}

func DrawEllipse

DrawEllipse draws an ellipse.

func (c *Context) DrawEllipse(x, y, rx, ry float64) {
	const k = 0.5522847498307936
	ox := rx * k
	oy := ry * k

	c.MoveTo(x+rx, y)
	c.CubicTo(x+rx, y+oy, x+ox, y+ry, x, y+ry)
	c.CubicTo(x-ox, y+ry, x-rx, y+oy, x-rx, y)
	c.CubicTo(x-rx, y-oy, x-ox, y-ry, x, y-ry)
	c.CubicTo(x+ox, y-ry, x+rx, y-oy, x+rx, y)
	c.ClosePath()
}

func DrawEllipticalArc

DrawEllipticalArc draws an elliptical arc (advanced).

func (c *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
	// This is a simplified version; full implementation would handle rotation
	c.Push()
	c.Translate(x, y)
	c.Scale(rx, ry)
	c.DrawArc(0, 0, 1, angle1, angle2)
	c.Pop()
}

func DrawLine

DrawLine draws a line between two points.

func (c *Context) DrawLine(x1, y1, x2, y2 float64) {
	c.MoveTo(x1, y1)
	c.LineTo(x2, y2)
}

func DrawPath

DrawPath replays the elements of p through the current transform matrix,

replacing the current path. Unlike SetPath (which copies raw coordinates),

DrawPath applies the current matrix (Translate, Scale, Rotate) to all points.

After DrawPath, call Fill() or Stroke() to render.

 

This is the correct way to render pre-built paths (e.g., from ParseSVGPath)

with transforms:

 

path, _ := gg.ParseSVGPath("M10,10 L90,10 L90,90 Z")

dc.Push()

dc.Translate(x, y)

dc.Scale(0.5, 0.5)

dc.DrawPath(path)

dc.Fill()

dc.Pop()

func (c *Context) DrawPath(p *Path) {
	c.ClearPath()
	if p == nil {
		return
	}
	p.Iterate(func(verb PathVerb, coords []float64) {
		switch verb {
		case MoveTo:
			c.MoveTo(coords[0], coords[1])
		case LineTo:
			c.LineTo(coords[0], coords[1])
		case QuadTo:
			c.QuadraticTo(coords[0], coords[1], coords[2], coords[3])
		case CubicTo:
			c.CubicTo(coords[0], coords[1], coords[2], coords[3], coords[4], coords[5])
		case Close:
			c.ClosePath()
		}
	})
}

func DrawPoint

DrawPoint draws a single point at the given coordinates.

func (c *Context) DrawPoint(x, y, r float64) {
	c.DrawCircle(x, y, r)
}

func DrawRectangle

DrawRectangle draws a rectangle.

func (c *Context) DrawRectangle(x, y, w, h float64) {
	c.MoveTo(x, y)
	c.LineTo(x+w, y)
	c.LineTo(x+w, y+h)
	c.LineTo(x, y+h)
	c.ClosePath()
}

func DrawRoundedRectangle

DrawRoundedRectangle draws a rectangle with rounded corners.

 

The corner radius r is clamped to half the smaller dimension.

All coordinates are transformed through the current matrix,

ensuring correct rendering on HiDPI/Retina displays.

func (c *Context) DrawRoundedRectangle(x, y, w, h, r float64) {
	maxR := math.Min(w, h) / 2
	if r > maxR {
		r = maxR
	}
	// Cubic Bézier approximation for 90° arcs (same constant as DrawCircle).
	const k = 0.5522847498307936
	kr := k * r
	// Top edge
	c.MoveTo(x+r, y)
	c.LineTo(x+w-r, y)
	// Top-right corner
	c.CubicTo(x+w-r+kr, y, x+w, y+r-kr, x+w, y+r)
	// Right edge
	c.LineTo(x+w, y+h-r)
	// Bottom-right corner
	c.CubicTo(x+w, y+h-r+kr, x+w-r+kr, y+h, x+w-r, y+h)
	// Bottom edge
	c.LineTo(x+r, y+h)
	// Bottom-left corner
	c.CubicTo(x+r-kr, y+h, x, y+h-r+kr, x, y+h-r)
	// Left edge
	c.LineTo(x, y+r)
	// Top-left corner
	c.CubicTo(x, y+r-kr, x+r-kr, y, x+r, y)
	c.ClosePath()
}

func EncodeJPEG

EncodeJPEG writes the image as JPEG with the given quality (1-100).

func (c *Context) EncodeJPEG(w io.Writer, quality int) error {
	return jpeg.Encode(w, c.Image(), &jpeg.Options{Quality: quality})
}

func EncodePNG

EncodePNG writes the image as PNG to the given writer.

This is useful for streaming, network output, or custom storage.

func (c *Context) EncodePNG(w io.Writer) error {
	return png.Encode(w, c.Image())
}

func Fill

Fill fills the current path and clears it.

If a GPU accelerator is registered and supports the path, it is used first.

Otherwise, the software renderer handles the operation.

The RasterizerMode set via SetRasterizerMode controls algorithm selection.

Returns an error if the rendering operation fails.

func (c *Context) Fill() error {
	c.trackDamage(c.path.Bounds())
	err := c.doFill()
	c.path.Clear()
	return err
}

func FillBrush

FillBrush returns the current fill brush.

func (c *Context) FillBrush() Brush {
	return c.paint.GetBrush()
}

func FillPath

FillPath is a convenience method that replays path p through the current

transform, fills it, and clears the path. Equivalent to DrawPath(p) + Fill().

func (c *Context) FillPath(p *Path) error {
	c.DrawPath(p)
	return c.Fill()
}

func FillPreserve

FillPreserve fills the current path without clearing it.

If a GPU accelerator is registered and supports the path, it is used first.

Otherwise, the software renderer handles the operation.

Returns an error if the rendering operation fails.

func (c *Context) FillPreserve() error {
	return c.doFill()
}

func FillRectCPU

FillRectCPU fills a rectangle directly on the CPU pixmap without engaging

the GPU SDF accelerator. Coordinates are in user space (device scale applied

automatically). Pending GPU shapes are flushed first for correct z-ordering.

 

Use for operations where GPU acceleration is counterproductive, such as

dirty-region background clearing in retained-mode compositors. Without this,

DrawRectangle+Fill routes through SDF accelerator → blocks non-MSAA blit path.

 

See ADR-016, TASK-GG-COMPOSITOR-003.

func (c *Context) FillRectCPU(x, y, w, h float64, col RGBA) {
	c.flushGPUAccelerator()

	ctm := c.totalMatrix()
	tl := ctm.TransformPoint(Pt(x, y))
	br := ctm.TransformPoint(Pt(x+w, y+h))

	px0 := int(tl.X)
	py0 := int(tl.Y)
	px1 := int(br.X + 0.5)
	py1 := int(br.Y + 0.5)

	pr := uint8(clamp255(col.R * col.A * 255))
	pg := uint8(clamp255(col.G * col.A * 255))
	pb := uint8(clamp255(col.B * col.A * 255))
	pa := uint8(clamp255(col.A * 255))

	c.pixmap.FillRect(image.Rect(px0, py0, px1, py1), pr, pg, pb, pa)
}

func FlushGPU

func (c *Context) FlushGPU() error {
	t := c.gpuRenderTarget()
	if rc := c.gpuCtxOps(); rc != nil {
		return rc.Flush(t)
	}
	if a := Accelerator(); a != nil {
		c.warnGPUFallback("FlushGPU")
		return a.Flush(t)
	}
	return nil
}

func FlushGPUWithView

FlushGPUWithView flushes pending GPU operations, resolving directly to the

given texture view instead of reading back to CPU. The view is passed

through GPURenderTarget.View so the render session uses it as the per-pass

resolve target, enabling multiple Contexts to render to different views

without cross-contamination.

 

This is the per-pass render target path for ggcanvas.RenderDirect.

When view is nil/zero, behaves identically to FlushGPU (CPU readback).

func (c *Context) FlushGPUWithView(view gpucontext.TextureView, width, height uint32) error {
	t := c.gpuRenderTarget()
	if !view.IsNil() {
		t.View = view
		t.ViewWidth = width
		t.ViewHeight = height
	}
	rc := c.gpuCtxOps()
	if rc != nil {
		return rc.Flush(t)
	}
	if a := Accelerator(); a != nil {
		c.warnGPUFallback("FlushGPUWithView")
		return a.Flush(t)
	}
	return ErrFallbackToCPU
}

func FlushGPUWithViewDamage

FlushGPUWithViewDamage flushes pending GPU operations with damage-aware

optimization. When damageRect is non-empty, the compositor uses LoadOpLoad

(preserves previous frame) and scissor-clips to the dirty region — only

the damaged pixels are re-composited. When damageRect is empty, behaves

identically to FlushGPUWithView (full compositor pass).

 

IMPORTANT: damage-aware rendering (LoadOpLoad + scissor) works only on the

blit-only compositor path (frames with only DrawGPUTextureBase/DrawGPUTexture

calls, no vector shapes). When the frame contains Fill/Stroke operations,

the MSAA render path is used which always does LoadOpClear — damageRect is

ignored and a warning is logged. This matches enterprise practice: Chrome,

Flutter, and Skia all re-render dirty layers fully via MSAA and composite

incrementally via blit-only path. See ADR-021.

 

This enables sub-region compositing: a 48×48 spinner updates only 9KB

instead of the full surface (8MB at 1080p). See ADR-016 Phase 2.

func (c *Context) FlushGPUWithViewDamage(view gpucontext.TextureView, width, height uint32, damageRect image.Rectangle) error {
	t := c.gpuRenderTarget()
	if !view.IsNil() {
		t.View = view
		t.ViewWidth = width
		t.ViewHeight = height
	}
	if !damageRect.Empty() {
		t.DamageRects = []image.Rectangle{damageRect}
	}
	if rc := c.gpuCtxOps(); rc != nil {
		return rc.Flush(t)
	}
	if a := Accelerator(); a != nil {
		c.warnGPUFallback("FlushGPUWithViewDamage")
		return a.Flush(t)
	}
	return nil
}

func FlushGPUWithViewDamageRects

FlushGPUWithViewDamageRects renders to a surface view with multiple damage rects

(ADR-028). Each overlay gets its own scissor from the closest damage rect,

enabling per-draw dynamic scissor for distant dirty regions.

func (c *Context) FlushGPUWithViewDamageRects(view gpucontext.TextureView, width, height uint32, rects []image.Rectangle) error {
	t := c.gpuRenderTarget()
	if !view.IsNil() {
		t.View = view
		t.ViewWidth = width
		t.ViewHeight = height
	}
	if len(rects) > 0 {
		t.DamageRects = rects
	}
	if rc := c.gpuCtxOps(); rc != nil {
		return rc.Flush(t)
	}
	if a := Accelerator(); a != nil {
		c.warnGPUFallback("FlushGPUWithViewDamageRects")
		return a.Flush(t)
	}
	return nil
}

func FrameDamage

FrameDamage returns the list of damage rectangles from draw operations

this frame. Each rect corresponds to one or more Fill/Stroke operations.

Used by ggcanvas → SetDamageRects → PresentWithDamage for per-rect OS blit.

Returns nil if no drawing operations occurred.

func (c *Context) FrameDamage() []image.Rectangle {
	if len(c.frameDamageRects) == 0 {
		return nil
	}
	return c.frameDamageRects
}

func FrameDamageUnion

FrameDamageUnion returns the bounding box of all damage rects this frame.

Convenience method for debug display or single-rect consumers.

func (c *Context) FrameDamageUnion() image.Rectangle {
	var r image.Rectangle
	for _, dr := range c.frameDamageRects {
		r = r.Union(dr)
	}
	return r
}

func GPURenderContext

GPURenderContext returns the per-context GPU render context, lazily created.

Returns nil if no GPU accelerator is registered or it does not support

per-context rendering. The returned value should be type-asserted to

*gpu.GPURenderContext in internal/gpu consumers.

func (c *Context) GPURenderContext() any {
	c.ensureGPUCtx()
	return c.gpuCtx
}

func GetCurrentPoint

GetCurrentPoint returns the current point of the path.

Returns (0, 0, false) if there is no current point.

func (c *Context) GetCurrentPoint() (x, y float64, ok bool) {
	if c.path == nil || !c.path.HasCurrentPoint() {
		return 0, 0, false
	}
	pt := c.path.CurrentPoint()
	return pt.X, pt.Y, true
}

func GetStroke

GetStroke returns the current stroke style.

func (c *Context) GetStroke() Stroke {
	return c.paint.GetStroke()
}

func GetTransform

GetTransform returns a copy of the current transformation matrix.

This is similar to CanvasRenderingContext2D.getTransform() in web browsers.

The returned matrix is a copy, so modifying it will not affect the context.

func (c *Context) GetTransform() Matrix {
	return c.matrix
}

func Height

Height returns the logical height of the context.

This is the coordinate space used by drawing operations.

For the physical pixel dimensions, use PixelHeight.

func (c *Context) Height() int {
	return c.height
}

func Identity

Identity resets the user transformation matrix to the identity matrix.

Device scale is applied separately at rendering boundaries (not in the CTM),

so Identity() always resets to a pure identity matrix regardless of scale.

func (c *Context) Identity() {
	c.matrix = Identity()
}

func Image

Image returns the context's image.

func (c *Context) Image() image.Image {
	return c.pixmap.ToImage()
}

func InvertY

InvertY inverts the Y axis (useful for coordinate system changes).

Uses logical height so the inversion works correctly at any device scale.

func (c *Context) InvertY() {
	c.Translate(0, float64(c.height))
	c.Scale(1, -1)
}

func IsDashed

IsDashed returns true if the current stroke uses a dash pattern.

func (c *Context) IsDashed() bool {
	return c.paint.IsDashed()
}

func LineTo

LineTo adds a line to the current path.

func (c *Context) LineTo(x, y float64) {
	p := c.matrix.TransformPoint(Pt(x, y))
	c.path.LineTo(p.X, p.Y)
}

func MoveTo

MoveTo starts a new subpath at the given point.

func (c *Context) MoveTo(x, y float64) {
	p := c.matrix.TransformPoint(Pt(x, y))
	c.path.MoveTo(p.X, p.Y)
}

func NewContext

func NewContext(width, height int, opts ...ContextOption) *Context {
	// Apply options
	options := defaultOptions()
	for _, opt := range opts {
		opt(&options)
	}

	scale := options.deviceScale
	if scale <= 0 {
		scale = 1.0
	}

	// Physical dimensions for the pixmap
	pw := int(float64(width) * scale)
	ph := int(float64(height) * scale)

	// Use provided pixmap or create one at physical resolution
	pixmap := options.pixmap
	if pixmap == nil {
		pixmap = NewPixmap(pw, ph)
	}

	// Use provided renderer or create software renderer at physical resolution
	renderer := options.renderer
	if renderer == nil {
		sr := NewSoftwareRenderer(pw, ph)
		if scale > 1.0 {
			sr.SetDeviceScale(float32(scale))
		}
		renderer = sr
	}

	// Device matrix: maps user coordinates to physical pixels.
	// User matrix starts as Identity — user transforms never include device scale.
	deviceMatrix := Identity()
	if scale != 1.0 {
		deviceMatrix = Scale(scale, scale)
	}

	if scale != 1.0 {
		Logger().Info("NewContext HiDPI",
			"logical_w", width, "logical_h", height,
			"scale", scale,
			"physical_w", pw, "physical_h", ph,
		)
	}

	return &Context{
		width:			width,
		height:			height,
		deviceScale:		scale,
		pixmap:			pixmap,
		renderer:		renderer,
		path:			NewPath(),
		paint:			NewPaint(),
		matrix:			Identity(),
		deviceMatrix:		deviceMatrix,
		stack:			make([]Matrix, 0, 8),
		clipStackDepth:		make([]int, 0, 8),
		pipelineMode:		options.pipelineMode,
		damageTrackingEnabled:	true,
		antiAlias:		true,
	}
}

func NewContextForImage

NewContextForImage creates a context for drawing on an existing image.

Optional ContextOption arguments can be used for dependency injection.

The image dimensions are treated as physical pixel dimensions (deviceScale=1.0).

func NewContextForImage(img image.Image, opts ...ContextOption) *Context {
	bounds := img.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()
	pixmap := FromImage(img)

	// Apply options
	options := defaultOptions()
	for _, opt := range opts {
		opt(&options)
	}

	// Use provided renderer or create software renderer
	renderer := options.renderer
	if renderer == nil {
		renderer = NewSoftwareRenderer(width, height)
	}

	return &Context{
		width:		width,
		height:		height,
		deviceScale:	1.0,
		pixmap:		pixmap,
		renderer:	renderer,
		path:		NewPath(),
		paint:		NewPaint(),
		matrix:		Identity(),
		deviceMatrix:	Identity(),
		stack:		make([]Matrix, 0, 8),
		clipStackDepth:	make([]int, 0, 8),
		pipelineMode:	options.pipelineMode,
	}
}

func NewContextForPixmap

NewContext creates a new drawing context with the given logical dimensions.

Optional ContextOption arguments can be used for dependency injection:

 

// Default software rendering (uses analytic anti-aliasing)

dc := gg.NewContext(800, 600)

 

// Custom GPU renderer (dependency injection)

dc := gg.NewContext(800, 600, gg.WithRenderer(gpuRenderer))

 

// HiDPI/Retina rendering (logical 800x600, physical 1600x1200)

dc := gg.NewContext(800, 600, gg.WithDeviceScale(2.0))

 

When WithDeviceScale is used, the internal pixmap is allocated at physical

resolution (width*scale x height*scale) while Width/Height return the

logical dimensions. All drawing operations use logical coordinates.

NewContextForPixmap creates a Context backed by an existing Pixmap.

The Context renders directly into the provided pixmap without allocating

a new one. Used by scene.Renderer for GPU-accelerated scene rendering.

func NewContextForPixmap(pm *Pixmap) *Context {
	if pm == nil {
		return nil
	}
	return NewContext(pm.Width(), pm.Height(), func(o *contextOptions) {
		o.pixmap = pm
	})
}

func NewContextWithScale

NewContextWithScale creates a new drawing context with the given logical

dimensions and device scale factor. This is a convenience wrapper for:

 

gg.NewContext(w, h, gg.WithDeviceScale(scale))

 

The internal pixmap is allocated at physical resolution (w*scale x h*scale).

All drawing operations use logical coordinates (w x h).

 

Example (macOS Retina 2x):

 

dc := gg.NewContextWithScale(800, 600, 2.0)

dc.Width() // 800 (logical)

dc.PixelWidth() // 1600 (physical)

dc.DrawCircle(400, 300, 100) // logical coordinates

func NewContextWithScale(width, height int, scale float64) *Context {
	return NewContext(width, height, WithDeviceScale(scale))
}

func NewSubPath

NewSubPath starts a new subpath without closing the previous one.

func (c *Context) NewSubPath() {
	// In most implementations, just starting with MoveTo creates a new subpath
	// This is a no-op but provided for API compatibility
}

func PipelineMode

PipelineMode returns the current pipeline mode.

func (c *Context) PipelineMode() PipelineMode {
	return c.pipelineMode
}

func PixelHeight

PixelHeight returns the physical pixel height of the internal pixmap.

This equals Height() * DeviceScale(), rounded to int.

On non-HiDPI displays (scale=1.0), this equals Height().

func (c *Context) PixelHeight() int {
	return int(float64(c.height) * c.deviceScale)
}

func PixelWidth

PixelWidth returns the physical pixel width of the internal pixmap.

This equals Width() * DeviceScale(), rounded to int.

On non-HiDPI displays (scale=1.0), this equals Width().

func (c *Context) PixelWidth() int {
	return int(float64(c.width) * c.deviceScale)
}

func Pop

Pop restores the last saved state.

func (c *Context) Pop() {
	if len(c.stack) == 0 {
		return
	}

	// Restore transform matrix
	c.matrix = c.stack[len(c.stack)-1]
	c.stack = c.stack[:len(c.stack)-1]

	// Restore clip stack depth
	if len(c.clipStackDepth) > 0 {
		targetDepth := c.clipStackDepth[len(c.clipStackDepth)-1]
		c.clipStackDepth = c.clipStackDepth[:len(c.clipStackDepth)-1]

		// Pop clip stack entries until we reach the target depth
		if c.clipStack != nil {
			for c.clipStack.Depth() > targetDepth {
				c.clipStack.Pop()
			}
			// Clear GPU clip path if all path clips were popped.
			if c.gpuClipPath != nil && c.clipStack.IsRRectOnly() {
				c.gpuClipPath = nil
			}
		}
	}

	// Restore mask
	if len(c.maskStack) > 0 {
		c.mask = c.maskStack[len(c.maskStack)-1]
		c.maskStack = c.maskStack[:len(c.maskStack)-1]
	}

	// Restore anti-aliasing state
	if len(c.antiAliasStack) > 0 {
		c.antiAlias = c.antiAliasStack[len(c.antiAliasStack)-1]
		c.antiAliasStack = c.antiAliasStack[:len(c.antiAliasStack)-1]
	}
}

func Push

Push saves the current state (transform, paint, clip, and mask).

func (c *Context) Push() {
	c.stack = append(c.stack, c.matrix)

	// Save current clip stack depth
	depth := 0
	if c.clipStack != nil {
		depth = c.clipStack.Depth()
	}
	c.clipStackDepth = append(c.clipStackDepth, depth)

	// Save current mask (clone if exists)
	var maskCopy *Mask
	if c.mask != nil {
		maskCopy = c.mask.Clone()
	}
	c.maskStack = append(c.maskStack, maskCopy)

	// Save current anti-aliasing state
	c.antiAliasStack = append(c.antiAliasStack, c.antiAlias)
}

func QuadraticTo

QuadraticTo adds a quadratic Bezier curve to the current path.

func (c *Context) QuadraticTo(cx, cy, x, y float64) {
	cp := c.matrix.TransformPoint(Pt(cx, cy))
	p := c.matrix.TransformPoint(Pt(x, y))
	c.path.QuadraticTo(cp.X, cp.Y, p.X, p.Y)
}

func RasterizerMode

RasterizerMode returns the current rasterizer mode.

func (c *Context) RasterizerMode() RasterizerMode {
	return c.rasterizerMode
}

func ResetFrameDamage

ResetFrameDamage clears the per-frame damage accumulator.

Call at the start of each frame before drawing operations.

func (c *Context) ResetFrameDamage() {
	c.frameDamageRects = c.frameDamageRects[:0]
}

func Resize

Resize changes the context logical dimensions, reusing internal buffers where possible.

If the dimensions haven't changed, this is a no-op.

Returns an error if width or height is <= 0.

 

The width and height are logical dimensions. The internal pixmap is

allocated at physical resolution (width*deviceScale x height*deviceScale).

 

After Resize:

- The pixmap is reallocated only if dimensions changed

- The clip region is reset to the full rectangle

- The transformation matrix is preserved (Push/Pop stack is preserved)

- The current path is cleared

 

This method is useful for UI frameworks that need to resize the canvas

when the window size changes, without creating a new Context.

func (c *Context) Resize(width, height int) error {
	if width <= 0 || height <= 0 {
		return fmt.Errorf("invalid dimensions: width=%d, height=%d (both must be > 0)", width, height)
	}

	// No-op if dimensions haven't changed
	if c.width == width && c.height == height {
		return nil
	}

	// Update logical dimensions
	c.width = width
	c.height = height

	// Physical dimensions
	pw := int(float64(width) * c.deviceScale)
	ph := int(float64(height) * c.deviceScale)

	// Reallocate pixmap at physical resolution
	c.pixmap = NewPixmap(pw, ph)

	// Resize renderer if it supports resizing
	if sr, ok := c.renderer.(*SoftwareRenderer); ok {
		sr.Resize(pw, ph)
	}

	// Reset clip stack to full rectangle
	c.clipStack = nil
	c.gpuClipPath = nil

	// Clear any existing path
	c.ClearPath()

	return nil
}

func ResizeTarget

ResizeTarget returns the underlying pixmap for resize operations.

This is primarily used by renderers and advanced users who need

direct access to the target buffer during resize operations.

func (c *Context) ResizeTarget() *Pixmap {
	return c.pixmap
}

func Rotate

Rotate applies a rotation (angle in radians).

func (c *Context) Rotate(angle float64) {
	c.matrix = c.matrix.Multiply(Rotate(angle))
}

func RotateAbout

RotateAbout rotates around a specific point.

func (c *Context) RotateAbout(angle, x, y float64) {
	c.Translate(x, y)
	c.Rotate(angle)
	c.Translate(-x, -y)
}

func SavePNG

SavePNG saves the context to a PNG file.

func (c *Context) SavePNG(path string) error {
	_ = c.FlushGPU()	// Flush pending GPU shapes before reading pixels.
	return c.pixmap.SavePNG(path)
}

func Scale

Scale applies a scaling transformation.

func (c *Context) Scale(x, y float64) {
	c.matrix = c.matrix.Multiply(Scale(x, y))
}

func SetAntiAlias

SetAntiAlias enables or disables anti-aliasing for geometry rendering.

 

When enabled (default), shapes are rendered with smooth edges using analytic

anti-aliasing (Skia AAA). When disabled, shapes are rendered with binary

coverage (fully inside or fully outside) producing crisp, aliased edges.

 

This is useful for pixel art, retro-style graphics, technical drawings,

and any use case where sub-pixel blending is undesirable.

 

Text anti-aliasing is controlled independently via SetTextMode.

The anti-aliasing state participates in Push/Pop.

 

Reference: Skia SkPaint::setAntiAlias, Cairo cairo_set_antialias,

tiny-skia Paint.anti_alias.

func (c *Context) SetAntiAlias(enabled bool) {
	c.antiAlias = enabled
}

func SetColor

SetColor sets the current drawing color.

func (c *Context) SetColor(col color.Color) {
	c.paint.solidColor = FromColor(col)
	c.paint.isSolid = true
	c.paint.Brush = nil
	c.paint.Pattern = nil
}

func SetDamageTracking

SetDamageTracking enables or disables per-operation damage recording.

When disabled, Fill/Stroke do not append to FrameDamage.

Used by retained-mode compositors to suppress damage during replay

of cached (clean) scene content (ADR-021 false positive fix).

func (c *Context) SetDamageTracking(enabled bool) {
	c.damageTrackingEnabled = enabled
}

func SetDash

SetDash sets the dash pattern for stroking.

Pass alternating dash and gap lengths.

Passing no arguments clears the dash pattern (returns to solid lines).

 

Example:

 

ctx.SetDash(5, 3) // 5 units dash, 3 units gap

ctx.SetDash(10, 5, 2, 5) // complex pattern

ctx.SetDash() // clear dash (solid line)

func (c *Context) SetDash(lengths ...float64) {
	if len(lengths) == 0 {
		c.ClearDash()
		return
	}

	dash := NewDash(lengths...)
	if dash == nil {
		c.ClearDash()
		return
	}

	// Ensure we have a Stroke to set the dash on
	if c.paint.Stroke == nil {
		stroke := c.paint.GetStroke()
		c.paint.Stroke = &stroke
	}
	c.paint.Stroke.Dash = dash
}

func SetDashOffset

SetDashOffset sets the starting offset into the dash pattern.

This has no effect if no dash pattern is set.

func (c *Context) SetDashOffset(offset float64) {
	if c.paint.Stroke == nil {
		// Create stroke from legacy fields if needed
		stroke := c.paint.GetStroke()
		c.paint.Stroke = &stroke
	}
	if c.paint.Stroke.Dash != nil {
		c.paint.Stroke.Dash = c.paint.Stroke.Dash.WithOffset(offset)
	}
}

func SetDeviceScale

SetDeviceScale changes the device scale factor on an existing context.

This reallocates the internal pixmap at the new physical resolution

and adjusts the base transform. The logical dimensions (Width, Height)

remain unchanged.

 

Use this when the window moves to a display with a different scale factor.

Scale must be > 0; values <= 0 are ignored.

func (c *Context) SetDeviceScale(scale float64) {
	if scale <= 0 || scale == c.deviceScale {
		return
	}

	oldScale := c.deviceScale
	c.deviceScale = scale

	// Physical dimensions
	pw := int(float64(c.width) * scale)
	ph := int(float64(c.height) * scale)

	Logger().Info("SetDeviceScale",
		"old_scale", oldScale, "new_scale", scale,
		"logical_w", c.width, "logical_h", c.height,
		"physical_w", pw, "physical_h", ph,
	)

	// Reallocate pixmap at new physical resolution
	c.pixmap = NewPixmap(pw, ph)

	// Update renderer dimensions and device scale
	if sr, ok := c.renderer.(*SoftwareRenderer); ok {
		sr.Resize(pw, ph)
		sr.SetDeviceScale(float32(scale))
	}

	// Update device matrix. User matrix (c.matrix) is NOT touched —
	// it contains only user transforms and is independent of device scale.
	c.deviceMatrix = Identity()
	if scale != 1.0 {
		c.deviceMatrix = Scale(scale, scale)
	}

	// Reset clip stack (clip regions are in pixel coordinates)
	c.clipStack = nil
	c.gpuClipPath = nil
	c.ClearPath()
}

func SetFillBrush

SetFillBrush sets the brush used for fill operations.

This is the preferred way to set fill styling in new code.

 

Example:

 

ctx.SetFillBrush(gg.Solid(gg.Red))

ctx.SetFillBrush(gg.SolidHex("#FF5733"))

ctx.SetFillBrush(gg.HorizontalGradient(gg.Red, gg.Blue, 0, 100))

func (c *Context) SetFillBrush(b Brush) {
	c.paint.SetBrush(b)
}

func SetFillRule

SetFillRule sets the fill rule.

func (c *Context) SetFillRule(rule FillRule) {
	c.paint.FillRule = rule
}

func SetHexColor

SetHexColor sets the current color using a hex string.

func (c *Context) SetHexColor(hex string) {
	c.paint.solidColor = Hex(hex)
	c.paint.isSolid = true
	c.paint.Brush = nil
	c.paint.Pattern = nil
}

func SetLCDLayout

SetLCDLayout sets the LCD subpixel layout for ClearType text rendering.

Use LCDLayoutRGB for most monitors, LCDLayoutBGR for rare BGR panels,

or LCDLayoutNone to disable subpixel rendering (grayscale, the default).

 

When a GPU accelerator is registered and implements LCDLayoutAware,

the layout is propagated so the glyph mask engine rasterizes glyphs

with 3x horizontal oversampling and the GPU uses the LCD fragment shader.

 

The setting is per-Context. Call this before drawing text.

func (c *Context) SetLCDLayout(layout LCDLayout) {
	a := Accelerator()
	if a == nil {
		return
	}
	if la, ok := a.(LCDLayoutAware); ok {
		la.SetLCDLayout(layout)
	}
}

func SetLineCap

SetLineCap sets the line cap style.

func (c *Context) SetLineCap(lineCap LineCap) {
	c.paint.LineCap = lineCap
}

func SetLineJoin

SetLineJoin sets the line join style.

func (c *Context) SetLineJoin(join LineJoin) {
	c.paint.LineJoin = join
}

func SetLineWidth

SetLineWidth sets the line width for stroking.

func (c *Context) SetLineWidth(width float64) {
	c.paint.LineWidth = width
}

func SetMiterLimit

SetMiterLimit sets the miter limit for line joins.

func (c *Context) SetMiterLimit(limit float64) {
	c.paint.MiterLimit = limit
}

func SetPath

SetPath replaces the current path with p.

The path is copied — subsequent modifications to p do not affect the context.

Use this to render pre-built paths (e.g., from ParseSVGPath):

 

path, _ := gg.ParseSVGPath("M10,10 L90,10 L90,90 Z")

dc.SetPath(path)

dc.Fill()

func (c *Context) SetPath(p *Path) {
	c.path.Clear()
	if p != nil {
		c.path.Append(p)
	}
}

func SetPipelineMode

SetPipelineMode sets the GPU rendering pipeline mode.

See PipelineMode for available modes.

 

If the registered accelerator implements PipelineModeAware, the mode is

propagated so the accelerator can route operations to the correct pipeline

(render pass vs compute).

func (c *Context) SetPipelineMode(mode PipelineMode) {
	c.pipelineMode = mode
	if rc := c.gpuCtxOps(); rc != nil {
		rc.SetPipelineMode(mode)
	} else if a := Accelerator(); a != nil {
		if pma, ok := a.(PipelineModeAware); ok {
			pma.SetPipelineMode(mode)
		}
	}
}

func SetPixel

SetPixel sets a single pixel.

func (c *Context) SetPixel(x, y int, col RGBA) {
	c.pixmap.SetPixel(x, y, col)
}

func SetRGB

SetRGB sets the current color using RGB values (0-1).

func (c *Context) SetRGB(r, g, b float64) {
	c.paint.solidColor = RGBA{R: r, G: g, B: b, A: 1}
	c.paint.isSolid = true
	c.paint.Brush = nil
	c.paint.Pattern = nil
}

func SetRGBA

SetRGBA sets the current color using RGBA values (0-1).

func (c *Context) SetRGBA(r, g, b, a float64) {
	c.paint.solidColor = RGBA{R: r, G: g, B: b, A: a}
	c.paint.isSolid = true
	c.paint.Brush = nil
	c.paint.Pattern = nil
}

func SetRasterizerMode

SetRasterizerMode sets the rasterization strategy for this context.

RasterizerAuto (default) uses intelligent auto-selection based on path

complexity, bounding box area, and shape type.

Other modes force a specific algorithm, bypassing auto-selection.

 

The mode is per-Context — different contexts can use different strategies.

func (c *Context) SetRasterizerMode(mode RasterizerMode) {
	c.rasterizerMode = mode
}

func SetSharedEncoder

FlushGPU flushes any pending GPU accelerator operations to the pixel buffer.

Call this before reading pixel data (e.g., SavePNG, Image) when using a

batch-capable GPU accelerator. For immediate-mode accelerators this is a no-op.

SetSharedEncoder sets a shared command encoder for single-command-buffer

frames (ADR-017, Flutter Impeller pattern). When set, FlushGPU/FlushGPUWithView

record render passes into this encoder instead of creating their own and

submitting. The caller is responsible for encoder.Finish() + queue.Submit().

 

Pass a zero-value CommandEncoder (IsNil() == true) to restore normal

per-context submit behavior.

func (c *Context) SetSharedEncoder(encoder gpucontext.CommandEncoder) {
	if rc := c.gpuCtxOps(); rc != nil {
		type encoderSetter interface {
			SetSharedEncoder(encoder gpucontext.CommandEncoder)
		}
		if es, ok := rc.(encoderSetter); ok {
			es.SetSharedEncoder(encoder)
		}
	}
}

func SetStroke

SetStroke sets the complete stroke style.

This is the preferred way to configure stroke properties.

 

Example:

 

ctx.SetStroke(gg.DefaultStroke().WithWidth(2).WithCap(gg.LineCapRound))

ctx.SetStroke(gg.DashedStroke(5, 3))

func (c *Context) SetStroke(stroke Stroke) {
	c.paint.SetStroke(stroke)
}

func SetStrokeBrush

SetStrokeBrush sets the brush used for stroke operations.

Note: In the current implementation, fill and stroke share the same brush.

This method is provided for API symmetry and future extensibility.

 

Example:

 

ctx.SetStrokeBrush(gg.Solid(gg.Black))

ctx.SetStrokeBrush(gg.SolidRGB(0.5, 0.5, 0.5))

func (c *Context) SetStrokeBrush(b Brush) {
	c.paint.SetBrush(b)
}

func SetTextMode

SetTextMode sets the text rendering strategy.

See TextMode constants for available strategies.

 

The mode is per-Context — different contexts can use different strategies.

func (c *Context) SetTextMode(mode TextMode) {
	c.textMode = mode
}

func SetTransform

SetTransform replaces the current transformation matrix with the given matrix.

This is similar to CanvasRenderingContext2D.setTransform() in web browsers.

Unlike Transform, this completely replaces the matrix rather than multiplying.

func (c *Context) SetTransform(m Matrix) {
	c.matrix = m
}

func Shear

Shear applies a shear transformation.

func (c *Context) Shear(x, y float64) {
	c.matrix = c.matrix.Multiply(Shear(x, y))
}

func Stroke

Stroke strokes the current path and clears it.

If a GPU accelerator is registered and supports the path, it is used first.

Otherwise, the software renderer handles the operation.

The RasterizerMode set via SetRasterizerMode controls algorithm selection.

Returns an error if the rendering operation fails.

func (c *Context) Stroke() error {
	c.trackDamage(c.path.Bounds())
	err := c.doStroke()
	c.path.Clear()
	return err
}

func StrokeBrush

StrokeBrush returns the current stroke brush.

Note: In the current implementation, fill and stroke share the same brush.

func (c *Context) StrokeBrush() Brush {
	return c.paint.GetBrush()
}

func StrokePath

StrokePath is a convenience method that replays path p through the current

transform, strokes it, and clears the path. Equivalent to DrawPath(p) + Stroke().

func (c *Context) StrokePath(p *Path) error {
	c.DrawPath(p)
	return c.Stroke()
}

func StrokePreserve

StrokePreserve strokes the current path without clearing it.

If a GPU accelerator is registered and supports the path, it is used first.

Otherwise, the software renderer handles the operation.

Returns an error if the rendering operation fails.

func (c *Context) StrokePreserve() error {
	return c.doStroke()
}

func SubmitSharedEncoder

SubmitSharedEncoder finishes the shared encoder and submits the resulting

command buffer to the GPU. Call after all contexts have flushed their

render passes into the encoder. Returns the command buffer submission

index for fence tracking, or error.

func (c *Context) SubmitSharedEncoder(encoder gpucontext.CommandEncoder) error {
	rc := c.gpuCtxOps()
	if rc == nil {
		return nil
	}
	type encoderSubmitter interface {
		SubmitEncoder(encoder gpucontext.CommandEncoder) error
	}
	if es, ok := rc.(encoderSubmitter); ok {
		return es.SubmitEncoder(encoder)
	}
	return nil
}

func TextMode

TextMode returns the current text rendering strategy.

func (c *Context) TextMode() TextMode {
	return c.textMode
}

func TrackDamageRect

TrackDamageRect registers an external damage rectangle on the surface.

Use this for compositor operations that modify the surface but don't use

Fill/Stroke (e.g., DrawGPUTexture for dirty RepaintBoundary overlays).

No-op when damage tracking is disabled or rect is empty.

 

Callers with retained-mode knowledge (e.g., ui widget tree) should call

this for each dirty boundary after compositing, so that FrameDamage()

accurately reflects which surface regions changed this frame.

 

Bounds are in logical (user-space) coordinates. The context automatically

scales them to physical pixels via deviceScale for the OS compositor.

func (c *Context) TrackDamageRect(bounds image.Rectangle) {
	c.trackDamage(bounds)
}

func Transform

Transform multiplies the current transformation matrix by the given matrix.

This is similar to CanvasRenderingContext2D.transform() in web browsers.

The transformation is applied in the order: current * m.

func (c *Context) Transform(m Matrix) {
	c.matrix = c.matrix.Multiply(m)
}

func TransformPoint

TransformPoint transforms a point by the current matrix.

func (c *Context) TransformPoint(x, y float64) (float64, float64) {
	p := c.matrix.TransformPoint(Pt(x, y))
	return p.X, p.Y
}

func Translate

Translate applies a translation to the transformation matrix.

func (c *Context) Translate(x, y float64) {
	c.matrix = c.matrix.Multiply(Translate(x, y))
}

func Width

Width returns the logical width of the context.

This is the coordinate space used by drawing operations.

For the physical pixel dimensions, use PixelWidth.

func (c *Context) Width() int {
	return c.width
}

Structs

type Context struct

Context is the main drawing context.

It maintains a pixmap, current path, paint state, and transformation stack.

Context implements io.Closer for proper resource cleanup.

 

When deviceScale > 1.0 (HiDPI/Retina), the Context maintains a larger physical

pixmap while exposing logical dimensions to user code. Drawing operations use

logical coordinates; the Context applies a base scale transform transparently.

type Context struct {
	width		int	// logical width (user-facing)
	height		int	// logical height (user-facing)
	pixmap		*Pixmap
	renderer	Renderer

	// HiDPI support
	deviceScale	float64	// physical pixels per logical pixel (default 1.0)

	// Current state
	path		*Path
	paint		*Paint
	face		text.Face	// Current font face for text drawing
	clipStack	*clip.ClipStack	// Clipping stack
	gpuClipPath	*Path		// device-space clip path for GPU depth clipping (GPU-CLIP-003a)

	// Transform and state stack
	matrix		Matrix	// user transform (starts as Identity, user-space only)
	deviceMatrix	Matrix	// device scale transform (Identity when scale=1.0, NEVER modified by user)
	stack		[]Matrix
	clipStackDepth	[]int	// Tracks clip stack depth for each Push/Pop

	// Layer support
	layerStack	*layerStack	// Layer stack for compositing
	basePixmap	*Pixmap		// Base pixmap when layers are active

	// Mask support
	mask		*Mask	// Current alpha mask
	maskStack	[]*Mask	// Mask stack for Push/Pop

	// Per-frame damage tracking (ADR-021 Level 1).
	// List of per-operation bounding boxes — NOT a single union rect.
	// Each Fill/Stroke adds its own rect. Passed as-is to PresentWithDamage
	// for per-rect OS blit. Merged to bounding box if count exceeds threshold.
	frameDamageRects	[]image.Rectangle
	damageTrackingEnabled	bool

	// Pipeline mode
	pipelineMode	PipelineMode	// GPU pipeline selection mode

	// Rasterizer mode
	rasterizerMode	RasterizerMode	// CPU rasterizer selection mode

	// Anti-aliasing
	antiAlias	bool	// anti-aliasing enabled (default: true)
	antiAliasStack	[]bool	// Push/Pop stack for antiAlias state

	// Text rendering
	textMode		TextMode		// text strategy selection (default: Auto)
	outlineExtractor	*text.OutlineExtractor	// lazy: for transform-aware text (Strategy B)
	glyphCache		*text.GlyphCache	// lazy: cached glyph outlines for drawStringAsOutlines

	// Per-context GPU render context (isolated pending commands, clips, frame tracking).
	// Lazily created when GPURenderContextProvider is available.
	// Typed as gpuContextOps (defined in this package) to avoid circular import
	// with internal/gpu while maintaining type safety.
	gpuCtx			gpuContextOps
	gpuFallbackWarned	bool	// true after first global fallback warning (avoid log spam)

	// Lifecycle
	closed	bool	// Indicates whether Close has been called
}