Skip to content

Commit 0a98c1c

Browse files
committed
parallel calculation
1 parent 67cd178 commit 0a98c1c

File tree

3 files changed

+132
-16
lines changed

3 files changed

+132
-16
lines changed

_example/main.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"image"
5+
"image/color"
6+
"math/rand/v2"
7+
8+
"github.com/setanarut/apng"
9+
)
10+
11+
const FrameCount int = 40
12+
13+
func main() {
14+
15+
frames := make([]image.Image, FrameCount)
16+
for i := range FrameCount {
17+
frames[i] = generateNoiseImage(600, 200)
18+
}
19+
apng.Save("out.png", frames, 3)
20+
}
21+
22+
func generateNoiseImage(width, height int) *image.RGBA {
23+
img := image.NewRGBA(image.Rect(0, 0, width, height))
24+
for y := range height {
25+
for x := range width {
26+
col := noisePalette[rand.IntN(4)]
27+
img.SetRGBA(x, y, col)
28+
}
29+
}
30+
return img
31+
}
32+
33+
var noisePalette = []color.RGBA{
34+
{R: 0, G: 0, B: 0, A: 255}, // Black
35+
{R: 255, G: 0, B: 0, A: 255}, // Red
36+
{R: 0, G: 255, B: 0, A: 255}, // Green
37+
{R: 0, G: 0, B: 255, A: 255}, // Blue
38+
}

helper.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ import (
88
)
99

1010
// Save writes an APNG file with the given images and uniform frame delay.
11+
//
12+
// The successive delay times, one per frame, in 100ths of a second (centiseconds).
13+
//
14+
// Note: For 30 FPS, each frame lasts 1/30 second ≈ 3.33 centiseconds.
15+
// When using integer delays, you might use 3 centiseconds per frame.
1116
func Save(filePath string, images []image.Image, delay uint16) error {
1217
totalFrames := len(images)
1318
if totalFrames == 0 {
@@ -34,6 +39,11 @@ func Save(filePath string, images []image.Image, delay uint16) error {
3439
}
3540

3641
// APNGBytes encodes a slice of images into an APNG byte stream with a consistent delay per frame.
42+
//
43+
// The successive delay times, one per frame, in 100ths of a second (centiseconds).
44+
//
45+
// Note: For 30 FPS, each frame lasts 1/30 second ≈ 3.33 centiseconds.
46+
// When using integer delays, you might use 3 centiseconds per frame.
3747
func APNGBytes(images []image.Image, delay uint16) ([]byte, error) {
3848
totalFrames := len(images)
3949
if totalFrames == 0 {

writer.go

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"image"
1010
"image/png"
1111
"io"
12+
"strconv"
13+
"sync"
1214
)
1315

1416
type idat []byte
@@ -38,13 +40,17 @@ func writeUint32(b []uint8, u uint32) {
3840

3941
// APNG encapsulates animated PNG frames, their delays, disposal methods, loop count, and global configuration.
4042
type APNG struct {
41-
Images []image.Image // The successive images.
42-
Delays []uint16 // The successive delay times, one per frame, in 100ths of a second.
43-
Disposals []byte // The successive disposal methods, one per frame.
44-
LoopCount uint32 // The loop count. 0 indicates infinite looping.
43+
Images []image.Image // The successive images.
44+
45+
// The successive delay times, one per frame, in 100ths of a second (centiseconds).
46+
//
47+
// Note: For 30 FPS, each frame lasts 1/30 second ≈ 3.33 centiseconds.
48+
// When using integer delays, you might use 3 centiseconds per frame.
49+
Delays []uint16
50+
Disposals []byte // The successive disposal methods, one per frame.
51+
LoopCount uint32 // The loop count. 0 indicates infinite looping.
4552
Config image.Config
4653
}
47-
4854
type encoder struct {
4955
aPNG *APNG
5056
writer io.Writer
@@ -281,20 +287,83 @@ func EncodeAll(w io.Writer, a *APNG) error {
281287

282288
_, e.err = io.WriteString(w, pngHeader)
283289

290+
// Data to be used while processing the first image
291+
var mutex sync.Mutex
292+
293+
// Prepare PNG data for all frames in parallel
294+
type frameData struct {
295+
index int
296+
ihdr []byte
297+
idats []idat // Changed from [][]byte to []idat
298+
}
299+
300+
frameDataChan := make(chan frameData, len(a.Images))
301+
var wg sync.WaitGroup
302+
284303
for i, img := range a.Images {
285-
bb := &bytes.Buffer{}
286-
if err := png.Encode(bb, img); err != nil {
287-
return errors.New("apng: png encoding error(" + err.Error() + ")")
288-
}
304+
wg.Add(1)
305+
go func(index int, image image.Image) {
306+
defer wg.Done()
307+
308+
bb := &bytes.Buffer{}
309+
if err := png.Encode(bb, image); err != nil {
310+
// Use mutex to handle error state
311+
mutex.Lock()
312+
if e.err == nil {
313+
e.err = errors.New("apng: png encoding error(" + err.Error() + ")")
314+
}
315+
mutex.Unlock()
316+
return
317+
}
318+
319+
pc, err := fetchPNGChunk(bb)
320+
if err != nil {
321+
mutex.Lock()
322+
if e.err == nil {
323+
e.err = err
324+
}
325+
mutex.Unlock()
326+
return
327+
}
289328

290-
pc, err := fetchPNGChunk(bb)
291-
if err != nil {
292-
return err
329+
// fmt.Printf("Frame %d calculated\n", index)
330+
331+
frameDataChan <- frameData{
332+
index: index,
333+
ihdr: pc.ihdr,
334+
idats: pc.idats, // The type of idats returned by fetchPNGChunk should be []idat
335+
}
336+
}(i, img)
337+
}
338+
339+
// Wait for all goroutines to complete
340+
go func() {
341+
wg.Wait()
342+
close(frameDataChan)
343+
}()
344+
345+
// Write output in correct order
346+
frameDataMap := make(map[int]frameData)
347+
for fd := range frameDataChan {
348+
frameDataMap[fd.index] = fd
349+
}
350+
351+
// Error check
352+
if e.err != nil {
353+
return e.err
354+
}
355+
356+
// Write the first frame and then other frames in correct order
357+
for i := range a.Images {
358+
fd, ok := frameDataMap[i]
359+
if !ok {
360+
return errors.New("apng: missing frame data for index " + strconv.Itoa(i))
293361
}
294-
e.ihdr = pc.ihdr
295-
e.idats = pc.idats
296362

297-
// First image is defalt image.
363+
e.ihdr = fd.ihdr
364+
e.idats = fd.idats
365+
366+
// The first image is the default image
298367
if i == 0 {
299368
e.writeIHDR()
300369
e.writeacTL()
@@ -309,5 +378,4 @@ func EncodeAll(w io.Writer, a *APNG) error {
309378
e.writeIEND()
310379

311380
return e.err
312-
313381
}

0 commit comments

Comments
 (0)