package util import ( "errors" "image" "image/color" "io" "log" "sort" "edgaru089.ink/go/gl01/internal/util/itype" ) // The largest width a texture atlas can grow to. const AtlasMaxWidth = 256 // Atlas stores a sequence of textures, binding them into one big texture. type Atlas struct { Image *image.RGBA ImageSize itype.Vec2i imgs []struct { name string img image.Image } rects map[string]itype.Recti } // Add adds a new image.Image with name. func (a *Atlas) Add(name string, img image.Image) { a.imgs = append(a.imgs, struct { name string img image.Image }{name, img}) } // AddFile adds a new image file with name. // // Returns error if image.Decode returns error. func (a *Atlas) AddFile(name string, imgfile io.Reader) error { img, _, err := image.Decode(imgfile) if err != nil { return errors.New("TexAtlas: decoding file: " + err.Error()) } a.imgs = append(a.imgs, struct { name string img image.Image }{name, img}) return nil } // Rect returns texture rect in pixel coordinates, with origin in the // top-left corner. func (a *Atlas) Rect(name string) itype.Recti { recti, ok := a.rects[name] if !ok { recti = a.rects["_dummy_nontransparent"] } return recti } // RectNormalized returns texture rect in normalized coordinates, // i.e., are between [0,1) divided by texture size on each dimension. func (a *Atlas) RectNormalized(name string) itype.Rectf { recti := a.Rect(name) return itype.Rectf{ Left: float32(recti.Left) / float32(a.ImageSize[0]), Top: float32(recti.Top) / float32(a.ImageSize[1]), Width: float32(recti.Width) / float32(a.ImageSize[0]), Height: float32(recti.Height) / float32(a.ImageSize[1]), } } // HasBuilt returns if the Atlas has been built. func (a *Atlas) HasBuilt() bool { return a.rects != nil } // BuildTexture builds the texture atlas. // /*** It can also swaps the texture upside-down to avoid OpenGL coordinate issues. ***/ func (a *Atlas) BuildTexture( /*upsideDown bool*/ ) { var offX, offY, sizeX, sizeY int var maxHeight int // HACK Add a dummy solid image to prevent skipping mask rendering // because of a transparent pixel dummy := image.NewRGBA(image.Rect(0, 0, 2, 2)) dummy.Set(0, 0, color.Black) dummy.Set(0, 1, color.RGBA{255, 0, 255, 255}) dummy.Set(1, 0, color.RGBA{255, 0, 255, 255}) dummy.Set(1, 1, color.Black) a.imgs = append(a.imgs, struct { name string img image.Image }{"_dummy_nontransparent", dummy}) sort.Sort(SortFunc{ Lenf: func() int { return len(a.imgs) }, Swapf: func(i, j int) { a.imgs[i], a.imgs[j] = a.imgs[j], a.imgs[i] }, Lessf: func(i, j int) bool { if a.imgs[i].name == "_dummy_nontransparent" { return true } else if a.imgs[j].name == "_dummy_nontransparent" { return false } else if a.imgs[i].img.Bounds().Dy() < a.imgs[j].img.Bounds().Dy() { return true } else if a.imgs[i].img.Bounds().Dy() == a.imgs[j].img.Bounds().Dy() && a.imgs[i].img.Bounds().Dx() < a.imgs[j].img.Bounds().Dx() { return true } return false }, }) a.rects = make(map[string]itype.Recti) for _, i := range a.imgs { if offX+i.img.Bounds().Dx() > AtlasMaxWidth { // New line offX = 0 offY += maxHeight maxHeight = 0 } a.rects[i.name] = itype.Recti{offX, offY, i.img.Bounds().Dx(), i.img.Bounds().Dy()} log.Printf("Atlas: texture %s at pos %v\n", i.name, a.rects[i.name]) sizeX = Maxi(sizeX, offX+i.img.Bounds().Dx()) sizeY = Maxi(sizeY, offY+i.img.Bounds().Dy()) maxHeight = Maxi(maxHeight, i.img.Bounds().Dy()) offX += i.img.Bounds().Dx() } a.Image = image.NewRGBA(image.Rect(0, 0, sizeX, sizeY)) a.ImageSize = itype.Vec2i{sizeX, sizeY} for _, i := range a.imgs { rect := a.rects[i.name] for x := 0; x < rect.Width; x++ { for y := 0; y < rect.Height; y++ { a.Image.Set( x+rect.Left, y+rect.Top, /*i.img.Bounds().Dy()-1-(y+rect.Top),*/ i.img.At( i.img.Bounds().Min.X+x, i.img.Bounds().Min.Y+y, ), ) } } } }