97 lines
4.2 KiB
Racket
97 lines
4.2 KiB
Racket
#lang racket/base
|
|
|
|
;; A library for creating gifs out of bitmap%s
|
|
|
|
(require racket/gui/base
|
|
racket/class
|
|
racket/list
|
|
net/gifwrite
|
|
racket/contract)
|
|
|
|
(provide write-gif)
|
|
|
|
(define (force-bm bm) (if (procedure? bm) (bm) bm))
|
|
|
|
(define (split-bytes b len offset)
|
|
(if (= offset (bytes-length b))
|
|
null
|
|
(cons (subbytes b offset (+ offset len))
|
|
(split-bytes b len (+ offset len)))))
|
|
|
|
(define (write-gifs bms delay filename one-at-a-time? last-frame-delay loop? disposal)
|
|
(let* ([init (force-bm (car bms))]
|
|
[w (send init get-width)]
|
|
[h (send init get-height)])
|
|
(let ([argb-thunks
|
|
(map (lambda (bm)
|
|
(lambda ()
|
|
(let ([bm (force-bm bm)]
|
|
[argb (make-bytes (* w h 4) 255)])
|
|
(send bm get-argb-pixels 0 0 w h argb)
|
|
(let ([mask (send bm get-loaded-mask)])
|
|
(when mask
|
|
(send mask get-argb-pixels 0 0 w h argb #t)))
|
|
argb)))
|
|
(cons init (cdr bms)))])
|
|
(if one-at-a-time?
|
|
;; Quantize individually, and stream the images through
|
|
(call-with-output-file*
|
|
filename
|
|
(lambda (p)
|
|
(let* ([gif (gif-start p w h 0 #f)])
|
|
(when loop?
|
|
(gif-add-loop-control gif 0))
|
|
(let ([last-argb-thunk (last argb-thunks)])
|
|
(for-each (lambda (argb-thunk)
|
|
(let-values ([(pixels colormap transparent)
|
|
(quantize (argb-thunk))])
|
|
(when (or transparent delay)
|
|
(gif-add-control gif disposal #f (or delay 0) transparent))
|
|
(gif-add-image gif 0 0 w h #f colormap pixels)
|
|
(when (and last-frame-delay (eq? argb-thunk last-argb-thunk))
|
|
(gif-add-control gif disposal #f last-frame-delay transparent)
|
|
(gif-add-image gif 0 0 w h #f colormap pixels))))
|
|
argb-thunks))
|
|
(gif-end gif))))
|
|
;; Build images and quantize all at once:
|
|
(let-values ([(pixels colormap transparent)
|
|
(quantize (apply bytes-append (map (lambda (t) (t)) argb-thunks)))])
|
|
(call-with-output-file*
|
|
filename
|
|
(lambda (p)
|
|
(let* ([gif (gif-start p w h 0 colormap)])
|
|
(when delay
|
|
(gif-add-loop-control gif 0))
|
|
(let* ([pixelss (split-bytes pixels (* w h) 0)]
|
|
[last-pixels (last pixelss)])
|
|
(for-each (lambda (pixels)
|
|
(when (or transparent delay)
|
|
(gif-add-control gif 'any #f (or delay 0) transparent))
|
|
(gif-add-image gif 0 0 w h #f #f pixels)
|
|
(when (and last-frame-delay (eq? pixels last-pixels))
|
|
(gif-add-control gif disposal #f last-frame-delay transparent)
|
|
(gif-add-image gif 0 0 w h #f colormap pixels)))
|
|
pixelss))
|
|
(gif-end gif)))))))))
|
|
|
|
(define (write-gif bm filename)
|
|
(write-gifs (list bm) #f filename #f #f #f 'any))
|
|
|
|
(provide/contract
|
|
[write-animated-gif
|
|
(->i ((bms (and/c (listof (or/c (is-a?/c bitmap%) (-> (is-a?/c bitmap%)))) pair?))
|
|
(delay (integer-in 0 4294967295))
|
|
(filename (or/c path? string?)))
|
|
(#:one-at-a-time? (one-at-a-time? any/c)
|
|
#:last-frame-delay (last-frame-delay (or/c (integer-in 0 4294967295) false/c))
|
|
#:loop? (Loop? (delay) (lambda (x) (and delay #t)))
|
|
#:disposal (disposal (or/c 'any 'keep 'restore-bg 'restore-prev)))
|
|
any)])
|
|
|
|
(define (write-animated-gif bms delay filename
|
|
#:one-at-a-time? [one-at-a-time? #f]
|
|
#:last-frame-delay [last-frame-delay #f]
|
|
#:loop? [loop? (and delay #t)]
|
|
#:disposal [disposal 'any])
|
|
(write-gifs bms delay filename one-at-a-time? last-frame-delay loop? disposal))
|