diff --git a/libvips/foreign/libnsgif/README-ns.md b/libvips/foreign/libnsgif/README-ns.md index cb380c34..63ccdc7f 100644 --- a/libvips/foreign/libnsgif/README-ns.md +++ b/libvips/foreign/libnsgif/README-ns.md @@ -49,10 +49,15 @@ Now you can load the GIF source data into the nsgif object with This scans the source data and decodes information about each frame, however it doesn't decode any of the bitmap data for the frames. The client may call -`nsgif_data_scan()` multiple times as source data is fetched. Once the -function has returned `NSGIF_OK` it has enough data to display at least one -frame. The early frames can be decoded before the later frames are scanned. -Frames have to be scanned before they can be decoded. +`nsgif_data_scan()` multiple times as source data is fetched. The early frames +can be decoded before the later frames are scanned. Frames have to be scanned +before they can be decoded. + +This function will sometimes return an error. That is OK, and even expected. +It is fine to proceed to decoding any frames that are available after a scan. +Some errors indicate that there is a flaw in the source GIF data (not at all +uncommon, GIF is an ancient format that has had many broken encoders), or that +it has reached the end of the source data. > **Note**: The client must not free the data until after calling > `nsgif_destroy()`. You can move the data, e.g. if you realloc to a bigger diff --git a/libvips/foreign/libnsgif/README.md b/libvips/foreign/libnsgif/README.md index 16a095b3..94f5ee0c 100644 --- a/libvips/foreign/libnsgif/README.md +++ b/libvips/foreign/libnsgif/README.md @@ -8,7 +8,7 @@ but within the libvips build system. Run `./update.sh` to update this copy of libnsgif from the upstream repo. It will also patch libnsgif.c to prevent it modifying the input. -Last updated 3 Mar 2022. +Last updated 8 Mar 2022. # To do diff --git a/libvips/foreign/libnsgif/gif.c b/libvips/foreign/libnsgif/gif.c index 4fdaaac1..287b6323 100644 --- a/libvips/foreign/libnsgif/gif.c +++ b/libvips/foreign/libnsgif/gif.c @@ -770,6 +770,34 @@ static nsgif_error nsgif__parse_extension_graphic_control( return NSGIF_OK; } +/** + * Check an app ext identifier and authentication code for loop count extension. + * + * \param[in] data The data to decode. + * \param[in] len Byte length of data. + * \return true if extension is a loop count extension. + */ +static bool nsgif__app_ext_is_loop_count( + const uint8_t *data, + size_t len) +{ + enum { + EXT_LOOP_COUNT_BLOCK_SIZE = 0x0b, + }; + + assert(len > 13); + (void)(len); + + if (data[1] == EXT_LOOP_COUNT_BLOCK_SIZE) { + if (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0 || + strncmp((const char *)data + 2, "ANIMEXTS1.0", 11) == 0) { + return true; + } + } + + return false; +} + /** * Parse the application extension * @@ -796,10 +824,24 @@ static nsgif_error nsgif__parse_extension_application( return NSGIF_ERR_END_OF_DATA; } - if ((data[1] == 0x0b) && - (strncmp((const char *)data + 2, "NETSCAPE2.0", 11) == 0) && - (data[13] == 0x03) && (data[14] == 0x01)) { - gif->info.loop_max = data[15] | (data[16] << 8); + if (nsgif__app_ext_is_loop_count(data, len)) { + enum { + EXT_LOOP_COUNT_SUB_BLOCK_SIZE = 0x03, + EXT_LOOP_COUNT_SUB_BLOCK_ID = 0x01, + }; + if ((data[13] == EXT_LOOP_COUNT_SUB_BLOCK_SIZE) && + (data[14] == EXT_LOOP_COUNT_SUB_BLOCK_ID)) { + gif->info.loop_max = data[15] | (data[16] << 8); + + /* The value in the source data means repeat N times + * after the first implied play. A value of zero has + * the special meaning of loop forever. (The only way + * to play the animation once is not to have this + * extension at all. */ + if (gif->info.loop_max > 0) { + gif->info.loop_max++; + } + } } return NSGIF_OK; diff --git a/libvips/foreign/libnsgif/nsgif.h b/libvips/foreign/libnsgif/nsgif.h index c2a98c18..54dcd719 100644 --- a/libvips/foreign/libnsgif/nsgif.h +++ b/libvips/foreign/libnsgif/nsgif.h @@ -284,7 +284,7 @@ typedef struct nsgif_info { uint32_t height; /** number of frames decoded */ uint32_t frame_count; - /** number of times to loop animation */ + /** number of times to play animation (zero means loop forever) */ int loop_max; /** number of animation loops so far */ int loop_count;