Noah Petherbridge
640e75ba4d
* You can now browse for a custom wallpaper image to use with your levels. A platform-native file picker dialog is used (no WASM support) * In the New/Edit Level Properties dialog, the Wallpaper drop-down includes an option to browse for a custom map. * When editing an existing level: the wallpaper takes effect immediately in your level once the file is picked. For NEW levels, the wallpaper will appear once the "Continue" button is pressed. * All common image types supported: png, jpeg, gif. * The wallpaper is embedded in the level using the filepath "assets/wallpapers/custom.b64img" as a Base64-encoded blob of the image data. * The `doodad show` command will list the names and sizes of files embedded in levels. `doodad show --attachment <name>` will get an attachment and print it to the console window. * To extract a wallpaper image from a level: `doodad show -a assets/wallpapers/custom.b64img | base64 -d > out.png`
186 lines
4.5 KiB
Go
186 lines
4.5 KiB
Go
package wallpaper
|
|
|
|
import (
|
|
"bytes"
|
|
"image"
|
|
"image/draw"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"encoding/base64"
|
|
|
|
"git.kirsle.net/apps/doodle/pkg/bindata"
|
|
"git.kirsle.net/apps/doodle/pkg/filesystem"
|
|
"git.kirsle.net/apps/doodle/pkg/log"
|
|
"git.kirsle.net/go/render"
|
|
)
|
|
|
|
// Wallpaper is a repeatable background image to go behind levels.
|
|
type Wallpaper struct {
|
|
Name string
|
|
Format string // image file format
|
|
Image *image.RGBA
|
|
|
|
// Ready status is set to true if the wallpaper loaded itself properly.
|
|
// Notably in WASM, wallpapers don't load currently.
|
|
ready bool
|
|
|
|
// Parsed values.
|
|
quarterWidth int
|
|
quarterHeight int
|
|
|
|
// The four parsed images.
|
|
corner *image.RGBA // Top Left corner
|
|
top *image.RGBA // Top repeating
|
|
left *image.RGBA // Left repeating
|
|
repeat *image.RGBA // Main repeating
|
|
|
|
// Cached textures.
|
|
tex struct {
|
|
corner render.Texturer
|
|
top render.Texturer
|
|
left render.Texturer
|
|
repeat render.Texturer
|
|
}
|
|
}
|
|
|
|
// FromImage creates a Wallpaper from an image.Image.
|
|
// If the renger.Engine is nil it will compute images but not pre-cache any
|
|
// textures yet.
|
|
func FromImage(e render.Engine, img *image.RGBA, name string) (*Wallpaper, error) {
|
|
wp := &Wallpaper{
|
|
Name: name,
|
|
Image: img,
|
|
}
|
|
wp.cache(e)
|
|
return wp, nil
|
|
}
|
|
|
|
// FromFile creates a Wallpaper from a file on disk.
|
|
// If the renger.Engine is nil it will compute images but not pre-cache any
|
|
// textures yet.
|
|
func FromFile(e render.Engine, filename string, embeddable filesystem.Embeddable) (*Wallpaper, error) {
|
|
// Default object to return on errors.
|
|
var defaultWP = &Wallpaper{
|
|
Name: strings.Split(filepath.Base(filename), ".")[0],
|
|
ready: false,
|
|
}
|
|
|
|
// WASM: no support yet for wallpapers.
|
|
if runtime.GOOS == "js" {
|
|
return defaultWP, nil
|
|
}
|
|
|
|
// Try and get an image object by any means.
|
|
var (
|
|
img image.Image
|
|
format string
|
|
imgErr error
|
|
)
|
|
|
|
// Try the level embedded files, then bindata, then filesystem.
|
|
if data, err := embeddable.GetFile(filename); err == nil {
|
|
log.Debug("wallpaper.FromFile(%s): found in embedded level files", filename)
|
|
bin, _ := base64.StdEncoding.DecodeString(string(data))
|
|
img, format, imgErr = image.Decode(bytes.NewReader(bin))
|
|
} else if data, err := bindata.Asset(filename); err == nil {
|
|
log.Debug("wallpaper.FromFile(%s): found in program bindata", filename)
|
|
fh := bytes.NewBuffer(data)
|
|
img, format, imgErr = image.Decode(fh)
|
|
} else {
|
|
log.Debug("wallpaper.FromFile(%s): opening from disk", filename)
|
|
fh, err := os.Open(filename)
|
|
if err != nil {
|
|
return defaultWP, err
|
|
}
|
|
img, format, imgErr = image.Decode(fh)
|
|
}
|
|
|
|
// Image loading error?
|
|
if imgErr != nil {
|
|
return defaultWP, imgErr
|
|
}
|
|
|
|
// Ugly hack: make it an image.RGBA because the thing we get tends to be
|
|
// an image.Paletted, UGH!
|
|
var b = img.Bounds()
|
|
rgba := image.NewRGBA(b)
|
|
for x := b.Min.X; x < b.Max.X; x++ {
|
|
for y := b.Min.Y; y < b.Max.Y; y++ {
|
|
rgba.Set(x, y, img.At(x, y))
|
|
}
|
|
}
|
|
|
|
wp := &Wallpaper{
|
|
Name: strings.Split(filepath.Base(filename), ".")[0],
|
|
Format: format,
|
|
Image: rgba,
|
|
ready: true,
|
|
}
|
|
wp.cache(e)
|
|
return wp, nil
|
|
}
|
|
|
|
// cache the bitmap images.
|
|
func (wp *Wallpaper) cache(e render.Engine) {
|
|
// Zero-bound the rect cuz an image.Rect doesn't necessarily contain 0,0
|
|
var rect = wp.Image.Bounds()
|
|
if rect.Min.X < 0 {
|
|
rect.Max.X += rect.Min.X
|
|
rect.Min.X = 0
|
|
}
|
|
if rect.Min.Y < 0 {
|
|
rect.Max.Y += rect.Min.Y
|
|
rect.Min.Y = 0
|
|
}
|
|
|
|
// Our quarter rect size.
|
|
wp.quarterWidth = int(float64((rect.Max.X - rect.Min.X) / 2))
|
|
wp.quarterHeight = int(float64((rect.Max.Y - rect.Min.Y) / 2))
|
|
quarter := image.Rect(0, 0, wp.quarterWidth, wp.quarterHeight)
|
|
|
|
// Slice the image into the four corners.
|
|
slice := func(dx, dy int) *image.RGBA {
|
|
slice := image.NewRGBA(quarter)
|
|
draw.Draw(
|
|
slice,
|
|
image.Rect(0, 0, wp.quarterWidth, wp.quarterHeight),
|
|
wp.Image,
|
|
image.Point{dx, dy},
|
|
draw.Over,
|
|
)
|
|
return slice
|
|
}
|
|
wp.corner = slice(0, 0)
|
|
wp.top = slice(wp.quarterWidth, 0)
|
|
wp.left = slice(0, wp.quarterHeight)
|
|
wp.repeat = slice(wp.quarterWidth, wp.quarterHeight)
|
|
|
|
}
|
|
|
|
// QuarterSize returns the width and height of the quarter images.
|
|
func (wp *Wallpaper) QuarterSize() (int, int) {
|
|
return wp.quarterWidth, wp.quarterHeight
|
|
}
|
|
|
|
// Corner returns the top left corner image.
|
|
func (wp *Wallpaper) Corner() *image.RGBA {
|
|
return wp.corner
|
|
}
|
|
|
|
// Top returns the top repeating image.
|
|
func (wp *Wallpaper) Top() *image.RGBA {
|
|
return wp.top
|
|
}
|
|
|
|
// Left returns the left repeating image.
|
|
func (wp *Wallpaper) Left() *image.RGBA {
|
|
return wp.left
|
|
}
|
|
|
|
// Repeat returns the main repeating image.
|
|
func (wp *Wallpaper) Repeat() *image.RGBA {
|
|
return wp.repeat
|
|
}
|