gifblock.tcl
# gifblock.tcl # # Manipulate GIF streams in pure Tcl # # Copyright (c) 2006-2008 Michael Thomas Greer # # Boost Software License - Version 1.0 - August 17th, 2003 # # Permission is hereby granted, free of charge, to any person or organization # obtaining a copy of the software and accompanying documentation covered by # this license (the "Software") to use, reproduce, display, distribute, # execute, and transmit the Software, and to prepare derivative works of the # Software, and to permit third-parties to whom the Software is furnished to # do so, all subject to the following: # # The copyright notices in the Software and this entire statement, including # the above license grant, this restriction and the following disclaimer, # must be included in all copies of the Software, in whole or in part, and # all derivative works of the Software, unless such copies or derivative # works are solely in the form of machine-executable object code generated by # a source language processor. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT # SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE # FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, # ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. # # See gifblock.txt for documentation # namespace eval ::gifblock:: { namespace export \ gif.blocknames \ gif.get \ gif.index \ gif.load \ gif.save \ gif.set package provide gifblock 1.0 } #----------------------------------------------------------------------------- proc ::gifblock::gif.blocknames varName { #----------------------------------------------------------------------------- upvar 1 $varName blocks set cntr -1 set count [llength $blocks] set result {} while {[incr cntr] < $count} {lappend result [gif.get blocks $cntr type]} return $result } #----------------------------------------------------------------------------- proc ::gifblock::gif.get {varName index args} { #----------------------------------------------------------------------------- upvar 1 $varName blocks foreach {index args} [eval gif.IndexBlock blocks [list $index] $args] break if {$args eq {}} {return [lindex $blocks $index]} array set block [lindex $blocks $index] foreach name $args {if {![info exists block($name)]} { if {$name eq {type}} \ then {return -code error "element \"type\" required in all blocks; missing in block #$index"} \ else {return -code error "element \"$name\" not found in block #$index ($block(type))"} } } if {[llength $args] == 1} {return $block([lindex $args 0])} foreach name $args {lappend result $block($name)} return $result } #----------------------------------------------------------------------------- proc ::gifblock::gif.index {varName index args} { #----------------------------------------------------------------------------- upvar 1 $varName blocks return [lindex [eval gif.IndexBlock blocks [list $index] $args] 0] } #----------------------------------------------------------------------------- proc ::gifblock::gif.load {varName filename} { #----------------------------------------------------------------------------- upvar 1 $varName result set result {} set f [open $filename r] fconfigure $f -translation binary set iserror [catch { # ................................................................... Header set sig [encoding convertfrom ascii [read $f 3]] set ver [encoding convertfrom ascii [read $f 3]] if {($sig ne {GIF}) \ || ![string is integer -strict [string range $ver 0 1]] \ || ![string is alpha -strict [string index $ver 2]]} { error "not a valid GIF" } lappend result [list \ type {GIF Header} \ version $ver \ ] # ................................................ Logical Screen Descriptor gif.LoadBlock $f { unsigned width unsigned height packed { 7 iscolormap color.resolution colorres 3 issorted color.table.size size } byte bgcidx aspect aspect } if {$iscolormap} { # GIF Global Color Table lappend result [list \ type {Color Table} \ sorted? $issorted \ colors [gif.LoadColorTable $f $size] \ ] } lappend result [list \ type {Logical Screen Descriptor} \ width $width \ height $height \ {color resolution} $colorres \ {background color index} $bgcidx \ {pixel aspect ratio} $aspect \ ] # .......................................................................... while {true} { gif.LoadBlock $f {byte blocktype} if {$blocktype == 0x21} { gif.LoadBlock $f {byte exttype} set blocktype ext-$exttype } switch -glob -- $blocktype { 44 { # ................................................ Image Descriptor gif.LoadBlock $f { unsigned left unsigned top unsigned width unsigned height packed { 7 iscolormap 6 isinterlaced 5 issorted 43 reserved color.table.size size } } if {$iscolormap} { # GIF Local Color Table lappend result [list \ type {Color Table} \ sorted? $issorted \ colors [gif.LoadColorTable $f $size] \ ] } gif.LoadBlock $f {byte codesize} lappend result [list \ type {Image Descriptor} \ left $left \ top $top \ width $width \ height $height \ interlaced? $isinterlaced \ reserved $reserved \ {lzw minimum code size} $codesize \ data [gif.LoadSubBlocks $f unpack] \ ] } ext-249 { # .................................. Graphic Control Extension gif.LoadBlock $f { byte size packed { 75 reserved 42 method 1 isui 0 istransp } unsigned delay byte transidx byte term } set temp [list \ type {Graphic Control} \ reserved $reserved \ {disposal method} $method \ {user input?} $isui \ {delay time} $delay \ ] if {$istransp} {lappend temp {transparent color index} $transidx} lappend result $temp } ext-254 { # .......................................... Comment Extension lappend result [list \ type Comment \ text [encoding convertfrom ascii [gif.LoadSubBlocks $f unpack]] \ ] } ext-1 { # ......................................... Plain Text Extension gif.LoadBlock $f { byte size unsigned left unsigned top unsigned width unsigned height byte cellwidth byte cellheight byte fgcidx byte bgcidx } lappend result [list \ type {Plain Text} \ left $left \ top $top \ width $width \ height $height \ {cell width} $cellwidth \ {cell height} $cellheight \ {foreground color index} $fgcidx \ {background color index} $bgcidx \ text [encoding convertfrom ascii [gif.LoadSubBlocks $f unpack]] \ ] } ext-255 { # ...................................... Application Extension read $f 1 set id [encoding convertfrom ascii [read $f 8]] gif.LoadBlock $f { byte a0 byte a1 byte a2 } set datablocks [gif.LoadSubBlocks $f leavepacked] lappend result [list \ type Application \ identifier $id \ {authentication code} [list $a0 $a1 $a2] \ datablocks $datablocks \ ] } ext-* { # ....................................... Unknown extension type lappend result [list \ type "Extension $exttype" \ datablocks [gif.LoadSubBlocks $f leavepacked] \ ] } 59 { # ..................................................... GIF Trailer break } default { error {cannot understand block types not listed in the GIF89a specification} } } } } errmsg] close $f if {$iserror} {return -code error $errmsg} return ;# $result } #----------------------------------------------------------------------------- proc ::gifblock::gif.save {varName filename} { #----------------------------------------------------------------------------- upvar 1 $varName blocks set count [llength $blocks] if {$count == 0} return set f [open $filename w] fconfigure $f -translation binary set iserror [catch { # Every block must have a proper type gif.blocknames blocks # ............................................................... GIF Header array set block {type {} version {}} array set block [lindex $blocks 0] if {$block(type) ne {GIF Header}} {error {first block must be "GIF Header"}} if {$block(version) eq {}} { set version 87a set blocknames [gif.blocknames blocks] foreach pattern { {Graphic Control} Comment {Plain Text} Application Extension* } { if {[lsearch -glob $blocknames $pattern] >= 0} { set version 89a break } } set block(version) $version } puts -nonewline $f [encoding convertto ascii GIF] puts -nonewline $f [encoding convertto ascii $block(version)] # .......................................................................... set cntr 0 while {[incr cntr] < $count} { array unset block array set block [lindex $blocks $cntr] switch -glob -- $block(type) { {Color Table} { # .......................................... Color Table gif.ValidateBlock block colors {{{sorted?} 0}} set len [llength $block(colors)] if {($len < 2) || ($len > 256)} { error {color table must have from 2 to 256 entries} } array set colorblock [lindex $blocks $cntr] } {Logical Screen Descriptor} { # .............. Logical Screen Descriptor if {$cntr > (1 +[info exists colorblock])} { error {invalid index for the Logical Screen Descriptor} } gif.ValidateBlock block {width height} { {{color resolution} 4} {{background color index} 0} {{pixel aspect ratio} 0} } set iscolormap [info exists colorblock] if {$iscolormap} \ then { set issorted [expr {!!$colorblock(sorted?)}] set size [gif.CalcColorTableSize [llength $colorblock(colors)]] } \ else { set issorted 0 set size 0 } gif.WriteBlock $f [list \ unsigned $block(width) \ unsigned $block(height) \ packed [list \ 7 $iscolormap \ color.resolution ${block(color resolution)} \ 3 $issorted \ 20 $size \ ] \ byte ${block(background color index)} \ aspect ${block(pixel aspect ratio)} \ ] if {$iscolormap} { gif.WriteColorTable $f $colorblock(colors) $size array unset colorblock } } {Image Descriptor} { # ................................ Image Descriptor gif.ValidateBlock block { width height {lzw minimum code size} data} { {left 0} {top 0} {interlaced? 0} {reserved 0} } set iscolormap [info exists colorblock] if {$iscolormap} \ then { set issorted [expr {!!$colorblock(sorted?)}] set size [gif.CalcColorTableSize [llength $colorblock(colors)]] } \ else { set issorted 0 set size 0 } gif.WriteBlock $f [list \ byte 44 \ unsigned $block(left) \ unsigned $block(top) \ unsigned $block(width) \ unsigned $block(height) \ packed [list \ 7 $iscolormap \ 6 $block(interlaced?) \ 5 $issorted \ 43 $block(reserved) \ 20 $size \ ] \ ] if {$iscolormap} { gif.WriteColorTable $f $colorblock(colors) $size array unset colorblock } gif.WriteBlock $f "byte ${block(lzw minimum code size)}" gif.WriteSubBlocks $f $block(data) pack gif.WriteBlock $f {byte 0} } {Graphic Control} { # ........................ Graphic Control Extension gif.ValidateBlock block {} { { reserved 0} {{disposal method} 0} {{user input?} 0} {{delay time} 0} {{transparent color index} -1} } set istransidx [expr {${block(transparent color index)} >= 0}] if {!$istransidx} {set {block(transparent color index)} 0} gif.WriteBlock $f [list \ byte 0x21 \ byte 249 \ byte 4 \ packed [list \ 75 $block(reserved) \ 42 ${block(disposal method)} \ 1 ${block(user input?)} \ 0 $istransidx \ ] \ unsigned ${block(delay time)} \ byte ${block(transparent color index)} \ byte 0 \ ] } Comment { # .......................................... Comment Extension gif.ValidateBlock block text {} gif.WriteBlock $f [list \ byte 0x21 \ byte 254 \ ] gif.WriteSubBlocks $f [encoding convertto ascii $block(text)] pack gif.WriteBlock $f {byte 0} } {Plain Text} { # .................................. Plain Text Extension gif.ValidateBlock block { left top width height {cell width} {cell height} {foreground color index} {background color index} text } {} gif.WriteBlock $f [list \ byte 0x21 \ byte 1 \ byte 12 \ unsigned $block(left) \ unsigned $block(top) \ unsigned $block(width) \ unsigned $block(height) \ byte ${block(cell width)} \ byte ${block(cell height)} \ byte ${block(foreground color index)} \ byte ${block(background color index)} \ ] gif.WriteSubBlocks $f $block(text) pack gif.WriteBlock $f {byte 0} } Application { # .................................. Application Extension gif.ValidateBlock block {identifier {authentication code}} {{datablocks {}}} gif.WriteBlock $f [list \ byte 0x21 \ byte 255 \ byte 11 \ ] if {[llength ${block(authentication code)}] < 3} { error {application authentication code must be a list of three 8-bit integers} } puts -nonewline $f [encoding convertto ascii $block(identifier)] puts -nonewline $f [binary format ccc ${block(authentication code)}] gif.WriteSubBlocks $f $block(datablocks) prepacked gif.WriteBlock $f {byte 0} } Extension* { # .................................. Unknown extension type gif.ValidateBlock block datablocks {} if {![llength $block(datablocks)]} { error "$block(type) must specify datablocks" } gif.WriteBlock $f [list byte 0x21 byte [lindex $block(type) 1]] gif.WriteSubBlocks $f $block(datablocks) prepacked gif.WriteBlock $f {byte 0} } default { error {cannot understand block types not listed in the GIF89a specification} } } } # .............................................................. GIF Trailer gif.WriteBlock $f {byte 59} } errmsg] close $f if {$iserror} {return -code error $errmsg} return } #----------------------------------------------------------------------------- proc ::gifblock::gif.set {varName index args} { #----------------------------------------------------------------------------- upvar 1 $varName blocks foreach {index args} [eval gif.IndexBlock blocks [list $index] $args] break array set block [lindex $blocks $index] if {([llength $args] % 2) == 1} { unset block([lindex $args end]) set args [lrange $args 0 end-1] } foreach {element value} $args {set block($element) $value} lset blocks $index [array get block] return } #----------------------------------------------------------------------------- proc ::gifblock::gif.CalcColorTableSize size { # gif.save #----------------------------------------------------------------------------- if {($size < 2) || ($size > 256)} { return -code error {color table must have from 2 to 256 entries} } foreach min {2 4 8 16 32 64 128 256} value {0 1 2 3 4 5 6 7} { if {$size <= $min} {return $value} } } #----------------------------------------------------------------------------- proc ::gifblock::gif.IndexBlock {varName index args} { # gif.index,.get,.set #----------------------------------------------------------------------------- upvar 1 $varName blocks if {![info exists blocks]} { return -code error "can't read \"$varName\": no such variable" } set count [llength $blocks] if {![string is integer -strict $index]} { if {![string is integer -strict [lindex $args 0]]} { return -code error {incorrect args: should be "gif.index varName ?type? index"} } set type $index set index [lindex $args 0] set args [lrange $args 1 end] for {set cntr 0} {$cntr < $count} {incr cntr} { array set block [lindex $blocks $cntr] if {$block(type) eq $type} {if {[incr index -1] < 0} break} } if {$block(type) ne $type} {return -1} set index $cntr } if {$index >= $count} {return -1} return [list $index $args] } #----------------------------------------------------------------------------- proc ::gifblock::gif.LoadBlock {f fargs} { # gif.load #----------------------------------------------------------------------------- foreach {format varName} $fargs { switch -exact -- $format { unsigned { binary scan [read $f 2] s val set val [expr {$val & 0xFFFF}] uplevel 1 [list set $varName $val] } byte { binary scan [read $f 1] c val set val [expr {$val & 0xFF}] uplevel 1 [list set $varName $val] } packed { uplevel 1 [list gif.LoadPacked $f $varName] } aspect { binary scan [read $f 1] c val set val [expr {$val & 0xFF}] set n [expr {($val) ? (($val +15) /64.0) : 0}] uplevel 1 [list set $varName $n] } } } } #----------------------------------------------------------------------------- proc ::gifblock::gif.LoadColorTable {f size} { # gif.load #----------------------------------------------------------------------------- set result {} incr size while {[incr size -1]} { gif.LoadBlock $f { byte red byte green byte blue } lappend result [list $red $green $blue] } return $result } #----------------------------------------------------------------------------- proc ::gifblock::gif.LoadPacked {f fargs} { # gif.load #----------------------------------------------------------------------------- binary scan [read $f 1] c data foreach {format varName} $fargs { switch -exact -- $format { 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 { set n [expr (($data >> $format) & 1) ? true : false] } color.resolution { set n [expr {(($data >> 4) & 0x7) +1}] } color.table.size { set n [expr {int( pow( 2, ($data & 0x7) +1 ))}] } default { if {![string is integer -strict $format] || (10 > $format) || ($format > 76)} { return -code error {invalid packed bitfield specification} } set length [expr [string index $format 0] -[string index $format 1] +1] set index [lsearch -exact {2 3 4 5 6 7} $length] if {$index < 0} { return -code error {invalid packed bitfield specification} } set mask [lindex {0x3 0x7 0xF 0x1F 0x3F 0x7F} $index] set n [expr ($data >> [string index $format 1]) & $mask] } } uplevel 1 [list set $varName $n] } } #----------------------------------------------------------------------------- proc ::gifblock::gif.LoadSubBlocks {f mode} { # gif.load #----------------------------------------------------------------------------- # mode := unpack | leavepacked set result {} for {gif.LoadBlock $f {byte size}} {$size} {gif.LoadBlock $f {byte size}} { set data [read $f $size] if {$mode eq {unpack}} \ then { append result $data} \ else {lappend result $data} } return $result } #----------------------------------------------------------------------------- proc ::gifblock::gif.ValidateBlock {varName requireds optionals} { # gif.save #----------------------------------------------------------------------------- upvar 1 $varName block foreach name $requireds { if {![info exists block($name)] || ($block($name) eq {})} { return -code error "$block(type) requires element '$name'" } } foreach name $optionals { set elt [lindex $name 0] set val [lindex $name 1] if {![info exists block($elt)] || ($block($elt) eq {})} { set block($elt) $val } } } #----------------------------------------------------------------------------- proc ::gifblock::gif.WriteBlock {f fargs} { # gif.save #----------------------------------------------------------------------------- foreach {format value} $fargs { switch -exact -- $format { unsigned {puts -nonewline $f [binary format s $value]} byte {puts -nonewline $f [binary format c $value]} packed {gif.WritePacked $f $value} aspect { if {$value != 0} { set value [expr {(int( ($value *64.0) +0.5 ) -15) & 0xFF}] } puts -nonewline $f [binary format c $value] } default {return -code error {invalid field specification}} } } } #----------------------------------------------------------------------------- proc ::gifblock::gif.WriteColorTable {f colors size} { # gif.save #----------------------------------------------------------------------------- set count [llength $colors] for {set cntr 0} {$cntr < $count} {incr cntr} { gif.WriteBlock $f " byte [lindex $colors $cntr 0] byte [lindex $colors $cntr 1] byte [lindex $colors $cntr 2] " } while {$cntr < $size} { gif.WriteBlock $f {byte 0 byte 0 byte 0} incr cntr } } #----------------------------------------------------------------------------- proc ::gifblock::gif.WritePacked {f fargs} { # gif.save #----------------------------------------------------------------------------- set result 0 foreach {format value} $fargs { switch -exact -- $format { 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 { set value [expr {!!$value}] set result [expr {$result | ($value << $format)}] } color.resolution { set result [expr {$result | ((($value -1) & 0x7) << 4)}] } default { if {![string is integer -strict $format] || (10 > $format) || ($format > 76)} { return -code error {invalid packed bitfield specification} } set length [expr [string index $format 0] -[string index $format 1] +1] set index [lsearch -exact {2 3 4 5 6 7} $length] if {$index < 0} { return -code error {invalid packed bitfield specification} } set mask [lindex {0x3 0x7 0xF 0x1F 0x3F 0x7F} $index] set result [expr {$result | (($value & $mask) << [string index $format 1])}] } } } puts -nonewline $f [binary format c $result] } #----------------------------------------------------------------------------- proc ::gifblock::gif.WriteSubBlocks {f data mode} { # gif.save #----------------------------------------------------------------------------- # mode := pack | prepacked # Does NOT write a sub-block terminator # if {$mode eq {pack}} { set length [string length $data] while {$length > 0} { if {$length >= 255} \ then { gif.WriteBlock $f {byte 255} puts -nonewline $f [string range $data 0 254] set data [string range $data 255 end] incr length -255 } \ else { gif.WriteBlock $f "byte $length" puts -nonewline $f $data set length 0 } } return } foreach subblock $data { gif.WriteBlock $f [string length $subblock] puts -nonewline $f $subblock } } #end gifblock.tcl
gifblock.txt
gifblock.txt Manipulate GIF streams in pure Tcl Copyright (c) 2006-2008 Michael Thomas Greer Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////// gifblock documentation ////////////////////////////////////////////////////////////////////////////// A GIF file (or 'stream') is an ordered list of 'blocks'. This library decomposes a GIF file into a list of blocks, composes a list of blocks into a GIF file, and gives a couple of useful functions for inspecting and modifying a list of blocks. Each block listed is stored as a record of key-value pairs (see the tcl proc 'array get' and 'array set' for more information). A sample block: { type {Logical Screen Descriptor} width 800 height 600 {color resolution} 3 {background color index} 255 } The best way to become familiar with the block structure is to play with a few GIFs and dump the structure. For example: # List the blocks, in order, found in 'my_image.gif' gif.load sunset sunset.gif set cntr -1 foreach name [gif.blocknames sunset] {puts "[incr cntr]: $name"} For those of you familiar with the GIF specification, please pay attention to the way the Color Table block is handled -- it is more conveniently coupled to the LSD and Image Descriptor blocks than in the GIF spec. The ORDER of blocks in the list is important. The relative order of elements in any given block is not. Each block must always have a 'type' element naming the type of the block. Block types and data are as follows. ------------------------------------------------------------------------------ type {GIF Header} Required. This must always be the first block in the list. Only one of these may appear in a list. version Optional. One of "87a" or "89a". If not given, gif.save will choose the appropriate version when writing the GIF file. ------------------------------------------------------------------------------ type {Logical Screen Descriptor} Required. Only one of these may appear in a list. It must always be the second or third block in the list, and may only be preceeded by the GIF Header block and an optional Color Table block. width height Required. The size of the GIF image. Unsigned 16-bit integers. color resolution Optional. The number of bits per primary color available to the original image. Defaults to 4. background color index Optional. Unsigned 8-bit integer. Defaults to 0. pixel aspect ratio Optional. The quotient of a pixel's width over its height. Defaults to 0 (meaning that the aspect ratio is not defined). ------------------------------------------------------------------------------ type {Color Table} Optional. A GIF file will generally have one of these, positioned immediately following the GIF Header block and immediately before the Logical Screen Descriptor block (making it the Global Color Table). Color Table blocks can also be located anywhere in the list preceeding an Image Descriptor block, indicating that the image data refers to this Local Color Table and not the Global Color Table (if any). sorted? Optional. Must be true or false (any valid Tcl boolean string will do). If true, indicates that the Color Table lists colors with the most frequently used color first and the least frequently used last. Defaults to false. colors Required. A list of triplets of the form {red green blue} where red, green, and blue are unsigned 8-bit integers. That is, it is a list of colors. The length of the color table may not exceed 256. ------------------------------------------------------------------------------ type {Image Descriptor} Optional. Image data. left top Optional. The location of the upper-left edge of the image in the Logical Screen. The upper-left corner of the Logical Screen is (0, 0). Unsigned 16-bit integers. Default to 0. width height Required. The image dimentions. Unsigned 16-bit integers. interlaced? Optional. Must be true or false (any valid Tcl boolean string will do). If true, the GIF Image Data is interlaced. See the GIF specification for more. Defaults to false. reserved Optional. Unsigned 2-bit integer. Should be 0. lzw minimum code size Required. The GIF LZW Minimum Code Size unsigned 8-bit integer value. data Required. The GIF Variable-Length-Code LZW Compressed binary image data. The {lzw minimum code size} and data are not modified or utilized by this library. ------------------------------------------------------------------------------ type {Graphic Control} Optional. This is everyone's favorite extension block, because it is the one that gives GIFs a transparent color and enables GIF animations. See the GIF 89a specification for more information. This block modifies the way the next 'graphic rendering block' (Image Descriptor block or Plain Text block) it is to be displayed. reserved Optional. Unsigned 3-bit integer. Should be 0. disposal method Optional. Unsigned 3-bit integer. One of: 0 No disposal method specified (default). 1 Leave the graphic block in place after drawing it. 2 After drawing the graphic but before drawing the next, fill the area used with the background color (see the Logical Screen Descriptor block). 3 After drawing the graphic but before drawing the next, restore the area used to its prior state (as if the graphic had never been drawn). 4-7 Undefined. Do not use. user input? Optional. Must be true or false (any valid Tcl boolean string will do). If true, the user must do something (like click a button or press a key) before the decoder displays the next graphic rendering block. Defaults to false. If true and a delay time is given, the decoder continues processing when the user gives input or the delay time times-out --whichever comes first. delay time Optional. Unsigned 16-bit integer. If non-zero, indicates the number of 1/100 seconds to pause before drawing the next graphic rendering block. Defaults to 0. transparent color index Optional. If present, indicates that the specified unsigned 8-bit color index is to be treated as transparent. Defaults to 'not present', i.e. no transparency. ------------------------------------------------------------------------------ type Comment Optional. Non-displayable textual data (stuff like "this image is copyright (c) 2027 Spiff Industries", etc.) text Required. A string containing the textual data. The GIF specification does not give any clear recommendations on what character codes may or may not appear in a GIF Comment string, but in real life it is not uncommon to have newline and carriage return codes embedded. ------------------------------------------------------------------------------ type {Plain Text} Optional. Textual information to display. Be aware that not many GIF decoders properly recognize this block. For example, the famous Irfanview complains that the GIF is invalid when it is in fact properly formed. You should generally use an image block instead. left top Required. The location of the upper-left edge of the image in the Logical Screen. The upper-left corner of the Logical Screen is (0, 0). Unsigned 16-bit integers. width height Required. The text grid dimentions. Unsigned 16-bit integers. Should be a multiple of cell width and cell height, respectively. cell width cell height Required. The cell grid dimentions. Unsigned 8-bit integers. foreground color index background color index Required. Always references the Global Color Table. Also, a Graphic Control block may modify this block, so the specified index might be a transparent color index... text Required. Plain ASCII text in the range 20h..F7h (out of range characters are recommended to be displayed as spaces [ASCII 20h]). The text should be pre-formatted (with spaces) to fit the grid appropriately. For example: +-+-+-+-+-+-+ |W|h|a|t|'|s| +-+-+-+-+-+-+ --> "What's up?" | | |u|p|?| | +-+-+-+-+-+-+ Notice the two spaces between words, so that the 6 by 2 grid is filled with the "up?" centered. +-+-+-+-+-+-+ |W|h|a|t|'|s| +-+-+-+-+-+-+ --> "What'sup?" |u|p|?| | | | +-+-+-+-+-+-+ Notice the lack of spaces between words, so that the text is left-justified in the 6 by 2 grid. ------------------------------------------------------------------------------ type Application Optional. Application-specific data. identifier Required. An 8-character string identifying the data. authentication code Required. A list of three unsigned 8-bit integer values. datablocks Optional. A list of binary application data sub-blocks. ------------------------------------------------------------------------------ type {Extension 12} Optional. Any number (except 1, 249, 254, and 255). This is an unknown extension block. You can specify those other numbers if you like, but they represent the Plain Text, Graphic Control, Comment, and Application extension blocks, which are treated specially. The number used identifies the type of block: 0..127 Graphic Rendering blocks (like the Plain Text and Image) 128..249 Control blocks (like the Graphic Control) 250..255 Special Purpose (like the Comment) See the GIF specification for more information. datablocks Required. A list of binary data sub-blocks. ////////////////////////////////////////////////////////////////////////////// function reference ////////////////////////////////////////////////////////////////////////////// ------------------------------------------------------------------------------ gif.blocknames varName ------------------------------------------------------------------------------ Return an ordered list of block types belonging to a named list of blocks. Most GIF files will look something like this: GIF Header Color Table Logical Screen Descriptor Graphic Control Image Descriptor See the top of this file for an example of use. ------------------------------------------------------------------------------ gif.get varName ?type? index ?element ...? ------------------------------------------------------------------------------ Find an indexed block and return one or more elements. arguments varName The name of the variable holding the list of blocks. type The type of block to index. If omitted, all block types are indexed. index Find the (n-1)th block (of the specified type). element ... One or more element names to find. returns The value of the specified element. Or A list of the values of the specified elements, in same order. Or The entire block, if no elements are specified. examples What is the width and height of the 4th image block? gif.get blocks {Image Descriptor} 3 width height --> a list, e.g.: 320 240 What is the type of the 3rd block (in a GIF with a Global Color Table)? gif.get blocks 2 type --> Logical Screen Descriptor Get the LSD screen dimentions (GIF image size): gif.get blocks {Logical Screen Descriptor} 0 width height --> a list, e.g.: 800 600 Get the entire 4th block, regardless of type: gif.get blocks 3 Use it as an array: array set block [gif.get blocks 3] ------------------------------------------------------------------------------ gif.index varName ?type? index ------------------------------------------------------------------------------ Return the absolute index of a GIF block. For example, given the blocks: 0: GIF Header 1: Color Table 2: Logical Screen Descriptor 3: Graphic Control 4: Image Descriptor 5: Graphic Control 6: Image Descriptor
- then
- gif.index blocks {Logical Screen Descriptor} 0
- returns
- 2
- and
- gif.index blocks {Image Descriptor} 1
- returns
- 6
See also gif.get ------------------------------------------------------------------------------ gif.save varName filename ------------------------------------------------------------------------------ Compile the blocks listed with the named variable and write them to a GIF file named as given by filename. Always overwrites. Be aware that gif.load blocks one.gif gif.save blocks two.gif may produce a GIF file (two.gif) that is binary distinct from the original GIF file (one.gif). This is because image data (and some other sub-blocks) are unpacked by gif.load and re-packed by gif.save. Gif.make always packs sub-blocks optimally, whereas some encoders may not. The resultant GIF file (two.gif) is valid and produces identically to the source GIF file (one.gif). All this means is that if you unpack and repack a GIF file using this library you might end-up with a smaller GIF than you began with. ------------------------------------------------------------------------------ gif.load varName filename ------------------------------------------------------------------------------ Read the named GIF file and set the named variable to the list of blocks. See the top of this file for an example of use. ------------------------------------------------------------------------------ gif.set varName ?type? index element ?value? ... ------------------------------------------------------------------------------ Index an element of a block the same way as gif.get and set one or more element values. If no value is specified, the named element is removed. end gifblock.txt
Oh yeah, the code has been fairly carefully tested, but that doesn't mean that there aren't any odd bugs lying underneath. Should you find something wrong go ahead and fix it here. Thanks! --Duoas
Duoas 2008-10-09 This was originally licensed under the LGPL, but I have since become disenamoured of the GPL in general. The current Boost license is compatible with the Tcl/BSD/MIT licenses and is completely non-viral.