Index: doc/photo.n ================================================================== --- doc/photo.n +++ doc/photo.n @@ -740,11 +740,11 @@ This dictionary is not relevant to the bitmap representation of the image, but may contain additional information like resolution or comments. Image format drivers may output metadata when image data is parsed, or may use metadata to be included in image files or formats. -.SS "METADATA KEYS" +.SS "METADATA KEYS (MULTIPLE FORMATS)" .PP Each image format driver supports an individual set of metadata dictionary keys. Predefined keys are: .TP DPI @@ -762,10 +762,35 @@ Image text comment. Supported by formats \fBgif\fR and \fBpng\fR. .PP It is valid to set any key in the metadata dict. A format driver will ignore keys it does not handle. +.SS "METADATA KEYS FOR ANIMATED GIF INFORMATION" +.PP +The following metadata keys are reported when reading a \fBgif\fR format file. +They are typically used in conjunction with the \fI-index\fR option of an +animated \fBgif\fR file to properly display the subimage sequence. +The options are linked to each subimage selected by \fI-index\fR. +.TP +\fBdelay time\fR \fItime\fR +. +Update delay time in 10ms unit. This key is only present, if delay time is not 0. +.TP +\fBdisposal method\fR \fImethod\fR +. +Disposal method of the preceeding image, if given for the current image. +Possible values are: \fIdo not dispose\fR, \fIrestore to background color\fR, \fIrestore to previous\fR. +.TP +\fBuser interaction\fR \fIbool\fR +. +The key is present with a value of 1, if user interaction is specified. +Otherwise, the key is not present. +.TP +\fBupdate region\fR \fIX0\fR, \fIY0\fR, \fIwidth\fR, \fIheight\fR +. +Update region of the current subimage, if subimage has not the same size as +the full image. The pixel outside of this box are all fully transparent. .PP .VE 8.7 .SH CREDITS .PP The photo image type was designed and implemented by Paul Mackerras, Index: generic/tkImgGIF.c ================================================================== --- generic/tkImgGIF.c +++ generic/tkImgGIF.c @@ -44,10 +44,22 @@ #define GIF_PAD (GIF_SPECIAL+1) #define GIF_SPACE (GIF_SPECIAL+2) #define GIF_BAD (GIF_SPECIAL+3) #define GIF_DONE (GIF_SPECIAL+4) +/* + * structure to hold the data of a Graphic Control Extension block. + */ + +typedef struct { + int blockPresent; /* if 1, the block was read and is in scope */ + int transparent; /* Transparency index */ + int delayTime; /* update delay time in 10ms */ + int disposalMethod; /* disposal method 0-3 */ + int userInteraction; /* user interaction 0/1 */ +} GIFGraphicControlExtensionBlock; + /* * structure to "mimic" FILE for Mread, so we can look like fread. The decoder * state keeps track of which byte we are about to read, or EOF. */ @@ -176,11 +188,13 @@ static int ReadOneByte(Tcl_Interp *interp, GIFImageConfig *gifConfPtr, Tcl_Channel chan); static int DoExtension(GIFImageConfig *gifConfPtr, Tcl_Channel chan, int label, unsigned char *buffer, - int *transparent, Tcl_Obj *metadataOutObj); + GIFGraphicControlExtensionBlock + *gifGraphicControlExtensionBlock, + Tcl_Obj *metadataOutObj); static int GetCode(Tcl_Channel chan, int code_size, int flag, GIFImageConfig *gifConfPtr); static int GetDataBlock(GIFImageConfig *gifConfPtr, Tcl_Channel chan, unsigned char *buf); static int ReadColorMap(GIFImageConfig *gifConfPtr, @@ -414,16 +428,17 @@ unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; - int transparent = -1; + GIFGraphicControlExtensionBlock gifGraphicControlExtensionBlock; static const char *const optionStrings[] = { "-index", NULL }; GIFImageConfig gifConf, *gifConfPtr = &gifConf; + gifGraphicControlExtensionBlock.blockPresent = 0; /* * Decode the magic used to convey when we're sourcing data from a string * source and not a file. */ @@ -545,11 +560,12 @@ if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadataOutObj) + gifConfPtr->workingBuffer, &gifGraphicControlExtensionBlock, + metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", NULL); @@ -637,11 +653,11 @@ /* * This extension starts a new scope, so Graphic control Extension * data should be cleared */ - transparent = -1; + gifGraphicControlExtensionBlock.blockPresent = 0; continue; } break; } @@ -686,11 +702,14 @@ height = imageHeight; } if ((width > 0) && (height > 0)) { Tk_PhotoImageBlock block; - + int transparent = -1; + if (gifGraphicControlExtensionBlock.blockPresent) { + transparent = gifGraphicControlExtensionBlock.transparent; + } /* * Read the data and put it into the photo buffer for display by the * general image machinery. */ @@ -725,10 +744,86 @@ ckfree(block.pixelPtr); goto error; } ckfree(block.pixelPtr); } + + /* + * Update the metadata dictionary with current image data + */ + + if (NULL != metadataOutObj) { + + /* + * Save the update box, if not the whole image + */ + + if ( width != fileWidth || height != fileHeight) { + Tcl_Obj *itemList[4]; + itemList[0] = Tcl_NewIntObj(destX); + itemList[1] = Tcl_NewIntObj(destY); + itemList[2] = Tcl_NewIntObj(width); + itemList[3] = Tcl_NewIntObj(height); + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("update region",-1), + Tcl_NewListObj(4, itemList) )) { + result = TCL_ERROR; + goto error; + } + } + + /* + * Copy the Graphic Control Extension Block data to the metadata + * dictionary + */ + + if (gifGraphicControlExtensionBlock.blockPresent) { + if ( gifGraphicControlExtensionBlock.delayTime != 0) { + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("delay time",-1), + Tcl_NewIntObj(gifGraphicControlExtensionBlock.delayTime) + )) { + result = TCL_ERROR; + goto error; + } + } + switch ( gifGraphicControlExtensionBlock.disposalMethod ) { + case 1: /* Do not dispose */ + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("disposal method",-1), + Tcl_NewStringObj("do not dispose",-1))) { + result = TCL_ERROR; + goto error; + } + break; + case 2: /* Restore to background color */ + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("disposal method",-1), + Tcl_NewStringObj("restore to background color",-1))) { + result = TCL_ERROR; + goto error; + } + break; + case 3: /* Restore to previous */ + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("disposal method",-1), + Tcl_NewStringObj("restore to previous",-1))) { + result = TCL_ERROR; + goto error; + } + break; + } + if ( gifGraphicControlExtensionBlock.userInteraction != 0) { + if ( TCL_OK != Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("user interaction",-1), + Tcl_NewBooleanObj(1))) { + result = TCL_ERROR; + goto error; + } + } + } + } /* * We've successfully read the GIF frame (or there was nothing to read, * which suits as well). We're done. */ @@ -748,11 +843,12 @@ if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadataOutObj) + gifConfPtr->workingBuffer, &gifGraphicControlExtensionBlock, + metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", NULL); @@ -1045,11 +1141,12 @@ * Results: * -1 to trigger an extension read error * >= 0 ok * * Side effects: -* The transparent color is set if present in current extensions +* The gifGraphicControlExtensionBlock is set if present in current +* extensions * The data of the following extensions are saved to the metadata dict: * - Application extension * - Comment extension in key "comment" * Plain text extensions are currently ignored. * @@ -1060,11 +1157,11 @@ DoExtension( GIFImageConfig *gifConfPtr, Tcl_Channel chan, int label, unsigned char *buf, /* defined as 280 byte working buffer */ - int *transparent, + GIFGraphicControlExtensionBlock *gifGraphicControlExtensionBlock, Tcl_Obj *metadataOutObj) { int count; /* Prepare extension name * Maximum string size: "comment" + Code(3) + trailing zero @@ -1076,20 +1173,32 @@ case 0x01: /* Plain Text Extension */ /* * This extension starts a new scope, so Graphic control Extension * data should be cleared */ - *transparent = -1; + gifGraphicControlExtensionBlock->blockPresent = 0; /* this extension is ignored, skip below */ break; case 0xf9: /* Graphic Control Extension */ count = GetDataBlock(gifConfPtr, chan, buf); if (count < 0) { return -1; } - if ((buf[0] & 0x1) != 0) { - *transparent = buf[3]; + gifGraphicControlExtensionBlock->blockPresent=1; + /* save disposal method */ + gifGraphicControlExtensionBlock->disposalMethod + = ((buf[0] & 0x1C) >> 2); + /* save disposal method */ + gifGraphicControlExtensionBlock->userInteraction = ((buf[0] & 2) >> 1); + /* save delay time */ + gifGraphicControlExtensionBlock->delayTime + = LM_to_uint(buf[1], buf[2]); + /* save transparent index if given */ + if ((buf[0] & 0x1) == 0) { + gifGraphicControlExtensionBlock->transparent = -1; + } else { + gifGraphicControlExtensionBlock->transparent = buf[3]; } break; case 0xfe: /* Comment Extension */ strcpy(extensionStreamName,"comment"); /* copy the extension data below */ Index: tests/imgPhoto.test ================================================================== --- tests/imgPhoto.test +++ tests/imgPhoto.test @@ -2511,10 +2511,151 @@ } -cleanup { catch {image delete gif1} file delete $path } -result {0} +test imgPhoto-23.21 {GIF delay time metadata} -setup { + set data $::gifstart + # Graphic control extension: 10 1/100s delay time + append data "\x21\xf9\x04\x00\x0a\x00\x00\x00" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{delay time} 10} + +test imgPhoto-23.22 {GIF disposal method "do not dispose" metadata} -setup { + set data $::gifstart + # Graphic control extension: disposdal method: + append data "\x21\xf9\x04\x04\x00\x00\x00\x00" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{disposal method} {do not dispose}} + +test imgPhoto-33.23 {GIF disposal method "restore to background color" metadata} -setup { + set data $::gifstart + # Graphic control extension: disposdal method: + append data "\x21\xf9\x04\x08\x00\x00\x00\x00" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{disposal method} {restore to background color}} + +test imgPhoto-43.24 {GIF disposal method "restore to previous" metadata} -setup { + set data $::gifstart + # Graphic control extension: disposdal method: + append data "\x21\xf9\x04\x0C\x00\x00\x00\x00" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{disposal method} {restore to previous}} + +test imgPhoto-53.25 {GIF user input flag metadata} -setup { + set data $::gifstart + # Graphic control extension: disposdal method: + append data "\x21\xf9\x04\x02\x00\x00\x00\x00" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{user interaction} 1} + +test imgPhoto-63.26 {GIF update region metadata} -setup { + # size 32x32, global color table size: 8 + set data "GIF89a\x20\x00\x20\x00\xc2\x07\x00" + # color table + append data "\x00\x00\x00\x33\x33\xff\xff\x33\x33\xff\x33\xff\x33\xff\x33\x33\xff\xff\xff\xff\x33\xff\xff\xff" + # Trailer + # As this is 16x16, we will get an update region setting + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{update region} {0 0 16 16}} + +test imgPhoto-63.27 {GIF multiple options metadata} -setup { + # size 32x32, global color table size: 8 + set data "GIF89a\x20\x00\x20\x00\xc2\x07\x00" + # color table + append data "\x00\x00\x00\x33\x33\xff\xff\x33\x33\xff\x33\xff\x33\xff\x33\x33\xff\xff\xff\xff\x33\xff\xff\xff" + # Graphic control extension: do not dispose, user interaction, transparent color, delay time 10 + append data "\x21\xf9\x04\x07\x0a\x00\x01\x00" + # Image data and trailer + # As this is 16x16, we will get an update region setting + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{update region} {0 0 16 16} {delay time} 10 {disposal method} {do not dispose} {user interaction} 1} + +test imgPhoto-63.27 {GIF multiple options metadata in -index 0} -setup { + # size 32x32, global color table size: 8 + set data "GIF89a\x20\x00\x20\x00\xc2\x07\x00" + # color table + append data "\x00\x00\x00\x33\x33\xff\xff\x33\x33\xff\x33\xff\x33\xff\x33\x33\xff\xff\xff\xff\x33\xff\xff\xff" + # Graphic control extension: do not dispose, user interaction, transparent color, delay time 4096 + append data "\x21\xf9\x04\x07\x00\x10\x01\x00" + # Image data + # As this is 16x16, we will get an update region setting + append data $::gifdata + # Graphic control extension: restore to background, delay time 1 + append data "\x21\xf9\x04\x08\x01\x00\x02\x00" + # Image data and trailer + # As this is 16x16, we will get an update region setting + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data -format "gif -index 0" + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{update region} {0 0 16 16} {delay time} 4096 {disposal method} {do not dispose} {user interaction} 1} + +test imgPhoto-63.28 {GIF multiple options metadata in -index 1} -setup { + # size 32x32, global color table size: 8 + set data "GIF89a\x20\x00\x20\x00\xc2\x07\x00" + # color table + append data "\x00\x00\x00\x33\x33\xff\xff\x33\x33\xff\x33\xff\x33\xff\x33\x33\xff\xff\xff\xff\x33\xff\xff\xff" + # Graphic control extension: restore to background, delay time 1 + append data "\x21\xf9\x04\x08\x01\x00\x02\x00" + # Image data + # As this is 16x16, we will get an update region setting + append data $::gifdata + # Graphic control extension: do not dispose, user interaction, transparent color, delay time 4096 + append data "\x21\xf9\x04\x07\x00\x10\x01\x00" + # Image data and trailer + # As this is 16x16, we will get an update region setting + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data -format "gif -index 1" + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {{update region} {0 0 16 16} {delay time} 4096 {disposal method} {do not dispose} {user interaction} 1} + unset -nocomplain gifstart gifdata gifend catch {rename foreachPixel {}} catch {rename checkImgTrans {}}