#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"
#include <webp/encode.h>
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
#include <webp/mux.h>
+#include <webp/demux.h>
#endif
#endif
\f
return(MagickFalse);
}
+static int FillBasicWEBPInfo(Image *image, const uint8_t *stream,
+ size_t length, WebPDecoderConfig *configure){
+ WebPBitstreamFeatures
+ *magick_restrict features = &configure->input;
+
+ int
+ webp_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;
+
+ ssize_t
+ y;
+
+ WebPDecBuffer
+ *magick_restrict webp_image = &configure->output;
+
+ MagickBooleanType
+ status;
+
+ 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++)
+ {
+ register Quantum
+ *q;
+
+ register ssize_t
+ x;
+
+ q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
+ if (q == (Quantum *) NULL)
+ break;
+ for (x=0; x < (ssize_t) image->columns; x++)
+ {
+ SetPixelRed(image,ScaleCharToQuantum(*p++),q);
+ SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
+ SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
+ SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
+ q+=GetPixelChannels(image);
+ }
+ if (SyncAuthenticPixels(image,exception) == MagickFalse)
+ break;
+ status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
+ image->rows);
+ if (status == MagickFalse)
+ break;
+ }
+ WebPFreeDecBuffer(webp_image);
+#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
+ {
+ StringInfo
+ *profile;
+
+ uint32_t
+ webp_flags = 0;
+
+ WebPData
+ chunk,
+ content = { stream, length };
+
+ WebPMux
+ *mux;
+
+ /*
+ Extract any profiles.
+ */
+ mux=WebPMuxCreate(&content,0);
+ (void) memset(&chunk,0,sizeof(chunk));
+ WebPMuxGetFeatures(mux,&webp_flags);
+ if (webp_flags & ICCP_FLAG)
+ {
+ WebPMuxGetChunk(mux,"ICCP",&chunk);
+ profile=BlobToStringInfo(chunk.bytes,chunk.size);
+ if (profile != (StringInfo *) NULL)
+ {
+ SetImageProfile(image,"ICC",profile,exception);
+ profile=DestroyStringInfo(profile);
+ }
+ }
+ if (webp_flags & EXIF_FLAG)
+ {
+ WebPMuxGetChunk(mux,"EXIF",&chunk);
+ profile=BlobToStringInfo(chunk.bytes,chunk.size);
+ if (profile != (StringInfo *) NULL)
+ {
+ SetImageProfile(image,"EXIF",profile,exception);
+ profile=DestroyStringInfo(profile);
+ }
+ }
+ if (webp_flags & XMP_FLAG)
+ {
+ WebPMuxGetChunk(mux,"XMP",&chunk);
+ profile=BlobToStringInfo(chunk.bytes,chunk.size);
+ if (profile != (StringInfo *) NULL)
+ {
+ SetImageProfile(image,"XMP",profile,exception);
+ profile=DestroyStringInfo(profile);
+ }
+ }
+ 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)
{
MagickBooleanType
status;
- register unsigned char
- *p;
-
size_t
length;
ssize_t
- count,
- y;
+ count;
unsigned char
header[12],
WebPDecBuffer
*magick_restrict webp_image = &configure.output;
- WebPBitstreamFeatures
- *magick_restrict features = &configure.input;
-
/*
Open image file.
*/
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);
+
+ 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)
{
default:
ThrowWEBPException(CorruptImageError,"CorruptImage");
}
- p=(unsigned char *) webp_image->u.RGBA.rgba;
- for (y=0; y < (ssize_t) image->rows; y++)
- {
- register Quantum
- *q;
- register ssize_t
- x;
-
- q=QueueAuthenticPixels(image,0,y,image->columns,1,exception);
- if (q == (Quantum *) NULL)
- break;
- for (x=0; x < (ssize_t) image->columns; x++)
- {
- SetPixelRed(image,ScaleCharToQuantum(*p++),q);
- SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
- SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
- SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
- q+=GetPixelChannels(image);
- }
- if (SyncAuthenticPixels(image,exception) == MagickFalse)
- break;
- status=SetImageProgress(image,LoadImageTag,(MagickOffsetType) y,
- image->rows);
- if (status == MagickFalse)
- break;
- }
- WebPFreeDecBuffer(webp_image);
-#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
- {
- StringInfo
- *profile;
-
- uint32_t
- webp_flags = 0;
-
- WebPData
- chunk,
- content = { stream, length };
-
- WebPMux
- *mux;
-
- /*
- Extract any profiles.
- */
- mux=WebPMuxCreate(&content,0);
- (void) memset(&chunk,0,sizeof(chunk));
- WebPMuxGetFeatures(mux,&webp_flags);
- if (webp_flags & ICCP_FLAG)
- {
- WebPMuxGetChunk(mux,"ICCP",&chunk);
- profile=BlobToStringInfo(chunk.bytes,chunk.size);
- if (profile != (StringInfo *) NULL)
- {
- SetImageProfile(image,"ICC",profile,exception);
- profile=DestroyStringInfo(profile);
- }
- }
- if (webp_flags & EXIF_FLAG)
- {
- WebPMuxGetChunk(mux,"EXIF",&chunk);
- profile=BlobToStringInfo(chunk.bytes,chunk.size);
- if (profile != (StringInfo *) NULL)
- {
- SetImageProfile(image,"EXIF",profile,exception);
- profile=DestroyStringInfo(profile);
- }
- }
- if (webp_flags & XMP_FLAG)
- {
- WebPMuxGetChunk(mux,"XMP",&chunk);
- profile=BlobToStringInfo(chunk.bytes,chunk.size);
- if (profile != (StringInfo *) NULL)
- {
- SetImageProfile(image,"XMP",profile,exception);
- profile=DestroyStringInfo(profile);
- }
- }
- WebPMuxDelete(mux);
- }
-#endif
stream=(unsigned char*) RelinquishMagickMemory(stream);
(void) CloseBlob(image);
return(image);
#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);
}
#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;
MagickBooleanType
status;
- MemoryInfo
- *pixel_info;
-
- register uint32_t
- *magick_restrict q;
-
- ssize_t
- y;
-
WebPAuxStats
statistics;
WebPPicture
picture;
+ PictureMemory
+ memory = {0};
+
/*
Open output image file.
*/
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;
WebPMemoryWriterInit(&writer_info);
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)
#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)
{
#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