You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1227 lines
37 KiB

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W W EEEEE BBBB PPPP %
% W W E B B P P %
% W W W EEE BBBB PPPP %
% WW WW E B B P %
% W W EEEEE BBBB P %
% %
% %
% Read/Write WebP Image Format %
% %
% Software Design %
% Cristy %
% March 2011 %
% %
% %
% Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization %
% dedicated to making software imaging solutions freely available. %
% %
% You may not use this file except in compliance with the License. You may %
% obtain a copy of the License at %
% %
% https://imagemagick.org/script/license.php %
% %
% Unless required by applicable law or agreed to in writing, software %
% distributed under the License is distributed on an "AS IS" BASIS, %
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
% See the License for the specific language governing permissions and %
% limitations under the License. %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/
/*
Include declarations.
*/
#include "MagickCore/studio.h"
#include "MagickCore/artifact.h"
#include "MagickCore/blob.h"
#include "MagickCore/blob-private.h"
#include "MagickCore/client.h"
#include "MagickCore/colorspace-private.h"
#include "MagickCore/display.h"
#include "MagickCore/exception.h"
#include "MagickCore/exception-private.h"
#include "MagickCore/image.h"
#include "MagickCore/image-private.h"
#include "MagickCore/layer.h"
#include "MagickCore/list.h"
#include "MagickCore/magick.h"
#include "MagickCore/monitor.h"
#include "MagickCore/monitor-private.h"
#include "MagickCore/memory_.h"
#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 "MagickCore/string-private.h"
#include "MagickCore/module.h"
#include "MagickCore/utility.h"
#include "MagickCore/xwindow.h"
#include "MagickCore/xwindow-private.h"
#if defined(MAGICKCORE_WEBP_DELEGATE)
#include <webp/decode.h>
#include <webp/encode.h>
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
#include <webp/mux.h>
#include <webp/demux.h>
#endif
#endif
/*
Forward declarations.
*/
#if defined(MAGICKCORE_WEBP_DELEGATE)
static MagickBooleanType
WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
#endif
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% I s W E B P %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% IsWEBP() returns MagickTrue if the image format type, identified by the
% magick string, is WebP.
%
% The format of the IsWEBP method is:
%
% MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
%
% A description of each parameter follows:
%
% o magick: compare image format pattern against these bytes.
%
% o length: Specifies the length of the magick string.
%
*/
static MagickBooleanType IsWEBP(const unsigned char *magick,const size_t length)
{
if (length < 12)
return(MagickFalse);
if (LocaleNCompare((const char *) magick+8,"WEBP",4) == 0)
return(MagickTrue);
return(MagickFalse);
}
#if defined(MAGICKCORE_WEBP_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e a d W E B P I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% ReadWEBPImage() reads an image in the WebP image format.
%
% The format of the ReadWEBPImage method is:
%
% Image *ReadWEBPImage(const ImageInfo *image_info,
% ExceptionInfo *exception)
%
% A description of each parameter follows:
%
% o image_info: the image info.
%
% o exception: return any errors or warnings in this structure.
%
*/
static inline uint32_t ReadWebPLSBWord(
const unsigned char *magick_restrict data)
{
const unsigned char
*p;
uint32_t
value;
p=data;
value=(uint32_t) (*p++);
value|=((uint32_t) (*p++)) << 8;
value|=((uint32_t) (*p++)) << 16;
value|=((uint32_t) (*p++)) << 24;
return(value);
}
static MagickBooleanType IsWEBPImageLossless(const unsigned char *stream,
const size_t length)
{
#define VP8_CHUNK_INDEX 15
#define LOSSLESS_FLAG 'L'
#define EXTENDED_HEADER 'X'
#define VP8_CHUNK_HEADER "VP8"
#define VP8_CHUNK_HEADER_SIZE 3
#define RIFF_HEADER_SIZE 12
#define VP8X_CHUNK_SIZE 10
#define TAG_SIZE 4
#define CHUNK_SIZE_BYTES 4
#define CHUNK_HEADER_SIZE 8
#define MAX_CHUNK_PAYLOAD (~0U-CHUNK_HEADER_SIZE-1)
size_t
offset;
/*
Read simple header.
*/
if (length <= VP8_CHUNK_INDEX)
return(MagickFalse);
if (stream[VP8_CHUNK_INDEX] != EXTENDED_HEADER)
return(stream[VP8_CHUNK_INDEX] == LOSSLESS_FLAG ? MagickTrue : MagickFalse);
/*
Read extended header.
*/
offset=RIFF_HEADER_SIZE+TAG_SIZE+CHUNK_SIZE_BYTES+VP8X_CHUNK_SIZE;
while (offset <= (length-TAG_SIZE-TAG_SIZE-4))
{
uint32_t
chunk_size,
chunk_size_pad;
chunk_size=ReadWebPLSBWord(stream+offset+TAG_SIZE);
if (chunk_size > MAX_CHUNK_PAYLOAD)
break;
chunk_size_pad=(CHUNK_HEADER_SIZE+chunk_size+1) & ~1;
if (memcmp(stream+offset,VP8_CHUNK_HEADER,VP8_CHUNK_HEADER_SIZE) == 0)
return(*(stream+offset+VP8_CHUNK_HEADER_SIZE) == LOSSLESS_FLAG ?
MagickTrue : MagickFalse);
offset+=chunk_size_pad;
}
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,
MagickBooleanType is_first)
{
int
webp_status;
unsigned char
*p;
size_t
canvas_width,
canvas_height,
image_width,
image_height;
ssize_t
x_offset,
y_offset,
y;
WebPDecBuffer
*magick_restrict webp_image;
MagickBooleanType
status;
webp_image=&configure->output;
if (is_first)
{
canvas_width=image->columns;
canvas_height=image->rows;
x_offset=image->page.x;
y_offset=image->page.y;
image->page.x=0;
image->page.y=0;
}
else
{
canvas_width=0;
canvas_height=0;
x_offset=0;
y_offset=0;
}
webp_status=FillBasicWEBPInfo(image,stream,length,configure);
image_width=image->columns;
image_height=image->rows;
if (is_first)
{
image->columns=canvas_width;
image->rows=canvas_height;
}
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++)
{
Quantum
*q;
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++)
{
if ((x >= x_offset && x < (ssize_t) (x_offset+image_width)) &&
(y >= y_offset && y < (ssize_t) (y_offset+image_height)))
{
SetPixelRed(image,ScaleCharToQuantum(*p++),q);
SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
}
else
{
SetPixelRed(image,0,q);
SetPixelGreen(image,0,q);
SetPixelBlue(image,0,q);
SetPixelAlpha(image,0,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;
WebPMux
*mux;
/*
Extract any profiles:
https://developers.google.com/speed/webp/docs/container-api.
*/
content.bytes=stream;
content.size=length;
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)
{
Image
*original_image;
int
image_count,
webp_status;
size_t
canvas_width,
canvas_height;
WebPData
data;
WebPDemuxer
*demux;
WebPIterator
iter;
image_count=0;
webp_status=0;
original_image=image;
webp_status=FillBasicWEBPInfo(image,stream,length,configure);
canvas_width=image->columns;
canvas_height=image->rows;
data.bytes=stream;
data.size=length;
{
WebPMux
*mux;
WebPMuxAnimParams
params;
WebPMuxError
status;
mux=WebPMuxCreate(&data,0);
status=WebPMuxGetAnimationParams(mux,&params);
if (status >= 0)
image->iterations=params.loop_count;
WebPMuxDelete(mux);
}
demux=WebPDemux(&data);
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,MagickFalse);
}
else
{
image->page.x=iter.x_offset;
image->page.y=iter.y_offset;
webp_status=ReadSingleWEBPImage(image,iter.fragment.bytes,
iter.fragment.size,configure,exception,MagickTrue);
}
if (webp_status != VP8_STATUS_OK)
break;
image->page.width=canvas_width;
image->page.height=canvas_height;
image->ticks_per_second=100;
image->delay=iter.duration/10;
image->dispose=NoneDispose;
if (iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND)
image->dispose=BackgroundDispose;
(void) SetImageProperty(image,"webp:mux-blend",
"AtopPreviousAlphaBlend",exception);
if (iter.blend_method == WEBP_MUX_BLEND)
(void) SetImageProperty(image,"webp:mux-blend",
"AtopBackgroundAlphaBlend",exception);
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 (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,MagickFalse);
}
}
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);
}
#endif
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% R e g i s t e r W E B P I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% RegisterWEBPImage() adds attributes for the WebP image format to
% the list of supported formats. The attributes include the image format
% tag, a method to read and/or write the format, whether the format
% supports the saving of more than one frame to the same file or blob,
% whether the format supports native in-memory I/O, and a brief
% description of the format.
%
% The format of the RegisterWEBPImage method is:
%
% size_t RegisterWEBPImage(void)
%
*/
ModuleExport size_t RegisterWEBPImage(void)
{
char
version[MagickPathExtent];
MagickInfo
*entry;
*version='\0';
entry=AcquireMagickInfo("WEBP","WEBP","WebP Image Format");
#if defined(MAGICKCORE_WEBP_DELEGATE)
entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
(void) FormatLocaleString(version,MagickPathExtent,"libwebp %d.%d.%d [%04X]",
(WebPGetEncoderVersion() >> 16) & 0xff,
(WebPGetEncoderVersion() >> 8) & 0xff,
(WebPGetEncoderVersion() >> 0) & 0xff,WEBP_ENCODER_ABI_VERSION);
#endif
entry->mime_type=ConstantString("image/webp");
entry->flags|=CoderDecoderSeekableStreamFlag;
#if !defined(MAGICKCORE_WEBPMUX_DELEGATE)
entry->flags^=CoderAdjoinFlag;
#endif
entry->magick=(IsImageFormatHandler *) IsWEBP;
if (*version != '\0')
entry->version=ConstantString(version);
(void) RegisterMagickInfo(entry);
return(MagickImageCoderSignature);
}
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% U n r e g i s t e r W E B P I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% UnregisterWEBPImage() removes format registrations made by the WebP module
% from the list of supported formats.
%
% The format of the UnregisterWEBPImage method is:
%
% UnregisterWEBPImage(void)
%
*/
ModuleExport void UnregisterWEBPImage(void)
{
(void) UnregisterMagickInfo("WEBP");
}
#if defined(MAGICKCORE_WEBP_DELEGATE)
/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% %
% %
% %
% W r i t e W E B P I m a g e %
% %
% %
% %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% WriteWEBPImage() writes an image in the WebP image format.
%
% The format of the WriteWEBPImage method is:
%
% MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
% Image *image)
%
% A description of each parameter follows.
%
% o image_info: the image info.
%
% o image: The image.
%
*/
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
static int WebPEncodeProgress(int percent,const WebPPicture* picture)
{
#define EncodeImageTag "Encode/Image"
Image
*image;
MagickBooleanType
status;
image=(Image *) picture->user_data;
status=SetImageProgress(image,EncodeImageTag,percent-1,100);
return(status == MagickFalse ? 0 : 1);
}
#endif
static const char * WebPErrorCodeMessage(WebPEncodingError error_code)
{
switch (error_code)
{
case VP8_ENC_OK:
return NULL;
case VP8_ENC_ERROR_OUT_OF_MEMORY:
return "out of memory";
case VP8_ENC_ERROR_BITSTREAM_OUT_OF_MEMORY:
return "bitstream out of memory";
case VP8_ENC_ERROR_NULL_PARAMETER:
return "NULL parameter";
case VP8_ENC_ERROR_INVALID_CONFIGURATION:
return "invalid configuration";
case VP8_ENC_ERROR_BAD_DIMENSION:
return "bad dimension";
case VP8_ENC_ERROR_PARTITION0_OVERFLOW:
return "partition 0 overflow (> 512K)";
case VP8_ENC_ERROR_PARTITION_OVERFLOW:
return "partition overflow (> 16M)";
case VP8_ENC_ERROR_BAD_WRITE:
return "bad write";
case VP8_ENC_ERROR_FILE_TOO_BIG:
return "file too big (> 4GB)";
#if WEBP_ENCODER_ABI_VERSION >= 0x0100
case VP8_ENC_ERROR_USER_ABORT:
return "user abort";
case VP8_ENC_ERROR_LAST:
return "error last";
#endif
}
return "unknown exception";
}
static MagickBooleanType WriteSingleWEBPPicture(const ImageInfo *image_info,
Image *image,WebPConfig *configure,WebPPicture *picture,ExceptionInfo *exception)
{
MagickBooleanType
status;
uint32_t
*magick_restrict q;
ssize_t
y;
MemoryInfo
*pixel_info;
#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);
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.
*/
status=MagickFalse;
q=picture->argb;
for (y=0; y < (ssize_t) image->rows; y++)
{
const Quantum
*magick_restrict p;
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;
}
if (status != MagickFalse)
status=(MagickBooleanType) WebPEncode(configure, picture);
RelinquishVirtualMemory(pixel_info);
if (status == MagickFalse)
(void) ThrowMagickException(exception,GetMagickModule(),CorruptImageError,
WebPErrorCodeMessage(picture->error_code),"`%s'",
image->filename);
return(status);
}
static MagickBooleanType WriteSingleWEBPImage(const ImageInfo *image_info,
Image *image,WebPConfig *configure,WebPMemoryWriter *writer,
ExceptionInfo *exception)
{
MagickBooleanType
status;
WebPPicture
picture;
if (WebPPictureInit(&picture) == 0)
ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
picture.writer=WebPMemoryWrite;
picture.custom_ptr=writer;
status=WriteSingleWEBPPicture(image_info,image,configure,&picture,exception);
WebPPictureFree(&picture);
return(status);
}
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
static MagickBooleanType WriteAnimatedWEBPImage(const ImageInfo *image_info,
Image *image,WebPConfig *configure,WebPData *webp_data,
ExceptionInfo *exception)
{
Image
*first_image;
size_t
effective_delta,
frame_timestamp;
WebPAnimEncoder
*enc;
WebPAnimEncoderOptions
enc_options;
WebPPicture
picture;
MagickBooleanType
status;
first_image=CoalesceImages(image,exception);
if (first_image == (Image *) NULL)
return(MagickFalse);
image=first_image;
effective_delta=0;
frame_timestamp=0;
(void) WebPAnimEncoderOptionsInit(&enc_options);
if (image_info->verbose)
enc_options.verbose=1;
enc=WebPAnimEncoderNew((int) image->page.width,(int) image->page.height,
&enc_options);
status=MagickTrue;
while (image != NULL)
{
status=(MagickBooleanType) WebPPictureInit(&picture);
if (status == MagickFalse)
{
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
break;
}
status=WriteSingleWEBPPicture(image_info,image,configure,&picture,
exception);
if (status == MagickFalse)
break;
status=(MagickBooleanType) WebPAnimEncoderAdd(enc,&picture,
(int) frame_timestamp,configure);
WebPPictureFree(&picture);
if (status == MagickFalse)
{
(void) ThrowMagickException(exception,GetMagickModule(),
CoderError,WebPAnimEncoderGetError(enc),"`%s'",image->filename);
break;
}
effective_delta=image->delay*1000*PerceptibleReciprocal(
image->ticks_per_second);
if (effective_delta < 10)
effective_delta=100; /* Consistent with gif2webp */
frame_timestamp+=effective_delta;
image=GetNextImageInList(image);
}
if (status != MagickFalse)
{
// add last null frame and assemble picture.
status=(MagickBooleanType) WebPAnimEncoderAdd(enc,(WebPPicture *) NULL,
(int) frame_timestamp,configure);
if (status != MagickFalse)
status=(MagickBooleanType) WebPAnimEncoderAssemble(enc,webp_data);
if (status == MagickFalse)
(void) ThrowMagickException(exception,GetMagickModule(),
CoderError,WebPAnimEncoderGetError(enc),"`%s'",
first_image->filename);
}
WebPAnimEncoderDelete(enc);
DestroyImageList(first_image);
return(status);
}
static MagickBooleanType WriteWEBPImageProfile(Image *image,
WebPData *webp_data,ExceptionInfo *exception)
{
const StringInfo
*icc_profile,
*exif_profile,
*xmp_profile;
WebPData
chunk;
WebPMux
*mux;
WebPMuxError
mux_error;
WebPMuxAnimParams
new_params;
icc_profile=GetImageProfile(image,"ICC");
exif_profile=GetImageProfile(image,"EXIF");
xmp_profile=GetImageProfile(image,"XMP");
if (icc_profile == (StringInfo *) NULL
&& exif_profile == (StringInfo *) NULL
&& xmp_profile == (StringInfo *) NULL
&& image->iterations == 0)
return(MagickTrue);
mux=WebPMuxCreate(webp_data, 1);
WebPDataClear(webp_data);
if (mux == NULL)
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
// Clean up returned data
memset(webp_data, 0, sizeof(*webp_data));
mux_error=WEBP_MUX_OK;
if (image->iterations > 0)
{
mux_error=WebPMuxGetAnimationParams(mux, &new_params);
/*
If there is only 1 frame webp_data will be created by WriteSingleWEBPImage
and WebPMuxGetAnimationParams will return WEBP_MUX_NOT_FOUND
*/
if (mux_error == WEBP_MUX_NOT_FOUND)
mux_error=WEBP_MUX_OK;
else
if (mux_error == WEBP_MUX_OK)
{
new_params.loop_count=MagickMin((int) image->iterations,65535);
mux_error=WebPMuxSetAnimationParams(mux, &new_params);
}
}
if ((icc_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
{
chunk.bytes=GetStringInfoDatum(icc_profile);
chunk.size=GetStringInfoLength(icc_profile);
mux_error=WebPMuxSetChunk(mux,"ICCP",&chunk,0);
}
if ((exif_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
{
chunk.bytes=GetStringInfoDatum(exif_profile);
chunk.size=GetStringInfoLength(exif_profile);
if ((chunk.size >= 6) &&
(chunk.bytes[0] == 'E') && (chunk.bytes[1] == 'x') &&
(chunk.bytes[2] == 'i') && (chunk.bytes[3] == 'f') &&
(chunk.bytes[4] == '\0') && (chunk.bytes[5] == '\0'))
{
chunk.bytes=GetStringInfoDatum(exif_profile)+6;
chunk.size-=6;
}
mux_error=WebPMuxSetChunk(mux,"EXIF",&chunk,0);
}
if ((xmp_profile != (StringInfo *) NULL) && (mux_error == WEBP_MUX_OK))
{
chunk.bytes=GetStringInfoDatum(xmp_profile);
chunk.size=GetStringInfoLength(xmp_profile);
mux_error=WebPMuxSetChunk(mux,"XMP",&chunk,0);
}
if (mux_error == WEBP_MUX_OK)
mux_error=WebPMuxAssemble(mux,webp_data);
WebPMuxDelete(mux);
if (mux_error != WEBP_MUX_OK)
(void) ThrowMagickException(exception,GetMagickModule(),
ResourceLimitError,"UnableToEncodeImageFile","`%s'",image->filename);
return(MagickTrue);
}
#endif
static inline void SetBooleanOption(const ImageInfo *image_info,
const char *option,int *setting)
{
const char
*value;
value=GetImageOption(image_info,option);
if (value != (char *) NULL)
*setting=(int) ParseCommandOption(MagickBooleanOptions,MagickFalse,value);
}
static inline void SetIntegerOption(const ImageInfo *image_info,
const char *option,int *setting)
{
const char
*value;
value=GetImageOption(image_info,option);
if (value != (const char *) NULL)
*setting=StringToInteger(value);
}
static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
Image *image,ExceptionInfo * exception)
{
const char
*value;
MagickBooleanType
status;
WebPConfig
configure;
WebPMemoryWriter
writer;
/*
Open output image file.
*/
assert(image_info != (const ImageInfo *) NULL);
assert(image_info->signature == MagickCoreSignature);
assert(image != (Image *) NULL);
assert(image->signature == MagickCoreSignature);
if (image->debug != MagickFalse)
(void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
if ((image->columns > 16383UL) || (image->rows > 16383UL))
ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
if (status == MagickFalse)
return(status);
if (WebPConfigInit(&configure) == 0)
ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
if (image->quality != UndefinedCompressionQuality)
configure.quality=(float) image->quality;
if (image->quality >= 100)
configure.lossless=1;
SetBooleanOption(image_info,"webp:lossless",&configure.lossless);
value=GetImageOption(image_info,"webp:image-hint");
if (value != (char *) NULL)
{
if (LocaleCompare(value,"default") == 0)
configure.image_hint=WEBP_HINT_DEFAULT;
if (LocaleCompare(value,"photo") == 0)
configure.image_hint=WEBP_HINT_PHOTO;
if (LocaleCompare(value,"picture") == 0)
configure.image_hint=WEBP_HINT_PICTURE;
#if WEBP_ENCODER_ABI_VERSION >= 0x0200
if (LocaleCompare(value,"graph") == 0)
configure.image_hint=WEBP_HINT_GRAPH;
#endif
}
SetBooleanOption(image_info,"webp:auto-filter",&configure.autofilter);
value=GetImageOption(image_info,"webp:target-psnr");
if (value != (char *) NULL)
configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
SetIntegerOption(image_info,"webp:alpha-compression",
&configure.alpha_compression);
SetIntegerOption(image_info,"webp:alpha-filtering",
&configure.alpha_filtering);
SetIntegerOption(image_info,"webp:alpha-quality",
&configure.alpha_quality);
SetIntegerOption(image_info,"webp:filter-strength",
&configure.filter_strength);
SetIntegerOption(image_info,"webp:filter-sharpness",
&configure.filter_sharpness);
SetIntegerOption(image_info,"webp:filter-type",&configure.filter_type);
SetIntegerOption(image_info,"webp:method",&configure.method);
SetIntegerOption(image_info,"webp:partitions",&configure.partitions);
SetIntegerOption(image_info,"webp:partition-limit",
&configure.partition_limit);
SetIntegerOption(image_info,"webp:pass",&configure.pass);
SetIntegerOption(image_info,"webp:preprocessing",&configure.preprocessing);
SetIntegerOption(image_info,"webp:segments",&configure.segments);
SetIntegerOption(image_info,"webp:show-compressed",
&configure.show_compressed);
SetIntegerOption(image_info,"webp:sns-strength",&configure.sns_strength);
SetIntegerOption(image_info,"webp:target-size",&configure.target_size);
#if WEBP_ENCODER_ABI_VERSION >= 0x0201
SetBooleanOption(image_info,"webp:emulate-jpeg-size",
&configure.emulate_jpeg_size);
SetBooleanOption(image_info,"webp:low-memory",&configure.low_memory);
SetIntegerOption(image_info,"webp:thread-level",&configure.thread_level);
#endif
#if WEBP_ENCODER_ABI_VERSION >= 0x0209
SetBooleanOption(image_info,"webp:exact",&configure.exact);
#endif
#if WEBP_ENCODER_ABI_VERSION >= 0x020e
SetIntegerOption(image_info,"webp:near-lossless",&configure.near_lossless);
SetBooleanOption(image_info,"webp:use-sharp-yuv",&configure.use_sharp_yuv);
#endif
if (WebPValidateConfig(&configure) == 0)
ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
#if defined(MAGICKCORE_WEBPMUX_DELEGATE)
{
WebPData
webp_data;
memset(&webp_data,0,sizeof(webp_data));
if ((image_info->adjoin != MagickFalse) &&
(GetPreviousImageInList(image) == (Image *) NULL) &&
(GetNextImageInList(image) != (Image *) NULL))
status=WriteAnimatedWEBPImage(image_info,image,&configure,&webp_data,
exception);
else
{
WebPMemoryWriterInit(&writer);
status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
exception);
if (status == MagickFalse)
WebPMemoryWriterClear(&writer);
else
{
webp_data.bytes=writer.mem;
webp_data.size=writer.size;
}
}
if (status != MagickFalse)
status=WriteWEBPImageProfile(image,&webp_data,exception);
if (status != MagickFalse)
(void) WriteBlob(image,webp_data.size,webp_data.bytes);
WebPDataClear(&webp_data);
}
#else
WebPMemoryWriterInit(&writer);
status=WriteSingleWEBPImage(image_info,image,&configure,&writer,
exception);
if (status != MagickFalse)
(void) WriteBlob(image,writer.size,writer.mem);
WebPMemoryWriterClear(&writer);
#endif
(void) CloseBlob(image);
return(status);
}
#endif