commit
74658233c3
3 changed files with 44 additions and 5 deletions
|
|
@ -318,6 +318,19 @@ steps: {
|
||||||
assert.Success(t, err)
|
assert.Success(t, err)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "one-layer-gif",
|
||||||
|
skipCI: true,
|
||||||
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
||||||
|
writeFile(t, dir, "in.d2", `x`)
|
||||||
|
err := runTestMain(t, ctx, dir, env, "--animate-interval=10", "in.d2", "out.gif")
|
||||||
|
assert.Success(t, err)
|
||||||
|
|
||||||
|
gifBytes := readFile(t, dir, "out.gif")
|
||||||
|
err = xgif.Validate(gifBytes, 1, 10)
|
||||||
|
assert.Success(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "stdin",
|
name: "stdin",
|
||||||
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) {
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 219 KiB After Width: | Height: | Size: 207 KiB |
|
|
@ -25,7 +25,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const INFINITE_LOOP = 0
|
const INFINITE_LOOP = 0
|
||||||
const BG_INDEX uint8 = 255
|
|
||||||
|
|
||||||
var BG_COLOR = color.White
|
var BG_COLOR = color.White
|
||||||
|
|
||||||
|
|
@ -63,7 +62,7 @@ func AnimatePNGs(ms *xmain.State, pngs [][]byte, animIntervalMs int) ([]byte, er
|
||||||
// 1. convert the PNG into a GIF compatible image (Bitmap) by quantizing it to 255 colors
|
// 1. convert the PNG into a GIF compatible image (Bitmap) by quantizing it to 255 colors
|
||||||
buf := bytes.NewBuffer(nil)
|
buf := bytes.NewBuffer(nil)
|
||||||
err := gif.Encode(buf, pngImage, &gif.Options{
|
err := gif.Encode(buf, pngImage, &gif.Options{
|
||||||
NumColors: 255, // GIFs can have up to 256 colors, so keep 1 slot for white background
|
NumColors: 256, // GIFs can have up to 256 colors
|
||||||
Quantizer: quantize.MedianCutQuantizer{},
|
Quantizer: quantize.MedianCutQuantizer{},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -85,12 +84,19 @@ func AnimatePNGs(ms *xmain.State, pngs [][]byte, animIntervalMs int) ([]byte, er
|
||||||
left := (width - bounds.Dx()) / 2
|
left := (width - bounds.Dx()) / 2
|
||||||
right := left + bounds.Dx()
|
right := left + bounds.Dx()
|
||||||
|
|
||||||
palettedImg.Palette[BG_INDEX] = BG_COLOR
|
var bgIndex int
|
||||||
|
if len(palettedImg.Palette) == 256 {
|
||||||
|
bgIndex = findWhiteIndex(palettedImg.Palette)
|
||||||
|
palettedImg.Palette[bgIndex] = BG_COLOR
|
||||||
|
} else {
|
||||||
|
bgIndex = len(palettedImg.Palette)
|
||||||
|
palettedImg.Palette = append(palettedImg.Palette, BG_COLOR)
|
||||||
|
}
|
||||||
frame := image.NewPaletted(image.Rect(0, 0, width, height), palettedImg.Palette)
|
frame := image.NewPaletted(image.Rect(0, 0, width, height), palettedImg.Palette)
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
for y := 0; y < height; y++ {
|
for y := 0; y < height; y++ {
|
||||||
if x <= left || y <= top || x >= right || y >= bottom {
|
if x <= left || y <= top || x >= right || y >= bottom {
|
||||||
frame.SetColorIndex(x, y, BG_INDEX)
|
frame.SetColorIndex(x, y, uint8(bgIndex))
|
||||||
} else {
|
} else {
|
||||||
frame.SetColorIndex(x, y, palettedImg.ColorIndexAt(x-left, y-top))
|
frame.SetColorIndex(x, y, palettedImg.ColorIndexAt(x-left, y-top))
|
||||||
}
|
}
|
||||||
|
|
@ -109,14 +115,34 @@ func AnimatePNGs(ms *xmain.State, pngs [][]byte, animIntervalMs int) ([]byte, er
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func findWhiteIndex(palette color.Palette) int {
|
||||||
|
nearestIndex := 0
|
||||||
|
nearestScore := 0.
|
||||||
|
for i, c := range palette {
|
||||||
|
r, g, b, _ := c.RGBA()
|
||||||
|
if r == 255 && g == 255 && b == 255 {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
avg := float64(r+g+b) / 255.
|
||||||
|
if avg > nearestScore {
|
||||||
|
nearestScore = avg
|
||||||
|
nearestIndex = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nearestIndex
|
||||||
|
}
|
||||||
|
|
||||||
func Validate(gifBytes []byte, nFrames int, intervalMS int) error {
|
func Validate(gifBytes []byte, nFrames int, intervalMS int) error {
|
||||||
anim, err := gif.DecodeAll(bytes.NewBuffer(gifBytes))
|
anim, err := gif.DecodeAll(bytes.NewBuffer(gifBytes))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if anim.LoopCount != INFINITE_LOOP {
|
if nFrames > 1 && anim.LoopCount != INFINITE_LOOP {
|
||||||
return fmt.Errorf("expected infinite loop, got=%d", anim.LoopCount)
|
return fmt.Errorf("expected infinite loop, got=%d", anim.LoopCount)
|
||||||
|
} else if nFrames == 1 && anim.LoopCount != -1 {
|
||||||
|
return fmt.Errorf("wrong loop count for single frame gif, got=%d", anim.LoopCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(anim.Image) != nFrames {
|
if len(anim.Image) != nFrames {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue