]> granicus.if.org Git - imagemagick/blobdiff - coders/webp.c
(no commit message)
[imagemagick] / coders / webp.c
index 49f81a12ba583bbad81ba3f4c8c22855f549944e..a56317e112f6c90e0b43bf514fc68ef8a1bc8ca5 100644 (file)
@@ -17,7 +17,7 @@
 %                                 March 2011                                  %
 %                                                                             %
 %                                                                             %
-%  Copyright 1999-2011 ImageMagick Studio LLC, a non-profit organization      %
+%  Copyright 1999-2013 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  %
@@ -58,6 +58,7 @@
 #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"
 */
 #if defined(MAGICKCORE_WEBP_DELEGATE)
 static MagickBooleanType
-  WriteWEBPImage(const ImageInfo *,Image *);
+  WriteWEBPImage(const ImageInfo *,Image *,ExceptionInfo *);
 #endif
 \f
+/*
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%                                                                             %
+%                                                                             %
+%                                                                             %
+%   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);
+}
+\f
 #if defined(MAGICKCORE_WEBP_DELEGATE)
 /*
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
@@ -101,25 +136,77 @@ static MagickBooleanType
 %    o exception: return any errors or warnings in this structure.
 %
 */
+
+static inline uint32_t ReadWebPLSBWord(const unsigned char *restrict data)
+{
+  register const unsigned char
+    *p;
+
+  register 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)
+
+  ssize_t
+    offset;
+
+  /*
+    Read simple header.
+  */
+  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)
+  {
+    uint32_t
+      chunk_size,
+      chunk_size_pad;
+
+    chunk_size=ReadWebPLSBWord(stream+pos+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 Image *ReadWEBPImage(const ImageInfo *image_info,
   ExceptionInfo *exception)
 {
-  int
-    height,
-    width;
-
   Image
     *image;
 
   MagickBooleanType
     status;
 
-  register Quantum
-    *q;
-
-  register ssize_t
-    x;
-
   register unsigned char
     *p;
 
@@ -131,8 +218,16 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
     y;
 
   unsigned char
-    *stream,
-    *pixels;
+    *stream;
+
+  WebPDecoderConfig
+    configure;
+
+  WebPDecBuffer
+    *restrict webp_image = &configure.output;
+
+  WebPBitstreamFeatures
+    *restrict features = &configure.input;
 
   /*
     Open image file.
@@ -144,13 +239,15 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
       image_info->filename);
   assert(exception != (ExceptionInfo *) NULL);
   assert(exception->signature == MagickSignature);
-  image=AcquireImage(image_info);
+  image=AcquireImage(image_info,exception);
   status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
   if (status == MagickFalse)
     {
       image=DestroyImageList(image);
       return((Image *) NULL);
     }
+  if (WebPInitDecoderConfig(&configure) == 0)
+    ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
   length=(size_t) GetBlobSize(image);
   stream=(unsigned char *) AcquireQuantumMemory(length,sizeof(*stream));
   if (stream == (unsigned char *) NULL)
@@ -158,14 +255,32 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
   count=ReadBlob(image,length,stream);
   if (count != (ssize_t) length)
     ThrowReaderException(CorruptImageError,"InsufficientImageDataInFile");
-  pixels=(unsigned char *) WebPDecodeRGBA(stream,length,&width,&height);
-  if (pixels == (unsigned char *) NULL)
-    ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
-  image->columns=(size_t) width;
-  image->rows=(size_t) height;
-  p=pixels;
+  if (WebPGetFeatures(stream,length,features) != 0)
+    {
+      stream=(unsigned char*) RelinquishMagickMemory(stream);
+      ThrowReaderException(ResourceLimitError,"UnableToDecodeImageFile");
+    }
+  webp_image->colorspace=MODE_RGBA;
+  if (WebPDecode(stream,length,&configure) != 0)
+    {
+      stream=(unsigned char*) RelinquishMagickMemory(stream);
+      ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
+    }
+  image->columns=(size_t) webp_image->width;
+  image->rows=(size_t) webp_image->height;
+  image->alpha_trait=features->has_alpha != 0 ? BlendPixelTrait :
+    UndefinedPixelTrait;
+  if (IsWEBPImageLossless(stream,length) != MagickFalse)
+    image->quality=100;
+  p=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;
@@ -175,8 +290,6 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
       SetPixelGreen(image,ScaleCharToQuantum(*p++),q);
       SetPixelBlue(image,ScaleCharToQuantum(*p++),q);
       SetPixelAlpha(image,ScaleCharToQuantum(*p++),q);
-      if (GetPixelAlpha(image,q) != OpaqueAlpha)
-        image->matte=MagickTrue;
       q+=GetPixelChannels(image);
     }
     if (SyncAuthenticPixels(image,exception) == MagickFalse)
@@ -186,8 +299,8 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
     if (status == MagickFalse)
       break;
   }
-  free(pixels);
-  pixels=(unsigned char *) NULL;
+  WebPFreeDecBuffer(webp_image);
+  stream=(unsigned char*) RelinquishMagickMemory(stream);
   return(image);
 }
 #endif
@@ -217,17 +330,28 @@ static Image *ReadWEBPImage(const ImageInfo *image_info,
 */
 ModuleExport size_t RegisterWEBPImage(void)
 {
+  char
+    version[MaxTextExtent];
+
   MagickInfo
     *entry;
 
+  *version='\0';
   entry=SetMagickInfo("WEBP");
 #if defined(MAGICKCORE_WEBP_DELEGATE)
   entry->decoder=(DecodeImageHandler *) ReadWEBPImage;
   entry->encoder=(EncodeImageHandler *) WriteWEBPImage;
+  (void) FormatLocaleString(version,MaxTextExtent,"libwebp %d.%d.%d",
+    (WebPGetDecoderVersion() >> 16) & 0xff,
+    (WebPGetDecoderVersion() >> 8) & 0xff,
+    (WebPGetDecoderVersion() >> 0) & 0xff);
 #endif
   entry->description=ConstantString("WebP Image Format");
   entry->adjoin=MagickFalse;
   entry->module=ConstantString("WEBP");
+  entry->magick=(IsImageFormatHandler *) IsWEBP;
+  if (*version != '\0')
+    entry->version=ConstantString(version);
   (void) RegisterMagickInfo(entry);
   return(MagickImageCoderSignature);
 }
@@ -294,29 +418,23 @@ static int WebPWriter(const unsigned char *stream,size_t length,
 }
 
 static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
-  Image *image)
+  Image *image,ExceptionInfo *exception)
 {
+  const char
+    *value;
+
   int
     webp_status;
 
   MagickBooleanType
     status;
 
-  register const Quantum
-    *restrict p;
-
-  register ssize_t
-    x;
-
-  register unsigned char
+  register uint32_t
     *restrict q;
 
   ssize_t
     y;
 
-  unsigned char
-    *pixels;
-
   WebPConfig
     configure;
 
@@ -335,49 +453,122 @@ static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
   assert(image->signature == MagickSignature);
   if (image->debug != MagickFalse)
     (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",image->filename);
-  status=OpenBlob(image_info,image,WriteBinaryBlobMode,&image->exception);
+  if ((image->columns > 16383UL) || (image->rows > 16383UL))
+    ThrowWriterException(ImageError,"WidthOrHeightExceedsLimit");
+  status=OpenBlob(image_info,image,WriteBinaryBlobMode,exception);
   if (status == MagickFalse)
     return(status);
-  if (WebPPictureInit(&picture) == 0)
-    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
+  if ((WebPPictureInit(&picture) == 0) || (WebPConfigInit(&configure) == 0))
+    ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
   picture.writer=WebPWriter;
   picture.custom_ptr=(void *) image;
   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 (WebPConfigInit(&configure) == 0)
-    ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
-  /*
-    Future: set custom configuration parameters here.
-  */
+  else
+    if (image->quality >= 100)
+      configure.lossless=1;
+  value=GetImageOption(image_info,"webp:lossless");
+  if (value != (char *) NULL)
+    configure.lossless=ParseCommandOption(MagickBooleanOptions,MagickFalse,
+      value);
+  value=GetImageOption(image_info,"webp:method");
+  if (value != (char *) NULL)
+    configure.method=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:image-hint");
+  if (value != (char *) NULL)
+    {
+      if (LocaleCompare(value,"graph") == 0)
+        configure.image_hint=WEBP_HINT_GRAPH;
+      if (LocaleCompare(value,"photo") == 0)
+        configure.image_hint=WEBP_HINT_PHOTO;
+      if (LocaleCompare(value,"picture") == 0)
+        configure.image_hint=WEBP_HINT_PICTURE;
+    }
+  value=GetImageOption(image_info,"webp:target-size");
+  if (value != (char *) NULL)
+    configure.target_size=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:target-psnr");
+  if (value != (char *) NULL)
+    configure.target_PSNR=(float) StringToDouble(value,(char **) NULL);
+  value=GetImageOption(image_info,"webp:segments");
+  if (value != (char *) NULL)
+    configure.segments=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:sns-strength");
+  if (value != (char *) NULL)
+    configure.sns_strength=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:filter-strength");
+  if (value != (char *) NULL)
+    configure.filter_strength=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:filter-sharpness");
+  if (value != (char *) NULL)
+    configure.filter_sharpness=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:filter-type");
+  if (value != (char *) NULL)
+    configure.filter_type=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:auto-filter");
+  if (value != (char *) NULL)
+    configure.autofilter=ParseCommandOption(MagickBooleanOptions,MagickFalse,
+      value);
+  value=GetImageOption(image_info,"webp:alpha-compression");
+  if (value != (char *) NULL)
+    configure.alpha_compression=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:alpha-filtering");
+  if (value != (char *) NULL)
+    configure.alpha_filtering=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:alpha-quality");
+  if (value != (char *) NULL)
+    configure.alpha_quality=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:pass");
+  if (value != (char *) NULL)
+    configure.pass=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:show-compressed");
+  if (value != (char *) NULL)
+    configure.show_compressed=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:preprocessing");
+  if (value != (char *) NULL)
+    configure.preprocessing=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:partitions");
+  if (value != (char *) NULL)
+    configure.partitions=StringToInteger(value);
+  value=GetImageOption(image_info,"webp:partition-limit");
+  if (value != (char *) NULL)
+    configure.partition_limit=StringToInteger(value);
   if (WebPValidateConfig(&configure) == 0)
     ThrowWriterException(ResourceLimitError,"UnableToEncodeImageFile");
   /*
     Allocate memory for pixels.
   */
-  pixels=(unsigned char *) AcquireQuantumMemory(image->columns,
-    (image->matte != MagickFalse ? 4 : 3)*image->rows*sizeof(*pixels));
-  if (pixels == (unsigned char *) NULL)
+  picture.argb=(uint32_t *) AcquireQuantumMemory(image->columns,image->rows*
+    sizeof(*picture.argb));
+  if (picture.argb == (uint32_t *) NULL)
     ThrowWriterException(ResourceLimitError,"MemoryAllocationFailed");
   /*
     Convert image to WebP raster pixels.
   */
-  q=pixels;
+  q=picture.argb;
   for (y=0; y < (ssize_t) image->rows; y++)
   {
-    p=GetVirtualPixels(image,0,y,image->columns,1,&image->exception);
+    register const Quantum
+      *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++=ScaleQuantumToChar(GetPixelRed(image,p));
-      *q++=ScaleQuantumToChar(GetPixelGreen(image,p));
-      *q++=ScaleQuantumToChar(GetPixelBlue(image,p));
-      if (image->matte != MagickFalse)
-        *q++=ScaleQuantumToChar((Quantum) (image->matte != MagickFalse ?
-          GetPixelAlpha(image,p) : OpaqueAlpha));
+      *q++=(uint32_t) (image->alpha_trait == BlendPixelTrait ?
+        ScaleQuantumToChar(GetPixelAlpha(image,p)) << 24 : 0xff000000u) |
+        (ScaleQuantumToChar(GetPixelRed(image,p)) << 16) |
+        (ScaleQuantumToChar(GetPixelGreen(image,p)) << 8) |
+        (ScaleQuantumToChar(GetPixelBlue(image,p)));
       p+=GetPixelChannels(image);
     }
     status=SetImageProgress(image,SaveImageTag,(MagickOffsetType) y,
@@ -385,12 +576,9 @@ static MagickBooleanType WriteWEBPImage(const ImageInfo *image_info,
     if (status == MagickFalse)
       break;
   }
-  if (image->matte == MagickFalse)
-    webp_status=WebPPictureImportRGB(&picture,pixels,3*picture.width);
-  else
-    webp_status=WebPPictureImportRGBA(&picture,pixels,4*picture.width);
-  pixels=(unsigned char *) RelinquishMagickMemory(pixels);
   webp_status=WebPEncode(&configure,&picture);
+  WebPPictureFree(&picture);
+  picture.argb=(uint32_t *) RelinquishMagickMemory(picture.argb);
   (void) CloseBlob(image);
   return(webp_status == 0 ? MagickFalse : MagickTrue);
 }