package allg5 // #include // #include 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 } 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 } // 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 } // 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 } // 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.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) 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 } 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() } C.al_destroy_bitmap(bmp) }