#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))