diff --git a/raster/painter.go b/raster/painter.go new file mode 100644 index 00000000..9ab01382 --- /dev/null +++ b/raster/painter.go @@ -0,0 +1,68 @@ +// This code is inspired to golang/freetype/raster + +// Package raster implements a Painter interface for rasterizing paths over +// a generic Image, using its ColorModel to convert from a generic raster color +// to the correct color model. +package raster + +import ( + "github.com/golang/freetype/raster" + "image/color" + "image/draw" +) + +// ImagePainter operates on a generic Image (not only sdl.Surfaces) and allows +// to rasterize a path using a specific color. +type ImagePainter struct { + Image draw.Image + c color.Color +} + +// Paint a batch of Spans using the current ImagePainter image and color. +// Image's Color model will be used to convert the color. +func (p *ImagePainter) Paint(ss []raster.Span, done bool) { + // Convert color to RGBA + dr, dg, db, da := p.c.RGBA() // 16 bit values + // Alpha mask + const m = 1<<16 - 1 + // Draw spans + b := p.Image.Bounds() + for _, s := range ss { + if s.Y < b.Min.Y || s.Y >= b.Max.Y { + continue + } + if s.X0 < b.Min.X { + s.X0 = b.Min.X + } + if s.X1 > b.Max.X { + s.X1 = b.Max.X + } + if s.X0 >= s.X1 { + continue + } + for x := s.X0; x < s.X1; x++ { + y := s.Y - b.Min.Y + var ma uint32 = s.Alpha // 16 bit value + // Get destination pixel color in RGBA64 + sr, sg, sb, sa := p.Image.At(x, y).RGBA() // 16 bit values + // Compute destination color in RGBA64 + var a = (m - (da * ma / m)) + rr := uint16((dr*ma + sr*a) / m) + gg := uint16((dg*ma + sg*a) / m) + bb := uint16((db*ma + sb*a) / m) + aa := uint16((da*ma + sa*a) / m) + // Use image model to convert + p.Image.Set(x, y, color.RGBA64{rr, gg, bb, aa}) + } + } +} + +// SetColor set the color to use when rasterizing +func (p *ImagePainter) SetColor(c color.Color) { + p.c = c +} + +// NewImagePainter builds a Painter for a generic Image +func NewImagePainter(m draw.Image) *ImagePainter { + return &ImagePainter{Image: m} +} diff --git a/raster/painter_test.go b/raster/painter_test.go new file mode 100644 index 00000000..53f1aadf --- /dev/null +++ b/raster/painter_test.go @@ -0,0 +1,41 @@ +package raster + +import ( + "testing" + + "image" + "image/color" + "image/png" + "os" + + "github.com/golang/freetype/raster" +) + +func TestImagePainter(t *testing.T) { + // Spans to render + ss := []raster.Span{ + raster.Span{0, 4, 6, 0xffff}, + raster.Span{1, 3, 7, 0xf0f0}, + raster.Span{2, 2, 8, 0x8f8f}, + raster.Span{3, 1, 9, 0x8080}, + raster.Span{4, 0, 10, 0x0f0f}, + } + + img := image.NewRGBA(image.Rect(0, 0, 10, 10)) + pt := NewImagePainter(img) + pt.SetColor(color.RGBA{0xff, 0x00, 0x00, 0xff}) + pt.Paint(ss, false) + + // Write to PNG + f, err := os.Create("output.png") + if err != nil { + t.Error(err) + } + if err := png.Encode(f, img); err != nil { + f.Close() + t.Error(err) + } + if err := f.Close(); err != nil { + t.Error(err) + } +} diff --git a/sdl/pixels.go b/sdl/pixels.go index 2ae54ea3..e059fb9d 100644 --- a/sdl/pixels.go +++ b/sdl/pixels.go @@ -3,6 +3,7 @@ package sdl // #include "sdl_wrapper.h" import "C" import "unsafe" +import "image/color" // PixelFormat contains pixel format information. // (https://wiki.libsdl.org/SDL_PixelFormat) @@ -39,14 +40,9 @@ type Palette struct { } type cPalette C.SDL_Palette -// Color represents a color. +// Color represents a color. This implements image/color.Color interface. // (https://wiki.libsdl.org/SDL_Color) -type Color struct { - R uint8 // the red component in the range 0-255 - G uint8 // the green component in the range 0-255 - B uint8 // the blue component in the range 0-255 - A uint8 // the alpha component in the range 0-255 -} +type Color color.RGBA // Uint32 return uint32 representation of RGBA color. func (c Color) Uint32() uint32 { @@ -58,25 +54,6 @@ func (c Color) Uint32() uint32 { return v } -// RGBA returns the alpha-premultiplied red, green, blue and alpha values -// for the color. Each value ranges within [0, 0xffff], but is represented -// by a uint32 so that multiplying by a blend factor up to 0xffff will not -// overflow. -// -// An alpha-premultiplied color component c has been scaled by alpha (a), -// so has valid values 0 <= c <= a. -func (c Color) RGBA() (r, g, b, a uint32) { - r = uint32(c.R) - r |= r << 8 - g = uint32(c.G) - g |= g << 8 - b = uint32(c.B) - b |= b << 8 - a = uint32(c.A) - a |= a << 8 - return -} - // Pixel types. const ( PIXELTYPE_UNKNOWN = C.SDL_PIXELTYPE_UNKNOWN diff --git a/sdl/surface.go b/sdl/surface.go index 088d012a..042d78b0 100644 --- a/sdl/surface.go +++ b/sdl/surface.go @@ -78,6 +78,8 @@ static inline SDL_Surface* SDL_CreateRGBSurfaceWithFormatFrom(void* pixels, int import "C" import "unsafe" import "reflect" +import "image" +import "image/color" // Surface flags (internal use) const ( @@ -521,3 +523,73 @@ func (surface *Surface) Duplicate() (newSurface *Surface, err error) { newSurface = (*Surface)(unsafe.Pointer(_newSurface)) return } + +// ColorModel returns the color model used by this Surface. +func (surface *Surface) ColorModel() color.Model { + switch surface.Format.Format { + case PIXELFORMAT_ARGB8888, PIXELFORMAT_ABGR8888: + return color.RGBAModel + case PIXELFORMAT_RGB888: + return color.RGBAModel + default: + panic("Not implemented yet") + } +} + +// Bounds return the bounds of this surface. Currently, it always starts at +// (0,0), but this is not guaranteed in the future so don't rely on it. +func (surface *Surface) Bounds() image.Rectangle { + return image.Rect(0, 0, int(surface.W), int(surface.H)) +} + +// At returns the pixel color at (x, y) +func (surface *Surface) At(x, y int) color.Color { + pix := surface.Pixels() + i := int32(y)*surface.Pitch + int32(x)*int32(surface.Format.BytesPerPixel) + switch surface.Format.Format { + /* + case PIXELFORMAT_ARGB8888: + return color.RGBA{pix[i+3], pix[i], pix[i+1], pix[i+2]} + case PIXELFORMAT_ABGR8888: + return color.RGBA{pix[i], pix[i+3], pix[i+2], pix[i+1]} + */ + case PIXELFORMAT_RGB888: + return color.RGBA{pix[i], pix[i+1], pix[i+2], 0xff} + default: + panic("Not implemented yet") + } +} + +// Set the color of the pixel at (x, y) using this surface's color model to +// convert c to the appropriate color. This method is required for the +// draw.Image interface. The surface may require locking before calling Set. +func (surface *Surface) Set(x, y int, c color.Color) { + pix := surface.Pixels() + i := int32(y)*surface.Pitch + int32(x)*int32(surface.Format.BytesPerPixel) + switch surface.Format.Format { + case PIXELFORMAT_ARGB8888: + col := surface.ColorModel().Convert(c).(color.RGBA) + pix[i+0] = col.R + pix[i+1] = col.G + pix[i+2] = col.B + pix[i+3] = col.A + case PIXELFORMAT_ABGR8888: + col := surface.ColorModel().Convert(c).(color.RGBA) + pix[i+3] = col.R + pix[i+2] = col.G + pix[i+1] = col.B + pix[i+0] = col.A + case PIXELFORMAT_RGB24, PIXELFORMAT_RGB888: + col := surface.ColorModel().Convert(c).(color.RGBA) + pix[i+0] = col.B + pix[i+1] = col.G + pix[i+2] = col.R + case PIXELFORMAT_BGR24, PIXELFORMAT_BGR888: + col := surface.ColorModel().Convert(c).(color.RGBA) + pix[i+2] = col.R + pix[i+1] = col.G + pix[i+0] = col.B + default: + panic("Unknown pixel format!") + } +}