From d39fc962b516427bb903ce22ee8ea44a08ba2f22 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 11 Dec 2018 10:21:08 +0000 Subject: TIP529 image metadata: create -metadata option --- generic/tkImgPhoto.c | 103 ++++++++++++++++++++++++++++++++++++++++++++------- generic/tkImgPhoto.h | 2 + 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index e1d54dd..a42c129 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -39,6 +39,7 @@ struct SubcommandOptions { XColor *background; /* Value specified for -background option. */ int compositingRule; /* Value specified for -compositingrule * option. */ + Tcl_Obj *metadata; /* Value specified for -metadata option. */ }; /* @@ -59,6 +60,7 @@ struct SubcommandOptions { * OPT_TO: Set if -to option allowed/specified. * OPT_WITHALPHA: Set if -withalpha option allowed/specified. * OPT_ZOOM: Set if -zoom option allowed/specified. + * OPT_METADATA: Set if -metadata option allowed/specified. */ #define OPT_ALPHA 1 @@ -72,6 +74,7 @@ struct SubcommandOptions { #define OPT_TO 0x100 #define OPT_WITHALPHA 0x200 #define OPT_ZOOM 0x400 +#define OPT_METADATA 0x800 /* * List of option names. The order here must match the order of declarations @@ -90,6 +93,7 @@ static const char *const optionNames[] = { "-to", "-withalpha", "-zoom", + "-metadata", NULL }; @@ -457,7 +461,11 @@ ImgPhotoCmd( if (masterPtr->format) { Tcl_SetObjResult(interp, masterPtr->format); } - } else { + } else if (strncmp(arg, "-metadata", length) == 0) { + if (masterPtr->metadata) { + Tcl_SetObjResult(interp, masterPtr->metadata); + } + } else { Tk_ConfigureValue(interp, Tk_MainWindow(interp), configSpecs, (char *) masterPtr, Tcl_GetString(objv[2]), 0); } @@ -492,7 +500,14 @@ ImgPhotoCmd( Tcl_AppendStringsToObj(subobj, " {}", NULL); } Tcl_ListObjAppendElement(interp, obj, subobj); - Tcl_ListObjAppendList(interp, obj, Tcl_GetObjResult(interp)); + subobj = Tcl_NewStringObj("-metadata {} {} {}", 16); + if (masterPtr->metadata) { + Tcl_ListObjAppendElement(NULL, subobj, masterPtr->metadata); + } else { + Tcl_AppendStringsToObj(subobj, " {}", NULL); + } + Tcl_ListObjAppendElement(interp, obj, subobj); + Tcl_ListObjAppendList(interp, obj, Tcl_GetObjResult(interp)); Tcl_SetObjResult(interp, obj); return TCL_OK; @@ -526,7 +541,22 @@ ImgPhotoCmd( Tcl_AppendResult(interp, " {}", NULL); } return TCL_OK; - } else { + } else if (length > 1 && + !strncmp(arg, "-metadata", length)) { + Tcl_AppendResult(interp, "-metadata {} {} {}", NULL); + if (masterPtr->metadata) { + /* + * TODO: Modifying result is bad! + */ + + Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp), + masterPtr->metadata); + } + else { + Tcl_AppendResult(interp, " {}", NULL); + } + return TCL_OK; + } else { return Tk_ConfigureInfo(interp, Tk_MainWindow(interp), configSpecs, (char *) masterPtr, arg, 0); } @@ -683,7 +713,8 @@ ImgPhotoCmd( options.fromX = 0; options.fromY = 0; if (ParseSubcommandOptions(&options, interp, - OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND, + OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND + | OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -868,7 +899,8 @@ ImgPhotoCmd( memset(&options, 0, sizeof(options)); options.name = NULL; options.format = NULL; - if (ParseSubcommandOptions(&options, interp, OPT_TO|OPT_FORMAT, + if (ParseSubcommandOptions(&options, interp, + OPT_TO|OPT_FORMAT|OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -933,7 +965,7 @@ ImgPhotoCmd( options.name = NULL; options.format = NULL; if (ParseSubcommandOptions(&options, interp, - OPT_FORMAT | OPT_FROM | OPT_TO | OPT_SHRINK, + OPT_FORMAT | OPT_FROM | OPT_TO | OPT_SHRINK | OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -1282,7 +1314,8 @@ ImgPhotoCmd( options.name = NULL; options.format = NULL; if (ParseSubcommandOptions(&options, interp, - OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND, + OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND + | OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -1441,8 +1474,8 @@ GetExtension( * * This function is invoked to process one of the options which may be * specified for the photo image subcommands, namely, -from, -to, -zoom, - * -subsample, -format, -shrink, -compositingrule, -alpha, -boolean and - * -withalpha. + * -subsample, -format, -shrink, -compositingrule, -alpha, -boolean, + * -withalpha and -metadata. * Parsing starts at the index in *optIndexPtr and stops at the end of * objv[] or at the first value that does not belong to an option. * @@ -1560,7 +1593,18 @@ ParseSubcommandOptions( } *optIndexPtr = ++index; optPtr->format = objv[index]; - } else if (bit == OPT_COMPOSITE) { + } else if (bit == OPT_METADATA) { + /* + * The -metadata option takes a single dict value. Note that + * parsing this is outside the scope of this function. + */ + + if (index + 1 >= objc) { + goto oneValueRequired; + } + *optIndexPtr = ++index; + optPtr->metadata = objv[index]; + } else if (bit == OPT_COMPOSITE) { /* * The -compositingrule option takes a single value from a * well-known set. @@ -1756,7 +1800,7 @@ ImgPhotoConfigureMaster( { PhotoInstance *instancePtr; const char *oldFileString, *oldPaletteString; - Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL; + Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL, *metadata = NULL; Tcl_Obj *tempdata, *tempformat; size_t length; int i, j, result, imageWidth, imageHeight, oldformat; @@ -1795,7 +1839,21 @@ ImgPhotoConfigureMaster( "MISSING_VALUE", NULL); return TCL_ERROR; } - } + } else if ((args[j][1] == 'm') && + !strncmp(args[j], "-metadata", length)) { + if (++i < objc) { + metadata = objv[i]; + j--; + } + else { + ckfree(args); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "value for \"-metadata\" missing", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", + "MISSING_VALUE", NULL); + return TCL_ERROR; + } + } } } @@ -1834,7 +1892,7 @@ ImgPhotoConfigureMaster( ckfree(args); /* - * Regard the empty string for -file, -data or -format as the null value. + * Regard the empty string for -file, -data, -format or -metadata as the null value. */ if ((masterPtr->fileString != NULL) && (masterPtr->fileString[0] == 0)) { @@ -1876,6 +1934,25 @@ ImgPhotoConfigureMaster( } masterPtr->format = format; } + if (metadata) { + /* + * Stringify to ignore -metadata "". It may come in as a list or other + * object. + */ + + /* HaO: ToDo: value is a dict, not a string */ + (void)Tcl_GetString(metadata); + if (metadata->length) { + Tcl_IncrRefCount(metadata); + } + else { + metadata = NULL; + } + if (masterPtr->metadata) { + Tcl_DecrRefCount(masterPtr->metadata); + } + masterPtr->metadata = metadata; + } /* * Set the image to the user-requested size, if any, and make sure storage * is correctly allocated for this image. diff --git a/generic/tkImgPhoto.h b/generic/tkImgPhoto.h index a84ebb3..8538bfe 100644 --- a/generic/tkImgPhoto.h +++ b/generic/tkImgPhoto.h @@ -165,6 +165,8 @@ struct PhotoMaster { Tcl_Obj *dataString; /* Object to use as contents of image. */ Tcl_Obj *format; /* User-specified format of data in image file * or string value. */ + Tcl_Obj *metadata; /* User-specified metadata dict or read from + * image file */ unsigned char *pix32; /* Local storage for 32-bit image. */ int ditherX, ditherY; /* Location of first incorrectly dithered * pixel in image. */ -- cgit v0.12 From b66ce2e9ac5df781a14e9b3475a08966acb50340 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 11 Dec 2018 10:35:53 +0000 Subject: Corrected indents and formatting --- generic/tkImgPhoto.c | 150 +++++++++++++++++++++++++-------------------------- generic/tkImgPhoto.h | 2 +- 2 files changed, 75 insertions(+), 77 deletions(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index a42c129..7d3c3d9 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -461,11 +461,11 @@ ImgPhotoCmd( if (masterPtr->format) { Tcl_SetObjResult(interp, masterPtr->format); } - } else if (strncmp(arg, "-metadata", length) == 0) { - if (masterPtr->metadata) { - Tcl_SetObjResult(interp, masterPtr->metadata); - } - } else { + } else if (strncmp(arg, "-metadata", length) == 0) { + if (masterPtr->metadata) { + Tcl_SetObjResult(interp, masterPtr->metadata); + } + } else { Tk_ConfigureValue(interp, Tk_MainWindow(interp), configSpecs, (char *) masterPtr, Tcl_GetString(objv[2]), 0); } @@ -500,14 +500,14 @@ ImgPhotoCmd( Tcl_AppendStringsToObj(subobj, " {}", NULL); } Tcl_ListObjAppendElement(interp, obj, subobj); - subobj = Tcl_NewStringObj("-metadata {} {} {}", 16); - if (masterPtr->metadata) { - Tcl_ListObjAppendElement(NULL, subobj, masterPtr->metadata); - } else { - Tcl_AppendStringsToObj(subobj, " {}", NULL); - } - Tcl_ListObjAppendElement(interp, obj, subobj); - Tcl_ListObjAppendList(interp, obj, Tcl_GetObjResult(interp)); + subobj = Tcl_NewStringObj("-metadata {} {} {}", 16); + if (masterPtr->metadata) { + Tcl_ListObjAppendElement(NULL, subobj, masterPtr->metadata); + } else { + Tcl_AppendStringsToObj(subobj, " {}", NULL); + } + Tcl_ListObjAppendElement(interp, obj, subobj); + Tcl_ListObjAppendList(interp, obj, Tcl_GetObjResult(interp)); Tcl_SetObjResult(interp, obj); return TCL_OK; @@ -541,22 +541,21 @@ ImgPhotoCmd( Tcl_AppendResult(interp, " {}", NULL); } return TCL_OK; - } else if (length > 1 && - !strncmp(arg, "-metadata", length)) { - Tcl_AppendResult(interp, "-metadata {} {} {}", NULL); - if (masterPtr->metadata) { - /* - * TODO: Modifying result is bad! - */ - - Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp), - masterPtr->metadata); - } - else { - Tcl_AppendResult(interp, " {}", NULL); - } - return TCL_OK; - } else { + } else if (length > 1 && + !strncmp(arg, "-metadata", length)) { + Tcl_AppendResult(interp, "-metadata {} {} {}", NULL); + if (masterPtr->metadata) { + /* + * TODO: Modifying result is bad! + */ + + Tcl_ListObjAppendElement(NULL, Tcl_GetObjResult(interp), + masterPtr->metadata); + } else { + Tcl_AppendResult(interp, " {}", NULL); + } + return TCL_OK; + } else { return Tk_ConfigureInfo(interp, Tk_MainWindow(interp), configSpecs, (char *) masterPtr, arg, 0); } @@ -714,7 +713,7 @@ ImgPhotoCmd( options.fromY = 0; if (ParseSubcommandOptions(&options, interp, OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND - | OPT_METADATA, + | OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -900,7 +899,7 @@ ImgPhotoCmd( options.name = NULL; options.format = NULL; if (ParseSubcommandOptions(&options, interp, - OPT_TO|OPT_FORMAT|OPT_METADATA, + OPT_TO|OPT_FORMAT|OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -1315,7 +1314,7 @@ ImgPhotoCmd( options.format = NULL; if (ParseSubcommandOptions(&options, interp, OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND - | OPT_METADATA, + | OPT_METADATA, &index, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -1593,18 +1592,18 @@ ParseSubcommandOptions( } *optIndexPtr = ++index; optPtr->format = objv[index]; - } else if (bit == OPT_METADATA) { - /* - * The -metadata option takes a single dict value. Note that - * parsing this is outside the scope of this function. - */ - - if (index + 1 >= objc) { - goto oneValueRequired; - } - *optIndexPtr = ++index; - optPtr->metadata = objv[index]; - } else if (bit == OPT_COMPOSITE) { + } else if (bit == OPT_METADATA) { + /* + * The -metadata option takes a single dict value. Note that + * parsing this is outside the scope of this function. + */ + + if (index + 1 >= objc) { + goto oneValueRequired; + } + *optIndexPtr = ++index; + optPtr->metadata = objv[index]; + } else if (bit == OPT_COMPOSITE) { /* * The -compositingrule option takes a single value from a * well-known set. @@ -1839,21 +1838,20 @@ ImgPhotoConfigureMaster( "MISSING_VALUE", NULL); return TCL_ERROR; } - } else if ((args[j][1] == 'm') && - !strncmp(args[j], "-metadata", length)) { - if (++i < objc) { - metadata = objv[i]; - j--; - } - else { - ckfree(args); - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "value for \"-metadata\" missing", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", - "MISSING_VALUE", NULL); - return TCL_ERROR; - } - } + } else if ((args[j][1] == 'm') && + !strncmp(args[j], "-metadata", length)) { + if (++i < objc) { + metadata = objv[i]; + j--; + } else { + ckfree(args); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "value for \"-metadata\" missing", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", + "MISSING_VALUE", NULL); + return TCL_ERROR; + } + } } } @@ -1935,23 +1933,23 @@ ImgPhotoConfigureMaster( masterPtr->format = format; } if (metadata) { - /* - * Stringify to ignore -metadata "". It may come in as a list or other - * object. - */ - - /* HaO: ToDo: value is a dict, not a string */ - (void)Tcl_GetString(metadata); - if (metadata->length) { - Tcl_IncrRefCount(metadata); - } - else { - metadata = NULL; - } - if (masterPtr->metadata) { - Tcl_DecrRefCount(masterPtr->metadata); - } - masterPtr->metadata = metadata; + /* + * Stringify to ignore -metadata "". It may come in as a list or other + * object. + */ + + /* HaO: ToDo: value is a dict, not a string */ + (void)Tcl_GetString(metadata); + if (metadata->length) { + Tcl_IncrRefCount(metadata); + } + else { + metadata = NULL; + } + if (masterPtr->metadata) { + Tcl_DecrRefCount(masterPtr->metadata); + } + masterPtr->metadata = metadata; } /* * Set the image to the user-requested size, if any, and make sure storage diff --git a/generic/tkImgPhoto.h b/generic/tkImgPhoto.h index 8538bfe..2554cf8 100644 --- a/generic/tkImgPhoto.h +++ b/generic/tkImgPhoto.h @@ -166,7 +166,7 @@ struct PhotoMaster { Tcl_Obj *format; /* User-specified format of data in image file * or string value. */ Tcl_Obj *metadata; /* User-specified metadata dict or read from - * image file */ + * image file */ unsigned char *pix32; /* Local storage for 32-bit image. */ int ditherX, ditherY; /* Location of first incorrectly dithered * pixel in image. */ -- cgit v0.12 From 15b1bfd5ec8b414572582b32de550799bae3e803 Mon Sep 17 00:00:00 2001 From: oehhar Date: Fri, 14 Dec 2018 14:09:43 +0000 Subject: Implemented metadata output of gif comment - crashes test image-15.1 - megadata write with Tk_PhotoGetMetadata does not work, as the photo handle is not available within the write function. --- generic/tk.decls | 8 +++ generic/tkDecls.h | 12 +++- generic/tkImgGIF.c | 182 +++++++++++++++++++++++++++++++++++++++++++-------- generic/tkImgPhoto.c | 66 +++++++++++++++++++ generic/tkStubInit.c | 2 + 5 files changed, 241 insertions(+), 29 deletions(-) diff --git a/generic/tk.decls b/generic/tk.decls index 9619416..9ab13b2 100644 --- a/generic/tk.decls +++ b/generic/tk.decls @@ -1068,6 +1068,14 @@ declare 272 { declare 273 { void Tk_CreateOldPhotoImageFormat(const Tk_PhotoImageFormat *formatPtr) } +# New in Tk8.7 +declare 274 { + Tcl_Obj *Tk_PhotoGetMetadata(Tk_PhotoHandle handle); +} +declare 275 { + void Tk_PhotoSetMetadata(Tk_PhotoHandle handle,Tcl_Obj *metadata); +} + # Define the platform specific public Tk interface. These functions are # only available on the designated platform. diff --git a/generic/tkDecls.h b/generic/tkDecls.h index 0d52f2f..6edcdaa 100644 --- a/generic/tkDecls.h +++ b/generic/tkDecls.h @@ -878,7 +878,11 @@ EXTERN void Tk_CreateOldImageType(const Tk_ImageType *typePtr); /* 273 */ EXTERN void Tk_CreateOldPhotoImageFormat( const Tk_PhotoImageFormat *formatPtr); - +/* 274 */ +EXTERN Tcl_Obj *Tk_PhotoGetMetadata(Tk_PhotoHandle handle); +/* 275 */ +EXTERN void Tk_PhotoSetMetadata(Tk_PhotoHandle handle,Tcl_Obj *metadata); + typedef struct { const struct TkPlatStubs *tkPlatStubs; const struct TkIntStubs *tkIntStubs; @@ -1164,6 +1168,8 @@ typedef struct TkStubs { Tcl_Interp * (*tk_Interp) (Tk_Window tkwin); /* 271 */ void (*tk_CreateOldImageType) (const Tk_ImageType *typePtr); /* 272 */ void (*tk_CreateOldPhotoImageFormat) (const Tk_PhotoImageFormat *formatPtr); /* 273 */ + Tcl_Obj *(*tk_PhotoGetMetadata) (Tk_PhotoHandle handle); /* 274 */ + void (*tk_PhotoSetMetadata) (Tk_PhotoHandle handle,Tcl_Obj *metadata); /* 275 */ } TkStubs; extern const TkStubs *tkStubsPtr; @@ -1724,6 +1730,10 @@ extern const TkStubs *tkStubsPtr; (tkStubsPtr->tk_CreateOldImageType) /* 272 */ #define Tk_CreateOldPhotoImageFormat \ (tkStubsPtr->tk_CreateOldPhotoImageFormat) /* 273 */ +#define Tk_PhotoGetMetadata \ + (tkStubsPtr->tk_PhotoGetMetadata) /* 274 */ +#define Tk_PhotoSetMetadata \ + (tkStubsPtr->tk_PhotoSetMetadata) /* 275 */ #endif /* defined(USE_TK_STUBS) */ diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index aacde56..c3a0321 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -165,9 +165,11 @@ Tk_PhotoImageFormat tkImgFmtGIF = { * Prototypes for local functions defined in this file: */ +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); + int *transparent, Tcl_Obj *metadata); static int GetCode(Tcl_Channel chan, int code_size, int flag, GIFImageConfig *gifConfPtr); static int GetDataBlock(GIFImageConfig *gifConfPtr, @@ -399,6 +401,8 @@ FileReadGIF( unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; + Tcl_Obj *metadata; + int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; int transparent = -1; static const char *const optionStrings[] = { @@ -499,25 +503,28 @@ FileReadGIF( destX + width, destY + height) != TCL_OK) { return TCL_ERROR; } + + /* + * ------------------------------------------------------------------------- + * From here on, go to error to not leave memory leaks + * ------------------------------------------------------------------------- + */ /* + * Initialize the metadata dict + */ + metadata = Tcl_NewDictObj(); + + /* * Search for the frame from the GIF to display. */ while (1) { - if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { - /* - * Premature end of image. - */ - - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "premature end of image data for this index", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", - NULL); + if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } - switch (buf[0]) { + switch (gifLabel) { case GIF_TERMINATOR: Tcl_SetObjResult(interp, Tcl_NewStringObj( "no image data for this index", -1)); @@ -529,16 +536,11 @@ FileReadGIF( * This is a GIF extension. */ - if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "error reading extension function code in GIF image", - -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", - NULL); + if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; } - if (DoExtension(gifConfPtr, chan, buf[0], - gifConfPtr->workingBuffer, &transparent) < 0) { + if (DoExtension(gifConfPtr, chan, gifLabel, + gifConfPtr->workingBuffer, &transparent, metadata) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", @@ -716,9 +718,72 @@ FileReadGIF( * which suits as well). We're done. */ + /* + * Read final zero + */ + if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { + goto error; + } + if (gifLabel != '\0') { + /* + * Premature end of image. + */ + + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "no image data termination", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", + NULL); + goto error; + } + while (1) { + if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { + goto error; + } + switch (gifLabel) { + case GIF_TERMINATOR: + break; + + case GIF_EXTENSION: + /* + * This is a GIF extension. + */ + + if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { + goto error; + } + if (DoExtension(gifConfPtr, chan, gifLabel, + gifConfPtr->workingBuffer, &transparent, metadata) < 0) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "error reading extension in GIF image", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", + NULL); + goto error; + } + continue; + case GIF_START: + /* + * There should not be a second image block - bail out without error + */ + break; + default: + /* + * Not a valid start character; ignore it. + */ + + continue; + } + break; + } + Tcl_SetObjResult(interp, Tcl_NewStringObj(tkImgFmtGIF.name, -1)); result = TCL_OK; + /* + * Set the metadata object. + * This increments the ref count + */ + Tk_PhotoSetMetadata(imageHandle, metadata); + error: /* * If a trash buffer has been allocated, free it now. @@ -727,9 +792,54 @@ FileReadGIF( if (trashBuffer != NULL) { ckfree(trashBuffer); } + + /* + * free the metadata object in case of error. + * Otherwise, it is not freed as the ref count was incremented above. + */ + Tcl_IncrRefCount(metadata); return result; } + +/* + *---------------------------------------------------------------------- + * + * Read one Byte -- + * + * Read one byte (label byte) from the image stream. + * + * Results: + * The return value is 1 if the first characters in the data are like GIF + * data, and 0 otherwise. + * + * Side effects: + * The size of the image is placed in widthPtr and heightPtr. + * + *---------------------------------------------------------------------- + */ + +static int +ReadOneByte( + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + GIFImageConfig *gifConfPtr, + Tcl_Channel chan /* The image file, open for reading. */ + ) +{ + unsigned char buf[2]; + if (Fread(gifConfPtr, buf, 1, 1, chan) != 1) { + /* + * Premature end of image. + */ + + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "premature end of image data", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", NULL); + return -1; + } + return buf[0]; +} + /* *---------------------------------------------------------------------- * @@ -748,6 +858,7 @@ FileReadGIF( *---------------------------------------------------------------------- */ + static int StringMatchGIF( Tcl_Obj *dataObj, /* the object containing the image data */ @@ -937,9 +1048,13 @@ DoExtension( Tcl_Channel chan, int label, unsigned char *buf, - int *transparent) + int *transparent, + Tcl_Obj *metadata) { int count; + Tcl_Obj *metadataData; + int length; + unsigned char *bytePtr; switch (label) { case 0x01: /* Plain Text Extension */ @@ -949,24 +1064,35 @@ DoExtension( break; case 0xfe: /* Comment Extension */ - do { - count = GetDataBlock(gifConfPtr, chan, buf); - } while (count > 0); + length = 0; + while ( 0 < + (count = GetDataBlock(gifConfPtr, chan, buf)) ) { + if (length == 0) { + metadataData = Tcl_NewByteArrayObj(buf, count); + } else { + bytePtr = Tcl_SetByteArrayLength(metadataData, length+count); + memcpy(bytePtr+length,buf,count); + } + length += count; + } + if (length > 0) { + if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, + Tcl_NewStringObj("comment",-1), metadataData)) { + return -1; + } + } return count; case 0xf9: /* Graphic Control Extension */ count = GetDataBlock(gifConfPtr, chan, buf); if (count < 0) { + /* HaO: return 1 on file read error will not show error - why ? */ return 1; } if ((buf[0] & 0x1) != 0) { *transparent = buf[3]; } - - do { - count = GetDataBlock(gifConfPtr, chan, buf); - } while (count > 0); - return count; + break; } do { diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 7d3c3d9..ba78be1 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -3804,6 +3804,72 @@ Tk_PhotoSetSize( /* *---------------------------------------------------------------------- * + * Tk_PhotoGetMetadata -- + * + * This function is called to obtain the metadata object of a photo + * image. + * + * Results: + * The image's metadata object pointer. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj * +Tk_PhotoGetMetadata( + Tk_PhotoHandle handle) /* Handle for the image whose dimensions are + * requested. */ +{ + PhotoMaster *masterPtr = (PhotoMaster *) handle; + + return masterPtr->metadata; +} + +/* + *---------------------------------------------------------------------- + * + * Tk_PhotoSetMetadata -- + * + * This function is called to obtain to set the metadata object of a + * photo image. + * + * Results: + * None + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +Tk_PhotoSetMetadata( + Tk_PhotoHandle handle, /* Handle for the image whose dimensions are + * requested. */ + Tcl_Obj *metadata) +{ + PhotoMaster *masterPtr = (PhotoMaster *) handle; + /* + * Free current object if present + */ + if(masterPtr->metadata != NULL) { + Tcl_DecrRefCount(masterPtr->metadata); + } + /* + * Increment ref count of new object to get it + */ + if (metadata != NULL) { + Tcl_IncrRefCount(metadata); + } + masterPtr->metadata = metadata; +} + +/* + *---------------------------------------------------------------------- + * * TkGetPhotoValidRegion -- * * This function is called to get the part of the photo where there is diff --git a/generic/tkStubInit.c b/generic/tkStubInit.c index 731fa50..a6e9728 100644 --- a/generic/tkStubInit.c +++ b/generic/tkStubInit.c @@ -1180,6 +1180,8 @@ const TkStubs tkStubs = { Tk_Interp, /* 271 */ Tk_CreateOldImageType, /* 272 */ Tk_CreateOldPhotoImageFormat, /* 273 */ + Tk_PhotoGetMetadata, /* 274 */ + Tk_PhotoSetMetadata, /* 275 */ }; /* !END!: Do not edit above this line. */ -- cgit v0.12 From 1898d612bd82c5d1fcfd88610c71f98b09b78005 Mon Sep 17 00:00:00 2001 From: oehhar Date: Fri, 1 Feb 2019 18:15:55 +0000 Subject: Fix a bug also present in standard Tk: Trailing segments of image data are not skipped, final 0 byte is not skipped, -index over 1 should not work IMHO. --- generic/tkImgGIF.c | 37 +++++++++++++++++++------------------ generic/tkImgPhoto.c | 3 +++ 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index c3a0321..480d52b 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -718,23 +718,6 @@ FileReadGIF( * which suits as well). We're done. */ - /* - * Read final zero - */ - if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { - goto error; - } - if (gifLabel != '\0') { - /* - * Premature end of image. - */ - - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "no image data termination", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "PREMATURE_END", - NULL); - goto error; - } while (1) { if (-1 == (gifLabel = ReadOneByte( interp, gifConfPtr, chan ) ) ) { goto error; @@ -1160,7 +1143,7 @@ ReadImage( int transparent) { unsigned char initialCodeSize; - int xpos = 0, ypos = 0, pass = 0, i; + int xpos = 0, ypos = 0, pass = 0, i, count; register unsigned char *pixelPtr; static const int interlaceStep[] = { 8, 8, 4, 2 }; static const int interlaceStart[] = { 0, 4, 2, 1 }; @@ -1378,6 +1361,24 @@ ReadImage( } pixelPtr = imagePtr + (ypos) * len * ((transparent>=0)?4:3); } + /* + * Now read until the final zero byte. + * It was observed that there might be 1 length blocks + * (test imgPhoto-14.1) which are not read. + * + * The field "stack" is abused for temporary buffer. it has 4096 bytes + * and we need 256. + * + * Loop until we hit a 0 length block which is the end sign. + */ + while ( 0 < (count = GetDataBlock(gifConfPtr, chan, stack))) + { + if (-1 == count ) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "error reading GIF image: %s", Tcl_PosixError(interp))); + return TCL_ERROR; + } + } return TCL_OK; } diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 6b218ad..ea6e76f 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -2211,6 +2211,9 @@ ImgPhotoDelete( if (masterPtr->format != NULL) { Tcl_DecrRefCount(masterPtr->format); } + if (masterPtr->metadata != NULL) { + Tcl_DecrRefCount(masterPtr->metadata); + } Tk_FreeOptions(configSpecs, (char *) masterPtr, NULL, 0); ckfree(masterPtr); } -- cgit v0.12 From 4ea25ded4fb5e2bcfea51ab015097a9c3faa4b84 Mon Sep 17 00:00:00 2001 From: oehhar Date: Fri, 1 Feb 2019 18:27:23 +0000 Subject: Correct metadata ref counting to be cleared in the error case. --- generic/tkImgGIF.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 480d52b..5c29622 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -512,9 +512,12 @@ FileReadGIF( /* * Initialize the metadata dict + * Start with a ref count of 1 to delete it in the error case with + * Tcl_DecrRefCount(). */ metadata = Tcl_NewDictObj(); - + Tcl_IncrRefCount(metadata); + /* * Search for the frame from the GIF to display. */ @@ -780,7 +783,7 @@ FileReadGIF( * free the metadata object in case of error. * Otherwise, it is not freed as the ref count was incremented above. */ - Tcl_IncrRefCount(metadata); + Tcl_DecrRefCount(metadata); return result; } -- cgit v0.12 From 71691327d5939949a34c5175e5db898ee39c15bb Mon Sep 17 00:00:00 2001 From: oehhar Date: Sun, 24 May 2020 18:24:57 +0000 Subject: TIP529: Add GIF Extension blocks of type Application and XMP to metadata dict --- generic/tkImgGIF.c | 131 +++++++++++++++++++++++++++++++++++++++++++++------- tests/imgPhoto.test | 6 +-- 2 files changed, 117 insertions(+), 20 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 3a66f28..f0213f3 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -803,7 +803,7 @@ FileReadGIF( * data, and 0 otherwise. * * Side effects: - * The size of the image is placed in widthPtr and heightPtr. + * The access position in the source is incremented. * *---------------------------------------------------------------------- */ @@ -1033,6 +1033,29 @@ ReadColorMap( return 1; } +/* +*---------------------------------------------------------------------- +* +* DoExtension -- +* +* Process a GIF extension block +* +* Results: +* -1 to trigger an extension read error +* >= 0 ok +* +* Side effects: +* The transparent color is set if present in current extensions +* The data of the following extensions are saved to the metadata dict: +* - Application extension +* - XMP data is stored in key "XMP" +* - any other under the key Application_ +* - Comment extension in key "Comment" +* Plain text extensions are currently ignored. +* +*---------------------------------------------------------------------- +*/ + static int DoExtension( GIFImageConfig *gifConfPtr, @@ -1046,18 +1069,102 @@ DoExtension( Tcl_Obj *metadataData; int length; unsigned char *bytePtr; + /* Prepare extension name + * Maximum string size: "Application_"(12) + App(8) + Code(3) + trailing zero + */ + char extensionStreamName[24]; + extensionStreamName[0] = '\0'; switch (label) { case 0x01: /* Plain Text Extension */ + /* this extension is ignored, skip below */ break; case 0xff: /* Application Extension */ + /* Length: fix = 11 + * Application Identifier: 8 bytes + * Application Authentication code: 3 Bytes + */ + count = GetDataBlock(gifConfPtr, chan, buf); + if (count != 11) { + return -1; + } + /* Detect XMP extension */ + if (0 == memcmp(buf,"XMP DataXMP",11)) { + /* XMP format does not use the block structure of GIF + * The data is utf-8 which never contains 0's + * A magic header of 258 bytes is added with the following data: + * 0x01 0xff 0xfe ... 0x01 0x00 0x00 + */ + Tcl_Encoding encoding; + Tcl_DString recodedDString; + int result; + length = 0; + bytePtr = ckalloc(1); + for (;;) { + if (1 != Fread(gifConfPtr, bytePtr+length, 1, 1, chan)) { + /* read error */ + ckfree(bytePtr); + return -1; + } + /* check for end of xmp header */ + if (bytePtr[length] == '\0') { + break; + } + length ++; + bytePtr = ckrealloc(bytePtr,length); + } + /* check if trailer of 258 bytes is present (length is -1) */ + if (length < 257) { + ckfree(bytePtr); + return -1; + } + length -= 257; + /* save in metadata dict key "XMP" */ + encoding = Tcl_GetEncoding(NULL, "utf-8"); + if (NULL == encoding) { + return -1; + } + Tcl_DStringInit(&recodedDString); + Tcl_ExternalToUtfDString(encoding, bytePtr, length, &recodedDString); + result = Tcl_DictObjPut(NULL, metadata, + Tcl_NewStringObj("XMP",-1), + Tcl_NewStringObj(Tcl_DStringValue(&recodedDString), + Tcl_DStringLength(&recodedDString))); + Tcl_DStringFree(&recodedDString); + Tcl_FreeEncoding(encoding); + if ( TCL_OK != result ) { + return -1; + } + return length; + } else { + /* Other extension */ + /* Name the extension: Application_xxxxxxxxxxx */ + /* 012345678901234567890123*/ + strcpy(extensionStreamName,"Application_"); + memcpy(extensionStreamName+12,buf,11); + extensionStreamName[23]='\0'; + } break; case 0xfe: /* Comment Extension */ + strcpy(extensionStreamName,"Comment"); + /* copy the extension data 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]; + } + break; + } + /* Add extension to dict */ + if (extensionStreamName[0] != '\0' ) { length = 0; - while ( 0 < - (count = GetDataBlock(gifConfPtr, chan, buf)) ) { + while ( 0 < (count = GetDataBlock(gifConfPtr, chan, buf)) ) { if (length == 0) { metadataData = Tcl_NewByteArrayObj(buf, count); } else { @@ -1068,28 +1175,18 @@ DoExtension( } if (length > 0) { if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, - Tcl_NewStringObj("comment",-1), metadataData)) { + Tcl_NewByteArrayObj(extensionStreamName,-1), metadataData)) { return -1; } } + /* this triggers a read error if returned count < 0 */ return count; - - case 0xf9: /* Graphic Control Extension */ - count = GetDataBlock(gifConfPtr, chan, buf); - if (count < 0) { - /* HaO: return 1 on file read error will not show error - why ? */ - return 1; - } - if ((buf[0] & 0x1) != 0) { - *transparent = buf[3]; - } - break; } - + /* skip eventual remaining data block bytes */ do { count = GetDataBlock(gifConfPtr, chan, buf); } while (count > 0); - return count; + return count; /* this may be -1 for error or 0 */ } static int diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index df4cfcb..5fc86bd 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -579,7 +579,7 @@ test imgPhoto-4.31 {ImgPhotoCmd procedure: read option} -constraints { photo1 read $teapotPhotoFile -zoom 2 } -returnCodes error -cleanup { image delete photo1 -} -result {unrecognized option "-zoom": must be -format, -from, -shrink, -to or -metadata} +} -result {unrecognized option "-zoom": must be -format, -from, -shrink, -to, or -metadata} test imgPhoto-4.32 {ImgPhotoCmd procedure: read option} -setup { image create photo photo1 } -body { @@ -1242,7 +1242,7 @@ test imgPhoto-4.104 {ImgPhotoCmd data: existing but not accepted opt} -setup { } -cleanup { imageCleanup } -returnCodes error -result \ -{unrecognized option "-to": must be -background, -format, -from, -grayscale} +{unrecognized option "-to": must be -background, -format, -from, -grayscale, or -metadata} test imgPhoto-4.105 {ImgPhotoCmd data: invalid option} -setup { image create photo photo1 } -body { @@ -1250,7 +1250,7 @@ test imgPhoto-4.105 {ImgPhotoCmd data: invalid option} -setup { } -cleanup { imageCleanup } -returnCodes error -result \ -{unrecognized option "-bogus": must be -background, -format, -from, or -grayscale} +{unrecognized option "-bogus": must be -background, -format, -from, -grayscale, or -metadata} test imgPhoto-4.106 {ImgPhotoCmd data: extra arg before options} -setup { image create photo photo1 } -body { -- cgit v0.12 From c06079bf0c257ee3249d236670c9cc9d3ea30865 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 26 May 2020 18:55:48 +0000 Subject: TIP 529 image metadata: make gif comment parsing work and start with tests. Also eliminate the base64 from the tests. --- generic/tkImgGIF.c | 2 +- tests/imgPhoto.test | 142 ++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 117 insertions(+), 27 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 9393ff2..5507639 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1172,7 +1172,7 @@ DoExtension( } if (length > 0) { if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, - Tcl_NewByteArrayObj((unsigned char *)extensionStreamName,-1), metadataData)) { + Tcl_NewByteArrayObj((unsigned char *)extensionStreamName, strlen(extensionStreamName)), metadataData)) { return -1; } } diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 5fc86bd..13070e2 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -57,6 +57,8 @@ # Tk_PhotoPutBlock_Panic no tests, probably none needed # Tk_PhotoPutZoomedBlock_Panic no tests, probably none needed # Tk_PhotoSetSize_Panic no tests, probably none needed +# Tk_PhotoGetMetadata: imgPhoto-19.* +# Tk_PhotoSetMetadata: imgPhoto-20.* #-------------------------------------------------------------------------- # @@ -130,14 +132,6 @@ testConstraint hasTeapotPhoto [file exists $teapotPhotoFile] set transpTeapotPhotoFile [file join [file dirname [info script]] teapotTransparent.png] testConstraint hasTranspTeapotPhoto [file exists $transpTeapotPhotoFile] -proc base64ok {} { - expr { - ![catch {package require base64}] - } -} - -testConstraint base64PackageNeeded [base64ok] - test imgPhoto-1.1 {options for photo images} -body { image create photo photo1 -width 79 -height 83 list [photo1 cget -width] [photo1 cget -height] \ @@ -202,6 +196,9 @@ test imgPhoto-1.13 {option -withalpha, normal use} -setup { } -cleanup { imageCleanup } -result {0 128 0 255} +test imgPhoto-1.14 {options for photo images - error case} -body { + image create photo photo1 -metadata +} -returnCodes error -result {value for "-metadata" missing} test imgPhoto-2.1 {ImgPhotoCreate procedure} -setup { imageCleanup @@ -1951,11 +1948,8 @@ test imgPhoto-19.8 {MatchStirngFormat: invalid data for gif} -setup { # Reject corrupted or truncated image [Bug b601ce3ab1]. # WARNING - tests 18.1-18.9 will cause a segfault on 8.5.19 and lower, # and on 8.6.6 and lower. -test imgPhoto-18.1 {Reject corrupted GIF (binary string)} -constraints { - base64PackageNeeded -} -setup { - package require base64 - set data [base64::decode { +test imgPhoto-18.1 {Reject corrupted GIF (binary string)} -setup { + set data [binary decode base64 { R0lGODlhAAQABP8zM/8z/zP/MzP/////M////yH5CiwheLrcLTBCd6Tv2qW16tdK4jhV 5qpraXIvM1JlNyAgOw== }] @@ -1981,11 +1975,8 @@ test imgPhoto-18.3 {Reject corrupted GIF (file)} -setup { } -cleanup { catch {image delete gif1} } -returnCodes error -result {error reading color map|not enough free memory for image buffer} -match regexp -test imgPhoto-18.4 {Reject truncated GIF (binary string)} -constraints { - base64PackageNeeded -} -setup { - package require base64 - set data [base64::decode { +test imgPhoto-18.4 {Reject truncated GIF (binary string)} -setup { + set data [binary decode base64 { R0lGODlhEAAQAMIHAAAAADMz//8zM/8z/zP/MzP///8= }] } -body { @@ -2010,14 +2001,13 @@ test imgPhoto-18.6 {Reject truncated GIF (file)} -setup { catch {image delete gif1} } -returnCodes error -result {error reading color map} test imgPhoto-18.7 {Reject corrupted GIF (> 4Gb) (binary string)} -constraints { - base64PackageNeeded nonPortable + nonPortable } -setup { # About the non portability constraint of this test: see ticket [cc42cc18a5] # If there is insufficient memory, the error message # {not enough free memory for image buffer} should be returned. # Instead, some systems (e.g. FreeBSD 11.1) terminate the test interpreter. - package require base64 - set data [base64::decode { + set data [binary decode base64 { R0lGODlhwmYz//8zM/8z/zP/MzP/////M////yH5Ciwhe LrcLTBCd6Tv2qW16tdK4jhV5qpraXIvM1JlNyAgOw== }] @@ -2055,14 +2045,11 @@ test imgPhoto-18.9 {Reject corrupted GIF (> 4Gb) (file)} -constraints { } -cleanup { catch {image delete gif1} } -returnCodes error -result {error reading color map|not enough free memory for image buffer} -match regexp -test imgPhoto-18.10 {Valid GIF (binary string)} -constraints { - base64PackageNeeded -} -setup { +test imgPhoto-18.10 {Valid GIF (binary string)} -setup { # Test the binary string reader with a valid GIF. # This is not tested elsewhere. # Tests 18.11, 18.12, with matching data, are included for completeness. - package require base64 - set data [base64::decode { + set data [binary decode base64 { R0lGODlhEAAQAMIHAAAAADMz//8zM/8z/zP/MzP/////M////yH5BAEKAAcALAAA AAAQABAAAAMheLrcLTBCd6QV79qlterXB0riOFXmmapraXIvM1IdZTcJADs= }] @@ -2089,6 +2076,109 @@ test imgPhoto-18.12 {Valid GIF (file)} -setup { catch {image delete gif1} } -result gif1 +# imgPhoto-21.x : Tk_PhotoGetMetadata + +test imgPhoto-21.1 {option -metadata, get configure list} -setup { + image create photo photo1 -metadata {dpi 100} +} -body { + photo1 configure -metadata +} -cleanup { + catch {image delete photo1} +} -result {-metadata {} {} {} {dpi 100}} + +test imgPhoto-21.2 {option -metadata, get value} -setup { + image create photo photo1 -metadata {dpi 100} +} -body { + photo1 cget -metadata +} -cleanup { + catch {image delete photo1} +} -result {dpi 100} + +test imgPhoto-21.3 {option -metadata, get default value} -setup { + image create photo photo1 +} -body { + photo1 cget -metadata +} -cleanup { + catch {image delete photo1} +} -result {} + +# imgPhoto-22.x : Tk_PhotoSetMetadata + +test imgPhoto-22.1 {option -metadata, set value} -setup { + image create photo photo1 +} -body { + photo1 configure -metadata {dpi 100} + photo1 cget -metadata +} -cleanup { + catch {image delete photo1} +} -result {dpi 100} + +test imgPhoto-22.2 {option -metadata, change value} -setup { + image create photo photo1 -metadata {dpi 200} +} -body { + photo1 configure -metadata {dpi 100} + photo1 cget -metadata +} -cleanup { + catch {image delete photo1} +} -result {dpi 100} + +test imgPhoto-22.3 {option -metadata, clear value} -setup { + image create photo photo1 -metadata {dpi 200} +} -body { + photo1 configure -metadata {} + photo1 cget -metadata +} -cleanup { + catch {image delete photo1} +} -result {} + +# 23.x GIF images with metadata + +# The following gif prefix data is used by the following data. +# The trailer \x3b is missing +# N.B. this is the same image as test imgPhoto-18.10 + +# size 16x16, global color table size: 8 +set gifstart "GIF89a\x10\x00\x10\x00\xc2\x07\x00" +# color table +append gifstart "\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: Transparent color index: 7 (not needed here) +# append gifdata "\x21\xf9\x04\x01\x0a\x00\x07\x00" +# Image descriptor: 16x16, no local color table +set gifdata "\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00" +# Image data +append gifdata "\x03\x21\x78\xba\xdc\x2d\x30\x42\x77\xa4\x15\xef\xda\xa5\xb5\xea\xd7\x07\x4a\xe2\x38\x55\xe6\x99\xaa\x6b\x69\x72\x2f\x33\x52\x1d\x65\x37\x09\x00" +set gifend "\x3b" + +test imgPhoto-23.1 {GIF comment before image data} -setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {Comment ABCD} + +test imgPhoto-23.2 {GIF comment after image data} -setup { + set data $::gifstart + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {Comment ABCD} + +unset -nocomplain gifstart gifdata gifend + + catch {rename foreachPixel {}} catch {rename checkImgTrans {}} catch {rename checkImgTransLoop {}} -- cgit v0.12 From dd57d00f5c65deebb5bea67608c586856c2c2b7a Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 27 May 2020 19:59:05 +0000 Subject: TIP529 image metadata: correct and test GIF XMP segment --- generic/tkImgGIF.c | 131 ++++++++++++++++++++++++++++++---------------------- tests/imgPhoto.test | 93 ++++++++++++++++++++++++++++++++++++- 2 files changed, 168 insertions(+), 56 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 5507639..c91a1c0 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1061,14 +1061,11 @@ DoExtension( GIFImageConfig *gifConfPtr, Tcl_Channel chan, int label, - unsigned char *buf, + unsigned char *buf, /* defined as 280 byte working buffer */ int *transparent, Tcl_Obj *metadata) { int count; - Tcl_Obj *metadataData; - int length; - unsigned char *bytePtr; /* Prepare extension name * Maximum string size: "Application_"(12) + App(8) + Code(3) + trailing zero */ @@ -1079,9 +1076,21 @@ DoExtension( case 0x01: /* Plain Text Extension */ /* 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]; + } + break; + case 0xfe: /* Comment Extension */ + strcpy(extensionStreamName,"Comment"); + /* copy the extension data below */ + break; case 0xff: /* Application Extension */ - /* Length: fix = 11 + /* Length: 11 * Application Identifier: 8 bytes * Application Authentication code: 3 Bytes */ @@ -1093,91 +1102,105 @@ DoExtension( if (0 == memcmp(buf,"XMP DataXMP",11)) { /* XMP format does not use the block structure of GIF * The data is utf-8 which never contains 0's - * A magic header of 258 bytes is added with the following data: + * A magic trailer of 258 bytes is added with the following data: * 0x01 0xff 0xfe ... 0x01 0x00 0x00 */ Tcl_Encoding encoding; Tcl_DString recodedDString; + Tcl_DString dataDString; + int length; int result; - length = 0; - bytePtr = (unsigned char *)ckalloc(1); + unsigned char lastbyte = 1; + Tcl_DStringInit(&dataDString); + for (;;) { - if (1 != Fread(gifConfPtr, bytePtr+length, 1, 1, chan)) { + unsigned char byte; + if (1 != Fread(gifConfPtr, &byte, 1, 1, chan)) { /* read error */ - ckfree(bytePtr); + Tcl_DStringFree(&dataDString); return -1; } + Tcl_DStringAppend(&dataDString,&byte,1); /* check for end of xmp header */ - if (bytePtr[length] == '\0') { + if (byte == 0 && lastbyte == 0) { break; } - length ++; - bytePtr = (unsigned char *)ckrealloc(bytePtr,length); + lastbyte = byte; } - /* check if trailer of 258 bytes is present (length is -1) */ - if (length < 257) { - ckfree(bytePtr); + + /* check if trailer of 258 bytes is present */ + length = Tcl_DStringLength(&dataDString); + if (length < 258) { + Tcl_DStringFree(&dataDString); return -1; } - length -= 257; - /* save in metadata dict key "XMP" */ + /* Remove the trailer from the data */ + length -= 258; + /* save the utf-8 data in the metadata dict key "XMP" */ encoding = Tcl_GetEncoding(NULL, "utf-8"); Tcl_DStringInit(&recodedDString); - Tcl_ExternalToUtfDString(encoding, (char *)bytePtr, length, &recodedDString); + Tcl_ExternalToUtfDString(encoding, Tcl_DStringValue(&dataDString), length, &recodedDString); result = Tcl_DictObjPut(NULL, metadata, Tcl_NewStringObj("XMP",-1), Tcl_NewStringObj(Tcl_DStringValue(&recodedDString), Tcl_DStringLength(&recodedDString))); Tcl_DStringFree(&recodedDString); + Tcl_DStringFree(&dataDString); Tcl_FreeEncoding(encoding); if ( TCL_OK != result ) { return -1; } - return length; + return 0; } else { - /* Other extension */ - /* Name the extension: Application_xxxxxxxxxxx */ - /* 012345678901234567890123*/ + /* + * Other extension + * Name the extension: Application_xxxxxxxxxxx + * 012345678901234567890123 + */ + /* Untested code commented out, no use case + */ + /* strcpy(extensionStreamName,"Application_"); memcpy(extensionStreamName+12,buf,11); extensionStreamName[23]='\0'; - } - break; - - case 0xfe: /* Comment Extension */ - strcpy(extensionStreamName,"Comment"); - /* copy the extension data 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]; + */ } break; } /* Add extension to dict */ if (extensionStreamName[0] != '\0' ) { - length = 0; - while ( 0 < (count = GetDataBlock(gifConfPtr, chan, buf)) ) { - if (length == 0) { - metadataData = Tcl_NewByteArrayObj(buf, count); - } else { - bytePtr = Tcl_SetByteArrayLength(metadataData, length+count); - memcpy(bytePtr+length,buf,count); - } - length += count; - } - if (length > 0) { - if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, - Tcl_NewByteArrayObj((unsigned char *)extensionStreamName, strlen(extensionStreamName)), metadataData)) { + Tcl_Obj *metadataData; + int length = 0; + for (;;) { + count = GetDataBlock(gifConfPtr, chan, buf); + switch (count) { + case -1: /* error */ return -1; + case 0: /* end of data */ + if (length > 0) { + if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, + Tcl_NewByteArrayObj((unsigned char *)extensionStreamName, + strlen(extensionStreamName)), metadataData)) { + return -1; + } + } + /* return success */ + return 0; + default: /* block received */ + if (length == 0) { + /* first block */ + metadataData = Tcl_NewByteArrayObj(buf, count); + length = count; + } else { + /* consecutive block */ + unsigned char *bytePtr; + bytePtr = Tcl_SetByteArrayLength(metadataData, length+count); + memcpy(bytePtr+length,buf,count); + length += count; + } + break; } - } - /* this triggers a read error if returned count < 0 */ - return count; + } /* for */ } /* skip eventual remaining data block bytes */ do { diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 13070e2..b7434a9 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2133,8 +2133,7 @@ test imgPhoto-22.3 {option -metadata, clear value} -setup { # 23.x GIF images with metadata -# The following gif prefix data is used by the following data. -# The trailer \x3b is missing +# The following gif core data is used by the following data. # N.B. this is the same image as test imgPhoto-18.10 # size 16x16, global color table size: 8 @@ -2176,6 +2175,96 @@ test imgPhoto-23.2 {GIF comment after image data} -setup { catch {image delete gif1} } -result {Comment ABCD} +test imgPhoto-23.3 {Two GIF comment blocks} -setup { + set data $::gifstart + # Append a comment extension block with data "1234" + append data "\x21\xfe\x04" "1234" "\x0" + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend +} -body { + image create photo gif1 -data $data + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {Comment ABCD} + +test imgPhoto-23.4 {XMP comment block before image} -setup { + set data $::gifstart + # Append an XMP comment extension block (including a Unicode codepoint 2022 + set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + append data $::gifdata + # Trailer + append data $::gifend +} -body { + image create photo gif1 -data $data + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} +} -cleanup { + catch {image delete gif1} +} -result {1} + +test imgPhoto-23.5 {XMP comment block after image} -setup { + set data $::gifstart + append data $::gifdata + + # Append an XMP comment extension block (including a Unicode codepoint 2022 + set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + # Trailer + append data $::gifend +} -body { + image create photo gif1 -data $data + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} +} -cleanup { + catch {image delete gif1} +} -result {1} + +test imgPhoto-23.6 {empty XMP comment block after image} -setup { + set data $::gifstart + append data $::gifdata + + append data "\x21\xff\x0B" "XMP DataXMP" + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + # Trailer + append data $::gifend +} -body { + image create photo gif1 -data $data + dict get [gif1 cget -metadata] XMP +} -cleanup { + catch {image delete gif1} +} -result {} + unset -nocomplain gifstart gifdata gifend -- cgit v0.12 From 6d5bc7e5ad44cc88246abcb6e02761e54866ca29 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 28 May 2020 14:54:43 +0000 Subject: Fix C++ build --- generic/tkImgGIF.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index c91a1c0..a02117a 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1120,7 +1120,7 @@ DoExtension( Tcl_DStringFree(&dataDString); return -1; } - Tcl_DStringAppend(&dataDString,&byte,1); + Tcl_DStringAppend(&dataDString,(char *)&byte,1); /* check for end of xmp header */ if (byte == 0 && lastbyte == 0) { break; -- cgit v0.12 From 9da42ea63840166de7979c55a3ba0476cfb245c4 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sat, 30 May 2020 21:53:44 +0000 Subject: TIP529 image metadata: untested sketch to implemet new call interface for Tcl_CreatePhotoImageType with metadata in all functions. Replaces Tk_Get/SetMetadata --- generic/tk.decls | 5 +- generic/tk.h | 56 +++++++++ generic/tkDecls.h | 8 +- generic/tkImgGIF.c | 102 +++++++++++++---- generic/tkImgPhoto.c | 312 ++++++++++++++++++++++++++++++++++++++++++--------- generic/tkInt.h | 2 +- generic/tkWindow.c | 2 +- 7 files changed, 400 insertions(+), 87 deletions(-) diff --git a/generic/tk.decls b/generic/tk.decls index 2952bc3..0cd8fa4 100644 --- a/generic/tk.decls +++ b/generic/tk.decls @@ -1070,10 +1070,7 @@ declare 273 { } # New in Tk8.7 declare 274 { - Tcl_Obj *Tk_PhotoGetMetadata(Tk_PhotoHandle handle); -} -declare 275 { - void Tk_PhotoSetMetadata(Tk_PhotoHandle handle,Tcl_Obj *metadata); + void Tk_CreatePhotoImageFormat87(const Tk_PhotoImageFormat87 *formatPtr) } diff --git a/generic/tk.h b/generic/tk.h index 5efd2cb..5aa8998 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1440,6 +1440,30 @@ typedef int (Tk_ImageStringWriteProc) (Tcl_Interp *interp, Tcl_Obj *format, #endif /* USE_OLD_IMAGE */ /* + * The following alternate definitions are used with the Tk8.7 file format + * supporting a metadata dict + */ + +typedef struct Tk_PhotoImageFormat87 Tk_PhotoImageFormat87; +typedef int (Tk_ImageFileMatchProc87) (Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, Tcl_Interp *interp, + Tcl_Obj **metadataPtr); +typedef int (Tk_ImageStringMatchProc87) (Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp, Tcl_Obj **metadataPtr); +typedef int (Tk_ImageFileReadProc87) (Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, int srcX, int srcY, + Tcl_Obj **metadataPtr); +typedef int (Tk_ImageStringReadProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY, Tcl_Obj **metadataPtr); +typedef int (Tk_ImageFileWriteProc87) (Tcl_Interp *interp, const char *fileName, + Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); +typedef int (Tk_ImageStringWriteProc87) (Tcl_Interp *interp, Tcl_Obj *format, + Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); + + +/* * The following structure represents a particular file format for storing * images (e.g., PPM, GIF, JPEG, etc.). It provides information to allow image * files of that format to be recognized and read into a photo image. @@ -1471,6 +1495,38 @@ struct Tk_PhotoImageFormat { * currently known. Filled in by Tk, not by * image format handler. */ }; + +/* + * The following structure is the same plus added support for the metadata + * structure. + */ + +struct Tk_PhotoImageFormat87 { + const char *name; /* Name of image file format */ + Tk_ImageFileMatchProc87 *fileMatchProc; + /* Procedure to call to determine whether an + * image file matches this format. */ + Tk_ImageStringMatchProc87 *stringMatchProc; + /* Procedure to call to determine whether the + * data in a string matches this format. */ + Tk_ImageFileReadProc87 *fileReadProc; + /* Procedure to call to read data from an + * image file into a photo image. */ + Tk_ImageStringReadProc87 *stringReadProc; + /* Procedure to call to read data from a + * string into a photo image. */ + Tk_ImageFileWriteProc87 *fileWriteProc; + /* Procedure to call to write data from a + * photo image to a file. */ + Tk_ImageStringWriteProc87 *stringWriteProc; + /* Procedure to call to obtain a string + * representation of the data in a photo + * image.*/ + struct Tk_PhotoImageFormat87 *nextPtr; + /* Next in list of all photo image formats + * currently known. Filled in by Tk, not by + * image format handler. */ +}; /* *---------------------------------------------------------------------- diff --git a/generic/tkDecls.h b/generic/tkDecls.h index 094e145..9ba5d88 100644 --- a/generic/tkDecls.h +++ b/generic/tkDecls.h @@ -879,9 +879,8 @@ EXTERN void Tk_CreateOldImageType(const Tk_ImageType *typePtr); EXTERN void Tk_CreateOldPhotoImageFormat( const Tk_PhotoImageFormat *formatPtr); /* 274 */ -EXTERN Tcl_Obj *Tk_PhotoGetMetadata(Tk_PhotoHandle handle); -/* 275 */ -EXTERN void Tk_PhotoSetMetadata(Tk_PhotoHandle handle,Tcl_Obj *metadata); +EXTERN void Tk_CreatePhotoImageFormat87( + const Tk_PhotoImageFormat87 *formatPtr); typedef struct { const struct TkPlatStubs *tkPlatStubs; @@ -1168,8 +1167,7 @@ typedef struct TkStubs { Tcl_Interp * (*tk_Interp) (Tk_Window tkwin); /* 271 */ void (*tk_CreateOldImageType) (const Tk_ImageType *typePtr); /* 272 */ void (*tk_CreateOldPhotoImageFormat) (const Tk_PhotoImageFormat *formatPtr); /* 273 */ - Tcl_Obj *(*tk_PhotoGetMetadata) (Tk_PhotoHandle handle); /* 274 */ - void (*tk_PhotoSetMetadata) (Tk_PhotoHandle handle,Tcl_Obj *metadata); /* 275 */ + void (*tk_CreatePhotoImageFormat87) (const Tk_PhotoImageFormat87 *formatPtr); /* 274 */ } TkStubs; extern const TkStubs *tkStubsPtr; diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index c91a1c0..f3b09e5 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -120,26 +120,29 @@ typedef size_t (WriteBytesFunc) (ClientData clientData, const char *bytes, static int FileMatchGIF(Tcl_Channel chan, const char *fileName, Tcl_Obj *format, int *widthPtr, int *heightPtr, - Tcl_Interp *interp); + Tcl_Interp *interp, Tcl_Obj **metadataPtr); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, - int width, int height, int srcX, int srcY); + int width, int height, int srcX, int srcY, + Tcl_Obj **metadataPtr); static int StringMatchGIF(Tcl_Obj *dataObj, Tcl_Obj *format, - int *widthPtr, int *heightPtr, Tcl_Interp *interp); + int *widthPtr, int *heightPtr, Tcl_Interp *interp, + Tcl_Obj **metadataPtr); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY); + int srcX, int srcY, Tcl_Obj **metadataPtr); static int FileWriteGIF(Tcl_Interp *interp, const char *filename, - Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr); + Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr, + Tcl_Obj *metadata); static int StringWriteGIF(Tcl_Interp *interp, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr); + Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); static int CommonWriteGIF(Tcl_Interp *interp, ClientData clientData, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr); + Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); -Tk_PhotoImageFormat tkImgFmtGIF = { +Tk_PhotoImageFormat87 tkImgFmtGIF = { "gif", /* name */ FileMatchGIF, /* fileMatchProc */ StringMatchGIF, /* stringMatchProc */ @@ -353,7 +356,8 @@ FileMatchGIF( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Interp *dummy) /* not used */ + Tcl_Interp *dummy, /* not used */ + Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ { GIFImageConfig gifConf; (void)fileName; @@ -394,8 +398,9 @@ FileReadGIF( * image to be written to. */ int width, int height, /* Dimensions of block of photo image to be * written to. */ - int srcX, int srcY) /* Coordinates of top-left pixel to be used in + int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ + Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ { int fileWidth, fileHeight, imageWidth, imageHeight; unsigned int nBytes; @@ -768,10 +773,22 @@ FileReadGIF( result = TCL_OK; /* - * Set the metadata object. - * This increments the ref count + * Return the metadata object + * Increment the refcount again, as it is decremented below + */ + if (*metadataPtr != NULL) { + Tcl_DecrRefCount(*metadataPtr); + } + *metadataPtr = metadata; + + /* + * If a trash buffer has been allocated, free it now. */ - Tk_PhotoSetMetadata(imageHandle, metadata); + + if (trashBuffer != NULL) { + ckfree(trashBuffer); + } + return result; error: /* @@ -854,7 +871,8 @@ StringMatchGIF( Tcl_Obj *format, /* the image format object, or NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Interp *dummy) /* not used */ + Tcl_Interp *dummy, /* not used */ + Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ { unsigned char *data, header[10]; TkSizeT got, length; @@ -925,7 +943,8 @@ StringReadGIF( Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ - int srcX, int srcY) + int srcX, int srcY, + Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ { MFile handle, *hdlPtr = &handle; TkSizeT length; @@ -954,7 +973,7 @@ StringReadGIF( */ return FileReadGIF(interp, (Tcl_Channel) hdlPtr, xferFormat, format, - imageHandle, destX, destY, width, height, srcX, srcY); + imageHandle, destX, destY, width, height, srcX, srcY, metadataPtr); } /* @@ -1866,7 +1885,8 @@ FileWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ const char *filename, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr) + Tk_PhotoImageBlock *blockPtr, + Tcl_Obj *metadata) { Tcl_Channel chan = NULL; int result; @@ -1881,7 +1901,7 @@ FileWriteGIF( return TCL_ERROR; } - result = CommonWriteGIF(interp, chan, WriteToChannel, format, blockPtr); + result = CommonWriteGIF(interp, chan, WriteToChannel, format, blockPtr, metadata); if (Tcl_Close(interp, chan) == TCL_ERROR) { return TCL_ERROR; @@ -1889,19 +1909,22 @@ FileWriteGIF( return result; } +/* New parameter "metadata" is appended at the end */ + static int StringWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors and * returning the GIF data. */ Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr) + Tk_PhotoImageBlock *blockPtr, + Tcl_Obj *metadata) { int result; Tcl_Obj *objPtr = Tcl_NewObj(); Tcl_IncrRefCount(objPtr); result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, - blockPtr); + blockPtr, metadata); if (result == TCL_OK) { Tcl_SetObjResult(interp, objPtr); } @@ -1941,7 +1964,8 @@ CommonWriteGIF( ClientData handle, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr) + Tk_PhotoImageBlock *blockPtr, + Tcl_Obj *metadata) { GifWriterState state; int resolution; @@ -2071,6 +2095,42 @@ CommonWriteGIF( c = 0; writeProc(handle, (char *) &c, 1); + /* Check for metadata comment block */ + if (NULL != metadata) { + Tcl_Obj *itemData; + if (TCL_ERROR == Tcl_DictObjGet(interp, metadata, + Tcl_NewStringObj("Comment",-1), + &itemData)) { + return TCL_ERROR; + } + /* Check if there is a Comment key */ + if (itemData != NULL) { + int length; + unsigned char *comment; + comment = Tcl_GetByteArrayFromObj(itemData, &length); + if (length > 0) { + /* write comment header */ + writeProc(handle, (char *) "\x21\fe", 2); + /* write comment blocks */ + for (;length > 0;) { + unsigned char blocklength; + if (length > 255) { + length -=255; + blocklength = 255; + } else { + blocklength = (unsigned char)length; + length = 0; + } + writeProc(handle, (char *) blocklength, 1); + writeProc(handle, (char *) comment, blocklength); + comment += blocklength; + } + /* Block terminator */ + c = 0; + writeProc(handle, (char *) &c, 1); + } + } + } c = GIF_TERMINATOR; writeProc(handle, (char *) &c, 1); diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index a71ad74..b4ad271 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -142,6 +142,9 @@ typedef struct { Tk_PhotoImageFormat *oldFormatList; /* Pointer to the first in the list of known * photo image formats.*/ + Tk_PhotoImageFormat87 *formatList87; + /* Pointer to the first in the list of known + * photo image formats in 87 format.*/ int initialized; /* Set to 1 if we've initialized the * structure. */ } ThreadSpecificData; @@ -197,11 +200,15 @@ static char * ImgGetPhoto(PhotoMaster *masterPtr, struct SubcommandOptions *optPtr); static int MatchFileFormat(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *formatString, + Tcl_Obj **metadataObjPtr, Tk_PhotoImageFormat **imageFormatPtr, + Tk_PhotoImageFormat87 **imageFormat87Ptr, int *widthPtr, int *heightPtr, int *oldformat); static int MatchStringFormat(Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *formatString, + Tcl_Obj **metadataObjPtr, Tk_PhotoImageFormat **imageFormatPtr, + Tk_PhotoImageFormat87 **imageFormat87Ptr, int *widthPtr, int *heightPtr, int *oldformat); static const char * GetExtension(const char *path); @@ -226,6 +233,7 @@ PhotoFormatThreadExitProc( ClientData dummy) /* not used */ { Tk_PhotoImageFormat *freePtr; + Tk_PhotoImageFormat87 *freePtr87; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); (void)dummy; @@ -241,12 +249,19 @@ PhotoFormatThreadExitProc( ckfree((char *)freePtr->name); ckfree(freePtr); } + while (tsdPtr->formatList87 != NULL) { + freePtr87 = tsdPtr->formatList87; + tsdPtr->formatList87 = tsdPtr->formatList87->nextPtr; + ckfree((char *)freePtr87->name); + ckfree(freePtr87); + } } /* *---------------------------------------------------------------------- * - * Tk_CreateOldPhotoImageFormat, Tk_CreatePhotoImageFormat -- + * Tk_CreateOldPhotoImageFormat, Tk_CreatePhotoImageFormat, + * Tk_CreatePhotoImageFormat87 -- * * This function is invoked by an image file handler to register a new * photo image format and the functions that handle the new format. The @@ -312,6 +327,30 @@ Tk_CreatePhotoImageFormat( tsdPtr->formatList = copyPtr; } } +void +Tk_CreatePhotoImageFormat87( + const Tk_PhotoImageFormat87 *formatPtr) + /* Structure describing the format. All of the + * fields except "nextPtr" must be filled in + * by caller. */ +{ + Tk_PhotoImageFormat87 *copyPtr; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + if (!tsdPtr->initialized) { + tsdPtr->initialized = 1; + Tcl_CreateThreadExitHandler(PhotoFormatThreadExitProc, NULL); + } + copyPtr = (Tk_PhotoImageFormat87 *)ckalloc(sizeof(Tk_PhotoImageFormat87)); + *copyPtr = *formatPtr; + /* for compatibility with aMSN: make a copy of formatPtr->name */ + char *name = (char *)ckalloc(strlen(formatPtr->name) + 1); + strcpy(name, formatPtr->name); + copyPtr->name = name; + copyPtr->nextPtr = tsdPtr->formatList87; + tsdPtr->formatList87 = copyPtr; +} /* *---------------------------------------------------------------------- @@ -416,6 +455,7 @@ ImgPhotoCmd( unsigned char *pixelPtr; Tk_PhotoImageBlock block; Tk_PhotoImageFormat *imageFormat; + Tk_PhotoImageFormat87 *imageFormat87; TkSizeT length; int imageWidth, imageHeight, matched, oldformat = 0; Tcl_Channel chan; @@ -900,6 +940,7 @@ ImgPhotoCmd( memset(&options, 0, sizeof(options)); options.name = NULL; options.format = NULL; + options.metadata = NULL; if (ParseSubcommandOptions(&options, interp, OPT_TO|OPT_FORMAT|OPT_METADATA, &index, objc, objv) != TCL_OK) { @@ -914,8 +955,10 @@ ImgPhotoCmd( * See if there's a format that can read the data */ - if (MatchStringFormat(interp, objv[2], options.format, &imageFormat, - &imageWidth, &imageHeight, &oldformat) != TCL_OK) { + if (MatchStringFormat(interp, objv[2], options.format, + &(options.metadata), &imageFormat, + &imageFormat87, &imageWidth, &imageHeight, &oldformat) + != TCL_OK) { return TCL_ERROR; } @@ -938,11 +981,21 @@ ImgPhotoCmd( data = (Tcl_Obj *) Tcl_GetString(data); } - if (imageFormat->stringReadProc(interp, data, format, - (Tk_PhotoHandle) masterPtr, options.toX, options.toY, - options.toX2 - options.toX, - options.toY2 - options.toY, 0, 0) != TCL_OK) { - return TCL_ERROR; + if (imageFormat != NULL) { + if (imageFormat->stringReadProc(interp, data, format, + (Tk_PhotoHandle) masterPtr, options.toX, options.toY, + options.toX2 - options.toX, + options.toY2 - options.toY, 0, 0) != TCL_OK) { + return TCL_ERROR; + } + } else { + if (imageFormat87->stringReadProc(interp, data, format, + (Tk_PhotoHandle) masterPtr, options.toX, options.toY, + options.toX2 - options.toX, + options.toY2 - options.toY, 0, 0, + &(options.metadata)) != TCL_OK) { + return TCL_ERROR; + } } /* * SB: is the next line really needed? The stringReadProc @@ -965,6 +1018,7 @@ ImgPhotoCmd( memset(&options, 0, sizeof(options)); options.name = NULL; options.format = NULL; + options.metadata = NULL; if (ParseSubcommandOptions(&options, interp, OPT_FORMAT | OPT_FROM | OPT_TO | OPT_SHRINK | OPT_METADATA, &index, objc, objv) != TCL_OK) { @@ -1007,8 +1061,10 @@ ImgPhotoCmd( } if (MatchFileFormat(interp, chan, - Tcl_GetString(options.name), options.format, &imageFormat, - &imageWidth, &imageHeight, &oldformat) != TCL_OK) { + Tcl_GetString(options.name), options.format, + &(options.metadata), &imageFormat, + &imageFormat87, &imageWidth, &imageHeight, &oldformat) + != TCL_OK) { Tcl_Close(NULL, chan); return TCL_ERROR; } @@ -1059,10 +1115,18 @@ ImgPhotoCmd( if (oldformat && format) { format = (Tcl_Obj *) Tcl_GetString(format); } - result = imageFormat->fileReadProc(interp, chan, - Tcl_GetString(options.name), - format, (Tk_PhotoHandle) masterPtr, options.toX, - options.toY, width, height, options.fromX, options.fromY); + if (imageFormat != NULL) { + result = imageFormat->fileReadProc(interp, chan, + Tcl_GetString(options.name), + format, (Tk_PhotoHandle) masterPtr, options.toX, + options.toY, width, height, options.fromX, options.fromY); + } else { + result = imageFormat87->fileReadProc(interp, chan, + Tcl_GetString(options.name), + format, (Tk_PhotoHandle) masterPtr, options.toX, + options.toY, width, height, options.fromX, options.fromY, + &(options.metadata)); + } if (chan != NULL) { Tcl_Close(NULL, chan); } @@ -1314,6 +1378,7 @@ ImgPhotoCmd( memset(&options, 0, sizeof(options)); options.name = NULL; options.format = NULL; + options.metadata = NULL; if (ParseSubcommandOptions(&options, interp, OPT_FORMAT | OPT_FROM | OPT_GRAYSCALE | OPT_BACKGROUND | OPT_METADATA, @@ -1359,6 +1424,7 @@ ImgPhotoCmd( matched = 0; redoFormatLookup: + imageFormat87 = NULL; for (imageFormat = tsdPtr->formatList; imageFormat != NULL; imageFormat = imageFormat->nextPtr) { if ((fmtString == NULL) @@ -1384,6 +1450,20 @@ ImgPhotoCmd( } } } + if (imageFormat == NULL) { + oldformat = 0; + for (imageFormat87 = tsdPtr->formatList87; imageFormat87 != NULL; + imageFormat87 = imageFormat87->nextPtr) { + if ((fmtString == NULL) + || (strncasecmp(fmtString, imageFormat87->name, + strlen(imageFormat87->name)) == 0)) { + matched = 1; + if (imageFormat87->fileWriteProc != NULL) { + break; + } + } + } + } if (usedExt && !matched) { /* * If we didn't find one and we're using file extensions as the @@ -1395,7 +1475,7 @@ ImgPhotoCmd( fmtString = NULL; goto redoFormatLookup; } - if (imageFormat == NULL) { + if (imageFormat == NULL && imageFormat87 == NULL) { if (fmtString == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no available image file format has file writing" @@ -1422,8 +1502,14 @@ ImgPhotoCmd( if (oldformat && format) { format = (Tcl_Obj *) Tcl_GetString(options.format); } - result = imageFormat->fileWriteProc(interp, - Tcl_GetString(options.name), format, &block); + if (imageFormat != NULL) { + result = imageFormat->fileWriteProc(interp, + Tcl_GetString(options.name), format, &block); + } else { + result = imageFormat87->fileWriteProc(interp, + Tcl_GetString(options.name), format, &block, + options.metadata); + } if (options.background) { Tk_FreeColor(options.background); } @@ -1808,6 +1894,7 @@ ImgPhotoConfigureMaster( double oldGamma; Tcl_Channel chan; Tk_PhotoImageFormat *imageFormat; + Tk_PhotoImageFormat87 *imageFormat87; const char **args; args = (const char **)ckalloc((objc + 1) * sizeof(char *)); @@ -2004,8 +2091,9 @@ ImgPhotoConfigureMaster( if ((Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) || (MatchFileFormat(interp, chan, masterPtr->fileString, - masterPtr->format, &imageFormat, &imageWidth, - &imageHeight, &oldformat) != TCL_OK)) { + masterPtr->format, &(masterPtr->metadata), + &imageFormat, &imageFormat87, + &imageWidth, &imageHeight, &oldformat) != TCL_OK)) { Tcl_Close(NULL, chan); goto errorExit; } @@ -2021,9 +2109,18 @@ ImgPhotoConfigureMaster( if (oldformat && tempformat) { tempformat = (Tcl_Obj *) Tcl_GetString(tempformat); } - result = imageFormat->fileReadProc(interp, chan, - masterPtr->fileString, tempformat, (Tk_PhotoHandle) masterPtr, - 0, 0, imageWidth, imageHeight, 0, 0); + if (imageFormat != NULL) { + result = imageFormat->fileReadProc(interp, chan, + masterPtr->fileString, tempformat, + (Tk_PhotoHandle) masterPtr, + 0, 0, imageWidth, imageHeight, 0, 0); + } else { + result = imageFormat87->fileReadProc(interp, chan, + masterPtr->fileString, tempformat, + (Tk_PhotoHandle) masterPtr, + 0, 0, imageWidth, imageHeight, 0, 0, + &(masterPtr->metadata)); + } Tcl_Close(NULL, chan); if (result != TCL_OK) { goto errorExit; @@ -2038,7 +2135,8 @@ ImgPhotoConfigureMaster( || (masterPtr->format != oldFormat))) { if (MatchStringFormat(interp, masterPtr->dataString, - masterPtr->format, &imageFormat, &imageWidth, + masterPtr->format, &(masterPtr->metadata), + &imageFormat, &imageFormat87, &imageWidth, &imageHeight, &oldformat) != TCL_OK) { goto errorExit; } @@ -2056,10 +2154,18 @@ ImgPhotoConfigureMaster( } tempdata = (Tcl_Obj *) Tcl_GetString(tempdata); } - if (imageFormat->stringReadProc(interp, tempdata, tempformat, - (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, - 0, 0) != TCL_OK) { - goto errorExit; + if (imageFormat != NULL) { + if (imageFormat->stringReadProc(interp, tempdata, tempformat, + (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, + 0, 0) != TCL_OK) { + goto errorExit; + } + } else { + if (imageFormat87->stringReadProc(interp, tempdata, tempformat, + (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, + 0, 0, &(masterPtr->metadata)) != TCL_OK) { + goto errorExit; + } } Tcl_ResetResult(interp); @@ -2452,9 +2558,9 @@ ImgPhotoSetSize( * * Results: * A standard TCL return value. If the return value is TCL_OK, a pointer - * to the image format record is returned in *imageFormatPtr, and the - * width and height of the image are returned in *widthPtr and - * *heightPtr. + * to the image format record is returned in *imageFormatPtr or + * *imageFormat87Ptr, and the width and height of the image are returned + * in *widthPtr and *heightPtr. * * Side effects: * None. @@ -2468,16 +2574,23 @@ MatchFileFormat( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *formatObj, /* User-specified format string, or NULL. */ + Tcl_Obj **metadataObjPtr, /* User-specified metadata and return it */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record - * is returned here. */ + * is returned here. For format87, this is set + * to NULL*/ + Tk_PhotoImageFormat87 **imageFormat87Ptr, + /* A pointer to the photo image format87 record + * is returned here. For non format87, this is + * set to NULL*/ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ int *oldformat) /* Returns 1 if the old image API is used. */ { - int matched = 0, useoldformat = 0; + int matched = 0, useoldformat = 0, use87format = 0; Tk_PhotoImageFormat *formatPtr; + Tk_PhotoImageFormat87 *format87Ptr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); const char *formatString = NULL; @@ -2558,27 +2671,78 @@ MatchFileFormat( } } - if (formatPtr == NULL) { - if ((formatObj != NULL) && !matched) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "image file format \"%s\" is not supported", - formatString)); - Tcl_SetErrorCode(interp, "TK", "LOOKUP", "PHOTO_FORMAT", - formatString, NULL); - } else { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't recognize data in image file \"%s\"", - fileName)); - Tcl_SetErrorCode(interp, "TK", "PHOTO", "IMAGE", - "UNRECOGNIZED_DATA", NULL); + /* + * For old and not 87 format, exit now with success + */ + + if (formatPtr != NULL) { + *imageFormatPtr = formatPtr; + *imageFormat87Ptr = NULL; + *oldformat = useoldformat; + (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); + return TCL_OK; + } + + /* + * Scan through the table of file format 87 handlers to find one which can + * handle the image. + */ + + for (format87Ptr = tsdPtr->formatList87; format87Ptr != NULL; + format87Ptr = format87Ptr->nextPtr) { + if (formatObj != NULL) { + if (strncasecmp(formatString, + format87Ptr->name, strlen(format87Ptr->name)) != 0) { + continue; + } + matched = 1; + if (format87Ptr->fileMatchProc == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "-file option isn't supported for %s images", + formatString)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", + "NOT_FILE_FORMAT", NULL); + return TCL_ERROR; + } + } + if (format87Ptr->fileMatchProc != NULL) { + (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); + + if (format87Ptr->fileMatchProc(chan, fileName, formatObj, + widthPtr, heightPtr, interp, metadataObjPtr)) { + if (*widthPtr < 1) { + *widthPtr = 1; + } + if (*heightPtr < 1) { + *heightPtr = 1; + } + *imageFormat87Ptr = format87Ptr; + *imageFormatPtr = NULL; + *oldformat = 0; + (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); + return TCL_OK; + } } - return TCL_ERROR; } + + /* + * No matching format found + */ - *imageFormatPtr = formatPtr; - *oldformat = useoldformat; - (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); - return TCL_OK; + if ((formatObj != NULL) && !matched) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "image file format \"%s\" is not supported", + formatString)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "PHOTO_FORMAT", + formatString, NULL); + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't recognize data in image file \"%s\"", + fileName)); + Tcl_SetErrorCode(interp, "TK", "PHOTO", "IMAGE", + "UNRECOGNIZED_DATA", NULL); + } + return TCL_ERROR; } /* @@ -2593,9 +2757,9 @@ MatchFileFormat( * * Results: * A standard TCL return value. If the return value is TCL_OK, a pointer - * to the image format record is returned in *imageFormatPtr, and the - * width and height of the image are returned in *widthPtr and - * *heightPtr. + * to the image format record is returned in *imageFormatPtr or + * *imageFormat87Ptr, and the width and height of the image are returned + * in *widthPtr and *heightPtr. * * Side effects: * None. @@ -2608,16 +2772,23 @@ MatchStringFormat( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Obj *data, /* Object containing the image data. */ Tcl_Obj *formatObj, /* User-specified format string, or NULL. */ + Tcl_Obj **metadataObjPtr, /* User-specified metadata and return it */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record - * is returned here. */ + * is returned here. For format87, this is set + * to NULL*/ + Tk_PhotoImageFormat87 **imageFormat87Ptr, + /* A pointer to the photo image format87 record + * is returned here. For non format87, this is + * set to NULL*/ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ int *oldformat) /* Returns 1 if the old image API is used. */ { - int matched = 0, useoldformat = 0; + int matched = 0, useoldformat = 0, use87format = 0; Tk_PhotoImageFormat *formatPtr, *defaultFormatPtr = NULL; + Tk_PhotoImageFormat87 *format87Ptr = NULL; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); const char *formatString = NULL; @@ -2707,6 +2878,36 @@ MatchStringFormat( } if (formatPtr == NULL) { + useoldformat = 0; + for (format87Ptr = tsdPtr->formatList87; format87Ptr != NULL; + format87Ptr = format87Ptr->nextPtr) { + if (formatObj != NULL) { + if (strncasecmp(formatString, + format87Ptr->name, strlen(format87Ptr->name)) != 0) { + continue; + } + matched = 1; + if (format87Ptr->stringMatchProc == NULL) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "-data option isn't supported for %s images", + formatString)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", + "NOT_DATA_FORMAT", NULL); + return TCL_ERROR; + } + } + if ((format87Ptr->stringMatchProc != NULL) + && (format87Ptr->stringReadProc != NULL) + && format87Ptr->stringMatchProc( + (Tcl_Obj *) Tcl_GetString(data), + (Tcl_Obj *) formatString, + widthPtr, heightPtr, interp, metadataObjPtr)) { + break; + } + } + } + + if (formatPtr == NULL && format87Ptr == 0) { /* * Try the default format as last resort (only if no -format option * was passed). @@ -2747,6 +2948,7 @@ MatchStringFormat( } *imageFormatPtr = formatPtr; + *imageFormat87Ptr = format87Ptr; *oldformat = useoldformat; /* diff --git a/generic/tkInt.h b/generic/tkInt.h index ab06435..ff35195 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -1056,7 +1056,7 @@ MODULE_SCOPE const Tcl_ObjType tkTextIndexType; MODULE_SCOPE const Tk_SmoothMethod tkBezierSmoothMethod; MODULE_SCOPE Tk_ImageType tkBitmapImageType; -MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtGIF; +MODULE_SCOPE Tk_PhotoImageFormat87 tkImgFmtGIF; MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; diff --git a/generic/tkWindow.c b/generic/tkWindow.c index b3527e6..4287b2a 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -337,7 +337,7 @@ CreateTopLevelWindow( */ Tk_CreatePhotoImageFormat(&tkImgFmtDefault); - Tk_CreatePhotoImageFormat(&tkImgFmtGIF); + Tk_CreatePhotoImageFormat87(&tkImgFmtGIF); Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); -- cgit v0.12 From 703ed78fe478fe9d52a297c3859512522cd41f3c Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 3 Jun 2020 21:30:20 +0000 Subject: TIP529 image metadata: use metadata copy for put and read. Merge metadata on gif read --- generic/tkImgGIF.c | 30 +++++++++++++++----- generic/tkImgPhoto.c | 77 ++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 25 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index a6e1bc5..b653e02 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -410,6 +410,7 @@ FileReadGIF( unsigned char *trashBuffer = NULL; int bitPixel; Tcl_Obj *metadata; + int dictSize = 0; int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; int transparent = -1; @@ -519,11 +520,21 @@ FileReadGIF( */ /* - * Initialize the metadata dict + * Initialize the metadata dict as an unshared copy of the input metadata. + * We do not want to modify the metadataPtr in case of an error. + * So first get a local unshared copy, which is assigned on success. + */ + + metadata = *metadataPtr; + if (Tcl_IsShared(metadata)) { + metadata = Tcl_DuplicateObj(metadata); + } + + /* * Start with a ref count of 1 to delete it in the error case with * Tcl_DecrRefCount(). */ - metadata = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadata); /* @@ -773,13 +784,18 @@ FileReadGIF( result = TCL_OK; /* - * Return the metadata object - * Increment the refcount again, as it is decremented below + * Merge the metadata object if there is data in the dict. */ - if (*metadataPtr != NULL) { - Tcl_DecrRefCount(*metadataPtr); + + if (TCL_OK != (result = Tcl_DictObjSize(interp,metadata, &dictSize))) { + goto error; + } + if (dictSize > 0) { + if (*metadataPtr != NULL) { + Tcl_DecrRefCount(*metadataPtr); + } + *metadataPtr = metadata; } - *metadataPtr = metadata; /* * If a trash buffer has been allocated, free it now. diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index b4ad271..8f5e69f 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -930,7 +930,8 @@ ImgPhotoCmd( } case PHOTO_PUT: { - Tcl_Obj *format, *data; + Tcl_Obj *format, *data, *metadata; + int result; /* * photo put command - first parse the options. @@ -952,14 +953,28 @@ ImgPhotoCmd( } /* + * Prepare a metadata dict. + * If the object pointer points to NULL, there is no metadata dict on input. + * The match and read calls may modify it and change it from NULL. + * ToDo: think about ref counting and freeing it below + */ + if (options.metadata == NULL) { + metadata = NULL; + } else { + metadata = options.metadata; + Tcl_IncrRefCount(metadata); + } + + /* * See if there's a format that can read the data */ if (MatchStringFormat(interp, objv[2], options.format, - &(options.metadata), &imageFormat, + &metadata, &imageFormat, &imageFormat87, &imageWidth, &imageHeight, &oldformat) != TCL_OK) { - return TCL_ERROR; + result = TCL_ERROR; + goto putsCleanup; } if (!(options.options & OPT_TO) || (options.toX2 < 0)) { @@ -986,15 +1001,17 @@ ImgPhotoCmd( (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, options.toY2 - options.toY, 0, 0) != TCL_OK) { - return TCL_ERROR; + result = TCL_ERROR; + goto putsCleanup; } } else { if (imageFormat87->stringReadProc(interp, data, format, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, options.toY2 - options.toY, 0, 0, - &(options.metadata)) != TCL_OK) { - return TCL_ERROR; + &metadata) != TCL_OK) { + result = TCL_ERROR; + goto putsCleanup; } } /* @@ -1004,11 +1021,15 @@ ImgPhotoCmd( * IMAGE_CHANGED bit. */ masterPtr->flags |= IMAGE_CHANGED; - - return TCL_OK; + result = TCL_OK; +putsCleanup: + if (metadata != NULL) + Tcl_DecrRefCount(metadata); + return result; } case PHOTO_READ: { - Tcl_Obj *format; + Tcl_Obj *format, *metadata; + int result; /* * photo read command - first parse the options specified. @@ -1060,13 +1081,26 @@ ImgPhotoCmd( return TCL_ERROR; } + /* + * Prepare a metadata dict. + * If the object pointer points to NULL, there is no metadata dict on input. + * The match and read calls may modify it and change it from NULL. + * ToDo: think about ref counting and freeing it below + */ + if (options.metadata == NULL) { + metadata = NULL; + } else { + metadata = options.metadata; + Tcl_IncrRefCount(metadata); + } + if (MatchFileFormat(interp, chan, Tcl_GetString(options.name), options.format, - &(options.metadata), &imageFormat, + &metadata, &imageFormat, &imageFormat87, &imageWidth, &imageHeight, &oldformat) != TCL_OK) { - Tcl_Close(NULL, chan); - return TCL_ERROR; + result = TCL_ERROR; + goto readCleanup; } /* @@ -1080,8 +1114,8 @@ ImgPhotoCmd( "coordinates for -from option extend outside source image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", "BAD_FROM", NULL); - Tcl_Close(NULL, chan); - return TCL_ERROR; + result = TCL_ERROR; + goto readCleanup; } if (!(options.options & OPT_FROM) || (options.fromX2 < 0)) { width = imageWidth - options.fromX; @@ -1102,7 +1136,8 @@ ImgPhotoCmd( Tcl_SetObjResult(interp, Tcl_NewStringObj( TK_PHOTO_ALLOC_FAILURE_MESSAGE, -1)); Tcl_SetErrorCode(interp, "TK", "MALLOC", NULL); - return TCL_ERROR; + result = TCL_ERROR; + goto readCleanup; } } @@ -1125,8 +1160,11 @@ ImgPhotoCmd( Tcl_GetString(options.name), format, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, width, height, options.fromX, options.fromY, - &(options.metadata)); + &metadata); } +readCleanup: + if (metadata != NULL) + Tcl_DecrRefCount(metadata); if (chan != NULL) { Tcl_Close(NULL, chan); } @@ -2130,6 +2168,10 @@ ImgPhotoConfigureMaster( masterPtr->flags |= IMAGE_CHANGED; } + /* + * ToDo: The case of a a dataString newly set to the empty string with a + * present metadata dict should also cause this. + */ if ((masterPtr->fileString == NULL) && (masterPtr->dataString != NULL) && ((masterPtr->dataString != oldData) || (masterPtr->format != oldFormat))) { @@ -2899,8 +2941,7 @@ MatchStringFormat( if ((format87Ptr->stringMatchProc != NULL) && (format87Ptr->stringReadProc != NULL) && format87Ptr->stringMatchProc( - (Tcl_Obj *) Tcl_GetString(data), - (Tcl_Obj *) formatString, + data, formatObj, widthPtr, heightPtr, interp, metadataObjPtr)) { break; } -- cgit v0.12 From e265abcfab16c99859b2538e0961693f963c6bcf Mon Sep 17 00:00:00 2001 From: oehhar Date: Thu, 4 Jun 2020 20:13:12 +0000 Subject: TIP529 image metadata: image data with gif comment test --- generic/tkImgGIF.c | 11 ++- generic/tkImgPhoto.c | 23 +++++- tests/imgPhoto.test | 207 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 169 insertions(+), 72 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index b653e02..1036bae 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -401,6 +401,7 @@ FileReadGIF( int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ + /* may point to a NULL pointer */ { int fileWidth, fileHeight, imageWidth, imageHeight; unsigned int nBytes; @@ -526,8 +527,12 @@ FileReadGIF( */ metadata = *metadataPtr; - if (Tcl_IsShared(metadata)) { - metadata = Tcl_DuplicateObj(metadata); + if (metadata == NULL) { + metadata = Tcl_NewDictObj(); + } else { + if (Tcl_IsShared(metadata)) { + metadata = Tcl_DuplicateObj(metadata); + } } /* @@ -2137,7 +2142,7 @@ CommonWriteGIF( blocklength = (unsigned char)length; length = 0; } - writeProc(handle, (char *) blocklength, 1); + writeProc(handle, (char *) &blocklength, 1); writeProc(handle, (char *) comment, blocklength); comment += blocklength; } diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 8f5e69f..ef5f38d 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -746,6 +746,7 @@ ImgPhotoCmd( */ Tk_ImageStringWriteProc *stringWriteProc = NULL; + Tk_ImageStringWriteProc87 *stringWriteProc87 = NULL; index = 1; memset(&options, 0, sizeof(options)); @@ -818,6 +819,21 @@ ImgPhotoCmd( } } if (stringWriteProc == NULL) { + oldformat = 0; + for (imageFormat87 = tsdPtr->formatList87; imageFormat87 != NULL; + imageFormat87 = imageFormat87->nextPtr) { + if ((strncasecmp(Tcl_GetString(options.format), + imageFormat87->name, + strlen(imageFormat87->name)) == 0)) { + matched = 1; + if (imageFormat87->stringWriteProc != NULL) { + stringWriteProc87 = imageFormat87->stringWriteProc; + break; + } + } + } + } + if (stringWriteProc == NULL && stringWriteProc87 == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "image string format \"%s\" is %s", Tcl_GetString(options.format), @@ -833,7 +849,10 @@ ImgPhotoCmd( data = ImgGetPhoto(masterPtr, &block, &options); - if (oldformat) { + if (stringWriteProc == NULL) { + result = (stringWriteProc87)(interp, + options.format, &block, options.metadata); + } else if (oldformat) { Tcl_DString buffer; typedef int (*OldStringWriteProc)(Tcl_Interp *interp, Tcl_DString *dataPtr, const char *formatString, @@ -956,7 +975,7 @@ ImgPhotoCmd( * Prepare a metadata dict. * If the object pointer points to NULL, there is no metadata dict on input. * The match and read calls may modify it and change it from NULL. - * ToDo: think about ref counting and freeing it below + * The refcount of options.metadata is >= 1 */ if (options.metadata == NULL) { metadata = NULL; diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index b7434a9..c75a126 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2150,121 +2150,194 @@ set gifend "\x3b" test imgPhoto-23.1 {GIF comment before image data} -setup { set data $::gifstart - # Append a comment extension block with data "ABCD" - append data "\x21\xfe\x04" "ABCD" "\x0" - # Trailer - append data $::gifdata $::gifend + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend } -body { image create photo gif1 -data $data - gif1 cget -metadata + gif1 cget -metadata } -cleanup { catch {image delete gif1} } -result {Comment ABCD} test imgPhoto-23.2 {GIF comment after image data} -setup { set data $::gifstart - append data $::gifdata - # Append a comment extension block with data "ABCD" - append data "\x21\xfe\x04" "ABCD" "\x0" - # Trailer - append data $::gifend + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend } -body { image create photo gif1 -data $data - gif1 cget -metadata + gif1 cget -metadata } -cleanup { catch {image delete gif1} } -result {Comment ABCD} test imgPhoto-23.3 {Two GIF comment blocks} -setup { set data $::gifstart - # Append a comment extension block with data "1234" - append data "\x21\xfe\x04" "1234" "\x0" - append data $::gifdata - # Append a comment extension block with data "ABCD" - append data "\x21\xfe\x04" "ABCD" "\x0" - # Trailer - append data $::gifend + # Append a comment extension block with data "1234" + append data "\x21\xfe\x04" "1234" "\x0" + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend } -body { image create photo gif1 -data $data - gif1 cget -metadata + gif1 cget -metadata } -cleanup { catch {image delete gif1} } -result {Comment ABCD} test imgPhoto-23.4 {XMP comment block before image} -setup { set data $::gifstart - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - append data $::gifdata - # Trailer - append data $::gifend + # Append an XMP comment extension block (including a Unicode codepoint 2022 + set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + append data $::gifdata + # Trailer + append data $::gifend } -body { image create photo gif1 -data $data - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} } -cleanup { catch {image delete gif1} } -result {1} test imgPhoto-23.5 {XMP comment block after image} -setup { set data $::gifstart - append data $::gifdata + append data $::gifdata - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - # Trailer - append data $::gifend + # Append an XMP comment extension block (including a Unicode codepoint 2022 + set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + # Trailer + append data $::gifend } -body { image create photo gif1 -data $data - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} } -cleanup { catch {image delete gif1} } -result {1} test imgPhoto-23.6 {empty XMP comment block after image} -setup { set data $::gifstart - append data $::gifdata + append data $::gifdata - append data "\x21\xff\x0B" "XMP DataXMP" - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - # Trailer - append data $::gifend + append data "\x21\xff\x0B" "XMP DataXMP" + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + # Trailer + append data $::gifend } -body { image create photo gif1 -data $data - dict get [gif1 cget -metadata] XMP + dict get [gif1 cget -metadata] XMP } -cleanup { catch {image delete gif1} } -result {} +test imgPhoto-23.7 {create: test if shared metadata object is not preserved} -setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend +} -body { + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + image create photo gif1 -data $data -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.8 {configure: test if shared metadata object is not preserved} -setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend +} -body { + image create photo gif1 + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + gif1 configure -data $data -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.9 {configure: test if shared metadata object is not preserved} -setup { + set data $::gifstart$::gifdata$::gifend +} -body { + image create photo gif1 -data $data + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + gif1 configure -data $data -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.9 {configure: test if shared metadata object is not preserved} -setup { + set data $::gifstart$::gifdata$::gifend +} -body { + image create photo gif1 -data $data + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + gif1 configure -data $data -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.10 {output data with comment} -setup { + set data $::gifstart$::gifdata$::gifend +} -body { + image create photo gif1 -data $data + set gifData [gif1 data -format gif -metadata [dict create Comment ABCD]] +} -cleanup { + catch {image delete gif1} +} -match glob -result {*ABCD*} + unset -nocomplain gifstart gifdata gifend -- cgit v0.12 From bfcb4bfc8122be5094ac6354dc03145e6b123908 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sat, 6 Jun 2020 18:07:00 +0000 Subject: TIP529 image metadata: changed driver interface: split metadata to in/out object, driver communication dstring, file close flag, reorder all options in a more logical order. Forking intended, will merge later. Thanks Jan, for your work. --- generic/tk.h | 27 ++--- generic/tkImgGIF.c | 153 +++++++++++--------------- generic/tkImgPhoto.c | 295 +++++++++++++++++++++++++++------------------------ generic/tkStubInit.c | 3 +- 4 files changed, 230 insertions(+), 248 deletions(-) diff --git a/generic/tk.h b/generic/tk.h index 5aa8998..cd16ce0 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1441,26 +1441,29 @@ typedef int (Tk_ImageStringWriteProc) (Tcl_Interp *interp, Tcl_Obj *format, /* * The following alternate definitions are used with the Tk8.7 file format - * supporting a metadata dict + * supporting a metadata dict, internal dstring and close file flag */ typedef struct Tk_PhotoImageFormat87 Tk_PhotoImageFormat87; -typedef int (Tk_ImageFileMatchProc87) (Tcl_Channel chan, const char *fileName, - Tcl_Obj *format, int *widthPtr, int *heightPtr, Tcl_Interp *interp, - Tcl_Obj **metadataPtr); -typedef int (Tk_ImageStringMatchProc87) (Tcl_Obj *dataObj, Tcl_Obj *format, - int *widthPtr, int *heightPtr, Tcl_Interp *interp, Tcl_Obj **metadataPtr); +typedef int (Tk_ImageFileMatchProc87) (Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, + int *heightPtr, int *closeChannelPtr, Tcl_DString *driverInternal); +typedef int (Tk_ImageStringMatchProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, + Tcl_DString *driverInternal); typedef int (Tk_ImageFileReadProc87) (Tcl_Interp *interp, Tcl_Channel chan, - const char *fileName, Tcl_Obj *format, Tk_PhotoHandle imageHandle, + const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, - Tcl_Obj **metadataPtr); + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); typedef int (Tk_ImageStringReadProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, - int width, int height, int srcX, int srcY, Tcl_Obj **metadataPtr); + Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, int srcX, int srcY, + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); typedef int (Tk_ImageFileWriteProc87) (Tcl_Interp *interp, const char *fileName, - Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); + Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); typedef int (Tk_ImageStringWriteProc87) (Tcl_Interp *interp, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); /* diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 1036bae..f9f0bea 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -118,29 +118,33 @@ typedef size_t (WriteBytesFunc) (ClientData clientData, const char *bytes, * The format record for the GIF file format: */ -static int FileMatchGIF(Tcl_Channel chan, const char *fileName, - Tcl_Obj *format, int *widthPtr, int *heightPtr, - Tcl_Interp *interp, Tcl_Obj **metadataPtr); +static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *format, + Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, + int *closeChannelPtr, Tcl_DString *driverInternal); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, - Tk_PhotoHandle imageHandle, int destX, int destY, - int width, int height, int srcX, int srcY, - Tcl_Obj **metadataPtr); -static int StringMatchGIF(Tcl_Obj *dataObj, Tcl_Obj *format, - int *widthPtr, int *heightPtr, Tcl_Interp *interp, - Tcl_Obj **metadataPtr); + Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY, + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); +static int StringMatchGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, + int *heightPtr, Tcl_DString *driverInternal); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tk_PhotoHandle imageHandle, + Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj **metadataPtr); + int srcX, int srcY, + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); static int FileWriteGIF(Tcl_Interp *interp, const char *filename, - Tcl_Obj *format, Tk_PhotoImageBlock *blockPtr, - Tcl_Obj *metadata); + Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoImageBlock *blockPtr); static int StringWriteGIF(Tcl_Interp *interp, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); static int CommonWriteGIF(Tcl_Interp *interp, ClientData clientData, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, Tcl_Obj *metadata); + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); Tk_PhotoImageFormat87 tkImgFmtGIF = { "gif", /* name */ @@ -172,7 +176,7 @@ 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 *metadata); + int *transparent, Tcl_Obj *metadataOut); static int GetCode(Tcl_Channel chan, int code_size, int flag, GIFImageConfig *gifConfPtr); static int GetDataBlock(GIFImageConfig *gifConfPtr, @@ -350,14 +354,16 @@ static void FlushChar(GIFState_t *statePtr); static int FileMatchGIF( + Tcl_Interp *dummy, /* not used */ Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *format, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataIn, /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Interp *dummy, /* not used */ - Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ + int *closeChannelPtr, /* Return if the channel may be closed */ + Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { GIFImageConfig gifConf; (void)fileName; @@ -393,6 +399,7 @@ FileReadGIF( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *format, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataIn, /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* The photo image to write into. */ int destX, int destY, /* Coordinates of top-left pixel in photo * image to be written to. */ @@ -400,8 +407,8 @@ FileReadGIF( * written to. */ int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ - Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ - /* may point to a NULL pointer */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternal)/* memory passed from FileMatchGIF */ { int fileWidth, fileHeight, imageWidth, imageHeight; unsigned int nBytes; @@ -410,7 +417,6 @@ FileReadGIF( unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; - Tcl_Obj *metadata; int dictSize = 0; int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; @@ -521,28 +527,6 @@ FileReadGIF( */ /* - * Initialize the metadata dict as an unshared copy of the input metadata. - * We do not want to modify the metadataPtr in case of an error. - * So first get a local unshared copy, which is assigned on success. - */ - - metadata = *metadataPtr; - if (metadata == NULL) { - metadata = Tcl_NewDictObj(); - } else { - if (Tcl_IsShared(metadata)) { - metadata = Tcl_DuplicateObj(metadata); - } - } - - /* - * Start with a ref count of 1 to delete it in the error case with - * Tcl_DecrRefCount(). - */ - - Tcl_IncrRefCount(metadata); - - /* * Search for the frame from the GIF to display. */ @@ -567,7 +551,8 @@ FileReadGIF( goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadata) < 0) { + gifConfPtr->workingBuffer, &transparent, metadataOut) + < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", @@ -762,7 +747,8 @@ FileReadGIF( goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadata) < 0) { + gifConfPtr->workingBuffer, &transparent, metadataOut) + < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "GIF", "BAD_EXT", @@ -788,30 +774,8 @@ FileReadGIF( Tcl_SetObjResult(interp, Tcl_NewStringObj(tkImgFmtGIF.name, -1)); result = TCL_OK; - /* - * Merge the metadata object if there is data in the dict. - */ - - if (TCL_OK != (result = Tcl_DictObjSize(interp,metadata, &dictSize))) { - goto error; - } - if (dictSize > 0) { - if (*metadataPtr != NULL) { - Tcl_DecrRefCount(*metadataPtr); - } - *metadataPtr = metadata; - } - - /* - * If a trash buffer has been allocated, free it now. - */ - - if (trashBuffer != NULL) { - ckfree(trashBuffer); - } - return result; - - error: +error: + /* * If a trash buffer has been allocated, free it now. */ @@ -819,12 +783,6 @@ FileReadGIF( if (trashBuffer != NULL) { ckfree(trashBuffer); } - - /* - * free the metadata object in case of error. - * Otherwise, it is not freed as the ref count was incremented above. - */ - Tcl_DecrRefCount(metadata); return result; } @@ -888,12 +846,13 @@ ReadOneByte( static int StringMatchGIF( + Tcl_Interp *dummy, /* not used */ Tcl_Obj *dataObj, /* the object containing the image data */ Tcl_Obj *format, /* the image format object, or NULL */ + Tcl_Obj *metadataIn, /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Interp *dummy, /* not used */ - Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ + Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ { unsigned char *data, header[10]; TkSizeT got, length; @@ -961,11 +920,13 @@ StringReadGIF( Tcl_Interp *interp, /* interpreter for reporting errors in */ Tcl_Obj *dataObj, /* object containing the image */ Tcl_Obj *format, /* format object, or NULL */ + Tcl_Obj *metadataIn, /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ int srcX, int srcY, - Tcl_Obj **metadataPtr) /* metadata to investigate and to return */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternal)/* memory passed from StringReadGIF */ { MFile handle, *hdlPtr = &handle; TkSizeT length; @@ -994,7 +955,9 @@ StringReadGIF( */ return FileReadGIF(interp, (Tcl_Channel) hdlPtr, xferFormat, format, - imageHandle, destX, destY, width, height, srcX, srcY, metadataPtr); + metadataIn, + imageHandle, destX, destY, width, height, srcX, srcY, + metadataOut, driverInternal); } /* @@ -1103,7 +1066,7 @@ DoExtension( int label, unsigned char *buf, /* defined as 280 byte working buffer */ int *transparent, - Tcl_Obj *metadata) + Tcl_Obj *metadataOut) { int count; /* Prepare extension name @@ -1139,7 +1102,8 @@ DoExtension( return -1; } /* Detect XMP extension */ - if (0 == memcmp(buf,"XMP DataXMP",11)) { + if (NULL != metadataOut + && 0 == memcmp(buf,"XMP DataXMP",11)) { /* XMP format does not use the block structure of GIF * The data is utf-8 which never contains 0's * A magic trailer of 258 bytes is added with the following data: @@ -1180,7 +1144,7 @@ DoExtension( encoding = Tcl_GetEncoding(NULL, "utf-8"); Tcl_DStringInit(&recodedDString); Tcl_ExternalToUtfDString(encoding, Tcl_DStringValue(&dataDString), length, &recodedDString); - result = Tcl_DictObjPut(NULL, metadata, + result = Tcl_DictObjPut(NULL, metadataOut, Tcl_NewStringObj("XMP",-1), Tcl_NewStringObj(Tcl_DStringValue(&recodedDString), Tcl_DStringLength(&recodedDString))); @@ -1208,7 +1172,8 @@ DoExtension( break; } /* Add extension to dict */ - if (extensionStreamName[0] != '\0' ) { + if (NULL != metadataOut + && extensionStreamName[0] != '\0' ) { Tcl_Obj *metadataData; int length = 0; for (;;) { @@ -1218,8 +1183,9 @@ DoExtension( return -1; case 0: /* end of data */ if (length > 0) { - if ( TCL_OK != Tcl_DictObjPut(NULL, metadata, - Tcl_NewByteArrayObj((unsigned char *)extensionStreamName, + if ( TCL_OK != Tcl_DictObjPut(NULL, metadataOut, + Tcl_NewByteArrayObj( + (unsigned char *)extensionStreamName, strlen(extensionStreamName)), metadataData)) { return -1; } @@ -1906,8 +1872,8 @@ FileWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ const char *filename, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, - Tcl_Obj *metadata) + Tcl_Obj *metadata, + Tk_PhotoImageBlock *blockPtr) { Tcl_Channel chan = NULL; int result; @@ -1922,7 +1888,8 @@ FileWriteGIF( return TCL_ERROR; } - result = CommonWriteGIF(interp, chan, WriteToChannel, format, blockPtr, metadata); + result = CommonWriteGIF(interp, chan, WriteToChannel, format, metadata, + blockPtr); if (Tcl_Close(interp, chan) == TCL_ERROR) { return TCL_ERROR; @@ -1937,15 +1904,15 @@ StringWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors and * returning the GIF data. */ Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, - Tcl_Obj *metadata) + Tcl_Obj *metadata, + Tk_PhotoImageBlock *blockPtr) { int result; Tcl_Obj *objPtr = Tcl_NewObj(); Tcl_IncrRefCount(objPtr); result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, - blockPtr, metadata); + metadata, blockPtr); if (result == TCL_OK) { Tcl_SetObjResult(interp, objPtr); } @@ -1985,8 +1952,8 @@ CommonWriteGIF( ClientData handle, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tk_PhotoImageBlock *blockPtr, - Tcl_Obj *metadata) + Tcl_Obj *metadata, + Tk_PhotoImageBlock *blockPtr) { GifWriterState state; int resolution; diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index ef5f38d..c79f128 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -200,16 +200,21 @@ static char * ImgGetPhoto(PhotoMaster *masterPtr, struct SubcommandOptions *optPtr); static int MatchFileFormat(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *formatString, - Tcl_Obj **metadataObjPtr, + Tcl_Obj *metadataInObj, + Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, Tk_PhotoImageFormat87 **imageFormat87Ptr, - int *widthPtr, int *heightPtr, int *oldformat); + int *widthPtr, int *heightPtr, int *oldformat, + int *closeChannelPtr, + Tcl_DString *driverInternalPtr); static int MatchStringFormat(Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *formatString, - Tcl_Obj **metadataObjPtr, + Tcl_Obj *metadataInObj, + Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, Tk_PhotoImageFormat87 **imageFormat87Ptr, - int *widthPtr, int *heightPtr, int *oldformat); + int *widthPtr, int *heightPtr, int *oldformat, + Tcl_DString *driverInternalPtr); static const char * GetExtension(const char *path); /* @@ -851,7 +856,7 @@ ImgPhotoCmd( if (stringWriteProc == NULL) { result = (stringWriteProc87)(interp, - options.format, &block, options.metadata); + options.format, options.metadata, &block); } else if (oldformat) { Tcl_DString buffer; typedef int (*OldStringWriteProc)(Tcl_Interp *interp, @@ -949,9 +954,9 @@ ImgPhotoCmd( } case PHOTO_PUT: { - Tcl_Obj *format, *data, *metadata; int result; - + Tcl_Obj *format, *data; + Tcl_DString driverInternalDString; /* * photo put command - first parse the options. */ @@ -970,30 +975,24 @@ ImgPhotoCmd( Tcl_WrongNumArgs(interp, 2, objv, "data ?-option value ...?"); return TCL_ERROR; } - + /* - * Prepare a metadata dict. - * If the object pointer points to NULL, there is no metadata dict on input. - * The match and read calls may modify it and change it from NULL. - * The refcount of options.metadata is >= 1 + * Prepare memory connection between format match and read function */ - if (options.metadata == NULL) { - metadata = NULL; - } else { - metadata = options.metadata; - Tcl_IncrRefCount(metadata); - } + + Tcl_DStringInit(&driverInternalDString); /* * See if there's a format that can read the data */ if (MatchStringFormat(interp, objv[2], options.format, - &metadata, &imageFormat, - &imageFormat87, &imageWidth, &imageHeight, &oldformat) + options.metadata, NULL, &imageFormat, + &imageFormat87, &imageWidth, &imageHeight, &oldformat, + &driverInternalDString) != TCL_OK) { result = TCL_ERROR; - goto putsCleanup; + goto putCleanup; } if (!(options.options & OPT_TO) || (options.toX2 < 0)) { @@ -1021,18 +1020,21 @@ ImgPhotoCmd( options.toX2 - options.toX, options.toY2 - options.toY, 0, 0) != TCL_OK) { result = TCL_ERROR; - goto putsCleanup; + goto putCleanup; } } else { if (imageFormat87->stringReadProc(interp, data, format, + options.metadata, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, options.toY2 - options.toY, 0, 0, - &metadata) != TCL_OK) { - result = TCL_ERROR; - goto putsCleanup; + NULL, &driverInternalDString) + != TCL_OK) { + result = TCL_ERROR; + goto putCleanup; } } + /* * SB: is the next line really needed? The stringReadProc * writes image data with Tk_PhotoPutBlock(), which in turn @@ -1040,15 +1042,18 @@ ImgPhotoCmd( * IMAGE_CHANGED bit. */ masterPtr->flags |= IMAGE_CHANGED; + result = TCL_OK; -putsCleanup: - if (metadata != NULL) - Tcl_DecrRefCount(metadata); +putCleanup: + Tcl_DStringFree(&driverInternalDString); return result; + } case PHOTO_READ: { - Tcl_Obj *format, *metadata; + Tcl_Obj *format; int result; + int closeChannel = 0; + Tcl_DString driverInternalDString; /* * photo read command - first parse the options specified. @@ -1101,22 +1106,16 @@ putsCleanup: } /* - * Prepare a metadata dict. - * If the object pointer points to NULL, there is no metadata dict on input. - * The match and read calls may modify it and change it from NULL. - * ToDo: think about ref counting and freeing it below - */ - if (options.metadata == NULL) { - metadata = NULL; - } else { - metadata = options.metadata; - Tcl_IncrRefCount(metadata); - } - + * Prepare memory connection between format match and read function + */ + + Tcl_DStringInit(&driverInternalDString); + closeChannel = 0; if (MatchFileFormat(interp, chan, Tcl_GetString(options.name), options.format, - &metadata, &imageFormat, - &imageFormat87, &imageWidth, &imageHeight, &oldformat) + options.metadata, NULL, &imageFormat, + &imageFormat87, &imageWidth, &imageHeight, &oldformat, + &closeChannel, &driverInternalDString) != TCL_OK) { result = TCL_ERROR; goto readCleanup; @@ -1177,13 +1176,12 @@ putsCleanup: } else { result = imageFormat87->fileReadProc(interp, chan, Tcl_GetString(options.name), - format, (Tk_PhotoHandle) masterPtr, options.toX, - options.toY, width, height, options.fromX, options.fromY, - &metadata); + format, options.metadata, (Tk_PhotoHandle) masterPtr, + options.toX, options.toY, width, height, options.fromX, + options.fromY, NULL, &driverInternalDString); } readCleanup: - if (metadata != NULL) - Tcl_DecrRefCount(metadata); + Tcl_DStringFree(&driverInternalDString); if (chan != NULL) { Tcl_Close(NULL, chan); } @@ -1564,8 +1562,8 @@ readCleanup: Tcl_GetString(options.name), format, &block); } else { result = imageFormat87->fileWriteProc(interp, - Tcl_GetString(options.name), format, &block, - options.metadata); + Tcl_GetString(options.name), format, options.metadata, + &block); } if (options.background) { Tk_FreeColor(options.background); @@ -1944,7 +1942,8 @@ ImgPhotoConfigureMaster( { PhotoInstance *instancePtr; const char *oldFileString, *oldPaletteString; - Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL, *metadata = NULL; + Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL, + *metadataIn = NULL, *metadataOut = NULL; Tcl_Obj *tempdata, *tempformat; TkSizeT length; int i, j, result, imageWidth, imageHeight, oldformat; @@ -1953,6 +1952,8 @@ ImgPhotoConfigureMaster( Tk_PhotoImageFormat *imageFormat; Tk_PhotoImageFormat87 *imageFormat87; const char **args; + Tcl_DString driverInternalDString; + int closeChannel; args = (const char **)ckalloc((objc + 1) * sizeof(char *)); for (i = 0, j = 0; i < objc; i++,j++) { @@ -1987,7 +1988,7 @@ ImgPhotoConfigureMaster( } else if ((args[j][1] == 'm') && !strncmp(args[j], "-metadata", length)) { if (++i < objc) { - metadata = objv[i]; + metadataIn = objv[i]; j--; } else { ckfree(args); @@ -2002,6 +2003,12 @@ ImgPhotoConfigureMaster( } /* + * Prepare memory connection between format match and read function + */ + + Tcl_DStringInit(&driverInternalDString); + + /* * Save the current values for fileString and dataString, so we can tell * if the user specifies them anew. IMPORTANT: if the format changes we * have to interpret "-file" and "-data" again as well! It might be that @@ -2078,29 +2085,30 @@ ImgPhotoConfigureMaster( } masterPtr->format = format; } - if (metadata) { + if (metadataIn) { /* * make -metadata a dict and take it if keys in. * Otherwise set a metadata null pointer. */ int dictSize; - if (TCL_OK != Tcl_DictObjSize(interp,metadata, &dictSize)) { + if (TCL_OK != Tcl_DictObjSize(interp,metadataIn, &dictSize)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "value for \"-metadata\" not a dict", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", "UNRECOGNIZED_DATA", NULL); return TCL_ERROR; } + if (dictSize > 0) { - Tcl_IncrRefCount(metadata); + Tcl_IncrRefCount(metadataIn); } else { - metadata = NULL; + metadataIn = NULL; } if (masterPtr->metadata) { Tcl_DecrRefCount(masterPtr->metadata); } - masterPtr->metadata = metadata; + masterPtr->metadata = metadataIn; } /* * Set the image to the user-requested size, if any, and make sure storage @@ -2123,6 +2131,7 @@ ImgPhotoConfigureMaster( if ((masterPtr->fileString != NULL) && ((masterPtr->fileString != oldFileString) || (masterPtr->format != oldFormat))) { + /* * Prevent file system access in a safe interpreter. */ @@ -2141,6 +2150,8 @@ ImgPhotoConfigureMaster( goto errorExit; } + closeChannel = 0; + /* * -translation binary also sets -encoding binary */ @@ -2148,9 +2159,10 @@ ImgPhotoConfigureMaster( if ((Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) || (MatchFileFormat(interp, chan, masterPtr->fileString, - masterPtr->format, &(masterPtr->metadata), + masterPtr->format, masterPtr->metadata, NULL, &imageFormat, &imageFormat87, - &imageWidth, &imageHeight, &oldformat) != TCL_OK)) { + &imageWidth, &imageHeight, &oldformat, &closeChannel, + &driverInternalDString) != TCL_OK)) { Tcl_Close(NULL, chan); goto errorExit; } @@ -2173,10 +2185,10 @@ ImgPhotoConfigureMaster( 0, 0, imageWidth, imageHeight, 0, 0); } else { result = imageFormat87->fileReadProc(interp, chan, - masterPtr->fileString, tempformat, + masterPtr->fileString, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0, - &(masterPtr->metadata)); + metadataOut, &driverInternalDString); } Tcl_Close(NULL, chan); if (result != TCL_OK) { @@ -2196,9 +2208,9 @@ ImgPhotoConfigureMaster( || (masterPtr->format != oldFormat))) { if (MatchStringFormat(interp, masterPtr->dataString, - masterPtr->format, &(masterPtr->metadata), + masterPtr->format, masterPtr->metadata, NULL, &imageFormat, &imageFormat87, &imageWidth, - &imageHeight, &oldformat) != TCL_OK) { + &imageHeight, &oldformat, &driverInternalDString) != TCL_OK) { goto errorExit; } if (ImgPhotoSetSize(masterPtr, imageWidth, imageHeight) != TCL_OK) { @@ -2222,9 +2234,18 @@ ImgPhotoConfigureMaster( goto errorExit; } } else { + + /* + * Flag that we want the metadata result dict + */ + + metadataOut = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadataOut); + if (imageFormat87->stringReadProc(interp, tempdata, tempformat, - (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, - 0, 0, &(masterPtr->metadata)) != TCL_OK) { + masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, + imageWidth, imageHeight, 0, 0, metadataOut, + &driverInternalDString) != TCL_OK) { goto errorExit; } } @@ -2234,6 +2255,48 @@ ImgPhotoConfigureMaster( } /* + * Merge driver returned metadata and master metadata + */ + if (metadataOut != NULL) { + int dictSize; + if (TCL_OK != Tcl_DictObjSize(interp,metadataOut, &dictSize)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "driver metadata not a dict", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", + "UNRECOGNIZED_DATA", NULL); + goto errorExit; + } + if (dictSize > 0) { + + /* + * We have driver return metadata + */ + + if (masterPtr->metadata == NULL) { + masterPtr->metadata = metadataOut; + metadataOut = NULL; + } else { + Tcl_DictSearch search; + Tcl_Obj *key, *value; + int done; + + if (Tcl_IsShared(masterPtr->metadata)) { + masterPtr->metadata = Tcl_DuplicateObj(masterPtr->metadata); + Tcl_IncrRefCount(masterPtr->metadata); + } + + if (Tcl_DictObjFirst(interp, metadataOut, &search, &key, &value, + &done) != TCL_OK) { + goto errorExit; + } + for (; !done ; Tcl_DictObjNext(&search, &key, &value, &done)) { + Tcl_DictObjPut(interp, masterPtr->metadata, key, value); + } + } + } + } + + /* * Enforce a reasonable value for gamma. */ @@ -2265,24 +2328,32 @@ ImgPhotoConfigureMaster( masterPtr->height, masterPtr->width, masterPtr->height); masterPtr->flags &= ~IMAGE_CHANGED; + Tcl_DStringInit(&driverInternalDString); if (oldData != NULL) { Tcl_DecrRefCount(oldData); } if (oldFormat != NULL) { Tcl_DecrRefCount(oldFormat); } + if (metadataOut != NULL) { + Tcl_DecrRefCount(metadataOut); + } ToggleComplexAlphaIfNeeded(masterPtr); return TCL_OK; errorExit: + Tcl_DStringInit(&driverInternalDString); if (oldData != NULL) { Tcl_DecrRefCount(oldData); } if (oldFormat != NULL) { Tcl_DecrRefCount(oldFormat); } + if (metadataOut != NULL) { + Tcl_DecrRefCount(metadataOut); + } return TCL_ERROR; } @@ -2635,7 +2706,8 @@ MatchFileFormat( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *formatObj, /* User-specified format string, or NULL. */ - Tcl_Obj **metadataObjPtr, /* User-specified metadata and return it */ + Tcl_Obj *metadataInObj, /* User-specified metadata, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata to return, may be NULL */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record * is returned here. For format87, this is set @@ -2647,7 +2719,10 @@ MatchFileFormat( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ - int *oldformat) /* Returns 1 if the old image API is used. */ + int *oldformat, /* Returns 1 if the old image API is used. */ + int *closeChannelPtr, /* Is set to 1 if channel can be closed */ + Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding + * ReadFileFormat function */ { int matched = 0, useoldformat = 0, use87format = 0; Tk_PhotoImageFormat *formatPtr; @@ -2769,8 +2844,9 @@ MatchFileFormat( if (format87Ptr->fileMatchProc != NULL) { (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); - if (format87Ptr->fileMatchProc(chan, fileName, formatObj, - widthPtr, heightPtr, interp, metadataObjPtr)) { + if (format87Ptr->fileMatchProc(interp, chan, fileName, formatObj, + metadataInObj, widthPtr, heightPtr, closeChannelPtr, + driverInternalPtr)) { if (*widthPtr < 1) { *widthPtr = 1; } @@ -2833,7 +2909,8 @@ MatchStringFormat( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Obj *data, /* Object containing the image data. */ Tcl_Obj *formatObj, /* User-specified format string, or NULL. */ - Tcl_Obj **metadataObjPtr, /* User-specified metadata and return it */ + Tcl_Obj *metadataInObj, /* User-specified metadata, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata output dict, may be NULL */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record * is returned here. For format87, this is set @@ -2845,7 +2922,9 @@ MatchStringFormat( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ - int *oldformat) /* Returns 1 if the old image API is used. */ + int *oldformat, /* Returns 1 if the old image API is used. */ + Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding + * ReadFileFormat function */ { int matched = 0, useoldformat = 0, use87format = 0; Tk_PhotoImageFormat *formatPtr, *defaultFormatPtr = NULL; @@ -2959,15 +3038,15 @@ MatchStringFormat( } if ((format87Ptr->stringMatchProc != NULL) && (format87Ptr->stringReadProc != NULL) - && format87Ptr->stringMatchProc( - data, formatObj, - widthPtr, heightPtr, interp, metadataObjPtr)) { + && format87Ptr->stringMatchProc(interp, data, formatObj, + metadataInObj, widthPtr, heightPtr, + driverInternalPtr)) { break; } } } - if (formatPtr == NULL && format87Ptr == 0) { + if (formatPtr == NULL && format87Ptr == NULL) { /* * Try the default format as last resort (only if no -format option * was passed). @@ -4085,72 +4164,6 @@ Tk_PhotoSetSize( /* *---------------------------------------------------------------------- * - * Tk_PhotoGetMetadata -- - * - * This function is called to obtain the metadata object of a photo - * image. - * - * Results: - * The image's metadata object pointer. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -Tcl_Obj * -Tk_PhotoGetMetadata( - Tk_PhotoHandle handle) /* Handle for the image whose dimensions are - * requested. */ -{ - PhotoMaster *masterPtr = (PhotoMaster *) handle; - - return masterPtr->metadata; -} - -/* - *---------------------------------------------------------------------- - * - * Tk_PhotoSetMetadata -- - * - * This function is called to obtain to set the metadata object of a - * photo image. - * - * Results: - * None - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -void -Tk_PhotoSetMetadata( - Tk_PhotoHandle handle, /* Handle for the image whose dimensions are - * requested. */ - Tcl_Obj *metadata) -{ - PhotoMaster *masterPtr = (PhotoMaster *) handle; - /* - * Free current object if present - */ - if(masterPtr->metadata != NULL) { - Tcl_DecrRefCount(masterPtr->metadata); - } - /* - * Increment ref count of new object to get it - */ - if (metadata != NULL) { - Tcl_IncrRefCount(metadata); - } - masterPtr->metadata = metadata; -} - -/* - *---------------------------------------------------------------------- - * * TkGetPhotoValidRegion -- * * This function is called to get the part of the photo where there is diff --git a/generic/tkStubInit.c b/generic/tkStubInit.c index 461ca1e..e52d404 100644 --- a/generic/tkStubInit.c +++ b/generic/tkStubInit.c @@ -1301,8 +1301,7 @@ const TkStubs tkStubs = { Tk_Interp, /* 271 */ Tk_CreateOldImageType, /* 272 */ Tk_CreateOldPhotoImageFormat, /* 273 */ - Tk_PhotoGetMetadata, /* 274 */ - Tk_PhotoSetMetadata, /* 275 */ + Tk_CreatePhotoImageFormat87 /* 274 */ }; /* !END!: Do not edit above this line. */ -- cgit v0.12 From ce30db2662a3ff764f4e3ecacdf5b307441abf09 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sat, 6 Jun 2020 18:36:10 +0000 Subject: TIP529 image metadata: changed order of arguments to alphabetical order --- generic/tkImgPhoto.c | 16 ++++++++-------- tests/imgPhoto.test | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index c79f128..f31683d 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -55,12 +55,12 @@ struct SubcommandOptions { * OPT_FORMAT: Set if -format option allowed/specified. * OPT_FROM: Set if -from option allowed/specified. * OPT_GRAYSCALE: Set if -grayscale option allowed/specified. + * OPT_METADATA: Set if -metadata option allowed/specified. * OPT_SHRINK: Set if -shrink option allowed/specified. * OPT_SUBSAMPLE: Set if -subsample option allowed/spec'd. * OPT_TO: Set if -to option allowed/specified. * OPT_WITHALPHA: Set if -withalpha option allowed/specified. * OPT_ZOOM: Set if -zoom option allowed/specified. - * OPT_METADATA: Set if -metadata option allowed/specified. */ #define OPT_ALPHA 1 @@ -69,12 +69,12 @@ struct SubcommandOptions { #define OPT_FORMAT 8 #define OPT_FROM 0x10 #define OPT_GRAYSCALE 0x20 -#define OPT_SHRINK 0x40 -#define OPT_SUBSAMPLE 0x80 -#define OPT_TO 0x100 -#define OPT_WITHALPHA 0x200 -#define OPT_ZOOM 0x400 -#define OPT_METADATA 0x800 +#define OPT_METADATA 0x40 +#define OPT_SHRINK 0x80 +#define OPT_SUBSAMPLE 0x100 +#define OPT_TO 0x200 +#define OPT_WITHALPHA 0x400 +#define OPT_ZOOM 0x800 /* * List of option names. The order here must match the order of declarations @@ -88,12 +88,12 @@ static const char *const optionNames[] = { "-format", "-from", "-grayscale", + "-metadata", "-shrink", "-subsample", "-to", "-withalpha", "-zoom", - "-metadata", NULL }; diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index c75a126..3d4e4ab 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -576,7 +576,7 @@ test imgPhoto-4.31 {ImgPhotoCmd procedure: read option} -constraints { photo1 read $teapotPhotoFile -zoom 2 } -returnCodes error -cleanup { image delete photo1 -} -result {unrecognized option "-zoom": must be -format, -from, -shrink, -to, or -metadata} +} -result {unrecognized option "-zoom": must be -format, -from, -metadata, -shrink, or -to} test imgPhoto-4.32 {ImgPhotoCmd procedure: read option} -setup { image create photo photo1 } -body { @@ -1093,13 +1093,13 @@ test imgPhoto-4.90 {ImgPhotoCmd put: existing but not allowed opt} -setup { } -cleanup { imageCleanup } -returnCodes error -result \ - {unrecognized option "-from": must be -format, -to, or -metadata} + {unrecognized option "-from": must be -format, -metadata, or -to} test imgPhoto-4.91 {ImgPhotoCmd put: invalid option} -setup { image create photo photo1 } -body { photo1 put {{0 1 2 3}} -bogus x } -returnCodes error -result \ - {unrecognized option "-bogus": must be -format, -to, or -metadata} + {unrecognized option "-bogus": must be -format, -metadata, or -to} test imgPhoto-4.92 {ImgPhotocmd put: missing data} -setup { image create photo photo1 } -body { -- cgit v0.12 From 994796338e757fb33fe92fbf54662199507ecc03 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sat, 6 Jun 2020 19:16:21 +0000 Subject: TIP529 image metadata: added GIF XMP write test, functionality not implemented jet --- generic/tk.h | 8 ++++---- tests/imgPhoto.test | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/generic/tk.h b/generic/tk.h index 183bce9..e271b12 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1444,19 +1444,19 @@ typedef int (Tk_ImageStringWriteProc) (Tcl_Interp *interp, Tcl_Obj *format, typedef struct Tk_PhotoImageFormat87 Tk_PhotoImageFormat87; typedef int (Tk_ImageFileMatchProc87) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, int *closeChannelPtr, Tcl_DString *driverInternal); + int *heightPtr, int *closeChannelPtr, Tcl_DString *driverInternalPtr); typedef int (Tk_ImageStringMatchProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - Tcl_DString *driverInternal); + Tcl_DString *driverInternalPtr); typedef int (Tk_ImageFileReadProc87) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); + Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); typedef int (Tk_ImageStringReadProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); + Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); typedef int (Tk_ImageFileWriteProc87) (Tcl_Interp *interp, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); typedef int (Tk_ImageStringWriteProc87) (Tcl_Interp *interp, Tcl_Obj *format, diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 3d4e4ab..751d589 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2338,6 +2338,24 @@ test imgPhoto-23.10 {output data with comment} -setup { catch {image delete gif1} } -match glob -result {*ABCD*} +test imgPhoto-23.11 {output data with XMP} -setup { + set data $::gifstart$::gifdata$::gifend + set XMPData\ + "\ + \ + 3" +} -body { + image create photo gif1 -data $data + set gifData [gif1 data -format gif -metadata [dict create\ + XMP $XMPData]] + image delete gif1 + image create photo gif1 -data $gifData + expr {[gif1 cget -metadata] eq $XMPData} +} -cleanup { + catch {image delete gif1} +} -result {1} + unset -nocomplain gifstart gifdata gifend -- cgit v0.12 From 764007d977e0131ee0c8abdaf029d7851007b603 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sun, 7 Jun 2020 10:26:16 +0000 Subject: TIP529 image metadata: rename function & friends from Tk_CreatePhotoImageFormat87 to Tk_CreatePhotoImageFormatVersion3 --- generic/tk.decls | 3 +- generic/tk.h | 50 +++++++------ generic/tkDecls.h | 7 +- generic/tkImgGIF.c | 2 +- generic/tkImgPhoto.c | 198 +++++++++++++++++++++++++++------------------------ generic/tkInt.h | 2 +- generic/tkStubInit.c | 2 +- generic/tkWindow.c | 2 +- 8 files changed, 140 insertions(+), 126 deletions(-) diff --git a/generic/tk.decls b/generic/tk.decls index 0cd8fa4..4ba7307 100644 --- a/generic/tk.decls +++ b/generic/tk.decls @@ -1070,7 +1070,8 @@ declare 273 { } # New in Tk8.7 declare 274 { - void Tk_CreatePhotoImageFormat87(const Tk_PhotoImageFormat87 *formatPtr) + void Tk_CreatePhotoImageFormatVersion3( + const Tk_PhotoImageFormatVersion3 *formatPtr) } diff --git a/generic/tk.h b/generic/tk.h index e271b12..c2be29c 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1441,26 +1441,30 @@ typedef int (Tk_ImageStringWriteProc) (Tcl_Interp *interp, Tcl_Obj *format, * supporting a metadata dict, internal dstring and close file flag */ -typedef struct Tk_PhotoImageFormat87 Tk_PhotoImageFormat87; -typedef int (Tk_ImageFileMatchProc87) (Tcl_Interp *interp, Tcl_Channel chan, - const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, int *closeChannelPtr, Tcl_DString *driverInternalPtr); -typedef int (Tk_ImageStringMatchProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - Tcl_DString *driverInternalPtr); -typedef int (Tk_ImageFileReadProc87) (Tcl_Interp *interp, Tcl_Channel chan, +typedef struct Tk_PhotoImageFormatVersion3 Tk_PhotoImageFormatVersion3; +typedef int (Tk_ImageFileMatchProcVersion3) (Tcl_Interp *interp, + Tcl_Channel chan, const char *fileName, Tcl_Obj *format, + Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, + int *closeChannelPtr, Tcl_DString *driverInternalPtr); +typedef int (Tk_ImageStringMatchProcVersion3) (Tcl_Interp *interp, + Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, + int *heightPtr, Tcl_DString *driverInternalPtr); +typedef int (Tk_ImageFileReadProcVersion3) (Tcl_Interp *interp, + Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); -typedef int (Tk_ImageStringReadProc87) (Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, - int destX, int destY, int width, int height, int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); -typedef int (Tk_ImageFileWriteProc87) (Tcl_Interp *interp, const char *fileName, +typedef int (Tk_ImageStringReadProcVersion3) (Tcl_Interp *interp, + Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, + int srcX, int srcY, Tcl_Obj *metadataOut, + Tcl_DString *driverInternalPtr); +typedef int (Tk_ImageFileWriteProcVersion3) (Tcl_Interp *interp, + const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoImageBlock *blockPtr); +typedef int (Tk_ImageStringWriteProcVersion3) (Tcl_Interp *interp, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); -typedef int (Tk_ImageStringWriteProc87) (Tcl_Interp *interp, Tcl_Obj *format, - Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); /* @@ -1501,28 +1505,28 @@ struct Tk_PhotoImageFormat { * structure. */ -struct Tk_PhotoImageFormat87 { +struct Tk_PhotoImageFormatVersion3 { const char *name; /* Name of image file format */ - Tk_ImageFileMatchProc87 *fileMatchProc; + Tk_ImageFileMatchProcVersion3 *fileMatchProc; /* Procedure to call to determine whether an * image file matches this format. */ - Tk_ImageStringMatchProc87 *stringMatchProc; + Tk_ImageStringMatchProcVersion3 *stringMatchProc; /* Procedure to call to determine whether the * data in a string matches this format. */ - Tk_ImageFileReadProc87 *fileReadProc; + Tk_ImageFileReadProcVersion3 *fileReadProc; /* Procedure to call to read data from an * image file into a photo image. */ - Tk_ImageStringReadProc87 *stringReadProc; + Tk_ImageStringReadProcVersion3 *stringReadProc; /* Procedure to call to read data from a * string into a photo image. */ - Tk_ImageFileWriteProc87 *fileWriteProc; + Tk_ImageFileWriteProcVersion3 *fileWriteProc; /* Procedure to call to write data from a * photo image to a file. */ - Tk_ImageStringWriteProc87 *stringWriteProc; + Tk_ImageStringWriteProcVersion3 *stringWriteProc; /* Procedure to call to obtain a string * representation of the data in a photo * image.*/ - struct Tk_PhotoImageFormat87 *nextPtr; + struct Tk_PhotoImageFormatVersion3 *nextPtr; /* Next in list of all photo image formats * currently known. Filled in by Tk, not by * image format handler. */ diff --git a/generic/tkDecls.h b/generic/tkDecls.h index 9ba5d88..7b583b8 100644 --- a/generic/tkDecls.h +++ b/generic/tkDecls.h @@ -879,8 +879,8 @@ EXTERN void Tk_CreateOldImageType(const Tk_ImageType *typePtr); EXTERN void Tk_CreateOldPhotoImageFormat( const Tk_PhotoImageFormat *formatPtr); /* 274 */ -EXTERN void Tk_CreatePhotoImageFormat87( - const Tk_PhotoImageFormat87 *formatPtr); +EXTERN void Tk_CreatePhotoImageFormatVersion3( + const Tk_PhotoImageFormatVersion3 *formatPtr); typedef struct { const struct TkPlatStubs *tkPlatStubs; @@ -1167,7 +1167,8 @@ typedef struct TkStubs { Tcl_Interp * (*tk_Interp) (Tk_Window tkwin); /* 271 */ void (*tk_CreateOldImageType) (const Tk_ImageType *typePtr); /* 272 */ void (*tk_CreateOldPhotoImageFormat) (const Tk_PhotoImageFormat *formatPtr); /* 273 */ - void (*tk_CreatePhotoImageFormat87) (const Tk_PhotoImageFormat87 *formatPtr); /* 274 */ + void (*tk_CreatePhotoImageFormatVersion3) ( + const Tk_PhotoImageFormatVersion3 *formatPtr); /* 274 */ } TkStubs; extern const TkStubs *tkStubsPtr; diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index f9f0bea..c077b6d 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -146,7 +146,7 @@ static int CommonWriteGIF(Tcl_Interp *interp, ClientData clientData, WriteBytesFunc *writeProc, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); -Tk_PhotoImageFormat87 tkImgFmtGIF = { +Tk_PhotoImageFormatVersion3 tkImgFmtGIF = { "gif", /* name */ FileMatchGIF, /* fileMatchProc */ StringMatchGIF, /* stringMatchProc */ diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index f31683d..27e12df 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -142,9 +142,9 @@ typedef struct { Tk_PhotoImageFormat *oldFormatList; /* Pointer to the first in the list of known * photo image formats.*/ - Tk_PhotoImageFormat87 *formatList87; + Tk_PhotoImageFormatVersion3 *formatListVersion3; /* Pointer to the first in the list of known - * photo image formats in 87 format.*/ + * photo image formats in Version3 format.*/ int initialized; /* Set to 1 if we've initialized the * structure. */ } ThreadSpecificData; @@ -203,7 +203,7 @@ static int MatchFileFormat(Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *metadataInObj, Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, - Tk_PhotoImageFormat87 **imageFormat87Ptr, + Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, int *widthPtr, int *heightPtr, int *oldformat, int *closeChannelPtr, Tcl_DString *driverInternalPtr); @@ -212,7 +212,7 @@ static int MatchStringFormat(Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *metadataInObj, Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, - Tk_PhotoImageFormat87 **imageFormat87Ptr, + Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, int *widthPtr, int *heightPtr, int *oldformat, Tcl_DString *driverInternalPtr); static const char * GetExtension(const char *path); @@ -238,7 +238,7 @@ PhotoFormatThreadExitProc( ClientData dummy) /* not used */ { Tk_PhotoImageFormat *freePtr; - Tk_PhotoImageFormat87 *freePtr87; + Tk_PhotoImageFormatVersion3 *freePtrVersion3; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); (void)dummy; @@ -254,11 +254,11 @@ PhotoFormatThreadExitProc( ckfree((char *)freePtr->name); ckfree(freePtr); } - while (tsdPtr->formatList87 != NULL) { - freePtr87 = tsdPtr->formatList87; - tsdPtr->formatList87 = tsdPtr->formatList87->nextPtr; - ckfree((char *)freePtr87->name); - ckfree(freePtr87); + while (tsdPtr->formatListVersion3 != NULL) { + freePtrVersion3 = tsdPtr->formatListVersion3; + tsdPtr->formatListVersion3 = tsdPtr->formatListVersion3->nextPtr; + ckfree((char *)freePtrVersion3->name); + ckfree(freePtrVersion3); } } @@ -266,7 +266,7 @@ PhotoFormatThreadExitProc( *---------------------------------------------------------------------- * * Tk_CreateOldPhotoImageFormat, Tk_CreatePhotoImageFormat, - * Tk_CreatePhotoImageFormat87 -- + * Tk_CreatePhotoImageFormatVersion3 -- * * This function is invoked by an image file handler to register a new * photo image format and the functions that handle the new format. The @@ -333,13 +333,13 @@ Tk_CreatePhotoImageFormat( } } void -Tk_CreatePhotoImageFormat87( - const Tk_PhotoImageFormat87 *formatPtr) +Tk_CreatePhotoImageFormatVersion3( + const Tk_PhotoImageFormatVersion3 *formatPtr) /* Structure describing the format. All of the * fields except "nextPtr" must be filled in * by caller. */ { - Tk_PhotoImageFormat87 *copyPtr; + Tk_PhotoImageFormatVersion3 *copyPtr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); @@ -347,14 +347,15 @@ Tk_CreatePhotoImageFormat87( tsdPtr->initialized = 1; Tcl_CreateThreadExitHandler(PhotoFormatThreadExitProc, NULL); } - copyPtr = (Tk_PhotoImageFormat87 *)ckalloc(sizeof(Tk_PhotoImageFormat87)); + copyPtr = (Tk_PhotoImageFormatVersion3 *) + ckalloc(sizeof(Tk_PhotoImageFormatVersion3)); *copyPtr = *formatPtr; /* for compatibility with aMSN: make a copy of formatPtr->name */ char *name = (char *)ckalloc(strlen(formatPtr->name) + 1); strcpy(name, formatPtr->name); copyPtr->name = name; - copyPtr->nextPtr = tsdPtr->formatList87; - tsdPtr->formatList87 = copyPtr; + copyPtr->nextPtr = tsdPtr->formatListVersion3; + tsdPtr->formatListVersion3 = copyPtr; } /* @@ -460,7 +461,7 @@ ImgPhotoCmd( unsigned char *pixelPtr; Tk_PhotoImageBlock block; Tk_PhotoImageFormat *imageFormat; - Tk_PhotoImageFormat87 *imageFormat87; + Tk_PhotoImageFormatVersion3 *imageFormatVersion3; TkSizeT length; int imageWidth, imageHeight, matched, oldformat = 0; Tcl_Channel chan; @@ -751,7 +752,7 @@ ImgPhotoCmd( */ Tk_ImageStringWriteProc *stringWriteProc = NULL; - Tk_ImageStringWriteProc87 *stringWriteProc87 = NULL; + Tk_ImageStringWriteProcVersion3 *stringWriteProcVersion3 = NULL; index = 1; memset(&options, 0, sizeof(options)); @@ -825,26 +826,28 @@ ImgPhotoCmd( } if (stringWriteProc == NULL) { oldformat = 0; - for (imageFormat87 = tsdPtr->formatList87; imageFormat87 != NULL; - imageFormat87 = imageFormat87->nextPtr) { - if ((strncasecmp(Tcl_GetString(options.format), - imageFormat87->name, - strlen(imageFormat87->name)) == 0)) { - matched = 1; - if (imageFormat87->stringWriteProc != NULL) { - stringWriteProc87 = imageFormat87->stringWriteProc; - break; - } - } + for (imageFormatVersion3 = tsdPtr->formatListVersion3; + imageFormatVersion3 != NULL; + imageFormatVersion3 = imageFormatVersion3->nextPtr) { + if ((strncasecmp(Tcl_GetString(options.format), + imageFormatVersion3->name, + strlen(imageFormatVersion3->name)) == 0)) { + matched = 1; + if (imageFormatVersion3->stringWriteProc != NULL) { + stringWriteProcVersion3 = + imageFormatVersion3->stringWriteProc; + break; + } + } } } - if (stringWriteProc == NULL && stringWriteProc87 == NULL) { + if (stringWriteProc == NULL && stringWriteProcVersion3 == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "image string format \"%s\" is %s", - Tcl_GetString(options.format), - (matched ? "not supported" : "unknown"))); + "image string format \"%s\" is %s", + Tcl_GetString(options.format), + (matched ? "not supported" : "unknown"))); Tcl_SetErrorCode(interp, "TK", "LOOKUP", "PHOTO_FORMAT", - Tcl_GetString(options.format), NULL); + Tcl_GetString(options.format), NULL); goto dataErrorExit; } @@ -855,7 +858,7 @@ ImgPhotoCmd( data = ImgGetPhoto(masterPtr, &block, &options); if (stringWriteProc == NULL) { - result = (stringWriteProc87)(interp, + result = (stringWriteProcVersion3)(interp, options.format, options.metadata, &block); } else if (oldformat) { Tcl_DString buffer; @@ -988,7 +991,7 @@ ImgPhotoCmd( if (MatchStringFormat(interp, objv[2], options.format, options.metadata, NULL, &imageFormat, - &imageFormat87, &imageWidth, &imageHeight, &oldformat, + &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &driverInternalDString) != TCL_OK) { result = TCL_ERROR; @@ -1023,7 +1026,7 @@ ImgPhotoCmd( goto putCleanup; } } else { - if (imageFormat87->stringReadProc(interp, data, format, + if (imageFormatVersion3->stringReadProc(interp, data, format, options.metadata, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, @@ -1114,7 +1117,7 @@ putCleanup: if (MatchFileFormat(interp, chan, Tcl_GetString(options.name), options.format, options.metadata, NULL, &imageFormat, - &imageFormat87, &imageWidth, &imageHeight, &oldformat, + &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &closeChannel, &driverInternalDString) != TCL_OK) { result = TCL_ERROR; @@ -1174,7 +1177,7 @@ putCleanup: format, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, width, height, options.fromX, options.fromY); } else { - result = imageFormat87->fileReadProc(interp, chan, + result = imageFormatVersion3->fileReadProc(interp, chan, Tcl_GetString(options.name), format, options.metadata, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, width, height, options.fromX, @@ -1479,7 +1482,7 @@ readCleanup: matched = 0; redoFormatLookup: - imageFormat87 = NULL; + imageFormatVersion3 = NULL; for (imageFormat = tsdPtr->formatList; imageFormat != NULL; imageFormat = imageFormat->nextPtr) { if ((fmtString == NULL) @@ -1507,13 +1510,14 @@ readCleanup: } if (imageFormat == NULL) { oldformat = 0; - for (imageFormat87 = tsdPtr->formatList87; imageFormat87 != NULL; - imageFormat87 = imageFormat87->nextPtr) { + for (imageFormatVersion3 = tsdPtr->formatListVersion3; + imageFormatVersion3 != NULL; + imageFormatVersion3 = imageFormatVersion3->nextPtr) { if ((fmtString == NULL) - || (strncasecmp(fmtString, imageFormat87->name, - strlen(imageFormat87->name)) == 0)) { + || (strncasecmp(fmtString, imageFormatVersion3->name, + strlen(imageFormatVersion3->name)) == 0)) { matched = 1; - if (imageFormat87->fileWriteProc != NULL) { + if (imageFormatVersion3->fileWriteProc != NULL) { break; } } @@ -1530,7 +1534,7 @@ readCleanup: fmtString = NULL; goto redoFormatLookup; } - if (imageFormat == NULL && imageFormat87 == NULL) { + if (imageFormat == NULL && imageFormatVersion3 == NULL) { if (fmtString == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "no available image file format has file writing" @@ -1561,7 +1565,7 @@ readCleanup: result = imageFormat->fileWriteProc(interp, Tcl_GetString(options.name), format, &block); } else { - result = imageFormat87->fileWriteProc(interp, + result = imageFormatVersion3->fileWriteProc(interp, Tcl_GetString(options.name), format, options.metadata, &block); } @@ -1950,7 +1954,7 @@ ImgPhotoConfigureMaster( double oldGamma; Tcl_Channel chan; Tk_PhotoImageFormat *imageFormat; - Tk_PhotoImageFormat87 *imageFormat87; + Tk_PhotoImageFormatVersion3 *imageFormatVersion3; const char **args; Tcl_DString driverInternalDString; int closeChannel; @@ -2160,7 +2164,7 @@ ImgPhotoConfigureMaster( "-translation", "binary") != TCL_OK) || (MatchFileFormat(interp, chan, masterPtr->fileString, masterPtr->format, masterPtr->metadata, NULL, - &imageFormat, &imageFormat87, + &imageFormat, &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &closeChannel, &driverInternalDString) != TCL_OK)) { Tcl_Close(NULL, chan); @@ -2184,7 +2188,7 @@ ImgPhotoConfigureMaster( (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0); } else { - result = imageFormat87->fileReadProc(interp, chan, + result = imageFormatVersion3->fileReadProc(interp, chan, masterPtr->fileString, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0, @@ -2209,7 +2213,7 @@ ImgPhotoConfigureMaster( if (MatchStringFormat(interp, masterPtr->dataString, masterPtr->format, masterPtr->metadata, NULL, - &imageFormat, &imageFormat87, &imageWidth, + &imageFormat, &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &driverInternalDString) != TCL_OK) { goto errorExit; } @@ -2242,7 +2246,7 @@ ImgPhotoConfigureMaster( metadataOut = Tcl_NewDictObj(); Tcl_IncrRefCount(metadataOut); - if (imageFormat87->stringReadProc(interp, tempdata, tempformat, + if (imageFormatVersion3->stringReadProc(interp, tempdata, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0, metadataOut, &driverInternalDString) != TCL_OK) { @@ -2691,8 +2695,8 @@ ImgPhotoSetSize( * Results: * A standard TCL return value. If the return value is TCL_OK, a pointer * to the image format record is returned in *imageFormatPtr or - * *imageFormat87Ptr, and the width and height of the image are returned - * in *widthPtr and *heightPtr. + * *imageFormatVersion3Ptr, and the width and height of the image are + * returned in *widthPtr and *heightPtr. * * Side effects: * None. @@ -2710,12 +2714,12 @@ MatchFileFormat( Tcl_Obj *metadataOutObj, /* metadata to return, may be NULL */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record - * is returned here. For format87, this is set - * to NULL*/ - Tk_PhotoImageFormat87 **imageFormat87Ptr, - /* A pointer to the photo image format87 record - * is returned here. For non format87, this is - * set to NULL*/ + * is returned here. For formatVersion3, this is + * set to NULL */ + Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, + /* A pointer to the photo image formatVersion3 + * record is returned here. For non + * formatVersion3, this is set to NULL*/ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ @@ -2724,9 +2728,9 @@ MatchFileFormat( Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding * ReadFileFormat function */ { - int matched = 0, useoldformat = 0, use87format = 0; + int matched = 0, useoldformat = 0, useVersion3format = 0; Tk_PhotoImageFormat *formatPtr; - Tk_PhotoImageFormat87 *format87Ptr; + Tk_PhotoImageFormatVersion3 *formatVersion3Ptr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); const char *formatString = NULL; @@ -2808,31 +2812,33 @@ MatchFileFormat( } /* - * For old and not 87 format, exit now with success + * For old and not Version3 format, exit now with success */ if (formatPtr != NULL) { *imageFormatPtr = formatPtr; - *imageFormat87Ptr = NULL; + *imageFormatVersion3Ptr = NULL; *oldformat = useoldformat; (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); return TCL_OK; } /* - * Scan through the table of file format 87 handlers to find one which can - * handle the image. + * Scan through the table of file format Version3 handlers to find one which + * can handle the image. */ - for (format87Ptr = tsdPtr->formatList87; format87Ptr != NULL; - format87Ptr = format87Ptr->nextPtr) { + for (formatVersion3Ptr = tsdPtr->formatListVersion3; + formatVersion3Ptr != NULL; + formatVersion3Ptr = formatVersion3Ptr->nextPtr) { if (formatObj != NULL) { if (strncasecmp(formatString, - format87Ptr->name, strlen(format87Ptr->name)) != 0) { + formatVersion3Ptr->name, strlen(formatVersion3Ptr->name)) + != 0) { continue; } matched = 1; - if (format87Ptr->fileMatchProc == NULL) { + if (formatVersion3Ptr->fileMatchProc == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "-file option isn't supported for %s images", formatString)); @@ -2841,19 +2847,19 @@ MatchFileFormat( return TCL_ERROR; } } - if (format87Ptr->fileMatchProc != NULL) { + if (formatVersion3Ptr->fileMatchProc != NULL) { (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); - if (format87Ptr->fileMatchProc(interp, chan, fileName, formatObj, - metadataInObj, widthPtr, heightPtr, closeChannelPtr, - driverInternalPtr)) { + if (formatVersion3Ptr->fileMatchProc(interp, chan, fileName, + formatObj, metadataInObj, widthPtr, heightPtr, + closeChannelPtr, driverInternalPtr)) { if (*widthPtr < 1) { *widthPtr = 1; } if (*heightPtr < 1) { *heightPtr = 1; } - *imageFormat87Ptr = format87Ptr; + *imageFormatVersion3Ptr = formatVersion3Ptr; *imageFormatPtr = NULL; *oldformat = 0; (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); @@ -2895,8 +2901,8 @@ MatchFileFormat( * Results: * A standard TCL return value. If the return value is TCL_OK, a pointer * to the image format record is returned in *imageFormatPtr or - * *imageFormat87Ptr, and the width and height of the image are returned - * in *widthPtr and *heightPtr. + * *imageFormatVersion3Ptr, and the width and height of the image are + * returned in *widthPtr and *heightPtr. * * Side effects: * None. @@ -2913,12 +2919,12 @@ MatchStringFormat( Tcl_Obj *metadataOutObj, /* metadata output dict, may be NULL */ Tk_PhotoImageFormat **imageFormatPtr, /* A pointer to the photo image format record - * is returned here. For format87, this is set - * to NULL*/ - Tk_PhotoImageFormat87 **imageFormat87Ptr, - /* A pointer to the photo image format87 record - * is returned here. For non format87, this is + * is returned here. For formatVersion3, this is * set to NULL*/ + Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, + /* A pointer to the photo image formatVersion3 + * record is returned here. For non + * formatVersion3, this is set to NULL*/ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ @@ -2926,9 +2932,9 @@ MatchStringFormat( Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding * ReadFileFormat function */ { - int matched = 0, useoldformat = 0, use87format = 0; + int matched = 0, useoldformat = 0, useVersion3format = 0; Tk_PhotoImageFormat *formatPtr, *defaultFormatPtr = NULL; - Tk_PhotoImageFormat87 *format87Ptr = NULL; + Tk_PhotoImageFormatVersion3 *formatVersion3Ptr = NULL; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); const char *formatString = NULL; @@ -3019,15 +3025,17 @@ MatchStringFormat( if (formatPtr == NULL) { useoldformat = 0; - for (format87Ptr = tsdPtr->formatList87; format87Ptr != NULL; - format87Ptr = format87Ptr->nextPtr) { + for (formatVersion3Ptr = tsdPtr->formatListVersion3; + formatVersion3Ptr != NULL; + formatVersion3Ptr = formatVersion3Ptr->nextPtr) { if (formatObj != NULL) { if (strncasecmp(formatString, - format87Ptr->name, strlen(format87Ptr->name)) != 0) { + formatVersion3Ptr->name, strlen(formatVersion3Ptr->name) + ) != 0) { continue; } matched = 1; - if (format87Ptr->stringMatchProc == NULL) { + if (formatVersion3Ptr->stringMatchProc == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "-data option isn't supported for %s images", formatString)); @@ -3036,17 +3044,17 @@ MatchStringFormat( return TCL_ERROR; } } - if ((format87Ptr->stringMatchProc != NULL) - && (format87Ptr->stringReadProc != NULL) - && format87Ptr->stringMatchProc(interp, data, formatObj, - metadataInObj, widthPtr, heightPtr, + if ((formatVersion3Ptr->stringMatchProc != NULL) + && (formatVersion3Ptr->stringReadProc != NULL) + && formatVersion3Ptr->stringMatchProc(interp, data, + formatObj, metadataInObj, widthPtr, heightPtr, driverInternalPtr)) { break; } } } - if (formatPtr == NULL && format87Ptr == NULL) { + if (formatPtr == NULL && formatVersion3Ptr == NULL) { /* * Try the default format as last resort (only if no -format option * was passed). @@ -3087,7 +3095,7 @@ MatchStringFormat( } *imageFormatPtr = formatPtr; - *imageFormat87Ptr = format87Ptr; + *imageFormatVersion3Ptr = formatVersion3Ptr; *oldformat = useoldformat; /* diff --git a/generic/tkInt.h b/generic/tkInt.h index ff35195..b3cd0fa 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -1056,7 +1056,7 @@ MODULE_SCOPE const Tcl_ObjType tkTextIndexType; MODULE_SCOPE const Tk_SmoothMethod tkBezierSmoothMethod; MODULE_SCOPE Tk_ImageType tkBitmapImageType; -MODULE_SCOPE Tk_PhotoImageFormat87 tkImgFmtGIF; +MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtGIF; MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; diff --git a/generic/tkStubInit.c b/generic/tkStubInit.c index dea8fd6..fe91998 100644 --- a/generic/tkStubInit.c +++ b/generic/tkStubInit.c @@ -1321,7 +1321,7 @@ const TkStubs tkStubs = { Tk_Interp, /* 271 */ Tk_CreateOldImageType, /* 272 */ Tk_CreateOldPhotoImageFormat, /* 273 */ - Tk_CreatePhotoImageFormat87 /* 274 */ + Tk_CreatePhotoImageFormatVersion3 /* 274 */ }; /* !END!: Do not edit above this line. */ diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 4287b2a..8e74baf 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -337,7 +337,7 @@ CreateTopLevelWindow( */ Tk_CreatePhotoImageFormat(&tkImgFmtDefault); - Tk_CreatePhotoImageFormat87(&tkImgFmtGIF); + Tk_CreatePhotoImageFormatVersion3(&tkImgFmtGIF); Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); -- cgit v0.12 From a9ccdcf892a930e096c174d1033f58063fecc433 Mon Sep 17 00:00:00 2001 From: oehhar Date: Sun, 7 Jun 2020 11:25:59 +0000 Subject: TIP529 image metadata: correct shared metadata object ref count on output, implement XMP gif write --- generic/tkImgGIF.c | 85 +++++++++++++++++++++++++++++++++++++++++----------- generic/tkImgPhoto.c | 1 + tests/imgPhoto.test | 2 +- 3 files changed, 70 insertions(+), 18 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index c077b6d..85a69e4 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1872,7 +1872,7 @@ FileWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ const char *filename, Tcl_Obj *format, - Tcl_Obj *metadata, + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr) { Tcl_Channel chan = NULL; @@ -1888,7 +1888,7 @@ FileWriteGIF( return TCL_ERROR; } - result = CommonWriteGIF(interp, chan, WriteToChannel, format, metadata, + result = CommonWriteGIF(interp, chan, WriteToChannel, format, metadataIn, blockPtr); if (Tcl_Close(interp, chan) == TCL_ERROR) { @@ -1897,14 +1897,12 @@ FileWriteGIF( return result; } -/* New parameter "metadata" is appended at the end */ - static int StringWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors and * returning the GIF data. */ Tcl_Obj *format, - Tcl_Obj *metadata, + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr) { int result; @@ -1912,7 +1910,7 @@ StringWriteGIF( Tcl_IncrRefCount(objPtr); result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, - metadata, blockPtr); + metadataIn, blockPtr); if (result == TCL_OK) { Tcl_SetObjResult(interp, objPtr); } @@ -1952,7 +1950,7 @@ CommonWriteGIF( ClientData handle, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tcl_Obj *metadata, + Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr) { GifWriterState state; @@ -2083,15 +2081,21 @@ CommonWriteGIF( c = 0; writeProc(handle, (char *) &c, 1); - /* Check for metadata comment block */ - if (NULL != metadata) { + /* + * Check for metadata keys to add to file + */ + if (NULL != metadataIn) { Tcl_Obj *itemData; - if (TCL_ERROR == Tcl_DictObjGet(interp, metadata, + + /* + * Check and code comment block + */ + + if (TCL_ERROR == Tcl_DictObjGet(interp, metadataIn, Tcl_NewStringObj("Comment",-1), &itemData)) { return TCL_ERROR; } - /* Check if there is a Comment key */ if (itemData != NULL) { int length; unsigned char *comment; @@ -2101,23 +2105,70 @@ CommonWriteGIF( writeProc(handle, (char *) "\x21\fe", 2); /* write comment blocks */ for (;length > 0;) { - unsigned char blocklength; + int blockLength; + unsigned char blockLengthChar; if (length > 255) { length -=255; - blocklength = 255; + blockLength = 255; } else { - blocklength = (unsigned char)length; + blockLength = length; length = 0; } - writeProc(handle, (char *) &blocklength, 1); - writeProc(handle, (char *) comment, blocklength); - comment += blocklength; + blockLengthChar = (unsigned char) blockLength; + writeProc(handle, (char *) &blockLengthChar, 1); + writeProc(handle, (char *) comment, blockLength); + comment += blockLength; } /* Block terminator */ c = 0; writeProc(handle, (char *) &c, 1); } } + + /* + * Check and code XMP block + */ + + if (TCL_ERROR == Tcl_DictObjGet(interp, metadataIn, + Tcl_NewStringObj("XMP",-1), + &itemData)) { + return TCL_ERROR; + } + if (itemData != NULL) { + Tcl_Encoding encoding; + Tcl_DString recodedDString; + char * itemString; + int itemLength; + int trailerChar; + + /* write header */ + writeProc(handle, "\x21\xff\x0bXMP DataXMP", 14); + + /* write utf-8 coded data */ + encoding = Tcl_GetEncoding(NULL, "utf-8"); + Tcl_DStringInit(&recodedDString); + itemString = Tcl_GetStringFromObj(itemData, &itemLength); + Tcl_UtfToExternalDString(encoding, itemString, itemLength, + &recodedDString); + writeProc(handle, Tcl_DStringValue(&recodedDString), + Tcl_DStringLength(&recodedDString)); + Tcl_DStringFree(&recodedDString); + Tcl_FreeEncoding(encoding); + + /* XMP format does not use the block structure of GIF + * The data is utf-8 which never contains 0's + * A magic trailer of 258 bytes is added with the following data: + * 0x01 0xff 0xfe ... 0x01 0x00 0x00 + */ + c = 1; + writeProc(handle, (char *) &c, 1); + for (trailerChar = 0xff; trailerChar >= 0; trailerChar--) { + c = (unsigned char)trailerChar; + writeProc(handle, (char *) &c, 1); + } + c = 0; + writeProc(handle, (char *) &c, 1); + } } c = GIF_TERMINATOR; writeProc(handle, (char *) &c, 1); diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 27e12df..126c43d 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -2285,6 +2285,7 @@ ImgPhotoConfigureMaster( int done; if (Tcl_IsShared(masterPtr->metadata)) { + Tcl_DecrRefCount(masterPtr->metadata); masterPtr->metadata = Tcl_DuplicateObj(masterPtr->metadata); Tcl_IncrRefCount(masterPtr->metadata); } diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 751d589..b0d41b1 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2351,7 +2351,7 @@ test imgPhoto-23.11 {output data with XMP} -setup { XMP $XMPData]] image delete gif1 image create photo gif1 -data $gifData - expr {[gif1 cget -metadata] eq $XMPData} + expr {[dict get [gif1 cget -metadata] XMP] eq $XMPData} } -cleanup { catch {image delete gif1} } -result {1} -- cgit v0.12 From 4eded734df9d4ee1709532a90fd1882d8eb7c26d Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 9 Jun 2020 09:28:02 +0000 Subject: TIP529 image metadata: allow match functions to output metadata. implement file close. --- generic/tk.h | 5 +- generic/tkImgGIF.c | 8 +++- generic/tkImgPhoto.c | 127 +++++++++++++++++++++++++++++++++++-------------- generic/tkImgSVGnano.c | 98 +++++++++++++++++++++++++------------- generic/tkInt.h | 2 +- generic/tkWindow.c | 4 +- 6 files changed, 168 insertions(+), 76 deletions(-) diff --git a/generic/tk.h b/generic/tk.h index 012e3ff..e8bcae4 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1445,10 +1445,11 @@ typedef struct Tk_PhotoImageFormatVersion3 Tk_PhotoImageFormatVersion3; typedef int (Tk_ImageFileMatchProcVersion3) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - int *closeChannelPtr, Tcl_DString *driverInternalPtr); + Tcl_Obj *metadataOut, int *closeChannelPtr, + Tcl_DString *driverInternalPtr); typedef int (Tk_ImageStringMatchProcVersion3) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, Tcl_DString *driverInternalPtr); + int *heightPtr, Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); typedef int (Tk_ImageFileReadProcVersion3) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 85a69e4..4aa6413 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -121,7 +121,8 @@ typedef size_t (WriteBytesFunc) (ClientData clientData, const char *bytes, static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - int *closeChannelPtr, Tcl_DString *driverInternal); + Tcl_Obj *metadataOut, int *closeChannelPtr, + Tcl_DString *driverInternal); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, @@ -130,7 +131,8 @@ static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *metadataOut, Tcl_DString *driverInternal); static int StringMatchGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, Tcl_DString *driverInternal); + int *heightPtr, Tcl_Obj *metadataOut, + Tcl_DString *driverInternal); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, @@ -362,6 +364,7 @@ FileMatchGIF( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ int *closeChannelPtr, /* Return if the channel may be closed */ Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { @@ -852,6 +855,7 @@ StringMatchGIF( Tcl_Obj *metadataIn, /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ { unsigned char *data, header[10]; diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index b93f0fb..08a79c4 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -1135,6 +1135,11 @@ putCleanup: result = TCL_ERROR; goto readCleanup; } + + if (closeChannel) { + Tcl_Close(NULL, chan); + chan = NULL; + } /* * Check the values given for the -from option. @@ -1961,7 +1966,7 @@ ImgPhotoConfigureMaster( PhotoInstance *instancePtr; const char *oldFileString, *oldPaletteString; Tcl_Obj *oldData, *data = NULL, *oldFormat, *format = NULL, - *metadataIn = NULL, *metadataOut = NULL; + *metadataInObj = NULL, *metadataOutObj = NULL; Tcl_Obj *tempdata, *tempformat; TkSizeT length; int i, j, result, imageWidth, imageHeight, oldformat; @@ -2006,7 +2011,7 @@ ImgPhotoConfigureMaster( } else if ((args[j][1] == 'm') && !strncmp(args[j], "-metadata", length)) { if (++i < objc) { - metadataIn = objv[i]; + metadataInObj = objv[i]; j--; } else { ckfree(args); @@ -2103,14 +2108,14 @@ ImgPhotoConfigureMaster( } masterPtr->format = format; } - if (metadataIn) { + if (metadataInObj) { /* * make -metadata a dict and take it if keys in. * Otherwise set a metadata null pointer. */ int dictSize; - if (TCL_OK != Tcl_DictObjSize(interp,metadataIn, &dictSize)) { + if (TCL_OK != Tcl_DictObjSize(interp,metadataInObj, &dictSize)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "value for \"-metadata\" not a dict", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", @@ -2119,14 +2124,14 @@ ImgPhotoConfigureMaster( } if (dictSize > 0) { - Tcl_IncrRefCount(metadataIn); + Tcl_IncrRefCount(metadataInObj); } else { - metadataIn = NULL; + metadataInObj = NULL; } if (masterPtr->metadata) { Tcl_DecrRefCount(masterPtr->metadata); } - masterPtr->metadata = metadataIn; + masterPtr->metadata = metadataInObj; } /* * Set the image to the user-requested size, if any, and make sure storage @@ -2171,19 +2176,30 @@ ImgPhotoConfigureMaster( closeChannel = 0; /* + * Flag that we want the metadata result dict + */ + + metadataOutObj = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadataOutObj); + + /* * -translation binary also sets -encoding binary */ if ((Tcl_SetChannelOption(interp, chan, "-translation", "binary") != TCL_OK) || (MatchFileFormat(interp, chan, masterPtr->fileString, - masterPtr->format, masterPtr->metadata, NULL, + masterPtr->format, masterPtr->metadata, metadataOutObj, &imageFormat, &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &closeChannel, &driverInternalDString) != TCL_OK)) { Tcl_Close(NULL, chan); goto errorExit; } + if (closeChannel) { + Tcl_Close(NULL, chan); + chan = NULL; + } result = ImgPhotoSetSize(masterPtr, imageWidth, imageHeight); if (result != TCL_OK) { Tcl_Close(NULL, chan); @@ -2206,9 +2222,11 @@ ImgPhotoConfigureMaster( masterPtr->fileString, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0, - metadataOut, &driverInternalDString); + metadataOutObj, &driverInternalDString); + } + if (chan != NULL) { + Tcl_Close(NULL, chan); } - Tcl_Close(NULL, chan); if (result != TCL_OK) { goto errorExit; } @@ -2225,8 +2243,15 @@ ImgPhotoConfigureMaster( && ((masterPtr->dataString != oldData) || (masterPtr->format != oldFormat))) { + /* + * Flag that we want the metadata result dict + */ + + metadataOutObj = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadataOutObj); + if (MatchStringFormat(interp, masterPtr->dataString, - masterPtr->format, masterPtr->metadata, NULL, + masterPtr->format, masterPtr->metadata, metadataOutObj, &imageFormat, &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, &driverInternalDString) != TCL_OK) { goto errorExit; @@ -2253,16 +2278,9 @@ ImgPhotoConfigureMaster( } } else { - /* - * Flag that we want the metadata result dict - */ - - metadataOut = Tcl_NewDictObj(); - Tcl_IncrRefCount(metadataOut); - if (imageFormatVersion3->stringReadProc(interp, tempdata, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, - imageWidth, imageHeight, 0, 0, metadataOut, + imageWidth, imageHeight, 0, 0, metadataOutObj, &driverInternalDString) != TCL_OK) { goto errorExit; } @@ -2275,9 +2293,9 @@ ImgPhotoConfigureMaster( /* * Merge driver returned metadata and master metadata */ - if (metadataOut != NULL) { + if (metadataOutObj != NULL) { int dictSize; - if (TCL_OK != Tcl_DictObjSize(interp,metadataOut, &dictSize)) { + if (TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize)) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "driver metadata not a dict", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "PHOTO", @@ -2291,8 +2309,8 @@ ImgPhotoConfigureMaster( */ if (masterPtr->metadata == NULL) { - masterPtr->metadata = metadataOut; - metadataOut = NULL; + masterPtr->metadata = metadataOutObj; + metadataOutObj = NULL; } else { Tcl_DictSearch search; Tcl_Obj *key, *value; @@ -2304,8 +2322,8 @@ ImgPhotoConfigureMaster( Tcl_IncrRefCount(masterPtr->metadata); } - if (Tcl_DictObjFirst(interp, metadataOut, &search, &key, &value, - &done) != TCL_OK) { + if (Tcl_DictObjFirst(interp, metadataOutObj, &search, &key, + &value, &done) != TCL_OK) { goto errorExit; } for (; !done ; Tcl_DictObjNext(&search, &key, &value, &done)) { @@ -2354,8 +2372,8 @@ ImgPhotoConfigureMaster( if (oldFormat != NULL) { Tcl_DecrRefCount(oldFormat); } - if (metadataOut != NULL) { - Tcl_DecrRefCount(metadataOut); + if (metadataOutObj != NULL) { + Tcl_DecrRefCount(metadataOutObj); } ToggleComplexAlphaIfNeeded(masterPtr); @@ -2370,8 +2388,8 @@ ImgPhotoConfigureMaster( if (oldFormat != NULL) { Tcl_DecrRefCount(oldFormat); } - if (metadataOut != NULL) { - Tcl_DecrRefCount(metadataOut); + if (metadataOutObj != NULL) { + Tcl_DecrRefCount(metadataOutObj); } return TCL_ERROR; } @@ -2829,7 +2847,7 @@ if (formatPtr == NULL) { #endif /* - * For old and not Version3 format, exit now with success + * For old and not version 3 format, exit now with success */ if (formatPtr != NULL) { @@ -2841,8 +2859,8 @@ if (formatPtr == NULL) { } /* - * Scan through the table of file format Version3 handlers to find one which - * can handle the image. + * Scan through the table of file format version 3 handlers to find one + * which can handle the image. */ for (formatVersion3Ptr = tsdPtr->formatListVersion3; @@ -2869,7 +2887,7 @@ if (formatPtr == NULL) { if (formatVersion3Ptr->fileMatchProc(interp, chan, fileName, formatObj, metadataInObj, widthPtr, heightPtr, - closeChannelPtr, driverInternalPtr)) { + metadataOutObj, closeChannelPtr, driverInternalPtr)) { if (*widthPtr < 1) { *widthPtr = 1; } @@ -2879,9 +2897,28 @@ if (formatPtr == NULL) { *imageFormatVersion3Ptr = formatVersion3Ptr; *imageFormatPtr = NULL; *oldformat = 0; - (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); + if (! *closeChannelPtr ) { + (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); + } return TCL_OK; } + + /* + * Clear eventual set keys in the metadata object + */ + + if (metadataOutObj != NULL) { + int dictSize; + if (TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) + || dictSize > 0) { + /* + * Driver has modified the metadata dict, so clear it + */ + Tcl_DecrRefCount(metadataOutObj); + metadataOutObj = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadataOutObj); + } + } } } @@ -2946,7 +2983,8 @@ MatchStringFormat( /* The dimensions of the image are returned * here. */ int *oldformat, /* Returns 1 if the old image API is used. */ - Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding + Tcl_DString *driverInternalPtr) + /* Memory to be passed to the corresponding * ReadFileFormat function */ { int matched = 0, useoldformat = 0; @@ -3067,9 +3105,26 @@ MatchStringFormat( && (formatVersion3Ptr->stringReadProc != NULL) && formatVersion3Ptr->stringMatchProc(interp, data, formatObj, metadataInObj, widthPtr, heightPtr, - driverInternalPtr)) { + metadataOutObj, driverInternalPtr)) { break; } + + /* + * Clear eventual set keys in the metadata object + */ + + if (metadataOutObj != NULL) { + int dictSize; + if (TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) + || dictSize > 0) { + /* + * Driver has modified the metadata dict, so clear it + */ + Tcl_DecrRefCount(metadataOutObj); + metadataOutObj = Tcl_NewDictObj(); + Tcl_IncrRefCount(metadataOutObj); + } + } } } diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c index 7575f86..bed4776 100644 --- a/generic/tkImgSVGnano.c +++ b/generic/tkImgSVGnano.c @@ -52,19 +52,28 @@ typedef struct { RastOpts ropts; } NSVGcache; -static int FileMatchSVG(Tcl_Channel chan, const char *fileName, - Tcl_Obj *format, int *widthPtr, int *heightPtr, - Tcl_Interp *interp); +static int FileMatchSVG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *formatObj, + Tcl_Obj *metadataInObj, int *widthPtr, + int *heightPtr, Tcl_Obj *metadataOutObj, + int *closeChannelPtr, + Tcl_DString *driverInternal); static int FileReadSVG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, - Tk_PhotoHandle imageHandle, int destX, int destY, - int width, int height, int srcX, int srcY); -static int StringMatchSVG(Tcl_Obj *dataObj, Tcl_Obj *format, - int *widthPtr, int *heightPtr, Tcl_Interp *interp); + Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY, + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); +static int StringMatchSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, + Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, + int *heightPtr, Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternal); static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tk_PhotoHandle imageHandle, + Tcl_Obj *format, Tcl_Obj *metadataIn, + Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY); + int srcX, int srcY, + Tcl_Obj *metadataOut, Tcl_DString *driverInternal); static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, const char *input, TkSizeT length, Tcl_Obj *format, RastOpts *ropts); @@ -87,7 +96,7 @@ static void FreeCache(ClientData clientData, Tcl_Interp *interp); * The format record for the SVG nano file format: */ -Tk_PhotoImageFormat tkImgFmtSVGnano = { +Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano = { "svg", /* name */ FileMatchSVG, /* fileMatchProc */ StringMatchSVG, /* stringMatchProc */ @@ -118,11 +127,18 @@ Tk_PhotoImageFormat tkImgFmtSVGnano = { static int FileMatchSVG( - Tcl_Channel chan, - const char *fileName, - Tcl_Obj *formatObj, + Tcl_Interp *interp, /* interpreter pointer */ + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *formatObj, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, - Tcl_Interp *interp) + /* The dimensions of the image are returned + * here if the file is a valid raw GIF file. */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + int *closeChannelPtr, /* Return if the channel may be closed */ + Tcl_DString *driverInternalPtr) + /* memory passed to FileReadGIF */ { TkSizeT length; Tcl_Obj *dataObj = Tcl_NewObj(); @@ -175,14 +191,21 @@ FileMatchSVG( static int FileReadSVG( - Tcl_Interp *interp, - Tcl_Channel chan, - const char *fileName, - Tcl_Obj *formatObj, - Tk_PhotoHandle imageHandle, - int destX, int destY, - int width, int height, - int srcX, int srcY) + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *formatObj, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + Tk_PhotoHandle imageHandle, /* The photo image to write into. */ + int destX, int destY, /* Coordinates of top-left pixel in photo + * image to be written to. */ + int width, int height, /* Dimensions of block of photo image to be + * written to. */ + int srcX, int srcY, /* Coordinates of top-left pixel to be used in + * image being read. */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternalPtr) + /* memory passed from FileMatchGIF */ { TkSizeT length; const char *data; @@ -232,10 +255,15 @@ FileReadSVG( static int StringMatchSVG( - Tcl_Obj *dataObj, - Tcl_Obj *formatObj, - int *widthPtr, int *heightPtr, - Tcl_Interp *interp) + Tcl_Interp *interp, /* interpreter to report errors */ + Tcl_Obj *dataObj, /* the object containing the image data */ + Tcl_Obj *formatObj, /* the image format object, or NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + int *widthPtr, /* where to put the string width */ + int *heightPtr, /* where to put the string height */ + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternalPtr) + /* memory to pass to StringReadGIF */ { TkSizeT length; const char *data; @@ -279,13 +307,17 @@ StringMatchSVG( static int StringReadSVG( - Tcl_Interp *interp, - Tcl_Obj *dataObj, - Tcl_Obj *formatObj, - Tk_PhotoHandle imageHandle, - int destX, int destY, - int width, int height, - int srcX, int srcY) + Tcl_Interp *interp, /* interpreter for reporting errors in */ + Tcl_Obj *dataObj, /* object containing the image */ + Tcl_Obj *formatObj, /* format object, or NULL */ + Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tk_PhotoHandle imageHandle, /* the image to write this data into */ + int destX, int destY, /* The rectangular region of the */ + int width, int height, /* image to copy */ + int srcX, int srcY, + Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternalPtr) + /* memory passed from StringReadGIF */ { TkSizeT length; const char *data; diff --git a/generic/tkInt.h b/generic/tkInt.h index b3cd0fa..8f0c0b5 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -1061,7 +1061,7 @@ MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; -MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtSVGnano; +MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano; MODULE_SCOPE TkMainInfo *tkMainWindowList; MODULE_SCOPE Tk_ImageType tkPhotoImageType; MODULE_SCOPE Tcl_HashTable tkPredefBitmapTable; diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 8e74baf..897a2bd 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -336,11 +336,11 @@ CreateTopLevelWindow( * Create built-in photo image formats. */ - Tk_CreatePhotoImageFormat(&tkImgFmtDefault); + Tk_CreatePhotoImageFormat(&tkImgFmtDefault); Tk_CreatePhotoImageFormatVersion3(&tkImgFmtGIF); Tk_CreatePhotoImageFormat(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); - Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); + Tk_CreatePhotoImageFormatVersion3(&tkImgFmtSVGnano); } if ((parent != NULL) && (screenName != NULL) && (screenName[0] == '\0')) { -- cgit v0.12 From 7724cf274e81e8f3edbfe0616616b4d6dd7df01d Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 9 Jun 2020 12:45:33 +0000 Subject: TIP 529 image metadata: PNG format driver to version 3 interface --- generic/tkImgGIF.c | 95 +++++++++++++++++++++++---------------------- generic/tkImgPNG.c | 112 ++++++++++++++++++++++++++++++++--------------------- generic/tkInt.h | 2 +- generic/tkWindow.c | 2 +- 4 files changed, 119 insertions(+), 92 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 4aa6413..1b9652d 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -120,33 +120,37 @@ typedef size_t (WriteBytesFunc) (ClientData clientData, const char *bytes, static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, - Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - Tcl_Obj *metadataOut, int *closeChannelPtr, - Tcl_DString *driverInternal); + Tcl_Obj *metadataInObj, int *widthPtr, + int *heightPtr, Tcl_Obj *metadataOutObj, + int *closeChannelPtr, Tcl_DString *driverInternal); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, - Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, + Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternal); static int StringMatchGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOut, + Tcl_Obj *format, Tcl_Obj *metadataInObj, + int *widthPtr, int *heightPtr, + Tcl_Obj *metadataOutObj, Tcl_DString *driverInternal); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, + Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternal); static int FileWriteGIF(Tcl_Interp *interp, const char *filename, - Tcl_Obj *format, Tcl_Obj *metadataIn, + Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); static int StringWriteGIF(Tcl_Interp *interp, Tcl_Obj *format, - Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); -static int CommonWriteGIF(Tcl_Interp *interp, ClientData clientData, + Tcl_Obj *metadataInObj, + Tk_PhotoImageBlock *blockPtr); +static int CommonWriteGIF(Tcl_Interp *interp, + ClientData clientData, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); + Tcl_Obj *metadataInObj, + Tk_PhotoImageBlock *blockPtr); Tk_PhotoImageFormatVersion3 tkImgFmtGIF = { "gif", /* name */ @@ -178,7 +182,7 @@ 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 *metadataOut); + int *transparent, Tcl_Obj *metadataOutObj); static int GetCode(Tcl_Channel chan, int code_size, int flag, GIFImageConfig *gifConfPtr); static int GetDataBlock(GIFImageConfig *gifConfPtr, @@ -360,11 +364,11 @@ FileMatchGIF( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *format, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ int *closeChannelPtr, /* Return if the channel may be closed */ Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { @@ -402,7 +406,7 @@ FileReadGIF( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *format, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* The photo image to write into. */ int destX, int destY, /* Coordinates of top-left pixel in photo * image to be written to. */ @@ -410,7 +414,7 @@ FileReadGIF( * written to. */ int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ Tcl_DString *driverInternal)/* memory passed from FileMatchGIF */ { int fileWidth, fileHeight, imageWidth, imageHeight; @@ -554,7 +558,7 @@ FileReadGIF( goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadataOut) + gifConfPtr->workingBuffer, &transparent, metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); @@ -750,7 +754,7 @@ FileReadGIF( goto error; } if (DoExtension(gifConfPtr, chan, gifLabel, - gifConfPtr->workingBuffer, &transparent, metadataOut) + gifConfPtr->workingBuffer, &transparent, metadataOutObj) < 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "error reading extension in GIF image", -1)); @@ -852,10 +856,10 @@ StringMatchGIF( Tcl_Interp *dummy, /* not used */ Tcl_Obj *dataObj, /* the object containing the image data */ Tcl_Obj *format, /* the image format object, or NULL */ - Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ { unsigned char *data, header[10]; @@ -924,12 +928,12 @@ StringReadGIF( Tcl_Interp *interp, /* interpreter for reporting errors in */ Tcl_Obj *dataObj, /* object containing the image */ Tcl_Obj *format, /* format object, or NULL */ - Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ int srcX, int srcY, - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ Tcl_DString *driverInternal)/* memory passed from StringReadGIF */ { MFile handle, *hdlPtr = &handle; @@ -959,9 +963,8 @@ StringReadGIF( */ return FileReadGIF(interp, (Tcl_Channel) hdlPtr, xferFormat, format, - metadataIn, - imageHandle, destX, destY, width, height, srcX, srcY, - metadataOut, driverInternal); + metadataInObj, imageHandle, destX, destY, width, height, srcX, srcY, + metadataOutObj, driverInternal); } /* @@ -1070,7 +1073,7 @@ DoExtension( int label, unsigned char *buf, /* defined as 280 byte working buffer */ int *transparent, - Tcl_Obj *metadataOut) + Tcl_Obj *metadataOutObj) { int count; /* Prepare extension name @@ -1106,7 +1109,7 @@ DoExtension( return -1; } /* Detect XMP extension */ - if (NULL != metadataOut + if (NULL != metadataOutObj && 0 == memcmp(buf,"XMP DataXMP",11)) { /* XMP format does not use the block structure of GIF * The data is utf-8 which never contains 0's @@ -1148,7 +1151,7 @@ DoExtension( encoding = Tcl_GetEncoding(NULL, "utf-8"); Tcl_DStringInit(&recodedDString); Tcl_ExternalToUtfDString(encoding, Tcl_DStringValue(&dataDString), length, &recodedDString); - result = Tcl_DictObjPut(NULL, metadataOut, + result = Tcl_DictObjPut(NULL, metadataOutObj, Tcl_NewStringObj("XMP",-1), Tcl_NewStringObj(Tcl_DStringValue(&recodedDString), Tcl_DStringLength(&recodedDString))); @@ -1176,9 +1179,9 @@ DoExtension( break; } /* Add extension to dict */ - if (NULL != metadataOut + if (NULL != metadataOutObj && extensionStreamName[0] != '\0' ) { - Tcl_Obj *metadataData; + Tcl_Obj *ValueObj; int length = 0; for (;;) { count = GetDataBlock(gifConfPtr, chan, buf); @@ -1187,10 +1190,10 @@ DoExtension( return -1; case 0: /* end of data */ if (length > 0) { - if ( TCL_OK != Tcl_DictObjPut(NULL, metadataOut, + if ( TCL_OK != Tcl_DictObjPut(NULL, metadataOutObj, Tcl_NewByteArrayObj( (unsigned char *)extensionStreamName, - strlen(extensionStreamName)), metadataData)) { + strlen(extensionStreamName)), ValueObj)) { return -1; } } @@ -1199,12 +1202,12 @@ DoExtension( default: /* block received */ if (length == 0) { /* first block */ - metadataData = Tcl_NewByteArrayObj(buf, count); + ValueObj = Tcl_NewByteArrayObj(buf, count); length = count; } else { /* consecutive block */ unsigned char *bytePtr; - bytePtr = Tcl_SetByteArrayLength(metadataData, length+count); + bytePtr = Tcl_SetByteArrayLength(ValueObj, length+count); memcpy(bytePtr+length,buf,count); length += count; } @@ -1876,7 +1879,7 @@ FileWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ const char *filename, Tcl_Obj *format, - Tcl_Obj *metadataIn, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { Tcl_Channel chan = NULL; @@ -1892,7 +1895,7 @@ FileWriteGIF( return TCL_ERROR; } - result = CommonWriteGIF(interp, chan, WriteToChannel, format, metadataIn, + result = CommonWriteGIF(interp, chan, WriteToChannel, format, metadataInObj, blockPtr); if (Tcl_Close(interp, chan) == TCL_ERROR) { @@ -1906,7 +1909,7 @@ StringWriteGIF( Tcl_Interp *interp, /* Interpreter to use for reporting errors and * returning the GIF data. */ Tcl_Obj *format, - Tcl_Obj *metadataIn, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { int result; @@ -1914,7 +1917,7 @@ StringWriteGIF( Tcl_IncrRefCount(objPtr); result = CommonWriteGIF(interp, objPtr, WriteToByteArray, format, - metadataIn, blockPtr); + metadataInObj, blockPtr); if (result == TCL_OK) { Tcl_SetObjResult(interp, objPtr); } @@ -1954,7 +1957,7 @@ CommonWriteGIF( ClientData handle, WriteBytesFunc *writeProc, Tcl_Obj *format, - Tcl_Obj *metadataIn, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { GifWriterState state; @@ -2088,14 +2091,14 @@ CommonWriteGIF( /* * Check for metadata keys to add to file */ - if (NULL != metadataIn) { + if (NULL != metadataInObj) { Tcl_Obj *itemData; /* * Check and code comment block */ - if (TCL_ERROR == Tcl_DictObjGet(interp, metadataIn, + if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, Tcl_NewStringObj("Comment",-1), &itemData)) { return TCL_ERROR; @@ -2133,7 +2136,7 @@ CommonWriteGIF( * Check and code XMP block */ - if (TCL_ERROR == Tcl_DictObjGet(interp, metadataIn, + if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, Tcl_NewStringObj("XMP",-1), &itemData)) { return TCL_ERROR; diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 0c62b58..669bc27 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -199,15 +199,20 @@ static int DecodePNG(Tcl_Interp *interp, PNGImage *pngPtr, int destX, int destY); static int EncodePNG(Tcl_Interp *interp, Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr); -static int FileMatchPNG(Tcl_Channel chan, const char *fileName, - Tcl_Obj *fmtObj, int *widthPtr, int *heightPtr, - Tcl_Interp *interp); +static int FileMatchPNG(Tcl_Interp *interp, Tcl_Channel chan, + const char *fileName, Tcl_Obj *fmtObj, + Tcl_Obj *metadataInObj, int *widthPtr, + int *heightPtr, Tcl_Obj *metadataOut, + int *closeChannelPtr, Tcl_DString *driverInternal); static int FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, - Tk_PhotoHandle imageHandle, int destX, int destY, - int width, int height, int srcX, int srcY); + Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, + int destX, int destY, int width, int height, + int srcX, int srcY, Tcl_Obj *metadataOutPtr, + Tcl_DString *driverInternal); static int FileWritePNG(Tcl_Interp *interp, const char *filename, - Tcl_Obj *fmtObj, Tk_PhotoImageBlock *blockPtr); + Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, + Tk_PhotoImageBlock *blockPtr); static int InitPNGImage(Tcl_Interp *interp, PNGImage *pngPtr, Tcl_Channel chan, Tcl_Obj *objPtr, int dir); static inline unsigned char Paeth(int a, int b, int c); @@ -236,14 +241,20 @@ static int ReadTRNS(Tcl_Interp *interp, PNGImage *pngPtr, int chunkSz, unsigned long crc); static int SkipChunk(Tcl_Interp *interp, PNGImage *pngPtr, int chunkSz, unsigned long crc); -static int StringMatchPNG(Tcl_Obj *dataObj, Tcl_Obj *fmtObj, +static int StringMatchPNG(Tcl_Interp *interp, Tcl_Obj *pObjData, + Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, - Tcl_Interp *interp); -static int StringReadPNG(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle, + Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternal); +static int StringReadPNG(Tcl_Interp *interp, Tcl_Obj *pObjData, + Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, + Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY); + int srcX, int srcY, Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternal); + static int StringWritePNG(Tcl_Interp *interp, Tcl_Obj *fmtObj, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); static int UnfilterLine(Tcl_Interp *interp, PNGImage *pngPtr); static inline int WriteByte(Tcl_Interp *interp, PNGImage *pngPtr, @@ -267,7 +278,7 @@ static inline int WriteInt32(Tcl_Interp *interp, PNGImage *pngPtr, * The format record for the PNG file format: */ -Tk_PhotoImageFormat tkImgFmtPNG = { +Tk_PhotoImageFormatVersion3 tkImgFmtPNG = { "png", /* name */ FileMatchPNG, /* fileMatchProc */ StringMatchPNG, /* stringMatchProc */ @@ -2667,12 +2678,17 @@ DecodePNG( static int FileMatchPNG( - Tcl_Channel chan, - const char *fileName, - Tcl_Obj *fmtObj, - int *widthPtr, - int *heightPtr, - Tcl_Interp *interp) + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *fmtObj, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + int *widthPtr, int *heightPtr, + /* The dimensions of the image are returned + * here if the file is a valid raw GIF file. */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + int *closeChannelPtr, /* Return if the channel may be closed */ + Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { PNGImage png; int match = 0; @@ -2713,17 +2729,20 @@ FileMatchPNG( static int FileReadPNG( - Tcl_Interp *interp, - Tcl_Channel chan, - const char *fileName, - Tcl_Obj *fmtObj, - Tk_PhotoHandle imageHandle, - int destX, - int destY, - int width, - int height, - int srcX, - int srcY) + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + Tcl_Channel chan, /* The image file, open for reading. */ + const char *fileName, /* The name of the image file. */ + Tcl_Obj *fmtObj, /* User-specified format object, or NULL. */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + Tk_PhotoHandle imageHandle, /* The photo image to write into. */ + int destX, int destY, /* Coordinates of top-left pixel in photo + * image to be written to. */ + int width, int height, /* Dimensions of block of photo image to be + * written to. */ + int srcX, int srcY, /* Coordinates of top-left pixel to be used in + * image being read. */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternal)/* memory passed from FileMatchGIF */ { PNGImage png; int result = TCL_ERROR; @@ -2763,11 +2782,14 @@ FileReadPNG( static int StringMatchPNG( - Tcl_Obj *pObjData, - Tcl_Obj *fmtObj, - int *widthPtr, - int *heightPtr, - Tcl_Interp *interp) + Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ + Tcl_Obj *pObjData, /* the object containing the image data */ + Tcl_Obj *fmtObj, /* the image format object, or NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + int *widthPtr, /* where to put the string width */ + int *heightPtr, /* where to put the string height */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ { PNGImage png; int match = 0; @@ -2807,16 +2829,16 @@ StringMatchPNG( static int StringReadPNG( - Tcl_Interp *interp, - Tcl_Obj *pObjData, - Tcl_Obj *fmtObj, - Tk_PhotoHandle imageHandle, - int destX, - int destY, - int width, - int height, - int srcX, - int srcY) + Tcl_Interp *interp, /* interpreter for reporting errors in */ + Tcl_Obj *pObjData, /* object containing the image */ + Tcl_Obj *fmtObj, /* format object, or NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + Tk_PhotoHandle imageHandle, /* the image to write this data into */ + int destX, int destY, /* The rectangular region of the */ + int width, int height, /* image to copy */ + int srcX, int srcY, + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternal)/* memory passed from StringReadGIF */ { PNGImage png; int result = TCL_ERROR; @@ -3466,6 +3488,7 @@ FileWritePNG( Tcl_Interp *interp, const char *filename, Tcl_Obj *fmtObj, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { Tcl_Channel chan; @@ -3537,6 +3560,7 @@ static int StringWritePNG( Tcl_Interp *interp, Tcl_Obj *fmtObj, + Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { Tcl_Obj *resultObj = Tcl_NewObj(); diff --git a/generic/tkInt.h b/generic/tkInt.h index 8f0c0b5..ae25937 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -1059,7 +1059,7 @@ MODULE_SCOPE Tk_ImageType tkBitmapImageType; MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtGIF; MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; -MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPNG; +MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano; MODULE_SCOPE TkMainInfo *tkMainWindowList; diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 897a2bd..2adb0fa 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -338,7 +338,7 @@ CreateTopLevelWindow( Tk_CreatePhotoImageFormat(&tkImgFmtDefault); Tk_CreatePhotoImageFormatVersion3(&tkImgFmtGIF); - Tk_CreatePhotoImageFormat(&tkImgFmtPNG); + Tk_CreatePhotoImageFormatVersion3(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); Tk_CreatePhotoImageFormatVersion3(&tkImgFmtSVGnano); } -- cgit v0.12 From 040e90faf796eff48ac41d4b595e12ecf1c9638d Mon Sep 17 00:00:00 2001 From: oehhar Date: Thu, 11 Jun 2020 14:28:11 +0000 Subject: TIP529 image metadata: implement image write/data with metadata property. GIF comment write correction. GIF file tests --- generic/tkImgGIF.c | 2 +- generic/tkImgPhoto.c | 29 ++++- tests/imgPhoto.test | 341 ++++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 354 insertions(+), 18 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 1b9652d..02ff197 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -2109,7 +2109,7 @@ CommonWriteGIF( comment = Tcl_GetByteArrayFromObj(itemData, &length); if (length > 0) { /* write comment header */ - writeProc(handle, (char *) "\x21\fe", 2); + writeProc(handle, (char *) "\x21\xfe", 2); /* write comment blocks */ for (;length > 0;) { int blockLength; diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 08a79c4..20cd036 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -756,6 +756,7 @@ ImgPhotoCmd( case PHOTO_DATA: { char *data = NULL; Tcl_Obj *freeObj = NULL; + Tcl_Obj *metadataIn; /* * photo data command - first parse and check any options given. @@ -768,6 +769,7 @@ ImgPhotoCmd( memset(&options, 0, sizeof(options)); options.name = NULL; options.format = NULL; + options.metadata = NULL; options.fromX = 0; options.fromY = 0; if (ParseSubcommandOptions(&options, interp, @@ -802,6 +804,16 @@ ImgPhotoCmd( options.format = Tcl_NewStringObj("default", -1); freeObj = options.format; } + + /* + * Use argument metadata if specified, otherwise the master metadata + */ + + if (NULL != options.metadata) { + metadataIn = options.metadata; + } else { + metadataIn = masterPtr->metadata; + } /* * Search for an appropriate image string format handler. @@ -871,7 +883,7 @@ ImgPhotoCmd( if (stringWriteProc == NULL) { result = (stringWriteProcVersion3)(interp, - options.format, options.metadata, &block); + options.format, metadataIn, &block); } else if (oldformat) { Tcl_DString buffer; typedef int (*OldStringWriteProc)(Tcl_Interp *interp, @@ -1431,7 +1443,7 @@ readCleanup: case PHOTO_WRITE: { char *data; const char *fmtString; - Tcl_Obj *format; + Tcl_Obj *format, *metadataIn; int usedExt; /* @@ -1492,6 +1504,17 @@ readCleanup: usedExt = 0; } + + /* + * Use argument metadata if specified, otherwise the master metadata + */ + + if (NULL != options.metadata) { + metadataIn = options.metadata; + } else { + metadataIn = masterPtr->metadata; + } + /* * Search for an appropriate image file format handler, and give an * error if none is found. @@ -1585,7 +1608,7 @@ readCleanup: Tcl_GetString(options.name), format, &block); } else { result = imageFormatVersion3->fileWriteProc(interp, - Tcl_GetString(options.name), format, options.metadata, + Tcl_GetString(options.name), format, metadataIn, &block); } if (options.background) { diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index b0d41b1..69ce7c1 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2148,7 +2148,7 @@ set gifdata "\x2c\x00\x00\x00\x00\x10\x00\x10\x00\x00" append gifdata "\x03\x21\x78\xba\xdc\x2d\x30\x42\x77\xa4\x15\xef\xda\xa5\xb5\xea\xd7\x07\x4a\xe2\x38\x55\xe6\x99\xaa\x6b\x69\x72\x2f\x33\x52\x1d\x65\x37\x09\x00" set gifend "\x3b" -test imgPhoto-23.1 {GIF comment before image data} -setup { +test imgPhoto-23.1 {GIF comment before image data (-data)} -setup { set data $::gifstart # Append a comment extension block with data "ABCD" append data "\x21\xfe\x04" "ABCD" "\x0" @@ -2161,7 +2161,25 @@ test imgPhoto-23.1 {GIF comment before image data} -setup { catch {image delete gif1} } -result {Comment ABCD} -test imgPhoto-23.2 {GIF comment after image data} -setup { +test imgPhoto-23.2 {GIF file comment before image data (-file)} -setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {Comment ABCD} + +test imgPhoto-23.3 {GIF comment after image data (-data)} -setup { set data $::gifstart append data $::gifdata # Append a comment extension block with data "ABCD" @@ -2175,7 +2193,26 @@ test imgPhoto-23.2 {GIF comment after image data} -setup { catch {image delete gif1} } -result {Comment ABCD} -test imgPhoto-23.3 {Two GIF comment blocks} -setup { +test imgPhoto-23.4 {GIF comment after image data (-file)} -setup { + set data $::gifstart + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {Comment ABCD} + +test imgPhoto-23.5 {Two GIF comment blocks (-data)} -setup { set data $::gifstart # Append a comment extension block with data "1234" append data "\x21\xfe\x04" "1234" "\x0" @@ -2191,7 +2228,28 @@ test imgPhoto-23.3 {Two GIF comment blocks} -setup { catch {image delete gif1} } -result {Comment ABCD} -test imgPhoto-23.4 {XMP comment block before image} -setup { +test imgPhoto-23.6 {Two GIF comment blocks (-file)} -setup { + set data $::gifstart + # Append a comment extension block with data "1234" + append data "\x21\xfe\x04" "1234" "\x0" + append data $::gifdata + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifend + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {Comment ABCD} + +test imgPhoto-23.7 {XMP comment block before image (-data)} -setup { set data $::gifstart # Append an XMP comment extension block (including a Unicode codepoint 2022 set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + append data $::gifdata + # Trailer + append data $::gifend + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {1} + +test imgPhoto-23.9 {XMP comment block after image (-data)} -setup { set data $::gifstart append data $::gifdata @@ -2244,7 +2333,40 @@ test imgPhoto-23.5 {XMP comment block after image} -setup { catch {image delete gif1} } -result {1} -test imgPhoto-23.6 {empty XMP comment block after image} -setup { +test imgPhoto-23.10 {XMP comment block after image (-file)} -setup { + set data $::gifstart + append data $::gifdata + + # Append an XMP comment extension block (including a Unicode codepoint 2022 + set xmpdata "\ + \ + 3" + append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + + # Trailer + append data $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + set d [dict get [gif1 cget -metadata] XMP] + expr {$d eq $xmpdata} +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {1} + +test imgPhoto-23.11 {empty XMP comment block after image (-data)} -setup { set data $::gifstart append data $::gifdata @@ -2264,7 +2386,35 @@ test imgPhoto-23.6 {empty XMP comment block after image} -setup { catch {image delete gif1} } -result {} -test imgPhoto-23.7 {create: test if shared metadata object is not preserved} -setup { +test imgPhoto-23.12 {empty XMP comment block after image (-file)} -setup { + set data $::gifstart + append data $::gifdata + + append data "\x21\xff\x0B" "XMP DataXMP" + # Special trailer of 1 ff fe ... 02 01 00 00 + append data "\x01" + for {set i 0xff} {$i != -1} {incr i -1} { + append data [binary format c $i] + } + append data "\x00" + # Trailer + append data $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -file $path + dict get [gif1 cget -metadata] XMP +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {} + +test imgPhoto-23.13 {create: test if shared metadata object is not preserved\ + (-data)}\ +-setup { set data $::gifstart # Append a comment extension block with data "ABCD" append data "\x21\xfe\x04" "ABCD" "\x0" @@ -2279,7 +2429,32 @@ test imgPhoto-23.7 {create: test if shared metadata object is not preserved} -se catch {image delete gif1} } -result {{A 1 Comment ABCD} {A 1} {A 1}} -test imgPhoto-23.8 {configure: test if shared metadata object is not preserved} -setup { +test imgPhoto-23.14 {create: test if shared metadata object is not preserved\ + (-file)}\ +-setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + image create photo gif1 -file $path -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.15 {configure: test if shared metadata object is not\ + preserved (empty image, -data)}\ +-setup { set data $::gifstart # Append a comment extension block with data "ABCD" append data "\x21\xfe\x04" "ABCD" "\x0" @@ -2295,24 +2470,75 @@ test imgPhoto-23.8 {configure: test if shared metadata object is not preserved} catch {image delete gif1} } -result {{A 1 Comment ABCD} {A 1} {A 1}} -test imgPhoto-23.9 {configure: test if shared metadata object is not preserved} -setup { - set data $::gifstart$::gifdata$::gifend +test imgPhoto-23.16 {configure: test if shared metadata object is not preserved\ + (empty image, -file)}\ +-setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h } -body { - image create photo gif1 -data $data + image create photo gif1 set metadataDict [dict create A 1] set metadataDict2 $metadataDict + gif1 configure -file $path -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.17 {configure: test if shared metadata object is not preserved\ + (metadata replace, -data}\ +-setup { set data $::gifstart # Append a comment extension block with data "ABCD" append data "\x21\xfe\x04" "ABCD" "\x0" # Trailer append data $::gifdata $::gifend +} -body { + image create photo gif1 -data "$::gifstart$::gifdata$::gifend" + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict gif1 configure -data $data -format gif -metadata $metadataDict list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 } -cleanup { catch {image delete gif1} } -result {{A 1 Comment ABCD} {A 1} {A 1}} -test imgPhoto-23.9 {configure: test if shared metadata object is not preserved} -setup { +test imgPhoto-23.18 {configure: test if shared metadata object is not preserved\ + (metadata replace, -file}\ +-setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -data "$::gifstart$::gifdata$::gifend" + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + gif1 configure -file $path -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.19 {configure: test if shared metadata object is not preserved\ + (-data)}\ +-setup { set data $::gifstart$::gifdata$::gifend } -body { image create photo gif1 -data $data @@ -2329,7 +2555,32 @@ test imgPhoto-23.9 {configure: test if shared metadata object is not preserved} catch {image delete gif1} } -result {{A 1 Comment ABCD} {A 1} {A 1}} -test imgPhoto-23.10 {output data with comment} -setup { +test imgPhoto-23.20 {configure: test if shared metadata object is not preserved\ + (-file)}\ +-setup { + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend + + set path [file join [configure -tmpdir] test.gif] + set h [open $path "WRONLY BINARY CREAT"] + puts $h $data + close $h +} -body { + image create photo gif1 -data "$::gifstart$::gifdata$::gifend" + set metadataDict [dict create A 1] + set metadataDict2 $metadataDict + gif1 configure -file $path -format gif -metadata $metadataDict + list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {{A 1 Comment ABCD} {A 1} {A 1}} + +test imgPhoto-23.21 {output data with comment (from -metadata argument)}\ +-setup { set data $::gifstart$::gifdata$::gifend } -body { image create photo gif1 -data $data @@ -2338,7 +2589,49 @@ test imgPhoto-23.10 {output data with comment} -setup { catch {image delete gif1} } -match glob -result {*ABCD*} -test imgPhoto-23.11 {output data with XMP} -setup { +test imgPhoto-23.22 {output file with comment (from -metadata argument)}\ +-setup { + set data $::gifstart$::gifdata$::gifend + set path [file join [configure -tmpdir] test.gif] +} -body { + image create photo gif1 -data $data + gif1 write $path -format gif -metadata [dict create Comment ABCD] + image delete gif1 + image create photo gif1 -file $path + dict get [gif1 cget -metadata] Comment +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {ABCD} + +test imgPhoto-23.23 {output data with comment (from -metadata property)}\ +-setup { + set data $::gifstart$::gifdata$::gifend +} -body { + image create photo gif1 -data $data + gif1 configure -metadata [dict create Comment ABCD] + set gifData [gif1 data -format gif] +} -cleanup { + catch {image delete gif1} +} -match glob -result {*ABCD*} + +test imgPhoto-23.24 {output file with comment (from -metadata property)}\ +-setup { + set data $::gifstart$::gifdata$::gifend + set path [file join [configure -tmpdir] test.gif] +} -body { + image create photo gif1 -data $data + gif1 configure -metadata [dict create Comment ABCD] + gif1 write $path -format gif + image delete gif1 + image create photo gif1 -file $path + dict get [gif1 cget -metadata] Comment +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {ABCD} + +test imgPhoto-23.25 {output data with XMP (-data)} -setup { set data $::gifstart$::gifdata$::gifend set XMPData\ "\ + \ + 3" + set path [file join [configure -tmpdir] test.gif] +} -body { + image create photo gif1 -data $data + set gifData [gif1 write $path -format gif -metadata [dict create\ + XMP $XMPData]] + image delete gif1 + image create photo gif1 -file $path + expr {[dict get [gif1 cget -metadata] XMP] eq $XMPData} +} -cleanup { + catch {image delete gif1} +} -result {1} + + unset -nocomplain gifstart gifdata gifend -- cgit v0.12 From fab62ba87797a77dd9eac2c168580ea41829711f Mon Sep 17 00:00:00 2001 From: oehhar Date: Fri, 12 Jun 2020 12:12:53 +0000 Subject: TIP529 image metadata: correct stubs entry --- generic/tkDecls.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/generic/tkDecls.h b/generic/tkDecls.h index f1b3640..fce0598 100644 --- a/generic/tkDecls.h +++ b/generic/tkDecls.h @@ -1729,10 +1729,8 @@ extern const TkStubs *tkStubsPtr; (tkStubsPtr->tk_CreateOldImageType) /* 272 */ #define Tk_CreateOldPhotoImageFormat \ (tkStubsPtr->tk_CreateOldPhotoImageFormat) /* 273 */ -#define Tk_PhotoGetMetadata \ - (tkStubsPtr->tk_PhotoGetMetadata) /* 274 */ -#define Tk_PhotoSetMetadata \ - (tkStubsPtr->tk_PhotoSetMetadata) /* 275 */ +#define Tk_CreatePhotoImageFormatVersion3 \ + (tkStubsPtr->tk_CreatePhotoImageFormatVersion3) /* 274 */ #endif /* defined(USE_TK_STUBS) */ -- cgit v0.12 From 2db28ea679f6ec225e01b37471beb98385f2741a Mon Sep 17 00:00:00 2001 From: oehhar Date: Sun, 21 Jun 2020 12:06:34 +0000 Subject: TIP529 image metadata: implement svg intermediate metadata memory --- generic/tkImgGIF.c | 6 +- generic/tkImgSVGnano.c | 1232 +++++++++++++++++++++++++++++++++++++----------- tests/imgSVGnano.test | 24 + 3 files changed, 985 insertions(+), 277 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 02ff197..06a44ac 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1060,7 +1060,7 @@ ReadColorMap( * - Application extension * - XMP data is stored in key "XMP" * - any other under the key Application_ -* - Comment extension in key "Comment" +* - Comment extension in key "comment" * Plain text extensions are currently ignored. * *---------------------------------------------------------------------- @@ -1096,7 +1096,7 @@ DoExtension( } break; case 0xfe: /* Comment Extension */ - strcpy(extensionStreamName,"Comment"); + strcpy(extensionStreamName,"comment"); /* copy the extension data below */ break; case 0xff: /* Application Extension */ @@ -2099,7 +2099,7 @@ CommonWriteGIF( */ if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, - Tcl_NewStringObj("Comment",-1), + Tcl_NewStringObj("comment",-1), &itemData)) { return TCL_ERROR; } diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c index bed4776..e15de54 100644 --- a/generic/tkImgSVGnano.c +++ b/generic/tkImgSVGnano.c @@ -27,13 +27,84 @@ #define NANOSVGRAST_IMPLEMENTATION #include "nanosvgrast.h" -/* Additional parameters to nsvgRasterize() */ +/* + * Serialized data version + * This consists of "svg" plus binary '1' at byte locations in an int. + * It serves as indication, version and endinaess check + */ + +#define STRUCTURE_VERSION ('s'+256*'v'+65535*'g'+16777216*1) + +/* + * Serialized image data header + */ typedef struct { + unsigned int structureVersion; double scale; int scaleToHeight; int scaleToWidth; -} RastOpts; + float width; // Width of the image. + float height; // Height of the image. + int shapeCount; + int pathCount; + int ptsCount; + int gradientCount; + int gradientStopCount; +} serializedHeader; + +/* + * Serialized structures from NSCG + * All pointers are replaced by array indexes + */ + +typedef struct NSVGgradientSerialized { + float xform[6]; + char spread; + float fx, fy; + int nstops; + int stops; +} NSVGgradientSerialized; + +typedef struct NSVGpaintSerialized { + char type; + union { + unsigned int color; + int gradient; + }; +} NSVGpaintSerialized; + + +typedef struct NSVGpathSerialized +{ + int pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... + int npts; // Total number of bezier points. + // Caution: pair of floats + char closed; // Flag indicating if shapes should be treated as closed. + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + int next; // Pointer to next path, or NULL if last element. +} NSVGpathSerialized; + +typedef struct NSVGshapeSerialized +{ + char id[64]; // Optional 'id' attr of the shape or its group + NSVGpaintSerialized fill; // Fill paint + NSVGpaintSerialized stroke; // Stroke paint + float opacity; // Opacity of the shape. + float strokeWidth; // Stroke width (scaled). + float strokeDashOffset; // Stroke dash offset (scaled). + float strokeDashArray[8]; // Stroke dash array (scaled). + char strokeDashCount; // Number of dash values in dash array. + char strokeLineJoin; // Stroke join type. + char strokeLineCap; // Stroke cap type. + float miterLimit; // Miter limit + char fillRule; // Fill rule, see NSVGfillRule. + unsigned char flags; // Logical or of NSVG_FLAGS_* flags + float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. + int paths; // Linked list of paths in the image. + int next; // Pointer to next shape, or NULL if last element. +} NSVGshapeSerialized; + /* * Per interp cache of last NSVGimage which was matched to @@ -49,7 +120,6 @@ typedef struct { ClientData dataOrChan; Tcl_DString formatString; NSVGimage *nsvgImage; - RastOpts ropts; } NSVGcache; static int FileMatchSVG(Tcl_Interp *interp, Tcl_Channel chan, @@ -76,21 +146,33 @@ static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *metadataOut, Tcl_DString *driverInternal); static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, const char *input, TkSizeT length, Tcl_Obj *format, - RastOpts *ropts); + serializedHeader *serializedHeaderPtr); static int RasterizeSVG(Tcl_Interp *interp, - Tk_PhotoHandle imageHandle, NSVGimage *nsvgImage, + Tk_PhotoHandle imageHandle, + char *svgBlob, + TkSizeT serializeDataLength, int destX, int destY, int width, int height, - int srcX, int srcY, RastOpts *ropts); -static double GetScaleFromParameters(NSVGimage *nsvgImage, - RastOpts *ropts, int *widthPtr, int *heightPtr); -static NSVGcache * GetCachePtr(Tcl_Interp *interp); -static int CacheSVG(Tcl_Interp *interp, ClientData dataOrChan, - Tcl_Obj *formatObj, NSVGimage *nsvgImage, - RastOpts *ropts); -static NSVGimage * GetCachedSVG(Tcl_Interp *interp, ClientData dataOrChan, - Tcl_Obj *formatObj, RastOpts *ropts); -static void CleanCache(Tcl_Interp *interp); -static void FreeCache(ClientData clientData, Tcl_Interp *interp); + int srcX, int srcY); +static double GetScaleFromParameters( + serializedHeader *serializedHeaderPtr, + int *widthPtr, int *heightPtr); +static void SerializeNSVG(Tcl_DString *driverInternalPtr, + NSVGimage *nsvgImage); +static void SerializePath(struct NSVGpath *pathPtr, + serializedHeader *serializedHeaderPtr, + Tcl_DString *pathDStringPtr, + Tcl_DString *ptrDStringPtr); +static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, + serializedHeader *serializedHeaderPtr, + Tcl_DString *gradientDStringPtr, + Tcl_DString *gradientStopDStringPtr); +static char * StringCheckMetadata(Tcl_Obj *dataObj, + Tcl_Obj *metadataInObj, int *LengthPtr); +static int SaveSVGBLOBToMetadata(Tcl_Interp *interp, Tcl_Obj *metadataOutObj, + Tcl_DString *driverInternalPtr); +static void nsvgRasterizeSerialized(NSVGrasterizer* r, char *svgBlobPtr, float tx, + float ty, float scale, unsigned char* dst, int w, + int h, int stride); /* * The format record for the SVG nano file format: @@ -143,28 +225,36 @@ FileMatchSVG( TkSizeT length; Tcl_Obj *dataObj = Tcl_NewObj(); const char *data; - RastOpts ropts; NSVGimage *nsvgImage; (void)fileName; + serializedHeader *serializedHeaderPtr; - CleanCache(interp); if (Tcl_ReadChars(chan, dataObj, -1, 0) == TCL_IO_FAILURE) { /* in case of an error reading the file */ Tcl_DecrRefCount(dataObj); return 0; } + Tcl_DStringSetLength(driverInternalPtr, sizeof(serializedHeader)); + serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); data = TkGetStringFromObj(dataObj, &length); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + serializedHeaderPtr); Tcl_DecrRefCount(dataObj); if (nsvgImage != NULL) { - GetScaleFromParameters(nsvgImage, &ropts, widthPtr, heightPtr); + serializedHeaderPtr->width = nsvgImage->width; + serializedHeaderPtr->height = nsvgImage->height; + GetScaleFromParameters(serializedHeaderPtr, widthPtr, heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; } - if (!CacheSVG(interp, chan, formatObj, nsvgImage, &ropts)) { - nsvgDelete(nsvgImage); - } + + /* + * Serialize the NSVGImage structure + * As the DString is resized, serializedHeaderPtr may get invalid + */ + SerializeNSVG(driverInternalPtr, nsvgImage); + nsvgDelete(nsvgImage); return 1; } return 0; @@ -173,6 +263,284 @@ FileMatchSVG( /* *---------------------------------------------------------------------- * + * SerializeNSVG -- + * + * This function saves the NSVGimage structure into the DString. + * + * Results: + * none. + * + * Side effects: + * The DString size is changed and thus, the value pointer may + * change. + * + *---------------------------------------------------------------------- + */ + +static void SerializeNSVG(Tcl_DString *driverInternalPtr, NSVGimage *nsvgImage) { + serializedHeader *serializedHeaderPtr; + Tcl_DString shapeDString, pathDString, ptsDString, gradientDString, + gradientStopDString; + NSVGshape *shapePtr; + + serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); + Tcl_DStringInit(&shapeDString); + Tcl_DStringInit(&pathDString); + Tcl_DStringInit(&ptsDString); + Tcl_DStringInit(&gradientDString); + Tcl_DStringInit(&gradientStopDString); + + serializedHeaderPtr->structureVersion = STRUCTURE_VERSION; + serializedHeaderPtr->shapeCount = 0; + serializedHeaderPtr->pathCount = 0; + serializedHeaderPtr->ptsCount = 0; + serializedHeaderPtr->gradientCount = 0; + serializedHeaderPtr->gradientStopCount = 0; + + for ( shapePtr = nsvgImage->shapes; shapePtr != NULL; + shapePtr = shapePtr->next) { + NSVGshapeSerialized shapeSerialized; + + /* + * Copy serialized shape fix data + */ + + memcpy(shapeSerialized.id, shapePtr->id, 64 * sizeof(char)); + + shapeSerialized.fill = SerializePaint(&(shapePtr->fill), + serializedHeaderPtr, &gradientDString, &gradientStopDString); + + shapeSerialized.stroke = SerializePaint(&(shapePtr->stroke), + serializedHeaderPtr, &gradientDString, &gradientStopDString); + + shapeSerialized.opacity = shapePtr->opacity; + shapeSerialized.strokeWidth = shapePtr->strokeWidth; + shapeSerialized.strokeDashOffset = shapePtr->strokeDashOffset; + memcpy(shapeSerialized.strokeDashArray, shapePtr->strokeDashArray, + 8*sizeof(float)); + shapeSerialized.strokeDashCount = shapePtr->strokeDashCount; + shapeSerialized.strokeLineJoin = shapePtr->strokeLineJoin; + shapeSerialized.strokeLineCap = shapePtr->strokeLineCap; + shapeSerialized.miterLimit = shapePtr->miterLimit; + shapeSerialized.fillRule = shapePtr->fillRule; + shapeSerialized.flags = shapePtr->flags; + memcpy(shapeSerialized.bounds, shapePtr->bounds, 4*sizeof(float)); + + /* + * Serialize the paths linked list + */ + + if ( shapePtr->paths == NULL ) { + shapeSerialized.paths = -1; + } else { + shapeSerialized.paths = serializedHeaderPtr->pathCount; + SerializePath(shapePtr->paths, serializedHeaderPtr, &pathDString, + &ptsDString); + } + + /* + * generate next array position and save to DString + */ + + serializedHeaderPtr->shapeCount++; + shapeSerialized.next = + shapePtr->next == NULL ? -1: + serializedHeaderPtr->shapeCount; + + Tcl_DStringAppend(&shapeDString, + (const char *)&shapeSerialized, sizeof(NSVGshapeSerialized)); + } + + /* + * Write the DStrings into the driver memory one after the other + * Note: serializedHeaderPtr may get invalid due to DString resize + */ + + if (Tcl_DStringLength(&shapeDString) > 0) { + Tcl_DStringAppend(driverInternalPtr, + Tcl_DStringValue(&shapeDString), + Tcl_DStringLength(&shapeDString)); + } + Tcl_DStringFree(&shapeDString); + + if (Tcl_DStringLength(&pathDString) > 0) { + Tcl_DStringAppend(driverInternalPtr, + Tcl_DStringValue(&pathDString), + Tcl_DStringLength(&pathDString)); + } + Tcl_DStringFree(&pathDString); + + if (Tcl_DStringLength(&ptsDString) > 0) { + Tcl_DStringAppend(driverInternalPtr, + Tcl_DStringValue(&ptsDString), + Tcl_DStringLength(&ptsDString)); + } + Tcl_DStringFree(&ptsDString); + + if (Tcl_DStringLength(&gradientDString) > 0) { + Tcl_DStringAppend(driverInternalPtr, + Tcl_DStringValue(&gradientDString), + Tcl_DStringLength(&gradientDString)); + } + Tcl_DStringFree(&gradientDString); + + if (Tcl_DStringLength(&gradientStopDString) > 0) { + Tcl_DStringAppend(driverInternalPtr, + Tcl_DStringValue(&gradientStopDString), + Tcl_DStringLength(&gradientStopDString)); + } + Tcl_DStringFree(&gradientStopDString); +} + +/* + *---------------------------------------------------------------------- + * + * SerializePaint -- + * + * This function transforms the NSVGpaint structure to a serialize + * version. + * The child structures gradient and gradientStop are serialized into + * their DString memory. + * + * Results: + * NSVGPaintSerialized structure. + * + * Side effects: + * The DString size is changed and thus, the value pointer may + * change. + * + *---------------------------------------------------------------------- + */ + +static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, + serializedHeader *serializedHeaderPtr, + Tcl_DString *gradientDStringPtr, + Tcl_DString *gradientStopDStringPtr) +{ + struct NSVGpaintSerialized paintSerialized; + + paintSerialized.type = paintPtr->type; + + if (paintPtr->type == NSVG_PAINT_LINEAR_GRADIENT + || paintPtr->type == NSVG_PAINT_RADIAL_GRADIENT) { + + /* + * Gradient union pointer present + */ + + NSVGgradient* gradientPtr; + NSVGgradientSerialized gradientSerialized; + + gradientPtr = paintPtr->gradient; + memcpy(&(gradientSerialized.xform), gradientPtr->xform, + 6 * sizeof(float) ); + gradientSerialized.spread = gradientPtr->spread; + gradientSerialized.fx = gradientPtr->fx; + gradientSerialized.fy = gradientPtr->fy; + + /* + * Copy gradient stop array to DString + */ + + gradientSerialized.nstops = gradientPtr->nstops; + if ( gradientPtr->nstops == 0 ) { + gradientSerialized.stops = -1; + } else { + gradientSerialized.stops = serializedHeaderPtr->gradientStopCount; + Tcl_DStringAppend(gradientStopDStringPtr, + (const char *)gradientPtr->stops, + gradientPtr->nstops * sizeof(NSVGgradientStop)); + + (serializedHeaderPtr->gradientStopCount) += gradientPtr->nstops; + } + } else { + + /* + * Color union or nothing present + */ + + paintSerialized.color = paintPtr->color; + } + return paintSerialized; +} +/* + *---------------------------------------------------------------------- + * + * SerializePath -- + * + * This function serializes a linked list of NSVGpath structure into + * the corresponding DString array. + * + * Results: + * none + * + * Side effects: + * The DString size is changed and thus, the value pointer may + * change. + * + *---------------------------------------------------------------------- + */ + +static void SerializePath(struct NSVGpath *pathPtr, + serializedHeader *serializedHeaderPtr, + Tcl_DString *pathDStringPtr, + Tcl_DString *ptsDStringPtr) +{ + + /* + * loop over path linked list + */ + + for (;pathPtr != NULL; pathPtr = pathPtr->next) { + NSVGpathSerialized pathSerialized; + int index; + + /* + * Save points in the ptr dstring. + * The first index and the count is saved. + */ + + pathSerialized.npts = pathPtr->npts; + if (pathPtr->npts == 0) { + pathSerialized.pts = -1; + } else { + + /* + * Attention: npts is a pair of floats + */ + + pathSerialized.pts = serializedHeaderPtr->ptsCount; + for (index = 0; index < (pathPtr->npts) * 2; index++) { + float ptCurrent; + ptCurrent = pathPtr->pts[index]; + Tcl_DStringAppend(ptsDStringPtr, + (const char *)&ptCurrent, sizeof(float)); + (serializedHeaderPtr->ptsCount)++; + } + } + + /* + * Copy the other items of the path structure + */ + + pathSerialized.closed = pathPtr->closed; + memcpy(pathSerialized.bounds, pathPtr->bounds, 4*sizeof(float)); + + /* + * Build the next item and add to DString + */ + + serializedHeaderPtr->pathCount++; + pathSerialized.next = (pathPtr->next == NULL) ? -1 : + serializedHeaderPtr->pathCount; + + Tcl_DStringAppend(pathDStringPtr, + (const char *)&pathSerialized, sizeof(NSVGpathSerialized)); + } +} +/* + *---------------------------------------------------------------------- + * * FileReadSVG -- * * This function is called by the photo image type to read SVG format @@ -207,32 +575,19 @@ FileReadSVG( Tcl_DString *driverInternalPtr) /* memory passed from FileMatchGIF */ { - TkSizeT length; - const char *data; - RastOpts ropts; - NSVGimage *nsvgImage = GetCachedSVG(interp, chan, formatObj, &ropts); + int result; (void)fileName; - if (nsvgImage == NULL) { - Tcl_Obj *dataObj = Tcl_NewObj(); - - if (Tcl_ReadChars(chan, dataObj, -1, 0) == TCL_IO_FAILURE) { - /* in case of an error reading the file */ - Tcl_DecrRefCount(dataObj); - Tcl_SetObjResult(interp, Tcl_NewStringObj("read error", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "READ_ERROR", NULL); - return TCL_ERROR; - } - data = TkGetStringFromObj(dataObj, &length); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, - &ropts); - Tcl_DecrRefCount(dataObj); - if (nsvgImage == NULL) { - return TCL_ERROR; - } + result = RasterizeSVG(interp, imageHandle, + Tcl_DStringValue(driverInternalPtr), + Tcl_DStringLength(driverInternalPtr), destX, destY, width, height, + srcX, srcY); + if (result == TCL_OK) { + result = SaveSVGBLOBToMetadata(interp, metadataOutObj, + driverInternalPtr); } - return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, - width, height, srcX, srcY, &ropts); + + return result; } /* @@ -267,21 +622,52 @@ StringMatchSVG( { TkSizeT length; const char *data; - RastOpts ropts; NSVGimage *nsvgImage; + serializedHeader *serializedHeaderPtr; + char * svgBlobPtr; - CleanCache(interp); data = TkGetStringFromObj(dataObj, &length); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); + + + /* + * Check for the special data to indicate that the metadata should be used. + * On special data, get the serialized header structure and check it. + */ + + svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, &length); + + if (NULL != svgBlobPtr) { + GetScaleFromParameters((serializedHeader *) svgBlobPtr, widthPtr, + heightPtr); + return 1; + } + + /* + * Check the passed data object and serialize it on success. + */ + + Tcl_DStringSetLength(driverInternalPtr, sizeof(serializedHeader)); + serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + serializedHeaderPtr); + if (nsvgImage != NULL) { - GetScaleFromParameters(nsvgImage, &ropts, widthPtr, heightPtr); + serializedHeaderPtr->width = nsvgImage->width; + serializedHeaderPtr->height = nsvgImage->height; + GetScaleFromParameters(serializedHeaderPtr, widthPtr, heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; } - if (!CacheSVG(interp, dataObj, formatObj, nsvgImage, &ropts)) { - nsvgDelete(nsvgImage); - } + + /* + * Serialize the NSVGImage structure + * As the DString is resized, serializedHeaderPtr may get invalid + */ + + SerializeNSVG(driverInternalPtr, nsvgImage); + + nsvgDelete(nsvgImage); return 1; } return 0; @@ -290,6 +676,65 @@ StringMatchSVG( /* *---------------------------------------------------------------------- * + * StringCheckMetadata -- + * + * Check the passed tring for a metadata serialized structure. + * + * Results: + * The svg blob pointer on success, NULL otherwise. + * + * Side effects: + * The file is saved in the internal cache for further use. + * + *---------------------------------------------------------------------- + */ + +static char * StringCheckMetadata( + Tcl_Obj *dataObj, /* the object containing the image data */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TkSizeT *lengthOutPtr) /* output data length on success */ +{ + TkSizeT length; + const char *data; + serializedHeader *serializedHeaderPtr; + char * svgBlobPtr; + Tcl_Obj *itemData; + + /* + * Check for the special data to indicate that the metadata should be used. + * On special data, get the serialized header structure and check it. + */ + + if (NULL == metadataInObj) { + return NULL; + } + + data = TkGetStringFromObj(dataObj, &length); + if (0 != strcmp(data, "") ) { + return NULL; + } + if (TCL_ERROR == Tcl_DictObjGet(NULL, metadataInObj, + Tcl_NewStringObj("SVGBLOB",-1), &itemData)) { + return NULL; + } + if (itemData == NULL) { + return NULL; + } + svgBlobPtr = Tcl_GetByteArrayFromObj(itemData, &length); + if (length < sizeof(serializedHeader) ) { + return NULL; + } + serializedHeaderPtr = (serializedHeader *)svgBlobPtr; + if (serializedHeaderPtr->structureVersion != STRUCTURE_VERSION ) { + return NULL; + } + *lengthOutPtr = length; + return svgBlobPtr; +} + +/* + *---------------------------------------------------------------------- + * * StringReadSVG -- * * This function is called by the photo image type to read SVG format @@ -310,30 +755,83 @@ StringReadSVG( Tcl_Interp *interp, /* interpreter for reporting errors in */ Tcl_Obj *dataObj, /* object containing the image */ Tcl_Obj *formatObj, /* format object, or NULL */ - Tcl_Obj *metadataIn, /* metadata input, may be NULL */ + Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ int srcX, int srcY, - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ Tcl_DString *driverInternalPtr) - /* memory passed from StringReadGIF */ + /* memory passed from StringReadSVG */ { + int result; + TkSizeT serializedDataLength; + char * data; TkSizeT length; - const char *data; - RastOpts ropts; - NSVGimage *nsvgImage = GetCachedSVG(interp, dataObj, formatObj, &ropts); + char * svgBlobPtr; - if (nsvgImage == NULL) { - data = TkGetStringFromObj(dataObj, &length); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, - &ropts); + data = TkGetStringFromObj(dataObj, &length); + + + /* + * Check for the special data to indicate that the metadata should be used. + * On special data, get the serialized header structure and use it. + */ + + svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, + &serializedDataLength); + + if (NULL != svgBlobPtr) { + return RasterizeSVG(interp, imageHandle, svgBlobPtr, + serializedDataLength, destX, destY, width, height, srcX, srcY); } - if (nsvgImage == NULL) { - return TCL_ERROR; + + /* + * Otherwise, the SVGBlob data is contained in the driver internal + * DString. Use that memory. + */ + + result = RasterizeSVG(interp, imageHandle, + Tcl_DStringValue(driverInternalPtr), + Tcl_DStringLength(driverInternalPtr), destX, destY, width, height, + srcX, srcY); + if (result != TCL_OK) { + return result; } - return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, - width, height, srcX, srcY, &ropts); + + return SaveSVGBLOBToMetadata(interp, metadataOutObj, + driverInternalPtr); +} + +/* + *---------------------------------------------------------------------- + * + * SaveSVGBLOBToMetadata -- + * + * Copy the driver internal DString into the metadata key SVGBLOB. + * + * Results: + * A TCL result value. + * + * Side effects: + * Change the output metadata. + * + *---------------------------------------------------------------------- + */ + +static int SaveSVGBLOBToMetadata( + Tcl_Interp *interp, /* interpreter for reporting errors in */ + Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ + Tcl_DString *driverInternalPtr) + /* memory passed from xxxReadSVG */ +{ + if (metadataOutObj == NULL) { + return TCL_OK; + } + return Tcl_DictObjPut(interp, metadataOutObj, + Tcl_NewStringObj("SVGBLOB",-1), + Tcl_NewByteArrayObj(Tcl_DStringValue(driverInternalPtr), + Tcl_DStringLength(driverInternalPtr))); } /* @@ -357,7 +855,7 @@ ParseSVGWithOptions( const char *input, TkSizeT length, Tcl_Obj *formatObj, - RastOpts *ropts) + serializedHeader *serializedHeaderPtr) { Tcl_Obj **objv = NULL; int objc = 0; @@ -390,9 +888,9 @@ ParseSVGWithOptions( * Process elements of format specification as a list. */ - ropts->scale = 1.0; - ropts->scaleToHeight = 0; - ropts->scaleToWidth = 0; + serializedHeaderPtr->scale = 1.0; + serializedHeaderPtr->scaleToHeight = 0; + serializedHeaderPtr->scaleToWidth = 0; if ((formatObj != NULL) && Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { goto error; @@ -460,11 +958,12 @@ ParseSVGWithOptions( } break; case OPT_SCALE: - if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->scale) == + if (Tcl_GetDoubleFromObj(interp, objv[0], + &(serializedHeaderPtr->scale)) == TCL_ERROR) { goto error; } - if (ropts->scale <= 0.0) { + if (serializedHeaderPtr->scale <= 0.0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scale value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", @@ -473,11 +972,11 @@ ParseSVGWithOptions( } break; case OPT_SCALE_TO_HEIGHT: - if (Tcl_GetIntFromObj(interp, objv[0], &ropts->scaleToHeight) == - TCL_ERROR) { + if (Tcl_GetIntFromObj(interp, objv[0], + &(serializedHeaderPtr->scaleToHeight)) == TCL_ERROR) { goto error; } - if (ropts->scaleToHeight <= 0) { + if (serializedHeaderPtr->scaleToHeight <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletoheight value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", @@ -486,11 +985,11 @@ ParseSVGWithOptions( } break; case OPT_SCALE_TO_WIDTH: - if (Tcl_GetIntFromObj(interp, objv[0], &ropts->scaleToWidth) == - TCL_ERROR) { + if (Tcl_GetIntFromObj(interp, objv[0], + &(serializedHeaderPtr->scaleToWidth)) == TCL_ERROR) { goto error; } - if (ropts->scaleToWidth <= 0) { + if (serializedHeaderPtr->scaleToWidth <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletowidth value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", @@ -520,6 +1019,65 @@ error: /* *---------------------------------------------------------------------- * + * GetScaleFromParameters -- + * + * Get the scale value from the already parsed parameters -scale, + * -scaletoheight and -scaletowidth. + * + * The image width and height is also returned. + * + * Results: + * The evaluated or configured scale value, or 0.0 on failure + * + * Side effects: + * heightPtr and widthPtr are set to height and width of the image. + * + *---------------------------------------------------------------------- + */ + +static double +GetScaleFromParameters( + serializedHeader *serializedHeaderPtr, + int *widthPtr, + int *heightPtr) +{ + double scale; + int width, height; + + if ((serializedHeaderPtr->width == 0.0) || (serializedHeaderPtr->height == 0.0)) { + width = height = 0; + scale = 1.0; + } else if (serializedHeaderPtr->scaleToHeight > 0) { + /* + * Fixed height + */ + height = serializedHeaderPtr->scaleToHeight; + scale = height / serializedHeaderPtr->height; + width = (int) ceil(serializedHeaderPtr->width * scale); + } else if (serializedHeaderPtr->scaleToWidth > 0) { + /* + * Fixed width + */ + width = serializedHeaderPtr->scaleToWidth; + scale = width / serializedHeaderPtr->width; + height = (int) ceil(serializedHeaderPtr->height * scale); + } else { + /* + * Scale factor + */ + scale = serializedHeaderPtr->scale; + width = (int) ceil(serializedHeaderPtr->width * scale); + height = (int) ceil(serializedHeaderPtr->height * scale); + } + + *heightPtr = height; + *widthPtr = width; + return scale; +} + +/* + *---------------------------------------------------------------------- + * * RasterizeSVG -- * * This function is called to rasterize the given nsvgImage and @@ -540,11 +1098,11 @@ static int RasterizeSVG( Tcl_Interp *interp, Tk_PhotoHandle imageHandle, - NSVGimage *nsvgImage, + char *svgBlobPtr, + TkSizeT serializedDataLength, int destX, int destY, int width, int height, - int srcX, int srcY, - RastOpts *ropts) + int srcX, int srcY) { int w, h, c; NSVGrasterizer *rast; @@ -553,15 +1111,16 @@ RasterizeSVG( double scale; (void)srcX; (void)srcY; - - scale = GetScaleFromParameters(nsvgImage, ropts, &w, &h); + + scale = GetScaleFromParameters((serializedHeader *) svgBlobPtr, + &w, &h); rast = nsvgCreateRasterizer(); if (rast == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot initialize rasterizer", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "RASTERIZER_ERROR", NULL); - goto cleanAST; + return TCL_ERROR; } imgData = (unsigned char *)attemptckalloc(w * h *4); if (imgData == NULL) { @@ -569,7 +1128,8 @@ RasterizeSVG( Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); goto cleanRAST; } - nsvgRasterize(rast, nsvgImage, 0, 0, + + nsvgRasterizeSerialized(rast, svgBlobPtr, 0, 0, (float) scale, imgData, w, h, w * 4); /* transfer the data to a photo block */ svgblock.pixelPtr = imgData; @@ -590,7 +1150,6 @@ RasterizeSVG( } ckfree(imgData); nsvgDeleteRasterizer(rast); - nsvgDelete(nsvgImage); return TCL_OK; cleanimg: @@ -599,244 +1158,369 @@ cleanimg: cleanRAST: nsvgDeleteRasterizer(rast); -cleanAST: - nsvgDelete(nsvgImage); return TCL_ERROR; } /* *---------------------------------------------------------------------- * - * GetScaleFromParameters -- + * Rasterize Serialized -- * - * Get the scale value from the already parsed parameters -scale, - * -scaletoheight and -scaletowidth. - * - * The image width and height is also returned. + * Fiunctions of svgnrast.h which requires modification due to the + * serialized data structure. * * Results: - * The evaluated or configured scale value, or 0.0 on failure + * * * Side effects: - * heightPtr and widthPtr are set to height and width of the image. * *---------------------------------------------------------------------- */ -static double -GetScaleFromParameters( - NSVGimage *nsvgImage, - RastOpts *ropts, - int *widthPtr, - int *heightPtr) +static void nsvg__flattenShapeSerialized(NSVGrasterizer* r, int pathIndex, + NSVGpathSerialized *pathSerializedPtr, float *ptsSerializedPtr, + float scale) { - double scale; - int width, height; + int i, j; + NSVGpathSerialized* path; - if ((nsvgImage->width == 0.0) || (nsvgImage->height == 0.0)) { - width = height = 0; - scale = 1.0; - } else if (ropts->scaleToHeight > 0) { - /* - * Fixed height - */ - height = ropts->scaleToHeight; - scale = height / nsvgImage->height; - width = (int) ceil(nsvgImage->width * scale); - } else if (ropts->scaleToWidth > 0) { - /* - * Fixed width - */ - width = ropts->scaleToWidth; - scale = width / nsvgImage->width; - height = (int) ceil(nsvgImage->height * scale); - } else { - /* - * Scale factor - */ - scale = ropts->scale; - width = (int) ceil(nsvgImage->width * scale); - height = (int) ceil(nsvgImage->height * scale); + for (; pathIndex != -1; pathIndex = pathSerializedPtr[pathIndex].next) { + path = &(pathSerializedPtr[pathIndex]); + r->npoints = 0; + // Flatten path + nsvg__addPathPoint(r, + ptsSerializedPtr[path->pts]*scale, + ptsSerializedPtr[path->pts+1]*scale, 0); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &ptsSerializedPtr[path->pts+i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); + } + // Close path + nsvg__addPathPoint(r, ptsSerializedPtr[path->pts]*scale, + ptsSerializedPtr[path->pts+1]*scale, 0); + // Build edges + for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) + nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); } - - *heightPtr = height; - *widthPtr = width; - return scale; } -/* - *---------------------------------------------------------------------- - * - * GetCachePtr -- - * - * This function is called to get the per interpreter used - * svg image cache. - * - * Results: - * Return a pointer to the used cache. - * - * Side effects: - * Initialize the cache on the first call. - * - *---------------------------------------------------------------------- - */ +static void nsvg__initPaintSerialized(NSVGcachedPaint* cache, + NSVGpaintSerialized* paint, float opacity, + NSVGgradientSerialized *gradientSerializedPtr, + NSVGgradientStop *gradientStopPtr) +{ + int i, j; + NSVGgradientSerialized* grad; -static NSVGcache * -GetCachePtr( - Tcl_Interp *interp -) { - NSVGcache *cachePtr = (NSVGcache *)Tcl_GetAssocData(interp, "tksvgnano", NULL); - if (cachePtr == NULL) { - cachePtr = (NSVGcache *)ckalloc(sizeof(NSVGcache)); - cachePtr->dataOrChan = NULL; - Tcl_DStringInit(&cachePtr->formatString); - cachePtr->nsvgImage = NULL; - Tcl_SetAssocData(interp, "tksvgnano", FreeCache, cachePtr); - } - return cachePtr; -} + cache->type = paint->type; -/* - *---------------------------------------------------------------------- - * - * CacheSVG -- - * - * Add the given svg image informations to the cache for further usage. - * - * Results: - * Return 1 on success, and 0 otherwise. - * - * Side effects: - * - *---------------------------------------------------------------------- - */ + if (paint->type == NSVG_PAINT_COLOR) { + cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); + return; + } -static int -CacheSVG( - Tcl_Interp *interp, - ClientData dataOrChan, - Tcl_Obj *formatObj, - NSVGimage *nsvgImage, - RastOpts *ropts) -{ - TkSizeT length; - const char *data; - NSVGcache *cachePtr = GetCachePtr(interp); + grad = &(gradientSerializedPtr[paint->gradient]); + + cache->spread = grad->spread; + memcpy(cache->xform, grad->xform, sizeof(float)*6); - if (cachePtr != NULL) { - cachePtr->dataOrChan = dataOrChan; - if (formatObj != NULL) { - data = TkGetStringFromObj(formatObj, &length); - Tcl_DStringAppend(&cachePtr->formatString, data, length); + if (grad->nstops == 0) { + for (i = 0; i < 256; i++) + cache->colors[i] = 0; + } if (grad->nstops == 1) { + for (i = 0; i < 256; i++) + cache->colors[i] = nsvg__applyOpacity( + gradientStopPtr[grad->stops+i].color, opacity); + } else { + unsigned int ca, cb = 0; + float ua, ub, du, u; + int ia, ib, count; + + ca = nsvg__applyOpacity(gradientStopPtr[grad->stops].color, opacity); + ua = nsvg__clampf(gradientStopPtr[grad->stops].offset, 0, 1); + ub = nsvg__clampf(gradientStopPtr[grad->stops+grad->nstops-1].offset, + ua, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + for (i = 0; i < ia; i++) { + cache->colors[i] = ca; } - cachePtr->nsvgImage = nsvgImage; - cachePtr->ropts = *ropts; - return 1; + + for (i = 0; i < grad->nstops-1; i++) { + ca = nsvg__applyOpacity(gradientStopPtr[grad->stops+i].color, + opacity); + cb = nsvg__applyOpacity(gradientStopPtr[grad->stops+i+1].color, + opacity); + ua = nsvg__clampf(gradientStopPtr[grad->stops+i].offset, 0, 1); + ub = nsvg__clampf(gradientStopPtr[grad->stops+i+1].offset, 0, 1); + ia = (int)(ua * 255.0f); + ib = (int)(ub * 255.0f); + count = ib - ia; + if (count <= 0) continue; + u = 0; + du = 1.0f / (float)count; + for (j = 0; j < count; j++) { + cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); + u += du; + } + } + + for (i = ib; i < 256; i++) + cache->colors[i] = cb; } - return 0; -} -/* - *---------------------------------------------------------------------- - * - * GetCachedSVG -- - * - * Try to get the NSVGimage from the internal cache. - * - * Results: - * Return the found NSVGimage on success, and NULL otherwise. - * - * Side effects: - * Calls the CleanCache() function. - * - *---------------------------------------------------------------------- - */ +} -static NSVGimage * -GetCachedSVG( - Tcl_Interp *interp, - ClientData dataOrChan, - Tcl_Obj *formatObj, - RastOpts *ropts) +static void nsvg__flattenShapeStrokeSerialized(NSVGrasterizer* r, + NSVGshapeSerialized* shape, NSVGpathSerialized *pathSerializedPtr, + float *ptsSerializedPtr, float scale) { - TkSizeT length; - const char *data; - NSVGcache *cachePtr = GetCachePtr(interp); - NSVGimage *nsvgImage = NULL; - - if ((cachePtr != NULL) && (cachePtr->nsvgImage != NULL) && - (cachePtr->dataOrChan == dataOrChan)) { - if (formatObj != NULL) { - data = TkGetStringFromObj(formatObj, &length); - if (strcmp(data, Tcl_DStringValue(&cachePtr->formatString)) == 0) { - nsvgImage = cachePtr->nsvgImage; - *ropts = cachePtr->ropts; - cachePtr->nsvgImage = NULL; + int i, j, closed, pathIndex; + NSVGpathSerialized* path; + NSVGpoint* p0, *p1; + float miterLimit = shape->miterLimit; + int lineJoin = shape->strokeLineJoin; + int lineCap = shape->strokeLineCap; + float lineWidth = shape->strokeWidth * scale; + + for (pathIndex = shape->paths; pathIndex != -1; + pathIndex = pathSerializedPtr[pathIndex].next) { + path = &(pathSerializedPtr[pathIndex]); + // Flatten path + r->npoints = 0; + nsvg__addPathPoint(r, ptsSerializedPtr[path->pts]*scale, + ptsSerializedPtr[path->pts+1]*scale, NSVG_PT_CORNER); + for (i = 0; i < path->npts-1; i += 3) { + float* p = &ptsSerializedPtr[path->pts+i*2]; + nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale, + p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, + NSVG_PT_CORNER); + } + if (r->npoints < 2) + continue; + + closed = path->closed; + + // If the first and last points are the same, remove the last, mark as closed path. + p0 = &r->points[r->npoints-1]; + p1 = &r->points[0]; + if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { + r->npoints--; + p0 = &r->points[r->npoints-1]; + closed = 1; + } + + if (shape->strokeDashCount > 0) { + int idash = 0, dashState = 1; + float totalDist = 0, dashLen, allDashLen, dashOffset; + NSVGpoint cur; + + if (closed) + nsvg__appendPathPoint(r, r->points[0]); + + // Duplicate points -> points2. + nsvg__duplicatePoints(r); + r->npoints = 0; + cur = r->points2[0]; + nsvg__appendPathPoint(r, cur); + + // Figure out dash offset. + allDashLen = 0; + for (j = 0; j < shape->strokeDashCount; j++) + allDashLen += shape->strokeDashArray[j]; + if (shape->strokeDashCount & 1) + allDashLen *= 2.0f; + // Find location inside pattern + dashOffset = fmodf(shape->strokeDashOffset, allDashLen); + if (dashOffset < 0.0f) + dashOffset += allDashLen; + + while (dashOffset > shape->strokeDashArray[idash]) { + dashOffset -= shape->strokeDashArray[idash]; + idash = (idash + 1) % shape->strokeDashCount; } - } else if (Tcl_DStringLength(&cachePtr->formatString) == 0) { - nsvgImage = cachePtr->nsvgImage; - *ropts = cachePtr->ropts; - cachePtr->nsvgImage = NULL; + dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; + + for (j = 1; j < r->npoints2; ) { + float dx = r->points2[j].x - cur.x; + float dy = r->points2[j].y - cur.y; + float dist = sqrtf(dx*dx + dy*dy); + + if ((totalDist + dist) > dashLen) { + // Calculate intermediate point + float d = (dashLen - totalDist) / dist; + float x = cur.x + dx * d; + float y = cur.y + dy * d; + nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); + + // Stroke + if (r->npoints > 1 && dashState) { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, 0, + lineJoin, lineCap, lineWidth); + } + // Advance dash pattern + dashState = !dashState; + idash = (idash+1) % shape->strokeDashCount; + dashLen = shape->strokeDashArray[idash] * scale; + // Restart + cur.x = x; + cur.y = y; + cur.flags = NSVG_PT_CORNER; + totalDist = 0.0f; + r->npoints = 0; + nsvg__appendPathPoint(r, cur); + } else { + totalDist += dist; + cur = r->points2[j]; + nsvg__appendPathPoint(r, cur); + j++; + } + } + // Stroke any leftover path + if (r->npoints > 1 && dashState) + nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, + lineCap, lineWidth); + } else { + nsvg__prepareStroke(r, miterLimit, lineJoin); + nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, + lineCap, lineWidth); } } - CleanCache(interp); - return nsvgImage; } + /* *---------------------------------------------------------------------- * - * CleanCache -- + * RasterizeSVGSerialized -- * - * Reset the cache and delete the saved image in it. + * This function is called to rasterize the given nsvgImage and + * fill the imageHandle with data. * * Results: + * A standard TCL completion code. If TCL_ERROR is returned then an error + * message is left in the interp's result. + * * * Side effects: + * On error the given nsvgImage will be deleted. * *---------------------------------------------------------------------- */ -static void -CleanCache(Tcl_Interp *interp) +static void nsvgRasterizeSerialized(NSVGrasterizer* r, + char *svgBlobPtr, float tx, float ty, float scale, + unsigned char* dst, int w, int h, int stride) { - NSVGcache *cachePtr = GetCachePtr(interp); - - if (cachePtr != NULL) { - cachePtr->dataOrChan = NULL; - Tcl_DStringSetLength(&cachePtr->formatString, 0); - if (cachePtr->nsvgImage != NULL) { - nsvgDelete(cachePtr->nsvgImage); - cachePtr->nsvgImage = NULL; - } + NSVGshapeSerialized *shape = NULL; + NSVGedge *e = NULL; + NSVGcachedPaint cache; + int i, shapeIndex; + serializedHeader * serializedHeaderPtr; + NSVGshapeSerialized *shapeSerializedPtr; + NSVGpathSerialized *pathSerializedPtr; + float *ptsSerializedPtr; + NSVGgradientSerialized *gradientSerializedPtr; + NSVGgradientStop *gradientStopPtr; + + /* + * Prepare the array pointers of the data array placed after serializedHeader + */ + serializedHeaderPtr = (serializedHeader *) svgBlobPtr; + svgBlobPtr += sizeof (serializedHeader); + shapeSerializedPtr = (NSVGshapeSerialized *) svgBlobPtr; + svgBlobPtr += serializedHeaderPtr->shapeCount * sizeof(NSVGshapeSerialized); + pathSerializedPtr = (NSVGpathSerialized *) svgBlobPtr; + svgBlobPtr += serializedHeaderPtr->pathCount * sizeof(NSVGpathSerialized); + ptsSerializedPtr = (float *) svgBlobPtr; + svgBlobPtr += serializedHeaderPtr->ptsCount * sizeof(float); + gradientSerializedPtr = (NSVGgradientSerialized *) svgBlobPtr; + svgBlobPtr += serializedHeaderPtr->gradientCount * + sizeof(NSVGgradientSerialized); + gradientStopPtr = (NSVGgradientStop *) svgBlobPtr; + + r->bitmap = dst; + r->width = w; + r->height = h; + r->stride = stride; + + if (w > r->cscanline) { + r->cscanline = w; + r->scanline = (unsigned char*)NANOSVG_realloc(r->scanline, w); + if (r->scanline == NULL) return; } -} -/* - *---------------------------------------------------------------------- - * - * FreeCache -- - * - * This function is called to clean up the internal cache data. - * - * Results: - * - * Side effects: - * Existing image data in the cache and the cache will be deleted. - * - *---------------------------------------------------------------------- - */ + for (i = 0; i < h; i++) + memset(&dst[i*stride], 0, w*4); -static void -FreeCache(ClientData clientData, Tcl_Interp *interp) -{ - NSVGcache *cachePtr = (NSVGcache *)clientData; - (void)interp; + for (shapeIndex = 0 ; shapeIndex < serializedHeaderPtr->shapeCount; + shapeIndex++) { + shape = &(shapeSerializedPtr[shapeIndex]); + if (!(shape->flags & NSVG_FLAGS_VISIBLE)) + continue; + + if (shape->fill.type != NSVG_PAINT_NONE) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeSerialized(r, shape->paths, pathSerializedPtr, ptsSerializedPtr, + scale); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - Tcl_DStringFree(&cachePtr->formatString); - if (cachePtr->nsvgImage != NULL) { - nsvgDelete(cachePtr->nsvgImage); + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaintSerialized(&cache, &shape->fill, shape->opacity, + gradientSerializedPtr, gradientStopPtr); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + } + if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { + nsvg__resetPool(r); + r->freelist = NULL; + r->nedges = 0; + + nsvg__flattenShapeStrokeSerialized(r, shape, pathSerializedPtr, + ptsSerializedPtr, scale); + + // dumpEdges(r, "edge.svg"); + + // Scale and translate edges + for (i = 0; i < r->nedges; i++) { + e = &r->edges[i]; + e->x0 = tx + e->x0; + e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; + e->x1 = tx + e->x1; + e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; + } + + // Rasterize edges + qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); + + // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule + nsvg__initPaintSerialized(&cache, &shape->stroke, shape->opacity, + gradientSerializedPtr, gradientStopPtr); + + nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); + } } - ckfree(cachePtr); + + nsvg__unpremultiplyAlpha(dst, w, h, stride); + + r->bitmap = NULL; + r->width = 0; + r->height = 0; + r->stride = 0; } diff --git a/tests/imgSVGnano.test b/tests/imgSVGnano.test index ff7046a..fce2f9d 100644 --- a/tests/imgSVGnano.test +++ b/tests/imgSVGnano.test @@ -207,6 +207,30 @@ test imgSVGnano-4.2 {error on file not accessible on reread due to configure} -s tcltest::removeFile tmpplus.svg } -returnCodes error -match glob -result {couldn't open "*/tmpplus.svg": no such file or directory} +test imgSVGnano-5.0 {svgblob metadata creation} -setup { + catch {rename foo ""} + tcltest::makeFile $data(plus) tmpplus.svg +} -body { + image create photo foo -file [file join [tcltest::configure -tmpdir] tmpplus.svg] + dict keys [foo cget -metadata] +} -cleanup { + rename foo "" + tcltest::removeFile tmpplus.svg +} -result {SVGBLOB} + +test imgSVGnano-5.1 {scale with svgblob metadata} -setup { + catch {rename foo ""} + tcltest::makeFile $data(plus) tmpplus.svg +} -body { + image create photo foo -file [file join [tcltest::configure -tmpdir] tmpplus.svg] + foo configure -file "" -data "" + foo configure -format "svg -scale 2" + lappend res [image width foo] [image height foo] +} -cleanup { + rename foo "" + tcltest::removeFile tmpplus.svg +} -result {200 200} + };# end of namespace svgnano namespace delete svgnano -- cgit v0.12 From 3f0bcf5127f3f554341d43576f656162f4cf5101 Mon Sep 17 00:00:00 2001 From: oehhar Date: Mon, 22 Jun 2020 13:21:48 +0000 Subject: TIP 529 image metadata: svg metadata optimization for -dpi parameter --- generic/tkImgSVGnano.c | 382 ++++++++++++++++++++++++++++++++----------------- tests/imgSVGnano.test | 98 ++++++++++++- 2 files changed, 347 insertions(+), 133 deletions(-) diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c index e15de54..f82a285 100644 --- a/generic/tkImgSVGnano.c +++ b/generic/tkImgSVGnano.c @@ -41,9 +41,7 @@ typedef struct { unsigned int structureVersion; - double scale; - int scaleToHeight; - int scaleToWidth; + float dpi; float width; // Width of the image. float height; // Height of the image. int shapeCount; @@ -54,6 +52,18 @@ typedef struct { } serializedHeader; /* + * Result of options parsing and first block in driver internal DString + */ + +typedef struct { + double scale; + int scaleToHeight; + int scaleToWidth; + float dpi; + int svgBlobFollows; +} optionsStruct; + +/* * Serialized structures from NSCG * All pointers are replaced by array indexes */ @@ -144,19 +154,19 @@ static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, int destX, int destY, int width, int height, int srcX, int srcY, Tcl_Obj *metadataOut, Tcl_DString *driverInternal); -static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, - const char *input, TkSizeT length, Tcl_Obj *format, - serializedHeader *serializedHeaderPtr); +static int ParseOptions(Tcl_Interp *interp, Tcl_Obj *formatObj, + optionsStruct *optionsPtr); +static NSVGimage * ParseSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, float dpi); static int RasterizeSVG(Tcl_Interp *interp, Tk_PhotoHandle imageHandle, - char *svgBlob, - TkSizeT serializeDataLength, + char *svgBlob, optionsStruct * optionsPtr, int destX, int destY, int width, int height, int srcX, int srcY); static double GetScaleFromParameters( serializedHeader *serializedHeaderPtr, - int *widthPtr, int *heightPtr); -static void SerializeNSVG(Tcl_DString *driverInternalPtr, + optionsStruct * optionsPtr, int *widthPtr, + int *heightPtr); +static void SerializeNSVGImage(Tcl_DString *driverInternalPtr, NSVGimage *nsvgImage); static void SerializePath(struct NSVGpath *pathPtr, serializedHeader *serializedHeaderPtr, @@ -166,8 +176,8 @@ static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, serializedHeader *serializedHeaderPtr, Tcl_DString *gradientDStringPtr, Tcl_DString *gradientStopDStringPtr); -static char * StringCheckMetadata(Tcl_Obj *dataObj, - Tcl_Obj *metadataInObj, int *LengthPtr); +static char * StringCheckMetadata(Tcl_Obj *dataObj, Tcl_Obj *metadataInObj, + float dpi, int *LengthPtr); static int SaveSVGBLOBToMetadata(Tcl_Interp *interp, Tcl_Obj *metadataOutObj, Tcl_DString *driverInternalPtr); static void nsvgRasterizeSerialized(NSVGrasterizer* r, char *svgBlobPtr, float tx, @@ -222,28 +232,58 @@ FileMatchSVG( Tcl_DString *driverInternalPtr) /* memory passed to FileReadGIF */ { - TkSizeT length; - Tcl_Obj *dataObj = Tcl_NewObj(); - const char *data; + Tcl_Obj *dataObj; NSVGimage *nsvgImage; (void)fileName; serializedHeader *serializedHeaderPtr; + optionsStruct options; + + /* + * Parse the options. Unfortunately, any error can not be returned. + */ + + if (TCL_OK != ParseOptions(interp, formatObj, &options) ) { + return 0; + } + /* + * Read the file data into a TCL object + */ + + dataObj = Tcl_NewObj(); if (Tcl_ReadChars(chan, dataObj, -1, 0) == TCL_IO_FAILURE) { /* in case of an error reading the file */ Tcl_DecrRefCount(dataObj); return 0; } - Tcl_DStringSetLength(driverInternalPtr, sizeof(serializedHeader)); - serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); - data = TkGetStringFromObj(dataObj, &length); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, - serializedHeaderPtr); + + /* + * Parse the SVG data + */ + + nsvgImage = ParseSVG(interp, dataObj, options.dpi); Tcl_DecrRefCount(dataObj); if (nsvgImage != NULL) { + + /* + * On successful parse, save the data in the header structure + */ + + Tcl_DStringSetLength(driverInternalPtr, + sizeof(optionsStruct)+sizeof(serializedHeader)); + + options.svgBlobFollows = 1; + memcpy( Tcl_DStringValue(driverInternalPtr), &options, + sizeof(optionsStruct)); + + serializedHeaderPtr = (serializedHeader *) + (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); + serializedHeaderPtr->width = nsvgImage->width; serializedHeaderPtr->height = nsvgImage->height; - GetScaleFromParameters(serializedHeaderPtr, widthPtr, heightPtr); + serializedHeaderPtr->dpi = options.dpi; + GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, + heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; @@ -253,17 +293,19 @@ FileMatchSVG( * Serialize the NSVGImage structure * As the DString is resized, serializedHeaderPtr may get invalid */ - SerializeNSVG(driverInternalPtr, nsvgImage); + + SerializeNSVGImage(driverInternalPtr, nsvgImage); nsvgDelete(nsvgImage); return 1; } return 0; + } /* *---------------------------------------------------------------------- * - * SerializeNSVG -- + * SerializeNSVGImage -- * * This function saves the NSVGimage structure into the DString. * @@ -277,13 +319,15 @@ FileMatchSVG( *---------------------------------------------------------------------- */ -static void SerializeNSVG(Tcl_DString *driverInternalPtr, NSVGimage *nsvgImage) { +static void SerializeNSVGImage(Tcl_DString *driverInternalPtr, + NSVGimage *nsvgImage) { serializedHeader *serializedHeaderPtr; Tcl_DString shapeDString, pathDString, ptsDString, gradientDString, gradientStopDString; NSVGshape *shapePtr; - serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); + serializedHeaderPtr = (serializedHeader *) + (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); Tcl_DStringInit(&shapeDString); Tcl_DStringInit(&pathDString); Tcl_DStringInit(&ptsDString); @@ -453,6 +497,14 @@ static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, (serializedHeaderPtr->gradientStopCount) += gradientPtr->nstops; } + (serializedHeaderPtr->gradientStopCount) += gradientPtr->nstops; + + paintSerialized.gradient = serializedHeaderPtr->gradientCount; + Tcl_DStringAppend(gradientDStringPtr, + (const char *) & gradientSerialized, + sizeof(NSVGgradientSerialized)); + (serializedHeaderPtr->gradientCount) ++; + } else { /* @@ -576,12 +628,25 @@ FileReadSVG( /* memory passed from FileMatchGIF */ { int result; + optionsStruct * optionsPtr; + char * svgBlob; (void)fileName; + + + optionsPtr = (optionsStruct *) Tcl_DStringValue(driverInternalPtr); + svgBlob = Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct); + + /* + * Raster the parsed SVG from the SVGBLOB in the driver internal DString + */ + + result = RasterizeSVG(interp, imageHandle, svgBlob, optionsPtr, destX, + destY, width, height, srcX, srcY); + + /* + * On success, output the SVGBLOB as metadata + */ - result = RasterizeSVG(interp, imageHandle, - Tcl_DStringValue(driverInternalPtr), - Tcl_DStringLength(driverInternalPtr), destX, destY, width, height, - srcX, srcY); if (result == TCL_OK) { result = SaveSVGBLOBToMetadata(interp, metadataOutObj, driverInternalPtr); @@ -621,23 +686,55 @@ StringMatchSVG( /* memory to pass to StringReadGIF */ { TkSizeT length; - const char *data; NSVGimage *nsvgImage; serializedHeader *serializedHeaderPtr; char * svgBlobPtr; + optionsStruct options; - data = TkGetStringFromObj(dataObj, &length); - + /* + * Parse the options. Unfortunately, any error can not be returned. + */ + + if (TCL_OK != ParseOptions(interp, formatObj, &options) ) { + return 0; + } /* * Check for the special data to indicate that the metadata should be used. * On special data, get the serialized header structure and check it. */ - svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, &length); + svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, options.dpi, + &length); if (NULL != svgBlobPtr) { - GetScaleFromParameters((serializedHeader *) svgBlobPtr, widthPtr, + serializedHeaderPtr = (serializedHeader *) svgBlobPtr; + } else { + Tcl_DStringSetLength(driverInternalPtr, + sizeof(optionsStruct)+sizeof(serializedHeader)); + + options.svgBlobFollows = 1; + memcpy( Tcl_DStringValue(driverInternalPtr), &options, + sizeof(optionsStruct)); + + serializedHeaderPtr = (serializedHeader *) + (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); + } + + /* + * Use the metadata svg blob if present to return width and height + */ + + if (NULL != svgBlobPtr) { + /* + * Save the options struct in the driver internal DString + */ + options.svgBlobFollows = 0; + Tcl_DStringSetLength(driverInternalPtr, sizeof(optionsStruct)); + memcpy(Tcl_DStringValue(driverInternalPtr), &options, + sizeof(optionsStruct)); + + GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, heightPtr); return 1; } @@ -646,15 +743,14 @@ StringMatchSVG( * Check the passed data object and serialize it on success. */ - Tcl_DStringSetLength(driverInternalPtr, sizeof(serializedHeader)); - serializedHeaderPtr = (serializedHeader *)Tcl_DStringValue(driverInternalPtr); - nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, - serializedHeaderPtr); + nsvgImage = ParseSVG(interp, dataObj, options.dpi); if (nsvgImage != NULL) { serializedHeaderPtr->width = nsvgImage->width; serializedHeaderPtr->height = nsvgImage->height; - GetScaleFromParameters(serializedHeaderPtr, widthPtr, heightPtr); + serializedHeaderPtr->dpi = options.dpi; + GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, + heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; @@ -665,7 +761,7 @@ StringMatchSVG( * As the DString is resized, serializedHeaderPtr may get invalid */ - SerializeNSVG(driverInternalPtr, nsvgImage); + SerializeNSVGImage(driverInternalPtr, nsvgImage); nsvgDelete(nsvgImage); return 1; @@ -692,6 +788,7 @@ StringMatchSVG( static char * StringCheckMetadata( Tcl_Obj *dataObj, /* the object containing the image data */ Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + float dpi, /* options dpi must match svgblob */ TkSizeT *lengthOutPtr) /* output data length on success */ { TkSizeT length; @@ -725,7 +822,9 @@ static char * StringCheckMetadata( return NULL; } serializedHeaderPtr = (serializedHeader *)svgBlobPtr; - if (serializedHeaderPtr->structureVersion != STRUCTURE_VERSION ) { + if (serializedHeaderPtr->structureVersion != STRUCTURE_VERSION + || serializedHeaderPtr->dpi != dpi + ) { return NULL; } *lengthOutPtr = length; @@ -765,42 +864,40 @@ StringReadSVG( /* memory passed from StringReadSVG */ { int result; - TkSizeT serializedDataLength; - char * data; TkSizeT length; char * svgBlobPtr; - - data = TkGetStringFromObj(dataObj, &length); - - - /* - * Check for the special data to indicate that the metadata should be used. - * On special data, get the serialized header structure and use it. - */ + optionsStruct * optionsPtr; + Tcl_Obj *itemData; - svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, - &serializedDataLength); + optionsPtr = (optionsStruct *) Tcl_DStringValue(driverInternalPtr); + if (optionsPtr->svgBlobFollows) { + svgBlobPtr = Tcl_DStringValue(driverInternalPtr)+ sizeof(optionsStruct); + } else { + if (NULL == metadataInObj + || TCL_ERROR == Tcl_DictObjGet(NULL, metadataInObj, + Tcl_NewStringObj("SVGBLOB",-1), &itemData) + || itemData == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "internal error: -metadata missing", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", + NULL); + return TCL_ERROR; + } - if (NULL != svgBlobPtr) { - return RasterizeSVG(interp, imageHandle, svgBlobPtr, - serializedDataLength, destX, destY, width, height, srcX, srcY); + svgBlobPtr = Tcl_GetByteArrayFromObj(itemData, &length); } - /* - * Otherwise, the SVGBlob data is contained in the driver internal - * DString. Use that memory. - */ - result = RasterizeSVG(interp, imageHandle, - Tcl_DStringValue(driverInternalPtr), - Tcl_DStringLength(driverInternalPtr), destX, destY, width, height, - srcX, srcY); + svgBlobPtr, optionsPtr, destX, destY, width, height, srcX, srcY); if (result != TCL_OK) { return result; } - return SaveSVGBLOBToMetadata(interp, metadataOutObj, - driverInternalPtr); + if (!optionsPtr->svgBlobFollows) { + return TCL_OK; + } + + return SaveSVGBLOBToMetadata(interp, metadataOutObj, driverInternalPtr); } /* @@ -830,38 +927,36 @@ static int SaveSVGBLOBToMetadata( } return Tcl_DictObjPut(interp, metadataOutObj, Tcl_NewStringObj("SVGBLOB",-1), - Tcl_NewByteArrayObj(Tcl_DStringValue(driverInternalPtr), - Tcl_DStringLength(driverInternalPtr))); + Tcl_NewByteArrayObj( + Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct), + Tcl_DStringLength(driverInternalPtr) - sizeof(optionsStruct))); } /* *---------------------------------------------------------------------- * - * ParseSVGWithOptions -- + * ParseOptions -- * - * This function is called to parse the given input string as SVG. + * Parse the options given in the -format parameter. * * Results: - * Return a newly create NSVGimage on success, and NULL otherwise. + * A normal tcl result * * Side effects: * *---------------------------------------------------------------------- */ -static NSVGimage * -ParseSVGWithOptions( +static int +ParseOptions( Tcl_Interp *interp, - const char *input, - TkSizeT length, Tcl_Obj *formatObj, - serializedHeader *serializedHeaderPtr) + optionsStruct * optionsPtr) { Tcl_Obj **objv = NULL; int objc = 0; - double dpi = 96.0; + double dpi; char *inputCopy = NULL; - NSVGimage *nsvgImage; int parameterScaleSeen = 0; static const char *const fmtOptions[] = { "-dpi", "-scale", "-scaletoheight", "-scaletowidth", NULL @@ -871,29 +966,16 @@ ParseSVGWithOptions( }; /* - * The parser destroys the original input string, - * therefore first duplicate. - */ - - inputCopy = (char *)attemptckalloc(length+1); - if (inputCopy == NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); - goto error; - } - memcpy(inputCopy, input, length); - inputCopy[length] = '\0'; - - /* * Process elements of format specification as a list. */ - serializedHeaderPtr->scale = 1.0; - serializedHeaderPtr->scaleToHeight = 0; - serializedHeaderPtr->scaleToWidth = 0; + optionsPtr->dpi = 96.0; + optionsPtr->scale = 1.0; + optionsPtr->scaleToHeight = 0; + optionsPtr->scaleToWidth = 0; if ((formatObj != NULL) && Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { - goto error; + return TCL_ERROR;; } for (; objc > 0 ; objc--, objv++) { int optIndex; @@ -908,14 +990,12 @@ ParseSVGWithOptions( if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { - goto error; + return TCL_ERROR;; } if (objc < 2) { - ckfree(inputCopy); - inputCopy = NULL; Tcl_WrongNumArgs(interp, 1, objv, "value"); - goto error; + return TCL_ERROR;; } objc--; @@ -933,7 +1013,7 @@ ParseSVGWithOptions( "only one of -scale, -scaletoheight, -scaletowidth may be given", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - goto error; + return TCL_ERROR;; } parameterScaleSeen = 1; break; @@ -947,73 +1027,114 @@ ParseSVGWithOptions( switch ((enum fmtOptions) optIndex) { case OPT_DPI: if (Tcl_GetDoubleFromObj(interp, objv[0], &dpi) == TCL_ERROR) { - goto error; + return TCL_ERROR;; } if (dpi < 0.0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-dpi value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_DPI", NULL); - goto error; + return TCL_ERROR;; } + optionsPtr->dpi = (float)dpi; break; case OPT_SCALE: if (Tcl_GetDoubleFromObj(interp, objv[0], - &(serializedHeaderPtr->scale)) == + &(optionsPtr->scale)) == TCL_ERROR) { - goto error; + return TCL_ERROR;; } - if (serializedHeaderPtr->scale <= 0.0) { + if (optionsPtr->scale <= 0.0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scale value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - goto error; + return TCL_ERROR;; } break; case OPT_SCALE_TO_HEIGHT: if (Tcl_GetIntFromObj(interp, objv[0], - &(serializedHeaderPtr->scaleToHeight)) == TCL_ERROR) { - goto error; + &(optionsPtr->scaleToHeight)) == TCL_ERROR) { + return TCL_ERROR;; } - if (serializedHeaderPtr->scaleToHeight <= 0) { + if (optionsPtr->scaleToHeight <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletoheight value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - goto error; + return TCL_ERROR;; } break; case OPT_SCALE_TO_WIDTH: if (Tcl_GetIntFromObj(interp, objv[0], - &(serializedHeaderPtr->scaleToWidth)) == TCL_ERROR) { - goto error; + &(optionsPtr->scaleToWidth)) == TCL_ERROR) { + return TCL_ERROR;; } - if (serializedHeaderPtr->scaleToWidth <= 0) { + if (optionsPtr->scaleToWidth <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletowidth value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - goto error; + return TCL_ERROR;; } break; } } - nsvgImage = nsvgParse(inputCopy, "px", (float) dpi); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * ParseSVG -- + * + * This function is called to parse the given input string as SVG. + * + * Results: + * Return a newly create NSVGimage on success, and NULL otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ + +static NSVGimage * +ParseSVG( + Tcl_Interp *interp, + Tcl_Obj *dataObj, + float dpi) +{ + const char *input; + char *inputCopy = NULL; + NSVGimage *nsvgImage; + TkSizeT length; + + /* + * The parser destroys the original input string, + * therefore first duplicate. + */ + + input = TkGetStringFromObj(dataObj, &length); + inputCopy = (char *)attemptckalloc(length+1); + if (inputCopy == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + return NULL; + } + memcpy(inputCopy, input, length); + inputCopy[length] = '\0'; + + nsvgImage = nsvgParse(inputCopy, "px", dpi); if (nsvgImage == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot parse SVG image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "PARSE_ERROR", NULL); - goto error; + ckfree(inputCopy); + return NULL; } ckfree(inputCopy); return nsvgImage; - -error: - if (inputCopy != NULL) { - ckfree(inputCopy); - } - return NULL; } /* @@ -1038,6 +1159,7 @@ error: static double GetScaleFromParameters( serializedHeader *serializedHeaderPtr, + optionsStruct * optionsPtr, int *widthPtr, int *heightPtr) { @@ -1047,25 +1169,25 @@ GetScaleFromParameters( if ((serializedHeaderPtr->width == 0.0) || (serializedHeaderPtr->height == 0.0)) { width = height = 0; scale = 1.0; - } else if (serializedHeaderPtr->scaleToHeight > 0) { + } else if (optionsPtr->scaleToHeight > 0) { /* * Fixed height */ - height = serializedHeaderPtr->scaleToHeight; + height = optionsPtr->scaleToHeight; scale = height / serializedHeaderPtr->height; width = (int) ceil(serializedHeaderPtr->width * scale); - } else if (serializedHeaderPtr->scaleToWidth > 0) { + } else if (optionsPtr->scaleToWidth > 0) { /* * Fixed width */ - width = serializedHeaderPtr->scaleToWidth; + width = optionsPtr->scaleToWidth; scale = width / serializedHeaderPtr->width; height = (int) ceil(serializedHeaderPtr->height * scale); } else { /* * Scale factor */ - scale = serializedHeaderPtr->scale; + scale = optionsPtr->scale; width = (int) ceil(serializedHeaderPtr->width * scale); height = (int) ceil(serializedHeaderPtr->height * scale); } @@ -1099,7 +1221,7 @@ RasterizeSVG( Tcl_Interp *interp, Tk_PhotoHandle imageHandle, char *svgBlobPtr, - TkSizeT serializedDataLength, + optionsStruct * optionsPtr, int destX, int destY, int width, int height, int srcX, int srcY) @@ -1112,7 +1234,7 @@ RasterizeSVG( (void)srcX; (void)srcY; - scale = GetScaleFromParameters((serializedHeader *) svgBlobPtr, + scale = GetScaleFromParameters((serializedHeader *) svgBlobPtr, optionsPtr, &w, &h); rast = nsvgCreateRasterizer(); diff --git a/tests/imgSVGnano.test b/tests/imgSVGnano.test index fce2f9d..f34316b 100644 --- a/tests/imgSVGnano.test +++ b/tests/imgSVGnano.test @@ -23,10 +23,36 @@ namespace eval svgnano { } + set data(bad) { } + set data(gradient) { + + + + + + + +} + tcltest::makeFile $data(plus) plus.svg set data(plusFilePath) [file join [tcltest::configure -tmpdir] plus.svg] @@ -207,7 +233,61 @@ test imgSVGnano-4.2 {error on file not accessible on reread due to configure} -s tcltest::removeFile tmpplus.svg } -returnCodes error -match glob -result {couldn't open "*/tmpplus.svg": no such file or directory} -test imgSVGnano-5.0 {svgblob metadata creation} -setup { +# rendering tests + +test imgSVGnano-5.0 {data gradient rendering} -setup { + catch {rename foo ""} + catch {rename gif ""} + image create photo gif\ + -data "R0lGODlhCgAKALMAANnZ2f/XAP/LAP+yAP+zAP+bAP+DAP9rAP9TAP9UAP88AP8kAP8MAP///////////yH5BAEAAAAALAAAAAAKAAoAAAQhMMhJhb14DMI7L2AoGmRpHmiqImyLJLAiz/Ri3zij73wEADs="\ + -format gif +} -body { + image create photo foo -data $data(gradient) + set res "" + for {set y 0} {$y < [image height foo]} {incr y} { + for {set x 0} {$x < [image width foo]} {incr x} { + lassign [foo get $x $y] r1 g1 b1 + lassign [gif get $x $y] r2 g2 b2 + if {$r1 != $r2 || $g1 != $g2 || $b1 != $b2} { + append res "pixel $x,$y unequal: $r1 $g1 $b1 != $r2 $g2 $b2\n" + } + } + } + set res +} -cleanup { + rename foo "" + rename gif "" +} -result {} + +test imgSVGnano-5.1 {file gradient rendering} -setup { + catch {rename foo ""} + tcltest::makeFile $data(gradient) tmpgradient.svg + catch {rename gif ""} + image create photo gif\ + -data "R0lGODlhCgAKALMAANnZ2f/XAP/LAP+yAP+zAP+bAP+DAP9rAP9TAP9UAP88AP8kAP8MAP///////////yH5BAEAAAAALAAAAAAKAAoAAAQhMMhJhb14DMI7L2AoGmRpHmiqImyLJLAiz/Ri3zij73wEADs="\ + -format gif +} -body { + image create photo foo -file [file join [tcltest::configure -tmpdir] tmpgradient.svg] + set res "" + for {set y 0} {$y < [image height foo]} {incr y} { + for {set x 0} {$x < [image width foo]} {incr x} { + lassign [foo get $x $y] r1 g1 b1 + lassign [gif get $x $y] r2 g2 b2 + if {$r1 != $r2 || $g1 != $g2 || $b1 != $b2} { + append res "pixel $x,$y unequal: $r1 $g1 $b1 != $r2 $g2 $b2\n" + } + } + } + set res +} -cleanup { + rename foo "" + tcltest::removeFile tmpgradient.svg + rename gif "" +} -result {} + +# metadata tests + +test imgSVGnano-6.0 {svgblob metadata creation} -setup { catch {rename foo ""} tcltest::makeFile $data(plus) tmpplus.svg } -body { @@ -218,14 +298,26 @@ test imgSVGnano-5.0 {svgblob metadata creation} -setup { tcltest::removeFile tmpplus.svg } -result {SVGBLOB} -test imgSVGnano-5.1 {scale with svgblob metadata} -setup { +test imgSVGnano-6.1 {scale with svgblob metadata (data)} -setup { + catch {rename foo ""} +} -body { + image create photo foo -data $data(plus) + foo configure -data "" -format "svg" + foo configure -format "svg -scale 2" + lappend res [image width foo] [image height foo] +} -cleanup { + rename foo "" + tcltest::removeFile tmpplus.svg +} -result {200 200} + +test imgSVGnano-6.2 {scale with svgblob metadata} -setup { catch {rename foo ""} tcltest::makeFile $data(plus) tmpplus.svg } -body { image create photo foo -file [file join [tcltest::configure -tmpdir] tmpplus.svg] foo configure -file "" -data "" foo configure -format "svg -scale 2" - lappend res [image width foo] [image height foo] + list [image width foo] [image height foo] } -cleanup { rename foo "" tcltest::removeFile tmpplus.svg -- cgit v0.12 From b39239f7ef80426cf1583b625f0ab22f94666c2a Mon Sep 17 00:00:00 2001 From: oehhar Date: Mon, 22 Jun 2020 21:20:04 +0000 Subject: TIP529 image metadata: read png DPI and aspect metadata --- generic/tkImgPNG.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++- tests/imgPNG.test | 15 ++++- 2 files changed, 179 insertions(+), 2 deletions(-) diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 669bc27..af0684e 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -175,6 +175,15 @@ typedef struct { Tcl_Obj *thisLineObj; /* Current line of pixels to process. */ int lineSize; /* Number of bytes in a PNG line. */ int phaseSize; /* Number of bytes/line in current phase. */ + + + /* + * Physical size: pHYS chunks. + */ + + double DPI; + double aspect; + } PNGImage; /* @@ -355,6 +364,13 @@ InitPNGImage( } return TCL_ERROR; } + + /* + * Initialize physical size pHYS values + */ + + pngPtr->DPI = -1; + pngPtr->aspect = -1; return TCL_OK; } @@ -944,6 +960,7 @@ ReadChunkHeader( case CHUNK_IDAT: case CHUNK_IEND: case CHUNK_IHDR: + case CHUNK_pHYs: case CHUNK_PLTE: case CHUNK_tRNS: break; @@ -962,7 +979,6 @@ ReadChunkHeader( case CHUNK_iTXt: case CHUNK_oFFs: case CHUNK_pCAL: - case CHUNK_pHYs: case CHUNK_sBIT: case CHUNK_sCAL: case CHUNK_sPLT: @@ -1654,6 +1670,84 @@ ReadTRNS( /* *---------------------------------------------------------------------- * + * ReadPHYS -- + * + * This function reads the PHYS (physical size) chunk data from + * the PNG file and populates the fields in the PNGImage + * structure. + * + * Results: + * TCL_OK, or TCL_ERROR if an I/O error occurs or the PHYS chunk is + * invalid. + * + * Side effects: + * The access position in f advances. + * + *---------------------------------------------------------------------- + */ + +static int +ReadPHYS( + Tcl_Interp *interp, + PNGImage *pngPtr, + int chunkSz, + unsigned long crc) +{ + unsigned long PPUx, PPUy; + char unitSpecifier; + + /* + * Check chunk size equal 9 bytes + */ + + if (chunkSz != 9) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "invalid physical chunk size", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_PHYS", NULL); + return TCL_ERROR; + } + + /* + * Read the chunk data + * 4 bytes: Pixels per unit, x axis + * 4 bytes: Pixels per unit, y axis + * 1 byte: unit specifier + */ + + if (ReadInt32(interp, pngPtr, &PPUx, &crc) == TCL_ERROR) { + return TCL_ERROR; + } + if (ReadInt32(interp, pngPtr, &PPUy, &crc) == TCL_ERROR) { + return TCL_ERROR; + } + if (ReadData(interp, pngPtr, &unitSpecifier, 1, &crc) == TCL_ERROR) { + return TCL_ERROR; + } + + if (CheckCRC(interp, pngPtr, crc) == TCL_ERROR) { + return TCL_ERROR; + } + + if ( PPUx > 2147483647 || PPUy > 2147483647 + || unitSpecifier > 1 ) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "invalid physical size value", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "BAD_PHYS", NULL); + return TCL_ERROR; + } + + if (PPUx > 0) { + pngPtr->aspect = ((double) PPUy) / ((double) PPUx); + } + if (1 == unitSpecifier) { + pngPtr->DPI = ((double) PPUx) * 0.0254; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * Paeth -- * * Utility function for applying the Paeth filter to a pixel. The Paeth @@ -2426,6 +2520,29 @@ DecodePNG( return TCL_ERROR; } + /* + * Physical header may be present here so try to parse it + */ + + if (CHUNK_pHYs == chunkType) { + /* + * Finish parsing the PHYS chunk. + */ + + if (ReadPHYS(interp, pngPtr, chunkSz, crc) == TCL_ERROR) { + return TCL_ERROR; + } + + /* + * Begin the next chunk. + */ + + if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType, + &crc) == TCL_ERROR) { + return TCL_ERROR; + } + } + if (CHUNK_PLTE == chunkType) { /* * Finish parsing the PLTE chunk. @@ -2477,6 +2594,29 @@ DecodePNG( } /* + * Physical header may be present here so try to parse it + */ + + if (CHUNK_pHYs == chunkType) { + /* + * Finish parsing the PHYS chunk. + */ + + if (ReadPHYS(interp, pngPtr, chunkSz, crc) == TCL_ERROR) { + return TCL_ERROR; + } + + /* + * Begin the next chunk. + */ + + if (ReadChunkHeader(interp, pngPtr, &chunkSz, &chunkType, + &crc) == TCL_ERROR) { + return TCL_ERROR; + } + } + + /* * Other ancillary chunk types could appear here, but for now we're only * interested in IDAT. The others should have been skipped. */ @@ -2758,6 +2898,18 @@ FileReadPNG( result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY); } + if (TCL_OK == result && metadataOutObj != NULL && png.DPI != -1) { + result = Tcl_DictObjPut(NULL, metadataOutObj, + Tcl_NewStringObj("DPI",-1), + Tcl_NewDoubleObj(png.DPI)); + } + + if (TCL_OK == result && metadataOutObj != NULL && png.aspect != -1) { + result = Tcl_DictObjPut(NULL, metadataOutObj, + Tcl_NewStringObj("aspect",-1), + Tcl_NewDoubleObj(png.aspect)); + } + CleanupPNGImage(&png); return result; } @@ -2854,6 +3006,18 @@ StringReadPNG( result = DecodePNG(interp, &png, fmtObj, imageHandle, destX, destY); } + if (TCL_OK == result && metadataOutObj != NULL && png.DPI != -1) { + result = Tcl_DictObjPut(NULL, metadataOutObj, + Tcl_NewStringObj("DPI",-1), + Tcl_NewDoubleObj(png.DPI)); + } + + if (TCL_OK == result && metadataOutObj != NULL && png.aspect != -1) { + result = Tcl_DictObjPut(NULL, metadataOutObj, + Tcl_NewStringObj("aspect",-1), + Tcl_NewDoubleObj(png.aspect)); + } + CleanupPNGImage(&png); return result; } diff --git a/tests/imgPNG.test b/tests/imgPNG.test index 4900e9c..537ea9e 100644 --- a/tests/imgPNG.test +++ b/tests/imgPNG.test @@ -1056,7 +1056,10 @@ duFtaSrZF3pfCpiGjN2imToJJ39m6BjG1XZRwrkAI8YUKSZWlEZQDAIrNArHnyvpXtmM/B7wJeAbwO fBcxKuQMrzfLdBoz29fX9led5v6u1XnBJW7vnr/YlrXEoNo22LRYOYlxZ1S6rkOfDcLvPAY/hGmWC7 H68uFI+x0oSPg2MAN/L5/M/vtqSED/T5cMu9J4Wf7HMGsB/4TEv/DFwe3Y/NPN57VXh+5BWApwFLlh r661tV1eju/ne8YJrkWtES0tmRe2VOviv2j2aBp5nHihiRaz/A4oCnsAsje/+AAAAAElFTkSuQmCC" - } + dpi100aspect2 +"iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAACXBIWXMAAA9hAAAewgEw8YEEAAAA +FklEQVR4nGP4+vXrP11lJgYGhj9xSQAzOwXsETZ69QAAAABJRU5ErkJggg==" + } # $encoded(basn0g08), $encoded(basn2c08), $encoded(basn3p08), $encoded(basn6a08) test imgPNG-1.1 {reading basic images; grayscale} -setup { @@ -1114,6 +1117,16 @@ test imgPNG-3.1 {reading image with unknown ancillary chunk - bug [1c659ef0f1]} } -cleanup { image delete $i } -result {0} + +test imgPNG-4.1 {reading image with unknown ancillary chunk - bug [1c659ef0f1]} -setup { + +} -body { + image create photo i1 -data $encoded(dpi100aspect2) + i1 cget -metadata +} -cleanup { + image delete i1 +} -result {DPI 99.9998 aspect 2.0} + } namespace delete png -- cgit v0.12 From 66ac6b6c7081489c23289749ded3ba20919f1ea0 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 23 Jun 2020 19:11:34 +0000 Subject: TIP529 image metadata: implement png metadata write --- generic/tkImgPNG.c | 125 +++++++++++++++++++++++++++++++++++++++++++++------ generic/tkImgPhoto.c | 9 ++-- tests/imgPNG.test | 40 +++++++++++++++-- tests/imgPhoto.test | 83 +++++++++++++++++++++++++--------- 4 files changed, 214 insertions(+), 43 deletions(-) diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index af0684e..852bdf3 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -207,7 +207,8 @@ static int DecodePNG(Tcl_Interp *interp, PNGImage *pngPtr, Tcl_Obj *fmtObj, Tk_PhotoHandle imageHandle, int destX, int destY); static int EncodePNG(Tcl_Interp *interp, - Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr); + Tk_PhotoImageBlock *blockPtr, PNGImage *pngPtr, + Tcl_Obj *metadataInObj); static int FileMatchPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, int *widthPtr, @@ -275,7 +276,7 @@ static int WriteData(Tcl_Interp *interp, PNGImage *pngPtr, const unsigned char *srcPtr, size_t srcSz, unsigned long *crcPtr); static int WriteExtraChunks(Tcl_Interp *interp, - PNGImage *pngPtr); + PNGImage *pngPtr, Tcl_Obj *metadataInObj); static int WriteIHDR(Tcl_Interp *interp, PNGImage *pngPtr, Tk_PhotoImageBlock *blockPtr); static int WriteIDAT(Tcl_Interp *interp, PNGImage *pngPtr, @@ -3104,6 +3105,34 @@ WriteByte( /* *---------------------------------------------------------------------- * + * LongToInt32 -- + * + * This function transforms to a 32-bit integer value as + * four bytes in network byte order. + * + * Results: + * None + * + * Side effects: + * Buffer will be modified. + * + *---------------------------------------------------------------------- + */ + +static inline void +LongToInt32( + unsigned long l, + unsigned char *pc) +{ + pc[0] = (unsigned char) ((l & 0xff000000) >> 24); + pc[1] = (unsigned char) ((l & 0x00ff0000) >> 16); + pc[2] = (unsigned char) ((l & 0x0000ff00) >> 8); + pc[3] = (unsigned char) ((l & 0x000000ff) >> 0); +} + +/* + *---------------------------------------------------------------------- + * * WriteInt32 -- * * This function writes a 32-bit integer value out to the PNG image as @@ -3126,12 +3155,7 @@ WriteInt32( unsigned long *crcPtr) { unsigned char pc[4]; - - pc[0] = (unsigned char) ((l & 0xff000000) >> 24); - pc[1] = (unsigned char) ((l & 0x00ff0000) >> 16); - pc[2] = (unsigned char) ((l & 0x0000ff00) >> 8); - pc[3] = (unsigned char) ((l & 0x000000ff) >> 0); - + LongToInt32(l,pc); return WriteData(interp, pngPtr, pc, 4, crcPtr); } @@ -3450,7 +3474,8 @@ WriteIDAT( static int WriteExtraChunks( Tcl_Interp *interp, - PNGImage *pngPtr) + PNGImage *pngPtr, + Tcl_Obj *metadataInObj) { static const unsigned char sBIT_contents[] = { 8, 8, 8, 8 @@ -3501,7 +3526,80 @@ WriteExtraChunks( return TCL_ERROR; } Tcl_DStringFree(&buf); + + /* + * Add a pHYs chunk if there is metadata for DPI and/or aspect + * aspect = PPUy / PPUx + * DPI = PPUx * 0.0254 + * The physical chunc consists of: + * - Points per meter in x direction (32 bit) + * - Points per meter in x direction (32 bit) + * - Unit specifier: 0: no unit (only aspect), 1: Points per meter + */ + + if (metadataInObj != NULL) { + + Tcl_Obj *aspectObj, *DPIObj; + double aspectValue=-1, DPIValue=-1; + unsigned long PPUx, PPUy; + char unitSpecifier; + + if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, + Tcl_NewStringObj("aspect",-1), + &aspectObj) || + TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, + Tcl_NewStringObj("DPI",-1), + &DPIObj) ) { + return TCL_ERROR; + } + if (DPIObj != NULL) { + if (TCL_ERROR == Tcl_GetDoubleFromObj(interp, DPIObj, &DPIValue)) + { + return TCL_ERROR; + } + PPUx = (unsigned long)round(DPIValue / 0.0254); + if (aspectObj == NULL) { + PPUy = PPUx; + } + unitSpecifier = 1; + } + if (aspectObj != NULL) { + if (TCL_ERROR == Tcl_GetDoubleFromObj(interp, aspectObj, + &aspectValue)) { + return TCL_ERROR; + } + /* + * aspect = PPUy / PPUx + */ + + if (DPIObj == NULL) { + unitSpecifier = 0; + PPUx = 65536; + PPUy = (unsigned long)round(65536.0 * aspectValue); + } else { + PPUy = (unsigned long)round(DPIValue * aspectValue / 0.0254); + } + } + if (DPIObj != NULL || aspectObj != NULL) { + unsigned char buffer[9]; + + if ( PPUx > 2147483647 || PPUy > 2147483647 ) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "DPI or aspect out of range", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "PNG", "PHYS", NULL); + return TCL_ERROR; + } + + LongToInt32(PPUx, buffer); + LongToInt32(PPUy, buffer+4); + buffer[8] = unitSpecifier; + if (WriteChunk(interp, pngPtr, CHUNK_pHYs, buffer, 9) + != TCL_OK) { + return TCL_ERROR; + } + } + } return TCL_OK; } @@ -3527,7 +3625,8 @@ static int EncodePNG( Tcl_Interp *interp, Tk_PhotoImageBlock *blockPtr, - PNGImage *pngPtr) + PNGImage *pngPtr, + Tcl_Obj *metadataInObj) { int greenOffset, blueOffset, alphaOffset; @@ -3610,7 +3709,7 @@ EncodePNG( * other programs more than us. */ - if (WriteExtraChunks(interp, pngPtr) == TCL_ERROR) { + if (WriteExtraChunks(interp, pngPtr, metadataInObj) == TCL_ERROR) { return TCL_ERROR; } @@ -3694,7 +3793,7 @@ FileWritePNG( * Write the raw PNG data out to the file. */ - result = EncodePNG(interp, blockPtr, &png); + result = EncodePNG(interp, blockPtr, &png, metadataInObj); cleanup: Tcl_Close(interp, chan); @@ -3746,7 +3845,7 @@ StringWritePNG( * back to the interpreter if successful. */ - result = EncodePNG(interp, blockPtr, &png); + result = EncodePNG(interp, blockPtr, &png, metadataInObj); if (TCL_OK == result) { Tcl_SetObjResult(interp, png.objDataPtr); diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 20cd036..f41cadd 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -2133,8 +2133,9 @@ ImgPhotoConfigureMaster( } if (metadataInObj) { /* - * make -metadata a dict and take it if keys in. - * Otherwise set a metadata null pointer. + * make -metadata a dict. + * Take also empty metadatas as this may be a sign to replace + * existing metadata. */ int dictSize; @@ -2258,10 +2259,6 @@ ImgPhotoConfigureMaster( masterPtr->flags |= IMAGE_CHANGED; } - /* - * ToDo: The case of a a dataString newly set to the empty string with a - * present metadata dict should also cause this. - */ if ((masterPtr->fileString == NULL) && (masterPtr->dataString != NULL) && ((masterPtr->dataString != oldData) || (masterPtr->format != oldFormat))) { diff --git a/tests/imgPNG.test b/tests/imgPNG.test index 537ea9e..4bd9d2a 100644 --- a/tests/imgPNG.test +++ b/tests/imgPNG.test @@ -1118,17 +1118,51 @@ test imgPNG-3.1 {reading image with unknown ancillary chunk - bug [1c659ef0f1]} image delete $i } -result {0} -test imgPNG-4.1 {reading image with unknown ancillary chunk - bug [1c659ef0f1]} -setup { - +test imgPNG-4.1 {data image with metadata} -body { + image create photo i1 -data $encoded(dpi100aspect2) + i1 cget -metadata +} -cleanup { + image delete i1 +} -result {DPI 99.9998 aspect 2.0} + +test imgPNG-4.2 {file image with metadata} -setup { + set path [file join [configure -tmpdir] test.png] + set h [open $path "WRONLY BINARY CREAT"] + puts -nonewline $h [binary decode base64 $encoded(dpi100aspect2)] + close $h } -body { + image create photo i1 -file $path + i1 cget -metadata +} -cleanup { + image delete i1 + file delete $path +} -result {DPI 99.9998 aspect 2.0} + +test imgPNG-4.3 {data output with metadata} -setup { image create photo i1 -data $encoded(dpi100aspect2) +} -body { + set data [i1 data -format png] + image delete i1 + image create photo i1 -data $data + i1 cget -metadata +} -cleanup { + image delete i1 +} -result {DPI 99.9998 aspect 2.0} + +test imgPNG-4.4 {file output with metadata} -setup { + image create photo i1 -data $encoded(dpi100aspect2) + set path [file join [configure -tmpdir] test.png] +} -body { + i1 write $path -format png + image delete i1 + image create photo i1 -file $path i1 cget -metadata } -cleanup { image delete i1 } -result {DPI 99.9998 aspect 2.0} - } + namespace delete png imageFinish cleanupTests diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 69ce7c1..5f71b9a 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2159,7 +2159,7 @@ test imgPhoto-23.1 {GIF comment before image data (-data)} -setup { gif1 cget -metadata } -cleanup { catch {image delete gif1} -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.2 {GIF file comment before image data (-file)} -setup { set data $::gifstart @@ -2169,7 +2169,7 @@ test imgPhoto-23.2 {GIF file comment before image data (-file)} -setup { append data $::gifdata $::gifend set path [file join [configure -tmpdir] test.gif] set h [open $path "WRONLY BINARY CREAT"] - puts $h $data + puts -nonewline $h $data close $h } -body { image create photo gif1 -file $path @@ -2177,7 +2177,7 @@ test imgPhoto-23.2 {GIF file comment before image data (-file)} -setup { } -cleanup { catch {image delete gif1} file delete $path -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.3 {GIF comment after image data (-data)} -setup { set data $::gifstart @@ -2191,7 +2191,7 @@ test imgPhoto-23.3 {GIF comment after image data (-data)} -setup { gif1 cget -metadata } -cleanup { catch {image delete gif1} -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.4 {GIF comment after image data (-file)} -setup { set data $::gifstart @@ -2210,7 +2210,7 @@ test imgPhoto-23.4 {GIF comment after image data (-file)} -setup { } -cleanup { catch {image delete gif1} file delete $path -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.5 {Two GIF comment blocks (-data)} -setup { set data $::gifstart @@ -2226,7 +2226,7 @@ test imgPhoto-23.5 {Two GIF comment blocks (-data)} -setup { gif1 cget -metadata } -cleanup { catch {image delete gif1} -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.6 {Two GIF comment blocks (-file)} -setup { set data $::gifstart @@ -2247,7 +2247,7 @@ test imgPhoto-23.6 {Two GIF comment blocks (-file)} -setup { } -cleanup { catch {image delete gif1} file delete $path -} -result {Comment ABCD} +} -result {comment ABCD} test imgPhoto-23.7 {XMP comment block before image (-data)} -setup { set data $::gifstart @@ -2427,7 +2427,7 @@ test imgPhoto-23.13 {create: test if shared metadata object is not preserved\ list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 } -cleanup { catch {image delete gif1} -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.14 {create: test if shared metadata object is not preserved\ (-file)}\ @@ -2450,7 +2450,7 @@ test imgPhoto-23.14 {create: test if shared metadata object is not preserved\ } -cleanup { catch {image delete gif1} file delete $path -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.15 {configure: test if shared metadata object is not\ preserved (empty image, -data)}\ @@ -2468,7 +2468,7 @@ test imgPhoto-23.15 {configure: test if shared metadata object is not\ list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 } -cleanup { catch {image delete gif1} -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.16 {configure: test if shared metadata object is not preserved\ (empty image, -file)}\ @@ -2492,7 +2492,7 @@ test imgPhoto-23.16 {configure: test if shared metadata object is not preserved\ } -cleanup { catch {image delete gif1} file delete $path -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.17 {configure: test if shared metadata object is not preserved\ (metadata replace, -data}\ @@ -2510,7 +2510,7 @@ test imgPhoto-23.17 {configure: test if shared metadata object is not preserved\ list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 } -cleanup { catch {image delete gif1} -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.18 {configure: test if shared metadata object is not preserved\ (metadata replace, -file}\ @@ -2534,7 +2534,7 @@ test imgPhoto-23.18 {configure: test if shared metadata object is not preserved\ } -cleanup { catch {image delete gif1} file delete $path -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.19 {configure: test if shared metadata object is not preserved\ (-data)}\ @@ -2553,7 +2553,7 @@ test imgPhoto-23.19 {configure: test if shared metadata object is not preserved\ list [dict get [gif1 cget -metadata]] $metadataDict $metadataDict2 } -cleanup { catch {image delete gif1} -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.20 {configure: test if shared metadata object is not preserved\ (-file)}\ @@ -2577,14 +2577,14 @@ test imgPhoto-23.20 {configure: test if shared metadata object is not preserved\ } -cleanup { catch {image delete gif1} file delete $path -} -result {{A 1 Comment ABCD} {A 1} {A 1}} +} -result {{A 1 comment ABCD} {A 1} {A 1}} test imgPhoto-23.21 {output data with comment (from -metadata argument)}\ -setup { set data $::gifstart$::gifdata$::gifend } -body { image create photo gif1 -data $data - set gifData [gif1 data -format gif -metadata [dict create Comment ABCD]] + set gifData [gif1 data -format gif -metadata [dict create comment ABCD]] } -cleanup { catch {image delete gif1} } -match glob -result {*ABCD*} @@ -2595,10 +2595,10 @@ test imgPhoto-23.22 {output file with comment (from -metadata argument)}\ set path [file join [configure -tmpdir] test.gif] } -body { image create photo gif1 -data $data - gif1 write $path -format gif -metadata [dict create Comment ABCD] + gif1 write $path -format gif -metadata [dict create comment ABCD] image delete gif1 image create photo gif1 -file $path - dict get [gif1 cget -metadata] Comment + dict get [gif1 cget -metadata] comment } -cleanup { catch {image delete gif1} file delete $path @@ -2609,7 +2609,7 @@ test imgPhoto-23.23 {output data with comment (from -metadata property)}\ set data $::gifstart$::gifdata$::gifend } -body { image create photo gif1 -data $data - gif1 configure -metadata [dict create Comment ABCD] + gif1 configure -metadata [dict create comment ABCD] set gifData [gif1 data -format gif] } -cleanup { catch {image delete gif1} @@ -2621,11 +2621,11 @@ test imgPhoto-23.24 {output file with comment (from -metadata property)}\ set path [file join [configure -tmpdir] test.gif] } -body { image create photo gif1 -data $data - gif1 configure -metadata [dict create Comment ABCD] + gif1 configure -metadata [dict create comment ABCD] gif1 write $path -format gif image delete gif1 image create photo gif1 -file $path - dict get [gif1 cget -metadata] Comment + dict get [gif1 cget -metadata] comment } -cleanup { catch {image delete gif1} file delete $path @@ -2668,6 +2668,47 @@ test imgPhoto-23.24 {output data with XMP (-file)} -setup { catch {image delete gif1} } -result {1} +test imgPhoto-23.25 {configure: empty metadata parameter overwrites image metadata} -setup { + image create photo gif1 -data $::gifstart$::gifdata$::gifend\ + -metadata {foo bar} + set data $::gifstart + # Append a comment extension block with data "ABCD" + append data "\x21\xfe\x04" "ABCD" "\x0" + # Trailer + append data $::gifdata $::gifend +} -body { + gif1 configure -data $data -metadata {} + gif1 cget -metadata +} -cleanup { + catch {image delete gif1} +} -result {comment ABCD} + +test imgPhoto-23.26 {write: empty metadata parameter overwrites image metadata} -setup { + image create photo gif1 -data $::gifstart$::gifdata$::gifend\ + -metadata {comment bar} + set path [file join [configure -tmpdir] test.gif] +} -body { + gif1 write $path -format gif -metadata {} + image delete gif1 + image create photo gif1 -file $path + dict size [gif1 cget -metadata] +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {0} + +test imgPhoto-23.27 {data: empty metadata parameter overwrites image metadata} -setup { + image create photo gif1 -data $::gifstart$::gifdata$::gifend\ + -metadata {comment bar} +} -body { + set data [gif1 data -format gif -metadata {}] + image delete gif1 + image create photo gif1 -data $data + dict size [gif1 cget -metadata] +} -cleanup { + catch {image delete gif1} + file delete $path +} -result {0} unset -nocomplain gifstart gifdata gifend -- cgit v0.12 From 0fdcaa049e07325d3f3890235edd399fcd238311 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 23 Jun 2020 19:46:58 +0000 Subject: TIP529 image metadata: make compile on MS-VC6 --- generic/tkImgPNG.c | 6 +++--- generic/tkImgSVGnano.c | 3 ++- tests/imgPNG.test | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 852bdf3..91eb393 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -3557,7 +3557,7 @@ WriteExtraChunks( { return TCL_ERROR; } - PPUx = (unsigned long)round(DPIValue / 0.0254); + PPUx = (unsigned long)floor(DPIValue / 0.0254+0.5); if (aspectObj == NULL) { PPUy = PPUx; } @@ -3576,9 +3576,9 @@ WriteExtraChunks( if (DPIObj == NULL) { unitSpecifier = 0; PPUx = 65536; - PPUy = (unsigned long)round(65536.0 * aspectValue); + PPUy = (unsigned long)floor(65536.0 * aspectValue+0.5); } else { - PPUy = (unsigned long)round(DPIValue * aspectValue / 0.0254); + PPUy = (unsigned long)floor(DPIValue * aspectValue / 0.0254+0.5); } } if (DPIObj != NULL || aspectObj != NULL) { diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c index f82a285..c4eceb0 100644 --- a/generic/tkImgSVGnano.c +++ b/generic/tkImgSVGnano.c @@ -234,10 +234,11 @@ FileMatchSVG( { Tcl_Obj *dataObj; NSVGimage *nsvgImage; - (void)fileName; serializedHeader *serializedHeaderPtr; optionsStruct options; + (void)fileName; + /* * Parse the options. Unfortunately, any error can not be returned. */ diff --git a/tests/imgPNG.test b/tests/imgPNG.test index 4bd9d2a..a95ffce 100644 --- a/tests/imgPNG.test +++ b/tests/imgPNG.test @@ -1141,9 +1141,9 @@ test imgPNG-4.2 {file image with metadata} -setup { test imgPNG-4.3 {data output with metadata} -setup { image create photo i1 -data $encoded(dpi100aspect2) } -body { - set data [i1 data -format png] + set imgData [i1 data -format png] image delete i1 - image create photo i1 -data $data + image create photo i1 -data $imgData i1 cget -metadata } -cleanup { image delete i1 -- cgit v0.12 From bebe116fb648144c7dedbee032f76ed41494bb83 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 23 Jun 2020 20:09:40 +0000 Subject: fix incomplete image test file earth.gif --- tests/earth.gif | Bin 51712 -> 51559 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/earth.gif b/tests/earth.gif index 2c229eb..d667244 100644 Binary files a/tests/earth.gif and b/tests/earth.gif differ -- cgit v0.12 From 70f472452d20f28db1f411b4ff0d80fc99fc2660 Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 24 Jun 2020 15:01:00 +0000 Subject: TIP 529 image metadata: remove optional feature: svg blob in metadata --- generic/tkImgSVGnano.c | 1547 +++++++++++------------------------------------- generic/tkInt.h | 2 +- generic/tkWindow.c | 2 +- tests/imgSVGnano.test | 116 ---- 4 files changed, 356 insertions(+), 1311 deletions(-) diff --git a/generic/tkImgSVGnano.c b/generic/tkImgSVGnano.c index c4eceb0..7575f86 100644 --- a/generic/tkImgSVGnano.c +++ b/generic/tkImgSVGnano.c @@ -27,94 +27,13 @@ #define NANOSVGRAST_IMPLEMENTATION #include "nanosvgrast.h" -/* - * Serialized data version - * This consists of "svg" plus binary '1' at byte locations in an int. - * It serves as indication, version and endinaess check - */ - -#define STRUCTURE_VERSION ('s'+256*'v'+65535*'g'+16777216*1) - -/* - * Serialized image data header - */ - -typedef struct { - unsigned int structureVersion; - float dpi; - float width; // Width of the image. - float height; // Height of the image. - int shapeCount; - int pathCount; - int ptsCount; - int gradientCount; - int gradientStopCount; -} serializedHeader; - -/* - * Result of options parsing and first block in driver internal DString - */ +/* Additional parameters to nsvgRasterize() */ typedef struct { double scale; int scaleToHeight; int scaleToWidth; - float dpi; - int svgBlobFollows; -} optionsStruct; - -/* - * Serialized structures from NSCG - * All pointers are replaced by array indexes - */ - -typedef struct NSVGgradientSerialized { - float xform[6]; - char spread; - float fx, fy; - int nstops; - int stops; -} NSVGgradientSerialized; - -typedef struct NSVGpaintSerialized { - char type; - union { - unsigned int color; - int gradient; - }; -} NSVGpaintSerialized; - - -typedef struct NSVGpathSerialized -{ - int pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ... - int npts; // Total number of bezier points. - // Caution: pair of floats - char closed; // Flag indicating if shapes should be treated as closed. - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - int next; // Pointer to next path, or NULL if last element. -} NSVGpathSerialized; - -typedef struct NSVGshapeSerialized -{ - char id[64]; // Optional 'id' attr of the shape or its group - NSVGpaintSerialized fill; // Fill paint - NSVGpaintSerialized stroke; // Stroke paint - float opacity; // Opacity of the shape. - float strokeWidth; // Stroke width (scaled). - float strokeDashOffset; // Stroke dash offset (scaled). - float strokeDashArray[8]; // Stroke dash array (scaled). - char strokeDashCount; // Number of dash values in dash array. - char strokeLineJoin; // Stroke join type. - char strokeLineCap; // Stroke cap type. - float miterLimit; // Miter limit - char fillRule; // Fill rule, see NSVGfillRule. - unsigned char flags; // Logical or of NSVG_FLAGS_* flags - float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy]. - int paths; // Linked list of paths in the image. - int next; // Pointer to next shape, or NULL if last element. -} NSVGshapeSerialized; - +} RastOpts; /* * Per interp cache of last NSVGimage which was matched to @@ -130,65 +49,45 @@ typedef struct { ClientData dataOrChan; Tcl_DString formatString; NSVGimage *nsvgImage; + RastOpts ropts; } NSVGcache; -static int FileMatchSVG(Tcl_Interp *interp, Tcl_Channel chan, - const char *fileName, Tcl_Obj *formatObj, - Tcl_Obj *metadataInObj, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOutObj, - int *closeChannelPtr, - Tcl_DString *driverInternal); +static int FileMatchSVG(Tcl_Channel chan, const char *fileName, + Tcl_Obj *format, int *widthPtr, int *heightPtr, + Tcl_Interp *interp); static int FileReadSVG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, - Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, - int destX, int destY, int width, int height, - int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); -static int StringMatchSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + Tk_PhotoHandle imageHandle, int destX, int destY, + int width, int height, int srcX, int srcY); +static int StringMatchSVG(Tcl_Obj *dataObj, Tcl_Obj *format, + int *widthPtr, int *heightPtr, Tcl_Interp *interp); static int StringReadSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, - Tcl_Obj *format, Tcl_Obj *metadataIn, - Tk_PhotoHandle imageHandle, + Tcl_Obj *format, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternal); -static int ParseOptions(Tcl_Interp *interp, Tcl_Obj *formatObj, - optionsStruct *optionsPtr); -static NSVGimage * ParseSVG(Tcl_Interp *interp, Tcl_Obj *dataObj, float dpi); + int srcX, int srcY); +static NSVGimage * ParseSVGWithOptions(Tcl_Interp *interp, + const char *input, TkSizeT length, Tcl_Obj *format, + RastOpts *ropts); static int RasterizeSVG(Tcl_Interp *interp, - Tk_PhotoHandle imageHandle, - char *svgBlob, optionsStruct * optionsPtr, + Tk_PhotoHandle imageHandle, NSVGimage *nsvgImage, int destX, int destY, int width, int height, - int srcX, int srcY); -static double GetScaleFromParameters( - serializedHeader *serializedHeaderPtr, - optionsStruct * optionsPtr, int *widthPtr, - int *heightPtr); -static void SerializeNSVGImage(Tcl_DString *driverInternalPtr, - NSVGimage *nsvgImage); -static void SerializePath(struct NSVGpath *pathPtr, - serializedHeader *serializedHeaderPtr, - Tcl_DString *pathDStringPtr, - Tcl_DString *ptrDStringPtr); -static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, - serializedHeader *serializedHeaderPtr, - Tcl_DString *gradientDStringPtr, - Tcl_DString *gradientStopDStringPtr); -static char * StringCheckMetadata(Tcl_Obj *dataObj, Tcl_Obj *metadataInObj, - float dpi, int *LengthPtr); -static int SaveSVGBLOBToMetadata(Tcl_Interp *interp, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternalPtr); -static void nsvgRasterizeSerialized(NSVGrasterizer* r, char *svgBlobPtr, float tx, - float ty, float scale, unsigned char* dst, int w, - int h, int stride); + int srcX, int srcY, RastOpts *ropts); +static double GetScaleFromParameters(NSVGimage *nsvgImage, + RastOpts *ropts, int *widthPtr, int *heightPtr); +static NSVGcache * GetCachePtr(Tcl_Interp *interp); +static int CacheSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, NSVGimage *nsvgImage, + RastOpts *ropts); +static NSVGimage * GetCachedSVG(Tcl_Interp *interp, ClientData dataOrChan, + Tcl_Obj *formatObj, RastOpts *ropts); +static void CleanCache(Tcl_Interp *interp); +static void FreeCache(ClientData clientData, Tcl_Interp *interp); /* * The format record for the SVG nano file format: */ -Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano = { +Tk_PhotoImageFormat tkImgFmtSVGnano = { "svg", /* name */ FileMatchSVG, /* fileMatchProc */ StringMatchSVG, /* stringMatchProc */ @@ -219,381 +118,45 @@ Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano = { static int FileMatchSVG( - Tcl_Interp *interp, /* interpreter pointer */ - Tcl_Channel chan, /* The image file, open for reading. */ - const char *fileName, /* The name of the image file. */ - Tcl_Obj *formatObj, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, int *widthPtr, int *heightPtr, - /* The dimensions of the image are returned - * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ - int *closeChannelPtr, /* Return if the channel may be closed */ - Tcl_DString *driverInternalPtr) - /* memory passed to FileReadGIF */ + Tcl_Interp *interp) { - Tcl_Obj *dataObj; + TkSizeT length; + Tcl_Obj *dataObj = Tcl_NewObj(); + const char *data; + RastOpts ropts; NSVGimage *nsvgImage; - serializedHeader *serializedHeaderPtr; - optionsStruct options; - (void)fileName; - /* - * Parse the options. Unfortunately, any error can not be returned. - */ - - if (TCL_OK != ParseOptions(interp, formatObj, &options) ) { - return 0; - } - - /* - * Read the file data into a TCL object - */ - - dataObj = Tcl_NewObj(); + CleanCache(interp); if (Tcl_ReadChars(chan, dataObj, -1, 0) == TCL_IO_FAILURE) { /* in case of an error reading the file */ Tcl_DecrRefCount(dataObj); return 0; } - - /* - * Parse the SVG data - */ - - nsvgImage = ParseSVG(interp, dataObj, options.dpi); + data = TkGetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); Tcl_DecrRefCount(dataObj); if (nsvgImage != NULL) { - - /* - * On successful parse, save the data in the header structure - */ - - Tcl_DStringSetLength(driverInternalPtr, - sizeof(optionsStruct)+sizeof(serializedHeader)); - - options.svgBlobFollows = 1; - memcpy( Tcl_DStringValue(driverInternalPtr), &options, - sizeof(optionsStruct)); - - serializedHeaderPtr = (serializedHeader *) - (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); - - serializedHeaderPtr->width = nsvgImage->width; - serializedHeaderPtr->height = nsvgImage->height; - serializedHeaderPtr->dpi = options.dpi; - GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, - heightPtr); + GetScaleFromParameters(nsvgImage, &ropts, widthPtr, heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; } - - /* - * Serialize the NSVGImage structure - * As the DString is resized, serializedHeaderPtr may get invalid - */ - - SerializeNSVGImage(driverInternalPtr, nsvgImage); - nsvgDelete(nsvgImage); + if (!CacheSVG(interp, chan, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } return 1; } return 0; - -} - -/* - *---------------------------------------------------------------------- - * - * SerializeNSVGImage -- - * - * This function saves the NSVGimage structure into the DString. - * - * Results: - * none. - * - * Side effects: - * The DString size is changed and thus, the value pointer may - * change. - * - *---------------------------------------------------------------------- - */ - -static void SerializeNSVGImage(Tcl_DString *driverInternalPtr, - NSVGimage *nsvgImage) { - serializedHeader *serializedHeaderPtr; - Tcl_DString shapeDString, pathDString, ptsDString, gradientDString, - gradientStopDString; - NSVGshape *shapePtr; - - serializedHeaderPtr = (serializedHeader *) - (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); - Tcl_DStringInit(&shapeDString); - Tcl_DStringInit(&pathDString); - Tcl_DStringInit(&ptsDString); - Tcl_DStringInit(&gradientDString); - Tcl_DStringInit(&gradientStopDString); - - serializedHeaderPtr->structureVersion = STRUCTURE_VERSION; - serializedHeaderPtr->shapeCount = 0; - serializedHeaderPtr->pathCount = 0; - serializedHeaderPtr->ptsCount = 0; - serializedHeaderPtr->gradientCount = 0; - serializedHeaderPtr->gradientStopCount = 0; - - for ( shapePtr = nsvgImage->shapes; shapePtr != NULL; - shapePtr = shapePtr->next) { - NSVGshapeSerialized shapeSerialized; - - /* - * Copy serialized shape fix data - */ - - memcpy(shapeSerialized.id, shapePtr->id, 64 * sizeof(char)); - - shapeSerialized.fill = SerializePaint(&(shapePtr->fill), - serializedHeaderPtr, &gradientDString, &gradientStopDString); - - shapeSerialized.stroke = SerializePaint(&(shapePtr->stroke), - serializedHeaderPtr, &gradientDString, &gradientStopDString); - - shapeSerialized.opacity = shapePtr->opacity; - shapeSerialized.strokeWidth = shapePtr->strokeWidth; - shapeSerialized.strokeDashOffset = shapePtr->strokeDashOffset; - memcpy(shapeSerialized.strokeDashArray, shapePtr->strokeDashArray, - 8*sizeof(float)); - shapeSerialized.strokeDashCount = shapePtr->strokeDashCount; - shapeSerialized.strokeLineJoin = shapePtr->strokeLineJoin; - shapeSerialized.strokeLineCap = shapePtr->strokeLineCap; - shapeSerialized.miterLimit = shapePtr->miterLimit; - shapeSerialized.fillRule = shapePtr->fillRule; - shapeSerialized.flags = shapePtr->flags; - memcpy(shapeSerialized.bounds, shapePtr->bounds, 4*sizeof(float)); - - /* - * Serialize the paths linked list - */ - - if ( shapePtr->paths == NULL ) { - shapeSerialized.paths = -1; - } else { - shapeSerialized.paths = serializedHeaderPtr->pathCount; - SerializePath(shapePtr->paths, serializedHeaderPtr, &pathDString, - &ptsDString); - } - - /* - * generate next array position and save to DString - */ - - serializedHeaderPtr->shapeCount++; - shapeSerialized.next = - shapePtr->next == NULL ? -1: - serializedHeaderPtr->shapeCount; - - Tcl_DStringAppend(&shapeDString, - (const char *)&shapeSerialized, sizeof(NSVGshapeSerialized)); - } - - /* - * Write the DStrings into the driver memory one after the other - * Note: serializedHeaderPtr may get invalid due to DString resize - */ - - if (Tcl_DStringLength(&shapeDString) > 0) { - Tcl_DStringAppend(driverInternalPtr, - Tcl_DStringValue(&shapeDString), - Tcl_DStringLength(&shapeDString)); - } - Tcl_DStringFree(&shapeDString); - - if (Tcl_DStringLength(&pathDString) > 0) { - Tcl_DStringAppend(driverInternalPtr, - Tcl_DStringValue(&pathDString), - Tcl_DStringLength(&pathDString)); - } - Tcl_DStringFree(&pathDString); - - if (Tcl_DStringLength(&ptsDString) > 0) { - Tcl_DStringAppend(driverInternalPtr, - Tcl_DStringValue(&ptsDString), - Tcl_DStringLength(&ptsDString)); - } - Tcl_DStringFree(&ptsDString); - - if (Tcl_DStringLength(&gradientDString) > 0) { - Tcl_DStringAppend(driverInternalPtr, - Tcl_DStringValue(&gradientDString), - Tcl_DStringLength(&gradientDString)); - } - Tcl_DStringFree(&gradientDString); - - if (Tcl_DStringLength(&gradientStopDString) > 0) { - Tcl_DStringAppend(driverInternalPtr, - Tcl_DStringValue(&gradientStopDString), - Tcl_DStringLength(&gradientStopDString)); - } - Tcl_DStringFree(&gradientStopDString); } /* *---------------------------------------------------------------------- * - * SerializePaint -- - * - * This function transforms the NSVGpaint structure to a serialize - * version. - * The child structures gradient and gradientStop are serialized into - * their DString memory. - * - * Results: - * NSVGPaintSerialized structure. - * - * Side effects: - * The DString size is changed and thus, the value pointer may - * change. - * - *---------------------------------------------------------------------- - */ - -static struct NSVGpaintSerialized SerializePaint(struct NSVGpaint *paintPtr, - serializedHeader *serializedHeaderPtr, - Tcl_DString *gradientDStringPtr, - Tcl_DString *gradientStopDStringPtr) -{ - struct NSVGpaintSerialized paintSerialized; - - paintSerialized.type = paintPtr->type; - - if (paintPtr->type == NSVG_PAINT_LINEAR_GRADIENT - || paintPtr->type == NSVG_PAINT_RADIAL_GRADIENT) { - - /* - * Gradient union pointer present - */ - - NSVGgradient* gradientPtr; - NSVGgradientSerialized gradientSerialized; - - gradientPtr = paintPtr->gradient; - memcpy(&(gradientSerialized.xform), gradientPtr->xform, - 6 * sizeof(float) ); - gradientSerialized.spread = gradientPtr->spread; - gradientSerialized.fx = gradientPtr->fx; - gradientSerialized.fy = gradientPtr->fy; - - /* - * Copy gradient stop array to DString - */ - - gradientSerialized.nstops = gradientPtr->nstops; - if ( gradientPtr->nstops == 0 ) { - gradientSerialized.stops = -1; - } else { - gradientSerialized.stops = serializedHeaderPtr->gradientStopCount; - Tcl_DStringAppend(gradientStopDStringPtr, - (const char *)gradientPtr->stops, - gradientPtr->nstops * sizeof(NSVGgradientStop)); - - (serializedHeaderPtr->gradientStopCount) += gradientPtr->nstops; - } - (serializedHeaderPtr->gradientStopCount) += gradientPtr->nstops; - - paintSerialized.gradient = serializedHeaderPtr->gradientCount; - Tcl_DStringAppend(gradientDStringPtr, - (const char *) & gradientSerialized, - sizeof(NSVGgradientSerialized)); - (serializedHeaderPtr->gradientCount) ++; - - } else { - - /* - * Color union or nothing present - */ - - paintSerialized.color = paintPtr->color; - } - return paintSerialized; -} -/* - *---------------------------------------------------------------------- - * - * SerializePath -- - * - * This function serializes a linked list of NSVGpath structure into - * the corresponding DString array. - * - * Results: - * none - * - * Side effects: - * The DString size is changed and thus, the value pointer may - * change. - * - *---------------------------------------------------------------------- - */ - -static void SerializePath(struct NSVGpath *pathPtr, - serializedHeader *serializedHeaderPtr, - Tcl_DString *pathDStringPtr, - Tcl_DString *ptsDStringPtr) -{ - - /* - * loop over path linked list - */ - - for (;pathPtr != NULL; pathPtr = pathPtr->next) { - NSVGpathSerialized pathSerialized; - int index; - - /* - * Save points in the ptr dstring. - * The first index and the count is saved. - */ - - pathSerialized.npts = pathPtr->npts; - if (pathPtr->npts == 0) { - pathSerialized.pts = -1; - } else { - - /* - * Attention: npts is a pair of floats - */ - - pathSerialized.pts = serializedHeaderPtr->ptsCount; - for (index = 0; index < (pathPtr->npts) * 2; index++) { - float ptCurrent; - ptCurrent = pathPtr->pts[index]; - Tcl_DStringAppend(ptsDStringPtr, - (const char *)&ptCurrent, sizeof(float)); - (serializedHeaderPtr->ptsCount)++; - } - } - - /* - * Copy the other items of the path structure - */ - - pathSerialized.closed = pathPtr->closed; - memcpy(pathSerialized.bounds, pathPtr->bounds, 4*sizeof(float)); - - /* - * Build the next item and add to DString - */ - - serializedHeaderPtr->pathCount++; - pathSerialized.next = (pathPtr->next == NULL) ? -1 : - serializedHeaderPtr->pathCount; - - Tcl_DStringAppend(pathDStringPtr, - (const char *)&pathSerialized, sizeof(NSVGpathSerialized)); - } -} -/* - *---------------------------------------------------------------------- - * * FileReadSVG -- * * This function is called by the photo image type to read SVG format @@ -612,48 +175,41 @@ static void SerializePath(struct NSVGpath *pathPtr, static int FileReadSVG( - Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ - Tcl_Channel chan, /* The image file, open for reading. */ - const char *fileName, /* The name of the image file. */ - Tcl_Obj *formatObj, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ - Tk_PhotoHandle imageHandle, /* The photo image to write into. */ - int destX, int destY, /* Coordinates of top-left pixel in photo - * image to be written to. */ - int width, int height, /* Dimensions of block of photo image to be - * written to. */ - int srcX, int srcY, /* Coordinates of top-left pixel to be used in - * image being read. */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternalPtr) - /* memory passed from FileMatchGIF */ + Tcl_Interp *interp, + Tcl_Channel chan, + const char *fileName, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) { - int result; - optionsStruct * optionsPtr; - char * svgBlob; + TkSizeT length; + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, chan, formatObj, &ropts); (void)fileName; - - - optionsPtr = (optionsStruct *) Tcl_DStringValue(driverInternalPtr); - svgBlob = Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct); - /* - * Raster the parsed SVG from the SVGBLOB in the driver internal DString - */ - - result = RasterizeSVG(interp, imageHandle, svgBlob, optionsPtr, destX, - destY, width, height, srcX, srcY); - - /* - * On success, output the SVGBLOB as metadata - */ + if (nsvgImage == NULL) { + Tcl_Obj *dataObj = Tcl_NewObj(); - if (result == TCL_OK) { - result = SaveSVGBLOBToMetadata(interp, metadataOutObj, - driverInternalPtr); + if (Tcl_ReadChars(chan, dataObj, -1, 0) == TCL_IO_FAILURE) { + /* in case of an error reading the file */ + Tcl_DecrRefCount(dataObj); + Tcl_SetObjResult(interp, Tcl_NewStringObj("read error", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "READ_ERROR", NULL); + return TCL_ERROR; + } + data = TkGetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); + Tcl_DecrRefCount(dataObj); + if (nsvgImage == NULL) { + return TCL_ERROR; + } } - - return result; + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); } /* @@ -676,95 +232,28 @@ FileReadSVG( static int StringMatchSVG( - Tcl_Interp *interp, /* interpreter to report errors */ - Tcl_Obj *dataObj, /* the object containing the image data */ - Tcl_Obj *formatObj, /* the image format object, or NULL */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ - int *widthPtr, /* where to put the string width */ - int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOut, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternalPtr) - /* memory to pass to StringReadGIF */ + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + int *widthPtr, int *heightPtr, + Tcl_Interp *interp) { TkSizeT length; + const char *data; + RastOpts ropts; NSVGimage *nsvgImage; - serializedHeader *serializedHeaderPtr; - char * svgBlobPtr; - optionsStruct options; - - /* - * Parse the options. Unfortunately, any error can not be returned. - */ - - if (TCL_OK != ParseOptions(interp, formatObj, &options) ) { - return 0; - } - - /* - * Check for the special data to indicate that the metadata should be used. - * On special data, get the serialized header structure and check it. - */ - - svgBlobPtr = StringCheckMetadata( dataObj, metadataInObj, options.dpi, - &length); - - if (NULL != svgBlobPtr) { - serializedHeaderPtr = (serializedHeader *) svgBlobPtr; - } else { - Tcl_DStringSetLength(driverInternalPtr, - sizeof(optionsStruct)+sizeof(serializedHeader)); - - options.svgBlobFollows = 1; - memcpy( Tcl_DStringValue(driverInternalPtr), &options, - sizeof(optionsStruct)); - - serializedHeaderPtr = (serializedHeader *) - (Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct)); - } - - /* - * Use the metadata svg blob if present to return width and height - */ - - if (NULL != svgBlobPtr) { - /* - * Save the options struct in the driver internal DString - */ - options.svgBlobFollows = 0; - Tcl_DStringSetLength(driverInternalPtr, sizeof(optionsStruct)); - memcpy(Tcl_DStringValue(driverInternalPtr), &options, - sizeof(optionsStruct)); - - GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, - heightPtr); - return 1; - } - - /* - * Check the passed data object and serialize it on success. - */ - - nsvgImage = ParseSVG(interp, dataObj, options.dpi); + CleanCache(interp); + data = TkGetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, &ropts); if (nsvgImage != NULL) { - serializedHeaderPtr->width = nsvgImage->width; - serializedHeaderPtr->height = nsvgImage->height; - serializedHeaderPtr->dpi = options.dpi; - GetScaleFromParameters(serializedHeaderPtr, &options, widthPtr, - heightPtr); + GetScaleFromParameters(nsvgImage, &ropts, widthPtr, heightPtr); if ((*widthPtr <= 0.0) || (*heightPtr <= 0.0)) { nsvgDelete(nsvgImage); return 0; } - - /* - * Serialize the NSVGImage structure - * As the DString is resized, serializedHeaderPtr may get invalid - */ - - SerializeNSVGImage(driverInternalPtr, nsvgImage); - - nsvgDelete(nsvgImage); + if (!CacheSVG(interp, dataObj, formatObj, nsvgImage, &ropts)) { + nsvgDelete(nsvgImage); + } return 1; } return 0; @@ -773,68 +262,6 @@ StringMatchSVG( /* *---------------------------------------------------------------------- * - * StringCheckMetadata -- - * - * Check the passed tring for a metadata serialized structure. - * - * Results: - * The svg blob pointer on success, NULL otherwise. - * - * Side effects: - * The file is saved in the internal cache for further use. - * - *---------------------------------------------------------------------- - */ - -static char * StringCheckMetadata( - Tcl_Obj *dataObj, /* the object containing the image data */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ - float dpi, /* options dpi must match svgblob */ - TkSizeT *lengthOutPtr) /* output data length on success */ -{ - TkSizeT length; - const char *data; - serializedHeader *serializedHeaderPtr; - char * svgBlobPtr; - Tcl_Obj *itemData; - - /* - * Check for the special data to indicate that the metadata should be used. - * On special data, get the serialized header structure and check it. - */ - - if (NULL == metadataInObj) { - return NULL; - } - - data = TkGetStringFromObj(dataObj, &length); - if (0 != strcmp(data, "") ) { - return NULL; - } - if (TCL_ERROR == Tcl_DictObjGet(NULL, metadataInObj, - Tcl_NewStringObj("SVGBLOB",-1), &itemData)) { - return NULL; - } - if (itemData == NULL) { - return NULL; - } - svgBlobPtr = Tcl_GetByteArrayFromObj(itemData, &length); - if (length < sizeof(serializedHeader) ) { - return NULL; - } - serializedHeaderPtr = (serializedHeader *)svgBlobPtr; - if (serializedHeaderPtr->structureVersion != STRUCTURE_VERSION - || serializedHeaderPtr->dpi != dpi - ) { - return NULL; - } - *lengthOutPtr = length; - return svgBlobPtr; -} - -/* - *---------------------------------------------------------------------- - * * StringReadSVG -- * * This function is called by the photo image type to read SVG format @@ -852,112 +279,59 @@ static char * StringCheckMetadata( static int StringReadSVG( - Tcl_Interp *interp, /* interpreter for reporting errors in */ - Tcl_Obj *dataObj, /* object containing the image */ - Tcl_Obj *formatObj, /* format object, or NULL */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ - Tk_PhotoHandle imageHandle, /* the image to write this data into */ - int destX, int destY, /* The rectangular region of the */ - int width, int height, /* image to copy */ - int srcX, int srcY, - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternalPtr) - /* memory passed from StringReadSVG */ + Tcl_Interp *interp, + Tcl_Obj *dataObj, + Tcl_Obj *formatObj, + Tk_PhotoHandle imageHandle, + int destX, int destY, + int width, int height, + int srcX, int srcY) { - int result; TkSizeT length; - char * svgBlobPtr; - optionsStruct * optionsPtr; - Tcl_Obj *itemData; - - optionsPtr = (optionsStruct *) Tcl_DStringValue(driverInternalPtr); - if (optionsPtr->svgBlobFollows) { - svgBlobPtr = Tcl_DStringValue(driverInternalPtr)+ sizeof(optionsStruct); - } else { - if (NULL == metadataInObj - || TCL_ERROR == Tcl_DictObjGet(NULL, metadataInObj, - Tcl_NewStringObj("SVGBLOB",-1), &itemData) - || itemData == NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "internal error: -metadata missing", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", - NULL); - return TCL_ERROR; - } - - svgBlobPtr = Tcl_GetByteArrayFromObj(itemData, &length); - } - - result = RasterizeSVG(interp, imageHandle, - svgBlobPtr, optionsPtr, destX, destY, width, height, srcX, srcY); - if (result != TCL_OK) { - return result; - } + const char *data; + RastOpts ropts; + NSVGimage *nsvgImage = GetCachedSVG(interp, dataObj, formatObj, &ropts); - if (!optionsPtr->svgBlobFollows) { - return TCL_OK; + if (nsvgImage == NULL) { + data = TkGetStringFromObj(dataObj, &length); + nsvgImage = ParseSVGWithOptions(interp, data, length, formatObj, + &ropts); } - - return SaveSVGBLOBToMetadata(interp, metadataOutObj, driverInternalPtr); -} - -/* - *---------------------------------------------------------------------- - * - * SaveSVGBLOBToMetadata -- - * - * Copy the driver internal DString into the metadata key SVGBLOB. - * - * Results: - * A TCL result value. - * - * Side effects: - * Change the output metadata. - * - *---------------------------------------------------------------------- - */ - -static int SaveSVGBLOBToMetadata( - Tcl_Interp *interp, /* interpreter for reporting errors in */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternalPtr) - /* memory passed from xxxReadSVG */ -{ - if (metadataOutObj == NULL) { - return TCL_OK; + if (nsvgImage == NULL) { + return TCL_ERROR; } - return Tcl_DictObjPut(interp, metadataOutObj, - Tcl_NewStringObj("SVGBLOB",-1), - Tcl_NewByteArrayObj( - Tcl_DStringValue(driverInternalPtr) + sizeof(optionsStruct), - Tcl_DStringLength(driverInternalPtr) - sizeof(optionsStruct))); + return RasterizeSVG(interp, imageHandle, nsvgImage, destX, destY, + width, height, srcX, srcY, &ropts); } /* *---------------------------------------------------------------------- * - * ParseOptions -- + * ParseSVGWithOptions -- * - * Parse the options given in the -format parameter. + * This function is called to parse the given input string as SVG. * * Results: - * A normal tcl result + * Return a newly create NSVGimage on success, and NULL otherwise. * * Side effects: * *---------------------------------------------------------------------- */ -static int -ParseOptions( +static NSVGimage * +ParseSVGWithOptions( Tcl_Interp *interp, + const char *input, + TkSizeT length, Tcl_Obj *formatObj, - optionsStruct * optionsPtr) + RastOpts *ropts) { Tcl_Obj **objv = NULL; int objc = 0; - double dpi; + double dpi = 96.0; char *inputCopy = NULL; + NSVGimage *nsvgImage; int parameterScaleSeen = 0; static const char *const fmtOptions[] = { "-dpi", "-scale", "-scaletoheight", "-scaletowidth", NULL @@ -967,16 +341,29 @@ ParseOptions( }; /* + * The parser destroys the original input string, + * therefore first duplicate. + */ + + inputCopy = (char *)attemptckalloc(length+1); + if (inputCopy == NULL) { + Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); + Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); + goto error; + } + memcpy(inputCopy, input, length); + inputCopy[length] = '\0'; + + /* * Process elements of format specification as a list. */ - optionsPtr->dpi = 96.0; - optionsPtr->scale = 1.0; - optionsPtr->scaleToHeight = 0; - optionsPtr->scaleToWidth = 0; + ropts->scale = 1.0; + ropts->scaleToHeight = 0; + ropts->scaleToWidth = 0; if ((formatObj != NULL) && Tcl_ListObjGetElements(interp, formatObj, &objc, &objv) != TCL_OK) { - return TCL_ERROR;; + goto error; } for (; objc > 0 ; objc--, objv++) { int optIndex; @@ -991,12 +378,14 @@ ParseOptions( if (Tcl_GetIndexFromObjStruct(interp, objv[0], fmtOptions, sizeof(char *), "option", 0, &optIndex) == TCL_ERROR) { - return TCL_ERROR;; + goto error; } if (objc < 2) { + ckfree(inputCopy); + inputCopy = NULL; Tcl_WrongNumArgs(interp, 1, objv, "value"); - return TCL_ERROR;; + goto error; } objc--; @@ -1014,7 +403,7 @@ ParseOptions( "only one of -scale, -scaletoheight, -scaletowidth may be given", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - return TCL_ERROR;; + goto error; } parameterScaleSeen = 1; break; @@ -1028,174 +417,72 @@ ParseOptions( switch ((enum fmtOptions) optIndex) { case OPT_DPI: if (Tcl_GetDoubleFromObj(interp, objv[0], &dpi) == TCL_ERROR) { - return TCL_ERROR;; + goto error; } if (dpi < 0.0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-dpi value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_DPI", NULL); - return TCL_ERROR;; + goto error; } - optionsPtr->dpi = (float)dpi; break; case OPT_SCALE: - if (Tcl_GetDoubleFromObj(interp, objv[0], - &(optionsPtr->scale)) == + if (Tcl_GetDoubleFromObj(interp, objv[0], &ropts->scale) == TCL_ERROR) { - return TCL_ERROR;; + goto error; } - if (optionsPtr->scale <= 0.0) { + if (ropts->scale <= 0.0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scale value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - return TCL_ERROR;; + goto error; } break; case OPT_SCALE_TO_HEIGHT: - if (Tcl_GetIntFromObj(interp, objv[0], - &(optionsPtr->scaleToHeight)) == TCL_ERROR) { - return TCL_ERROR;; + if (Tcl_GetIntFromObj(interp, objv[0], &ropts->scaleToHeight) == + TCL_ERROR) { + goto error; } - if (optionsPtr->scaleToHeight <= 0) { + if (ropts->scaleToHeight <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletoheight value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - return TCL_ERROR;; + goto error; } break; case OPT_SCALE_TO_WIDTH: - if (Tcl_GetIntFromObj(interp, objv[0], - &(optionsPtr->scaleToWidth)) == TCL_ERROR) { - return TCL_ERROR;; + if (Tcl_GetIntFromObj(interp, objv[0], &ropts->scaleToWidth) == + TCL_ERROR) { + goto error; } - if (optionsPtr->scaleToWidth <= 0) { + if (ropts->scaleToWidth <= 0) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "-scaletowidth value must be positive", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "BAD_SCALE", NULL); - return TCL_ERROR;; + goto error; } break; } } - - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * ParseSVG -- - * - * This function is called to parse the given input string as SVG. - * - * Results: - * Return a newly create NSVGimage on success, and NULL otherwise. - * - * Side effects: - * - *---------------------------------------------------------------------- - */ - -static NSVGimage * -ParseSVG( - Tcl_Interp *interp, - Tcl_Obj *dataObj, - float dpi) -{ - const char *input; - char *inputCopy = NULL; - NSVGimage *nsvgImage; - TkSizeT length; - - /* - * The parser destroys the original input string, - * therefore first duplicate. - */ - - input = TkGetStringFromObj(dataObj, &length); - inputCopy = (char *)attemptckalloc(length+1); - if (inputCopy == NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot alloc data buffer", -1)); - Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); - return NULL; - } - memcpy(inputCopy, input, length); - inputCopy[length] = '\0'; - - nsvgImage = nsvgParse(inputCopy, "px", dpi); + nsvgImage = nsvgParse(inputCopy, "px", (float) dpi); if (nsvgImage == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot parse SVG image", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "PARSE_ERROR", NULL); - ckfree(inputCopy); - return NULL; + goto error; } ckfree(inputCopy); return nsvgImage; -} - -/* - *---------------------------------------------------------------------- - * - * GetScaleFromParameters -- - * - * Get the scale value from the already parsed parameters -scale, - * -scaletoheight and -scaletowidth. - * - * The image width and height is also returned. - * - * Results: - * The evaluated or configured scale value, or 0.0 on failure - * - * Side effects: - * heightPtr and widthPtr are set to height and width of the image. - * - *---------------------------------------------------------------------- - */ - -static double -GetScaleFromParameters( - serializedHeader *serializedHeaderPtr, - optionsStruct * optionsPtr, - int *widthPtr, - int *heightPtr) -{ - double scale; - int width, height; - if ((serializedHeaderPtr->width == 0.0) || (serializedHeaderPtr->height == 0.0)) { - width = height = 0; - scale = 1.0; - } else if (optionsPtr->scaleToHeight > 0) { - /* - * Fixed height - */ - height = optionsPtr->scaleToHeight; - scale = height / serializedHeaderPtr->height; - width = (int) ceil(serializedHeaderPtr->width * scale); - } else if (optionsPtr->scaleToWidth > 0) { - /* - * Fixed width - */ - width = optionsPtr->scaleToWidth; - scale = width / serializedHeaderPtr->width; - height = (int) ceil(serializedHeaderPtr->height * scale); - } else { - /* - * Scale factor - */ - scale = optionsPtr->scale; - width = (int) ceil(serializedHeaderPtr->width * scale); - height = (int) ceil(serializedHeaderPtr->height * scale); +error: + if (inputCopy != NULL) { + ckfree(inputCopy); } - - *heightPtr = height; - *widthPtr = width; - return scale; + return NULL; } /* @@ -1221,11 +508,11 @@ static int RasterizeSVG( Tcl_Interp *interp, Tk_PhotoHandle imageHandle, - char *svgBlobPtr, - optionsStruct * optionsPtr, + NSVGimage *nsvgImage, int destX, int destY, int width, int height, - int srcX, int srcY) + int srcX, int srcY, + RastOpts *ropts) { int w, h, c; NSVGrasterizer *rast; @@ -1234,16 +521,15 @@ RasterizeSVG( double scale; (void)srcX; (void)srcY; - - scale = GetScaleFromParameters((serializedHeader *) svgBlobPtr, optionsPtr, - &w, &h); + + scale = GetScaleFromParameters(nsvgImage, ropts, &w, &h); rast = nsvgCreateRasterizer(); if (rast == NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj("cannot initialize rasterizer", -1)); Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "RASTERIZER_ERROR", NULL); - return TCL_ERROR; + goto cleanAST; } imgData = (unsigned char *)attemptckalloc(w * h *4); if (imgData == NULL) { @@ -1251,8 +537,7 @@ RasterizeSVG( Tcl_SetErrorCode(interp, "TK", "IMAGE", "SVG", "OUT_OF_MEMORY", NULL); goto cleanRAST; } - - nsvgRasterizeSerialized(rast, svgBlobPtr, 0, 0, + nsvgRasterize(rast, nsvgImage, 0, 0, (float) scale, imgData, w, h, w * 4); /* transfer the data to a photo block */ svgblock.pixelPtr = imgData; @@ -1273,6 +558,7 @@ RasterizeSVG( } ckfree(imgData); nsvgDeleteRasterizer(rast); + nsvgDelete(nsvgImage); return TCL_OK; cleanimg: @@ -1281,369 +567,244 @@ cleanimg: cleanRAST: nsvgDeleteRasterizer(rast); +cleanAST: + nsvgDelete(nsvgImage); return TCL_ERROR; } /* *---------------------------------------------------------------------- * - * Rasterize Serialized -- + * GetScaleFromParameters -- * - * Fiunctions of svgnrast.h which requires modification due to the - * serialized data structure. + * Get the scale value from the already parsed parameters -scale, + * -scaletoheight and -scaletowidth. * - * Results: + * The image width and height is also returned. * + * Results: + * The evaluated or configured scale value, or 0.0 on failure * * Side effects: + * heightPtr and widthPtr are set to height and width of the image. * *---------------------------------------------------------------------- */ -static void nsvg__flattenShapeSerialized(NSVGrasterizer* r, int pathIndex, - NSVGpathSerialized *pathSerializedPtr, float *ptsSerializedPtr, - float scale) +static double +GetScaleFromParameters( + NSVGimage *nsvgImage, + RastOpts *ropts, + int *widthPtr, + int *heightPtr) { - int i, j; - NSVGpathSerialized* path; + double scale; + int width, height; - for (; pathIndex != -1; pathIndex = pathSerializedPtr[pathIndex].next) { - path = &(pathSerializedPtr[pathIndex]); - r->npoints = 0; - // Flatten path - nsvg__addPathPoint(r, - ptsSerializedPtr[path->pts]*scale, - ptsSerializedPtr[path->pts+1]*scale, 0); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &ptsSerializedPtr[path->pts+i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale,p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, 0); - } - // Close path - nsvg__addPathPoint(r, ptsSerializedPtr[path->pts]*scale, - ptsSerializedPtr[path->pts+1]*scale, 0); - // Build edges - for (i = 0, j = r->npoints-1; i < r->npoints; j = i++) - nsvg__addEdge(r, r->points[j].x, r->points[j].y, r->points[i].x, r->points[i].y); + if ((nsvgImage->width == 0.0) || (nsvgImage->height == 0.0)) { + width = height = 0; + scale = 1.0; + } else if (ropts->scaleToHeight > 0) { + /* + * Fixed height + */ + height = ropts->scaleToHeight; + scale = height / nsvgImage->height; + width = (int) ceil(nsvgImage->width * scale); + } else if (ropts->scaleToWidth > 0) { + /* + * Fixed width + */ + width = ropts->scaleToWidth; + scale = width / nsvgImage->width; + height = (int) ceil(nsvgImage->height * scale); + } else { + /* + * Scale factor + */ + scale = ropts->scale; + width = (int) ceil(nsvgImage->width * scale); + height = (int) ceil(nsvgImage->height * scale); } -} - -static void nsvg__initPaintSerialized(NSVGcachedPaint* cache, - NSVGpaintSerialized* paint, float opacity, - NSVGgradientSerialized *gradientSerializedPtr, - NSVGgradientStop *gradientStopPtr) -{ - int i, j; - NSVGgradientSerialized* grad; - - cache->type = paint->type; - if (paint->type == NSVG_PAINT_COLOR) { - cache->colors[0] = nsvg__applyOpacity(paint->color, opacity); - return; - } + *heightPtr = height; + *widthPtr = width; + return scale; +} - grad = &(gradientSerializedPtr[paint->gradient]); +/* + *---------------------------------------------------------------------- + * + * GetCachePtr -- + * + * This function is called to get the per interpreter used + * svg image cache. + * + * Results: + * Return a pointer to the used cache. + * + * Side effects: + * Initialize the cache on the first call. + * + *---------------------------------------------------------------------- + */ - cache->spread = grad->spread; - memcpy(cache->xform, grad->xform, sizeof(float)*6); +static NSVGcache * +GetCachePtr( + Tcl_Interp *interp +) { + NSVGcache *cachePtr = (NSVGcache *)Tcl_GetAssocData(interp, "tksvgnano", NULL); + if (cachePtr == NULL) { + cachePtr = (NSVGcache *)ckalloc(sizeof(NSVGcache)); + cachePtr->dataOrChan = NULL; + Tcl_DStringInit(&cachePtr->formatString); + cachePtr->nsvgImage = NULL; + Tcl_SetAssocData(interp, "tksvgnano", FreeCache, cachePtr); + } + return cachePtr; +} - if (grad->nstops == 0) { - for (i = 0; i < 256; i++) - cache->colors[i] = 0; - } if (grad->nstops == 1) { - for (i = 0; i < 256; i++) - cache->colors[i] = nsvg__applyOpacity( - gradientStopPtr[grad->stops+i].color, opacity); - } else { - unsigned int ca, cb = 0; - float ua, ub, du, u; - int ia, ib, count; +/* + *---------------------------------------------------------------------- + * + * CacheSVG -- + * + * Add the given svg image informations to the cache for further usage. + * + * Results: + * Return 1 on success, and 0 otherwise. + * + * Side effects: + * + *---------------------------------------------------------------------- + */ - ca = nsvg__applyOpacity(gradientStopPtr[grad->stops].color, opacity); - ua = nsvg__clampf(gradientStopPtr[grad->stops].offset, 0, 1); - ub = nsvg__clampf(gradientStopPtr[grad->stops+grad->nstops-1].offset, - ua, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - for (i = 0; i < ia; i++) { - cache->colors[i] = ca; - } +static int +CacheSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + NSVGimage *nsvgImage, + RastOpts *ropts) +{ + TkSizeT length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); - for (i = 0; i < grad->nstops-1; i++) { - ca = nsvg__applyOpacity(gradientStopPtr[grad->stops+i].color, - opacity); - cb = nsvg__applyOpacity(gradientStopPtr[grad->stops+i+1].color, - opacity); - ua = nsvg__clampf(gradientStopPtr[grad->stops+i].offset, 0, 1); - ub = nsvg__clampf(gradientStopPtr[grad->stops+i+1].offset, 0, 1); - ia = (int)(ua * 255.0f); - ib = (int)(ub * 255.0f); - count = ib - ia; - if (count <= 0) continue; - u = 0; - du = 1.0f / (float)count; - for (j = 0; j < count; j++) { - cache->colors[ia+j] = nsvg__lerpRGBA(ca,cb,u); - u += du; - } + if (cachePtr != NULL) { + cachePtr->dataOrChan = dataOrChan; + if (formatObj != NULL) { + data = TkGetStringFromObj(formatObj, &length); + Tcl_DStringAppend(&cachePtr->formatString, data, length); } - - for (i = ib; i < 256; i++) - cache->colors[i] = cb; + cachePtr->nsvgImage = nsvgImage; + cachePtr->ropts = *ropts; + return 1; } - + return 0; } -static void nsvg__flattenShapeStrokeSerialized(NSVGrasterizer* r, - NSVGshapeSerialized* shape, NSVGpathSerialized *pathSerializedPtr, - float *ptsSerializedPtr, float scale) -{ - int i, j, closed, pathIndex; - NSVGpathSerialized* path; - NSVGpoint* p0, *p1; - float miterLimit = shape->miterLimit; - int lineJoin = shape->strokeLineJoin; - int lineCap = shape->strokeLineCap; - float lineWidth = shape->strokeWidth * scale; - - for (pathIndex = shape->paths; pathIndex != -1; - pathIndex = pathSerializedPtr[pathIndex].next) { - path = &(pathSerializedPtr[pathIndex]); - // Flatten path - r->npoints = 0; - nsvg__addPathPoint(r, ptsSerializedPtr[path->pts]*scale, - ptsSerializedPtr[path->pts+1]*scale, NSVG_PT_CORNER); - for (i = 0; i < path->npts-1; i += 3) { - float* p = &ptsSerializedPtr[path->pts+i*2]; - nsvg__flattenCubicBez(r, p[0]*scale,p[1]*scale, p[2]*scale, - p[3]*scale, p[4]*scale,p[5]*scale, p[6]*scale,p[7]*scale, 0, - NSVG_PT_CORNER); - } - if (r->npoints < 2) - continue; - - closed = path->closed; - - // If the first and last points are the same, remove the last, mark as closed path. - p0 = &r->points[r->npoints-1]; - p1 = &r->points[0]; - if (nsvg__ptEquals(p0->x,p0->y, p1->x,p1->y, r->distTol)) { - r->npoints--; - p0 = &r->points[r->npoints-1]; - closed = 1; - } - - if (shape->strokeDashCount > 0) { - int idash = 0, dashState = 1; - float totalDist = 0, dashLen, allDashLen, dashOffset; - NSVGpoint cur; - - if (closed) - nsvg__appendPathPoint(r, r->points[0]); - - // Duplicate points -> points2. - nsvg__duplicatePoints(r); - r->npoints = 0; - cur = r->points2[0]; - nsvg__appendPathPoint(r, cur); - - // Figure out dash offset. - allDashLen = 0; - for (j = 0; j < shape->strokeDashCount; j++) - allDashLen += shape->strokeDashArray[j]; - if (shape->strokeDashCount & 1) - allDashLen *= 2.0f; - // Find location inside pattern - dashOffset = fmodf(shape->strokeDashOffset, allDashLen); - if (dashOffset < 0.0f) - dashOffset += allDashLen; - - while (dashOffset > shape->strokeDashArray[idash]) { - dashOffset -= shape->strokeDashArray[idash]; - idash = (idash + 1) % shape->strokeDashCount; - } - dashLen = (shape->strokeDashArray[idash] - dashOffset) * scale; - - for (j = 1; j < r->npoints2; ) { - float dx = r->points2[j].x - cur.x; - float dy = r->points2[j].y - cur.y; - float dist = sqrtf(dx*dx + dy*dy); - - if ((totalDist + dist) > dashLen) { - // Calculate intermediate point - float d = (dashLen - totalDist) / dist; - float x = cur.x + dx * d; - float y = cur.y + dy * d; - nsvg__addPathPoint(r, x, y, NSVG_PT_CORNER); +/* + *---------------------------------------------------------------------- + * + * GetCachedSVG -- + * + * Try to get the NSVGimage from the internal cache. + * + * Results: + * Return the found NSVGimage on success, and NULL otherwise. + * + * Side effects: + * Calls the CleanCache() function. + * + *---------------------------------------------------------------------- + */ - // Stroke - if (r->npoints > 1 && dashState) { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, 0, - lineJoin, lineCap, lineWidth); - } - // Advance dash pattern - dashState = !dashState; - idash = (idash+1) % shape->strokeDashCount; - dashLen = shape->strokeDashArray[idash] * scale; - // Restart - cur.x = x; - cur.y = y; - cur.flags = NSVG_PT_CORNER; - totalDist = 0.0f; - r->npoints = 0; - nsvg__appendPathPoint(r, cur); - } else { - totalDist += dist; - cur = r->points2[j]; - nsvg__appendPathPoint(r, cur); - j++; - } +static NSVGimage * +GetCachedSVG( + Tcl_Interp *interp, + ClientData dataOrChan, + Tcl_Obj *formatObj, + RastOpts *ropts) +{ + TkSizeT length; + const char *data; + NSVGcache *cachePtr = GetCachePtr(interp); + NSVGimage *nsvgImage = NULL; + + if ((cachePtr != NULL) && (cachePtr->nsvgImage != NULL) && + (cachePtr->dataOrChan == dataOrChan)) { + if (formatObj != NULL) { + data = TkGetStringFromObj(formatObj, &length); + if (strcmp(data, Tcl_DStringValue(&cachePtr->formatString)) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; } - // Stroke any leftover path - if (r->npoints > 1 && dashState) - nsvg__expandStroke(r, r->points, r->npoints, 0, lineJoin, - lineCap, lineWidth); - } else { - nsvg__prepareStroke(r, miterLimit, lineJoin); - nsvg__expandStroke(r, r->points, r->npoints, closed, lineJoin, - lineCap, lineWidth); + } else if (Tcl_DStringLength(&cachePtr->formatString) == 0) { + nsvgImage = cachePtr->nsvgImage; + *ropts = cachePtr->ropts; + cachePtr->nsvgImage = NULL; } } + CleanCache(interp); + return nsvgImage; } - /* *---------------------------------------------------------------------- * - * RasterizeSVGSerialized -- + * CleanCache -- * - * This function is called to rasterize the given nsvgImage and - * fill the imageHandle with data. + * Reset the cache and delete the saved image in it. * * Results: - * A standard TCL completion code. If TCL_ERROR is returned then an error - * message is left in the interp's result. - * * * Side effects: - * On error the given nsvgImage will be deleted. * *---------------------------------------------------------------------- */ -static void nsvgRasterizeSerialized(NSVGrasterizer* r, - char *svgBlobPtr, float tx, float ty, float scale, - unsigned char* dst, int w, int h, int stride) +static void +CleanCache(Tcl_Interp *interp) { - NSVGshapeSerialized *shape = NULL; - NSVGedge *e = NULL; - NSVGcachedPaint cache; - int i, shapeIndex; - serializedHeader * serializedHeaderPtr; - NSVGshapeSerialized *shapeSerializedPtr; - NSVGpathSerialized *pathSerializedPtr; - float *ptsSerializedPtr; - NSVGgradientSerialized *gradientSerializedPtr; - NSVGgradientStop *gradientStopPtr; - - /* - * Prepare the array pointers of the data array placed after serializedHeader - */ - serializedHeaderPtr = (serializedHeader *) svgBlobPtr; - svgBlobPtr += sizeof (serializedHeader); - shapeSerializedPtr = (NSVGshapeSerialized *) svgBlobPtr; - svgBlobPtr += serializedHeaderPtr->shapeCount * sizeof(NSVGshapeSerialized); - pathSerializedPtr = (NSVGpathSerialized *) svgBlobPtr; - svgBlobPtr += serializedHeaderPtr->pathCount * sizeof(NSVGpathSerialized); - ptsSerializedPtr = (float *) svgBlobPtr; - svgBlobPtr += serializedHeaderPtr->ptsCount * sizeof(float); - gradientSerializedPtr = (NSVGgradientSerialized *) svgBlobPtr; - svgBlobPtr += serializedHeaderPtr->gradientCount * - sizeof(NSVGgradientSerialized); - gradientStopPtr = (NSVGgradientStop *) svgBlobPtr; - - r->bitmap = dst; - r->width = w; - r->height = h; - r->stride = stride; - - if (w > r->cscanline) { - r->cscanline = w; - r->scanline = (unsigned char*)NANOSVG_realloc(r->scanline, w); - if (r->scanline == NULL) return; - } - - for (i = 0; i < h; i++) - memset(&dst[i*stride], 0, w*4); - - for (shapeIndex = 0 ; shapeIndex < serializedHeaderPtr->shapeCount; - shapeIndex++) { - shape = &(shapeSerializedPtr[shapeIndex]); - if (!(shape->flags & NSVG_FLAGS_VISIBLE)) - continue; - - if (shape->fill.type != NSVG_PAINT_NONE) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShapeSerialized(r, shape->paths, pathSerializedPtr, ptsSerializedPtr, - scale); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } - - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); - - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaintSerialized(&cache, &shape->fill, shape->opacity, - gradientSerializedPtr, gradientStopPtr); - - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, shape->fillRule); + NSVGcache *cachePtr = GetCachePtr(interp); + + if (cachePtr != NULL) { + cachePtr->dataOrChan = NULL; + Tcl_DStringSetLength(&cachePtr->formatString, 0); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); + cachePtr->nsvgImage = NULL; } - if (shape->stroke.type != NSVG_PAINT_NONE && (shape->strokeWidth * scale) > 0.01f) { - nsvg__resetPool(r); - r->freelist = NULL; - r->nedges = 0; - - nsvg__flattenShapeStrokeSerialized(r, shape, pathSerializedPtr, - ptsSerializedPtr, scale); - - // dumpEdges(r, "edge.svg"); - - // Scale and translate edges - for (i = 0; i < r->nedges; i++) { - e = &r->edges[i]; - e->x0 = tx + e->x0; - e->y0 = (ty + e->y0) * NSVG__SUBSAMPLES; - e->x1 = tx + e->x1; - e->y1 = (ty + e->y1) * NSVG__SUBSAMPLES; - } + } +} - // Rasterize edges - qsort(r->edges, r->nedges, sizeof(NSVGedge), nsvg__cmpEdge); +/* + *---------------------------------------------------------------------- + * + * FreeCache -- + * + * This function is called to clean up the internal cache data. + * + * Results: + * + * Side effects: + * Existing image data in the cache and the cache will be deleted. + * + *---------------------------------------------------------------------- + */ - // now, traverse the scanlines and find the intersections on each scanline, use non-zero rule - nsvg__initPaintSerialized(&cache, &shape->stroke, shape->opacity, - gradientSerializedPtr, gradientStopPtr); +static void +FreeCache(ClientData clientData, Tcl_Interp *interp) +{ + NSVGcache *cachePtr = (NSVGcache *)clientData; + (void)interp; - nsvg__rasterizeSortedEdges(r, tx,ty,scale, &cache, NSVG_FILLRULE_NONZERO); - } + Tcl_DStringFree(&cachePtr->formatString); + if (cachePtr->nsvgImage != NULL) { + nsvgDelete(cachePtr->nsvgImage); } - - nsvg__unpremultiplyAlpha(dst, w, h, stride); - - r->bitmap = NULL; - r->width = 0; - r->height = 0; - r->stride = 0; + ckfree(cachePtr); } diff --git a/generic/tkInt.h b/generic/tkInt.h index ae25937..c3fe72f 100644 --- a/generic/tkInt.h +++ b/generic/tkInt.h @@ -1061,7 +1061,7 @@ MODULE_SCOPE void (*tkHandleEventProc) (XEvent* eventPtr); MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtDefault; MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtPNG; MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtPPM; -MODULE_SCOPE Tk_PhotoImageFormatVersion3 tkImgFmtSVGnano; +MODULE_SCOPE Tk_PhotoImageFormat tkImgFmtSVGnano; MODULE_SCOPE TkMainInfo *tkMainWindowList; MODULE_SCOPE Tk_ImageType tkPhotoImageType; MODULE_SCOPE Tcl_HashTable tkPredefBitmapTable; diff --git a/generic/tkWindow.c b/generic/tkWindow.c index 2adb0fa..aaa4a06 100644 --- a/generic/tkWindow.c +++ b/generic/tkWindow.c @@ -340,7 +340,7 @@ CreateTopLevelWindow( Tk_CreatePhotoImageFormatVersion3(&tkImgFmtGIF); Tk_CreatePhotoImageFormatVersion3(&tkImgFmtPNG); Tk_CreatePhotoImageFormat(&tkImgFmtPPM); - Tk_CreatePhotoImageFormatVersion3(&tkImgFmtSVGnano); + Tk_CreatePhotoImageFormat(&tkImgFmtSVGnano); } if ((parent != NULL) && (screenName != NULL) && (screenName[0] == '\0')) { diff --git a/tests/imgSVGnano.test b/tests/imgSVGnano.test index f34316b..ff7046a 100644 --- a/tests/imgSVGnano.test +++ b/tests/imgSVGnano.test @@ -23,36 +23,10 @@ namespace eval svgnano { } - set data(bad) { } - set data(gradient) { - - - - - - - -} - tcltest::makeFile $data(plus) plus.svg set data(plusFilePath) [file join [tcltest::configure -tmpdir] plus.svg] @@ -233,96 +207,6 @@ test imgSVGnano-4.2 {error on file not accessible on reread due to configure} -s tcltest::removeFile tmpplus.svg } -returnCodes error -match glob -result {couldn't open "*/tmpplus.svg": no such file or directory} -# rendering tests - -test imgSVGnano-5.0 {data gradient rendering} -setup { - catch {rename foo ""} - catch {rename gif ""} - image create photo gif\ - -data "R0lGODlhCgAKALMAANnZ2f/XAP/LAP+yAP+zAP+bAP+DAP9rAP9TAP9UAP88AP8kAP8MAP///////////yH5BAEAAAAALAAAAAAKAAoAAAQhMMhJhb14DMI7L2AoGmRpHmiqImyLJLAiz/Ri3zij73wEADs="\ - -format gif -} -body { - image create photo foo -data $data(gradient) - set res "" - for {set y 0} {$y < [image height foo]} {incr y} { - for {set x 0} {$x < [image width foo]} {incr x} { - lassign [foo get $x $y] r1 g1 b1 - lassign [gif get $x $y] r2 g2 b2 - if {$r1 != $r2 || $g1 != $g2 || $b1 != $b2} { - append res "pixel $x,$y unequal: $r1 $g1 $b1 != $r2 $g2 $b2\n" - } - } - } - set res -} -cleanup { - rename foo "" - rename gif "" -} -result {} - -test imgSVGnano-5.1 {file gradient rendering} -setup { - catch {rename foo ""} - tcltest::makeFile $data(gradient) tmpgradient.svg - catch {rename gif ""} - image create photo gif\ - -data "R0lGODlhCgAKALMAANnZ2f/XAP/LAP+yAP+zAP+bAP+DAP9rAP9TAP9UAP88AP8kAP8MAP///////////yH5BAEAAAAALAAAAAAKAAoAAAQhMMhJhb14DMI7L2AoGmRpHmiqImyLJLAiz/Ri3zij73wEADs="\ - -format gif -} -body { - image create photo foo -file [file join [tcltest::configure -tmpdir] tmpgradient.svg] - set res "" - for {set y 0} {$y < [image height foo]} {incr y} { - for {set x 0} {$x < [image width foo]} {incr x} { - lassign [foo get $x $y] r1 g1 b1 - lassign [gif get $x $y] r2 g2 b2 - if {$r1 != $r2 || $g1 != $g2 || $b1 != $b2} { - append res "pixel $x,$y unequal: $r1 $g1 $b1 != $r2 $g2 $b2\n" - } - } - } - set res -} -cleanup { - rename foo "" - tcltest::removeFile tmpgradient.svg - rename gif "" -} -result {} - -# metadata tests - -test imgSVGnano-6.0 {svgblob metadata creation} -setup { - catch {rename foo ""} - tcltest::makeFile $data(plus) tmpplus.svg -} -body { - image create photo foo -file [file join [tcltest::configure -tmpdir] tmpplus.svg] - dict keys [foo cget -metadata] -} -cleanup { - rename foo "" - tcltest::removeFile tmpplus.svg -} -result {SVGBLOB} - -test imgSVGnano-6.1 {scale with svgblob metadata (data)} -setup { - catch {rename foo ""} -} -body { - image create photo foo -data $data(plus) - foo configure -data "" -format "svg" - foo configure -format "svg -scale 2" - lappend res [image width foo] [image height foo] -} -cleanup { - rename foo "" - tcltest::removeFile tmpplus.svg -} -result {200 200} - -test imgSVGnano-6.2 {scale with svgblob metadata} -setup { - catch {rename foo ""} - tcltest::makeFile $data(plus) tmpplus.svg -} -body { - image create photo foo -file [file join [tcltest::configure -tmpdir] tmpplus.svg] - foo configure -file "" -data "" - foo configure -format "svg -scale 2" - list [image width foo] [image height foo] -} -cleanup { - rename foo "" - tcltest::removeFile tmpplus.svg -} -result {200 200} - };# end of namespace svgnano namespace delete svgnano -- cgit v0.12 From 30bbcbc89a0aa5de07fd8a097939d9b31037d8ef Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 24 Jun 2020 15:23:24 +0000 Subject: TIP529 image metadata: remove close file flag returned by format driver file match function --- generic/tk.h | 3 +-- generic/tkImgGIF.c | 3 +-- generic/tkImgPNG.c | 3 +-- generic/tkImgPhoto.c | 29 ++++++----------------------- 4 files changed, 9 insertions(+), 29 deletions(-) diff --git a/generic/tk.h b/generic/tk.h index e8bcae4..d25fe1b 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1445,8 +1445,7 @@ typedef struct Tk_PhotoImageFormatVersion3 Tk_PhotoImageFormatVersion3; typedef int (Tk_ImageFileMatchProcVersion3) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - Tcl_Obj *metadataOut, int *closeChannelPtr, - Tcl_DString *driverInternalPtr); + Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); typedef int (Tk_ImageStringMatchProcVersion3) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 06a44ac..73cb4ce 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -122,7 +122,7 @@ static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, Tcl_Obj *metadataOutObj, - int *closeChannelPtr, Tcl_DString *driverInternal); + Tcl_DString *driverInternal); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, @@ -369,7 +369,6 @@ FileMatchGIF( /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - int *closeChannelPtr, /* Return if the channel may be closed */ Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { GIFImageConfig gifConf; diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 91eb393..16c3ec8 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -213,7 +213,7 @@ static int FileMatchPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, Tcl_Obj *metadataOut, - int *closeChannelPtr, Tcl_DString *driverInternal); + Tcl_DString *driverInternal); static int FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, @@ -2828,7 +2828,6 @@ FileMatchPNG( /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - int *closeChannelPtr, /* Return if the channel may be closed */ Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ { PNGImage png; diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index f41cadd..dc067a1 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -207,7 +207,6 @@ static int MatchFileFormat(Tcl_Interp *interp, Tcl_Channel chan, Tk_PhotoImageFormat **imageFormatPtr, Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, int *widthPtr, int *heightPtr, int *oldformat, - int *closeChannelPtr, Tcl_DString *driverInternalPtr); static int MatchStringFormat(Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *formatString, @@ -1079,7 +1078,6 @@ putCleanup: case PHOTO_READ: { Tcl_Obj *format; int result; - int closeChannel = 0; Tcl_DString driverInternalDString; /* @@ -1137,22 +1135,16 @@ putCleanup: */ Tcl_DStringInit(&driverInternalDString); - closeChannel = 0; if (MatchFileFormat(interp, chan, Tcl_GetString(options.name), options.format, options.metadata, NULL, &imageFormat, &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, - &closeChannel, &driverInternalDString) + &driverInternalDString) != TCL_OK) { result = TCL_ERROR; goto readCleanup; } - if (closeChannel) { - Tcl_Close(NULL, chan); - chan = NULL; - } - /* * Check the values given for the -from option. */ @@ -1999,7 +1991,6 @@ ImgPhotoConfigureMaster( Tk_PhotoImageFormatVersion3 *imageFormatVersion3; const char **args; Tcl_DString driverInternalDString; - int closeChannel; args = (const char **)ckalloc((objc + 1) * sizeof(char *)); for (i = 0, j = 0; i < objc; i++,j++) { @@ -2197,8 +2188,6 @@ ImgPhotoConfigureMaster( goto errorExit; } - closeChannel = 0; - /* * Flag that we want the metadata result dict */ @@ -2215,15 +2204,11 @@ ImgPhotoConfigureMaster( (MatchFileFormat(interp, chan, masterPtr->fileString, masterPtr->format, masterPtr->metadata, metadataOutObj, &imageFormat, &imageFormatVersion3, - &imageWidth, &imageHeight, &oldformat, &closeChannel, + &imageWidth, &imageHeight, &oldformat, &driverInternalDString) != TCL_OK)) { Tcl_Close(NULL, chan); goto errorExit; } - if (closeChannel) { - Tcl_Close(NULL, chan); - chan = NULL; - } result = ImgPhotoSetSize(masterPtr, imageWidth, imageHeight); if (result != TCL_OK) { Tcl_Close(NULL, chan); @@ -2777,11 +2762,11 @@ MatchFileFormat( /* The dimensions of the image are returned * here. */ int *oldformat, /* Returns 1 if the old image API is used. */ - int *closeChannelPtr, /* Is set to 1 if channel can be closed */ Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding * ReadFileFormat function */ { - int matched = 0, useoldformat = 0; + int matched = 0; + int useoldformat = 0; Tk_PhotoImageFormat *formatPtr; Tk_PhotoImageFormatVersion3 *formatVersion3Ptr; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) @@ -2907,7 +2892,7 @@ if (formatPtr == NULL) { if (formatVersion3Ptr->fileMatchProc(interp, chan, fileName, formatObj, metadataInObj, widthPtr, heightPtr, - metadataOutObj, closeChannelPtr, driverInternalPtr)) { + metadataOutObj, driverInternalPtr)) { if (*widthPtr < 1) { *widthPtr = 1; } @@ -2917,9 +2902,7 @@ if (formatPtr == NULL) { *imageFormatVersion3Ptr = formatVersion3Ptr; *imageFormatPtr = NULL; *oldformat = 0; - if (! *closeChannelPtr ) { - (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); - } + (void) Tcl_Seek(chan, Tcl_LongAsWide(0L), SEEK_SET); return TCL_OK; } -- cgit v0.12 From e2af510d008d17dec556410202d79c52f05b6e80 Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 24 Jun 2020 15:42:43 +0000 Subject: TIP529 image metadata: remove optional feature: driver internal DString communication between match and read function --- generic/tk.h | 9 +++--- generic/tkImgGIF.c | 26 ++++++---------- generic/tkImgPNG.c | 24 +++++--------- generic/tkImgPhoto.c | 88 ++++++++++++++-------------------------------------- 4 files changed, 44 insertions(+), 103 deletions(-) diff --git a/generic/tk.h b/generic/tk.h index d25fe1b..910c22a 100644 --- a/generic/tk.h +++ b/generic/tk.h @@ -1445,21 +1445,20 @@ typedef struct Tk_PhotoImageFormatVersion3 Tk_PhotoImageFormatVersion3; typedef int (Tk_ImageFileMatchProcVersion3) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, int *heightPtr, - Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); + Tcl_Obj *metadataOut); typedef int (Tk_ImageStringMatchProcVersion3) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); + int *heightPtr, Tcl_Obj *metadataOut); typedef int (Tk_ImageFileReadProcVersion3) (Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, int srcX, int srcY, - Tcl_Obj *metadataOut, Tcl_DString *driverInternalPtr); + Tcl_Obj *metadataOut); typedef int (Tk_ImageStringReadProcVersion3) (Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj *metadataOut, - Tcl_DString *driverInternalPtr); + int srcX, int srcY, Tcl_Obj *metadataOut); typedef int (Tk_ImageFileWriteProcVersion3) (Tcl_Interp *interp, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataIn, Tk_PhotoImageBlock *blockPtr); diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 73cb4ce..23e02f7 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -121,25 +121,21 @@ typedef size_t (WriteBytesFunc) (ClientData clientData, const char *bytes, static int FileMatchGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + int *heightPtr, Tcl_Obj *metadataOutObj); static int FileReadGIF(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutObj); static int StringMatchGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, - Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + Tcl_Obj *metadataOutObj); static int StringReadGIF(Tcl_Interp *interp, Tcl_Obj *dataObj, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutObj); static int FileWriteGIF(Tcl_Interp *interp, const char *filename, Tcl_Obj *format, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); @@ -368,8 +364,7 @@ FileMatchGIF( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { GIFImageConfig gifConf; (void)fileName; @@ -413,8 +408,7 @@ FileReadGIF( * written to. */ int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed from FileMatchGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { int fileWidth, fileHeight, imageWidth, imageHeight; unsigned int nBytes; @@ -858,8 +852,7 @@ StringMatchGIF( Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { unsigned char *data, header[10]; TkSizeT got, length; @@ -932,8 +925,7 @@ StringReadGIF( int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ int srcX, int srcY, - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed from StringReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { MFile handle, *hdlPtr = &handle; TkSizeT length; @@ -963,7 +955,7 @@ StringReadGIF( return FileReadGIF(interp, (Tcl_Channel) hdlPtr, xferFormat, format, metadataInObj, imageHandle, destX, destY, width, height, srcX, srcY, - metadataOutObj, driverInternal); + metadataOutObj); } /* diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 16c3ec8..1ead8c0 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -212,14 +212,12 @@ static int EncodePNG(Tcl_Interp *interp, static int FileMatchPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, int *widthPtr, - int *heightPtr, Tcl_Obj *metadataOut, - Tcl_DString *driverInternal); + int *heightPtr, Tcl_Obj *metadataOut); static int FileReadPNG(Tcl_Interp *interp, Tcl_Channel chan, const char *fileName, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj *metadataOutPtr, - Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutPtr); static int FileWritePNG(Tcl_Interp *interp, const char *filename, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr); @@ -254,14 +252,12 @@ static int SkipChunk(Tcl_Interp *interp, PNGImage *pngPtr, static int StringMatchPNG(Tcl_Interp *interp, Tcl_Obj *pObjData, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, int *widthPtr, int *heightPtr, - Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + Tcl_Obj *metadataOutObj); static int StringReadPNG(Tcl_Interp *interp, Tcl_Obj *pObjData, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, Tk_PhotoHandle imageHandle, int destX, int destY, int width, int height, - int srcX, int srcY, Tcl_Obj *metadataOutObj, - Tcl_DString *driverInternal); + int srcX, int srcY, Tcl_Obj *metadataOutObj); static int StringWritePNG(Tcl_Interp *interp, Tcl_Obj *fmtObj, Tcl_Obj *metadataInObj, @@ -2827,8 +2823,7 @@ FileMatchPNG( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed to FileReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int match = 0; @@ -2881,8 +2876,7 @@ FileReadPNG( * written to. */ int srcX, int srcY, /* Coordinates of top-left pixel to be used in * image being read. */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed from FileMatchGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int result = TCL_ERROR; @@ -2940,8 +2934,7 @@ StringMatchPNG( Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory to pass to StringReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int match = 0; @@ -2989,8 +2982,7 @@ StringReadPNG( int destX, int destY, /* The rectangular region of the */ int width, int height, /* image to copy */ int srcX, int srcY, - Tcl_Obj *metadataOutObj, /* metadata return dict, may be NULL */ - Tcl_DString *driverInternal)/* memory passed from StringReadGIF */ + Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int result = TCL_ERROR; diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index dc067a1..92e4670 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -206,16 +206,14 @@ static int MatchFileFormat(Tcl_Interp *interp, Tcl_Channel chan, Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, - int *widthPtr, int *heightPtr, int *oldformat, - Tcl_DString *driverInternalPtr); + int *widthPtr, int *heightPtr, int *oldformat); static int MatchStringFormat(Tcl_Interp *interp, Tcl_Obj *data, Tcl_Obj *formatString, Tcl_Obj *metadataInObj, Tcl_Obj *metadataOutObj, Tk_PhotoImageFormat **imageFormatPtr, Tk_PhotoImageFormatVersion3 **imageFormatVersion3Ptr, - int *widthPtr, int *heightPtr, int *oldformat, - Tcl_DString *driverInternalPtr); + int *widthPtr, int *heightPtr, int *oldformat); static const char * GetExtension(const char *path); /* @@ -980,9 +978,8 @@ ImgPhotoCmd( } case PHOTO_PUT: { - int result; Tcl_Obj *format, *data; - Tcl_DString driverInternalDString; + /* * photo put command - first parse the options. */ @@ -1001,12 +998,6 @@ ImgPhotoCmd( Tcl_WrongNumArgs(interp, 2, objv, "data ?-option value ...?"); return TCL_ERROR; } - - /* - * Prepare memory connection between format match and read function - */ - - Tcl_DStringInit(&driverInternalDString); /* * See if there's a format that can read the data @@ -1014,11 +1005,9 @@ ImgPhotoCmd( if (MatchStringFormat(interp, objv[2], options.format, options.metadata, NULL, &imageFormat, - &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, - &driverInternalDString) + &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat) != TCL_OK) { - result = TCL_ERROR; - goto putCleanup; + return TCL_ERROR; } if (!(options.options & OPT_TO) || (options.toX2 < 0)) { @@ -1045,8 +1034,7 @@ ImgPhotoCmd( (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, options.toY2 - options.toY, 0, 0) != TCL_OK) { - result = TCL_ERROR; - goto putCleanup; + return TCL_ERROR; } } else { if (imageFormatVersion3->stringReadProc(interp, data, format, @@ -1054,10 +1042,9 @@ ImgPhotoCmd( (Tk_PhotoHandle) masterPtr, options.toX, options.toY, options.toX2 - options.toX, options.toY2 - options.toY, 0, 0, - NULL, &driverInternalDString) + NULL) != TCL_OK) { - result = TCL_ERROR; - goto putCleanup; + return TCL_ERROR; } } @@ -1069,16 +1056,11 @@ ImgPhotoCmd( */ masterPtr->flags |= IMAGE_CHANGED; - result = TCL_OK; -putCleanup: - Tcl_DStringFree(&driverInternalDString); - return result; - + return TCL_OK; } case PHOTO_READ: { Tcl_Obj *format; int result; - Tcl_DString driverInternalDString; /* * photo read command - first parse the options specified. @@ -1130,21 +1112,15 @@ putCleanup: return TCL_ERROR; } - /* - * Prepare memory connection between format match and read function - */ - - Tcl_DStringInit(&driverInternalDString); if (MatchFileFormat(interp, chan, Tcl_GetString(options.name), options.format, options.metadata, NULL, &imageFormat, - &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat, - &driverInternalDString) + &imageFormatVersion3, &imageWidth, &imageHeight, &oldformat) != TCL_OK) { result = TCL_ERROR; goto readCleanup; } - + /* * Check the values given for the -from option. */ @@ -1202,10 +1178,9 @@ putCleanup: Tcl_GetString(options.name), format, options.metadata, (Tk_PhotoHandle) masterPtr, options.toX, options.toY, width, height, options.fromX, - options.fromY, NULL, &driverInternalDString); + options.fromY, NULL); } readCleanup: - Tcl_DStringFree(&driverInternalDString); if (chan != NULL) { Tcl_Close(NULL, chan); } @@ -1990,7 +1965,6 @@ ImgPhotoConfigureMaster( Tk_PhotoImageFormat *imageFormat; Tk_PhotoImageFormatVersion3 *imageFormatVersion3; const char **args; - Tcl_DString driverInternalDString; args = (const char **)ckalloc((objc + 1) * sizeof(char *)); for (i = 0, j = 0; i < objc; i++,j++) { @@ -2040,12 +2014,6 @@ ImgPhotoConfigureMaster( } /* - * Prepare memory connection between format match and read function - */ - - Tcl_DStringInit(&driverInternalDString); - - /* * Save the current values for fileString and dataString, so we can tell * if the user specifies them anew. IMPORTANT: if the format changes we * have to interpret "-file" and "-data" again as well! It might be that @@ -2204,8 +2172,7 @@ ImgPhotoConfigureMaster( (MatchFileFormat(interp, chan, masterPtr->fileString, masterPtr->format, masterPtr->metadata, metadataOutObj, &imageFormat, &imageFormatVersion3, - &imageWidth, &imageHeight, &oldformat, - &driverInternalDString) != TCL_OK)) { + &imageWidth, &imageHeight, &oldformat) != TCL_OK)) { Tcl_Close(NULL, chan); goto errorExit; } @@ -2231,11 +2198,10 @@ ImgPhotoConfigureMaster( masterPtr->fileString, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, imageWidth, imageHeight, 0, 0, - metadataOutObj, &driverInternalDString); - } - if (chan != NULL) { - Tcl_Close(NULL, chan); + metadataOutObj); } + + Tcl_Close(NULL, chan); if (result != TCL_OK) { goto errorExit; } @@ -2258,7 +2224,7 @@ ImgPhotoConfigureMaster( if (MatchStringFormat(interp, masterPtr->dataString, masterPtr->format, masterPtr->metadata, metadataOutObj, &imageFormat, &imageFormatVersion3, &imageWidth, - &imageHeight, &oldformat, &driverInternalDString) != TCL_OK) { + &imageHeight, &oldformat) != TCL_OK) { goto errorExit; } if (ImgPhotoSetSize(masterPtr, imageWidth, imageHeight) != TCL_OK) { @@ -2285,8 +2251,7 @@ ImgPhotoConfigureMaster( if (imageFormatVersion3->stringReadProc(interp, tempdata, tempformat, masterPtr->metadata, (Tk_PhotoHandle) masterPtr, 0, 0, - imageWidth, imageHeight, 0, 0, metadataOutObj, - &driverInternalDString) != TCL_OK) { + imageWidth, imageHeight, 0, 0, metadataOutObj) != TCL_OK) { goto errorExit; } } @@ -2337,7 +2302,7 @@ ImgPhotoConfigureMaster( } } } - + /* * Enforce a reasonable value for gamma. */ @@ -2370,7 +2335,6 @@ ImgPhotoConfigureMaster( masterPtr->height, masterPtr->width, masterPtr->height); masterPtr->flags &= ~IMAGE_CHANGED; - Tcl_DStringInit(&driverInternalDString); if (oldData != NULL) { Tcl_DecrRefCount(oldData); } @@ -2386,7 +2350,6 @@ ImgPhotoConfigureMaster( return TCL_OK; errorExit: - Tcl_DStringInit(&driverInternalDString); if (oldData != NULL) { Tcl_DecrRefCount(oldData); } @@ -2761,9 +2724,7 @@ MatchFileFormat( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ - int *oldformat, /* Returns 1 if the old image API is used. */ - Tcl_DString *driverInternalPtr)/* Memory to be passed to the corresponding - * ReadFileFormat function */ + int *oldformat) /* Returns 1 if the old image API is used. */ { int matched = 0; int useoldformat = 0; @@ -2892,7 +2853,7 @@ if (formatPtr == NULL) { if (formatVersion3Ptr->fileMatchProc(interp, chan, fileName, formatObj, metadataInObj, widthPtr, heightPtr, - metadataOutObj, driverInternalPtr)) { + metadataOutObj)) { if (*widthPtr < 1) { *widthPtr = 1; } @@ -2985,10 +2946,7 @@ MatchStringFormat( int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here. */ - int *oldformat, /* Returns 1 if the old image API is used. */ - Tcl_DString *driverInternalPtr) - /* Memory to be passed to the corresponding - * ReadFileFormat function */ + int *oldformat) /* Returns 1 if the old image API is used. */ { int matched = 0, useoldformat = 0; Tk_PhotoImageFormat *formatPtr, *defaultFormatPtr = NULL; @@ -3108,7 +3066,7 @@ MatchStringFormat( && (formatVersion3Ptr->stringReadProc != NULL) && formatVersion3Ptr->stringMatchProc(interp, data, formatObj, metadataInObj, widthPtr, heightPtr, - metadataOutObj, driverInternalPtr)) { + metadataOutObj)) { break; } -- cgit v0.12 From 6a10411bfb5de2631d2457a85d4b6995df87d3e3 Mon Sep 17 00:00:00 2001 From: oehhar Date: Wed, 24 Jun 2020 16:01:49 +0000 Subject: TIP529 image metadata: remove optional feature "gif XMP metadata support". --- generic/tkImgGIF.c | 129 +---------------------------- tests/imgPhoto.test | 228 ++++------------------------------------------------ 2 files changed, 16 insertions(+), 341 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 23e02f7..7dc78d8 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -1049,8 +1049,6 @@ ReadColorMap( * The transparent color is set if present in current extensions * The data of the following extensions are saved to the metadata dict: * - Application extension -* - XMP data is stored in key "XMP" -* - any other under the key Application_ * - Comment extension in key "comment" * Plain text extensions are currently ignored. * @@ -1068,9 +1066,9 @@ DoExtension( { int count; /* Prepare extension name - * Maximum string size: "Application_"(12) + App(8) + Code(3) + trailing zero + * Maximum string size: "comment" + Code(3) + trailing zero */ - char extensionStreamName[24]; + char extensionStreamName[8]; extensionStreamName[0] = '\0'; switch (label) { @@ -1090,84 +1088,6 @@ DoExtension( strcpy(extensionStreamName,"comment"); /* copy the extension data below */ break; - case 0xff: /* Application Extension */ - /* Length: 11 - * Application Identifier: 8 bytes - * Application Authentication code: 3 Bytes - */ - count = GetDataBlock(gifConfPtr, chan, buf); - if (count != 11) { - return -1; - } - /* Detect XMP extension */ - if (NULL != metadataOutObj - && 0 == memcmp(buf,"XMP DataXMP",11)) { - /* XMP format does not use the block structure of GIF - * The data is utf-8 which never contains 0's - * A magic trailer of 258 bytes is added with the following data: - * 0x01 0xff 0xfe ... 0x01 0x00 0x00 - */ - Tcl_Encoding encoding; - Tcl_DString recodedDString; - Tcl_DString dataDString; - int length; - int result; - unsigned char lastbyte = 1; - Tcl_DStringInit(&dataDString); - - for (;;) { - unsigned char byte; - if (1 != Fread(gifConfPtr, &byte, 1, 1, chan)) { - /* read error */ - Tcl_DStringFree(&dataDString); - return -1; - } - Tcl_DStringAppend(&dataDString,(char *)&byte,1); - /* check for end of xmp header */ - if (byte == 0 && lastbyte == 0) { - break; - } - lastbyte = byte; - } - - /* check if trailer of 258 bytes is present */ - length = Tcl_DStringLength(&dataDString); - if (length < 258) { - Tcl_DStringFree(&dataDString); - return -1; - } - /* Remove the trailer from the data */ - length -= 258; - /* save the utf-8 data in the metadata dict key "XMP" */ - encoding = Tcl_GetEncoding(NULL, "utf-8"); - Tcl_DStringInit(&recodedDString); - Tcl_ExternalToUtfDString(encoding, Tcl_DStringValue(&dataDString), length, &recodedDString); - result = Tcl_DictObjPut(NULL, metadataOutObj, - Tcl_NewStringObj("XMP",-1), - Tcl_NewStringObj(Tcl_DStringValue(&recodedDString), - Tcl_DStringLength(&recodedDString))); - Tcl_DStringFree(&recodedDString); - Tcl_DStringFree(&dataDString); - Tcl_FreeEncoding(encoding); - if ( TCL_OK != result ) { - return -1; - } - return 0; - } else { - /* - * Other extension - * Name the extension: Application_xxxxxxxxxxx - * 012345678901234567890123 - */ - /* Untested code commented out, no use case - */ - /* - strcpy(extensionStreamName,"Application_"); - memcpy(extensionStreamName+12,buf,11); - extensionStreamName[23]='\0'; - */ - } - break; } /* Add extension to dict */ if (NULL != metadataOutObj @@ -2122,51 +2042,6 @@ CommonWriteGIF( writeProc(handle, (char *) &c, 1); } } - - /* - * Check and code XMP block - */ - - if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, - Tcl_NewStringObj("XMP",-1), - &itemData)) { - return TCL_ERROR; - } - if (itemData != NULL) { - Tcl_Encoding encoding; - Tcl_DString recodedDString; - char * itemString; - int itemLength; - int trailerChar; - - /* write header */ - writeProc(handle, "\x21\xff\x0bXMP DataXMP", 14); - - /* write utf-8 coded data */ - encoding = Tcl_GetEncoding(NULL, "utf-8"); - Tcl_DStringInit(&recodedDString); - itemString = Tcl_GetStringFromObj(itemData, &itemLength); - Tcl_UtfToExternalDString(encoding, itemString, itemLength, - &recodedDString); - writeProc(handle, Tcl_DStringValue(&recodedDString), - Tcl_DStringLength(&recodedDString)); - Tcl_DStringFree(&recodedDString); - Tcl_FreeEncoding(encoding); - - /* XMP format does not use the block structure of GIF - * The data is utf-8 which never contains 0's - * A magic trailer of 258 bytes is added with the following data: - * 0x01 0xff 0xfe ... 0x01 0x00 0x00 - */ - c = 1; - writeProc(handle, (char *) &c, 1); - for (trailerChar = 0xff; trailerChar >= 0; trailerChar--) { - c = (unsigned char)trailerChar; - writeProc(handle, (char *) &c, 1); - } - c = 0; - writeProc(handle, (char *) &c, 1); - } } c = GIF_TERMINATOR; writeProc(handle, (char *) &c, 1); diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 5f71b9a..f0cd730 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -2249,170 +2249,7 @@ test imgPhoto-23.6 {Two GIF comment blocks (-file)} -setup { file delete $path } -result {comment ABCD} -test imgPhoto-23.7 {XMP comment block before image (-data)} -setup { - set data $::gifstart - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - append data $::gifdata - # Trailer - append data $::gifend -} -body { - image create photo gif1 -data $data - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} -} -cleanup { - catch {image delete gif1} -} -result {1} - -test imgPhoto-23.8 {XMP comment block before image (-file)} -setup { - set data $::gifstart - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - append data $::gifdata - # Trailer - append data $::gifend - set path [file join [configure -tmpdir] test.gif] - set h [open $path "WRONLY BINARY CREAT"] - puts $h $data - close $h -} -body { - image create photo gif1 -file $path - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} -} -cleanup { - catch {image delete gif1} - file delete $path -} -result {1} - -test imgPhoto-23.9 {XMP comment block after image (-data)} -setup { - set data $::gifstart - append data $::gifdata - - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - # Trailer - append data $::gifend -} -body { - image create photo gif1 -data $data - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} -} -cleanup { - catch {image delete gif1} -} -result {1} - -test imgPhoto-23.10 {XMP comment block after image (-file)} -setup { - set data $::gifstart - append data $::gifdata - - # Append an XMP comment extension block (including a Unicode codepoint 2022 - set xmpdata "\ - \ - 3" - append data "\x21\xff\x0B" "XMP DataXMP" [encoding convertto utf-8 $xmpdata] - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - - # Trailer - append data $::gifend - - set path [file join [configure -tmpdir] test.gif] - set h [open $path "WRONLY BINARY CREAT"] - puts $h $data - close $h -} -body { - image create photo gif1 -file $path - set d [dict get [gif1 cget -metadata] XMP] - expr {$d eq $xmpdata} -} -cleanup { - catch {image delete gif1} - file delete $path -} -result {1} - -test imgPhoto-23.11 {empty XMP comment block after image (-data)} -setup { - set data $::gifstart - append data $::gifdata - - append data "\x21\xff\x0B" "XMP DataXMP" - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - # Trailer - append data $::gifend -} -body { - image create photo gif1 -data $data - dict get [gif1 cget -metadata] XMP -} -cleanup { - catch {image delete gif1} -} -result {} - -test imgPhoto-23.12 {empty XMP comment block after image (-file)} -setup { - set data $::gifstart - append data $::gifdata - - append data "\x21\xff\x0B" "XMP DataXMP" - # Special trailer of 1 ff fe ... 02 01 00 00 - append data "\x01" - for {set i 0xff} {$i != -1} {incr i -1} { - append data [binary format c $i] - } - append data "\x00" - # Trailer - append data $::gifend - - set path [file join [configure -tmpdir] test.gif] - set h [open $path "WRONLY BINARY CREAT"] - puts $h $data - close $h -} -body { - image create photo gif1 -file $path - dict get [gif1 cget -metadata] XMP -} -cleanup { - catch {image delete gif1} - file delete $path -} -result {} - -test imgPhoto-23.13 {create: test if shared metadata object is not preserved\ +test imgPhoto-23.7 {create: test if shared metadata object is not preserved\ (-data)}\ -setup { set data $::gifstart @@ -2429,7 +2266,7 @@ test imgPhoto-23.13 {create: test if shared metadata object is not preserved\ catch {image delete gif1} } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.14 {create: test if shared metadata object is not preserved\ +test imgPhoto-23.8 {create: test if shared metadata object is not preserved\ (-file)}\ -setup { set data $::gifstart @@ -2452,7 +2289,7 @@ test imgPhoto-23.14 {create: test if shared metadata object is not preserved\ file delete $path } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.15 {configure: test if shared metadata object is not\ +test imgPhoto-23.9 {configure: test if shared metadata object is not\ preserved (empty image, -data)}\ -setup { set data $::gifstart @@ -2470,7 +2307,7 @@ test imgPhoto-23.15 {configure: test if shared metadata object is not\ catch {image delete gif1} } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.16 {configure: test if shared metadata object is not preserved\ +test imgPhoto-23.10 {configure: test if shared metadata object is not preserved\ (empty image, -file)}\ -setup { set data $::gifstart @@ -2494,7 +2331,7 @@ test imgPhoto-23.16 {configure: test if shared metadata object is not preserved\ file delete $path } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.17 {configure: test if shared metadata object is not preserved\ +test imgPhoto-23.11 {configure: test if shared metadata object is not preserved\ (metadata replace, -data}\ -setup { set data $::gifstart @@ -2512,7 +2349,7 @@ test imgPhoto-23.17 {configure: test if shared metadata object is not preserved\ catch {image delete gif1} } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.18 {configure: test if shared metadata object is not preserved\ +test imgPhoto-23.12 {configure: test if shared metadata object is not preserved\ (metadata replace, -file}\ -setup { set data $::gifstart @@ -2536,7 +2373,7 @@ test imgPhoto-23.18 {configure: test if shared metadata object is not preserved\ file delete $path } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.19 {configure: test if shared metadata object is not preserved\ +test imgPhoto-23.13 {configure: test if shared metadata object is not preserved\ (-data)}\ -setup { set data $::gifstart$::gifdata$::gifend @@ -2555,7 +2392,7 @@ test imgPhoto-23.19 {configure: test if shared metadata object is not preserved\ catch {image delete gif1} } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.20 {configure: test if shared metadata object is not preserved\ +test imgPhoto-23.14 {configure: test if shared metadata object is not preserved\ (-file)}\ -setup { set data $::gifstart @@ -2579,7 +2416,7 @@ test imgPhoto-23.20 {configure: test if shared metadata object is not preserved\ file delete $path } -result {{A 1 comment ABCD} {A 1} {A 1}} -test imgPhoto-23.21 {output data with comment (from -metadata argument)}\ +test imgPhoto-23.15 {output data with comment (from -metadata argument)}\ -setup { set data $::gifstart$::gifdata$::gifend } -body { @@ -2604,7 +2441,7 @@ test imgPhoto-23.22 {output file with comment (from -metadata argument)}\ file delete $path } -result {ABCD} -test imgPhoto-23.23 {output data with comment (from -metadata property)}\ +test imgPhoto-23.16 {output data with comment (from -metadata property)}\ -setup { set data $::gifstart$::gifdata$::gifend } -body { @@ -2615,7 +2452,7 @@ test imgPhoto-23.23 {output data with comment (from -metadata property)}\ catch {image delete gif1} } -match glob -result {*ABCD*} -test imgPhoto-23.24 {output file with comment (from -metadata property)}\ +test imgPhoto-23.17 {output file with comment (from -metadata property)}\ -setup { set data $::gifstart$::gifdata$::gifend set path [file join [configure -tmpdir] test.gif] @@ -2631,44 +2468,7 @@ test imgPhoto-23.24 {output file with comment (from -metadata property)}\ file delete $path } -result {ABCD} -test imgPhoto-23.25 {output data with XMP (-data)} -setup { - set data $::gifstart$::gifdata$::gifend - set XMPData\ - "\ - \ - 3" -} -body { - image create photo gif1 -data $data - set gifData [gif1 data -format gif -metadata [dict create\ - XMP $XMPData]] - image delete gif1 - image create photo gif1 -data $gifData - expr {[dict get [gif1 cget -metadata] XMP] eq $XMPData} -} -cleanup { - catch {image delete gif1} -} -result {1} - -test imgPhoto-23.24 {output data with XMP (-file)} -setup { - set data $::gifstart$::gifdata$::gifend - set XMPData\ - "\ - \ - 3" - set path [file join [configure -tmpdir] test.gif] -} -body { - image create photo gif1 -data $data - set gifData [gif1 write $path -format gif -metadata [dict create\ - XMP $XMPData]] - image delete gif1 - image create photo gif1 -file $path - expr {[dict get [gif1 cget -metadata] XMP] eq $XMPData} -} -cleanup { - catch {image delete gif1} -} -result {1} - -test imgPhoto-23.25 {configure: empty metadata parameter overwrites image metadata} -setup { +test imgPhoto-23.18 {configure: empty metadata parameter overwrites image metadata} -setup { image create photo gif1 -data $::gifstart$::gifdata$::gifend\ -metadata {foo bar} set data $::gifstart @@ -2683,7 +2483,7 @@ test imgPhoto-23.25 {configure: empty metadata parameter overwrites image metada catch {image delete gif1} } -result {comment ABCD} -test imgPhoto-23.26 {write: empty metadata parameter overwrites image metadata} -setup { +test imgPhoto-23.19 {write: empty metadata parameter overwrites image metadata} -setup { image create photo gif1 -data $::gifstart$::gifdata$::gifend\ -metadata {comment bar} set path [file join [configure -tmpdir] test.gif] @@ -2697,7 +2497,7 @@ test imgPhoto-23.26 {write: empty metadata parameter overwrites image metadata} file delete $path } -result {0} -test imgPhoto-23.27 {data: empty metadata parameter overwrites image metadata} -setup { +test imgPhoto-23.20 {data: empty metadata parameter overwrites image metadata} -setup { image create photo gif1 -data $::gifstart$::gifdata$::gifend\ -metadata {comment bar} } -body { -- cgit v0.12 From e8c84a297a502e5e9e366fe2dcaef0d81e66e6c2 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 26 Jun 2020 10:42:55 +0000 Subject: Make C++ compiler more happy (for Travis build) --- generic/tkImgGIF.c | 33 ++++++++++++--------------------- generic/tkImgPNG.c | 50 ++++++++++++++++++-------------------------------- generic/tkImgPhoto.c | 12 ++++-------- 3 files changed, 34 insertions(+), 61 deletions(-) diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 7dc78d8..93a891e 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -356,20 +356,17 @@ static void FlushChar(GIFState_t *statePtr); static int FileMatchGIF( - Tcl_Interp *dummy, /* not used */ + TCL_UNUSED(Tcl_Interp *), /* not used */ Tcl_Channel chan, /* The image file, open for reading. */ - const char *fileName, /* The name of the image file. */ - Tcl_Obj *format, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(const char *), /* The name of the image file. */ + TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ + TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { GIFImageConfig gifConf; - (void)fileName; - (void)format; - (void)dummy; memset(&gifConf, 0, sizeof(GIFImageConfig)); return ReadGIFHeader(&gifConf, chan, widthPtr, heightPtr); @@ -400,7 +397,7 @@ FileReadGIF( Tcl_Channel chan, /* The image file, open for reading. */ const char *fileName, /* The name of the image file. */ Tcl_Obj *format, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* The photo image to write into. */ int destX, int destY, /* Coordinates of top-left pixel in photo * image to be written to. */ @@ -417,7 +414,6 @@ FileReadGIF( unsigned char buf[100]; unsigned char *trashBuffer = NULL; int bitPixel; - int dictSize = 0; int gifLabel; unsigned char colorMap[MAXCOLORMAPSIZE][4]; int transparent = -1; @@ -846,19 +842,17 @@ ReadOneByte( static int StringMatchGIF( - Tcl_Interp *dummy, /* not used */ + TCL_UNUSED(Tcl_Interp *), /* not used */ Tcl_Obj *dataObj, /* the object containing the image data */ - Tcl_Obj *format, /* the image format object, or NULL */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(Tcl_Obj *), /* the image format object, or NULL */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ + TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { unsigned char *data, header[10]; TkSizeT got, length; MFile handle; - (void)format; - (void)dummy; data = TkGetByteArrayFromObj(dataObj, &length); @@ -1187,7 +1181,7 @@ ReadImage( Tcl_Channel chan, int len, int rows, unsigned char cmap[MAXCOLORMAPSIZE][4], - int srcX, int srcY, + TCL_UNUSED(int), TCL_UNUSED(int), int interlace, int transparent) { @@ -1202,8 +1196,6 @@ ReadImage( unsigned char *top; int codeSize, clearCode, inCode, endCode, oldCode, maxCode; int code, firstCode, v; - (void)srcX; - (void)srcY; /* * Initialize the decoder @@ -1867,7 +1859,7 @@ CommonWriteGIF( Tcl_Interp *interp, ClientData handle, WriteBytesFunc *writeProc, - Tcl_Obj *format, + TCL_UNUSED(Tcl_Obj *), Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { @@ -1876,7 +1868,6 @@ CommonWriteGIF( long width, height, x; unsigned char c; unsigned int top, left; - (void)format; top = 0; left = 0; diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 1ead8c0..1476de4 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -1717,7 +1717,7 @@ ReadPHYS( if (ReadInt32(interp, pngPtr, &PPUy, &crc) == TCL_ERROR) { return TCL_ERROR; } - if (ReadData(interp, pngPtr, &unitSpecifier, 1, &crc) == TCL_ERROR) { + if (ReadData(interp, pngPtr, (unsigned char *)&unitSpecifier, 1, &crc) == TCL_ERROR) { return TCL_ERROR; } @@ -2817,18 +2817,16 @@ static int FileMatchPNG( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Channel chan, /* The image file, open for reading. */ - const char *fileName, /* The name of the image file. */ - Tcl_Obj *fmtObj, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(const char *), /* The name of the image file. */ + TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned * here if the file is a valid raw GIF file. */ - Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ + TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { PNGImage png; int match = 0; - (void)fileName; - (void)fmtObj; InitPNGImage(NULL, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE); @@ -2866,25 +2864,20 @@ static int FileReadPNG( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Channel chan, /* The image file, open for reading. */ - const char *fileName, /* The name of the image file. */ + TCL_UNUSED(const char *), /* The name of the image file. */ Tcl_Obj *fmtObj, /* User-specified format object, or NULL. */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* The photo image to write into. */ int destX, int destY, /* Coordinates of top-left pixel in photo * image to be written to. */ - int width, int height, /* Dimensions of block of photo image to be + TCL_UNUSED(int), TCL_UNUSED(int), /* Dimensions of block of photo image to be * written to. */ - int srcX, int srcY, /* Coordinates of top-left pixel to be used in + TCL_UNUSED(int), TCL_UNUSED(int), /* Coordinates of top-left pixel to be used in * image being read. */ Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int result = TCL_ERROR; - (void)fileName; - (void)width; - (void)height; - (void)srcX; - (void)srcY; result = InitPNGImage(interp, &png, chan, NULL, TCL_ZLIB_STREAM_INFLATE); @@ -2930,15 +2923,14 @@ static int StringMatchPNG( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Obj *pObjData, /* the object containing the image data */ - Tcl_Obj *fmtObj, /* the image format object, or NULL */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(Tcl_Obj *), /* the image format object, or NULL */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ - Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ + TCL_UNUSED(Tcl_Obj *)) /* metadata return dict, may be NULL */ { PNGImage png; int match = 0; - (void)fmtObj; InitPNGImage(NULL, &png, NULL, pObjData, TCL_ZLIB_STREAM_INFLATE); @@ -2977,19 +2969,15 @@ StringReadPNG( Tcl_Interp *interp, /* interpreter for reporting errors in */ Tcl_Obj *pObjData, /* object containing the image */ Tcl_Obj *fmtObj, /* format object, or NULL */ - Tcl_Obj *metadataInObj, /* metadata input, may be NULL */ + TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ - int width, int height, /* image to copy */ - int srcX, int srcY, + TCL_UNUSED(int), TCL_UNUSED(int), /* image to copy */ + TCL_UNUSED(int), TCL_UNUSED(int), Tcl_Obj *metadataOutObj) /* metadata return dict, may be NULL */ { PNGImage png; int result = TCL_ERROR; - (void)width; - (void)height; - (void)srcX; - (void)srcY; result = InitPNGImage(interp, &png, NULL, pObjData, TCL_ZLIB_STREAM_INFLATE); @@ -3532,7 +3520,7 @@ WriteExtraChunks( Tcl_Obj *aspectObj, *DPIObj; double aspectValue=-1, DPIValue=-1; - unsigned long PPUx, PPUy; + unsigned long PPUx = 65536, PPUy = 65536; char unitSpecifier; if (TCL_ERROR == Tcl_DictObjGet(interp, metadataInObj, @@ -3741,14 +3729,13 @@ static int FileWritePNG( Tcl_Interp *interp, const char *filename, - Tcl_Obj *fmtObj, + TCL_UNUSED(Tcl_Obj *), Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { Tcl_Channel chan; PNGImage png; int result = TCL_ERROR; - (void)fmtObj; /* * Open a Tcl file channel where the image data will be stored. Tk ought @@ -3813,14 +3800,13 @@ FileWritePNG( static int StringWritePNG( Tcl_Interp *interp, - Tcl_Obj *fmtObj, + TCL_UNUSED(Tcl_Obj *), Tcl_Obj *metadataInObj, Tk_PhotoImageBlock *blockPtr) { Tcl_Obj *resultObj = Tcl_NewObj(); PNGImage png; int result = TCL_ERROR; - (void)fmtObj; /* * Initalize PNGImage instance for encoding. diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 92e4670..91b7ec1 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -234,13 +234,12 @@ static const char * GetExtension(const char *path); static void PhotoFormatThreadExitProc( - ClientData dummy) /* not used */ + TCL_UNUSED(void *)) /* not used */ { Tk_PhotoImageFormat *freePtr; Tk_PhotoImageFormatVersion3 *freePtrVersion3; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); - (void)dummy; #if !defined(TK_NO_DEPRECATED) && TCL_MAJOR_VERSION < 9 while (tsdPtr->oldFormatList != NULL) { @@ -390,14 +389,13 @@ ImgPhotoCreate( int objc, /* Number of arguments. */ Tcl_Obj *const objv[], /* Argument objects for options (doesn't * include image name or type). */ - const Tk_ImageType *typePtr,/* Pointer to our type record (not used). */ + TCL_UNUSED(const Tk_ImageType *),/* Pointer to our type record (not used). */ Tk_ImageMaster master, /* Token for image, to be used by us in later * callbacks. */ ClientData *clientDataPtr) /* Store manager's token for image here; it * will be returned in later callbacks. */ { PhotoMaster *masterPtr; - (void)typePtr; /* * Allocate and initialize the photo image master record. @@ -4489,15 +4487,13 @@ static int ImgPhotoPostscript( ClientData clientData, /* Handle for the photo image. */ Tcl_Interp *interp, /* Interpreter. */ - Tk_Window tkwin, /* (unused) */ + TCL_UNUSED(Tk_Window), /* (unused) */ Tk_PostscriptInfo psInfo, /* Postscript info. */ int x, int y, /* First pixel to output. */ int width, int height, /* Width and height of area. */ - int prepass) /* (unused) */ + TCL_UNUSED(int)) /* (unused) */ { Tk_PhotoImageBlock block; - (void)tkwin; - (void)prepass; Tk_PhotoGetImage(clientData, &block); block.pixelPtr += y * block.pitch + x * block.pixelSize; -- cgit v0.12 -- cgit v0.12 From 2c0244b76797cbed7d7fe16343fd7b90beb85610 Mon Sep 17 00:00:00 2001 From: oehhar Date: Mon, 25 Jan 2021 18:28:08 +0000 Subject: TIP529 image metadata: "photo" documentation page started. --- doc/photo.n | 88 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/doc/photo.n b/doc/photo.n index 3b206f5..f09fddf 100644 --- a/doc/photo.n +++ b/doc/photo.n @@ -101,6 +101,16 @@ Specifies the height of the image, in pixels. This option is useful primarily in situations where the user wishes to build up the contents of the image piece by piece. A value of zero (the default) allows the image to expand or shrink vertically to fit the data stored in it. +.VS 8.7 +.TP +\fB\-metadata \fImetadata\fR +. +Set the metadata dictionary of the image. +Additional keys may be set within the metadata dictionary of the image, +if image data is processed due to a \fB\-file\fR or \fB\-data\fR options +and the driver outputs any metadata keys. +See section \fBMETADATA DICT\fR below. +.VE 8.7 .TP \fB\-palette \fIpalette-spec\fR . @@ -146,7 +156,7 @@ The following commands are possible for photo images: . Blank the image; that is, set the entire image to have no data, so it will be displayed as transparent, and the background of whatever -window it is displayed in will show through. +window it is displayed in will show through. The metadata dict of the image is not changed. .TP \fIimageName \fBcget\fR \fIoption\fR . @@ -169,6 +179,10 @@ modifies the given option(s) to have the given value(s); in this case the command returns an empty string. \fIOption\fR may have any of the values accepted by the \fBimage create\fR \fBphoto\fR command. +.VS 8.7 +Note: setting the \fB\-metadata\fR option without any other option +will not invoke the image format driver to recreate the bitmap. +.VE 8.7 .TP \fIimageName \fBcopy\fR \fIsourceImage\fR ?\fIoption value(s) ...\fR? . @@ -287,9 +301,18 @@ whole image. . If this options is specified, the data will not contain color information. All pixel data will be transformed into grayscale. -.RE .VS 8.7 .TP +\fB\-metadata\fR \fmetadata\fR +. +Image format handler may use metadata to be included in the returned +data string. +The specified metadata is passed to the driver for inclusion in the +data. +If no \fB\-metadata\fR option is given, the current metadata of the +image is used. +.VE 8.7 +.RE \fIimageName \fBget\fR \fIx y\fR ?\fB-withalpha\fR? . Returns the color of the pixel at coordinates (\fIx\fR,\fIy\fR) in the @@ -322,6 +345,15 @@ format handler to read the data. Note: the value of this option must be a Tcl list. This means that the braces may be omitted if the argument has only one word. Also, instead of braces, double quotes may be used for quoting. +.VS 8.7 +.TP +\fB\-metadata\fR \fmetadata\fR +. +A specified metadata is passed to the image format driver when interpreting +the data. +Note: The current metadata of the image is not passed to the format driver +and is not changed by the command. +.VE 8.7 .TP \fB\-to \fIx1 y1\fR ?\fIx2 y2\fR? . @@ -367,6 +399,15 @@ corner of the image in the image file. If all four coordinates are specified, they specify diagonally opposite corners or the region. The default, if this option is not specified, is the whole of the image in the image file. +.VS 8.7 +.TP +\fB\-metadata\fR \fmetadata\fR +. +A specified metadata is passed to the image format driver when interpreting +the data. +Note: The current metadata of the image is not passed to the format driver +and is not changed by the command. +.VE 8.7 .TP \fB\-shrink\fR . @@ -460,6 +501,16 @@ if this option is not given, is the whole image. . If this options is specified, the data will not contain color information. All pixel data will be transformed into grayscale. +.TP +.VS 8.7 +\fB\-metadata\fR \fmetadata\fR +. +Image format handler may use metadata to be included in the written file. +The specified metadata is passed to the driver for inclusion in the +file. +If no \fB\-metadata\fR option is given, the current metadata of the +image is used. +.VE 8.7 .RE .SH "IMAGE FORMATS" .PP @@ -682,6 +733,39 @@ each primary color to try to allocate. It can also be used to force the image to be displayed in shades of gray, even on a color display, by giving a single number rather than three numbers separated by slashes. +.VS 8.7 +.SH "METADATA DICTIONARY" +.PP +Each image has a metadata dictionary property. +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" +.PP +Each image format driver supports an individual set of metadata dictionary +keys. Predefined keys are: +.TP +. +DPI +Horizontal image resolution in DPI as a double value. +Supported by format \fBpng\fR. +.TP +. +aspect +Aspect ratio horizontal divided by vertical as double value. +Supported by formats \fBgif\fR and \fBpng\fR. +.TP +. +comment +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. +.PP +.VE 8.7 .SH CREDITS .PP The photo image type was designed and implemented by Paul Mackerras, -- cgit v0.12 From 8882f00b3d0596b7e09cf1d4f666e4bd2ac7ece5 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 26 Jan 2021 11:54:21 +0000 Subject: TIP529: image metadata: added driver interface documentation --- doc/CrtPhImgFmt.3 | 299 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 269 insertions(+), 30 deletions(-) diff --git a/doc/CrtPhImgFmt.3 b/doc/CrtPhImgFmt.3 index 92f2441..6cc478b 100644 --- a/doc/CrtPhImgFmt.3 +++ b/doc/CrtPhImgFmt.3 @@ -9,6 +9,9 @@ '\" Department of Computer Science, '\" Australian National University. '\" +.VS 8.7 +.TH Tk_CreatePhotoImageFormatVersion3 3 8.7 Tk "Tk Library Procedures" +.VE 8.7 .TH Tk_CreatePhotoImageFormat 3 8.5 Tk "Tk Library Procedures" .so man.macros .BS @@ -18,15 +21,26 @@ Tk_CreatePhotoImageFormat \- define new file format for photo images .nf \fB#include \fR .sp +.VS 8.7 +\fBTk_CreatePhotoImageFormatVersion3\fR(\fIformatVersion3Ptr\fR) +.VE 8.7 +.sp \fBTk_CreatePhotoImageFormat\fR(\fIformatPtr\fR) .SH ARGUMENTS +.VS 8.7 +.AS "const Tk_PhotoImageFormatVersion3" *formatVersion3Ptr +.VE 8.7 .AS "const Tk_PhotoImageFormat" *formatPtr +.VS 8.7 +.AP "const Tk_PhotoImageFormatVersion3" *formatVersion3Ptr in +Structure that defines the new file format including metadata functionality. +.VE 8.7 .AP "const Tk_PhotoImageFormat" *formatPtr in Structure that defines the new file format. .BE .SH DESCRIPTION .PP -\fBTk_CreatePhotoImageFormat\fR is invoked to define a new file format +\fBTk_CreatePhotoImageFormatVersion3\fR is invoked to define a new file format for image data for use with photo images. The code that implements an image file format is called an image file format handler, or handler for short. The photo image code @@ -38,22 +52,29 @@ The user can specify which handler to use with the \fB\-format\fR image configuration option or the \fB\-format\fR option to the \fBread\fR and \fBwrite\fR photo image subcommands. .PP +The alternate version 2 function \fBTk_CreatePhotoImageFormat\fR has +identical functionality, but does not allow to handler to get or return +the metadata dictionary of the image. +It is described in section \fBVERSION 2 INTERFACE\fR below. +.PP An image file format handler consists of a collection of procedures -plus a Tk_PhotoImageFormat structure, which contains the name of the -image file format and pointers to six procedures provided by the -handler to deal with files and strings in this format. The -Tk_PhotoImageFormat structure contains the following fields: +plus a \fBTk_PhotoImageFormatVersion3\fR structure, which contains the +name of the image file format and pointers to six procedures provided +by the handler to deal with files and strings in this format. The +Tk_PhotoImageFormatVersion3 structure contains the following fields: +.VS 8.7 .CS -typedef struct Tk_PhotoImageFormat { +typedef struct Tk_PhotoImageFormatVersion3 { const char *\fIname\fR; - Tk_ImageFileMatchProc *\fIfileMatchProc\fR; - Tk_ImageStringMatchProc *\fIstringMatchProc\fR; - Tk_ImageFileReadProc *\fIfileReadProc\fR; - Tk_ImageStringReadProc *\fIstringReadProc\fR; - Tk_ImageFileWriteProc *\fIfileWriteProc\fR; - Tk_ImageStringWriteProc *\fIstringWriteProc\fR; -} \fBTk_PhotoImageFormat\fR; + Tk_ImageFileMatchProcVersion3 *\fIfileMatchProc\fR; + Tk_ImageStringMatchProcVersion3 *\fIstringMatchProc\fR; + Tk_ImageFileReadProcVersion3 *\fIfileReadProc\fR; + Tk_ImageStringReadProcVersion3 *\fIstringReadProc\fR; + Tk_ImageFileWriteProcVersion3 *\fIfileWriteProc\fR; + Tk_ImageStringWriteProcVersion3 *\fIstringWriteProc\fR; +} \fBTk_PhotoImageFormatVersion3\fR; .CE +.VE 8.7 .PP The handler need not provide implementations of all six procedures. For example, the procedures that handle string data would not be @@ -67,14 +88,16 @@ procedure, and the \fIstringMatchProc\fR procedure if it provides the .SS NAME .PP \fIformatPtr->name\fR provides a name for the image type. -Once \fBTk_CreatePhotoImageFormat\fR returns, this name may be used -in the \fB\-format\fR photo image configuration and subcommand option. +Once \fBTk_CreatePhotoImageFormatVersion3\fR returns, this name may be +used in the \fB\-format\fR photo image configuration and subcommand +option. The manual page for the photo image (photo(n)) describes how image file formats are chosen based on their names and the value given to the \fB\-format\fR option. The first character of \fIformatPtr->name\fR must not be an uppercase character from the ASCII character set (that is, one of the characters \fBA\fR-\fBZ\fR). Such names are used only for legacy interface support (see below). +.VS 8.7 .SS FILEMATCHPROC .PP \fIformatPtr->fileMatchProc\fR provides the address of a procedure for @@ -82,39 +105,47 @@ Tk to call when it is searching for an image file format handler suitable for reading data in a given file. \fIformatPtr->fileMatchProc\fR must match the following prototype: .CS -typedef int \fBTk_ImageFileMatchProc\fR( +typedef int \fBTk_ImageFileMatchProcVersion3\fR( + Tcl_Interp *\fIinterp\fR, Tcl_Channel \fIchan\fR, const char *\fIfileName\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, int *\fIwidthPtr\fR, int *\fIheightPtr\fR, - Tcl_Interp *\fIinterp\fR); + Tcl_Obj *\fImetadataOut\fR); .CE The \fIfileName\fR argument is the name of the file containing the image data, which is open for reading as \fIchan\fR. The \fIformat\fR argument contains the value given for the \fB\-format\fR option, or NULL if the option was not specified. +\fBmetadataIn\fR and \fBmetadataOut\fR inputs and returns a metadata +dictionary as described in section \fBMETADATA INTERFACE\fR below. If the data in the file appears to be in the format supported by this handler, the \fIformatPtr->fileMatchProc\fR procedure should store the width and height of the image in *\fIwidthPtr\fR and *\fIheightPtr\fR respectively, and return 1. Otherwise it should return 0. .SS STRINGMATCHPROC .PP -\fIformatPtr->stringMatchProc\fR provides the address of a procedure for -Tk to call when it is searching for an image file format handler for -suitable for reading data from a given string. +\fIformatPtr->stringMatchProc\fR provides the address of a procedure +for Tk to call when it is searching for an image file format handler +for suitable for reading data from a given string. \fIformatPtr->stringMatchProc\fR must match the following prototype: .CS -typedef int \fBTk_ImageStringMatchProc\fR( +typedef int \fBTk_ImageStringMatchProcVersion3\fR( + Tcl_Interp *\fIinterp\fR, Tcl_Obj *\fIdata\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, int *\fIwidthPtr\fR, int *\fIheightPtr\fR, - Tcl_Interp *\fIinterp\fR); + Tcl_Obj *\fImetadataOut\fR); .CE The \fIdata\fR argument points to the object containing the image data. The \fIformat\fR argument contains the value given for the \fB\-format\fR option, or NULL if the option was not specified. +\fBmetadataIn\fR and \fBmetadataOut\fR inputs and returns a metadata +dictionary as described in section \fBMETADATA INTERFACE\fR below. If the data in the string appears to be in the format supported by this handler, the \fIformatPtr->stringMatchProc\fR procedure should store the width and height of the image in *\fIwidthPtr\fR and @@ -131,10 +162,12 @@ typedef int \fBTk_ImageFileReadProc\fR( Tcl_Channel \fIchan\fR, const char *\fIfileName\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, PhotoHandle \fIimageHandle\fR, int \fIdestX\fR, int \fIdestY\fR, int \fIwidth\fR, int \fIheight\fR, - int \fIsrcX\fR, int \fIsrcY\fR); + int \fIsrcX\fR, int \fIsrcY\fR, + Tcl_Obj *\fImetadataOut\fR); .CE The \fIinterp\fR argument is the interpreter in which the command was invoked to read the image; it should be used for reporting errors. @@ -148,34 +181,40 @@ dimensions \fIwidth\fR x \fIheight\fR and has its top-left corner at coordinates (\fIsrcX\fR,\fIsrcY\fR). It is to be stored in the photo image with its top-left corner at coordinates (\fIdestX\fR,\fIdestY\fR) using the \fBTk_PhotoPutBlock\fR procedure. +\fBmetadataIn\fR and \fBmetadataOut\fR inputs and returns a metadata +dictionary as described in section \fBMETADATA INTERFACE\fR below. The return value is a standard Tcl return value. .SS STRINGREADPROC .PP -\fIformatPtr->stringReadProc\fR provides the address of a procedure for -Tk to call to read data from a string into a photo image. +\fIformatPtr->stringReadProc\fR provides the address of a procedure +for Tk to call to read data from a string into a photo image. \fIformatPtr->stringReadProc\fR must match the following prototype: .CS typedef int \fBTk_ImageStringReadProc\fR( Tcl_Interp *\fIinterp\fR, Tcl_Obj *\fIdata\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, PhotoHandle \fIimageHandle\fR, int \fIdestX\fR, int \fIdestY\fR, int \fIwidth\fR, int \fIheight\fR, - int \fIsrcX\fR, int \fIsrcY\fR); + int \fIsrcX\fR, int \fIsrcY\fR, + Tcl_Obj *\fImetadataOut\fR); .CE The \fIinterp\fR argument is the interpreter in which the command was invoked to read the image; it should be used for reporting errors. The \fIdata\fR argument points to the image data in object form. The \fIformat\fR argument contains the value given for the \fB\-format\fR option, or NULL if the option was -not specified. The image data in the string, or a subimage of it, is to -be read into the photo image identified by the handle +not specified. The image data in the string, or a subimage of it, is +to be read into the photo image identified by the handle \fIimageHandle\fR. The subimage of the data in the string is of dimensions \fIwidth\fR x \fIheight\fR and has its top-left corner at coordinates (\fIsrcX\fR,\fIsrcY\fR). It is to be stored in the photo image with its top-left corner at coordinates (\fIdestX\fR,\fIdestY\fR) using the \fBTk_PhotoPutBlock\fR procedure. +\fBmetadataIn\fR and \fBmetadataOut\fR inputs and returns a metadata +dictionary as described in section \fBMETADATA INTERFACE\fR below. The return value is a standard Tcl return value. .SS FILEWRITEPROC .PP @@ -187,6 +226,7 @@ typedef int \fBTk_ImageFileWriteProc\fR( Tcl_Interp *\fIinterp\fR, const char *\fIfileName\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, Tk_PhotoImageBlock *\fIblockPtr\fR); .CE The \fIinterp\fR argument is the interpreter in which the command was @@ -201,16 +241,20 @@ not specified. The format string can contain extra characters after the name of the format. If appropriate, the \fIformatPtr->fileWriteProc\fR procedure may interpret these characters to specify further details about the image file. +\fBmetadataIn\fR may contain metadata keys that a driver may include +into the output data. The return value is a standard Tcl return value. .SS STRINGWRITEPROC .PP -\fIformatPtr->stringWriteProc\fR provides the address of a procedure for -Tk to call to translate image data from a photo image into a string. +\fIformatPtr->stringWriteProc\fR provides the address of a procedure +for Tk to call to translate image data from a photo image into a +string. \fIformatPtr->stringWriteProc\fR must match the following prototype: .CS typedef int \fBTk_ImageStringWriteProc\fR( Tcl_Interp *\fIinterp\fR, Tcl_Obj *\fIformat\fR, + Tcl_Obj *\fImetadataIn\fR, Tk_PhotoImageBlock *\fIblockPtr\fR); .CE The \fIinterp\fR argument is the interpreter in which the command was @@ -225,7 +269,202 @@ not specified. The format string can contain extra characters after the name of the format. If appropriate, the \fIformatPtr->stringWriteProc\fR procedure may interpret these characters to specify further details about the image file. +\fBmetadataIn\fR may contain metadata keys that a driver may include +into the output data. The return value is a standard Tcl return value. +.PP +.SH "METADATA INTERFACE" +.PP +Image formats contain a description of the image bitmap and may +contain additional information like image resolution or comments. +Image metadata may be read from image files and passed to the script +level by including dictionary keys into the metadata property of the +image. Image metadata may be written to image data on file write or +image data output. +.PP +.PP +.SS "METADATA KEYS" +.PP +The metadata may contain any key. +A driver will handle only a set of dictionary keys documented in the +documentation. See the photo image manual page for currently defined +keys for the system drivers. +.PP +The following rules may give guidance to name metadata keys: +.RS +Abreviation are in upper case +.RE +.RS +Words are in US English in small case (except propper nouns) +.RE +.RS +Vertical DPI is expressed as DPI/aspect. The reason is, that some +image formats may feature aspect and no resolution value. +.RE +.SS "METADATA INPUT" +.PP +Each driver function gets a tcl object pointer \fBmetadataIn\fR as +parameter. This parameter serves to input a metadata dict to the +driver function. +It may be NULL to flag that the metadata dict is empty. +.PP +A typical driver code snipped to check for a metadata key is: +.CS +if (NULL != metadataIn) { + Tcl_Obj *itemData; + Tcl_DictObjGet(interp, metadataIn, Tcl_NewStringObj("Comment",-1), &itemData)); +.CE +.PP +The \-metadata command option data of the following commands is passed +to the driver: \fBimage create\fR, \fBconfigure\fR, \fBput\fR, +\fBread\fR, \fBdata\fR and \fBwrite\fR. +If no \-metadata command option available or not given, the metadata +property of the image is passed to the driver using the following +commands: \fBcget\fR, \fBconfigure\fR, \fBdata\fR and \fBwrite\fR. +.PP +Note that setting the \-metadata property of an image using +\fBconfigure\fR without any other option does not invoke any driver +function. +.PP +The metadata dictionary is not suited to pass options to the driver +related to the bitmap representation, as the image bitmap is not +recreated on a metadata change. The format string should be used for +this purpose. +.PP +.SS "METADATA OUTPUT" +.PP +The image match and read driver functions may set keys in a prepared +matadata dict to return them. +Those functions get a tcl object pointer \fBmetadataOut\fR as +parameter. +metadataOut may be NULL to indicate, that no metadata return is +attended (put, read subcommands). +metadataOut is initialized to an empty unshared dict object if +metadata return is attended (image create command, configure +subcommand). The driver may set dict keys in this object to return +metadata. +If a match function succeeds, the metadataOut pointer is passed to the +corresponding read function. +.PP +A sample driver code snippet is: +.CS +if (NULL != metadataOut) { + Tcl_DictObjPut(NULL, metadataOut, Tcl_NewStringObj("XMP",-1), Tcl_NewStringObj(xmpMetadata); +.CE +.PP +The metadata keys returned by the driver are merged into the present +metadata property of the image or into the metadata dict given by the +\fB\-metadata\fR command line option. +On the script level, the command \fBimage create\fR and the +\fBconfigure\fR method may return metadata from the driver. +.PP +Format string options or metadata keys may influence the creation of +metadata within the driver. +For example, the creation of an expensive metadata key may depend on a +format string option or on a metadata input key. +.PP +.VE 8.7 +.SH "VERSION 2 INTERFACE" +.PP +Version 2 Interface does not include the possibility for the driver to +use the metadata dict for input or output. +.SS SYNOPSIS +.nf +\fB#include \fR +.sp +\fBTk_CreatePhotoImageFormat\fR(\fIformatPtr\fR) +.SS ARGUMENTS +.AS "const Tk_PhotoImageFormat" *formatPtr +.AP "const Tk_PhotoImageFormat" *formatPtr in +Structure that defines the new file format. +.BE +.SS DESCRIPTION +A driver using the version 2 interface invokes \fBTk_CreatePhotoImageFormat\fR +for driver registration. The Tk_PhotoImageFormat structure +contains the following fields: +.CS +typedef struct Tk_PhotoImageFormat { + const char *\fIname\fR; + Tk_ImageFileMatchProc *\fIfileMatchProc\fR; + Tk_ImageStringMatchProc *\fIstringMatchProc\fR; + Tk_ImageFileReadProc *\fIfileReadProc\fR; + Tk_ImageStringReadProc *\fIstringReadProc\fR; + Tk_ImageFileWriteProc *\fIfileWriteProc\fR; + Tk_ImageStringWriteProc *\fIstringWriteProc\fR; +} \fBTk_PhotoImageFormat\fR; +.CE +.PP +.SS FILEMATCHPROC +.PP +\fIformatPtr->fileMatchProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageFileMatchProc\fR( + Tcl_Channel \fIchan\fR, + const char *\fIfileName\fR, + Tcl_Obj *\fIformat\fR, + int *\fIwidthPtr\fR, + int *\fIheightPtr\fR, + Tcl_Interp *\fIinterp\fR); +.CE +.PP +.SS STRINGMATCHPROC +.PP +\fIformatPtr->stringMatchProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageStringMatchProc\fR( + Tcl_Obj *\fIdata\fR, + Tcl_Obj *\fIformat\fR, + int *\fIwidthPtr\fR, + int *\fIheightPtr\fR, + Tcl_Interp *\fIinterp\fR); +.CE +.SS FILEREADPROC +.PP +\fIformatPtr->fileReadProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageFileReadProc\fR( + Tcl_Interp *\fIinterp\fR, + Tcl_Channel \fIchan\fR, + const char *\fIfileName\fR, + Tcl_Obj *\fIformat\fR, + PhotoHandle \fIimageHandle\fR, + int \fIdestX\fR, int \fIdestY\fR, + int \fIwidth\fR, int \fIheight\fR, + int \fIsrcX\fR, int \fIsrcY\fR); +.CE +.SS STRINGREADPROC +.PP +\fIformatPtr->stringReadProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageStringReadProc\fR( + Tcl_Interp *\fIinterp\fR, + Tcl_Obj *\fIdata\fR, + Tcl_Obj *\fIformat\fR, + PhotoHandle \fIimageHandle\fR, + int \fIdestX\fR, int \fIdestY\fR, + int \fIwidth\fR, int \fIheight\fR, + int \fIsrcX\fR, int \fIsrcY\fR); +.CE +.SS FILEWRITEPROC +.PP +\fIformatPtr->fileWriteProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageFileWriteProc\fR( + Tcl_Interp *\fIinterp\fR, + const char *\fIfileName\fR, + Tcl_Obj *\fIformat\fR, + Tk_PhotoImageBlock *\fIblockPtr\fR); +.CE +.SS STRINGWRITEPROC +.PP +\fIformatPtr->stringWriteProc\fR must match the following prototype: +.CS +typedef int \fBTk_ImageStringWriteProc\fR( + Tcl_Interp *\fIinterp\fR, + Tcl_Obj *\fIformat\fR, + Tk_PhotoImageBlock *\fIblockPtr\fR); +.CE +.PP .SH "LEGACY INTERFACE SUPPORT" .PP In Tk 8.2 and earlier, the definition of all the function pointer -- cgit v0.12 From e14eddca76cc553a3fc20350e289de6057ae23ba Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 26 Jan 2021 15:12:28 +0000 Subject: Correct groff syntax --- doc/CrtPhImgFmt.3 | 5 +---- doc/photo.n | 10 +++++----- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/CrtPhImgFmt.3 b/doc/CrtPhImgFmt.3 index 6cc478b..737ddd1 100644 --- a/doc/CrtPhImgFmt.3 +++ b/doc/CrtPhImgFmt.3 @@ -9,10 +9,7 @@ '\" Department of Computer Science, '\" Australian National University. '\" -.VS 8.7 -.TH Tk_CreatePhotoImageFormatVersion3 3 8.7 Tk "Tk Library Procedures" -.VE 8.7 -.TH Tk_CreatePhotoImageFormat 3 8.5 Tk "Tk Library Procedures" +.TH Tk_CreatePhotoImageFormat 3 8.7 Tk "Tk Library Procedures" .so man.macros .BS .SH NAME diff --git a/doc/photo.n b/doc/photo.n index f09fddf..2ec462e 100644 --- a/doc/photo.n +++ b/doc/photo.n @@ -501,9 +501,9 @@ if this option is not given, is the whole image. . If this options is specified, the data will not contain color information. All pixel data will be transformed into grayscale. -.TP .VS 8.7 -\fB\-metadata\fR \fmetadata\fR +.TP +\fB\-metadata\fR \fBmetadata\fR . Image format handler may use metadata to be included in the written file. The specified metadata is passed to the driver for inclusion in the @@ -747,18 +747,18 @@ parsed, or may use metadata to be included in image files or formats. Each image format driver supports an individual set of metadata dictionary keys. Predefined keys are: .TP -. DPI +. Horizontal image resolution in DPI as a double value. Supported by format \fBpng\fR. .TP -. aspect +. Aspect ratio horizontal divided by vertical as double value. Supported by formats \fBgif\fR and \fBpng\fR. .TP -. comment +. Image text comment. Supported by formats \fBgif\fR and \fBpng\fR. .PP -- cgit v0.12 From 8abb31f953038d32ffecc1e2ed070a2657552589 Mon Sep 17 00:00:00 2001 From: Kevin Walzer Date: Fri, 29 Jan 2021 02:01:46 +0000 Subject: Fix typo in man page --- doc/CrtPhImgFmt.3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CrtPhImgFmt.3 b/doc/CrtPhImgFmt.3 index 737ddd1..f7be5be 100644 --- a/doc/CrtPhImgFmt.3 +++ b/doc/CrtPhImgFmt.3 @@ -292,7 +292,7 @@ The following rules may give guidance to name metadata keys: Abreviation are in upper case .RE .RS -Words are in US English in small case (except propper nouns) +Words are in US English in small case (except proper nouns) .RE .RS Vertical DPI is expressed as DPI/aspect. The reason is, that some -- cgit v0.12 From d55cbcd9f8665a381c289d6cf13fe5fd2b3e0be9 Mon Sep 17 00:00:00 2001 From: fvogel Date: Sun, 31 Jan 2021 18:37:17 +0000 Subject: Fix warning when building the man page ('CrtPhImgFmt: SYNOPSIS: output-directive: ignoring .nf') --- doc/CrtPhImgFmt.3 | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/CrtPhImgFmt.3 b/doc/CrtPhImgFmt.3 index f7be5be..0b825e9 100644 --- a/doc/CrtPhImgFmt.3 +++ b/doc/CrtPhImgFmt.3 @@ -366,7 +366,6 @@ format string option or on a metadata input key. Version 2 Interface does not include the possibility for the driver to use the metadata dict for input or output. .SS SYNOPSIS -.nf \fB#include \fR .sp \fBTk_CreatePhotoImageFormat\fR(\fIformatPtr\fR) -- cgit v0.12 From d09b260e24e82ada4a1b0ea0a2383df28571a1ad Mon Sep 17 00:00:00 2001 From: oehhar Date: Sat, 6 Feb 2021 18:07:06 +0000 Subject: TIP-529 metadata dict: on unused output metadata dict, recreate it also, if driver has shared the object. --- generic/tkImgPhoto.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 37b18db..37977f1 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -2866,16 +2866,15 @@ if (formatPtr == NULL) { } /* - * Clear eventual set keys in the metadata object + * Check if driver has shared or changed the metadata tcl object. + * In this case, release and recreate it. */ if (metadataOutObj != NULL) { int dictSize; - if (TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) + if (Tcl_IsShared(metadataOutObj) + || TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) || dictSize > 0) { - /* - * Driver has modified the metadata dict, so clear it - */ Tcl_DecrRefCount(metadataOutObj); metadataOutObj = Tcl_NewDictObj(); Tcl_IncrRefCount(metadataOutObj); @@ -3069,16 +3068,15 @@ MatchStringFormat( } /* - * Clear eventual set keys in the metadata object + * Check if driver has shared or changed the metadata tcl object. + * In this case, release and recreate it. */ if (metadataOutObj != NULL) { int dictSize; - if (TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) + if (Tcl_IsShared(metadataOutObj) + || TCL_OK != Tcl_DictObjSize(interp,metadataOutObj, &dictSize) || dictSize > 0) { - /* - * Driver has modified the metadata dict, so clear it - */ Tcl_DecrRefCount(metadataOutObj); metadataOutObj = Tcl_NewDictObj(); Tcl_IncrRefCount(metadataOutObj); -- cgit v0.12 From 8f1c4eb009d892fae50c3c0036a6215a9a74a826 Mon Sep 17 00:00:00 2001 From: fvogel Date: Sun, 7 Feb 2021 12:41:55 +0000 Subject: Remove superfluous .AS call in CrtPhImgFmt.3, and fix typos and indentation. --- doc/CrtPhImgFmt.3 | 19 ++++++++----------- doc/photo.n | 10 +++++----- generic/tkImgGIF.c | 8 ++++---- generic/tkImgPNG.c | 10 +++++----- generic/tkImgPhoto.c | 5 ++--- 5 files changed, 24 insertions(+), 28 deletions(-) diff --git a/doc/CrtPhImgFmt.3 b/doc/CrtPhImgFmt.3 index 0b825e9..c7accdb 100644 --- a/doc/CrtPhImgFmt.3 +++ b/doc/CrtPhImgFmt.3 @@ -24,10 +24,7 @@ Tk_CreatePhotoImageFormat \- define new file format for photo images .sp \fBTk_CreatePhotoImageFormat\fR(\fIformatPtr\fR) .SH ARGUMENTS -.VS 8.7 .AS "const Tk_PhotoImageFormatVersion3" *formatVersion3Ptr -.VE 8.7 -.AS "const Tk_PhotoImageFormat" *formatPtr .VS 8.7 .AP "const Tk_PhotoImageFormatVersion3" *formatVersion3Ptr in Structure that defines the new file format including metadata functionality. @@ -50,7 +47,7 @@ image configuration option or the \fB\-format\fR option to the \fBread\fR and \fBwrite\fR photo image subcommands. .PP The alternate version 2 function \fBTk_CreatePhotoImageFormat\fR has -identical functionality, but does not allow to handler to get or return +identical functionality, but does not allow the handler to get or return the metadata dictionary of the image. It is described in section \fBVERSION 2 INTERFACE\fR below. .PP @@ -126,7 +123,7 @@ respectively, and return 1. Otherwise it should return 0. .PP \fIformatPtr->stringMatchProc\fR provides the address of a procedure for Tk to call when it is searching for an image file format handler -for suitable for reading data from a given string. +suitable for reading data from a given string. \fIformatPtr->stringMatchProc\fR must match the following prototype: .CS typedef int \fBTk_ImageStringMatchProcVersion3\fR( @@ -300,7 +297,7 @@ image formats may feature aspect and no resolution value. .RE .SS "METADATA INPUT" .PP -Each driver function gets a tcl object pointer \fBmetadataIn\fR as +Each driver function gets a Tcl object pointer \fBmetadataIn\fR as parameter. This parameter serves to input a metadata dict to the driver function. It may be NULL to flag that the metadata dict is empty. @@ -332,12 +329,12 @@ this purpose. .PP The image match and read driver functions may set keys in a prepared matadata dict to return them. -Those functions get a tcl object pointer \fBmetadataOut\fR as +Those functions get a Tcl object pointer \fBmetadataOut\fR as parameter. metadataOut may be NULL to indicate, that no metadata return is -attended (put, read subcommands). -metadataOut is initialized to an empty unshared dict object if -metadata return is attended (image create command, configure +attended(\fBput\fR, \fBread\fR subcommands). +\fBmetadataOut\fR is initialized to an empty unshared dict object if +metadata return is attended (\fBimage create\fR command, \fBconfigure\fR subcommand). The driver may set dict keys in this object to return metadata. If a match function succeeds, the metadataOut pointer is passed to the @@ -352,7 +349,7 @@ if (NULL != metadataOut) { The metadata keys returned by the driver are merged into the present metadata property of the image or into the metadata dict given by the \fB\-metadata\fR command line option. -On the script level, the command \fBimage create\fR and the +At the script level, the command \fBimage create\fR and the \fBconfigure\fR method may return metadata from the driver. .PP Format string options or metadata keys may influence the creation of diff --git a/doc/photo.n b/doc/photo.n index c32e57f..f30039d 100644 --- a/doc/photo.n +++ b/doc/photo.n @@ -109,7 +109,7 @@ Set the metadata dictionary of the image. Additional keys may be set within the metadata dictionary of the image, if image data is processed due to a \fB\-file\fR or \fB\-data\fR options and the driver outputs any metadata keys. -See section \fBMETADATA DICT\fR below. +See section \fBMETADATA DICTIONARY\fR below. .VE 8.7 .TP \fB\-palette \fIpalette-spec\fR @@ -307,7 +307,7 @@ information. All pixel data will be transformed into grayscale. . Image format handler may use metadata to be included in the returned data string. -The specified metadata is passed to the driver for inclusion in the +The specified \fImetadata\fR is passed to the driver for inclusion in the data. If no \fB\-metadata\fR option is given, the current metadata of the image is used. @@ -349,7 +349,7 @@ word. Also, instead of braces, double quotes may be used for quoting. .TP \fB\-metadata\fR \fImetadata\fR . -A specified metadata is passed to the image format driver when interpreting +A specified \fImetadata\fR is passed to the image format driver when interpreting the data. Note: The current metadata of the image is not passed to the format driver and is not changed by the command. @@ -403,7 +403,7 @@ image in the image file. .TP \fB\-metadata\fR \fImetadata\fR . -A specified metadata is passed to the image format driver when interpreting +A specified \fImetadata\fR is passed to the image format driver when interpreting the data. Note: The current metadata of the image is not passed to the format driver and is not changed by the command. @@ -506,7 +506,7 @@ information. All pixel data will be transformed into grayscale. \fB\-metadata\fR \fBmetadata\fR . Image format handler may use metadata to be included in the written file. -The specified metadata is passed to the driver for inclusion in the +The specified \fImetadata\fR is passed to the driver for inclusion in the file. If no \fB\-metadata\fR option is given, the current metadata of the image is used. diff --git a/generic/tkImgGIF.c b/generic/tkImgGIF.c index 588ca97..8702970 100644 --- a/generic/tkImgGIF.c +++ b/generic/tkImgGIF.c @@ -356,10 +356,10 @@ static void FlushChar(GIFState_t *statePtr); static int FileMatchGIF( - TCL_UNUSED(Tcl_Interp *), /* not used */ + TCL_UNUSED(Tcl_Interp *), /* not used */ Tcl_Channel chan, /* The image file, open for reading. */ TCL_UNUSED(const char *), /* The name of the image file. */ - TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ + TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned @@ -1181,8 +1181,8 @@ ReadImage( Tcl_Channel chan, int len, int rows, unsigned char cmap[MAXCOLORMAPSIZE][4], - TCL_UNUSED(int), - TCL_UNUSED(int), + TCL_UNUSED(int), + TCL_UNUSED(int), int interlace, int transparent) { diff --git a/generic/tkImgPNG.c b/generic/tkImgPNG.c index 5e802d5..5fd741c 100644 --- a/generic/tkImgPNG.c +++ b/generic/tkImgPNG.c @@ -2818,7 +2818,7 @@ FileMatchPNG( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Channel chan, /* The image file, open for reading. */ TCL_UNUSED(const char *), /* The name of the image file. */ - TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ + TCL_UNUSED(Tcl_Obj *), /* User-specified format object, or NULL. */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, int *heightPtr, /* The dimensions of the image are returned @@ -2870,7 +2870,7 @@ FileReadPNG( Tk_PhotoHandle imageHandle, /* The photo image to write into. */ int destX, int destY, /* Coordinates of top-left pixel in photo * image to be written to. */ - TCL_UNUSED(int), /* Dimensions of block of photo image to be + TCL_UNUSED(int), /* Dimensions of block of photo image to be * written to. */ TCL_UNUSED(int), TCL_UNUSED(int), /* Coordinates of top-left pixel to be used in @@ -2925,7 +2925,7 @@ static int StringMatchPNG( Tcl_Interp *interp, /* Interpreter to use for reporting errors. */ Tcl_Obj *pObjData, /* the object containing the image data */ - TCL_UNUSED(Tcl_Obj *), /* the image format object, or NULL */ + TCL_UNUSED(Tcl_Obj *), /* the image format object, or NULL */ TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ int *widthPtr, /* where to put the string width */ int *heightPtr, /* where to put the string height */ @@ -2974,7 +2974,7 @@ StringReadPNG( TCL_UNUSED(Tcl_Obj *), /* metadata input, may be NULL */ Tk_PhotoHandle imageHandle, /* the image to write this data into */ int destX, int destY, /* The rectangular region of the */ - TCL_UNUSED(int), /* image to copy */ + TCL_UNUSED(int), /* image to copy */ TCL_UNUSED(int), TCL_UNUSED(int), TCL_UNUSED(int), @@ -3514,7 +3514,7 @@ WriteExtraChunks( * Add a pHYs chunk if there is metadata for DPI and/or aspect * aspect = PPUy / PPUx * DPI = PPUx * 0.0254 - * The physical chunc consists of: + * The physical chunk consists of: * - Points per meter in x direction (32 bit) * - Points per meter in x direction (32 bit) * - Unit specifier: 0: no unit (only aspect), 1: Points per meter diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 37977f1..476149d 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -2090,7 +2090,7 @@ ImgPhotoConfigureModel( } if (metadataInObj) { /* - * make -metadata a dict. + * Make -metadata a dict. * Take also empty metadatas as this may be a sign to replace * existing metadata. */ @@ -2246,7 +2246,6 @@ ImgPhotoConfigureModel( goto errorExit; } } else { - if (imageFormatVersion3->stringReadProc(interp, tempdata, tempformat, modelPtr->metadata, (Tk_PhotoHandle) modelPtr, 0, 0, imageWidth, imageHeight, 0, 0, metadataOutObj) != TCL_OK) { @@ -2866,7 +2865,7 @@ if (formatPtr == NULL) { } /* - * Check if driver has shared or changed the metadata tcl object. + * Check if driver has shared or changed the metadata Tcl object. * In this case, release and recreate it. */ -- cgit v0.12 From a6fcc32ebf1951f7cbe9c686563b962a1a1f72a1 Mon Sep 17 00:00:00 2001 From: fvogel Date: Sat, 20 Feb 2021 14:11:19 +0000 Subject: Fix [be9cade996]: sigsegv in tkwait. Propagate the fix from https://core.tcl-lang.org/tcl/tktview/16828b3744521541660af48501fa06e63e564ad3 from vwait to tkwait. --- generic/tkCmds.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/generic/tkCmds.c b/generic/tkCmds.c index 193c3d6..c5f0a50 100644 --- a/generic/tkCmds.c +++ b/generic/tkCmds.c @@ -1120,14 +1120,16 @@ Tk_TkwaitObjCmd( static char * WaitVariableProc( ClientData clientData, /* Pointer to integer to set to 1. */ - TCL_UNUSED(Tcl_Interp *), /* Interpreter containing variable. */ - TCL_UNUSED(const char *), /* Name of variable. */ + Tcl_Interp *interp, /* Interpreter containing variable. */ + const char *name1, /* Name of variable. */ TCL_UNUSED(const char *), /* Second part of variable name. */ TCL_UNUSED(int)) /* Information about what happened. */ { int *donePtr = (int *)clientData; *donePtr = 1; + Tcl_UntraceVar(interp, name1, TCL_TRACE_WRITES|TCL_TRACE_UNSETS, + WaitVariableProc, clientData); return NULL; } -- cgit v0.12 From 9988b9f11de5164413915d7bfa34d94da8f7d3f3 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 25 Feb 2021 08:27:44 +0000 Subject: Fix [234ee4f83b]: macOS Crash - Pasting text with special characters into Text widget --- macosx/tkMacOSXClipboard.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/macosx/tkMacOSXClipboard.c b/macosx/tkMacOSXClipboard.c index bae2b89..b8159d6 100644 --- a/macosx/tkMacOSXClipboard.c +++ b/macosx/tkMacOSXClipboard.c @@ -137,13 +137,7 @@ TkSelGetSelection( string = [pb stringForType:type]; } if (string) { - if (target == dispPtr->utf8Atom) { - result = proc(clientData, interp, string.UTF8String); - } else if (target == XA_STRING) { - const char *latin1 = [string - cStringUsingEncoding:NSISOLatin1StringEncoding]; - result = proc(clientData, interp, latin1); - } + result = proc(clientData, interp, string.UTF8String); } } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( -- cgit v0.12 From 4da53f29acd9f944a31ca57f1666ce0d4f935f28 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 25 Feb 2021 10:08:35 +0000 Subject: ClipboardGetProc() should never assume the bytes it receives are valid UTF-8 --- generic/tkClipboard.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generic/tkClipboard.c b/generic/tkClipboard.c index 703ef41..8bc4237 100644 --- a/generic/tkClipboard.c +++ b/generic/tkClipboard.c @@ -708,7 +708,13 @@ ClipboardGetProc( * used). */ const char *portion) /* New information to be appended. */ { - Tcl_DStringAppend((Tcl_DString *) clientData, portion, -1); + Tcl_Encoding utf8 = Tcl_GetEncoding(NULL, "utf-8"); + Tcl_DString ds; + + Tcl_ExternalToUtfDString(utf8, portion, -1, &ds); + Tcl_DStringAppend((Tcl_DString *) clientData, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds)); + Tcl_DStringFree(&ds); + Tcl_FreeEncoding(utf8); return TCL_OK; } -- cgit v0.12 From 92ada87caf02929cb12fc0430474176a15ffde66 Mon Sep 17 00:00:00 2001 From: fvogel Date: Thu, 25 Feb 2021 14:04:08 +0000 Subject: Fix reference of tests --- tests/imgPhoto.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/imgPhoto.test b/tests/imgPhoto.test index 1ab5a65..544b2e6 100644 --- a/tests/imgPhoto.test +++ b/tests/imgPhoto.test @@ -57,8 +57,8 @@ # Tk_PhotoPutBlock_Panic no tests, probably none needed # Tk_PhotoPutZoomedBlock_Panic no tests, probably none needed # Tk_PhotoSetSize_Panic no tests, probably none needed -# Tk_PhotoGetMetadata: imgPhoto-19.* -# Tk_PhotoSetMetadata: imgPhoto-20.* +# Tk_PhotoGetMetadata: imgPhoto-21.* +# Tk_PhotoSetMetadata: imgPhoto-22.* #-------------------------------------------------------------------------- # -- cgit v0.12 From 0b626e656a1ba14a266ebabb57bb403195dd9e9e Mon Sep 17 00:00:00 2001 From: marc_culler Date: Sun, 28 Feb 2021 03:55:53 +0000 Subject: Fix [3b9296159c]: The NULL pointer guard in XSetupEvent is misplaced. (Why it is needed remains a mystery.) --- macosx/tkMacOSXKeyEvent.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index 8d93cd4..d092749 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -594,11 +594,12 @@ static void setupXEvent(XEvent *xEvent, Tk_Window tkwin, NSUInteger modifiers) { unsigned int state = 0; - Display *display = Tk_Display(tkwin); + Display *display; if (tkwin == NULL) { return; } + display = Tk_Display(tkwin); if (modifiers) { state = (modifiers & NSAlphaShiftKeyMask ? LockMask : 0) | (modifiers & NSShiftKeyMask ? ShiftMask : 0) | -- cgit v0.12 From f34acfade25075e5edea71801578a4025292533c Mon Sep 17 00:00:00 2001 From: fvogel Date: Sun, 28 Feb 2021 08:53:39 +0000 Subject: Binding to all in test event-9 introduces coupling between this test and other tests, for instance textTag-18.1, which then fails when event.test and textTag.test are run together. Fix this binding to .top only, which produces the same result (see the 'bind' man page: 'If the tag is the name of a toplevel window the binding applies to the toplevel window and all its internal windows.'). --- tests/event.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/event.test b/tests/event.test index d71b664..e2ca9f5 100644 --- a/tests/event.test +++ b/tests/event.test @@ -872,7 +872,7 @@ test event-9 {no event is generated for the container window when its } -body { toplevel .top pack propagate .top 0 - bind all {lappend res %W} + bind .top {lappend res %W} pack [frame .top.f -bg green -width 50 -height 50] -anchor se -side bottom update event generate .top.f -warp 1 -x 25 -y 25 ; # sent to .top and .top.f -- cgit v0.12 From 3b982fb8e11165c90a1376a4c7eb1f60a251c89b Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 1 Mar 2021 08:11:05 +0000 Subject: Fix a few "$tcl_platform(platform) ==" which should have been "[tk windowingsystem] ==". On Cygwin this makes a difference --- tests/textDisp.test | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/textDisp.test b/tests/textDisp.test index 3022a54..e7df886 100644 --- a/tests/textDisp.test +++ b/tests/textDisp.test @@ -598,7 +598,7 @@ test textDisp-4.4 {UpdateDisplayInfo, wrap-mode "none"} {textfonts} { list [.t bbox 2.0] [.t bbox 2.25] [.t bbox 3.0] $tk_textRelayout } [list [list 5 [expr {$fixedDiff + 18}] 7 $fixedHeight] {} [list 5 [expr {2*$fixedDiff + 31}] 7 $fixedHeight] {1.0 2.0 3.0}] test textDisp-4.5 {UpdateDisplayInfo, tiny window} {textfonts} { - if {$tcl_platform(platform) == "windows"} { + if {[tk windowingsystem] == "win32"} { wm overrideredirect . 1 } wm geom . 103x$height @@ -609,7 +609,7 @@ test textDisp-4.5 {UpdateDisplayInfo, tiny window} {textfonts} { updateText list [.t bbox 2.0] [.t bbox 2.1] [.t bbox 3.0] $tk_textRelayout } [list [list 5 [expr {$fixedDiff + 18}] 1 $fixedHeight] {} [list 5 [expr {2*$fixedDiff + 31}] 1 $fixedHeight] {1.0 2.0 3.0}] -if {$tcl_platform(platform) == "windows"} { +if {[tk windowingsystem] == "win32"} { wm overrideredirect . 0 } test textDisp-4.6 {UpdateDisplayInfo, tiny window} { @@ -620,7 +620,7 @@ test textDisp-4.6 {UpdateDisplayInfo, tiny window} { # the overrideredirect on "." confuses the window manager and # causes subsequent tests to fail. - if {$tcl_platform(platform) == "windows"} { + if {[tk windowingsystem] == "win32"} { wm overrideredirect . 1 } frame .f2 -width 20 -height 100 @@ -652,7 +652,7 @@ test textDisp-4.7 {UpdateDisplayInfo, filling in extra vertical space} { # the overrideredirect on "." confuses the window manager and # causes subsequent tests to fail. - if {$tcl_platform(platform) == "windows"} { + if {[tk windowingsystem] == "win32"} { wm overrideredirect . 1 } .t delete 1.0 end @@ -3318,7 +3318,7 @@ test textDisp-24.15 {TkTextCharLayoutProc, -wrap none} {textfonts} { list [.t bbox 1.19] [.t bbox 1.20] } [list [list 136 3 7 $fixedHeight] [list 143 3 3 $fixedHeight]] test textDisp-24.16 {TkTextCharLayoutProc, no chars fit} {textfonts} { - if {$tcl_platform(platform) == "windows"} { + if {[tk windowingsystem] == "win32"} { wm overrideredirect . 1 } .t configure -wrap char @@ -3328,7 +3328,7 @@ test textDisp-24.16 {TkTextCharLayoutProc, no chars fit} {textfonts} { updateText list [.t bbox 1.0] [.t bbox 1.1] [.t bbox 1.2] } [list [list 3 3 1 $fixedHeight] [list 3 [expr {$fixedDiff + 16}] 1 $fixedHeight] [list 3 [expr {2*$fixedDiff + 29}] 1 $fixedHeight]] -if {$tcl_platform(platform) == "windows"} { +if {[tk windowingsystem] == "win32"} { wm overrideredirect . 0 } test textDisp-24.17 {TkTextCharLayoutProc, -wrap word} {textfonts} { -- cgit v0.12 From 0b82f5dfc3f107abe09656652dd68ec0b616ddf7 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 1 Mar 2021 15:59:34 +0000 Subject: Fix event-9 testcase on Windows on Tk 8.6. See also [85c8397412] --- tests/event.test | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/event.test b/tests/event.test index e2ca9f5..983743c 100644 --- a/tests/event.test +++ b/tests/event.test @@ -876,8 +876,7 @@ test event-9 {no event is generated for the container window when its pack [frame .top.f -bg green -width 50 -height 50] -anchor se -side bottom update event generate .top.f -warp 1 -x 25 -y 25 ; # sent to .top and .top.f - after 50 ; # Win specific - wait for SendInput to be executed - update ; # idletasks not enough + update idletasks ; after 50 destroy .top.f ; # no event sent update set res -- cgit v0.12 From cd0bb71d21281005bb453d48682714e4f77bc2d0 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Mon, 1 Mar 2021 16:24:40 +0000 Subject: Need extra "update" after "after 50" --- tests/event.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/event.test b/tests/event.test index 983743c..bf618ac 100644 --- a/tests/event.test +++ b/tests/event.test @@ -876,7 +876,7 @@ test event-9 {no event is generated for the container window when its pack [frame .top.f -bg green -width 50 -height 50] -anchor se -side bottom update event generate .top.f -warp 1 -x 25 -y 25 ; # sent to .top and .top.f - update idletasks ; after 50 + update idletasks ; after 50 ; update destroy .top.f ; # no event sent update set res -- cgit v0.12 From 3b2587e2008e430f2b44a9ef8a28537db23d2b02 Mon Sep 17 00:00:00 2001 From: culler Date: Mon, 1 Mar 2021 17:34:46 +0000 Subject: Fix [1626ed65b8]: Aqua aborts when Command-backquote is pressed on a Spanish keyboard. --- macosx/tkMacOSXKeyEvent.c | 3 +++ macosx/tkMacOSXMenu.c | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index d092749..c4e613d 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -393,6 +393,9 @@ static NSUInteger textInputModifiers; replacementRange: (NSRange)repRange { TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); + if (!winPtr) { + return; + } Tk_Window focusWin = (Tk_Window)winPtr->dispPtr->focusPtr; NSString *temp; NSString *str; diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 1af18c4..470d63e 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -192,6 +192,25 @@ TKBackgroundLoop *backgroundLoop = nil; { return (_tkSpecial == special); } + +/* + * The performKeyEquivalent method is being overridden here to work around an + * Apple bug revealed by [1626ed65b8]. Sometimes the AppKit will call this + * method and pass it a KeyDown event which has no characters. This causes an + * NSInvalidArgumentException in the default implementation of the method. + * These malformed events are not sent to the Tk application but are handled + * directly by the system using this method. So far this bug has only been + * seen when pressing Command-backquote on a Spanish keyboard. + */ + +- (BOOL)performKeyEquivalent:(NSEvent *)event +{ + if (event.characters.length == 0) { + TKLog(@"performKeyEquivalent: the key event has no characters."); + return NO; + } + return [super performKeyEquivalent:event]; +} @end @implementation TKMenu(TKMenuPrivate) -- cgit v0.12 From 6eed1c966158c4272228e45d6a27dc17b0f47016 Mon Sep 17 00:00:00 2001 From: fvogel Date: Mon, 1 Mar 2021 19:59:23 +0000 Subject: Fix [9eb2c5c90c]: warning in tkImgPhoto.c --- generic/tkImgPhoto.c | 1 - 1 file changed, 1 deletion(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 476149d..249b5cf 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -1058,7 +1058,6 @@ ImgPhotoCmd( } case PHOTO_READ: { Tcl_Obj *format; - int result; /* * photo read command - first parse the options specified. -- cgit v0.12 From 2a1b7d06c5a547fcead30d6546c1c29306f0507b Mon Sep 17 00:00:00 2001 From: culler Date: Tue, 2 Mar 2021 19:02:01 +0000 Subject: Adjust the key event processing logic to handle dead keys being used as menu accelerators. This prevents the crash at exit. --- macosx/tkMacOSXKeyEvent.c | 22 +++++++++++++--------- macosx/tkMacOSXMenu.c | 25 +++++++++++++++++-------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/macosx/tkMacOSXKeyEvent.c b/macosx/tkMacOSXKeyEvent.c index c4e613d..a46a646 100644 --- a/macosx/tkMacOSXKeyEvent.c +++ b/macosx/tkMacOSXKeyEvent.c @@ -92,26 +92,33 @@ static NSUInteger textInputModifiers; */ if (type == NSKeyUp || type == NSKeyDown) { - if ([[theEvent characters] length] > 0) { - keychar = [[theEvent characters] characterAtIndex:0]; + NSString *characters = [theEvent characters]; + if (characters.length > 0) { + keychar = [characters characterAtIndex:0]; /* * Currently, real keys always send BMP characters, but who knows? */ if (CFStringIsSurrogateHighCharacter(keychar)) { - UniChar lowChar = [[theEvent characters] characterAtIndex:1]; + UniChar lowChar = [characters characterAtIndex:1]; keychar = CFStringGetLongCharacterForSurrogatePair( keychar, lowChar); } } else { /* - * This is a dead key, such as Option-e, so it should go to the - * TextInputClient. + * This is a dead key, such as Option-e, so it usually should get + * passed to the TextInputClient. But if it has a Command modifier + * then it is not functioning as a dead key and should not be + * handled by the TextInputClient. See ticket [1626ed65b8] and the + * method performKeyEquivalent which is implemented in + * tkMacOSXMenu.c. */ - use_text_input = YES; + if (!(modifiers & NSCommandKeyMask)) { + use_text_input = YES; + } } /* @@ -393,9 +400,6 @@ static NSUInteger textInputModifiers; replacementRange: (NSRange)repRange { TkWindow *winPtr = TkMacOSXGetTkWindow([self window]); - if (!winPtr) { - return; - } Tk_Window focusWin = (Tk_Window)winPtr->dispPtr->focusPtr; NSString *temp; NSString *str; diff --git a/macosx/tkMacOSXMenu.c b/macosx/tkMacOSXMenu.c index 470d63e..c45f967 100644 --- a/macosx/tkMacOSXMenu.c +++ b/macosx/tkMacOSXMenu.c @@ -194,19 +194,28 @@ TKBackgroundLoop *backgroundLoop = nil; } /* - * The performKeyEquivalent method is being overridden here to work around an - * Apple bug revealed by [1626ed65b8]. Sometimes the AppKit will call this - * method and pass it a KeyDown event which has no characters. This causes an - * NSInvalidArgumentException in the default implementation of the method. - * These malformed events are not sent to the Tk application but are handled - * directly by the system using this method. So far this bug has only been - * seen when pressing Command-backquote on a Spanish keyboard. + * There are cases where a KeyEquivalent (aka menu accelerator) is defined for + * a "dead key", i.e. a key which does not have an associated character but is + * only meant to be the start of a composition sequence. For example, on a + * Spanish keyboard both the ' and the ` keys are dead keys used to place + * accents over letters. But ⌘` is a standard KeyEquivalent which cycles + * through the open windows of an application, changing the focus to the next + * window. + * + * The performKeyEquivalent callback method is being overridden here to work + * around a bug reported in [1626ed65b8]. When a dead key that is also as a + * KeyEquivalent is pressed, a KeyDown event with no characters is passed to + * performKeyEquivalent. The default implementation provided by Apple will + * cause that event to be routed to some private methods of NSMenu which raise + * NSInvalidArgumentException, causing an abort. Returning NO in such a case + * prevents the abort, but does not prevent the KeyEquivalent action from being + * invoked, presumably because the event does get correctly handled higher in + * the responder chain. */ - (BOOL)performKeyEquivalent:(NSEvent *)event { if (event.characters.length == 0) { - TKLog(@"performKeyEquivalent: the key event has no characters."); return NO; } return [super performKeyEquivalent:event]; -- cgit v0.12 From 650424e84adec49580e1316c15c907a97d7563a6 Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Thu, 4 Mar 2021 20:39:24 +0000 Subject: Fix [8227d46f9d]: imgPhoto test failures and [236dfa6057]: imgPNG-4.4 test failure, which were essentially the same bug: a wrongly placed #endif --- generic/tkImgPhoto.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generic/tkImgPhoto.c b/generic/tkImgPhoto.c index 249b5cf..5352a15 100644 --- a/generic/tkImgPhoto.c +++ b/generic/tkImgPhoto.c @@ -1513,6 +1513,7 @@ readCleanup: } } } +#endif if (imageFormat == NULL) { oldformat = 0; for (imageFormatVersion3 = tsdPtr->formatListVersion3; @@ -1528,7 +1529,6 @@ readCleanup: } } } -#endif if (usedExt && !matched) { /* * If we didn't find one and we're using file extensions as the -- cgit v0.12 From 0a9213075871ce2995c09448b33795b1ae53118e Mon Sep 17 00:00:00 2001 From: "jan.nijtmans" Date: Fri, 5 Mar 2021 07:39:10 +0000 Subject: Add Linux builds against Tcl 8.7 and 9.0 too --- .github/workflows/linux-with-tcl8-build.yml | 186 ++++++++++++++++++++++++++++ .github/workflows/linux-with-tcl9-build.yml | 186 ++++++++++++++++++++++++++++ 2 files changed, 372 insertions(+) create mode 100644 .github/workflows/linux-with-tcl8-build.yml create mode 100644 .github/workflows/linux-with-tcl9-build.yml diff --git a/.github/workflows/linux-with-tcl8-build.yml b/.github/workflows/linux-with-tcl8-build.yml new file mode 100644 index 0000000..4e56b64 --- /dev/null +++ b/.github/workflows/linux-with-tcl8-build.yml @@ -0,0 +1,186 @@ +name: Linux (with Tcl 8.7) +on: [push] +defaults: + run: + shell: bash + working-directory: tk/unix +env: + ERROR_ON_FAILURES: 1 +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + compiler: + - "gcc" + - "clang" + cfgopt: + - "" + - "CFLAGS=-DTK_NO_DEPRECATED=1" + - "--disable-shared" + - "--disable-xft" + - "--disable-xss" + - "--enable-symbols" + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: tk + - name: Checkout Tcl + uses: actions/checkout@v2 + with: + repository: tcltk/tcl + ref: core-8-branch + path: tcl + - name: Setup Environment (compiler=${{ matrix.compiler }}) + run: | + sudo apt-get install libxss-dev + mkdir "$HOME/install dir" + touch tk/doc/man.macros tk/generic/tkStubInit.c + echo "CFGOPT=$CFGOPT" >> $GITHUB_ENV + echo "CC=$COMPILER" >> $GITHUB_ENV + echo "TOOL_DIR=$(cd tcl/tools;pwd)" >> $GITHUB_ENV + echo "BUILD_CONFIG_ID=$OPTS" >> $GITHUB_ENV + working-directory: "." + env: + CFGOPT: ${{ matrix.cfgopt }} + COMPILER: ${{ matrix.compiler }} + OPTS: ${{ matrix.compiler }}${{ matrix.cfgopt }} + - name: Configure and Build Tcl + run: | + ./configure ${CFGOPT} "--prefix=$HOME/install dir" || { + cat config.log + echo "::warning::Failure during Tcl Configure" + exit 1 + } + make all install || { + echo "::warning::Failure during Tcl Build" + exit 1 + } + echo "TCL_CONFIG_PATH=`pwd`" >> $GITHUB_ENV + working-directory: tcl/unix + - name: Configure (opts=${{ matrix.cfgopt }}) + run: | + ./configure $CFGOPT --with-tcl=$TCL_CONFIG_PATH "--prefix=$HOME/install dir" || { + cat config.log + echo "::error::Failure during Configure" + exit 1 + } + - name: Build + run: | + make binaries libraries || { + echo "::error::Failure during Build" + exit 1 + } + - name: Build Test Harness + run: | + make tktest || { + echo "::error::Failure during Build" + exit 1 + } + - name: Test-Drive Installation + run: | + make install || { + echo "::error::Failure during Install" + exit 1 + } + - name: Create Distribution Package + run: | + make dist || { + echo "::error::Failure during Distribute" + exit 1 + } + - name: Convert Documentation to HTML + run: | + make html-tk TOOL_DIR=$TOOL_DIR || { + echo "::error::Failure during Distribute" + exit 1 + } + - name: Discover Version ID + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + run: | + cd /tmp/dist + echo "VERSION=`ls -d tk* | sed 's/tk//'`" >> $GITHUB_ENV + - name: Upload Source Distribution + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + uses: actions/upload-artifact@v2 + with: + name: Tk ${{ env.VERSION }} Source distribution (unofficial) + path: | + /tmp/dist/tk* + !/tmp/dist/tk*/html/** + - name: Upload Documentation Distribution + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + uses: actions/upload-artifact@v2 + with: + name: Tk ${{ env.VERSION }} HTML documentation (unofficial) + path: /tmp/dist/tk*/html + test: + runs-on: ubuntu-20.04 + strategy: + matrix: + compiler: + - "gcc" + cfgopt: + - "" + - "--enable-symbols" + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: tk + - name: Checkout Tcl + uses: actions/checkout@v2 + with: + repository: tcltk/tcl + ref: core-8-branch + path: tcl + - name: Setup Environment (compiler=${{ matrix.compiler }}) + run: | + sudo apt-get install libxss-dev xvfb + mkdir "$HOME/install dir" + touch tk/doc/man.macros tk/generic/tkStubInit.c + echo "CFGOPT=$CFGOPT" >> $GITHUB_ENV + echo "CC=$COMPILER" >> $GITHUB_ENV + working-directory: "." + env: + CFGOPT: ${{ matrix.cfgopt }} + COMPILER: ${{ matrix.compiler }} + - name: Configure and Build Tcl + run: | + ./configure ${CFGOPT} "--prefix=$HOME/install dir" || { + cat config.log + echo "::warning::Failure during Tcl Configure" + exit 1 + } + make all install || { + echo "::warning::Failure during Tcl Build" + exit 1 + } + echo "TCL_CONFIG_PATH=`pwd`" >> $GITHUB_ENV + working-directory: tcl/unix + - name: Configure ${{ matrix.cfgopt }} + run: | + ./configure $CFGOPT --with-tcl=$TCL_CONFIG_PATH "--prefix=$HOME/install dir" || { + cat config.log + echo "::error::Failure during Configure" + exit 1 + } + - name: Build + run: | + make binaries libraries tktest || { + echo "::error::Failure during Build" + exit 1 + } + - name: Run Tests + run: | + xvfb-run --auto-servernum make test-classic | tee out-classic.txt + xvfb-run --auto-servernum make test-ttk | tee out-ttk.txt + grep -q "Failed 0" out-classic.txt || { + echo "::error::Failure during Test" + exit 1 + } + grep -q "Failed 0" out-ttk.txt || { + echo "::error::Failure during Test" + exit 1 + } diff --git a/.github/workflows/linux-with-tcl9-build.yml b/.github/workflows/linux-with-tcl9-build.yml new file mode 100644 index 0000000..5335708 --- /dev/null +++ b/.github/workflows/linux-with-tcl9-build.yml @@ -0,0 +1,186 @@ +name: Linux (with Tcl 9.0) +on: [push] +defaults: + run: + shell: bash + working-directory: tk/unix +env: + ERROR_ON_FAILURES: 1 +jobs: + build: + runs-on: ubuntu-20.04 + strategy: + matrix: + compiler: + - "gcc" + - "clang" + cfgopt: + - "" + - "CFLAGS=-DTK_NO_DEPRECATED=1" + - "--disable-shared" + - "--disable-xft" + - "--disable-xss" + - "--enable-symbols" + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: tk + - name: Checkout Tcl + uses: actions/checkout@v2 + with: + repository: tcltk/tcl + ref: main + path: tcl + - name: Setup Environment (compiler=${{ matrix.compiler }}) + run: | + sudo apt-get install libxss-dev + mkdir "$HOME/install dir" + touch tk/doc/man.macros tk/generic/tkStubInit.c + echo "CFGOPT=$CFGOPT" >> $GITHUB_ENV + echo "CC=$COMPILER" >> $GITHUB_ENV + echo "TOOL_DIR=$(cd tcl/tools;pwd)" >> $GITHUB_ENV + echo "BUILD_CONFIG_ID=$OPTS" >> $GITHUB_ENV + working-directory: "." + env: + CFGOPT: ${{ matrix.cfgopt }} + COMPILER: ${{ matrix.compiler }} + OPTS: ${{ matrix.compiler }}${{ matrix.cfgopt }} + - name: Configure and Build Tcl + run: | + ./configure ${CFGOPT} "--prefix=$HOME/install dir" || { + cat config.log + echo "::warning::Failure during Tcl Configure" + exit 1 + } + make all install || { + echo "::warning::Failure during Tcl Build" + exit 1 + } + echo "TCL_CONFIG_PATH=`pwd`" >> $GITHUB_ENV + working-directory: tcl/unix + - name: Configure (opts=${{ matrix.cfgopt }}) + run: | + ./configure $CFGOPT --with-tcl=$TCL_CONFIG_PATH "--prefix=$HOME/install dir" || { + cat config.log + echo "::error::Failure during Configure" + exit 1 + } + - name: Build + run: | + make binaries libraries || { + echo "::error::Failure during Build" + exit 1 + } + - name: Build Test Harness + run: | + make tktest || { + echo "::error::Failure during Build" + exit 1 + } + - name: Test-Drive Installation + run: | + make install || { + echo "::error::Failure during Install" + exit 1 + } + - name: Create Distribution Package + run: | + make dist || { + echo "::error::Failure during Distribute" + exit 1 + } + - name: Convert Documentation to HTML + run: | + make html-tk TOOL_DIR=$TOOL_DIR || { + echo "::error::Failure during Distribute" + exit 1 + } + - name: Discover Version ID + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + run: | + cd /tmp/dist + echo "VERSION=`ls -d tk* | sed 's/tk//'`" >> $GITHUB_ENV + - name: Upload Source Distribution + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + uses: actions/upload-artifact@v2 + with: + name: Tk ${{ env.VERSION }} Source distribution (unofficial) + path: | + /tmp/dist/tk* + !/tmp/dist/tk*/html/** + - name: Upload Documentation Distribution + if: ${{ env.BUILD_CONFIG_ID == 'gcc' }} + uses: actions/upload-artifact@v2 + with: + name: Tk ${{ env.VERSION }} HTML documentation (unofficial) + path: /tmp/dist/tk*/html + test: + runs-on: ubuntu-20.04 + strategy: + matrix: + compiler: + - "gcc" + cfgopt: + - "" + - "--enable-symbols" + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + path: tk + - name: Checkout Tcl + uses: actions/checkout@v2 + with: + repository: tcltk/tcl + ref: main + path: tcl + - name: Setup Environment (compiler=${{ matrix.compiler }}) + run: | + sudo apt-get install libxss-dev xvfb + mkdir "$HOME/install dir" + touch tk/doc/man.macros tk/generic/tkStubInit.c + echo "CFGOPT=$CFGOPT" >> $GITHUB_ENV + echo "CC=$COMPILER" >> $GITHUB_ENV + working-directory: "." + env: + CFGOPT: ${{ matrix.cfgopt }} + COMPILER: ${{ matrix.compiler }} + - name: Configure and Build Tcl + run: | + ./configure ${CFGOPT} "--prefix=$HOME/install dir" || { + cat config.log + echo "::warning::Failure during Tcl Configure" + exit 1 + } + make all install || { + echo "::warning::Failure during Tcl Build" + exit 1 + } + echo "TCL_CONFIG_PATH=`pwd`" >> $GITHUB_ENV + working-directory: tcl/unix + - name: Configure ${{ matrix.cfgopt }} + run: | + ./configure $CFGOPT --with-tcl=$TCL_CONFIG_PATH "--prefix=$HOME/install dir" || { + cat config.log + echo "::error::Failure during Configure" + exit 1 + } + - name: Build + run: | + make binaries libraries tktest || { + echo "::error::Failure during Build" + exit 1 + } + - name: Run Tests + run: | + xvfb-run --auto-servernum make test-classic | tee out-classic.txt + xvfb-run --auto-servernum make test-ttk | tee out-ttk.txt + grep -q "Failed 0" out-classic.txt || { + echo "::error::Failure during Test" + exit 1 + } + grep -q "Failed 0" out-ttk.txt || { + echo "::error::Failure during Test" + exit 1 + } -- cgit v0.12