diff --git a/backend/software/fill.go b/backend/software/fill.go new file mode 100644 index 0000000..235937e --- /dev/null +++ b/backend/software/fill.go @@ -0,0 +1,23 @@ +package softwarebackend + +import ( + "image/color" + + "github.com/tfriedel6/canvas/backend/backendbase" +) + +func (b *SoftwareBackend) Clear(pts [4][2]float64) { + iterateTriangles(pts[:], func(tri [][2]float64) { + b.fillTriangle(tri, func(x, y int) { + b.Image.SetRGBA(x, y, color.RGBA{}) + }) + }) +} + +func (b *SoftwareBackend) Fill(style *backendbase.FillStyle, pts [][2]float64) { + iterateTriangles(pts[:], func(tri [][2]float64) { + b.fillTriangle(tri, func(x, y int) { + b.Image.SetRGBA(x, y, style.Color) + }) + }) +} diff --git a/backend/software/images.go b/backend/software/images.go new file mode 100644 index 0000000..0439326 --- /dev/null +++ b/backend/software/images.go @@ -0,0 +1,42 @@ +package softwarebackend + +import ( + "image" + + "github.com/tfriedel6/canvas/backend/backendbase" +) + +type Image struct { + img image.Image +} + +func (b *SoftwareBackend) LoadImage(img image.Image) (backendbase.Image, error) { + return &Image{img: img}, nil +} + +func (b *SoftwareBackend) DrawImage(dimg backendbase.Image, sx, sy, sw, sh float64, pts [4][2]float64, alpha float64) { +} + +func (b *SoftwareBackend) FillImageMask(style *backendbase.FillStyle, mask *image.Alpha, pts [][2]float64) { +} + +func (img *Image) Width() int { + return img.img.Bounds().Dx() +} + +func (img *Image) Height() int { + return img.img.Bounds().Dy() +} + +func (img *Image) Size() (w, h int) { + b := img.img.Bounds() + return b.Dx(), b.Dy() +} + +func (img *Image) Delete() { +} + +func (img *Image) Replace(src image.Image) error { + img.img = src + return nil +} diff --git a/backend/software/software.go b/backend/software/software.go new file mode 100644 index 0000000..2171ead --- /dev/null +++ b/backend/software/software.go @@ -0,0 +1,81 @@ +package softwarebackend + +import ( + "image" + "image/draw" + + "github.com/tfriedel6/canvas/backend/backendbase" +) + +type SoftwareBackend struct { + Image *image.RGBA + w, h int +} + +func New(w, h int) *SoftwareBackend { + return &SoftwareBackend{ + Image: image.NewRGBA(image.Rect(0, 0, w, h)), + w: w, + h: h, + } +} + +func (b *SoftwareBackend) SetSize(w, h int) { + b.w, b.h = w, h + b.Image = image.NewRGBA(image.Rect(0, 0, w, h)) +} + +func (b *SoftwareBackend) Size() (int, int) { + return b.w, b.h +} + +func (b *SoftwareBackend) ClearClip() { +} + +func (b *SoftwareBackend) Clip(pts [][2]float64) { +} + +func (b *SoftwareBackend) GetImageData(x, y, w, h int) *image.RGBA { + return b.Image.SubImage(image.Rect(x, y, w, h)).(*image.RGBA) +} + +func (b *SoftwareBackend) PutImageData(img *image.RGBA, x, y int) { + draw.Draw(b.Image, image.Rect(x, y, img.Rect.Dx(), img.Rect.Dy()), img, image.ZP, draw.Src) +} + +func (b *SoftwareBackend) CanUseAsImage(b2 backendbase.Backend) bool { + return false +} + +func (b *SoftwareBackend) AsImage() backendbase.Image { + return nil +} + +type LinearGradient struct { + data backendbase.Gradient +} +type RadialGradient struct { + data backendbase.Gradient +} + +func (b *SoftwareBackend) LoadLinearGradient(data backendbase.Gradient) backendbase.LinearGradient { + return &LinearGradient{data: data} +} + +func (b *SoftwareBackend) LoadRadialGradient(data backendbase.Gradient) backendbase.RadialGradient { + return &RadialGradient{data: data} +} + +func (g *LinearGradient) Delete() { +} + +func (g *LinearGradient) Replace(data backendbase.Gradient) { + g.data = data +} + +func (g *RadialGradient) Delete() { +} + +func (g *RadialGradient) Replace(data backendbase.Gradient) { + g.data = data +} diff --git a/backend/software/triangles.go b/backend/software/triangles.go new file mode 100644 index 0000000..d3ddfc8 --- /dev/null +++ b/backend/software/triangles.go @@ -0,0 +1,94 @@ +package softwarebackend + +import "math" + +func triangleLR(tri [][2]float64, y float64) (l, r float64) { + a, b, c := tri[0], tri[1], tri[2] + + // check general bounds + if y < a[1] && y < b[1] && y < c[1] { + return 0, 0 + } + if y > a[1] && y > b[1] && y > c[1] { + return 0, 0 + } + + // sort by y + if a[1] > b[1] { + a, b = b, a + } + if b[1] > c[1] { + b, c = c, b + if a[1] > b[1] { + a, b = b, a + } + } + + // find left and right x at y + if y >= a[1] && y <= b[1] { + r0 := (y - a[1]) / (b[1] - a[1]) + l = (b[0]-a[0])*r0 + a[0] + r1 := (y - a[1]) / (c[1] - a[1]) + r = (c[0]-a[0])*r1 + a[0] + } else { + r0 := (y - b[1]) / (c[1] - b[1]) + l = (c[0]-b[0])*r0 + b[0] + r1 := (y - a[1]) / (c[1] - a[1]) + r = (c[0]-a[0])*r1 + a[0] + } + if l > r { + l, r = r, l + } + + return +} + +func (b *SoftwareBackend) fillTriangle(tri [][2]float64, fn func(x, y int)) { + minY := int(math.Floor(math.Min(math.Min(tri[0][1], tri[1][1]), tri[2][1]))) + maxY := int(math.Ceil(math.Max(math.Max(tri[0][1], tri[1][1]), tri[2][1]))) + if minY < 0 { + minY = 0 + } else if minY >= b.h { + return + } + if maxY < 0 { + return + } else if maxY >= b.h { + maxY = b.h - 1 + } + for y := minY; y <= maxY; y++ { + lf, rf := triangleLR(tri, float64(y)+0.5) + l := int(math.Floor(lf)) + r := int(math.Ceil(rf)) + if l < 0 { + l = 0 + } else if l >= b.w { + continue + } + if r < 0 { + continue + } else if r >= b.w { + r = b.w - 1 + } + for x := l; x <= r; x++ { + fn(x, y) + } + } +} + +func iterateTriangles(pts [][2]float64, fn func(tri [][2]float64)) { + if len(pts) == 4 { + var buf [3][2]float64 + buf[0] = pts[0] + buf[1] = pts[1] + buf[2] = pts[2] + fn(buf[:]) + buf[1] = pts[2] + buf[2] = pts[3] + fn(buf[:]) + return + } + for i := 3; i <= len(pts); i += 3 { + fn(pts[i-3 : i]) + } +}