surface/image_surface.go

Functions Structs

Functions

func Capabilities

Capabilities returns the surface capabilities.

func (s *ImageSurface) Capabilities() Capabilities {
	return Capabilities{
		SupportsSubSurface:	false,
		SupportsResize:		false,
		SupportsClipping:	false,
		SupportsBlendModes:	false,
		SupportsAntialias:	true,
		MaxWidth:		0,	// Unlimited
		MaxHeight:		0,
	}
}

func Clear

Clear fills the entire surface with the given color.

func (s *ImageSurface) Clear(c color.Color) {
	if s.closed {
		return
	}

	r, g, b, a := c.RGBA()
	//nolint:gosec // G115: safe - r>>8 is always in [0, 255]
	rgba := color.RGBA{
		R:	uint8(r >> 8),
		G:	uint8(g >> 8),
		B:	uint8(b >> 8),
		A:	uint8(a >> 8),
	}

	draw.Draw(s.img, s.img.Bounds(), &image.Uniform{rgba}, image.Point{}, draw.Src)
}

func Close

Close releases resources associated with the surface.

func (s *ImageSurface) Close() error {
	if s.closed {
		return nil
	}
	s.closed = true
	s.img = nil
	s.filler = nil
	s.edgeBuilder = nil
	return nil
}

func DrawImage

DrawImage draws an image at the specified position.

func (s *ImageSurface) DrawImage(img image.Image, at Point, opts *DrawImageOptions) {
	if s.closed || img == nil {
		return
	}

	srcBounds := img.Bounds()
	if opts != nil && opts.SrcRect != nil {
		srcBounds = *opts.SrcRect
	}

	dstX := int(at.X)
	dstY := int(at.Y)

	alpha := 1.0
	if opts != nil {
		alpha = opts.Alpha
	}

	// Simple nearest-neighbor blit for now
	for sy := srcBounds.Min.Y; sy < srcBounds.Max.Y; sy++ {
		dy := dstY + (sy - srcBounds.Min.Y)
		if dy < 0 || dy >= s.height {
			continue
		}

		for sx := srcBounds.Min.X; sx < srcBounds.Max.X; sx++ {
			dx := dstX + (sx - srcBounds.Min.X)
			if dx < 0 || dx >= s.width {
				continue
			}

			srcColor := img.At(sx, sy)
			if alpha < 1.0 {
				srcColor = s.applyAlpha(srcColor, alpha)
			}
			s.blendPixel(dx, dy, srcColor)
		}
	}
}

func Fill

Fill fills the given path using the specified style.

func (s *ImageSurface) Fill(path *Path, style FillStyle) {
	if s.closed || path == nil || path.IsEmpty() {
		return
	}

	// Get fill color
	fillColor := s.resolveColor(style.Color, style.Pattern)

	// Convert fill rule
	var fillRule raster.FillRule
	switch style.Rule {
	case FillRuleEvenOdd:
		fillRule = raster.FillRuleEvenOdd
	default:
		fillRule = raster.FillRuleNonZero
	}

	// Build edges from path
	s.edgeBuilder.Reset()
	s.edgeBuilder.SetFlattenCurves(true)	// Use line approximation for reliability
	s.edgeBuilder.BuildFromPath(path, raster.IdentityTransform{})

	if s.edgeBuilder.IsEmpty() {
		return
	}

	// Reset filler and render
	s.filler.Reset()
	s.filler.Fill(s.edgeBuilder, fillRule, func(y int, runs *raster.AlphaRuns) {
		if y < 0 || y >= s.height {
			return
		}
		s.blendRow(y, runs, fillColor)
	})
}

func Flush

Flush ensures all pending operations are complete.

For ImageSurface, this is a no-op.

func (s *ImageSurface) Flush() error {
	return nil
}

func Height

Height returns the surface height.

func (s *ImageSurface) Height() int {
	return s.height
}

func Image

Image returns the underlying image.RGBA.

This is a direct reference, not a copy.

func (s *ImageSurface) Image() *image.RGBA {
	return s.img
}

func NewImageSurface

NewImageSurface creates a new CPU-based surface with the given dimensions.

func NewImageSurface(width, height int) *ImageSurface {
	if width <= 0 {
		width = 1
	}
	if height <= 0 {
		height = 1
	}

	return &ImageSurface{
		width:		width,
		height:		height,
		img:		image.NewRGBA(image.Rect(0, 0, width, height)),
		filler:		raster.NewAnalyticFiller(width, height),
		edgeBuilder:	raster.NewEdgeBuilder(2),	// 4x AA quality
	}
}

func NewImageSurfaceFromImage

NewImageSurfaceFromImage creates a surface backed by an existing image.

The surface will render into the provided image directly.

func NewImageSurfaceFromImage(img *image.RGBA) *ImageSurface {
	bounds := img.Bounds()
	width := bounds.Dx()
	height := bounds.Dy()

	return &ImageSurface{
		width:		width,
		height:		height,
		img:		img,
		filler:		raster.NewAnalyticFiller(width, height),
		edgeBuilder:	raster.NewEdgeBuilder(2),
	}
}

func Snapshot

Snapshot returns a copy of the current surface contents.

func (s *ImageSurface) Snapshot() *image.RGBA {
	if s.closed {
		return nil
	}

	result := image.NewRGBA(image.Rect(0, 0, s.width, s.height))
	copy(result.Pix, s.img.Pix)
	return result
}

func Stroke

Stroke strokes the given path using the specified style.

func (s *ImageSurface) Stroke(path *Path, style StrokeStyle) {
	if s.closed || path == nil || path.IsEmpty() {
		return
	}

	// Get stroke color
	strokeColor := s.resolveColor(style.Color, style.Pattern)

	// Create stroke path by expanding the original path
	strokePath := s.expandStroke(path, style)
	if strokePath == nil || strokePath.IsEmpty() {
		return
	}

	// Build edges from expanded stroke path
	s.edgeBuilder.Reset()
	s.edgeBuilder.SetFlattenCurves(true)
	s.edgeBuilder.BuildFromPath(strokePath, raster.IdentityTransform{})

	if s.edgeBuilder.IsEmpty() {
		return
	}

	// Render as filled shape (stroke expansion creates filled outline)
	s.filler.Reset()
	s.filler.Fill(s.edgeBuilder, raster.FillRuleNonZero, func(y int, runs *raster.AlphaRuns) {
		if y < 0 || y >= s.height {
			return
		}
		s.blendRow(y, runs, strokeColor)
	})
}

func Width

Width returns the surface width.

func (s *ImageSurface) Width() int {
	return s.width
}

Structs

type ImageSurface struct

ImageSurface is a CPU-based surface that renders to an *image.RGBA.

 

It uses raster.AnalyticFiller for high-quality anti-aliased rendering.

This is the default surface implementation for software rendering.

 

Example:

 

s := surface.NewImageSurface(800, 600)

defer s.Close()

 

s.Clear(color.White)

path := surface.NewPath()

path.Circle(400, 300, 100)

s.Fill(path, surface.FillStyle{Color: color.RGBA{255, 0, 0, 255}})

 

img := s.Snapshot()

type ImageSurface struct {
	width	int
	height	int
	img	*image.RGBA

	// filler provides analytic anti-aliasing
	filler	*raster.AnalyticFiller

	// edgeBuilder converts paths to edges
	edgeBuilder	*raster.EdgeBuilder

	// closed tracks if Close has been called
	closed	bool
}