nori/render.go

136 lines
3.5 KiB
Go

package nori
import (
"fmt"
"image"
"image/color"
"image/draw"
"git.tuxpa.in/a/zlog/log"
"github.com/disintegration/imaging"
"github.com/phrozen/blend"
"gitlab.com/gfxlabs/gfximg/apng"
)
func copyImage(img image.Image) image.Image {
copied := image.NewNRGBA(img.Bounds())
draw.Draw(copied, img.Bounds(), img, img.Bounds().Min, draw.Over)
return copied
}
type RenderFunc = func(img image.Image, delay int)
func RenderAnimation(n *Nori, num int, f RenderFunc) error {
g := n.Gawi
if len(n.Animations[num].Frames) == 0 {
return fmt.Errorf("no frames found for animation")
}
images := make([]image.Image, 0, len(n.Animations[num].Frames))
durations := make([]uint32, 0, len(n.Animations[num].Frames))
for _, frame := range n.Animations[num].Frames {
planes := frame.Planes
var canvasRect image.Rectangle
for i, plane := range planes {
if int(plane.BitmapId) >= len(g.Images) {
log.Printf("could not find bitmap %d, only have %d", plane.BitmapId, len(g.Images))
}
bitmap := g.Images[plane.BitmapId]
if len(bitmap.SubImages) == 0 {
return fmt.Errorf("no subimages in bitmap")
}
pt := image.Pt(int(plane.PlaneX), int(plane.PlaneY)) // where to put the point
rc := bitmap.SubImages[0].Img.Bounds().Add(pt) // translate rectangle to put in the global canvas
if i == 0 {
canvasRect = rc
} else {
canvasRect = canvasRect.Union(rc)
}
}
img := image.NewNRGBA(canvasRect)
for _, plane := range planes {
bitmap := g.Images[plane.BitmapId]
drawBitmap := bitmap.SubImages[0].Img
transparent := false
//flipx
if plane.RenderFlag&1 != 0 {
if bitmap.SubImages[0].Img == drawBitmap {
drawBitmap = copyImage(bitmap.SubImages[0].Img)
}
drawBitmap = imaging.FlipH(drawBitmap)
}
//flipy
if plane.RenderFlag&2 != 0 {
if bitmap.SubImages[0].Img == drawBitmap {
drawBitmap = copyImage(bitmap.SubImages[0].Img)
}
drawBitmap = imaging.FlipV(drawBitmap)
}
// is transparent
if plane.RenderFlag&0x20 != 0 {
transparent = true
}
_ = transparent
pt := image.Pt(int(plane.PlaneX), int(plane.PlaneY)) // where to put the point
rc := drawBitmap.Bounds().Add(pt)
src := drawBitmap
switch plane.Blend {
case BlendMode_InvertMul, BlendMode_InvertMul5:
img = imaging.Invert(img)
fallthrough
case BlendMode_Alpha:
case BlendMode_Mul, BlendMode_Mul7:
blend.BlendImage(img, drawBitmap, blend.Multiply)
case BlendMode_Add, BlendMode_Add8:
blend.BlendImage(img, drawBitmap, blend.Add)
case BlendMode_None:
default:
return fmt.Errorf("unknown blend mode: %d", plane.Blend)
}
mask := (image.Image)(nil)
if transparent {
mask = &image.Uniform{C: color.RGBA{A: 160}}
}
draw.DrawMask(
img,
rc,
src,
src.Bounds().Min,
mask,
src.Bounds().Min,
draw.Over,
)
}
images = append(images, img)
durations = append(durations, frame.Duration)
}
or := images[0].Bounds()
for _, realFrame := range images {
or = realFrame.Bounds().Union(or)
}
if or.Size().X == 0 || or.Size().Y == 0 {
return fmt.Errorf("no frames found for animation")
}
for i, realFrame := range images {
//log.Info().Int("frame", num).Interface("rect", or).Interface("frame", realFrame.Bounds()).Msg("")
src := realFrame
big := image.NewNRGBA64(or)
draw.Draw(
big,
src.Bounds(),
src,
src.Bounds().Min,
draw.Src,
)
fr := apng.Frame{
Image: big,
DelayDenominator: 1000,
DelayNumerator: uint16(durations[i]),
}
f(big, int(fr.DelayNumerator))
}
return nil
}