2018-11-21 13:35:14 +00:00
|
|
|
package allg5
|
2017-10-03 18:38:09 +00:00
|
|
|
|
|
|
|
// #include <allegro5/allegro.h>
|
|
|
|
// #include <stdlib.h>
|
|
|
|
import "C"
|
|
|
|
|
|
|
|
import (
|
2018-02-10 08:12:42 +00:00
|
|
|
"errors"
|
2017-10-23 09:21:02 +00:00
|
|
|
"image"
|
2017-10-03 18:41:45 +00:00
|
|
|
"unsafe"
|
2017-10-03 18:38:09 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Bitmap represents an in memory bitmap
|
|
|
|
type Bitmap struct {
|
2017-10-03 18:41:45 +00:00
|
|
|
bitmap *C.ALLEGRO_BITMAP
|
|
|
|
width int
|
|
|
|
height int
|
2018-08-07 04:59:52 +00:00
|
|
|
subs []*Bitmap
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type DrawOptions struct {
|
2017-10-23 09:21:02 +00:00
|
|
|
Center bool
|
2019-03-13 18:49:00 +00:00
|
|
|
Scale *Scale
|
2017-10-23 09:21:02 +00:00
|
|
|
Tint *Color
|
|
|
|
Rotation *Rotation
|
|
|
|
}
|
|
|
|
|
2019-03-13 18:49:00 +00:00
|
|
|
type Scale struct {
|
|
|
|
Horizontal float32
|
|
|
|
Vertical float32
|
2017-10-23 09:21:02 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:49:00 +00:00
|
|
|
func NewScale(hor, ver float32) *Scale {
|
|
|
|
return &Scale{hor, ver}
|
2017-10-23 09:21:02 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:49:00 +00:00
|
|
|
func NewUniformScale(s float32) *Scale {
|
|
|
|
return &Scale{s, s}
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
2017-10-23 09:21:02 +00:00
|
|
|
type Rotation struct {
|
|
|
|
Angle float32
|
|
|
|
Center bool
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
2018-02-26 18:46:53 +00:00
|
|
|
func newBitmap(width, height int, mut func(m FlagMutation), flags []NewBitmapFlag) (*Bitmap, error) {
|
|
|
|
var newBmpFlags = CaptureNewBitmapFlags()
|
|
|
|
defer newBmpFlags.Revert()
|
|
|
|
newBmpFlags.Mutate(func(m FlagMutation) {
|
2019-03-05 20:52:18 +00:00
|
|
|
if mut != nil {
|
2018-02-26 18:46:53 +00:00
|
|
|
mut(m)
|
|
|
|
}
|
|
|
|
for _, f := range flags {
|
|
|
|
m.Set(f)
|
|
|
|
}
|
|
|
|
})
|
2017-10-03 18:41:45 +00:00
|
|
|
b := C.al_create_bitmap(C.int(width), C.int(height))
|
2019-03-05 20:52:18 +00:00
|
|
|
if b == nil {
|
2018-02-13 19:59:55 +00:00
|
|
|
return nil, errors.New("error creating bitmap")
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|
2018-08-07 04:59:52 +00:00
|
|
|
return &Bitmap{b, width, height, nil}, nil
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
2018-02-26 18:46:53 +00:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
|
2017-10-23 09:21:02 +00:00
|
|
|
// NewBitmapFromImage creates a new bitmap starting from a Go native image (image.Image)
|
2019-03-13 18:49:00 +00:00
|
|
|
func NewBitmapFromImage(src image.Image, video bool) (*Bitmap, error) {
|
2018-02-26 06:51:58 +00:00
|
|
|
var newBmpFlags = CaptureNewBitmapFlags()
|
2018-02-15 19:17:27 +00:00
|
|
|
defer newBmpFlags.Revert()
|
2018-02-26 06:51:58 +00:00
|
|
|
newBmpFlags.Mutate(func(m FlagMutation) {
|
|
|
|
m.Unset(NewBitmapFlagVideoBitmap)
|
|
|
|
m.Set(NewBitmapFlagMemoryBitmap)
|
|
|
|
m.Set(NewBitmapFlagMinLinear)
|
2018-02-15 19:17:27 +00:00
|
|
|
})
|
2019-03-13 18:49:00 +00:00
|
|
|
var bnd = src.Bounds()
|
2018-02-10 08:12:42 +00:00
|
|
|
width, height := bnd.Dx(), bnd.Dy()
|
2018-02-15 19:17:27 +00:00
|
|
|
var b = C.al_create_bitmap(C.int(width), C.int(height))
|
2019-03-05 20:52:18 +00:00
|
|
|
if b == nil {
|
2018-02-15 19:17:27 +00:00
|
|
|
return nil, errors.New("error creating memory bitmap")
|
2017-10-23 09:21:02 +00:00
|
|
|
}
|
2019-03-13 18:49:00 +00:00
|
|
|
region := C.al_lock_bitmap(b, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_WRITEONLY)
|
|
|
|
if region == nil {
|
2018-02-15 19:17:27 +00:00
|
|
|
C.al_destroy_bitmap(b)
|
2018-02-10 08:12:42 +00:00
|
|
|
return nil, errors.New("unable to lock bitmap")
|
|
|
|
}
|
2019-03-13 18:49:00 +00:00
|
|
|
dst := (*[1 << 30]uint8)(region.data)
|
2018-02-10 08:12:42 +00:00
|
|
|
for y := 0; y < height; y++ {
|
2019-03-13 18:49:00 +00:00
|
|
|
row := dst[y*int(region.pitch):]
|
2018-02-10 08:12:42 +00:00
|
|
|
for x := 0; x < width; x++ {
|
2019-03-13 18:49:00 +00:00
|
|
|
r, g, b, a := src.At(x, 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)
|
2018-02-10 08:12:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-02-15 19:17:27 +00:00
|
|
|
C.al_unlock_bitmap(b)
|
|
|
|
if video {
|
2018-02-26 06:51:58 +00:00
|
|
|
newBmpFlags.Mutate(func(m FlagMutation) {
|
|
|
|
m.Unset(NewBitmapFlagMemoryBitmap)
|
|
|
|
m.Set(NewBitmapFlagVideoBitmap)
|
|
|
|
m.Set(NewBitmapFlagMinLinear)
|
2018-02-15 19:17:27 +00:00
|
|
|
})
|
|
|
|
C.al_convert_bitmap(b)
|
|
|
|
}
|
2018-08-07 04:59:52 +00:00
|
|
|
return &Bitmap{b, width, height, nil}, nil
|
2017-10-23 09:21:02 +00:00
|
|
|
}
|
|
|
|
|
2017-10-03 18:41:45 +00:00
|
|
|
// LoadBitmap tries to load the image at the specified path as a bitmap
|
2017-10-03 18:38:09 +00:00
|
|
|
func LoadBitmap(path string) (*Bitmap, error) {
|
2017-10-03 18:41:45 +00:00
|
|
|
p := C.CString(path)
|
|
|
|
defer C.free(unsafe.Pointer(p))
|
|
|
|
b := C.al_load_bitmap(p)
|
2019-03-05 20:52:18 +00:00
|
|
|
if b == nil {
|
2018-02-13 19:59:55 +00:00
|
|
|
return nil, errors.New("error loading bitmap")
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|
|
|
|
width := int(C.al_get_bitmap_width(b))
|
|
|
|
height := int(C.al_get_bitmap_height(b))
|
2018-08-07 04:59:52 +00:00
|
|
|
return &Bitmap{b, width, height, nil}, nil
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw draws the bitmap at the given location
|
|
|
|
func (b *Bitmap) Draw(left, top float32) {
|
2017-10-03 18:41:45 +00:00
|
|
|
C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0)
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) {
|
2017-10-03 18:41:45 +00:00
|
|
|
width := float32(b.width)
|
|
|
|
height := float32(b.height)
|
2017-10-03 18:38:09 +00:00
|
|
|
|
2019-03-13 18:49:00 +00:00
|
|
|
scale := options.Scale != nil
|
2017-10-03 18:41:45 +00:00
|
|
|
if scale {
|
2019-03-13 18:49:00 +00:00
|
|
|
width *= options.Scale.Horizontal
|
|
|
|
height *= options.Scale.Vertical
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|
|
|
|
if options.Center {
|
|
|
|
left -= width * 0.5
|
|
|
|
top -= height * 0.5
|
|
|
|
}
|
2019-03-13 18:49:00 +00:00
|
|
|
rotated := options.Rotation != nil
|
2017-10-23 09:21:02 +00:00
|
|
|
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
|
|
|
|
}
|
2017-10-03 18:41:45 +00:00
|
|
|
|
|
|
|
if scale {
|
2019-03-05 20:52:18 +00:00
|
|
|
if options.Tint == nil { // scaled
|
2017-10-23 09:21:02 +00:00
|
|
|
if rotated { // scaled & rotated
|
2019-03-13 18:49:00 +00:00
|
|
|
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)
|
2017-10-23 09:21:02 +00:00
|
|
|
} 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
|
2019-03-13 18:49:00 +00:00
|
|
|
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)
|
2017-10-23 09:21:02 +00:00
|
|
|
} 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)
|
|
|
|
}
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|
|
|
|
} else {
|
2019-03-05 20:52:18 +00:00
|
|
|
if options.Tint == nil {
|
2017-10-23 09:21:02 +00:00
|
|
|
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)
|
|
|
|
}
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
2018-08-07 04:59:52 +00:00
|
|
|
// 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))
|
2019-03-05 20:52:18 +00:00
|
|
|
if sub == nil {
|
2018-08-07 04:59:52 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
var bmp = &Bitmap{sub, w, h, nil}
|
|
|
|
b.subs = append(b.subs, bmp)
|
|
|
|
return bmp
|
|
|
|
}
|
|
|
|
|
|
|
|
// Subs returns the slice of sub-bitmaps
|
|
|
|
func (b *Bitmap) Subs() []*Bitmap {
|
|
|
|
return b.subs
|
|
|
|
}
|
|
|
|
|
2017-10-03 18:38:09 +00:00
|
|
|
func (b *Bitmap) Width() int {
|
2017-10-03 18:41:45 +00:00
|
|
|
return b.width
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Bitmap) Height() int {
|
2017-10-03 18:41:45 +00:00
|
|
|
return b.height
|
2017-10-03 18:38:09 +00:00
|
|
|
}
|
|
|
|
|
2019-03-13 18:49:00 +00:00
|
|
|
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)
|
|
|
|
src := (*[1 << 30]uint8)(region.data)
|
|
|
|
dst := im.Pix
|
|
|
|
var srcOff, dstOff int
|
|
|
|
for y := 0; y < b.height; y++ {
|
|
|
|
copy(dst[dstOff:], src[srcOff:srcOff+b.width*4])
|
|
|
|
srcOff += int(region.pitch)
|
|
|
|
dstOff += im.Stride
|
|
|
|
}
|
|
|
|
return im
|
|
|
|
}
|
|
|
|
|
2018-02-26 06:51:58 +00:00
|
|
|
func (b *Bitmap) SetAsTarget() {
|
|
|
|
C.al_set_target_bitmap(b.bitmap)
|
|
|
|
}
|
|
|
|
|
2017-10-03 18:38:09 +00:00
|
|
|
// Destroy destroys the bitmap
|
|
|
|
func (b *Bitmap) Destroy() {
|
2018-08-07 04:59:52 +00:00
|
|
|
var bmp = b.bitmap
|
2019-03-05 20:52:18 +00:00
|
|
|
if bmp == nil {
|
2018-08-07 04:59:52 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
b.bitmap = nil
|
|
|
|
for _, sub := range b.subs {
|
|
|
|
sub.Destroy()
|
|
|
|
}
|
|
|
|
C.al_destroy_bitmap(bmp)
|
2017-10-03 18:41:45 +00:00
|
|
|
}
|