blooblib/image.go

357 lines
10 KiB
Go

package bloob
import (
"bytes"
_ "github.com/askeladdk/aseprite"
"image"
_ "image/png"
"math"
"os"
"runtime/pprof"
"sync"
"unsafe"
)
type Image struct {
Data []uint32
Size Vec2i
Alpha bool
}
var wg sync.WaitGroup
var threadProfile = pprof.Lookup("threadcreate")
func NewImage(size Vec2i) *Image {
return &Image{Data: make([]uint32, size.Size()), Size: Vec2i{X: size.X, Y: size.Y}, Alpha: true}
}
func NewImageFromPointer(pointer unsafe.Pointer, size Vec2i) *Image {
return &Image{Data: unsafe.Slice((*uint32)(pointer), size.Size()), Size: Vec2i{X: size.X, Y: size.Y}, Alpha: true}
}
func LoadImage(path string) *Image {
data, err := os.ReadFile(path)
if err != nil {
panic(err)
}
return LoadImageBytes(data)
}
func LoadImageBytes(data []byte) *Image {
file, _, _ := image.Decode(bytes.NewReader(data))
width := file.Bounds().Max.X
height := file.Bounds().Max.Y
img := NewImage(Vec2i{X: width, Y: height})
for y := 0; y < height; y += 1 {
for x := 0; x < width; x += 1 {
r, g, b, a := file.At(x, y).RGBA()
r = r >> 8
g = g >> 8
b = b >> 8
a = a >> 8
img.Data[x+y*width] = r<<16 | g<<8 | b | a<<24
}
}
return img
}
func (image *Image) Clear(color uint32) {
for i := 0; i < image.Size.X*image.Size.Y; i += 1 {
image.Data[i] = color
}
}
func CropToArea(sizeDst Vec2i, rectSrc Recti, posDst Vec2i) (start, end, dst Vec2i) {
if posDst.X+rectSrc.Pos.X < 0 {
start.X = -posDst.X
} else {
start.X = rectSrc.Pos.X
}
if posDst.X+rectSrc.Size.X-rectSrc.Pos.X >= sizeDst.X {
end.X = sizeDst.X - posDst.X
} else {
end.X = rectSrc.Pos.X + rectSrc.Size.X
}
if posDst.Y+rectSrc.Pos.Y < 0 {
start.Y = -posDst.Y
} else {
start.Y = rectSrc.Pos.Y
}
if posDst.Y+rectSrc.Size.Y-rectSrc.Pos.Y >= sizeDst.Y {
end.Y = sizeDst.Y - posDst.Y
} else {
end.Y = rectSrc.Pos.Y + rectSrc.Size.Y
}
dst = Sub(posDst, rectSrc.Pos)
//println(dst.X, posDst.X, rectSrc.Pos.X)
return
}
func (image *Image) Draw(other *Image, dst Vec2i) {
image.DrawSub(other, dst, Recti{Size: other.Size})
}
func (image *Image) DrawSub(other *Image, pos Vec2i, rect Recti) {
start, end, dst := CropToArea(image.Size, rect, pos)
//println(start.X, "|", end.X, "|", dst.X, "|", pos.X)
//println(end.Y)
if other.Alpha {
for y := start.Y; y < end.Y; y += 1 {
for x := start.X; x < end.X; x += 1 {
//println(y)
val := other.Data[x+y*other.Size.X]
if val&0xff000000 > 0 {
image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = val
}
}
}
} else {
for y := start.Y; y < end.Y; y += 1 {
for x := start.X; x < end.X; x += 1 {
image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = other.Data[x+y*other.Size.X]
}
}
}
}
func (image *Image) DrawColor(other *Image, dst Vec2i, color uint32) {
image.DrawSubColor(other, dst, Recti{Size: other.Size}, color)
}
func (image *Image) DrawSubColor(other *Image, pos Vec2i, rect Recti, color uint32) {
start, end, dst := CropToArea(image.Size, rect, pos)
for y := start.Y; y < end.Y; y += 1 {
for x := start.X; x < end.X; x += 1 {
//println(y)
val := other.Data[x+y*other.Size.X]
if val&0xff000000 > 0 {
image.Data[(x+dst.X)+(y+dst.Y)*image.Size.X] = color
}
}
}
}
func (image *Image) DrawUpscale(other *Image) {
var scale int
if image.Size.X/other.Size.X < image.Size.Y/other.Size.Y {
scale = image.Size.X / other.Size.X
} else {
scale = image.Size.Y / other.Size.Y
}
switch scale {
case 1:
wg.Add(other.Size.Y)
for y := 0; y < other.Size.Y; y += 1 {
go func(image, other *Image, y int) {
defer wg.Done()
for x := 0; x < other.Size.X; x += 1 {
val := other.Data[x+y*other.Size.X]
image.Data[(x*scale)+(y*scale)*image.Size.X] = val
}
}(image, other, y)
}
wg.Wait()
break
case 2:
wg.Add(other.Size.Y)
for y := 0; y < other.Size.Y; y += 1 {
go func(image, other *Image, y int) {
defer wg.Done()
for x := 0; x < other.Size.X; x += 1 {
val := other.Data[x+y*other.Size.X]
val2 := (val & 0xfefefe) >> 1
dx := x * 2
dy := y * 2
image.Data[(dx+0)+(dy+0)*image.Size.X] = val
image.Data[(dx+1)+(dy+0)*image.Size.X] = val
image.Data[(dx+0)+(dy+1)*image.Size.X] = val2
image.Data[(dx+1)+(dy+1)*image.Size.X] = val2
}
}(image, other, y)
}
wg.Wait()
break
case 3:
wg.Add(other.Size.Y)
//println("Before thread count : ", threadProfile.Count())
for y := 0; y < other.Size.Y; y += 1 {
go func(image, other *Image, y int) {
defer wg.Done()
for x := 0; x < other.Size.X; x += 1 {
val := other.Data[x+y*other.Size.X]
val2 := (val & 0xfefefe) >> 1
dx := x * 3
dy := y * 3
image.Data[(dx+0)+(dy+0)*image.Size.X] = val
image.Data[(dx+1)+(dy+0)*image.Size.X] = val
image.Data[(dx+2)+(dy+0)*image.Size.X] = val2
image.Data[(dx+0)+(dy+1)*image.Size.X] = val
image.Data[(dx+1)+(dy+1)*image.Size.X] = val
image.Data[(dx+2)+(dy+1)*image.Size.X] = val2
image.Data[(dx+0)+(dy+2)*image.Size.X] = val2
image.Data[(dx+1)+(dy+2)*image.Size.X] = val2
image.Data[(dx+2)+(dy+2)*image.Size.X] = val2
}
}(image, other, y)
}
//println("After thread count : ", threadProfile.Count())
wg.Wait()
break
case 4:
wg.Add(other.Size.Y)
for y := 0; y < other.Size.Y; y += 1 {
go func(image, other *Image, y int) {
defer wg.Done()
for x := 0; x < other.Size.X; x += 1 {
val := other.Data[x+y*other.Size.X]
dx := x * 4
dy := y * 4
image.Data[(dx+0)+(dy+0)*image.Size.X] = val
image.Data[(dx+1)+(dy+0)*image.Size.X] = val
image.Data[(dx+2)+(dy+0)*image.Size.X] = val
image.Data[(dx+3)+(dy+0)*image.Size.X] = val
image.Data[(dx+0)+(dy+1)*image.Size.X] = val
image.Data[(dx+1)+(dy+1)*image.Size.X] = val
image.Data[(dx+2)+(dy+1)*image.Size.X] = val
image.Data[(dx+3)+(dy+1)*image.Size.X] = val
image.Data[(dx+0)+(dy+2)*image.Size.X] = val
image.Data[(dx+1)+(dy+2)*image.Size.X] = val
image.Data[(dx+2)+(dy+2)*image.Size.X] = val
image.Data[(dx+3)+(dy+2)*image.Size.X] = val
image.Data[(dx+0)+(dy+3)*image.Size.X] = val
image.Data[(dx+1)+(dy+3)*image.Size.X] = val
image.Data[(dx+2)+(dy+3)*image.Size.X] = val
image.Data[(dx+3)+(dy+3)*image.Size.X] = val
}
}(image, other, y)
}
wg.Wait()
break
default:
wg.Add(other.Size.Y)
for y := 0; y < other.Size.Y; y += 1 {
go func(image, other *Image, y int) {
defer wg.Done()
for x := 0; x < other.Size.X; x += 1 {
val := other.Data[x+y*other.Size.X]
for sy := 0; sy < scale; sy += 1 {
for sx := 0; sx < scale; sx += 1 {
image.Data[(x*scale+sx)+(y*scale+sy)*image.Size.X] = val
}
}
}
}(image, other, y)
}
wg.Wait()
break
}
}
func (image *Image) DrawText(str string, font *Font, pos Vec2i, color uint32) {
origX := pos.X
for _, ch := range str {
c := font.chars[ch]
if ch == 10 {
pos.X = origX
pos.Y += font.lineHeight
continue
}
image.DrawColor(c.image, pos, color)
pos.X += c.xForward
}
}
func (image *Image) DrawTextSin(str string, font *Font, pos Vec2i, color uint32, freq, amplitude, phase float64) {
origX := pos.X
for _, ch := range str {
c := font.chars[ch]
if ch == 10 {
pos.X = origX
pos.Y += font.lineHeight
continue
}
image.DrawColor(c.image, Vec2i{X: pos.X, Y: int(math.Sin(float64(pos.X)*math.Pi/4/freq+phase*math.Pi*2)*amplitude) + pos.Y}, color)
pos.X += c.xForward
}
}
func (image *Image) DrawTextOutline(str string, font *Font, pos Vec2i, color, outlineColor uint32) {
origX := pos.X
for _, ch := range str {
c := font.chars[ch]
if ch == 10 {
pos.X = origX
pos.Y += font.lineHeight
continue
}
x := pos.X
y := pos.Y
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y + 2}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 2}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 2}, outlineColor)
image.DrawColor(c.image, pos, color)
pos.X += c.xForward
}
}
func (image *Image) DrawTextOutlineSin(str string, font *Font, pos Vec2i, color, outlineColor uint32, freq, amplitude, phase float64) {
origX := pos.X
for _, ch := range str {
c := font.chars[ch]
if ch == 10 {
pos.X = origX
pos.Y += font.lineHeight
continue
}
x := pos.X
y := int(math.Sin(float64(pos.X)*math.Pi/4/freq+phase*math.Pi*2)*amplitude) + pos.Y
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y - 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 1}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y + 2}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x - 1, Y: y + 2}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x + 1, Y: y + 2}, outlineColor)
image.DrawColor(c.image, Vec2i{X: x, Y: y}, color)
pos.X += c.xForward
}
}
func (image *Image) DrawTilemap(tilemap *Tilemap, pos Vec2i) {
tilesize := tilemap.Tileset[0].Size
for y := 0; y < tilemap.Size.Y; y += 1 {
for x := 0; x < tilemap.Size.X; x += 1 {
tpos := Vec2i{X: x, Y: y}
image.Draw(tilemap.Tileset[tilemap.Get(tpos)], Add(Mul(tpos, tilesize), pos))
}
}
}
func (image *Image) MakeTileset(tileSize Vec2i) []*Image {
tileCount := Div(image.Size, tileSize)
var tiles []*Image
for y := 0; y < tileCount.Y; y += 1 {
for x := 0; x < tileCount.X; x += 1 {
tile := NewImage(tileSize)
tile.DrawSub(image, Vec2i{}, Recti{Pos: Vec2i{X: x * tileSize.X, Y: y * tileSize.Y}, Size: tileSize})
tiles = append(tiles, tile)
}
}
return tiles
}