Skip to content

Commit

Permalink
Added bitmap RGBA32
Browse files Browse the repository at this point in the history
  • Loading branch information
djthorpe committed Apr 5, 2021
1 parent f99baea commit d35d4e2
Show file tree
Hide file tree
Showing 11 changed files with 687 additions and 2 deletions.
1 change: 1 addition & 0 deletions graphics.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type Bitmap interface {
Format() SurfaceFormat
Size() Size
ClearToColor(color.Color)
SetAt(color.Color, int, int) error
}

// FontManager for font management
Expand Down
120 changes: 120 additions & 0 deletions pkg/graphics/bitmap/bitmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package bitmap

import (
"fmt"
"sync"

// Modules
gopi "github.com/djthorpe/gopi/v3"
multierror "github.com/hashicorp/go-multierror"
)

////////////////////////////////////////////////////////////////////////////////
// INTERFACE

type BitmapFactory interface {
New(fmt gopi.SurfaceFormat, w, h uint32) (gopi.Bitmap, error)
Dispose(gopi.Bitmap) error
}

type Manager struct {
gopi.Unit
gopi.Platform
sync.Mutex

bitmaps []gopi.Bitmap
}

////////////////////////////////////////////////////////////////////////////////
// CONSTANTS

var (
factories = make(map[gopi.SurfaceFormat]BitmapFactory)
)

////////////////////////////////////////////////////////////////////////////////
// LIFECYCLE

func (this *Manager) New(gopi.Config) error {
this.Require(this.Platform)
return nil
}

func (this *Manager) Dispose() error {
this.Mutex.Lock()
defer this.Mutex.Unlock()

// Release all bitmaps
var result error
for _, bitmap := range this.bitmaps {
if bitmap == nil {
// NOOP
} else if err := this.disposeBitmap(bitmap); err != nil {
result = multierror.Append(result, err)
}
}

// Release resources
this.bitmaps = nil

// Return any errors
return result
}

////////////////////////////////////////////////////////////////////////////////
// PUBLIC METHODS

func (this *Manager) NewBitmap(format gopi.SurfaceFormat, w, h uint32) (gopi.Bitmap, error) {
this.Mutex.Lock()
defer this.Mutex.Unlock()

if factory, exists := factories[format]; exists == false {
return nil, gopi.ErrNotFound.WithPrefix(format)
} else if bitmap, err := factory.New(format, w, h); err != nil {
return nil, err
} else {
this.bitmaps = append(this.bitmaps, bitmap)
return bitmap, nil
}
}

func (this *Manager) DisposeBitmap(bitmap gopi.Bitmap) error {
this.Mutex.Lock()
defer this.Mutex.Unlock()

for i := range this.bitmaps {
if this.bitmaps[i] != bitmap {
continue
}
err := this.disposeBitmap(bitmap)
this.bitmaps[i] = nil
return err
}
return gopi.ErrNotFound.WithPrefix("DisposeBitmap")
}

func (this *Manager) disposeBitmap(bitmap gopi.Bitmap) error {
if factory, exists := factories[bitmap.Format()]; exists == false {
return gopi.ErrInternalAppError.WithPrefix("Dispose")
} else {
return factory.Dispose(bitmap)
}
}

////////////////////////////////////////////////////////////////////////////////
// PRIVATE METHODS

func RegisterFactory(factory BitmapFactory, formats ...gopi.SurfaceFormat) {
for _, format := range formats {
if _, exists := factories[format]; exists {
panic("RegisterFactory: Duplicate: " + fmt.Sprint(format))
} else {
factories[format] = factory
}
}
}

func AlignUp(v uint32, a uint32) uint32 {
// Align a value on a byte-bounrary
return ((v - 1) & ^(a - 1)) + a
}
95 changes: 95 additions & 0 deletions pkg/graphics/bitmap/bitmap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package bitmap_test

import (
"image"
"image/color"
"image/png"
"io/ioutil"
"os"
"testing"

// Modules
gopi "github.com/djthorpe/gopi/v3"
bitmap "github.com/djthorpe/gopi/v3/pkg/graphics/bitmap"
tool "github.com/djthorpe/gopi/v3/pkg/tool"

// Dependencies
_ "github.com/djthorpe/gopi/v3/pkg/graphics/bitmap/rgba32"
_ "github.com/djthorpe/gopi/v3/pkg/graphics/bitmap/rgba32dx"
_ "github.com/djthorpe/gopi/v3/pkg/hw/platform"
)

type App struct {
gopi.Unit
*bitmap.Manager
}

const (
PNG_FILEPATH = "../../../etc/images/gopi-800x388.png"
)

func Test_Bitmap_001(t *testing.T) {
tool.Test(t, nil, new(App), func(app *App) {
app.Require(app.Manager)

if bitmap, err := app.NewBitmap(gopi.SURFACE_FMT_RGBA32, 10, 10); err != nil {
t.Error(err)
} else {
t.Log(bitmap)
}
})
}

func Test_Bitmap_002(t *testing.T) {
tool.Test(t, nil, new(App), func(app *App) {
app.Require(app.Manager)

bitmap, err := app.NewBitmap(gopi.SURFACE_FMT_RGBA32, 100, 100)
if err != nil {
t.Fatal(err)
}
bitmap.ClearToColor(color.RGBA{0xFF, 0x00, 0x00, 0xFF})
if writer, err := ioutil.TempFile("", "png"); err != nil {
t.Error(err)
} else if err := png.Encode(writer, bitmap); err != nil {
t.Error(err)
} else {
writer.Close()
t.Log(writer.Name())
}
})
}

func Test_Bitmap_003(t *testing.T) {
tool.Test(t, nil, new(App), func(app *App) {
app.Require(app.Manager)

reader, err := os.Open(PNG_FILEPATH)
if err != nil {
t.Fatal(err)
}
defer reader.Close()
if bitmap, _, err := image.Decode(reader); err != nil {
t.Error(err)
} else if dest, err := app.Manager.NewBitmap(gopi.SURFACE_FMT_RGBA32, uint32(bitmap.Bounds().Dx()), uint32(bitmap.Bounds().Dy())); err != nil {
t.Error(err)
} else {
bounds := bitmap.Bounds()
for y := bounds.Min.Y; y <= bounds.Max.Y; y++ {
for x := bounds.Min.X; x <= bounds.Max.X; x++ {
c := bitmap.At(x, y)
dest.SetAt(c, x, y)
}
}

if writer, err := ioutil.TempFile("", "png"); err != nil {
t.Error(err)
} else if err := png.Encode(writer, dest); err != nil {
t.Error(err)
} else {
writer.Close()
t.Log(writer.Name())
}
}
})
}
3 changes: 3 additions & 0 deletions pkg/graphics/bitmap/rgba32/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package rgba32

/* Represents RGBA32 with in-memory buffer */
142 changes: 142 additions & 0 deletions pkg/graphics/bitmap/rgba32/rgba32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// +build !dispmanx

package rgba32

import (
"fmt"
"image"
"image/color"

// Modules
gopi "github.com/djthorpe/gopi/v3"
bitmap "github.com/djthorpe/gopi/v3/pkg/graphics/bitmap"
)

////////////////////////////////////////////////////////////////////////////////
// INIT

func init() {
bitmap.RegisterFactory(new(Factory), gopi.SURFACE_FMT_RGBA32)
}

////////////////////////////////////////////////////////////////////////////////
// TYPES

type Factory struct{}

type RGBA32 struct {
w, h uint32
stride uint32
buf []Pixel
}

type Model struct{}

type Pixel uint32

////////////////////////////////////////////////////////////////////////////////
// LIFECYCLE

func (this *Factory) New(fmt gopi.SurfaceFormat, w, h uint32) (gopi.Bitmap, error) {
handle := new(RGBA32)
if fmt != gopi.SURFACE_FMT_RGBA32 {
return nil, gopi.ErrBadParameter.WithPrefix("RGBA32")
} else if w == 0 || h == 0 {
return nil, gopi.ErrBadParameter.WithPrefix("RGBA32")
} else {
handle.w = w
handle.h = h
}

// The stride is on 16-byte boundaries
handle.stride = bitmap.AlignUp(handle.w<<2, 16)
handle.buf = make([]Pixel, handle.h*handle.stride)

// Return success
return handle, nil
}

func (this *Factory) Dispose(bitmap gopi.Bitmap) error {
handle := bitmap.(*RGBA32)
handle.w, handle.h = 0, 0
handle.buf = nil
return nil
}

////////////////////////////////////////////////////////////////////////////////
// STRINGIFY

func (this *RGBA32) String() string {
str := "<bitmap.rgba32"
if this.buf != nil {
str += fmt.Sprintf(" format=%q", this.Format())
str += fmt.Sprintf(" size=%v", this.Size())
str += fmt.Sprintf(" stride=%v", this.stride)
}
return str + ">"
}

////////////////////////////////////////////////////////////////////////////////
// METHODS

func (RGBA32) Format() gopi.SurfaceFormat {
return gopi.SURFACE_FMT_RGBA32
}

func (this *RGBA32) Size() gopi.Size {
return gopi.Size{float32(this.w), float32(this.h)}
}

func (this *RGBA32) ClearToColor(c color.Color) {
pixel := this.ColorModel().Convert(c).(Pixel)
for i := range this.buf {
this.buf[i] = pixel
}
}

func (this *RGBA32) At(x, y int) color.Color {
if x < 0 || y < 0 || uint32(x) >= this.w || uint32(y) >= this.h || this.buf == nil {
return Pixel(0x808080FF)
} else {
i := uint32(x) + uint32(y)*(this.stride>>2)
return Pixel(this.buf[i])
}
}

func (this *RGBA32) SetAt(c color.Color, x, y int) error {
if x < 0 || y < 0 || uint32(x) >= this.w || uint32(y) >= this.h || this.buf == nil {
return gopi.ErrBadParameter
}
pixel := this.ColorModel().Convert(c).(Pixel)
i := uint32(x) + uint32(y)*(this.stride>>2)
this.buf[i] = pixel
return nil
}

func (this *RGBA32) ColorModel() color.Model {
return Model{}
}

func (this *RGBA32) Bounds() image.Rectangle {
return image.Rectangle{image.Point{0, 0}, image.Point{int(this.w) - 1, int(this.h) - 1}}

}

////////////////////////////////////////////////////////////////////////////////
// COLOR MODEL

func (Model) Convert(c color.Color) color.Color {
if c, ok := c.(Pixel); ok {
return c
}
r, g, b, a := c.RGBA()
return Pixel(r<<16&0xFF000000) | Pixel(g<<8&0x00FF0000) | Pixel(b<<0&0x0000FF00) | Pixel(a>>8&0x000000FF)
}

func (p Pixel) RGBA() (uint32, uint32, uint32, uint32) {
r := uint32(byte(p>>24)) * 0x0101
g := uint32(byte(p>>16)) * 0x0101
b := uint32(byte(p>>8)) * 0x0101
a := uint32(byte(p)) * 0x0101
return r, g, b, a
}
Loading

0 comments on commit d35d4e2

Please sign in to comment.