@@ -21,7 +21,7 @@ module fplotlib_png
2121 ! zlib_compress is here rather than in the PDF backend because a PDF
2222 ! FlateDecode stream is the same zlib stream a PNG carries, and one
2323 ! compressor is enough.
24- public :: png_encode, zlib_compress
24+ public :: png_encode, apng_encode, zlib_compress
2525
2626 ! LZ77 parameters. WSIZE is fixed by the format; CHAIN bounds how hard the
2727 ! matcher looks before settling, which is the usual size/speed dial.
@@ -314,12 +314,113 @@ function png_encode(w, h, rgba) result(bytes)
314314 integer , intent (in ) :: w, h
315315 integer , intent (in ) :: rgba(4 , w, h)
316316 character (len= :), allocatable :: bytes
317+ type (bitbuf_t) :: out
318+
319+ call push_signature(out )
320+ call ihdr(out , w, h, 6 )
321+ call chunk(out , " IDAT" , filtered_zlib(w, h, 4 , rgba))
322+ call chunk(out , " IEND" , NO_DATA)
323+ bytes = to_string(out )
324+ end function png_encode
325+
326+ ! ------------------------------------------------------------------
327+ ! Encode nframes RGB frames as an animated PNG. pix holds the frames as
328+ ! packed RGB bytes, one after another, as the animation reel keeps them.
329+ ! A delay is delay_num/delay_den seconds; loop true repeats forever.
330+ ! Every frame is a full, opaque replacement of the canvas, so the first
331+ ! frame is also the default image that viewers without APNG support show.
332+ ! ------------------------------------------------------------------
333+
334+ function apng_encode (w , h , nframes , pix , delay_num , delay_den , loop ) result(bytes)
335+ integer , intent (in ) :: w, h, nframes, delay_num, delay_den
336+ character (len=* ), intent (in ) :: pix
337+ logical , intent (in ) :: loop
338+ character (len= :), allocatable :: bytes
339+ integer , allocatable :: rgb(:, :, :), z(:), d(:)
340+ integer :: f, x, y, c, k, seq, base, nz
341+ type (bitbuf_t) :: out
342+
343+ allocate (rgb(3 , w, h))
344+ call push_signature(out )
345+ call ihdr(out , w, h, 2 )
346+
347+ allocate (d(8 ))
348+ call be32(nframes, d(1 :4 ))
349+ d(5 :8 ) = 0 ! zero plays forever
350+ if (.not. loop) call be32(1 , d(5 :8 ))
351+ call chunk(out , " acTL" , d)
352+ deallocate (d)
353+
354+ seq = 0
355+ do f = 1 , nframes
356+ base = (f - 1 )* 3 * w* h
357+ do concurrent (y = 1 :h, x = 1 :w, c = 1 :3 )
358+ k = base + (y - 1 )* 3 * w + (x - 1 )* 3 + c
359+ rgb(c, x, y) = iachar (pix(k:k))
360+ end do
361+
362+ allocate (d(26 ))
363+ call be32(seq, d(1 :4 ))
364+ call be32(w, d(5 :8 ))
365+ call be32(h, d(9 :12 ))
366+ d(13 :20 ) = 0 ! x and y offset
367+ d(21 ) = iand (ishft(delay_num, - 8 ), 255 )
368+ d(22 ) = iand (delay_num, 255 )
369+ d(23 ) = iand (ishft(delay_den, - 8 ), 255 )
370+ d(24 ) = iand (delay_den, 255 )
371+ d(25 ) = 0 ! dispose: leave the canvas as it is
372+ d(26 ) = 0 ! blend: replace
373+ call chunk(out , " fcTL" , d)
374+ deallocate (d)
375+ seq = seq + 1
376+
377+ z = filtered_zlib(w, h, 3 , rgb)
378+ if (f == 1 ) then
379+ call chunk(out , " IDAT" , z)
380+ else
381+ nz = size (z)
382+ allocate (d(4 + nz))
383+ call be32(seq, d(1 :4 ))
384+ d(5 :) = z
385+ call chunk(out , " fdAT" , d)
386+ deallocate (d)
387+ seq = seq + 1
388+ end if
389+ end do
390+
391+ call chunk(out , " IEND" , NO_DATA)
392+ bytes = to_string(out )
393+ end function apng_encode
394+
395+ subroutine push_signature (out )
396+ type (bitbuf_t), intent (inout ) :: out
397+
398+ call push(out , 137 ); call push(out , 80 ); call push(out , 78 )
399+ call push(out , 71 ); call push(out , 13 ); call push(out , 10 )
400+ call push(out , 26 ); call push(out , 10 )
401+ end subroutine push_signature
402+
403+ function to_string (out ) result(bytes)
404+ type (bitbuf_t), intent (in ) :: out
405+ character (len= :), allocatable :: bytes
406+ integer :: i
407+
408+ allocate (character (len= out % n) :: bytes)
409+ do i = 1 , out % n
410+ bytes(i:i) = achar (out % b(i))
411+ end do
412+ end function to_string
413+
414+ ! The zlib stream of an image's scanlines, each filtered with whichever of
415+ ! the five PNG filters looks cheapest. px is bpp interleaved channels.
416+ function filtered_zlib (w , h , bpp , px ) result(z)
417+ integer , intent (in ) :: w, h, bpp
418+ integer , intent (in ) :: px(bpp, w, h)
419+ integer , allocatable :: z(:)
317420 integer , allocatable :: raw(:), comp(:), cur(:), up(:), try(:), best(:)
318- integer :: bpp, stride, nraw, x, y, c, i, p, f, score, bscore, bf
421+ integer :: stride, nraw, x, y, c, i, p, f, score, bscore, bf
319422 integer :: a_, b_, c_, v
320- type (bitbuf_t) :: out
321423
322- bpp = 4
323424 stride = w* bpp
324425 nraw = h* (stride + 1 )
325426 allocate (raw(nraw), cur(stride), up(stride), try(stride), best(stride))
@@ -329,9 +430,9 @@ function png_encode(w, h, rgba) result(bytes)
329430 do y = 1 , h
330431 i = 0
331432 do x = 1 , w
332- do c = 1 , 4
433+ do c = 1 , bpp
333434 i = i + 1
334- cur(i) = iand (rgba (c, x, y), 255 )
435+ cur(i) = iand (px (c, x, y), 255 )
335436 end do
336437 end do
337438
@@ -372,21 +473,8 @@ function png_encode(w, h, rgba) result(bytes)
372473 end do
373474
374475 comp = deflate(raw, nraw)
375-
376- ! Signature
377- call push(out , 137 ); call push(out , 80 ); call push(out , 78 )
378- call push(out , 71 ); call push(out , 13 ); call push(out , 10 )
379- call push(out , 26 ); call push(out , 10 )
380-
381- call ihdr(out , w, h)
382- call chunk(out , " IDAT" , zlib_wrap(comp, raw, nraw))
383- call chunk(out , " IEND" , NO_DATA)
384-
385- allocate (character (len= out % n) :: bytes)
386- do i = 1 , out % n
387- bytes(i:i) = achar (out % b(i))
388- end do
389- end function png_encode
476+ z = zlib_wrap(comp, raw, nraw)
477+ end function filtered_zlib
390478
391479 ! Bytes in, a zlib stream out. Unlike a PNG the caller supplies its own
392480 ! layout, so nothing is filtered here.
@@ -421,15 +509,15 @@ function zlib_wrap(comp, raw, nraw) result(z)
421509 z(n + 6 ) = iand (a, 255 )
422510 end function zlib_wrap
423511
424- subroutine ihdr (out , w , h )
512+ subroutine ihdr (out , w , h , ctype )
425513 type (bitbuf_t), intent (inout ) :: out
426- integer , intent (in ) :: w, h
514+ integer , intent (in ) :: w, h, ctype
427515 integer :: d(13 )
428516
429517 call be32(w, d(1 :4 ))
430518 call be32(h, d(5 :8 ))
431519 d(9 ) = 8 ! bit depth
432- d(10 ) = 6 ! truecolor with alpha
520+ d(10 ) = ctype ! 2 truecolor, 6 truecolor with alpha
433521 d(11 ) = 0 ! deflate
434522 d(12 ) = 0 ! adaptive filtering
435523 d(13 ) = 0 ! no interlace
0 commit comments