allg5/bitmap.go

285 lines
7.6 KiB
Go

package allg5
// #include <allegro5/allegro.h>
// #include <stdlib.h>
import "C"
import (
"errors"
"image"
"unsafe"
)
// Bitmap represents an in memory bitmap
type Bitmap struct {
bitmap *C.ALLEGRO_BITMAP
width int
height int
subs []*Bitmap
sup *Bitmap
}
type DrawOptions struct {
Center bool
Scale *Scale
Tint *Color
Rotation *Rotation
}
type Scale struct {
Horizontal float32
Vertical float32
}
func NewScale(hor, ver float32) *Scale {
return &Scale{hor, ver}
}
func NewUniformScale(s float32) *Scale {
return &Scale{s, s}
}
type Rotation struct {
Angle float32
Center bool
}
func newBitmap(width, height int, mut func(m FlagMutation), flags []NewBitmapFlag) (*Bitmap, error) {
var newBmpFlags = CaptureNewBitmapFlags()
defer newBmpFlags.Revert()
newBmpFlags.Mutate(func(m FlagMutation) {
if mut != nil {
mut(m)
}
for _, f := range flags {
m.Set(f)
}
})
b := C.al_create_bitmap(C.int(width), C.int(height))
if b == nil {
return nil, errors.New("error creating bitmap")
}
return &Bitmap{b, width, height, nil, nil}, nil
}
// NewBitmap creates a new bitmap of given width and height and optional flags
func NewBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, nil, flags)
}
// NewVideoBitmap creates a new video bitmap of given width and height and optional flags
func NewVideoBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, func(m FlagMutation) {
m.Unset(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagVideoBitmap)
}, flags)
}
// NewMemoryBitmap creates a new video bitmap of given width and height and optional flags
func NewMemoryBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) {
return newBitmap(width, height, func(m FlagMutation) {
m.Unset(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMemoryBitmap)
}, flags)
}
// NewBitmapFromImage creates a new bitmap starting from a Go native image (image.Image)
func NewBitmapFromImage(src image.Image, video bool) (*Bitmap, error) {
var newBmpFlags = CaptureNewBitmapFlags()
defer newBmpFlags.Revert()
newBmpFlags.Mutate(func(m FlagMutation) {
m.Unset(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagMinLinear)
})
var bnd = src.Bounds()
width, height := bnd.Dx(), bnd.Dy()
var b = C.al_create_bitmap(C.int(width), C.int(height))
if b == nil {
return nil, errors.New("error creating memory bitmap")
}
region := C.al_lock_bitmap(b, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_WRITEONLY)
if region == nil {
C.al_destroy_bitmap(b)
return nil, errors.New("unable to lock bitmap")
}
dst := (*[1 << 30]uint8)(region.data)
left, top := bnd.Min.X, bnd.Min.Y
for y := 0; y < height; y++ {
row := dst[y*int(region.pitch):]
for x := 0; x < width; x++ {
r, g, b, a := src.At(left+x, top+y).RGBA()
row[x*4] = uint8(r >> 8)
row[x*4+1] = uint8(g >> 8)
row[x*4+2] = uint8(b >> 8)
row[x*4+3] = uint8(a >> 8)
}
}
C.al_unlock_bitmap(b)
if video {
newBmpFlags.Mutate(func(m FlagMutation) {
m.Unset(NewBitmapFlagMemoryBitmap)
m.Set(NewBitmapFlagVideoBitmap)
m.Set(NewBitmapFlagMinLinear)
})
C.al_convert_bitmap(b)
}
return &Bitmap{b, width, height, nil, nil}, nil
}
// LoadBitmap tries to load the image at the specified path as a bitmap
func LoadBitmap(path string) (*Bitmap, error) {
p := C.CString(path)
defer C.free(unsafe.Pointer(p))
b := C.al_load_bitmap(p)
if b == nil {
return nil, errors.New("error loading bitmap")
}
width := int(C.al_get_bitmap_width(b))
height := int(C.al_get_bitmap_height(b))
return &Bitmap{b, width, height, nil, nil}, nil
}
// Draw draws the bitmap at the given location
func (b *Bitmap) Draw(left, top float32) {
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
}
func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) {
width := float32(b.width)
height := float32(b.height)
scale := options.Scale != nil
if scale {
width *= options.Scale.Horizontal
height *= options.Scale.Vertical
}
if options.Center {
left -= width * 0.5
top -= height * 0.5
}
rotated := options.Rotation != nil
var centerX C.float
var centerY C.float
if rotated && options.Rotation.Center {
centerX = C.float(b.width) * 0.5
centerY = C.float(b.height) * 0.5
}
if scale {
if options.Tint == nil { // scaled
if rotated { // scaled & rotated
C.al_draw_scaled_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal), C.float(options.Scale.Vertical), C.float(options.Rotation.Angle), 0)
} else { // scaled
C.al_draw_scaled_bitmap(b.bitmap, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0)
}
} else { // tinted & scaled
if rotated { // scaled, tinted & rotated
C.al_draw_tinted_scaled_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal), C.float(options.Scale.Vertical), C.float(options.Rotation.Angle), 0)
} else { // tinted, scaled
C.al_draw_tinted_scaled_bitmap(b.bitmap, options.Tint.color, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0)
}
}
} else {
if options.Tint == nil {
if rotated { // rotated
C.al_draw_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
} else {
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
}
} else { // tinted
if rotated { // tinted & rotated
C.al_draw_tinted_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0)
} else {
C.al_draw_tinted_bitmap(b.bitmap, options.Tint.color, C.float(left), C.float(top), 0)
}
}
}
}
// Sub creates a sub-bitmap of the original bitmap
func (b *Bitmap) Sub(x, y, w, h int) *Bitmap {
var sub = C.al_create_sub_bitmap(b.bitmap, C.int(x), C.int(y), C.int(w), C.int(h))
if sub == nil {
return nil
}
var bmp = &Bitmap{sub, w, h, nil, b}
b.subs = append(b.subs, bmp)
return bmp
}
// Subs returns the slice of sub-bitmaps
func (b *Bitmap) Subs() []*Bitmap {
return b.subs
}
func (b *Bitmap) Width() int {
return b.width
}
func (b *Bitmap) Height() int {
return b.height
}
func (b *Bitmap) IsVideo() bool {
return C.al_get_bitmap_flags(b.bitmap)&C.ALLEGRO_VIDEO_BITMAP == C.ALLEGRO_VIDEO_BITMAP
}
func (b *Bitmap) Image() image.Image {
im := image.NewRGBA(image.Rect(0, 0, b.width, b.height))
region := C.al_lock_bitmap(b.bitmap, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_READONLY)
if region == nil {
return nil
}
defer C.al_unlock_bitmap(b.bitmap)
pitch := int(region.pitch)
length := b.height * pitch
data := uintptr(region.data)
var srcOff, dstOff int
if pitch < 0 {
srcOff = (b.height - 1) * -pitch
length *= -1
data = data - uintptr(srcOff)
}
rowLength := b.width * 4
src := (*[1 << 28]uint8)(unsafe.Pointer(data))[:length:length]
dst := im.Pix
for y := 0; y < b.height; y++ {
copy(dst[dstOff:], src[srcOff:srcOff+rowLength])
srcOff += pitch
dstOff += im.Stride
}
return im
}
func (b *Bitmap) SetAsTarget() {
C.al_set_target_bitmap(b.bitmap)
}
// Destroy destroys the bitmap
func (b *Bitmap) Destroy() {
var bmp = b.bitmap
if bmp == nil {
return
}
b.bitmap = nil
for _, sub := range b.subs {
sub.Destroy()
}
if b.sup != nil {
b.sup.removeSub(b)
}
C.al_destroy_bitmap(bmp)
}
func (b *Bitmap) removeSub(sub *Bitmap) {
for i := 0; i < len(b.subs); i++ {
if b.subs[i] == sub {
b.subs = append(b.subs[:i], b.subs[i+1:]...)
return
}
}
}