https://github.com/ImageMagick/ImageMagick/pull/1708
diff --git a/coders/webp.c b/coders/webp.c
index cf2e554..70334e4 100644
--- a/coders/webp.c
+++ b/coders/webp.c
@@ -58,6 +58,7 @@
#include "MagickCore/option.h"
#include "MagickCore/pixel-accessor.h"
#include "MagickCore/profile.h"
+#include "MagickCore/property.h"
#include "MagickCore/quantum-private.h"
#include "MagickCore/static.h"
#include "MagickCore/string_.h"
@@ -71,6 +72,7 @@
#include <webp/encode.h>
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
#include <webp/mux.h>
+#include <webp/demux.h>
#endif
#endif
@@ -207,155 +209,56 @@
return(MagickFalse);
}
-static Image *ReadWEBPImage(const ImageInfo *image_info,
- ExceptionInfo *exception)
-{
-#define ThrowWEBPException(severity,tag) \
-{ \
- if (stream != (unsigned char *) NULL) \
- stream=(unsigned char*) RelinquishMagickMemory(stream); \
- if (webp_image != (WebPDecBuffer *) NULL) \
- WebPFreeDecBuffer(webp_image); \
- ThrowReaderException(severity,tag); \
-}
-
- Image
- *image;
+static int FillBasicWEBPInfo(Image *image, const uint8_t *stream,
+ size_t length, WebPDecoderConfig *configure){
+ WebPBitstreamFeatures
+ *magick_restrict features = &configure->input;
int
webp_status;
- MagickBooleanType
- status;
+ webp_status=WebPGetFeatures(stream,length,features);
+
+ if (webp_status != VP8_STATUS_OK)
+ return webp_status;
+
+ image->columns=(size_t) features->width;
+ image->rows=(size_t) features->height;
+ image->depth=8;
+ image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
+ UndefinedPixelTrait;
+
+ return webp_status;
+}
+
+static int ReadSingleWEBPImage(Image *image, const uint8_t *stream, size_t length,
+ WebPDecoderConfig *configure, ExceptionInfo *exception){
+ int
+ webp_status;
register unsigned char
*p;
- size_t
- length;
-
ssize_t
- count,
y;
- unsigned char
- header[12],
- *stream;
-
- WebPDecoderConfig
- configure;
-
WebPDecBuffer
- *magick_restrict webp_image = &configure.output;
+ *magick_restrict webp_image = &configure->output;
- WebPBitstreamFeatures
- *magick_restrict features = &configure.input;
+ MagickBooleanType
+ status;
- /*
- Open image file.
- */
- assert(image_info != (const ImageInfo *) NULL);
- assert(image_info->signature == MagickCoreSignature);
- if (image_info->debug != MagickFalse)
- (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
- image_info->filename);
- assert(exception != (ExceptionInfo *) NULL);
- assert(exception->signature == MagickCoreSignature);
- image=AcquireImage(image_info,exception);
- status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
- if (status == MagickFalse)
- {
- image=DestroyImageList(image);
- return((Image *) NULL);
- }
- stream=(unsigned char *) NULL;
- if (WebPInitDecoderConfig(&configure) == 0)
- ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
- webp_image->colorspace=MODE_RGBA;
- count=ReadBlob(image,12,header);
- if (count != 12)
- ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
- status=IsWEBP(header,count);
- if (status == MagickFalse)
- ThrowWEBPException(CorruptImageError,"CorruptImage");
- length=(size_t) (ReadWebPLSBWord(header+4)+8);
- if (length < 12)
- ThrowWEBPException(CorruptImageError,"CorruptImage");
- if (length > GetBlobSize(image))
- ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
- stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
- if (stream == (unsigned char *) NULL)
- ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
- (void) memcpy(stream,header,12);
- count=ReadBlob(image,length-12,stream+12);
- if (count != (ssize_t) (length-12))
- ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
- webp_status=WebPGetFeatures(stream,length,features);
- if (webp_status == VP8_STATUS_OK)
- {
- image->columns=(size_t) features->width;
- image->rows=(size_t) features->height;
- image->depth=8;
- image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
- UndefinedPixelTrait;
- if (image_info->ping != MagickFalse)
- {
- stream=(unsigned char*) RelinquishMagickMemory(stream);
- (void) CloseBlob(image);
- return(GetFirstImageInList(image));
- }
- status=SetImageExtent(image,image->columns,image->rows,exception);
- if (status == MagickFalse)
- {
- stream=(unsigned char*) RelinquishMagickMemory(stream);
- (void) CloseBlob(image);
- return(DestroyImageList(image));
- }
- if (IsWEBPImageLossless(stream,length) != MagickFalse)
- image->quality=100;
- webp_status=WebPDecode(stream,length,&configure);
- }
- if (webp_status != VP8_STATUS_OK)
- switch (webp_status)
- {
- case VP8_STATUS_OUT_OF_MEMORY:
- {
- ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
- break;
- }
- case VP8_STATUS_INVALID_PARAM:
- {
- ThrowWEBPException(CorruptImageError,"invalid parameter");
- break;
- }
- case VP8_STATUS_BITSTREAM_ERROR:
- {
- ThrowWEBPException(CorruptImageError,"CorruptImage");
- break;
- }
- case VP8_STATUS_UNSUPPORTED_FEATURE:
- {
- ThrowWEBPException(CoderError,"DataEncodingSchemeIsNotSupported");
- break;
- }
- case VP8_STATUS_SUSPENDED:
- {
- ThrowWEBPException(CorruptImageError,"decoder suspended");
- break;
- }
- case VP8_STATUS_USER_ABORT:
- {
- ThrowWEBPException(CorruptImageError,"user abort");
- break;
- }
- case VP8_STATUS_NOT_ENOUGH_DATA:
- {
- ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
- break;
- }
- default:
- ThrowWEBPException(CorruptImageError,"CorruptImage");
- }
+ webp_status = FillBasicWEBPInfo(image,stream,length,configure);
+ if(webp_status != VP8_STATUS_OK)
+ return webp_status;
+
+ if (IsWEBPImageLossless(stream,length) != MagickFalse)
+ image->quality=100;
+
+ webp_status=WebPDecode(stream,length, configure);
+ if(webp_status != VP8_STATUS_OK)
+ return webp_status;
+
p=(unsigned char *) webp_image->u.RGBA.rgba;
for (y=0; y < (ssize_t) image->rows; y++)
{
@@ -438,6 +341,202 @@
WebPMuxDelete(mux);
}
#endif
+ return webp_status;
+}
+
+#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
+static int ReadAnimatedWEBPImage(const ImageInfo *image_info, Image *image,
+ uint8_t *stream, size_t length, WebPDecoderConfig *configure,
+ ExceptionInfo *exception) {
+ WebPData data = {.bytes=stream, .size=length};
+
+ WebPDemuxer* demux = WebPDemux(&data);
+
+ WebPIterator iter;
+
+ int
+ webp_status = 0;
+
+ int image_count = 0;
+
+ Image *original_image = image;
+
+ if (WebPDemuxGetFrame(demux, 1, &iter)) {
+ do {
+ if (image_count != 0)
+ {
+ AcquireNextImage(image_info,image,exception);
+ if (GetNextImageInList(image) == (Image *) NULL)
+ {
+ break;
+ }
+ image=SyncNextImageInList(image);
+ CloneImageProperties(image, original_image);
+ image->page.x = iter.x_offset;
+ image->page.y = iter.y_offset;
+ }
+
+ webp_status = ReadSingleWEBPImage(image, iter.fragment.bytes,
+ iter.fragment.size,
+ configure, exception);
+ if(webp_status != VP8_STATUS_OK)
+ break;
+
+ image->ticks_per_second = 100;
+ image->delay = iter.duration / 10;
+ if (image_info->verbose)
+ fprintf(stderr, "Reading WebP frame with delay %u\n", iter.duration);
+ image_count++;
+
+ } while (WebPDemuxNextFrame(&iter));
+ WebPDemuxReleaseIterator(&iter);
+ }
+ WebPDemuxDelete(demux);
+ return webp_status;
+}
+#endif
+
+static Image *ReadWEBPImage(const ImageInfo *image_info,
+ ExceptionInfo *exception)
+{
+#define ThrowWEBPException(severity,tag) \
+{ \
+ if (stream != (unsigned char *) NULL) \
+ stream=(unsigned char*) RelinquishMagickMemory(stream); \
+ if (webp_image != (WebPDecBuffer *) NULL) \
+ WebPFreeDecBuffer(webp_image); \
+ ThrowReaderException(severity,tag); \
+}
+
+ Image
+ *image;
+
+ int
+ webp_status;
+
+ MagickBooleanType
+ status;
+
+ size_t
+ length;
+
+ ssize_t
+ count;
+
+ unsigned char
+ header[12],
+ *stream;
+
+ WebPDecoderConfig
+ configure;
+
+ WebPDecBuffer
+ *magick_restrict webp_image = &configure.output;
+
+ /*
+ Open image file.
+ */
+ assert(image_info != (const ImageInfo *) NULL);
+ assert(image_info->signature == MagickCoreSignature);
+ if (image_info->debug != MagickFalse)
+ (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
+ image_info->filename);
+ assert(exception != (ExceptionInfo *) NULL);
+ assert(exception->signature == MagickCoreSignature);
+ image=AcquireImage(image_info,exception);
+ status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
+ if (status == MagickFalse)
+ {
+ image=DestroyImageList(image);
+ return((Image *) NULL);
+ }
+ stream=(unsigned char *) NULL;
+ if (WebPInitDecoderConfig(&configure) == 0)
+ ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
+ webp_image->colorspace=MODE_RGBA;
+ count=ReadBlob(image,12,header);
+ if (count != 12)
+ ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
+ status=IsWEBP(header,count);
+ if (status == MagickFalse)
+ ThrowWEBPException(CorruptImageError,"CorruptImage");
+ length=(size_t) (ReadWebPLSBWord(header+4)+8);
+ if (length < 12)
+ ThrowWEBPException(CorruptImageError,"CorruptImage");
+ if (length > GetBlobSize(image))
+ ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
+ stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
+ if (stream == (unsigned char *) NULL)
+ ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
+ (void) memcpy(stream,header,12);
+ count=ReadBlob(image,length-12,stream+12);
+ if (count != (ssize_t) (length-12))
+ ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
+
+ webp_status=FillBasicWEBPInfo(image,stream,length,&configure);
+ if (webp_status == VP8_STATUS_OK) {
+ if (image_info->ping != MagickFalse)
+ {
+ stream=(unsigned char*) RelinquishMagickMemory(stream);
+ (void) CloseBlob(image);
+ return(GetFirstImageInList(image));
+ }
+
+ if (configure.input.has_animation) {
+#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
+ webp_status=ReadAnimatedWEBPImage(image_info, image, stream,
+ length, &configure, exception);
+#else
+ webp_status=VP8_STATUS_UNSUPPORTED_FEATURE
+#endif
+ } else {
+ webp_status=ReadSingleWEBPImage(image, stream,
+ length, &configure, exception);
+ }
+ }
+
+ if (webp_status != VP8_STATUS_OK)
+ switch (webp_status)
+ {
+ case VP8_STATUS_OUT_OF_MEMORY:
+ {
+ ThrowWEBPException(ResourceLimitError,"MemoryAllocationFailed");
+ break;
+ }
+ case VP8_STATUS_INVALID_PARAM:
+ {
+ ThrowWEBPException(CorruptImageError,"invalid parameter");
+ break;
+ }
+ case VP8_STATUS_BITSTREAM_ERROR:
+ {
+ ThrowWEBPException(CorruptImageError,"CorruptImage");
+ break;
+ }
+ case VP8_STATUS_UNSUPPORTED_FEATURE:
+ {
+ ThrowWEBPException(CoderError,"DataEncodingSchemeIsNotSupported");
+ break;
+ }
+ case VP8_STATUS_SUSPENDED:
+ {
+ ThrowWEBPException(CorruptImageError,"decoder suspended");
+ break;
+ }
+ case VP8_STATUS_USER_ABORT:
+ {
+ ThrowWEBPException(CorruptImageError,"user abort");
+ break;
+ }
+ case VP8_STATUS_NOT_ENOUGH_DATA:
+ {
+ ThrowWEBPException(CorruptImageError,"InsufficientImageDataInFile");
+ break;
+ }
+ default:
+ ThrowWEBPException(CorruptImageError,"CorruptImage");
+ }
+
stream=(unsigned char*) RelinquishMagickMemory(stream);
(void) CloseBlob(image);
return(image);
@@ -487,7 +586,7 @@
#endif
entry->mime_type=ConstantString("image/webp");
entry->flags|=CoderDecoderSeekableStreamFlag;
- entry->flags^=CoderAdjoinFlag;
+ entry->flags|=CoderAdjoinFlag;
entry->magick=(IsImageFormatHandler *) IsWEBP;
if (*version != '\0')
entry->version=ConstantString(version);
@@ -575,8 +674,151 @@
}
#endif
+typedef struct PictureMemory {
+ MemoryInfo *pixel_info;
+ struct PictureMemory *next;
+} PictureMemory;
+
+static void FreePictureMemoryList (PictureMemory* head) {
+ PictureMemory* next;
+ while(head != NULL) {
+ next = head->next;
+ if(head->pixel_info != NULL)
+ RelinquishVirtualMemory(head->pixel_info);
+ free(head);
+ head = next;
+ }
+}
+
+static MagickBooleanType WriteSingleWEBPImage(const ImageInfo *image_info, Image *image,
+ WebPPicture *picture, PictureMemory *picture_memory, ExceptionInfo *exception)
+{
+ MagickBooleanType
+ status = MagickFalse;
+
+ register uint32_t
+ *magick_restrict q;
+
+ ssize_t
+ y;
+
+#if WEBP_ENCODER_ABI_VERSION >= 0x0100
+ picture->progress_hook=WebPEncodeProgress;
+ picture->user_data=(void *) image;
+#endif
+ picture->width=(int) image->columns;
+ picture->height=(int) image->rows;
+ picture->argb_stride=(int) image->columns;
+ picture->use_argb=1;
+
+ /*
+ Allocate memory for pixels.
+ */
+ (void) TransformImageColorspace(image,sRGBColorspace,exception);
+ picture_memory->pixel_info=AcquireVirtualMemory(image->columns,image->rows*
+ sizeof(*(picture->argb)));
+
+ if (picture_memory->pixel_info == (MemoryInfo *) NULL)
+ ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
+ picture->argb=(uint32_t *) GetVirtualMemoryBlob(picture_memory->pixel_info);
+ /*
+ Convert image to WebP raster pixels.
+ */
+ q=picture->argb;
+ for (y=0; y < (ssize_t) image->rows; y++)
+ {
+ register const Quantum
+ *magick_restrict p;
+
+ register ssize_t
+ x;
+
+ p=GetVirtualPixels(image,0,y,image->columns,1,exception);
+ if (p == (const Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ? (uint32_t)
+ ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
+ ((uint32_t) ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
+ ((uint32_t) ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
+ ((uint32_t) ScaleQuantumToChar(GetPixelBlue(image,p)));
+ p+=GetPixelChannels(image);
+ }
+ status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
+ image->rows);
+ if (status == MagickFalse)
+ break;
+ }
+ return status;
+}
+
+#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
+static MagickBooleanType WriteAnimatedWEBPImage(const ImageInfo *image_info,
+ Image *image, WebPConfig *configure,
+ WebPMemoryWriter *writer_info, ExceptionInfo *exception)
+{
+ WebPAnimEncoderOptions
+ enc_options;
+ WebPPicture
+ picture;
+ WebPAnimEncoder
+ *enc;
+ Image
+ *first_image;
+ PictureMemory
+ *head, *current;
+
+ size_t frame_timestamp = 0,
+ effective_delta = 0;
+
+ WebPAnimEncoderOptionsInit(&enc_options);
+ if (image_info->verbose)
+ enc_options.verbose = 1;
+
+ image = CoalesceImages(image, exception);
+ first_image = image;
+ enc = WebPAnimEncoderNew(image->page.width, image->page.height, &enc_options);
+
+ head = calloc(sizeof(*head), 1);
+ current = head;
+
+ while(image != NULL) {
+ if (WebPPictureInit(&picture) == 0)
+ ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
+
+ WriteSingleWEBPImage(image_info, image, &picture, current, exception);
+
+ effective_delta = image->delay*1000/image->ticks_per_second;
+ if (effective_delta < 10)
+ effective_delta = 100; // Consistent with gif2webp
+ frame_timestamp+=effective_delta;
+
+ if (image_info->verbose)
+ fprintf(stderr, "Writing WebP frame with delay %zu\n", effective_delta);
+
+ WebPAnimEncoderAdd(enc, &picture, frame_timestamp, configure);
+
+ image = GetNextImageInList(image);
+ current->next = calloc(sizeof(*head), 1);
+ current = current->next;
+ }
+ WebPData
+ webp_data = { writer_info->mem, writer_info->size };
+
+ WebPAnimEncoderAssemble(enc, &webp_data);
+ WebPMemoryWriterClear(writer_info);
+ writer_info->size=webp_data.size;
+ writer_info->mem=(unsigned char *) webp_data.bytes;
+ WebPAnimEncoderDelete(enc);
+ DestroyImageList(first_image);
+ FreePictureMemoryList(head);
+ return MagickTrue;
+}
+#endif
+
static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
- Image *image,ExceptionInfo *exception)
+ Image *image,ExceptionInfo * exception)
{
const char
*value;
@@ -587,15 +829,6 @@
MagickBooleanType
status;
- MemoryInfo
- *pixel_info;
-
- register uint32_t
- *magick_restrict q;
-
- ssize_t
- y;
-
WebPAuxStats
statistics;
@@ -610,6 +843,9 @@
WebPPicture
picture;
+ PictureMemory
+ memory = {0};
+
/*
Open output image file.
*/
@@ -624,7 +860,9 @@
status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
if (status == MagickFalse)
return(status);
- if ((WebPPictureInit(&picture) == 0) || (WebPConfigInit(&configure) == 0))
+ if (WebPConfigInit(&configure) == 0)
+ ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
+ if (WebPPictureInit(&picture) == 0)
ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
#if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
picture.writer=WebPEncodeWriter;
@@ -634,15 +872,7 @@
picture.writer=WebPMemoryWrite;
picture.custom_ptr=(&writer_info);
#endif
-#if WEBP_ENCODER_ABI_VERSION >= 0x0100
- picture.progress_hook=WebPEncodeProgress;
- picture.user_data=(void *) image;
-#endif
picture.stats=(&statistics);
- picture.width=(int) image->columns;
- picture.height=(int) image->rows;
- picture.argb_stride=(int) image->columns;
- picture.use_argb=1;
if (image->quality != UndefinedCompressionQuality)
configure.quality=(float) image->quality;
if (image->quality >= 100)
@@ -737,44 +967,19 @@
#endif
if (WebPValidateConfig(&configure) == 0)
ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
- /*
- Allocate memory for pixels.
- */
- (void) TransformImageColorspace(image,sRGBColorspace,exception);
- pixel_info=AcquireVirtualMemory(image->columns,image->rows*
- sizeof(*picture.argb));
- if (pixel_info == (MemoryInfo *) NULL)
- ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
- picture.argb=(uint32_t *) GetVirtualMemoryBlob(pixel_info);
- /*
- Convert image to WebP raster pixels.
- */
- q=picture.argb;
- for (y=0; y < (ssize_t) image->rows; y++)
- {
- register const Quantum
- *magick_restrict p;
- register ssize_t
- x;
+ WriteSingleWEBPImage(image_info, image, &picture,
+ &memory, exception);
- p=GetVirtualPixels(image,0,y,image->columns,1,exception);
- if (p == (const Quantum *) NULL)
- break;
- for (x=0; x < (ssize_t) image->columns; x++)
- {
- *q++=(uint32_t) (image->alpha_trait != UndefinedPixelTrait ? (uint32_t)
- ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000) |
- ((uint32_t) ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
- ((uint32_t) ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
- ((uint32_t) ScaleQuantumToChar(GetPixelBlue(image,p)));
- p+=GetPixelChannels(image);
- }
- status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
- image->rows);
- if (status == MagickFalse)
- break;
+#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
+ if ((GetPreviousImageInList(image) == (Image *) NULL) &&
+ (GetNextImageInList(image) != (Image *) NULL) &&
+ (image->iterations != 1)) {
+ WriteAnimatedWEBPImage(image_info, image, &configure,
+ &writer_info, exception);
}
+#endif
+
webp_status=WebPEncode(&configure,&picture);
if (webp_status == 0)
{
@@ -912,8 +1117,8 @@
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
WebPMemoryWriterClear(&writer_info);
#endif
- pixel_info=RelinquishVirtualMemory(pixel_info);
(void) CloseBlob(image);
+ RelinquishVirtualMemory(memory.pixel_info);
return(webp_status == 0 ? MagickFalse : MagickTrue);
}
#endif