148 lines
5.1 KiB
Go
148 lines
5.1 KiB
Go
package art
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"image"
|
|
"image/color"
|
|
"image/png"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestImportPNGFramesPreservesPixelsAndIndependentFrames(t *testing.T) {
|
|
sheet := image.NewNRGBA(image.Rect(0, 0, 4, 2))
|
|
for y := 0; y < 2; y++ {
|
|
for x := 0; x < 4; x++ {
|
|
sheet.SetNRGBA(x, y, color.NRGBA{R: uint8(20 + x*40), G: uint8(30 + y*40), B: 90, A: 255})
|
|
}
|
|
}
|
|
var encoded bytes.Buffer
|
|
if err := png.Encode(&encoded, sheet); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rects := []image.Rectangle{image.Rect(2, 0, 4, 2), image.Rect(0, 0, 2, 2)}
|
|
doc, err := ImportPNGFrames(encoded.Bytes(), rects, 80, Limits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer doc.Close()
|
|
info := doc.Info()
|
|
if info.Canvas.Width != 2 || info.Canvas.Height != 2 || len(info.Frames) != 2 || len(info.Cels) != 2 {
|
|
t.Fatalf("unexpected frame document: %+v", info)
|
|
}
|
|
if info.Frames[0].ID == info.Frames[1].ID || info.Cels[0].Image == info.Cels[1].Image {
|
|
t.Fatal("imported frames must have separate identities and pixels")
|
|
}
|
|
for i, frame := range info.Frames {
|
|
if frame.DurationMS != 80 {
|
|
t.Fatalf("frame %d duration is %d", i, frame.DurationMS)
|
|
}
|
|
got, err := doc.Render(frame.ID, RenderOptions{Channel: Albedo})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for y := 0; y < 2; y++ {
|
|
for x := 0; x < 2; x++ {
|
|
want := sheet.NRGBAAt(rects[i].Min.X+x, rects[i].Min.Y+y)
|
|
if pixel := got.NRGBAAt(x, y); pixel != want {
|
|
t.Fatalf("frame %d pixel (%d,%d): got %v, want %v", i, x, y, pixel, want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
session, err := NewSession(doc, NewJournal(Limits{}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := session.Execute(context.Background(), Operation{Kind: "stroke", Points: []image.Point{{0, 0}}, Brush: Brush{Size: 1, Paint: Paint{Color: Color{R: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other, err := doc.Render(info.Frames[1].ID, RenderOptions{Channel: Albedo})
|
|
if err != nil || other.NRGBAAt(0, 0) != sheet.NRGBAAt(0, 0) {
|
|
t.Fatalf("editing one imported frame changed the other: %v", err)
|
|
}
|
|
data, err := doc.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reopened, err := Decode(data, Limits{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer reopened.Close()
|
|
if got := reopened.Info(); len(got.Frames) != 2 || got.Frames[0].ID != info.Frames[0].ID || got.Frames[1].ID != info.Frames[1].ID {
|
|
t.Fatal("saved import did not retain frame identities")
|
|
}
|
|
}
|
|
|
|
func TestPNGImportsRejectSixteenBitInsteadOfSilentlyReducingPrecision(t *testing.T) {
|
|
gray := image.NewGray16(image.Rect(0, 0, 2, 1))
|
|
gray.SetGray16(0, 0, color.Gray16{Y: 0x1234})
|
|
rgba := image.NewRGBA64(image.Rect(0, 0, 2, 1))
|
|
rgba.SetRGBA64(0, 0, color.RGBA64{R: 0x1234, G: 0x4567, B: 0x789a, A: 0xffff})
|
|
nrgba := image.NewNRGBA64(image.Rect(0, 0, 2, 1))
|
|
nrgba.SetNRGBA64(0, 0, color.NRGBA64{R: 0x1234, G: 0x4567, B: 0x789a, A: 0xabcd})
|
|
for _, tc := range []struct {
|
|
name string
|
|
im image.Image
|
|
}{{"gray", gray}, {"rgba", rgba}, {"nrgba", nrgba}} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var encoded bytes.Buffer
|
|
if err := png.Encode(&encoded, tc.im); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
before := bytes.Clone(encoded.Bytes())
|
|
for _, open := range []func([]byte) (*Document, error){
|
|
func(data []byte) (*Document, error) { return ImportPNG(data, Limits{}) },
|
|
func(data []byte) (*Document, error) {
|
|
return ImportPNGFrames(data, []image.Rectangle{image.Rect(0, 0, 1, 1)}, 100, Limits{})
|
|
},
|
|
} {
|
|
doc, err := open(encoded.Bytes())
|
|
if doc != nil {
|
|
doc.Close()
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), "16-bit") || !bytes.Equal(before, encoded.Bytes()) {
|
|
t.Fatalf("16-bit PNG was accepted or modified: %v", err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestImportPNGFramesRejectsInvalidInputAndBudgets(t *testing.T) {
|
|
var b bytes.Buffer
|
|
if err := png.Encode(&b, image.NewNRGBA(image.Rect(0, 0, 4, 2))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
data []byte
|
|
rects []image.Rectangle
|
|
duration int
|
|
limits Limits
|
|
}{
|
|
{"invalid PNG", []byte("not a PNG"), []image.Rectangle{image.Rect(0, 0, 2, 2)}, 100, Limits{}},
|
|
{"no frames", b.Bytes(), nil, 100, Limits{}},
|
|
{"outside image", b.Bytes(), []image.Rectangle{image.Rect(3, 0, 5, 2)}, 100, Limits{}},
|
|
{"negative origin", b.Bytes(), []image.Rectangle{image.Rect(-1, 0, 1, 2)}, 100, Limits{}},
|
|
{"empty frame", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 0, 2)}, 100, Limits{}},
|
|
{"mixed dimensions", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 2, 2), image.Rect(2, 0, 3, 2)}, 100, Limits{}},
|
|
{"duration", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 2, 2)}, 0, Limits{}},
|
|
{"frame limit", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 2, 2), image.Rect(2, 0, 4, 2)}, 100, Limits{MaxFrames: 1}},
|
|
{"cel limit", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 2, 2), image.Rect(2, 0, 4, 2)}, 100, Limits{MaxCels: 1}},
|
|
{"sheet budget", b.Bytes(), []image.Rectangle{image.Rect(0, 0, 2, 2)}, 100, Limits{MaxCanvasPixels: 4}},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
doc, err := ImportPNGFrames(tc.data, tc.rects, tc.duration, tc.limits)
|
|
if doc != nil {
|
|
doc.Close()
|
|
}
|
|
if err == nil {
|
|
t.Fatal("invalid import succeeded")
|
|
}
|
|
})
|
|
}
|
|
}
|