clean up file/gif vs. net/gifwrie confusion

svn: r9848
This commit is contained in:
Matthew Flatt 2008-05-15 15:54:44 +00:00
parent 31d9a7d885
commit 02485e1e50
10 changed files with 713 additions and 946 deletions

View File

@ -1,4 +1,682 @@
#lang scheme/base #|
/******************************************************************************
* "Gif-Lib" - Yet another gif library.
*
* Written by: Gershon Elber Ver 1.1, Aug. 1990
******************************************************************************
* The kernel of the GIF Encoding process can be found here.
******************************************************************************
* History:
* 14 Jun 89 - Version 1.0 by Gershon Elber.
* 3 Sep 90 - Version 1.1 by Gershon Elber (Support for Gif89, Unique names).
* 26 Jun 96 - Version 3.0 by Eric S. Raymond (Full GIF89 support)
* 5 Jan 07 - Ported to MzScheme by Matthew
*****************************************************************************/
|#
(require net/gifwrite) #reader scribble/reader
(provide (all-from-out net/gifwrite)) (module gif scheme/base
(require scheme/contract
scribble/srcdoc)
(require/doc scheme/base
scribble/manual)
(define LZ_MAX_CODE 4095)
(define GifVersionPrefix #"GIF89a")
(provide/doc
(proc-doc gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[@t] if @scheme[v] is a GIF stream created by @scheme[gif-write],
@scheme[#f] otherwise.})
(proc-doc image-ready-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that is not in
@scheme['done] mode, @scheme[#f] otherwise.})
(proc-doc image-or-control-ready-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that is in
@scheme['init] or @scheme['image-or-control] mode, @scheme[#f] otherwise.})
(proc-doc empty-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that in
@scheme['init] mode, @scheme[#f] otherwise.})
(proc-doc gif-colormap? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] represets a colormap, @scheme[#f] otherwise.
A colormap is a list whose size is a power of @math{2} between @math{2^1} and @math{2^8},
and whose elements are vectors of size 3 containing colors
(i.e., exact integers between @math{0} and @math{255} inclusive).})
(proc-doc color? (([v any/c]) () . ->d . [_ boolean?])
@{The same as @scheme[byte?].})
(proc-doc dimension? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is an exact integer between
@scheme[#x0] and @scheme[#xFFFF] inclusive, @scheme[#f]
otherwise.}))
(define-struct gif-stream (port
SWidth
SHeight
SBackGroundColor
SColorMap
[FileState #:mutable]))
(define (image-ready-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init image-or-control image))))
(define (image-or-control-ready-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init image-or-control))))
(define (empty-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init))))
(define color? byte?)
(define (dimension? x) (and (exact? x) (integer? x) (<= 0 x #xFFFF)))
(define (gif-colormap? l)
(and (list? l)
(member (length l) '(2 4 8 16 32 64 128 256))
(andmap (lambda (c)
(and (vector? c)
(= 3 (vector-length c))
(color? (vector-ref c 0))
(color? (vector-ref c 1))
(color? (vector-ref c 2))))
l)))
(define (color-map->bytes ColorMap)
(apply bytes (apply append (map vector->list ColorMap))))
(define (bits-per-pixel ColorMap)
(case (length ColorMap)
[(2) 1]
[(4) 2]
[(8) 3]
[(16) 4]
[(32) 5]
[(64) 6]
[(128) 7]
[(256) 8]
[else (error 'bits-per-pixel
"strange colormap size: ~e"
(length ColorMap))]))
(define (WRITE g bytes)
(write-bytes bytes (gif-stream-port g)))
(provide/doc (proc-doc gif-state (([stream gif-stream?]) () . ->d . [_ symbol?])
@{Returns the state of @scheme[stream].}))
(define (gif-state GifFile)
(gif-stream-FileState GifFile))
;;/******************************************************************************
;; * This routine should be called before any other EGif calls, immediately
;; * follows the GIF file openning.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-start
(([out output-port?]
[w dimension?]
[h dimension?]
[bg-color color?]
[cmap (or/c false/c gif-colormap?)])
()
. ->d . [_ gif-stream?])
@{Writes the start of a GIF file to the given output port, and returns
a GIF stream that adds to the output port.
The width and height determine a virtual space for the overall GIF
image. Individual images added to the GIF stream must fit within this
virtual space. The space is initialized by the given background color.
Finally, the default meaning of color numbers (such as the background color)
is determined by the given colormap, but individual images
within the GIF file can have their own colormaps.
A global colormap need not be supplied, in which case a colormap must
be supplied for each image. Beware that the bg-color is ill-defined if
a global colormap is not provided.}))
(define (gif-start port
Width
Height
BackGround
ColorMap)
(define GifFile
(make-gif-stream port
Width
Height
BackGround
ColorMap
'init))
(when ColorMap
(unless (BackGround . < . (length ColorMap))
(error 'gif-start
"background color ~a is out of range for the color map: ~e"
BackGround
ColorMap)))
(WRITE GifFile GifVersionPrefix)
;;/*
;; * Put the logical screen descriptor into the file:
;; */
;;/* Logical Screen Descriptor: Dimensions */
(EGifPutWord Width GifFile)
(EGifPutWord Height GifFile)
;;/* Logical Screen Descriptor: Packed Fields */
;;/* Note: We have actual size of the color table default to the largest
;; * possible size (7+1 == 8 bits) because the decoder can use it to decide
;; * how to display the files.
;; */
(WRITE GifFile
(bytes (bitwise-ior
(if ColorMap #x80 #x00)
(arithmetic-shift #x7 4) ; /* Bits originally allocated to each primary color */
(if ColorMap (sub1 (bits-per-pixel ColorMap)) #x07)) ; /* Actual size of the color table. */
BackGround ; /* Index into the ColorTable for background color */
0)) ; /* Pixel Aspect Ratio */
;; /* If we have Global color map - dump it also: */
(when ColorMap
(WRITE GifFile (color-map->bytes ColorMap)))
GifFile)
;;/******************************************************************************
;; * This routine should be called before any attempt to dump an image - any
;; * call to any of the pixel dump routines.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-image
(([stream image-ready-gif-stream?]
[left dimension?]
[top dimension?]
[width dimension?]
[height dimension?]
[interlaced? any/c]
[cmap (or/c false/c gif-colormap?)]
[bstr bytes?])
()
. ->d . [_ void?])
@{Writes an image to the given GIF stream. The @scheme[left], @scheme[top],
@scheme[width], and @scheme[height] values specify the location and
size of the image within the overall GIF image's virtual space.
If @scheme[interlaced?] is true, then @scheme[bstr] should provide bytes
ininterlaced order instead of top-to-bottom order. Interlaced order is:
@(itemize
@item{every 8th row, starting with 0}
@item{every 8th row, starting with 4}
@item{every 4th row, starting with 2}
@item{every 2nd row, starting with 1})
If a global color is provided with @scheme[gif-start], a @scheme[#f] value
can be provided for @scheme[cmap].
The @scheme[bstr] argument specifies the pixel content of the image. Each
byte specifies a color (i.e., an index in the colormap). Each row is
provided left-to-right, and the rows provided either top-to-bottom or
in interlaced order (see above). If the image is prefixed with a
control that specifies an transparent index (see @scheme[gif-add-control]),
then the corresponding ``color'' doesn't draw into the overall GIF
image.
An exception is raised if any byte value in @scheme[bstr] is larger than the
colormap's length, if the @scheme[bstr] length is not @scheme[width] times
@scheme[height], or if the @scheme[top], @scheme[left], @scheme[width], and
@scheme[height] dimensions specify a region beyond the overall GIF image's
virtual space.}))
(define (gif-add-image GifFile
Left
Top
Width
Height
Interlace
ColorMap
Line)
(unless ColorMap
(unless (gif-stream-SColorMap GifFile)
(error 'gif-add-image
"no color map for image")))
(unless (= (bytes-length Line)
(* Width Height))
(error 'gif-add-image
"bytes string size doesn't match width times height: ~e"
Line))
(unless (and ((+ Left Width) . <= . (gif-stream-SWidth GifFile))
((+ Top Height) . <= . (gif-stream-SHeight GifFile)))
(error 'gif-add-image
"image extends beyond GIF virtual screen"))
(WRITE GifFile #",") ; /* Image seperator character. */
(EGifPutWord Left GifFile)
(EGifPutWord Top GifFile)
(EGifPutWord Width GifFile)
(EGifPutWord Height GifFile)
(WRITE GifFile
(bytes
(bitwise-ior
(if ColorMap #x80 #x00)
(if Interlace #x40 #x00)
(if ColorMap (sub1 (bits-per-pixel ColorMap)) #x00))))
;; /* If we have local color map - dump it also: */
(when ColorMap
(WRITE GifFile (color-map->bytes ColorMap)))
(let* ([cmap (or ColorMap
(gif-stream-SColorMap GifFile))])
(check-line-bytes (length cmap) Line)
(EGifCompress GifFile
(max 2 ;; min code size of LZW is 2
(bits-per-pixel cmap))
Line))
(set-gif-stream-FileState! GifFile 'image-or-control))
(define (check-line-bytes sz Line)
(let loop ([i (bytes-length Line)])
(unless (zero? i)
(let ([i (sub1 i)])
(unless ((bytes-ref Line i) . < . sz)
(error 'gif-add-image
"out-of-range color index ~a in line: ~e"
(bytes-ref Line i)
Line))
(loop i)))))
;;/******************************************************************************
;; * This routine should be called to add graphic control before the next image
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-control
(([stream image-or-control-ready-gif-stream?]
[disposal (one-of/c 'any 'keep 'restore-bg 'restore-prev)]
[wait-for-input? any/c]
[delay dimension?]
[transparent (or/c false/c color?)])
()
. ->d . [_ void?])
@{Writes an image-control command to a GIF stream. Such a control must
appear just before an image, and it applies to the following image.
The GIF image model involves processing images one by one, placing
each image into the specified position within the overall image's
virtual space. An image-control command can specify a delay before an
image is added (to create animated GIFs), and it also specifies how
the image should be kept or removed from the overall image before
proceeding to the next one (also for GIF animation).
The @scheme[disposal] argument specifies how to proceed:
@(itemize
@item{@scheme['any] : doesn't matter (perhaps because the next image
completely overwrites the current one)}
@item{@scheme['keep] : leave the image in place}
@item{@scheme['restore-bg] : replace the image with the background color}
@item{@scheme['restore-prev] : restore the overall image content to the
content before the image is added})
If @scheme[wait-for-input?] is true, then the display program may wait for
some cue from the user (perhaps a mouse click) before adding the
image.
The @scheme[delay] argument specifies a delay in 1/100s of a
second.
If the @scheme[transparent] argument is a color, then it
determines an index that is used to represent transparent pixels in the
follow image (as opposed to the color specified by the colormap for the index).
An exception is raised if a control is already added to @scheme[stream]
without a corresponding image.}))
(define (gif-add-control GifFile
Disposal
UserInput?
Delay ; 1/100s of a second
TransparentColor)
(WRITE GifFile #"\x21\xF9\x04")
(WRITE GifFile
(bytes (bitwise-ior
(case Disposal
[(any) 0]
[(keep) #x4]
[(restore-bg) #x8]
[(restore-prev) #xC])
(if UserInput? #x2 0)
(if TransparentColor #x1 0))))
(EGifPutWord Delay GifFile)
(WRITE GifFile (bytes
(or TransparentColor 0)
0)) ; block terminator
(set-gif-stream-FileState! GifFile 'image))
;;/******************************************************************************
;; * This routine should be called to add the "loop" graphic control
;; before adding any images
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-loop-control
(([stream empty-gif-stream?]
[iteration dimension?])
()
. ->d . [_ void?])
@{Writes a control command to a GIF stream for which no images or other
commands have already been written. The command causes the animating
sequence of images in the GIF to be repeated `iteration-dimension'
times, where 0 can be used to mean ``infinity.''
An exception is raise if some control or image has been added to the
stream already.}))
(define (gif-add-loop-control GifFile
Iterations)
(WRITE GifFile #"\x21\xFF\x0BNETSCAPE2.0\x03\x01")
(EGifPutWord Iterations GifFile)
(WRITE GifFile #"\x00")
(set-gif-stream-FileState! GifFile 'image-or-control))
;;/******************************************************************************
;; * This routine should be called to add arbitrary comment text
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-comment
(([stream image-or-control-ready-gif-stream?]
[bstr bytes?])
()
. ->d . [_ void?])
@{Adds a generic comment to the GIF stream.
An exception is raised if an image-control command was just written to
the stream (so that an image is required next).}))
(define (gif-add-comment GifFile
Str)
(WRITE GifFile #"\x21\xFE")
(let loop ([pos 0])
(when (pos . < . (bytes-length Str))
(let ([amt (min 255 (- (bytes-length Str) pos))])
(write-byte amt (gif-stream-port GifFile))
(write-bytes Str (gif-stream-port GifFile) pos (+ pos amt))
(loop (+ pos amt)))))
(WRITE GifFile #"\0")
(set-gif-stream-FileState! GifFile 'image-or-control))
;;/******************************************************************************
;; * This routine should be called last, to end GIF file.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-end
(([stream image-or-control-ready-gif-stream?])
()
. ->d . [_ void?])
@{Finishes
writing a GIF file. The GIF stream's output port is not
automatically closed.
An exception is raised if an image-control command was just written to
the stream (so that an image is required next).}))
(define (gif-end GifFile)
(WRITE GifFile #";")
(set-gif-stream-FileState! GifFile 'done))
;;/******************************************************************************
;; * Put 2 bytes (word) into the given file:
;; *****************************************************************************/
(define (EGifPutWord Word GifFile)
(WRITE GifFile (integer->integer-bytes Word 2 #f #f)))
;;/******************************************************************************
;; * LZ compression output
;; *****************************************************************************/
(define (write-buffered-byte b buf port)
(let ([cnt (add1 (bytes-ref buf 0))])
(bytes-set! buf cnt b)
(bytes-set! buf 0 cnt)
(when (= cnt 255)
(write-bytes buf port)
(bytes-set! buf 0 0))))
(define (CompressOutput port buf Code
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)
(let-values ([(CrntShiftState CrntShiftDWord)
(let loop ([CrntShiftDWord (bitwise-ior
CrntShiftDWord
(arithmetic-shift Code CrntShiftState))]
[CrntShiftState (+ CrntShiftState RunningBits)])
(if (CrntShiftState . >= . 8)
(begin
(write-buffered-byte (bitwise-and CrntShiftDWord #xff) buf port)
(loop (arithmetic-shift CrntShiftDWord -8)
(- CrntShiftState 8)))
(values CrntShiftState CrntShiftDWord)))])
(if (and (RunningCode . >= . MaxCode1)
(Code . <= . 4095))
(values (add1 RunningBits) (arithmetic-shift 1 (add1 RunningBits))
CrntShiftState CrntShiftDWord)
(values RunningBits MaxCode1
CrntShiftState CrntShiftDWord))))
;;/******************************************************************************
;; * LZ compression
;; *****************************************************************************/
(define (EGifCompress GifFile BitsPerPixel Line)
(WRITE GifFile (bytes BitsPerPixel))
(let* ([ClearCode (arithmetic-shift 1 BitsPerPixel)]
[EOFCode (add1 ClearCode)]
[RunningCode (add1 EOFCode)]
[RunningBits (add1 BitsPerPixel)]; /* Number of bits per code. */
[MaxCode1 (arithmetic-shift 1 RunningBits)]; /* Max. code + 1. */
[HashTable (make-hasheq)]
[CrntShiftState 0]; /* No information in CrntShiftDWord. */
[CrntShiftDWord 0]
[port (gif-stream-port GifFile)]
[len (bytes-length Line)]
[buf (make-bytes 256 0)])
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf ClearCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let loop ([CrntCode (bytes-ref Line 0)]
[RunningCode RunningCode]
[RunningBits RunningBits]
[MaxCode1 MaxCode1]
[CrntShiftState CrntShiftState]
[CrntShiftDWord CrntShiftDWord]
[HashTable HashTable]
[i 1])
(if (= i len)
;; Finish:
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf CrntCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf EOFCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
;; Flush output:
(let loop ([CrntShiftState CrntShiftState]
[CrntShiftDWord CrntShiftDWord])
(when (CrntShiftState . > . 0)
(write-buffered-byte (bitwise-and CrntShiftDWord #xff) buf port)
(loop (arithmetic-shift CrntShiftDWord -8)
(- CrntShiftState 8))))
(unless (zero? (bytes-ref buf 0))
(write-bytes buf port 0 (add1 (bytes-ref buf 0))))
(write-bytes #"\0" port)))
;; /* Get next pixel from stream. */
(let ([Pixel (bytes-ref Line i)])
;; /* Form a new unique key to search hash table for the code combines
;; * CrntCode as Prefix string with Pixel as postfix char.
;; */
(let* ([NewKey (bitwise-ior (arithmetic-shift CrntCode 8) Pixel)]
[NewCode (hash-ref HashTable NewKey #f)])
(if NewCode
;;/* This Key is already there, or the string is old one, so
;; * simple take new code as our CrntCode:
;; */
(loop NewCode
RunningCode RunningBits MaxCode1
CrntShiftState CrntShiftDWord
HashTable (add1 i))
;;/* Put it in hash table, output the prefix code, and make our
;; * CrntCode equal to Pixel. */
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf CrntCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let ([CrntCode Pixel])
;; /* If however the HashTable if full, we send a clear first and
;; * Clear the hash table. */
(if (RunningCode . >= . LZ_MAX_CODE)
;; /* Time to do some clearance: */
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf ClearCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(loop CrntCode
(add1 EOFCode) (add1 BitsPerPixel) (arithmetic-shift 1 (add1 BitsPerPixel))
CrntShiftState CrntShiftDWord
(make-hasheq) (add1 i)))
;; /* Put this unique key with its relative Code in hash table: */
(begin
(hash-set! HashTable NewKey RunningCode)
(loop CrntCode
(add1 RunningCode) RunningBits MaxCode1
CrntShiftState CrntShiftDWord
HashTable (add1 i))))))))))))))
;;/******************************************************************************
;; * color quantization utility
;; *****************************************************************************/
(define (argb-bytes? b)
(and (bytes? b)
(zero? (remainder (bytes-length b) 4))))
(provide/doc
(proc-doc quantize
(([bstr argb-bytes?])
()
. ->d .
(values [_ bytes?] [_ gif-colormap?] [_ (or/c false/c color?)]))
@{Each
image in a GIF stream is limited to 256 colors, including the
transparent ``color,'' if any. The @scheme[quantize] function helps converts a
24-bit image (plus alpha channel) into an indexed-color image,reducing
the number of colors if necessary.
Given a set of pixels expressed in ARGB format
(i.e., each four bytes is a set of values for one pixel: alpha, red, blue, and green),
@scheme[quantize] produces produces
@(itemize
@item{bytes for the image (i.e., a array of colors, expressed as a byte string)}
@item{a colormap}
@item{either @scheme[#f] or a color index for the transparent ``color''})
The conversion treats alpha values less than 128 as transparent
pixels, and other alpha values as solid.
The quantization process first attempts to use all (non-transparent)
colors in the image. if that fails, it reduces the image to 12-bit
color (3 bits per each of red, green, and blue) by rounding up pixel
values, and tries again. If that fails, it reduces the image to 6-bit
color (2 bits per each of red, green, and blue).
To convert a collection of images all with the same quantization,
simply append them for the input of a single call of @scheme[quantize], and
then break apart the result bytes.}))
(define (quantize argb)
(let* ([len (quotient (bytes-length argb) 4)]
[result (make-bytes len)]
[rgb-key (lambda (r g b)
(bitwise-ior (bitwise-ior
(arithmetic-shift r 16)
(arithmetic-shift g 8))
b))]
[key->rgb (lambda (k)
(vector (arithmetic-shift k -16)
(bitwise-and #xFF (arithmetic-shift k -8))
(bitwise-and #xFF k)))])
(let loop ([masks (list
;; 8 bits per color
(lambda (v) v)
;; 4 bits per color
(lambda (v)
(bitwise-ior
(bitwise-ior
v
(arithmetic-shift (bitwise-and v #x55) 1))
(arithmetic-shift (bitwise-and v #xCC) -1)))
;; 1 bit per color
(lambda (v)
(if (v . > . 127)
255
0)))])
(let ([mask (car masks)]
[transparent #f]
[table (make-hasheq)] ; relying on fixnums
[idx 0])
;; Iterate over image to count colors
;; (as reduced by mask)
(let loop ([i 0][pos 0])
(unless (= i len)
(let ([this-idx
(if ((bytes-ref argb pos) . < . 128)
(begin
(unless transparent
(set! transparent idx)
(set! idx (add1 idx)))
transparent)
(let ([vec (rgb-key
(mask (bytes-ref argb (+ 1 pos)))
(mask (bytes-ref argb (+ 2 pos)))
(mask (bytes-ref argb (+ 3 pos))))])
(let ([v (hash-ref table vec #f)])
(or v
(begin
(hash-set! table vec idx)
(set! idx (add1 idx))
(sub1 idx))))))])
(unless (= this-idx 256)
(bytes-set! result i this-idx)
(loop (add1 i) (+ pos 4))))))
(if ((hash-count table) . > . (if transparent
255
256))
;; Try again
(loop (cdr masks))
;; Found an ok quantization
(values result
(let* ([cnt (+ (hash-count table)
(if transparent 1 0))]
[size (cond
[(<= cnt 2) 2]
[(<= cnt 4) 4]
[(<= cnt 8) 8]
[(<= cnt 16) 16]
[(<= cnt 32) 32]
[(<= cnt 64) 64]
[(<= cnt 128) 128]
[else 256])]
[t (make-vector size #(0 0 0))])
(hash-for-each
table
(lambda (k v)
(vector-set! t v (key->rgb k))))
(vector->list t))
transparent)))))))

View File

@ -1,232 +1,36 @@
#lang scribble/doc #lang scribble/doc
@(require "common.ss" @(require scribble/manual
(for-label file/gif scribble/extract
(only-in scheme/gui bitmap%))) (for-label file/gif))
@title{GIF File Writing} @title[#:tag "gif"]{GIF File Writing}
@defmodule[file/gif]{The @schememodname[file/gif] library provides @defmodule[file/gif]
functions for writing GIF files to a stream, including GIF files with
multiple images and controls (such as animated GIFs).}
This library does not rely on @schememodname[scheme/gui]. See The @schememodname[file/gif] library provides functions for
@scheme[bitmap%] (which is part of @schememodname[scheme/gui]) to read writing GIF files to a stream, including GIF files with multiple
a GIF file. See also @schememodname[mrlib/gif]. images and controls (such as animated GIFs).
@defproc[(gif-start [out output-port?] A GIF stream is created by @scheme[gif-start], and then individual
[width (integer-in 0 #xFFFFFFFF)] images are written with @scheme[gif-add-image]. Optionally,
[height (integer-in 0 #xFFFFFFFF)] @scheme[gif-add-control] inserts instructions for rendering the
[bg-color byte?] images. The @scheme[gif-end] function ends the GIF stream.
[colormap (or/c false/c gif-colormap?)])
gif-stream?]{
Writes the start of a GIF file to the given output port, and returns a A GIF stream can be in any one of the following states:
GIF stream that adds to the output port.
The width and height determine a virtual space for the overall GIF
image. Individual images added to the GIF stream must fit within this
virtual space. The space is initialized by the given background color.
The default meaning of color numbers (such as the background color) is
determined by the given colormap, but individual images within the GIF
file can have their own colormaps.
A global colormap need not be supplied, in which case a colormap must
be supplied for each image. Beware that the @scheme[bg-color]'s
meaning is ill-defined if a global colormap is not provided.}
@defproc[(gif-stream? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a GIF stream created by
@scheme[gif-start].
A stream can be in any of several states, some of which are recognized
by more specific predicates:
@itemize{
@item{@scheme[empty-gif-stream?] : no images or controls have been added to the stream}
@item{@scheme[image-or-control-ready-gif-stream?] : another image or control can be written now}
@item{@scheme[image-ready-gif-stream?] : another image can be written
now (since a control was written).}
@item{done : nothing more can be added}
}}
@defproc[(empty-gif-stream? [v any/c]) boolean?]{
See @scheme[gif-stream?].}
@defproc[(image-or-control-ready-gif-stream? [v any/c]) boolean?]{
See @scheme[gif-stream?].}
@defproc[(image-ready-gif-stream? [v any/c]) boolean?]{
See @scheme[gif-stream?].}
@defproc[(gif-colormap? [v any/c]) boolean?]{
Returns @scheme[#t] if @scheme[v] is a list of vectors where each
vector must contain three bytes---one for red, one for blue, and one
for green---and the list length must be 2, 4, 8, 16, 32, 64, 128, or
256. The colors are indexed (starting from 0) by their order in the
list.}
@defproc[(gif-end [gs image-or-control-ready-gif-stream?]) void?]{
Finishes writing a GIF file. The GIF stream's output port is not
automatically closed.}
@defproc[(gif-add-image [gs image-or-control-ready-gif-stream?]
[left (integer-in 0 #xFFFFFFFF)]
[top (integer-in 0 #xFFFFFFFF)]
[width (integer-in 0 #xFFFFFFFF)]
[height (integer-in 0 #xFFFFFFFF)]
[interlaced? any/c]
[colormap (or/c gif-colormap? false/c)]
[bstr bytes?])
void?]{
Writes an image to the given GIF stream @scheme[gs]. The
@scheme[left], @scheme[top], @scheme[width], and @scheme[height]
values specify the location and size of the image within the overall
GIF image's virtual space.
If @scheme[interlaced?] is true, then @scheme[bytes] should provide
bytes in interlaced order instead of top-to-bottom order. Interlaced
order is:
@itemize{ @itemize{
@item{every 8th row, starting with 0} @item{@scheme['init] : no images or controls have been added to the
@item{every 8th row, starting with 4} stream}
@item{every 4th row, starting with 2}
@item{every 2nd row, starting with 1} @item{@scheme['image-or-control] : another image or control can be
written}
@item{@scheme['image] : another image can be written (but not a
control, since a control was written)}
@item{@scheme['done] : nothing more can be added}
} }
If a global color is provided with @scheme[gif-start], a @scheme[#f] @(include-extracted "../gif.ss")
value can be provided for @scheme[colormap].
The @scheme[bstr] argument specifies the pixel content of the
image. Each byte specifies a color (i.e., an index in the
colormap). Each row is provided left-to-right, and the rows provided
either top-to-bottom or in interlaced order (see above). If the image
is prefixed with a control that specifies an transparent index (see
@scheme[gif-add-control]), then the corresponding ``color'' doesn't
draw into the overall GIF image.
An exception is raised if any byte value in @scheme[bstr] is larger
than the colormap's length, if the @scheme[bstr] length is not
@scheme[width] times @scheme[height], or if the @scheme[top],
@scheme[left], @scheme[width], and @scheme[height] dimensions specify
a region beyond the overall GIF image's virtual space.}
@defproc[(gif-add-control [gs image-or-control-ready-gif-stream?]
[disposal (one-of/c 'any 'keep 'restore-bg 'restore-prev)]
[wait-for-input? any/c]
[delay-csec (integer-in 0 #xFFFFFFFF)]
[transparent (or/c byte? false/c)])
void?]{
Writes an image-control command to a GIF stream. Such a control must
appear just before an image, and it applies to the following image.
The GIF image model involves processing images one by one, placing
each image into the specified position within the overall image's
virtual space. An image-control command can specify a delay before an
image is added (to create animated GIFs), and it also specifies how
the image should be kept or removed from the overall image before
proceeding to the next one (also for GIF animation).
The @scheme[disposal] argument specifies how to proceed:
@itemize{
@item{@scheme['any] : doesn't matter (perhaps because the next image
completely overwrites the current one)}
@item{@scheme['keep] : leave the image in place}
@item{@scheme['restore-bg] : replace the image with the background
color}
@item{@scheme['restore-prev] : restore the overall image content to the
content before the image is added}
}
If @scheme[wait-for-input?] is true, then the display program may wait
for some cue from the user (perhaps a mouse click) before adding the
image.
The @scheme[delay-csec] argument specifies a delay in @math{1/100}s of
a second.
If the @scheme[transparent] argument is a byte, then it determines an
index that is used to represent transparent pixels in the follow image
(as opposed to the color specified by the colormap for the index).}
@defproc[(gif-add-loop-control [gs image-or-control-ready-gif-stream?]
[iteration (integer-in 0 #xFFFFFFFF)])
void?]{
Writes an control command to a GIF stream for which no images or other
commands have already been written. The command causes the animating
sequence of images in the GIF to be repeated @scheme[iteration]
times, where 0 can be used to mean ``infinity.''}
@defproc[(gif-add-comment [gs image-or-control-ready-gif-stream?])
void?]{
Adds a generic comment to the GIF stream.}
@defproc[(quantize [argb-bstr bytes?])
(values bytes?
gif-colormap?
(or/c byte? false/c))]{
Each image in a GIF stream is limited to 256 colors, including the
transparent ``color,'' if any. The @scheme[quantize] function helps
converts a 24-bit image (plus alpha channel) into an indexed-color
image, reducing the number of colors if necessary.
Given a set of pixels expressed in ARGB format (i.e., each four bytes
is a set of values for one pixel: alpha, red, blue, and green),
@scheme[quantize] produces produces
@itemize{
@item{bytes for the image (i.e., a array of colors, expressed as a byte
string),}
@item{a colormap, and}
@item{either @scheme[#f] or a color index for the transparent ``color.''}
}
The conversion treats alpha values less than 128 as transparent
pixels, and other alpha values as solid.
The quantization process first attempts to use all (non-transparent)
colors in the image. If that fails, it reduces the image to 12-bit
color (3 bits per each of red, green, and blue) by rounding up pixel
values, and tries again. If that fails, it reduces the image to 6-bit
color (2 bits per each of red, green, and blue).
To convert a collection of images all with the same quantization,
append them for the input of a single call of @scheme[quantize], and
then break apart the result bytes.}

View File

@ -3,7 +3,7 @@
(for-label file/gunzip (for-label file/gunzip
file/gzip)) file/gzip))
@title{@exec{gzip} Decompression} @title[#:tag "gunzip"]{@exec{gzip} Decompression}
@defmodule[file/gunzip]{The @schememodname[file/gunzip] library provides @defmodule[file/gunzip]{The @schememodname[file/gunzip] library provides
utilities to decompress archive files in @exec{gzip} format, or simply utilities to decompress archive files in @exec{gzip} format, or simply

View File

@ -2,7 +2,7 @@
@(require "common.ss" @(require "common.ss"
(for-label file/gzip)) (for-label file/gzip))
@title{@exec{gzip} Compression and File Creation} @title[#:tag "gzip"]{@exec{gzip} Compression and File Creation}
@defmodule[file/gzip]{The @schememodname[file/gzip] library provides @defmodule[file/gzip]{The @schememodname[file/gzip] library provides
utilities to create archive files in @exec{gzip} format, or simply to utilities to create archive files in @exec{gzip} format, or simply to

View File

@ -7,7 +7,7 @@
@(define md5-eval (make-base-eval)) @(define md5-eval (make-base-eval))
@interaction-eval[#:eval md5-eval (require file/md5)] @interaction-eval[#:eval md5-eval (require file/md5)]
@title{MD5 Message Digest} @title[#:tag "md5"]{MD5 Message Digest}
@defmodule[file/md5] @defmodule[file/md5]

View File

@ -2,7 +2,7 @@
@(require "common.ss" @(require "common.ss"
(for-label file/tar)) (for-label file/tar))
@title{@exec{tar} File Creation} @title[#:tag "tar"]{@exec{tar} File Creation}
@defmodule[file/tar]{The @schememodname[file/tar] library provides @defmodule[file/tar]{The @schememodname[file/tar] library provides
utilities to create archive files in USTAR format, like the archive utilities to create archive files in USTAR format, like the archive

View File

@ -4,7 +4,7 @@
file/gunzip file/gunzip
scheme/file)) scheme/file))
@title{@exec{zip} File Creation} @title[#:tag "zip"]{@exec{zip} File Creation}
@defmodule[file/zip]{The @schememodname[file/zip] library provides @defmodule[file/zip]{The @schememodname[file/zip] library provides
utilities to create @exec{zip} archive files, which are compatible utilities to create @exec{zip} archive files, which are compatible

View File

@ -1,682 +1,4 @@
#| #lang scheme/base
/******************************************************************************
* "Gif-Lib" - Yet another gif library.
*
* Written by: Gershon Elber Ver 1.1, Aug. 1990
******************************************************************************
* The kernel of the GIF Encoding process can be found here.
******************************************************************************
* History:
* 14 Jun 89 - Version 1.0 by Gershon Elber.
* 3 Sep 90 - Version 1.1 by Gershon Elber (Support for Gif89, Unique names).
* 26 Jun 96 - Version 3.0 by Eric S. Raymond (Full GIF89 support)
* 5 Jan 07 - Ported to MzScheme by Matthew
*****************************************************************************/
|#
#reader scribble/reader (require file/gif)
(module gifwrite scheme/base (provide (all-from-out file/gif))
(require scheme/contract
scribble/srcdoc)
(require/doc scheme/base
scribble/manual)
(define LZ_MAX_CODE 4095)
(define GifVersionPrefix #"GIF89a")
(provide/doc
(proc-doc gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[@t] if @scheme[v] is a GIF stream created by @scheme[gif-write],
@scheme[#f] otherwise.})
(proc-doc image-ready-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that is not in
@scheme['done] mode, @scheme[#f] otherwise.})
(proc-doc image-or-control-ready-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that is in
@scheme['init] or @scheme['image-or-control] mode, @scheme[#f] otherwise.})
(proc-doc empty-gif-stream? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is a GIF stream that in
@scheme['init] mode, @scheme[#f] otherwise.})
(proc-doc gif-colormap? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] represets a colormap, @scheme[#f] otherwise.
A colormap is a list whose size is a power of @math{2} between @math{2^1} and @math{2^8},
and whose elements are vectors of size 3 containing colors
(i.e., exact integers between @math{0} and @math{255} inclusive).})
(proc-doc color? (([v any/c]) () . ->d . [_ boolean?])
@{The same as @scheme[byte?].})
(proc-doc dimension? (([v any/c]) () . ->d . [_ boolean?])
@{Returns @scheme[#t] if @scheme[v] is an exact integer between
@scheme[#x0] and @scheme[#xFFFF] inclusive, @scheme[#f]
otherwise.}))
(define-struct gif-stream (port
SWidth
SHeight
SBackGroundColor
SColorMap
[FileState #:mutable]))
(define (image-ready-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init image-or-control image))))
(define (image-or-control-ready-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init image-or-control))))
(define (empty-gif-stream? g)
(and (gif-stream? g)
(memq (gif-stream-FileState g) '(init))))
(define color? byte?)
(define (dimension? x) (and (exact? x) (integer? x) (<= 0 x #xFFFF)))
(define (gif-colormap? l)
(and (list? l)
(member (length l) '(2 4 8 16 32 64 128 256))
(andmap (lambda (c)
(and (vector? c)
(= 3 (vector-length c))
(color? (vector-ref c 0))
(color? (vector-ref c 1))
(color? (vector-ref c 2))))
l)))
(define (color-map->bytes ColorMap)
(apply bytes (apply append (map vector->list ColorMap))))
(define (bits-per-pixel ColorMap)
(case (length ColorMap)
[(2) 1]
[(4) 2]
[(8) 3]
[(16) 4]
[(32) 5]
[(64) 6]
[(128) 7]
[(256) 8]
[else (error 'bits-per-pixel
"strange colormap size: ~e"
(length ColorMap))]))
(define (WRITE g bytes)
(write-bytes bytes (gif-stream-port g)))
(provide/doc (proc-doc gif-state (([stream gif-stream?]) () . ->d . [_ symbol?])
@{Returns the state of @scheme[stream].}))
(define (gif-state GifFile)
(gif-stream-FileState GifFile))
;;/******************************************************************************
;; * This routine should be called before any other EGif calls, immediately
;; * follows the GIF file openning.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-start
(([out output-port?]
[w dimension?]
[h dimension?]
[bg-color color?]
[cmap (or/c false/c gif-colormap?)])
()
. ->d . [_ gif-stream?])
@{Writes the start of a GIF file to the given output port, and returns
a GIF stream that adds to the output port.
The width and height determine a virtual space for the overall GIF
image. Individual images added to the GIF stream must fit within this
virtual space. The space is initialized by the given background color.
Finally, the default meaning of color numbers (such as the background color)
is determined by the given colormap, but individual images
within the GIF file can have their own colormaps.
A global colormap need not be supplied, in which case a colormap must
be supplied for each image. Beware that the bg-color is ill-defined if
a global colormap is not provided.}))
(define (gif-start port
Width
Height
BackGround
ColorMap)
(define GifFile
(make-gif-stream port
Width
Height
BackGround
ColorMap
'init))
(when ColorMap
(unless (BackGround . < . (length ColorMap))
(error 'gif-start
"background color ~a is out of range for the color map: ~e"
BackGround
ColorMap)))
(WRITE GifFile GifVersionPrefix)
;;/*
;; * Put the logical screen descriptor into the file:
;; */
;;/* Logical Screen Descriptor: Dimensions */
(EGifPutWord Width GifFile)
(EGifPutWord Height GifFile)
;;/* Logical Screen Descriptor: Packed Fields */
;;/* Note: We have actual size of the color table default to the largest
;; * possible size (7+1 == 8 bits) because the decoder can use it to decide
;; * how to display the files.
;; */
(WRITE GifFile
(bytes (bitwise-ior
(if ColorMap #x80 #x00)
(arithmetic-shift #x7 4) ; /* Bits originally allocated to each primary color */
(if ColorMap (sub1 (bits-per-pixel ColorMap)) #x07)) ; /* Actual size of the color table. */
BackGround ; /* Index into the ColorTable for background color */
0)) ; /* Pixel Aspect Ratio */
;; /* If we have Global color map - dump it also: */
(when ColorMap
(WRITE GifFile (color-map->bytes ColorMap)))
GifFile)
;;/******************************************************************************
;; * This routine should be called before any attempt to dump an image - any
;; * call to any of the pixel dump routines.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-image
(([stream image-ready-gif-stream?]
[left dimension?]
[top dimension?]
[width dimension?]
[height dimension?]
[interlaced? any/c]
[cmap (or/c false/c gif-colormap?)]
[bstr bytes?])
()
. ->d . [_ void?])
@{Writes an image to the given GIF stream. The @scheme[left], @scheme[top],
@scheme[width], and @scheme[height] values specify the location and
size of the image within the overall GIF image's virtual space.
If @scheme[interlaced?] is true, then @scheme[bstr] should provide bytes
ininterlaced order instead of top-to-bottom order. Interlaced order is:
@(itemize
@item{every 8th row, starting with 0}
@item{every 8th row, starting with 4}
@item{every 4th row, starting with 2}
@item{every 2nd row, starting with 1})
If a global color is provided with @scheme[gif-start], a @scheme[#f] value
can be provided for @scheme[cmap].
The @scheme[bstr] argument specifies the pixel content of the image. Each
byte specifies a color (i.e., an index in the colormap). Each row is
provided left-to-right, and the rows provided either top-to-bottom or
in interlaced order (see above). If the image is prefixed with a
control that specifies an transparent index (see @scheme[gif-add-control]),
then the corresponding ``color'' doesn't draw into the overall GIF
image.
An exception is raised if any byte value in @scheme[bstr] is larger than the
colormap's length, if the @scheme[bstr] length is not @scheme[width] times
@scheme[height], or if the @scheme[top], @scheme[left], @scheme[width], and
@scheme[height] dimensions specify a region beyond the overall GIF image's
virtual space.}))
(define (gif-add-image GifFile
Left
Top
Width
Height
Interlace
ColorMap
Line)
(unless ColorMap
(unless (gif-stream-SColorMap GifFile)
(error 'gif-add-image
"no color map for image")))
(unless (= (bytes-length Line)
(* Width Height))
(error 'gif-add-image
"bytes string size doesn't match width times height: ~e"
Line))
(unless (and ((+ Left Width) . <= . (gif-stream-SWidth GifFile))
((+ Top Height) . <= . (gif-stream-SHeight GifFile)))
(error 'gif-add-image
"image extends beyond GIF virtual screen"))
(WRITE GifFile #",") ; /* Image seperator character. */
(EGifPutWord Left GifFile)
(EGifPutWord Top GifFile)
(EGifPutWord Width GifFile)
(EGifPutWord Height GifFile)
(WRITE GifFile
(bytes
(bitwise-ior
(if ColorMap #x80 #x00)
(if Interlace #x40 #x00)
(if ColorMap (sub1 (bits-per-pixel ColorMap)) #x00))))
;; /* If we have local color map - dump it also: */
(when ColorMap
(WRITE GifFile (color-map->bytes ColorMap)))
(let* ([cmap (or ColorMap
(gif-stream-SColorMap GifFile))])
(check-line-bytes (length cmap) Line)
(EGifCompress GifFile
(max 2 ;; min code size of LZW is 2
(bits-per-pixel cmap))
Line))
(set-gif-stream-FileState! GifFile 'image-or-control))
(define (check-line-bytes sz Line)
(let loop ([i (bytes-length Line)])
(unless (zero? i)
(let ([i (sub1 i)])
(unless ((bytes-ref Line i) . < . sz)
(error 'gif-add-image
"out-of-range color index ~a in line: ~e"
(bytes-ref Line i)
Line))
(loop i)))))
;;/******************************************************************************
;; * This routine should be called to add graphic control before the next image
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-control
(([stream image-or-control-ready-gif-stream?]
[disposal (one-of/c 'any 'keep 'restore-bg 'restore-prev)]
[wait-for-input? any/c]
[delay dimension?]
[transparent (or/c false/c color?)])
()
. ->d . [_ void?])
@{Writes an image-control command to a GIF stream. Such a control must
appear just before an image, and it applies to the following image.
The GIF image model involves processing images one by one, placing
each image into the specified position within the overall image's
virtual space. An image-control command can specify a delay before an
image is added (to create animated GIFs), and it also specifies how
the image should be kept or removed from the overall image before
proceeding to the next one (also for GIF animation).
The @scheme[disposal] argument specifies how to proceed:
@(itemize
@item{@scheme['any] : doesn't matter (perhaps because the next image
completely overwrites the current one)}
@item{@scheme['keep] : leave the image in place}
@item{@scheme['restore-bg] : replace the image with the background color}
@item{@scheme['restore-prev] : restore the overall image content to the
content before the image is added})
If @scheme[wait-for-input?] is true, then the display program may wait for
some cue from the user (perhaps a mouse click) before adding the
image.
The @scheme[delay] argument specifies a delay in 1/100s of a
second.
If the @scheme[transparent] argument is a color, then it
determines an index that is used to represent transparent pixels in the
follow image (as opposed to the color specified by the colormap for the index).
An exception is raised if a control is already added to @scheme[stream]
without a corresponding image.}))
(define (gif-add-control GifFile
Disposal
UserInput?
Delay ; 1/100s of a second
TransparentColor)
(WRITE GifFile #"\x21\xF9\x04")
(WRITE GifFile
(bytes (bitwise-ior
(case Disposal
[(any) 0]
[(keep) #x4]
[(restore-bg) #x8]
[(restore-prev) #xC])
(if UserInput? #x2 0)
(if TransparentColor #x1 0))))
(EGifPutWord Delay GifFile)
(WRITE GifFile (bytes
(or TransparentColor 0)
0)) ; block terminator
(set-gif-stream-FileState! GifFile 'image))
;;/******************************************************************************
;; * This routine should be called to add the "loop" graphic control
;; before adding any images
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-loop-control
(([stream empty-gif-stream?]
[iteration dimension?])
()
. ->d . [_ void?])
@{Writes a control command to a GIF stream for which no images or other
commands have already been written. The command causes the animating
sequence of images in the GIF to be repeated `iteration-dimension'
times, where 0 can be used to mean ``infinity.''
An exception is raise if some control or image has been added to the
stream already.}))
(define (gif-add-loop-control GifFile
Iterations)
(WRITE GifFile #"\x21\xFF\x0BNETSCAPE2.0\x03\x01")
(EGifPutWord Iterations GifFile)
(WRITE GifFile #"\x00")
(set-gif-stream-FileState! GifFile 'image-or-control))
;;/******************************************************************************
;; * This routine should be called to add arbitrary comment text
;; *****************************************************************************/
(provide/doc (proc-doc
gif-add-comment
(([stream image-or-control-ready-gif-stream?]
[bstr bytes?])
()
. ->d . [_ void?])
@{Adds a generic comment to the GIF stream.
An exception is raised if an image-control command was just written to
the stream (so that an image is required next).}))
(define (gif-add-comment GifFile
Str)
(WRITE GifFile #"\x21\xFE")
(let loop ([pos 0])
(when (pos . < . (bytes-length Str))
(let ([amt (min 255 (- (bytes-length Str) pos))])
(write-byte amt (gif-stream-port GifFile))
(write-bytes Str (gif-stream-port GifFile) pos (+ pos amt))
(loop (+ pos amt)))))
(WRITE GifFile #"\0")
(set-gif-stream-FileState! GifFile 'image-or-control))
;;/******************************************************************************
;; * This routine should be called last, to end GIF file.
;; *****************************************************************************/
(provide/doc (proc-doc
gif-end
(([stream image-or-control-ready-gif-stream?])
()
. ->d . [_ void?])
@{Finishes
writing a GIF file. The GIF stream's output port is not
automatically closed.
An exception is raised if an image-control command was just written to
the stream (so that an image is required next).}))
(define (gif-end GifFile)
(WRITE GifFile #";")
(set-gif-stream-FileState! GifFile 'done))
;;/******************************************************************************
;; * Put 2 bytes (word) into the given file:
;; *****************************************************************************/
(define (EGifPutWord Word GifFile)
(WRITE GifFile (integer->integer-bytes Word 2 #f #f)))
;;/******************************************************************************
;; * LZ compression output
;; *****************************************************************************/
(define (write-buffered-byte b buf port)
(let ([cnt (add1 (bytes-ref buf 0))])
(bytes-set! buf cnt b)
(bytes-set! buf 0 cnt)
(when (= cnt 255)
(write-bytes buf port)
(bytes-set! buf 0 0))))
(define (CompressOutput port buf Code
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)
(let-values ([(CrntShiftState CrntShiftDWord)
(let loop ([CrntShiftDWord (bitwise-ior
CrntShiftDWord
(arithmetic-shift Code CrntShiftState))]
[CrntShiftState (+ CrntShiftState RunningBits)])
(if (CrntShiftState . >= . 8)
(begin
(write-buffered-byte (bitwise-and CrntShiftDWord #xff) buf port)
(loop (arithmetic-shift CrntShiftDWord -8)
(- CrntShiftState 8)))
(values CrntShiftState CrntShiftDWord)))])
(if (and (RunningCode . >= . MaxCode1)
(Code . <= . 4095))
(values (add1 RunningBits) (arithmetic-shift 1 (add1 RunningBits))
CrntShiftState CrntShiftDWord)
(values RunningBits MaxCode1
CrntShiftState CrntShiftDWord))))
;;/******************************************************************************
;; * LZ compression
;; *****************************************************************************/
(define (EGifCompress GifFile BitsPerPixel Line)
(WRITE GifFile (bytes BitsPerPixel))
(let* ([ClearCode (arithmetic-shift 1 BitsPerPixel)]
[EOFCode (add1 ClearCode)]
[RunningCode (add1 EOFCode)]
[RunningBits (add1 BitsPerPixel)]; /* Number of bits per code. */
[MaxCode1 (arithmetic-shift 1 RunningBits)]; /* Max. code + 1. */
[HashTable (make-hasheq)]
[CrntShiftState 0]; /* No information in CrntShiftDWord. */
[CrntShiftDWord 0]
[port (gif-stream-port GifFile)]
[len (bytes-length Line)]
[buf (make-bytes 256 0)])
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf ClearCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let loop ([CrntCode (bytes-ref Line 0)]
[RunningCode RunningCode]
[RunningBits RunningBits]
[MaxCode1 MaxCode1]
[CrntShiftState CrntShiftState]
[CrntShiftDWord CrntShiftDWord]
[HashTable HashTable]
[i 1])
(if (= i len)
;; Finish:
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf CrntCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf EOFCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
;; Flush output:
(let loop ([CrntShiftState CrntShiftState]
[CrntShiftDWord CrntShiftDWord])
(when (CrntShiftState . > . 0)
(write-buffered-byte (bitwise-and CrntShiftDWord #xff) buf port)
(loop (arithmetic-shift CrntShiftDWord -8)
(- CrntShiftState 8))))
(unless (zero? (bytes-ref buf 0))
(write-bytes buf port 0 (add1 (bytes-ref buf 0))))
(write-bytes #"\0" port)))
;; /* Get next pixel from stream. */
(let ([Pixel (bytes-ref Line i)])
;; /* Form a new unique key to search hash table for the code combines
;; * CrntCode as Prefix string with Pixel as postfix char.
;; */
(let* ([NewKey (bitwise-ior (arithmetic-shift CrntCode 8) Pixel)]
[NewCode (hash-ref HashTable NewKey #f)])
(if NewCode
;;/* This Key is already there, or the string is old one, so
;; * simple take new code as our CrntCode:
;; */
(loop NewCode
RunningCode RunningBits MaxCode1
CrntShiftState CrntShiftDWord
HashTable (add1 i))
;;/* Put it in hash table, output the prefix code, and make our
;; * CrntCode equal to Pixel. */
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf CrntCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(let ([CrntCode Pixel])
;; /* If however the HashTable if full, we send a clear first and
;; * Clear the hash table. */
(if (RunningCode . >= . LZ_MAX_CODE)
;; /* Time to do some clearance: */
(let-values ([(RunningBits MaxCode1 CrntShiftState CrntShiftDWord)
(CompressOutput port buf ClearCode
RunningBits RunningCode MaxCode1
CrntShiftState CrntShiftDWord)])
(loop CrntCode
(add1 EOFCode) (add1 BitsPerPixel) (arithmetic-shift 1 (add1 BitsPerPixel))
CrntShiftState CrntShiftDWord
(make-hasheq) (add1 i)))
;; /* Put this unique key with its relative Code in hash table: */
(begin
(hash-set! HashTable NewKey RunningCode)
(loop CrntCode
(add1 RunningCode) RunningBits MaxCode1
CrntShiftState CrntShiftDWord
HashTable (add1 i))))))))))))))
;;/******************************************************************************
;; * color quantization utility
;; *****************************************************************************/
(define (argb-bytes? b)
(and (bytes? b)
(zero? (remainder (bytes-length b) 4))))
(provide/doc
(proc-doc quantize
(([bstr argb-bytes?])
()
. ->d .
(values [_ bytes?] [_ gif-colormap?] [_ (or/c false/c color?)]))
@{Each
image in a GIF stream is limited to 256 colors, including the
transparent ``color,'' if any. The @scheme[quantize] function helps converts a
24-bit image (plus alpha channel) into an indexed-color image,reducing
the number of colors if necessary.
Given a set of pixels expressed in ARGB format
(i.e., each four bytes is a set of values for one pixel: alpha, red, blue, and green),
@scheme[quantize] produces produces
@(itemize
@item{bytes for the image (i.e., a array of colors, expressed as a byte string)}
@item{a colormap}
@item{either @scheme[#f] or a color index for the transparent ``color''})
The conversion treats alpha values less than 128 as transparent
pixels, and other alpha values as solid.
The quantization process first attempts to use all (non-transparent)
colors in the image. if that fails, it reduces the image to 12-bit
color (3 bits per each of red, green, and blue) by rounding up pixel
values, and tries again. If that fails, it reduces the image to 6-bit
color (2 bits per each of red, green, and blue).
To convert a collection of images all with the same quantization,
simply append them for the input of a single call of @scheme[quantize], and
then break apart the result bytes.}))
(define (quantize argb)
(let* ([len (quotient (bytes-length argb) 4)]
[result (make-bytes len)]
[rgb-key (lambda (r g b)
(bitwise-ior (bitwise-ior
(arithmetic-shift r 16)
(arithmetic-shift g 8))
b))]
[key->rgb (lambda (k)
(vector (arithmetic-shift k -16)
(bitwise-and #xFF (arithmetic-shift k -8))
(bitwise-and #xFF k)))])
(let loop ([masks (list
;; 8 bits per color
(lambda (v) v)
;; 4 bits per color
(lambda (v)
(bitwise-ior
(bitwise-ior
v
(arithmetic-shift (bitwise-and v #x55) 1))
(arithmetic-shift (bitwise-and v #xCC) -1)))
;; 1 bit per color
(lambda (v)
(if (v . > . 127)
255
0)))])
(let ([mask (car masks)]
[transparent #f]
[table (make-hasheq)] ; relying on fixnums
[idx 0])
;; Iterate over image to count colors
;; (as reduced by mask)
(let loop ([i 0][pos 0])
(unless (= i len)
(let ([this-idx
(if ((bytes-ref argb pos) . < . 128)
(begin
(unless transparent
(set! transparent idx)
(set! idx (add1 idx)))
transparent)
(let ([vec (rgb-key
(mask (bytes-ref argb (+ 1 pos)))
(mask (bytes-ref argb (+ 2 pos)))
(mask (bytes-ref argb (+ 3 pos))))])
(let ([v (hash-ref table vec #f)])
(or v
(begin
(hash-set! table vec idx)
(set! idx (add1 idx))
(sub1 idx))))))])
(unless (= this-idx 256)
(bytes-set! result i this-idx)
(loop (add1 i) (+ pos 4))))))
(if ((hash-count table) . > . (if transparent
255
256))
;; Try again
(loop (cdr masks))
;; Found an ok quantization
(values result
(let* ([cnt (+ (hash-count table)
(if transparent 1 0))]
[size (cond
[(<= cnt 2) 2]
[(<= cnt 4) 4]
[(<= cnt 8) 8]
[(<= cnt 16) 16]
[(<= cnt 32) 32]
[(<= cnt 64) 64]
[(<= cnt 128) 128]
[else 256])]
[t (make-vector size #(0 0 0))])
(hash-for-each
table
(lambda (k v)
(vector-set! t v (key->rgb k))))
(vector->list t))
transparent)))))))

View File

@ -1,36 +0,0 @@
#lang scribble/doc
@(require scribble/manual
scribble/extract
(for-label net/gifwrite))
@title[#:tag "gifwrite"]{GIF: Writing Image Files}
@defmodule[net/gifwrite]
The @schememodname[net/gifwrite] library provides functions for
writing GIF files to a stream, including GIF files with multiple
images and controls (such as animated GIFs).
A GIF stream is created by @scheme[gif-start], and then individual
images are written with @scheme[gif-add-image]. Optionally,
@scheme[gif-add-control] inserts instructions for rendering the
images. The @scheme[gif-end] function ends the GIF stream.
A GIF stream can be in any one of the following states:
@itemize{
@item{@scheme['init] : no images or controls have been added to the
stream}
@item{@scheme['image-or-control] : another image or control can be
written}
@item{@scheme['image] : another image can be written (but not a
control, since a control was written)}
@item{@scheme['done] : nothing more can be added}
}
@(include-extracted "../gifwrite.ss")

View File

@ -24,7 +24,6 @@
@include-section["ssl-tcp-unit.scrbl"] @include-section["ssl-tcp-unit.scrbl"]
@include-section["cgi.scrbl"] @include-section["cgi.scrbl"]
@include-section["cookie.scrbl"] @include-section["cookie.scrbl"]
@include-section["gifwrite.scrbl"]
@(bibliography @(bibliography