surface/image_surface.go
Functions
func 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
}
Capabilities returns the surface capabilities.