Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add convenience function for loading a picture directly from a file #111

Merged
merged 6 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions data.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package pixel

import (
"embed"
"fmt"
"image"
"image/color"
"image/draw"
"io"
"math"
"os"
)

// zeroValueTriangleData is the default value of a TriangleData element
Expand Down Expand Up @@ -179,6 +182,95 @@ func verticalFlip(rgba *image.RGBA) {
}
}

type DecoderFunc func(io.Reader) (image.Image, error)

// DefaultDecoderFunc is a DecoderFunc that uses image.Decode to decode images.
// In order to decode, you must import the image formats you wish to use.
// ex. import _ "image/png"
func DefaultDecoderFunc(r io.Reader) (image.Image, error) {
i, _, err := image.Decode(r)
return i, err
}

// ImageFromEmbed loads an image from an embedded file using the given decoder.
//
// 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 ImageFromEmbed(fs embed.FS, path string, decoder DecoderFunc) (image.Image, error) {
f, err := fs.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

if decoder == nil {
decoder = DefaultDecoderFunc
}

return decoder(f)
}

// ImageFromFile loads an image from a file using the given decoder.
//
// 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 ImageFromFile(path string, decoder DecoderFunc) (image.Image, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()

if decoder == nil {
decoder = DefaultDecoderFunc
}

return decoder(f)
}

// 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 DecoderFunc) (*PictureData, error) {
img, err := ImageFromFile(path, decoder)
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.
Expand Down
24 changes: 11 additions & 13 deletions ext/atlas/atlas.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,13 @@ func (a *Atlas) AddImage(img image.Image) (id TextureId) {
}

// 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)
func (a *Atlas) AddEmbed(fs embed.FS, path string, decoder pixel.DecoderFunc) (id TextureId) {
return a.DefaultGroup().AddEmbed(fs, path, decoder)
}

// AddFile loads an image file to the atlas.
func (a *Atlas) AddFile(path string) (id TextureId) {
return a.DefaultGroup().AddFile(path)
func (a *Atlas) AddFile(path string, decoder pixel.DecoderFunc) (id TextureId) {
return a.DefaultGroup().AddFile(path, decoder)
}

// SliceImage evenly divides the given image into cells of the given size.
Expand All @@ -114,13 +114,13 @@ func (a *Atlas) SliceImage(img image.Image, cellSize pixel.Vec) (id SliceId) {
}

// 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)
func (a *Atlas) SliceFile(path string, cellSize pixel.Vec, decoder pixel.DecoderFunc) (id SliceId) {
return a.DefaultGroup().SliceFile(path, cellSize, decoder)
}

// 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) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec, decoder pixel.DecoderFunc) (id SliceId) {
return a.DefaultGroup().SliceEmbed(fs, path, cellSize, decoder)
}

// Pack takes all of the added textures and adds them to the atlas largest to smallest,
Expand All @@ -133,7 +133,7 @@ func (a *Atlas) Pack() {
}

// If we've already packed the textures, we need to convert them back to images to repack them
if a.internal != nil && len(a.internal) > 0 {
if len(a.internal) > 0 {
images := make([]*image.RGBA, len(a.internal))
for i, data := range a.internal {
images[i] = data.Image()
Expand Down Expand Up @@ -260,10 +260,10 @@ func (a *Atlas) Pack() {
case iImageEntry:
sprite = add.Data()
case iEmbedEntry:
sprite, err = loadEmbedSprite(add.FS(), add.Path())
sprite, err = pixel.ImageFromEmbed(add.FS(), add.Path(), add.DecoderFunc())
err = errors.Wrapf(err, "failed to load embed sprite: %v", add.Path())
case iFileEntry:
sprite, err = loadSprite(add.Path())
sprite, err = pixel.ImageFromFile(add.Path(), add.DecoderFunc())
err = errors.Wrapf(err, "failed to load sprite file: %v", add.Path())
}
if err != nil {
Expand All @@ -281,6 +281,4 @@ func (a *Atlas) Pack() {

a.adding = nil
a.clean = true

return
}
10 changes: 9 additions & 1 deletion ext/atlas/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package atlas
import (
"embed"
"image"

"github.com/gopxl/pixel/v2"
)

type iEntry interface {
Expand Down Expand Up @@ -54,17 +56,23 @@ func (i imageEntry) Data() image.Image {
type iFileEntry interface {
iEntry
Path() string
DecoderFunc() pixel.DecoderFunc
}

type fileEntry struct {
entry
path string
path string
decoderFunc pixel.DecoderFunc
}

func (f fileEntry) Path() string {
return f.path
}

func (f fileEntry) DecoderFunc() pixel.DecoderFunc {
return f.decoderFunc
}

type iSliceEntry interface {
iEntry
Frame() image.Point
Expand Down
28 changes: 16 additions & 12 deletions ext/atlas/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ func (g *Group) AddImage(img image.Image) (id TextureId) {
}

// 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)
func (g *Group) AddEmbed(fs embed.FS, path string, decoder pixel.DecoderFunc) (id TextureId) {
img, err := pixel.ImageFromEmbed(fs, path, decoder)
if err != nil {
panic(err)
}
Expand All @@ -95,16 +95,17 @@ func (g *Group) AddEmbed(fs embed.FS, path string) (id TextureId) {
id: g.atlas.id,
bounds: img.Bounds(),
},
path: path,
path: path,
decoderFunc: decoder,
},
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)
func (g *Group) AddFile(path string, decoder pixel.DecoderFunc) (id TextureId) {
img, err := pixel.ImageFromFile(path, decoder)
if err != nil {
panic(err)
}
bhperry marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -113,7 +114,8 @@ func (g *Group) AddFile(path string) (id TextureId) {
id: g.atlas.id,
bounds: img.Bounds(),
},
path: path,
path: path,
decoderFunc: decoder,
}
return g.addEntry(e)
}
Expand Down Expand Up @@ -145,9 +147,9 @@ func (g *Group) SliceImage(img image.Image, cellSize pixel.Vec) (id SliceId) {
}

// 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) {
func (g *Group) SliceFile(path string, cellSize pixel.Vec, decoder pixel.DecoderFunc) (id SliceId) {
frame := image.Pt(int(cellSize.X), int(cellSize.Y))
img, err := loadSprite(path)
img, err := pixel.ImageFromFile(path, decoder)
if err != nil {
panic(err)
}
Expand All @@ -162,7 +164,8 @@ func (g *Group) SliceFile(path string, cellSize pixel.Vec) (id SliceId) {
id: g.atlas.id,
bounds: bounds,
},
path: path,
path: path,
decoderFunc: decoder,
},
sliceEntry: sliceEntry{
frame: frame,
Expand All @@ -176,8 +179,8 @@ func (g *Group) SliceFile(path string, cellSize pixel.Vec) (id SliceId) {
}

// 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)
func (g *Group) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec, decoder pixel.DecoderFunc) (id SliceId) {
img, err := pixel.ImageFromEmbed(fs, path, decoder)
if err != nil {
panic(err)
}
Expand All @@ -194,7 +197,8 @@ func (g *Group) SliceEmbed(fs embed.FS, path string, cellSize pixel.Vec) (id Sli
id: g.atlas.id,
bounds: bounds,
},
path: path,
path: path,
decoderFunc: decoder,
},
fs: fs,
},
Expand Down
24 changes: 0 additions & 24 deletions ext/atlas/help.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package atlas

import (
"embed"
"image"
"os"

// need the following to automatically register for image.decode
_ "image/jpeg"
Expand All @@ -29,28 +27,6 @@ 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) {
f, err := fs.Open(file)
if err != nil {
return
}
defer f.Close()

i, _, err = image.Decode(f)
return
}

func loadSprite(file string) (i image.Image, err error) {
f, err := os.Open(file)
if err != nil {
return
}
defer f.Close()

i, _, err = image.Decode(f)
return
}

// split is the actual algorithm for splitting a given space (by j in spcs) to fit the given width and height.
// Will return an empty rectangle if a space wasn't available
// This function is based on this project (https://github.com/TeamHypersomnia/rectpack2D)
Expand Down
Loading