package bloob import ( "bytes" _ "github.com/askeladdk/aseprite" "image" _ "image/png" "math" "os" "runtime/pprof" "sync" "unsafe" ) type Image struct { Data []Color Size Vec2i Alpha bool } type Color = uint32 var wg sync.WaitGroup var threadProfile = pprof.Lookup("threadcreate") func NewImage(size Vec2i) *Image { return &Image{Data: make([]Color, 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((*Color)(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 Color) { 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) DrawHLine(pos Vec2i, length int, color Color) { for i := 0; i < length; i += 1 { image.Data[pos.X+i+pos.Y*image.Size.X] = color } } func (image *Image) DrawVLine(pos Vec2i, length int, color Color) { for i := 0; i < length; i += 1 { image.Data[pos.X+(pos.Y+i)*image.Size.X] = color } } 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 Color) { image.DrawSubColor(other, dst, Recti{Size: other.Size}, color) } func (image *Image) DrawSubColor(other *Image, pos Vec2i, rect Recti, color Color) { 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 Color) { 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 Color, 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 Color) { 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 Color, 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 }