feat: add median-cut quantization for custom GIF palette
Generate optimal 256-color palette from rendered frames when dithering disabled, replacing generic Plan9 palette for better color accuracy without dithering artifacts.
This commit is contained in:
parent
fb3c4a0107
commit
6345eb9821
2 changed files with 179 additions and 10 deletions
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/color/palette"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
|
|
@ -32,6 +33,27 @@ func RenderGIF(glbBytes []byte, atlas *texture.Atlas, background string, frames,
|
|||
// Calculate rotation per frame
|
||||
rotationPerFrame := 360.0 / float64(frames)
|
||||
|
||||
// Render all frames first (in parallel)
|
||||
renderedFrames := make([]image.Image, frames)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < frames; i++ {
|
||||
wg.Add(1)
|
||||
go func(frameIdx int) {
|
||||
defer wg.Done()
|
||||
rotation := float64(frameIdx) * rotationPerFrame
|
||||
renderedFrames[frameIdx] = RenderScene(mesh, atlasImage, rotation, width, height, bgColor)
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Determine palette
|
||||
var pal color.Palette
|
||||
if dithering {
|
||||
pal = palette.Plan9
|
||||
} else {
|
||||
pal = MedianCutQuantize(renderedFrames, 256)
|
||||
}
|
||||
|
||||
// Create GIF structure
|
||||
g := &gif.GIF{
|
||||
Image: make([]*image.Paletted, frames),
|
||||
|
|
@ -39,25 +61,18 @@ func RenderGIF(glbBytes []byte, atlas *texture.Atlas, background string, frames,
|
|||
LoopCount: 0, // 0 = infinite loop
|
||||
}
|
||||
|
||||
// Render frames in parallel
|
||||
var wg sync.WaitGroup
|
||||
// Quantize frames to palette (in parallel)
|
||||
for i := 0; i < frames; i++ {
|
||||
wg.Add(1)
|
||||
go func(frameIdx int) {
|
||||
defer wg.Done()
|
||||
rotation := float64(frameIdx) * rotationPerFrame
|
||||
|
||||
// Render frame
|
||||
img := RenderScene(mesh, atlasImage, rotation, width, height, bgColor)
|
||||
|
||||
// Quantize to palette
|
||||
paletted := image.NewPaletted(img.Bounds(), palette.Plan9)
|
||||
img := renderedFrames[frameIdx]
|
||||
paletted := image.NewPaletted(img.Bounds(), pal)
|
||||
if dithering {
|
||||
draw.FloydSteinberg.Draw(paletted, img.Bounds(), img, image.Point{})
|
||||
} else {
|
||||
draw.Draw(paletted, img.Bounds(), img, image.Point{}, draw.Src)
|
||||
}
|
||||
|
||||
g.Image[frameIdx] = paletted
|
||||
g.Delay[frameIdx] = delay
|
||||
}(i)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue