Noah Petherbridge
4469847c72
In the Level Editor, the "Level->Giant Screenshot" menu will take a full scale PNG screenshot of the entire level, with its wallpaper and doodads, and save it in ~/.config/doodle/screenshots. It is currently CPU intensive and slow. With future work it should be made asynchronous. The function is abstracted away nicely so that the doodad CLI tool may support this as well.
190 lines
4.6 KiB
Go
190 lines
4.6 KiB
Go
package wallpaper
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/base64"
|
|
"image"
|
|
"image/draw"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"git.kirsle.net/apps/doodle/assets"
|
|
"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(img *image.RGBA, name string) (*Wallpaper, error) {
|
|
wp := &Wallpaper{
|
|
Name: name,
|
|
Image: img,
|
|
}
|
|
wp.cache()
|
|
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(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 := assets.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()
|
|
return wp, nil
|
|
}
|
|
|
|
// cache the bitmap images.
|
|
func (wp *Wallpaper) cache() {
|
|
// 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
|
|
}
|
|
|
|
// QuarterRect returns a Rect of the size of the quarter images.
|
|
func (wp *Wallpaper) QuarterRect() render.Rect {
|
|
return render.NewRect(wp.QuarterSize())
|
|
}
|
|
|
|
// 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
|
|
}
|