diff --git a/data.go b/data.go index 8561fb3..25e09a8 100644 --- a/data.go +++ b/data.go @@ -7,7 +7,6 @@ import ( "image/draw" "io" "math" - "os" ) // zeroValueTriangleData is the default value of a TriangleData element @@ -181,44 +180,13 @@ func verticalFlip(rgba *image.RGBA) { } } -func defaultDecoder(r io.Reader) (image.Image, error) { +type DecoderFunc (func(r io.Reader) (image.Image, error)) + +func DefaultImageDecoder(r io.Reader) (image.Image, error) { i, _, err := image.Decode(r) return i, err } -// PictureDataFromFile loads an image from a file using the given decoder and converts it into PictureData. -// -// We take a decoder function (png.Decode, jpeg.Decode, etc.) as an argument; in order to decode images, -// you have to register the format (png, jpeg, etc.) with the image package, this will increase the number -// of dependencies imposed on a project. We want to avoid importing these in Pixel as it will increase the -// size of the project and it will increase maintanence if we miss a format, or if a new format is added. -// -// With this argument, you implicitly import and register the file formats you need and the Pixel project -// doesn't have to carry all formats around. -// -// The decoder can be nil, and Pixel will fallback onto using image.Decode and require you to import the -// formats you wish to use. -// -// See the example https://github.com/gopxl/pixel-examples/tree/main/core/loadingpictures. -func PictureDataFromFile(path string, decoder func(r io.Reader) (image.Image, error)) (*PictureData, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - if decoder == nil { - decoder = defaultDecoder - } - - img, err := decoder(f) - if err != nil { - return nil, err - } - - return PictureDataFromImage(img), nil -} - // PictureDataFromImage converts an image.Image into PictureData. // // The resulting PictureData's Bounds will be the equivalent of the supplied image.Image's Bounds. diff --git a/ext/atlas/atlas.go b/ext/atlas/atlas.go index eaed6e0..8e0ab73 100644 --- a/ext/atlas/atlas.go +++ b/ext/atlas/atlas.go @@ -1,7 +1,6 @@ package atlas import ( - "embed" "fmt" "image" "image/draw" @@ -12,7 +11,6 @@ import ( "sort" "github.com/gopxl/pixel/v2" - "github.com/pkg/errors" ) const ( @@ -93,34 +91,12 @@ func (a *Atlas) Images() []image.Image { return images } -// AddImage loads an image to the atlas. -func (a *Atlas) AddImage(img image.Image) (id TextureId) { - return a.DefaultGroup().AddImage(img) +func (a *Atlas) Add(loader pixel.LoaderFunc) (id TextureId) { + return a.DefaultGroup().Add(loader) } -// AddEmbed loads an embed.FS image to the atlas. -func (a *Atlas) AddEmbed(fs embed.FS, path string) (id TextureId) { - return a.DefaultGroup().AddEmbed(fs, path) -} - -// AddFile loads an image file to the atlas. -func (a *Atlas) AddFile(path string) (id TextureId) { - return a.DefaultGroup().AddFile(path) -} - -// SliceImage evenly divides the given image into cells of the given size. -func (a *Atlas) SliceImage(img image.Image, cellSize pixel.Vec) (id SliceId) { - return a.DefaultGroup().SliceImage(img, cellSize) -} - -// Slice loads an image and evenly divides it into cells of the given size. -func (a *Atlas) SliceFile(path string, cellSize pixel.Vec) (id SliceId) { - return a.DefaultGroup().SliceFile(path, cellSize) -} - -// SliceEmbed loads an embeded image and evenly divides it into cells of the given size. -func (a *Atlas) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec) (id SliceId) { - return a.DefaultGroup().SliceEmbed(fs, path, cellSize) +func (a *Atlas) Slice(loader pixel.LoaderFunc, cellSize pixel.Vec) (id SliceId) { + return a.DefaultGroup().Slice(loader, cellSize) } // Pack takes all of the added textures and adds them to the atlas largest to smallest, @@ -259,12 +235,12 @@ func (a *Atlas) Pack() { switch add := add.(type) { case iImageEntry: sprite = add.Data() - case iEmbedEntry: - sprite, err = loadEmbedSprite(add.FS(), add.Path()) - err = errors.Wrapf(err, "failed to load embed sprite: %v", add.Path()) - case iFileEntry: - sprite, err = loadSprite(add.Path()) - err = errors.Wrapf(err, "failed to load sprite file: %v", add.Path()) + // case iEmbedEntry: + // sprite, err = loadEmbedSprite(add.FS(), add.Path()) + // err = errors.Wrapf(err, "failed to load embed sprite: %v", add.Path()) + // case iFileEntry: + // sprite, err = loadSprite(add.Path()) + // err = errors.Wrapf(err, "failed to load sprite file: %v", add.Path()) } if err != nil { panic(err) diff --git a/ext/atlas/group.go b/ext/atlas/group.go index 7d1a21f..e1490e5 100644 --- a/ext/atlas/group.go +++ b/ext/atlas/group.go @@ -1,7 +1,6 @@ package atlas import ( - "embed" "fmt" "image" @@ -71,8 +70,11 @@ func (g *Group) addEntry(entry iEntry) (id TextureId) { return } -// AddImage loads an image to the atlas. -func (g *Group) AddImage(img image.Image) (id TextureId) { +func (g *Group) Add(loader pixel.LoaderFunc) (id TextureId) { + img, err := loader() + if err != nil { + panic(err) + } e := imageEntry{ entry: entry{ id: g.atlas.id, @@ -83,43 +85,13 @@ func (g *Group) AddImage(img image.Image) (id TextureId) { return g.addEntry(e) } -// AddEmbed loads an embed.FS image to the atlas. -func (g *Group) AddEmbed(fs embed.FS, path string) (id TextureId) { - img, err := loadEmbedSprite(fs, path) +// SliceImage evenly divides the given image into cells of the given size. +func (g *Group) Slice(loader pixel.LoaderFunc, cellSize pixel.Vec) (id SliceId) { + img, err := loader() if err != nil { panic(err) } - e := embedEntry{ - fileEntry: fileEntry{ - entry: entry{ - id: g.atlas.id, - bounds: img.Bounds(), - }, - path: path, - }, - fs: fs, - } - return g.addEntry(e) -} -// AddFile loads an image file to the atlas. -func (g *Group) AddFile(path string) (id TextureId) { - img, err := loadSprite(path) - if err != nil { - panic(err) - } - e := fileEntry{ - entry: entry{ - id: g.atlas.id, - bounds: img.Bounds(), - }, - path: path, - } - return g.addEntry(e) -} - -// SliceImage evenly divides the given image into cells of the given size. -func (g *Group) SliceImage(img image.Image, cellSize pixel.Vec) (id SliceId) { frame := image.Pt(int(cellSize.X), int(cellSize.Y)) bounds := img.Bounds() if bounds.Dx()%frame.X != 0 || bounds.Dy()%frame.Y != 0 { @@ -143,68 +115,3 @@ func (g *Group) SliceImage(img image.Image, cellSize pixel.Vec) (id SliceId) { len: uint32((bounds.Dx() / frame.X) * (bounds.Dy() / frame.Y)), } } - -// SliceFile loads an image and evenly divides it into cells of the given size. -func (g *Group) SliceFile(path string, cellSize pixel.Vec) (id SliceId) { - frame := image.Pt(int(cellSize.X), int(cellSize.Y)) - img, err := loadSprite(path) - if err != nil { - panic(err) - } - bounds := img.Bounds() - if bounds.Dx()%frame.X != 0 || bounds.Dy()%frame.Y != 0 { - panic(fmt.Sprintf("Texture size (%v,%v) must be multiple of cellSize (%v,%v)", bounds.Dx(), bounds.Dy(), cellSize.X, cellSize.Y)) - } - - e := sliceFileEntry{ - fileEntry: fileEntry{ - entry: entry{ - id: g.atlas.id, - bounds: bounds, - }, - path: path, - }, - sliceEntry: sliceEntry{ - frame: frame, - }, - } - - return SliceId{ - start: g.addEntry(e), - len: uint32((bounds.Dx() / frame.X) * (bounds.Dy() / frame.Y)), - } -} - -// SliceEmbed loads an embeded image and evenly divides it into cells of the given size. -func (g *Group) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec) (id SliceId) { - img, err := loadEmbedSprite(fs, path) - if err != nil { - panic(err) - } - frame := image.Pt(int(cellSize.X), int(cellSize.Y)) - bounds := img.Bounds() - if bounds.Dx()%frame.X != 0 || bounds.Dy()%frame.Y != 0 { - panic(fmt.Sprintf("Texture size (%v,%v) must be multiple of cellSize (%v,%v)", bounds.Dx(), bounds.Dy(), cellSize.X, cellSize.Y)) - } - - e := sliceEmbedEntry{ - embedEntry: embedEntry{ - fileEntry: fileEntry{ - entry: entry{ - id: g.atlas.id, - bounds: bounds, - }, - path: path, - }, - fs: fs, - }, - sliceEntry: sliceEntry{ - frame: frame, - }, - } - - return SliceId{ - start: g.addEntry(e), - len: uint32((bounds.Dx() / frame.X) * (bounds.Dy() / frame.Y)), - } -} diff --git a/ext/atlas/group_test.go b/ext/atlas/group_test.go index 3f1d677..a7d6e14 100644 --- a/ext/atlas/group_test.go +++ b/ext/atlas/group_test.go @@ -5,6 +5,7 @@ import ( "image/color" "testing" + "github.com/gopxl/pixel/v2" "github.com/stretchr/testify/require" ) @@ -32,8 +33,8 @@ func TestAtlas_Clear(t *testing.T) { g1 := a.MakeGroup() // Add our two images to the atlas - s1 := a.AddImage(i1) - g1.AddImage(i2) + s1 := a.Add(pixel.LoadFromImage(i1)) + g1.Add(pixel.LoadFromImage(i2)) a.Pack() @@ -56,8 +57,8 @@ func TestAtlas_ClearAll(t *testing.T) { a := Atlas{} // Add our two images to the atlas - a.AddImage(i1) - a.AddImage(i2) + a.Add(pixel.LoadFromImage(i1)) + a.Add(pixel.LoadFromImage(i2)) a.Pack() diff --git a/ext/atlas/help.go b/ext/atlas/help.go index 5f27b4b..a78a59d 100644 --- a/ext/atlas/help.go +++ b/ext/atlas/help.go @@ -5,10 +5,6 @@ import ( "image" "os" - // need the following to automatically register for image.decode - _ "image/jpeg" - _ "image/png" - "github.com/gopxl/pixel/v2" "golang.org/x/exp/constraints" ) @@ -29,26 +25,32 @@ func image2PixelRect(r image.Rectangle) pixel.Rect { return pixelRect(r.Min.X, r.Min.Y, r.Max.X, r.Max.Y) } -func loadEmbedSprite(fs embed.FS, file string) (i image.Image, err error) { +func loadEmbedSprite(fs embed.FS, file string, decoder pixel.DecoderFunc) (i image.Image, err error) { f, err := fs.Open(file) if err != nil { return } defer f.Close() - i, _, err = image.Decode(f) - return + if decoder == nil { + decoder = pixel.DefaultImageDecoder + } + + return decoder(f) } -func loadSprite(file string) (i image.Image, err error) { +func loadSprite(file string, decoder pixel.DecoderFunc) (i image.Image, err error) { f, err := os.Open(file) if err != nil { return } defer f.Close() - i, _, err = image.Decode(f) - return + if decoder == nil { + decoder = pixel.DefaultImageDecoder + } + + return decoder(f) } // split is the actual algorithm for splitting a given space (by j in spcs) to fit the given width and height. diff --git a/loader.go b/loader.go new file mode 100644 index 0000000..352b417 --- /dev/null +++ b/loader.go @@ -0,0 +1,75 @@ +package pixel + +import ( + "embed" + "image" + "os" +) + +type LoaderFunc func() (image.Image, error) + +func LoadFromFile(path string) LoaderFunc { + return LoadFromFileDecoder(path, DefaultImageDecoder) +} + +func LoadFromFileDecoder(path string, decoder DecoderFunc) LoaderFunc { + return func() (image.Image, error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + if decoder == nil { + decoder = DefaultImageDecoder + } + + return decoder(f) + } +} + +func LoadFromImage(img image.Image) LoaderFunc { + return func() (image.Image, error) { + return img, nil + } +} + +func LoadFromEmbed(fs embed.FS, path string) LoaderFunc { + return LoadFromEmbedDecoder(fs, path, DefaultImageDecoder) +} + +func LoadFromEmbedDecoder(fs embed.FS, path string, decoder DecoderFunc) LoaderFunc { + return func() (image.Image, error) { + f, err := fs.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + if decoder == nil { + decoder = DefaultImageDecoder + } + + return decoder(f) + } +} + +func (l LoaderFunc) PictureData() (*PictureData, error) { + i, err := l() + if err != nil { + return nil, err + } + + return PictureDataFromImage(i), nil +} + +func (l LoaderFunc) Sprite() (*Sprite, error) { + i, err := l() + if err != nil { + return nil, err + } + + pd := PictureDataFromImage(i) + + return NewSprite(pd, pd.Bounds()), nil +}