Skip to content
This repository has been archived by the owner on Aug 22, 2024. It is now read-only.

Commit

Permalink
Fixing and testing packer
Browse files Browse the repository at this point in the history
  • Loading branch information
dusk125 committed Oct 14, 2021
1 parent 115ef18 commit 04ba280
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 139 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@

# Dependency directories (remove the comment below to include it)
# vendor/

*.png
7 changes: 2 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
module github.com/dusk125/rectpack

go 1.16
go 1.17

require (
github.com/dusk125/pixelutils v1.0.0
github.com/faiface/pixel v0.10.0
)
require golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
33 changes: 2 additions & 31 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,33 +1,4 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dusk125/pixelutils v1.0.0 h1:oaJW0Bl6I+WQb4DU8gzMKxNW5+ZwBBQXQLriXsiaKEU=
github.com/dusk125/pixelutils v1.0.0/go.mod h1:V17sw4+q2QWactw9hamp1XgGQQqKBJ6MAO1jEu+fPp8=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0=
github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q=
github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M=
github.com/faiface/pixel v0.10.0 h1:EHm3ZdQw2Ck4y51cZqFfqQpwLqNHOoXwbNEc9Dijql0=
github.com/faiface/pixel v0.10.0/go.mod h1:lU0YYcW77vL0F1CG8oX51GXurymL45MXd57otHNLK7A=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72 h1:b+9H1GAsx5RsjvDFLoS5zkNBzIQMuVKUYQDmxU3N5XE=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI=
github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pzsz/voronoi v0.0.0-20130609164533-4314be88c79f h1:YeUeWAPTrycaikVXiVoAu1dxIR4+tSsn9IbTkNfRsss=
github.com/pzsz/voronoi v0.0.0-20130609164533-4314be88c79f/go.mod h1:T6EcHUIQ7r0Xr1jjOJ7hmpUaqVyqb9WimeNzS9nz4Zo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
84 changes: 51 additions & 33 deletions pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package rectpack
import (
"errors"
"image"
"image/color"
"image/jpeg"
"image/png"
"os"
Expand All @@ -19,53 +18,47 @@ var (
ErrorSplitFailed = errors.New("Split failed")
ErrGrowthFailed = errors.New("A previously added texture failed to be added after packer growth")
ErrUnsupportedSaveExt = errors.New("Unsupported save filename extension")
ErrNotPacked = errors.New("Packer must be packed")
ErrNotFoundNoDefault = errors.New("Id doesn't exist and a default sprite wasn't specified")
)

type PackFlags uint8
type CreateFlags uint8

const (
AllowGrowth CreateFlags = 1 << iota // Should the packer space try to grow larger to fit oversized images
)

const (
InsertFlipped PackFlags = 1 << iota // Should the sprite be inserted into the packer upside-down
)

type IPack interface {
Pack(flags PackFlags) (err error)
Save(filename string) (err error)
type PackerCfg struct {
Flags CreateFlags
}

type Packer struct {
cfg PackerCfg
bounds image.Rectangle
emptySpaces []image.Rectangle
queued []queuedData
rects map[int]image.Rectangle
images map[int]*image.RGBA
flags CreateFlags
pic *image.RGBA
nfId int
packed bool
}

// Creates a new packer instance
func NewPacker(width, height int, flags CreateFlags) (pack *Packer) {
bounds := rect(0, 0, width, height)
func NewPacker(cfg PackerCfg) (pack *Packer) {
bounds := rect(0, 0, 0, 0)
pack = &Packer{
cfg: cfg,
bounds: bounds,
flags: flags,
emptySpaces: []image.Rectangle{bounds},
emptySpaces: []image.Rectangle{},
rects: make(map[int]image.Rectangle),
images: make(map[int]*image.RGBA),
queued: make([]queuedData, 0),
nfId: -1,
}
return
}

// Inserts PictureData into the packer
func (pack *Packer) Insert(id int, pic *image.RGBA) (err error) {
func (pack *Packer) Insert(id int, pic *image.RGBA) {
pack.queued = append(pack.queued, queuedData{id: id, pic: pic})
return
}

// Helper to find the smallest empty space that'll fit the given bounds
Expand Down Expand Up @@ -134,7 +127,8 @@ func (pack *Packer) insert(data queuedData) (err error) {
}

// Pack takes the added textures and packs them into the packer texture, growing the texture if necessary.
func (pack *Packer) Pack(flags PackFlags) (err error) {
func (pack *Packer) Pack() (err error) {
// sort queued images largest to smallest
sort.Slice(pack.queued, func(i, j int) bool {
return area(pack.queued[i].pic.Bounds()) > area(pack.queued[j].pic.Bounds())
})
Expand All @@ -146,10 +140,6 @@ func (pack *Packer) Pack(flags PackFlags) (err error) {
)

if !found {
if pack.flags&AllowGrowth == 0 {
return ErrorNoEmptySpace
}

if err = pack.grow(bounds.Size(), i); err != nil {
return
}
Expand All @@ -165,27 +155,26 @@ func (pack *Packer) Pack(flags PackFlags) (err error) {
for x := 0; x < pic.Bounds().Dx(); x++ {
for y := 0; y < pic.Bounds().Dy(); y++ {
var (
c color.Color
rect = pack.rects[id]
)
if flags&InsertFlipped != 0 {
c = pic.At(x, (pic.Bounds().Dy()-1)-y)
} else {
c = pic.At(x, y)
}
pack.pic.Set(x+rect.Min.X, y+rect.Min.Y, c)
pack.pic.Set(x+rect.Min.X, y+rect.Min.Y, pic.At(x, y))
}
}
}
pack.queued = nil
pack.emptySpaces = nil
pack.images = nil
pack.packed = true

return
}

// Saves the internal texture as a file on disk, the output type is defined by the filename extension
func (pack *Packer) Save(filename string) (err error) {
if !pack.packed {
return ErrNotPacked
}

var (
file *os.File
)
Expand Down Expand Up @@ -214,18 +203,47 @@ func (pack *Packer) Save(filename string) (err error) {
return
}

func (pack *Packer) SetNotFoundId(id int) {
// Sets the default Id for the packer
// If an id doesn't exist in the packer when 'Get' is called, the packer will return this sprite instead.
func (pack *Packer) SetDefaultId(id int) {
pack.nfId = id
}

// Returns the subimage bounds from the given id
func (pack *Packer) Get(id int) (rect image.Rectangle) {
if !pack.packed {
panic(ErrNotPacked)
}

var has bool
if rect, has = pack.rects[id]; !has {
if pack.nfId == -1 {
panic(ErrNotFoundNoDefault)
}
rect = pack.rects[pack.nfId]
}
return
}

func (pack *Packer) Image() image.Image {
// Returns the subimage, as a copy, from the given id
func (pack *Packer) SubImage(id int) (img *image.RGBA) {
if !pack.packed {
panic(ErrNotPacked)
}

r := pack.Get(id)
i := pack.pic.PixOffset(r.Min.X, r.Min.Y)
return &image.RGBA{
Pix: pack.pic.Pix[i:],
Stride: pack.pic.Stride,
Rect: image.Rect(0, 0, r.Dx(), r.Dy()),
}
}

// Returns the entire packed image
func (pack *Packer) Image() *image.RGBA {
if !pack.packed {
panic(ErrNotPacked)
}
return pack.pic
}
137 changes: 137 additions & 0 deletions pack_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package rectpack_test

import (
"errors"
"fmt"
"image"
"image/color"
"image/jpeg"
"image/png"
"math/rand"
"os"
"path"
"testing"

"github.com/dusk125/rectpack"
"golang.org/x/image/colornames"
)

func fill(w, h int, c color.Color) (img *image.RGBA) {
img = image.NewRGBA(image.Rect(0, 0, w, h))
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
img.Set(x, y, c)
}
}
return
}

func colorEq(i2 image.Image, w, h int, c color.Color) (err error) {
var i1 = fill(w, h, c)

if !i1.Bounds().Size().Eq(i2.Bounds().Size()) {
return fmt.Errorf("Image sizes are not the same: Expected: %s, Got: %s", i1.Bounds().Size(), i2.Bounds().Size())
}

for x := 0; x < i1.Bounds().Dx(); x++ {
for y := 0; y < i1.Bounds().Dy(); y++ {
r1, g1, b1, a1 := i1.At(x, y).RGBA()
r2, g2, b2, a2 := i2.At(x, y).RGBA()
if r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2 {
return fmt.Errorf("At: (%d, %d), Expected: (%v, %v, %v, %v), Got: (%v, %v, %v, %v)", x, y, r1, g1, b1, a1, r2, b2, g2, a2)
}
}
}

return nil
}

func TestNewPacker(t *testing.T) {
t.Run("Test", func(t *testing.T) {
pack := rectpack.NewPacker(rectpack.PackerCfg{})
colors := []struct {
col color.Color
w, h int
}{
{
col: colornames.Black,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Aliceblue,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Navy,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Salmon,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Orchid,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Olive,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
{
col: colornames.Oldlace,
w: rand.Intn(1024),
h: rand.Intn(1024),
},
}
for i, c := range colors {
pack.Insert(i, fill(c.w, c.h, c.col))
}
if err := pack.Pack(); err != nil {
t.Error(err)
}
if err := pack.Save("test.png"); err != nil {
t.Error(err)
}
for i, c := range colors {
img := pack.SubImage(i)
if err := colorEq(img, c.w, c.h, c.col); err != nil {
t.Errorf("%d is not expected: %s", i, err.Error())
}
}
})
}

func Save(filename string, img image.Image) (err error) {
var (
file *os.File
)

if err = os.Remove(filename); err != nil && !errors.Is(err, os.ErrNotExist) {
return
}

if file, err = os.Create(filename); err != nil {
return
}
defer file.Close()

switch path.Ext(filename) {
case ".png":
err = png.Encode(file, img)
case ".jpeg", ".jpg":
err = jpeg.Encode(file, img, nil)
default:
err = errors.New("Bad extension")
}
if err != nil {
return
}

return
}
Loading

0 comments on commit 04ba280

Please sign in to comment.