From 6f80564d625a4732370a4869c020725fded6d28e Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Tue, 25 Nov 2014 21:16:51 +0800 Subject: [PATCH 01/63] start work on #33 add giflib source and include it binding.gyp --- README.md | 9 +- binding.gyp | 20 +- src/lib/gif/AUTHORS | 36 + src/lib/gif/COPYING | 19 + src/lib/gif/README | 27 + src/lib/gif/dgif_lib.c | 1163 +++++++++++++++++++++++++++++++++ src/lib/gif/egif_lib.c | 1150 ++++++++++++++++++++++++++++++++ src/lib/gif/gif_err.c | 97 +++ src/lib/gif/gif_font.c | 252 +++++++ src/lib/gif/gif_hash.c | 132 ++++ src/lib/gif/gif_hash.h | 39 ++ src/lib/gif/gif_lib.h | 309 +++++++++ src/lib/gif/gif_lib_private.h | 59 ++ src/lib/gif/gifalloc.c | 401 ++++++++++++ src/lib/gif/quantize.c | 330 ++++++++++ 15 files changed, 4035 insertions(+), 8 deletions(-) create mode 100644 src/lib/gif/AUTHORS create mode 100644 src/lib/gif/COPYING create mode 100644 src/lib/gif/README create mode 100644 src/lib/gif/dgif_lib.c create mode 100644 src/lib/gif/egif_lib.c create mode 100644 src/lib/gif/gif_err.c create mode 100644 src/lib/gif/gif_font.c create mode 100644 src/lib/gif/gif_hash.c create mode 100644 src/lib/gif/gif_hash.h create mode 100644 src/lib/gif/gif_lib.h create mode 100644 src/lib/gif/gif_lib_private.h create mode 100644 src/lib/gif/gifalloc.c create mode 100644 src/lib/gif/quantize.c diff --git a/README.md b/README.md index 886559c4..9095a106 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ If `npm install lwip` failes, you probably need to setup your system. ```Javascript // obtain an image object: require('lwip').open('image.jpg', function(err, image){ - + // check err... // define a batch of manipulations and save to disk as JPEG: image.batch() @@ -102,7 +102,7 @@ var lwip = require('lwip'); // obtain an image object: lwip.open('image.jpg', function(err, image){ - + // check err... // manipulate image: image.scale(0.5, function(err, image){ @@ -160,7 +160,7 @@ Colors are specified in one of three ways: "blue" // {r: 0, g: 0, b: 255, a: 100} "yellow" // {r: 255, g: 255, b: 0, a: 100} "cyan" // {r: 0, g: 255, b: 255, a: 100} - "magenta" // {r: 255, g: 0, b: 255, a: 100} + "magenta" // {r: 255, g: 0, b: 255, a: 100} ``` - As an array `[R, G, B, A]` where `R`, `G` and `B` are integers between 0 and @@ -691,3 +691,6 @@ The native part of this module is compiled from source which uses the following: - The CImg Library - [Website](http://cimg.sourceforge.net/) - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/cimg/README.txt) +- giflib + - [Website](http://giflib.sourceforge.net/) + - [Readme](https://github.com/EyalAr/lwip/blob/master/src/lib/gif/README) diff --git a/binding.gyp b/binding.gyp index 6eef55e7..0ac4613e 100644 --- a/binding.gyp +++ b/binding.gyp @@ -2,7 +2,7 @@ "targets": [{ "target_name": "lwip_decoder", "sources": [ - # LWIP: + # LWIP: ####### "src/decoder/init.cpp", "src/decoder/util.cpp", @@ -66,7 +66,12 @@ "src/lib/zlib/inffast.c", "src/lib/zlib/uncompr.c", "src/lib/zlib/zutil.c", - "src/lib/zlib/trees.c" + "src/lib/zlib/trees.c", + # LIB GIF: + ########## + "src/lib/gif/dgif_lib.c", + "src/lib/gif/gif_err.c", + "src/lib/gif/gifalloc.c", ], 'include_dirs': [ ' + callbacks to write data via user defined function + +Daniel Eisenbud + Fixes for crashes with invalid gif files and double freeing of + colormaps + +Gershon Elber + original giflib code + +Marc Ewing + spec file (for rpms) updates + +Toshio Kuratomi + uncompressed gif writing code + autoconf/automake process + former maintainer + +marek + Gif initialization fix + windows build code + +Peter Mehlitz + callbacks to read data from arbitrary sources (like libjpeg/libpng) + +Dick Porter + int/pointer fixes for Alpha + +Eric Raymond + current as well as long time former maintainer of giflib code + +Petter Reinholdtsen + Tru64 build fixs + +Georg Schwarz + IRIX fixes diff --git a/src/lib/gif/COPYING b/src/lib/gif/COPYING new file mode 100644 index 00000000..b9c0b501 --- /dev/null +++ b/src/lib/gif/COPYING @@ -0,0 +1,19 @@ +The GIFLIB distribution is Copyright (c) 1997 Eric S. Raymond + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/lib/gif/README b/src/lib/gif/README new file mode 100644 index 00000000..38a9233c --- /dev/null +++ b/src/lib/gif/README @@ -0,0 +1,27 @@ += GIFLIB = + +This is the README file of GIFLIB, a library for manipulating GIF files. + +Latest versions of GIFLIB are currently hosted at: + http://sourceforge.net/projects/giflib + +== Overview == + +GIF is a legacy format; we recommend against generating new images in +it. For a cleaner, more extensible design with better color support +and compression, look up PNG. + +giflib provides code for reading GIF files and transforming them into +RGB bitmaps, and for writing RGB bitmaps as GIF files. + +The (permissive) open-source license is in the file COPYING. + +You will find build instructions in build.asc + +You will find full documentation of the API in doc/ and on the +project website. + +The project has a long and confusing history, described in history.asc + +The project to-do list is in TODO. + diff --git a/src/lib/gif/dgif_lib.c b/src/lib/gif/dgif_lib.c new file mode 100644 index 00000000..5bd5bf8b --- /dev/null +++ b/src/lib/gif/dgif_lib.c @@ -0,0 +1,1163 @@ +/****************************************************************************** + +dgif_lib.c - GIF decoding + +The functions here and in egif_lib.c are partitioned carefully so that +if you only require one of read and write capability, only one of these +two modules will be linked. Preserve this property! + +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#endif /* _WIN32 */ + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* compose unsigned little endian value */ +#define UNSIGNED_LITTLE_ENDIAN(lo, hi) ((lo) | ((hi) << 8)) + +/* avoid extra function call in case we use fread (TVT) */ +#define READ(_gif,_buf,_len) \ + (((GifFilePrivateType*)_gif->Private)->Read ? \ + ((GifFilePrivateType*)_gif->Private)->Read(_gif,_buf,_len) : \ + fread(_buf,1,_len,((GifFilePrivateType*)_gif->Private)->File)) + +static int DGifGetWord(GifFileType *GifFile, GifWord *Word); +static int DGifSetupDecompress(GifFileType *GifFile); +static int DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, + int LineLen); +static int DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode); +static int DGifDecompressInput(GifFileType *GifFile, int *Code); +static int DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, + GifByteType *NextByte); + +/****************************************************************************** + Open a new GIF file for read, given by its name. + Returns dynamically allocated GifFileType pointer which serves as the GIF + info record. +******************************************************************************/ +GifFileType * +DGifOpenFileName(const char *FileName, int *Error) +{ + int FileHandle; + GifFileType *GifFile; + + if ((FileHandle = open(FileName, O_RDONLY)) == -1) { + if (Error != NULL) + *Error = D_GIF_ERR_OPEN_FAILED; + return NULL; + } + + GifFile = DGifOpenFileHandle(FileHandle, Error); + return GifFile; +} + +/****************************************************************************** + Update a new GIF file, given its file handle. + Returns dynamically allocated GifFileType pointer which serves as the GIF + info record. +******************************************************************************/ +GifFileType * +DGifOpenFileHandle(int FileHandle, int *Error) +{ + char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + (void)close(FileHandle); + return NULL; + } + + /*@i1@*/memset(GifFile, '\0', sizeof(GifFileType)); + + /* Belt and suspenders, in case the null pointer isn't zero */ + GifFile->SavedImages = NULL; + GifFile->SColorMap = NULL; + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + (void)close(FileHandle); + free((char *)GifFile); + return NULL; + } +#ifdef _WIN32 + _setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* _WIN32 */ + + f = fdopen(FileHandle, "rb"); /* Make it into a stream: */ + + /*@-mustfreeonly@*/ + GifFile->Private = (void *)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_READ; + Private->Read = NULL; /* don't use alternate input method (TVT) */ + GifFile->UserData = NULL; /* TVT */ + /*@=mustfreeonly@*/ + + /* Let's see if this is a GIF file: */ + if (READ(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) { + if (Error != NULL) + *Error = D_GIF_ERR_READ_FAILED; + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* Check for GIF prefix at start of file */ + Buf[GIF_STAMP_LEN] = 0; + if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_GIF_FILE; + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + (void)fclose(f); + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + GifFile->Error = 0; + + /* What version of GIF? */ + Private->gif89 = (Buf[GIF_VERSION_POS] == '9'); + + return GifFile; +} + +/****************************************************************************** + GifFileType constructor with user supplied input function (TVT) +******************************************************************************/ +GifFileType * +DGifOpen(void *userData, InputFunc readFunc, int *Error) +{ + char Buf[GIF_STAMP_LEN + 1]; + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + /* Belt and suspenders, in case the null pointer isn't zero */ + GifFile->SavedImages = NULL; + GifFile->SColorMap = NULL; + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (!Private) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_ENOUGH_MEM; + free((char *)GifFile); + return NULL; + } + + GifFile->Private = (void *)Private; + Private->FileHandle = 0; + Private->File = NULL; + Private->FileState = FILE_STATE_READ; + + Private->Read = readFunc; /* TVT */ + GifFile->UserData = userData; /* TVT */ + + /* Lets see if this is a GIF file: */ + if (READ(GifFile, (unsigned char *)Buf, GIF_STAMP_LEN) != GIF_STAMP_LEN) { + if (Error != NULL) + *Error = D_GIF_ERR_READ_FAILED; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + /* Check for GIF prefix at start of file */ + Buf[GIF_STAMP_LEN] = '\0'; + if (strncmp(GIF_STAMP, Buf, GIF_VERSION_POS) != 0) { + if (Error != NULL) + *Error = D_GIF_ERR_NOT_GIF_FILE; + free((char *)Private); + free((char *)GifFile); + return NULL; + } + + if (DGifGetScreenDesc(GifFile) == GIF_ERROR) { + free((char *)Private); + free((char *)GifFile); + *Error = D_GIF_ERR_NO_SCRN_DSCR; + return NULL; + } + + GifFile->Error = 0; + + /* What version of GIF? */ + Private->gif89 = (Buf[GIF_VERSION_POS] == '9'); + + return GifFile; +} + +/****************************************************************************** + This routine should be called before any other DGif calls. Note that + this routine is called automatically from DGif file open routines. +******************************************************************************/ +int +DGifGetScreenDesc(GifFileType *GifFile) +{ + int BitsPerPixel; + bool SortFlag; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + /* Put the screen descriptor into the file: */ + if (DGifGetWord(GifFile, &GifFile->SWidth) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->SHeight) == GIF_ERROR) + return GIF_ERROR; + + if (READ(GifFile, Buf, 3) != 3) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + return GIF_ERROR; + } + GifFile->SColorResolution = (((Buf[0] & 0x70) + 1) >> 4) + 1; + SortFlag = (Buf[0] & 0x08) != 0; + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->SBackGroundColor = Buf[1]; + GifFile->AspectByte = Buf[2]; + if (Buf[0] & 0x80) { /* Do we have global color map? */ + int i; + + GifFile->SColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->SColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the global color map: */ + GifFile->SColorMap->SortFlag = SortFlag; + for (i = 0; i < GifFile->SColorMap->ColorCount; i++) { + if (READ(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + GifFile->SColorMap->Colors[i].Red = Buf[0]; + GifFile->SColorMap->Colors[i].Green = Buf[1]; + GifFile->SColorMap->Colors[i].Blue = Buf[2]; + } + } else { + GifFile->SColorMap = NULL; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. +******************************************************************************/ +int +DGifGetRecordType(GifFileType *GifFile, GifRecordType* Type) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + switch (Buf) { + case DESCRIPTOR_INTRODUCER: + *Type = IMAGE_DESC_RECORD_TYPE; + break; + case EXTENSION_INTRODUCER: + *Type = EXTENSION_RECORD_TYPE; + break; + case TERMINATOR_INTRODUCER: + *Type = TERMINATE_RECORD_TYPE; + break; + default: + *Type = UNDEFINED_RECORD_TYPE; + GifFile->Error = D_GIF_ERR_WRONG_RECORD; + return GIF_ERROR; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to read an image. + Note it is assumed the Image desc. header has been read. +******************************************************************************/ +int +DGifGetImageDesc(GifFileType *GifFile) +{ + unsigned int BitsPerPixel; + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + SavedImage *sp; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifGetWord(GifFile, &GifFile->Image.Left) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Top) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Width) == GIF_ERROR || + DGifGetWord(GifFile, &GifFile->Image.Height) == GIF_ERROR) + return GIF_ERROR; + if (READ(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + BitsPerPixel = (Buf[0] & 0x07) + 1; + GifFile->Image.Interlace = (Buf[0] & 0x40) ? true : false; + + /* Setup the colormap */ + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + /* Does this image have local color map? */ + if (Buf[0] & 0x80) { + unsigned int i; + + GifFile->Image.ColorMap = GifMakeMapObject(1 << BitsPerPixel, NULL); + if (GifFile->Image.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + + /* Get the image local color map: */ + for (i = 0; i < GifFile->Image.ColorMap->ColorCount; i++) { + if (READ(GifFile, Buf, 3) != 3) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Error = D_GIF_ERR_READ_FAILED; + GifFile->Image.ColorMap = NULL; + return GIF_ERROR; + } + GifFile->Image.ColorMap->Colors[i].Red = Buf[0]; + GifFile->Image.ColorMap->Colors[i].Green = Buf[1]; + GifFile->Image.ColorMap->Colors[i].Blue = Buf[2]; + } + } + + if (GifFile->SavedImages) { + if ((GifFile->SavedImages = (SavedImage *)realloc(GifFile->SavedImages, + sizeof(SavedImage) * + (GifFile->ImageCount + 1))) == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else { + if ((GifFile->SavedImages = + (SavedImage *) malloc(sizeof(SavedImage))) == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + + sp = &GifFile->SavedImages[GifFile->ImageCount]; + memcpy(&sp->ImageDesc, &GifFile->Image, sizeof(GifImageDesc)); + if (GifFile->Image.ColorMap != NULL) { + sp->ImageDesc.ColorMap = GifMakeMapObject( + GifFile->Image.ColorMap->ColorCount, + GifFile->Image.ColorMap->Colors); + if (sp->ImageDesc.ColorMap == NULL) { + GifFile->Error = D_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } + sp->RasterBits = (unsigned char *)NULL; + sp->ExtensionBlockCount = 0; + sp->ExtensionBlocks = (ExtensionBlock *) NULL; + + GifFile->ImageCount++; + + Private->PixelCount = (long)GifFile->Image.Width * + (long)GifFile->Image.Height; + + /* Reset decompress algorithm parameters. */ + return DGifSetupDecompress(GifFile); +} + +/****************************************************************************** + Get one full scanned line (Line) of length LineLen from GIF file. +******************************************************************************/ +int +DGifGetLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) +{ + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (!LineLen) + LineLen = GifFile->Image.Width; + + if ((Private->PixelCount -= LineLen) > 0xffff0000UL) { + GifFile->Error = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, Line, LineLen) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably won't be called any more, so let's clean up + * everything before we return: need to flush out all the + * rest of image until an empty block (size 0) + * detected. We use GetCodeNext. + */ + do + if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR) + return GIF_ERROR; + while (Dummy != NULL) ; + } + return GIF_OK; + } else + return GIF_ERROR; +} + +/****************************************************************************** + Put one pixel (Pixel) into GIF file. +******************************************************************************/ +int +DGifGetPixel(GifFileType *GifFile, GifPixelType Pixel) +{ + GifByteType *Dummy; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + if (--Private->PixelCount > 0xffff0000UL) + { + GifFile->Error = D_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + + if (DGifDecompressLine(GifFile, &Pixel, 1) == GIF_OK) { + if (Private->PixelCount == 0) { + /* We probably won't be called any more, so let's clean up + * everything before we return: need to flush out all the + * rest of image until an empty block (size 0) + * detected. We use GetCodeNext. + */ + do + if (DGifGetCodeNext(GifFile, &Dummy) == GIF_ERROR) + return GIF_ERROR; + while (Dummy != NULL) ; + } + return GIF_OK; + } else + return GIF_ERROR; +} + +/****************************************************************************** + Get an extension block (see GIF manual) from GIF file. This routine only + returns the first data block, and DGifGetExtensionNext should be called + after this one until NULL extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). + Note it is assumed the Extension description header has been read. +******************************************************************************/ +int +DGifGetExtension(GifFileType *GifFile, int *ExtCode, GifByteType **Extension) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *ExtCode = Buf; + + return DGifGetExtensionNext(GifFile, Extension); +} + +/****************************************************************************** + Get a following extension block (see GIF manual) from GIF file. This + routine should be called until NULL Extension is returned. + The Extension should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +DGifGetExtensionNext(GifFileType *GifFile, GifByteType ** Extension) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + if (Buf > 0) { + *Extension = Private->Buf; /* Use private unused buffer. */ + (*Extension)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data] */ + if (READ(GifFile, &((*Extension)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else + *Extension = NULL; + + return GIF_OK; +} + +/****************************************************************************** + Extract a Graphics Control Block from raw extension data +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB) +{ + if (GifExtensionLength != 4) { + return GIF_ERROR; + } + + GCB->DisposalMode = (GifExtension[0] >> 2) & 0x07; + GCB->UserInputFlag = (GifExtension[0] & 0x02) != 0; + GCB->DelayTime = UNSIGNED_LITTLE_ENDIAN(GifExtension[1], GifExtension[2]); + if (GifExtension[0] & 0x01) + GCB->TransparentColor = (int)GifExtension[3]; + else + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + + return GIF_OK; +} + +/****************************************************************************** + Extract the Graphics Control Block for a saved image, if it exists. +******************************************************************************/ + +int DGifSavedExtensionToGCB(GifFileType *GifFile, + int ImageIndex, GraphicsControlBlock *GCB) +{ + int i; + + if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) + return GIF_ERROR; + + GCB->DisposalMode = DISPOSAL_UNSPECIFIED; + GCB->UserInputFlag = false; + GCB->DelayTime = 0; + GCB->TransparentColor = NO_TRANSPARENT_COLOR; + + for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) { + ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; + if (ep->Function == GRAPHICS_EXT_FUNC_CODE) + return DGifExtensionToGCB(ep->ByteCount, ep->Bytes, GCB); + } + + return GIF_ERROR; +} + +/****************************************************************************** + This routine should be called last, to close the GIF file. +******************************************************************************/ +int +DGifCloseFile(GifFileType *GifFile, int *ErrorCode) +{ + GifFilePrivateType *Private; + + if (GifFile == NULL || GifFile->Private == NULL) + return GIF_ERROR; + + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + + if (GifFile->SColorMap) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + + if (GifFile->SavedImages) { + GifFreeSavedImages(GifFile); + GifFile->SavedImages = NULL; + } + + GifFreeExtensions(&GifFile->ExtensionBlockCount, &GifFile->ExtensionBlocks); + + Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + if (ErrorCode != NULL) + *ErrorCode = D_GIF_ERR_NOT_READABLE; + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + if (Private->File && (fclose(Private->File) != 0)) { + if (ErrorCode != NULL) + *ErrorCode = D_GIF_ERR_CLOSE_FAILED; + free((char *)GifFile->Private); + free(GifFile); + return GIF_ERROR; + } + + free((char *)GifFile->Private); + free(GifFile); + if (ErrorCode != NULL) + *ErrorCode = D_GIF_SUCCEEDED; + return GIF_OK; +} + +/****************************************************************************** + Get 2 bytes (word) from the given file: +******************************************************************************/ +static int +DGifGetWord(GifFileType *GifFile, GifWord *Word) +{ + unsigned char c[2]; + + if (READ(GifFile, c, 2) != 2) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + *Word = (GifWord)UNSIGNED_LITTLE_ENDIAN(c[0], c[1]); + return GIF_OK; +} + +/****************************************************************************** + Get the image code in compressed form. This routine can be called if the + information needed to be piped out as is. Obviously this is much faster + than decoding and encoding again. This routine should be followed by calls + to DGifGetCodeNext, until NULL block is returned. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +DGifGetCode(GifFileType *GifFile, int *CodeSize, GifByteType **CodeBlock) +{ + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + *CodeSize = Private->BitsPerPixel; + + return DGifGetCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + Continue to get the image code in compressed form. This routine should be + called until NULL block is returned. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +DGifGetCodeNext(GifFileType *GifFile, GifByteType **CodeBlock) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + /* coverity[tainted_data_argument] */ + if (READ(GifFile, &Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + + /* coverity[lower_bounds] */ + if (Buf > 0) { + *CodeBlock = Private->Buf; /* Use private unused buffer. */ + (*CodeBlock)[0] = Buf; /* Pascal strings notation (pos. 0 is len.). */ + /* coverity[tainted_data] */ + if (READ(GifFile, &((*CodeBlock)[1]), Buf) != Buf) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + } else { + *CodeBlock = NULL; + Private->Buf[0] = 0; /* Make sure the buffer is empty! */ + Private->PixelCount = 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + Setup the LZ decompression for this image: +******************************************************************************/ +static int +DGifSetupDecompress(GifFileType *GifFile) +{ + int i, BitsPerPixel; + GifByteType CodeSize; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (READ(GifFile, &CodeSize, 1) < 1) { /* Read Code size from file. */ + return GIF_ERROR; /* Failed to read Code size. */ + } + BitsPerPixel = CodeSize; + + Private->Buf[0] = 0; /* Input Buffer empty. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->StackPtr = 0; /* No pixels on the pixel stack. */ + Private->LastCode = NO_SUCH_CODE; + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + Prefix = Private->Prefix; + for (i = 0; i <= LZ_MAX_CODE; i++) + Prefix[i] = NO_SUCH_CODE; + + return GIF_OK; +} + +/****************************************************************************** + The LZ decompression routine: + This version decompress the given GIF file into Line of length LineLen. + This routine can be called few times (one per scan line, for example), in + order the complete the whole image. +******************************************************************************/ +static int +DGifDecompressLine(GifFileType *GifFile, GifPixelType *Line, int LineLen) +{ + int i = 0; + int j, CrntCode, EOFCode, ClearCode, CrntPrefix, LastCode, StackPtr; + GifByteType *Stack, *Suffix; + GifPrefixType *Prefix; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + StackPtr = Private->StackPtr; + Prefix = Private->Prefix; + Suffix = Private->Suffix; + Stack = Private->Stack; + EOFCode = Private->EOFCode; + ClearCode = Private->ClearCode; + LastCode = Private->LastCode; + + if (StackPtr > LZ_MAX_CODE) { + return GIF_ERROR; + } + + if (StackPtr != 0) { + /* Let pop the stack off before continueing to read the GIF file: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + + while (i < LineLen) { /* Decode LineLen items. */ + if (DGifDecompressInput(GifFile, &CrntCode) == GIF_ERROR) + return GIF_ERROR; + + if (CrntCode == EOFCode) { + /* Note however that usually we will not be here as we will stop + * decoding as soon as we got all the pixel, or EOF code will + * not be read at all, and DGifGetLine/Pixel clean everything. */ + GifFile->Error = D_GIF_ERR_EOF_TOO_SOON; + return GIF_ERROR; + } else if (CrntCode == ClearCode) { + /* We need to start over again: */ + for (j = 0; j <= LZ_MAX_CODE; j++) + Prefix[j] = NO_SUCH_CODE; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + LastCode = Private->LastCode = NO_SUCH_CODE; + } else { + /* Its regular code - if in pixel range simply add it to output + * stream, otherwise trace to codes linked list until the prefix + * is in pixel range: */ + if (CrntCode < ClearCode) { + /* This is simple - its pixel scalar, so add it to output: */ + Line[i++] = CrntCode; + } else { + /* Its a code to needed to be traced: trace the linked list + * until the prefix is a pixel, while pushing the suffix + * pixels on our stack. If we done, pop the stack in reverse + * (thats what stack is good for!) order to output. */ + if (Prefix[CrntCode] == NO_SUCH_CODE) { + CrntPrefix = LastCode; + + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + if (CrntCode == Private->RunningCode - 2) { + Suffix[Private->RunningCode - 2] = + Stack[StackPtr++] = DGifGetPrefixChar(Prefix, + LastCode, + ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + Stack[StackPtr++] = DGifGetPrefixChar(Prefix, + CrntCode, + ClearCode); + } + } else + CrntPrefix = CrntCode; + + /* Now (if image is O.K.) we should not get a NO_SUCH_CODE + * during the trace. As we might loop forever, in case of + * defective image, we use StackPtr as loop counter and stop + * before overflowing Stack[]. */ + while (StackPtr < LZ_MAX_CODE && + CrntPrefix > ClearCode && CrntPrefix <= LZ_MAX_CODE) { + Stack[StackPtr++] = Suffix[CrntPrefix]; + CrntPrefix = Prefix[CrntPrefix]; + } + if (StackPtr >= LZ_MAX_CODE || CrntPrefix > LZ_MAX_CODE) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + /* Push the last character on stack: */ + Stack[StackPtr++] = CrntPrefix; + + /* Now lets pop all the stack into output: */ + while (StackPtr != 0 && i < LineLen) + Line[i++] = Stack[--StackPtr]; + } + if (LastCode != NO_SUCH_CODE && Prefix[Private->RunningCode - 2] == NO_SUCH_CODE) { + Prefix[Private->RunningCode - 2] = LastCode; + + if (CrntCode == Private->RunningCode - 2) { + /* Only allowed if CrntCode is exactly the running code: + * In that case CrntCode = XXXCode, CrntCode or the + * prefix code is last code and the suffix char is + * exactly the prefix of last code! */ + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, LastCode, ClearCode); + } else { + Suffix[Private->RunningCode - 2] = + DGifGetPrefixChar(Prefix, CrntCode, ClearCode); + } + } + LastCode = CrntCode; + } + } + + Private->LastCode = LastCode; + Private->StackPtr = StackPtr; + + return GIF_OK; +} + +/****************************************************************************** + Routine to trace the Prefixes linked list until we get a prefix which is + not code, but a pixel value (less than ClearCode). Returns that pixel value. + If image is defective, we might loop here forever, so we limit the loops to + the maximum possible if image O.k. - LZ_MAX_CODE times. +******************************************************************************/ +static int +DGifGetPrefixChar(GifPrefixType *Prefix, int Code, int ClearCode) +{ + int i = 0; + + while (Code > ClearCode && i++ <= LZ_MAX_CODE) { + if (Code > LZ_MAX_CODE) { + return NO_SUCH_CODE; + } + Code = Prefix[Code]; + } + return Code; +} + +/****************************************************************************** + Interface for accessing the LZ codes directly. Set Code to the real code + (12bits), or to -1 if EOF code is returned. +******************************************************************************/ +int +DGifGetLZCodes(GifFileType *GifFile, int *Code) +{ + GifByteType *CodeBlock; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_READABLE(Private)) { + /* This file was NOT open for reading: */ + GifFile->Error = D_GIF_ERR_NOT_READABLE; + return GIF_ERROR; + } + + if (DGifDecompressInput(GifFile, Code) == GIF_ERROR) + return GIF_ERROR; + + if (*Code == Private->EOFCode) { + /* Skip rest of codes (hopefully only NULL terminating block): */ + do { + if (DGifGetCodeNext(GifFile, &CodeBlock) == GIF_ERROR) + return GIF_ERROR; + } while (CodeBlock != NULL) ; + + *Code = -1; + } else if (*Code == Private->ClearCode) { + /* We need to start over again: */ + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + } + + return GIF_OK; +} + +/****************************************************************************** + The LZ decompression input routine: + This routine is responsable for the decompression of the bit stream from + 8 bits (bytes) packets, into the real codes. + Returns GIF_OK if read successfully. +******************************************************************************/ +static int +DGifDecompressInput(GifFileType *GifFile, int *Code) +{ + static const unsigned short CodeMasks[] = { + 0x0000, 0x0001, 0x0003, 0x0007, + 0x000f, 0x001f, 0x003f, 0x007f, + 0x00ff, 0x01ff, 0x03ff, 0x07ff, + 0x0fff + }; + + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + GifByteType NextByte; + + /* The image can't contain more than LZ_BITS per code. */ + if (Private->RunningBits > LZ_BITS) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + + while (Private->CrntShiftState < Private->RunningBits) { + /* Needs to get more bytes from input stream for next code: */ + if (DGifBufferedInput(GifFile, Private->Buf, &NextByte) == GIF_ERROR) { + return GIF_ERROR; + } + Private->CrntShiftDWord |= + ((unsigned long)NextByte) << Private->CrntShiftState; + Private->CrntShiftState += 8; + } + *Code = Private->CrntShiftDWord & CodeMasks[Private->RunningBits]; + + Private->CrntShiftDWord >>= Private->RunningBits; + Private->CrntShiftState -= Private->RunningBits; + + /* If code cannot fit into RunningBits bits, must raise its size. Note + * however that codes above 4095 are used for special signaling. + * If we're using LZ_BITS bits already and we're at the max code, just + * keep using the table as it is, don't increment Private->RunningCode. + */ + if (Private->RunningCode < LZ_MAX_CODE + 2 && + ++Private->RunningCode > Private->MaxCode1 && + Private->RunningBits < LZ_BITS) { + Private->MaxCode1 <<= 1; + Private->RunningBits++; + } + return GIF_OK; +} + +/****************************************************************************** + This routines read one GIF data block at a time and buffers it internally + so that the decompression routine could access it. + The routine returns the next byte from its internal buffer (or read next + block in if buffer empty) and returns GIF_OK if succesful. +******************************************************************************/ +static int +DGifBufferedInput(GifFileType *GifFile, GifByteType *Buf, GifByteType *NextByte) +{ + if (Buf[0] == 0) { + /* Needs to read the next buffer - this one is empty: */ + if (READ(GifFile, Buf, 1) != 1) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + /* There shouldn't be any empty data blocks here as the LZW spec + * says the LZW termination code should come first. Therefore we + * shouldn't be inside this routine at that point. + */ + if (Buf[0] == 0) { + GifFile->Error = D_GIF_ERR_IMAGE_DEFECT; + return GIF_ERROR; + } + if (READ(GifFile, &Buf[1], Buf[0]) != Buf[0]) { + GifFile->Error = D_GIF_ERR_READ_FAILED; + return GIF_ERROR; + } + *NextByte = Buf[1]; + Buf[1] = 2; /* We use now the second place as last char read! */ + Buf[0]--; + } else { + *NextByte = Buf[Buf[1]++]; + Buf[0]--; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine reads an entire GIF into core, hanging all its state info off + the GifFileType pointer. Call DGifOpenFileName() or DGifOpenFileHandle() + first to initialize I/O. Its inverse is EGifSpew(). +*******************************************************************************/ +int +DGifSlurp(GifFileType *GifFile) +{ + size_t ImageSize; + GifRecordType RecordType; + SavedImage *sp; + GifByteType *ExtData; + int ExtFunction; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + + do { + if (DGifGetRecordType(GifFile, &RecordType) == GIF_ERROR) + return (GIF_ERROR); + + switch (RecordType) { + case IMAGE_DESC_RECORD_TYPE: + if (DGifGetImageDesc(GifFile) == GIF_ERROR) + return (GIF_ERROR); + + sp = &GifFile->SavedImages[GifFile->ImageCount - 1]; + /* Allocate memory for the image */ + if (sp->ImageDesc.Width < 0 && sp->ImageDesc.Height < 0 && + sp->ImageDesc.Width > (INT_MAX / sp->ImageDesc.Height)) { + return GIF_ERROR; + } + ImageSize = sp->ImageDesc.Width * sp->ImageDesc.Height; + + if (ImageSize > (SIZE_MAX / sizeof(GifPixelType))) { + return GIF_ERROR; + } + sp->RasterBits = (unsigned char *)malloc(ImageSize * + sizeof(GifPixelType)); + + if (sp->RasterBits == NULL) { + return GIF_ERROR; + } + + if (sp->ImageDesc.Interlace) { + int i, j; + /* + * The way an interlaced image should be read - + * offsets and jumps... + */ + int InterlacedOffset[] = { 0, 4, 2, 1 }; + int InterlacedJumps[] = { 8, 8, 4, 2 }; + /* Need to perform 4 passes on the image */ + for (i = 0; i < 4; i++) + for (j = InterlacedOffset[i]; + j < sp->ImageDesc.Height; + j += InterlacedJumps[i]) { + if (DGifGetLine(GifFile, + sp->RasterBits+j*sp->ImageDesc.Width, + sp->ImageDesc.Width) == GIF_ERROR) + return GIF_ERROR; + } + } + else { + if (DGifGetLine(GifFile,sp->RasterBits,ImageSize)==GIF_ERROR) + return (GIF_ERROR); + } + + if (GifFile->ExtensionBlocks) { + sp->ExtensionBlocks = GifFile->ExtensionBlocks; + sp->ExtensionBlockCount = GifFile->ExtensionBlockCount; + + GifFile->ExtensionBlocks = NULL; + GifFile->ExtensionBlockCount = 0; + } + break; + + case EXTENSION_RECORD_TYPE: + if (DGifGetExtension(GifFile,&ExtFunction,&ExtData) == GIF_ERROR) + return (GIF_ERROR); + /* Create an extension block with our data */ + if (ExtData != NULL) { + if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, + ExtFunction, ExtData[0], &ExtData[1]) + == GIF_ERROR) + return (GIF_ERROR); + } + while (ExtData != NULL) { + if (DGifGetExtensionNext(GifFile, &ExtData) == GIF_ERROR) + return (GIF_ERROR); + /* Continue the extension block */ + if (ExtData != NULL) + if (GifAddExtensionBlock(&GifFile->ExtensionBlockCount, + &GifFile->ExtensionBlocks, + CONTINUE_EXT_FUNC_CODE, + ExtData[0], &ExtData[1]) == GIF_ERROR) + return (GIF_ERROR); + } + break; + + case TERMINATE_RECORD_TYPE: + break; + + default: /* Should be trapped by DGifGetRecordType */ + break; + } + } while (RecordType != TERMINATE_RECORD_TYPE); + + return (GIF_OK); +} + +/* end */ diff --git a/src/lib/gif/egif_lib.c b/src/lib/gif/egif_lib.c new file mode 100644 index 00000000..39a62b2e --- /dev/null +++ b/src/lib/gif/egif_lib.c @@ -0,0 +1,1150 @@ +/****************************************************************************** + +egif_lib.c - GIF encoding + +The functions here and in dgif_lib.c are partitioned carefully so that +if you only require one of read and write capability, only one of these +two modules will be linked. Preserve this property! + +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif /* _WIN32 */ +#include + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/* Masks given codes to BitsPerPixel, to make sure all codes are in range: */ +/*@+charint@*/ +static const GifPixelType CodeMask[] = { + 0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff +}; +/*@-charint@*/ + +static int EGifPutWord(int Word, GifFileType * GifFile); +static int EGifSetupCompress(GifFileType * GifFile); +static int EGifCompressLine(GifFileType * GifFile, GifPixelType * Line, + int LineLen); +static int EGifCompressOutput(GifFileType * GifFile, int Code); +static int EGifBufferedOutput(GifFileType * GifFile, GifByteType * Buf, + int c); + +/* extract bytes from an unsigned word */ +#define LOBYTE(x) ((x) & 0xff) +#define HIBYTE(x) (((x) >> 8) & 0xff) + +/****************************************************************************** + Open a new GIF file for write, specified by name. If TestExistance then + if the file exists this routines fails (returns NULL). + Returns a dynamically allocated GifFileType pointer which serves as the GIF + info record. The Error member is cleared if successful. +******************************************************************************/ +GifFileType * +EGifOpenFileName(const char *FileName, const bool TestExistence, int *Error) +{ + + int FileHandle; + GifFileType *GifFile; + + if (TestExistence) + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_EXCL, + S_IREAD | S_IWRITE); + else + FileHandle = open(FileName, O_WRONLY | O_CREAT | O_TRUNC, + S_IREAD | S_IWRITE); + + if (FileHandle == -1) { + if (Error != NULL) + *Error = E_GIF_ERR_OPEN_FAILED; + return NULL; + } + GifFile = EGifOpenFileHandle(FileHandle, Error); + if (GifFile == (GifFileType *) NULL) + (void)close(FileHandle); + return GifFile; +} + +/****************************************************************************** + Update a new GIF file, given its file handle, which must be opened for + write in binary mode. + Returns dynamically allocated a GifFileType pointer which serves as the GIF + info record. + Only fails on a memory allocation error. +******************************************************************************/ +GifFileType * +EGifOpenFileHandle(const int FileHandle, int *Error) +{ + GifFileType *GifFile; + GifFilePrivateType *Private; + FILE *f; + + GifFile = (GifFileType *) malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + if (Error != NULL) + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + if ((Private->HashTable = _InitHashTable()) == NULL) { + free(GifFile); + free(Private); + if (Error != NULL) + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + +#ifdef _WIN32 + _setmode(FileHandle, O_BINARY); /* Make sure it is in binary mode. */ +#endif /* _WIN32 */ + + f = fdopen(FileHandle, "wb"); /* Make it into a stream: */ + + GifFile->Private = (void *)Private; + Private->FileHandle = FileHandle; + Private->File = f; + Private->FileState = FILE_STATE_WRITE; + + Private->Write = (OutputFunc) 0; /* No user write routine (MRB) */ + GifFile->UserData = (void *)NULL; /* No user write handle (MRB) */ + + GifFile->Error = 0; + + return GifFile; +} + +/****************************************************************************** + Output constructor that takes user supplied output function. + Basically just a copy of EGifOpenFileHandle. (MRB) +******************************************************************************/ +GifFileType * +EGifOpen(void *userData, OutputFunc writeFunc, int *Error) +{ + GifFileType *GifFile; + GifFilePrivateType *Private; + + GifFile = (GifFileType *)malloc(sizeof(GifFileType)); + if (GifFile == NULL) { + if (Error != NULL) + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + memset(GifFile, '\0', sizeof(GifFileType)); + + Private = (GifFilePrivateType *)malloc(sizeof(GifFilePrivateType)); + if (Private == NULL) { + free(GifFile); + if (Error != NULL) + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + Private->HashTable = _InitHashTable(); + if (Private->HashTable == NULL) { + free (GifFile); + free (Private); + if (Error != NULL) + *Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return NULL; + } + + GifFile->Private = (void *)Private; + Private->FileHandle = 0; + Private->File = (FILE *) 0; + Private->FileState = FILE_STATE_WRITE; + + Private->Write = writeFunc; /* User write routine (MRB) */ + GifFile->UserData = userData; /* User write handle (MRB) */ + + Private->gif89 = false; /* initially, write GIF87 */ + + GifFile->Error = 0; + + return GifFile; +} + +/****************************************************************************** + Routine to compute the GIF version that will be written on output. +******************************************************************************/ +const char * +EGifGetGifVersion(GifFileType *GifFile) +{ + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + int i, j; + + /* + * Bulletproofing - always write GIF89 if we need to. + * Note, we don't clear the gif89 flag here because + * users of the sequential API might have called EGifSetGifVersion() + * in order to set that flag. + */ + for (i = 0; i < GifFile->ImageCount; i++) { + for (j = 0; j < GifFile->SavedImages[i].ExtensionBlockCount; j++) { + int function = + GifFile->SavedImages[i].ExtensionBlocks[j].Function; + + if (function == COMMENT_EXT_FUNC_CODE + || function == GRAPHICS_EXT_FUNC_CODE + || function == PLAINTEXT_EXT_FUNC_CODE + || function == APPLICATION_EXT_FUNC_CODE) + Private->gif89 = true; + } + } + for (i = 0; i < GifFile->ExtensionBlockCount; i++) { + int function = GifFile->ExtensionBlocks[i].Function; + + if (function == COMMENT_EXT_FUNC_CODE + || function == GRAPHICS_EXT_FUNC_CODE + || function == PLAINTEXT_EXT_FUNC_CODE + || function == APPLICATION_EXT_FUNC_CODE) + Private->gif89 = true; + } + + if (Private->gif89) + return GIF89_STAMP; + else + return GIF87_STAMP; +} + +/****************************************************************************** + Set the GIF version. In the extremely unlikely event that there is ever + another version, replace the bool argument with an enum in which the + GIF87 value is 0 (numerically the same as bool false) and the GIF89 value + is 1 (numerically the same as bool true). That way we'll even preserve + object-file compatibility! +******************************************************************************/ +void EGifSetGifVersion(GifFileType *GifFile, const bool gif89) +{ + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + Private->gif89 = gif89; +} + +/****************************************************************************** + All writes to the GIF should go through this. +******************************************************************************/ +static int InternalWrite(GifFileType *GifFileOut, + const unsigned char *buf, size_t len) +{ + GifFilePrivateType *Private = (GifFilePrivateType*)GifFileOut->Private; + if (Private->Write) + return Private->Write(GifFileOut,buf,len); + else + return fwrite(buf, 1, len, Private->File); +} + +/****************************************************************************** + This routine should be called before any other EGif calls, immediately + following the GIF file opening. +******************************************************************************/ +int +EGifPutScreenDesc(GifFileType *GifFile, + const int Width, + const int Height, + const int ColorRes, + const int BackGround, + const ColorMapObject *ColorMap) +{ + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + const char *write_version; + + if (Private->FileState & FILE_STATE_SCREEN) { + /* If already has screen descriptor - something is wrong! */ + GifFile->Error = E_GIF_ERR_HAS_SCRN_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + write_version = EGifGetGifVersion(GifFile); + + /* First write the version prefix into the file. */ + if (InternalWrite(GifFile, (unsigned char *)write_version, + strlen(write_version)) != strlen(write_version)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + + GifFile->SWidth = Width; + GifFile->SHeight = Height; + GifFile->SColorResolution = ColorRes; + GifFile->SBackGroundColor = BackGround; + if (ColorMap) { + GifFile->SColorMap = GifMakeMapObject(ColorMap->ColorCount, + ColorMap->Colors); + if (GifFile->SColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else + GifFile->SColorMap = NULL; + + /* + * Put the logical screen descriptor into the file: + */ + /* Logical Screen Descriptor: Dimensions */ + (void)EGifPutWord(Width, GifFile); + (void)EGifPutWord(Height, GifFile); + + /* Logical Screen Descriptor: Packed Fields */ + /* Note: We have actual size of the color table default to the largest + * possible size (7+1 == 8 bits) because the decoder can use it to decide + * how to display the files. + */ + Buf[0] = (ColorMap ? 0x80 : 0x00) | /* Yes/no global colormap */ + ((ColorRes - 1) << 4) | /* Bits allocated to each primary color */ + (ColorMap ? ColorMap->BitsPerPixel - 1 : 0x07 ); /* Actual size of the + color table. */ + if (ColorMap != NULL && ColorMap->SortFlag) + Buf[0] |= 0x08; + Buf[1] = BackGround; /* Index into the ColorTable for background color */ + Buf[2] = GifFile->AspectByte; /* Pixel Aspect Ratio */ + InternalWrite(GifFile, Buf, 3); + + /* If we have Global color map - dump it also: */ + if (ColorMap != NULL) { + int i; + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (InternalWrite(GifFile, Buf, 3) != 3) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } + } + + /* Mark this file as has screen descriptor, and no pixel written yet: */ + Private->FileState |= FILE_STATE_SCREEN; + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called before any attempt to dump an image - any + call to any of the pixel dump routines. +******************************************************************************/ +int +EGifPutImageDesc(GifFileType *GifFile, + const int Left, + const int Top, + const int Width, + const int Height, + const bool Interlace, + const ColorMapObject *ColorMap) +{ + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (Private->FileState & FILE_STATE_IMAGE && + Private->PixelCount > 0xffff0000UL) { + /* If already has active image descriptor - something is wrong! */ + GifFile->Error = E_GIF_ERR_HAS_IMAG_DSCR; + return GIF_ERROR; + } + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + GifFile->Image.Left = Left; + GifFile->Image.Top = Top; + GifFile->Image.Width = Width; + GifFile->Image.Height = Height; + GifFile->Image.Interlace = Interlace; + if (ColorMap) { + if (GifFile->Image.ColorMap != NULL) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + GifFile->Image.ColorMap = GifMakeMapObject(ColorMap->ColorCount, + ColorMap->Colors); + if (GifFile->Image.ColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NOT_ENOUGH_MEM; + return GIF_ERROR; + } + } else { + GifFile->Image.ColorMap = NULL; + } + + /* Put the image descriptor into the file: */ + Buf[0] = DESCRIPTOR_INTRODUCER; /* Image separator character. */ + InternalWrite(GifFile, Buf, 1); + (void)EGifPutWord(Left, GifFile); + (void)EGifPutWord(Top, GifFile); + (void)EGifPutWord(Width, GifFile); + (void)EGifPutWord(Height, GifFile); + Buf[0] = (ColorMap ? 0x80 : 0x00) | + (Interlace ? 0x40 : 0x00) | + (ColorMap ? ColorMap->BitsPerPixel - 1 : 0); + InternalWrite(GifFile, Buf, 1); + + /* If we have Global color map - dump it also: */ + if (ColorMap != NULL) { + int i; + for (i = 0; i < ColorMap->ColorCount; i++) { + /* Put the ColorMap out also: */ + Buf[0] = ColorMap->Colors[i].Red; + Buf[1] = ColorMap->Colors[i].Green; + Buf[2] = ColorMap->Colors[i].Blue; + if (InternalWrite(GifFile, Buf, 3) != 3) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } + } + if (GifFile->SColorMap == NULL && GifFile->Image.ColorMap == NULL) { + GifFile->Error = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + /* Mark this file as has screen descriptor: */ + Private->FileState |= FILE_STATE_IMAGE; + Private->PixelCount = (long)Width *(long)Height; + + /* Reset compress algorithm parameters. */ + (void)EGifSetupCompress(GifFile); + + return GIF_OK; +} + +/****************************************************************************** + Put one full scanned line (Line) of length LineLen into GIF file. +******************************************************************************/ +int +EGifPutLine(GifFileType * GifFile, GifPixelType *Line, int LineLen) +{ + int i; + GifPixelType Mask; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (!LineLen) + LineLen = GifFile->Image.Width; + if (Private->PixelCount < (unsigned)LineLen) { + GifFile->Error = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + Private->PixelCount -= LineLen; + + /* Make sure the codes are not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: */ + Mask = CodeMask[Private->BitsPerPixel]; + for (i = 0; i < LineLen; i++) + Line[i] &= Mask; + + return EGifCompressLine(GifFile, Line, LineLen); +} + +/****************************************************************************** + Put one pixel (Pixel) into GIF file. +******************************************************************************/ +int +EGifPutPixel(GifFileType *GifFile, GifPixelType Pixel) +{ + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (Private->PixelCount == 0) { + GifFile->Error = E_GIF_ERR_DATA_TOO_BIG; + return GIF_ERROR; + } + --Private->PixelCount; + + /* Make sure the code is not out of bit range, as we might generate + * wrong code (because of overflow when we combine them) in this case: */ + Pixel &= CodeMask[Private->BitsPerPixel]; + + return EGifCompressLine(GifFile, &Pixel, 1); +} + +/****************************************************************************** + Put a comment into GIF file using the GIF89 comment extension block. +******************************************************************************/ +int +EGifPutComment(GifFileType *GifFile, const char *Comment) +{ + unsigned int length; + char *buf; + + length = strlen(Comment); + if (length <= 255) { + return EGifPutExtension(GifFile, COMMENT_EXT_FUNC_CODE, + length, Comment); + } else { + buf = (char *)Comment; + if (EGifPutExtensionLeader(GifFile, COMMENT_EXT_FUNC_CODE) + == GIF_ERROR) { + return GIF_ERROR; + } + + /* Break the comment into 255 byte sub blocks */ + while (length > 255) { + if (EGifPutExtensionBlock(GifFile, 255, buf) == GIF_ERROR) { + return GIF_ERROR; + } + buf = buf + 255; + length -= 255; + } + /* Output any partial block and the clear code. */ + if (length > 0) { + if (EGifPutExtensionBlock(GifFile, length, buf) == GIF_ERROR) { + return GIF_ERROR; + } + } + if (EGifPutExtensionTrailer(GifFile) == GIF_ERROR) { + return GIF_ERROR; + } + } + return GIF_OK; +} + +/****************************************************************************** + Begin an extension block (see GIF manual). More + extensions can be dumped using EGifPutExtensionBlock until + EGifPutExtensionTrailer is invoked. +******************************************************************************/ +int +EGifPutExtensionLeader(GifFileType *GifFile, const int ExtCode) +{ + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + Buf[0] = EXTENSION_INTRODUCER; + Buf[1] = ExtCode; + InternalWrite(GifFile, Buf, 2); + + return GIF_OK; +} + +/****************************************************************************** + Put extension block data (see GIF manual) into a GIF file. +******************************************************************************/ +int +EGifPutExtensionBlock(GifFileType *GifFile, + const int ExtLen, + const void *Extension) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + Buf = ExtLen; + InternalWrite(GifFile, &Buf, 1); + InternalWrite(GifFile, Extension, ExtLen); + + return GIF_OK; +} + +/****************************************************************************** + Put a terminating block (see GIF manual) into a GIF file. +******************************************************************************/ +int +EGifPutExtensionTrailer(GifFileType *GifFile) { + + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* Write the block terminator */ + Buf = 0; + InternalWrite(GifFile, &Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + Put an extension block (see GIF manual) into a GIF file. + Warning: This function is only useful for Extension blocks that have at + most one subblock. Extensions with more than one subblock need to use the + EGifPutExtension{Leader,Block,Trailer} functions instead. +******************************************************************************/ +int +EGifPutExtension(GifFileType *GifFile, + const int ExtCode, + const int ExtLen, + const void *Extension) { + + GifByteType Buf[3]; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + if (ExtCode == 0) + InternalWrite(GifFile, (GifByteType *)&ExtLen, 1); + else { + Buf[0] = EXTENSION_INTRODUCER; + Buf[1] = ExtCode; /* Extension Label */ + Buf[2] = ExtLen; /* Extension length */ + InternalWrite(GifFile, Buf, 3); + } + InternalWrite(GifFile, Extension, ExtLen); + Buf[0] = 0; + InternalWrite(GifFile, Buf, 1); + + return GIF_OK; +} + +/****************************************************************************** + Render a Graphics Control Block as raw extension data +******************************************************************************/ + +size_t EGifGCBToExtension(const GraphicsControlBlock *GCB, + GifByteType *GifExtension) +{ + GifExtension[0] = 0; + GifExtension[0] |= (GCB->TransparentColor == NO_TRANSPARENT_COLOR) ? 0x00 : 0x01; + GifExtension[0] |= GCB->UserInputFlag ? 0x02 : 0x00; + GifExtension[0] |= ((GCB->DisposalMode & 0x07) << 2); + GifExtension[1] = LOBYTE(GCB->DelayTime); + GifExtension[2] = HIBYTE(GCB->DelayTime); + GifExtension[3] = (char)GCB->TransparentColor; + return 4; +} + +/****************************************************************************** + Replace the Graphics Control Block for a saved image, if it exists. +******************************************************************************/ + +int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, + GifFileType *GifFile, int ImageIndex) +{ + int i; + size_t Len; + GifByteType buf[sizeof(GraphicsControlBlock)]; /* a bit dodgy... */ + + if (ImageIndex < 0 || ImageIndex > GifFile->ImageCount - 1) + return GIF_ERROR; + + for (i = 0; i < GifFile->SavedImages[ImageIndex].ExtensionBlockCount; i++) { + ExtensionBlock *ep = &GifFile->SavedImages[ImageIndex].ExtensionBlocks[i]; + if (ep->Function == GRAPHICS_EXT_FUNC_CODE) { + EGifGCBToExtension(GCB, ep->Bytes); + return GIF_OK; + } + } + + Len = EGifGCBToExtension(GCB, (GifByteType *)buf); + if (GifAddExtensionBlock(&GifFile->SavedImages[ImageIndex].ExtensionBlockCount, + &GifFile->SavedImages[ImageIndex].ExtensionBlocks, + GRAPHICS_EXT_FUNC_CODE, + Len, + (unsigned char *)buf) == GIF_ERROR) + return (GIF_ERROR); + + return (GIF_OK); +} + +/****************************************************************************** + Put the image code in compressed form. This routine can be called if the + information needed to be piped out as is. Obviously this is much faster + than decoding and encoding again. This routine should be followed by calls + to EGifPutCodeNext, until NULL block is given. + The block should NOT be freed by the user (not dynamically allocated). +******************************************************************************/ +int +EGifPutCode(GifFileType *GifFile, int CodeSize, const GifByteType *CodeBlock) +{ + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + GifFile->Error = E_GIF_ERR_NOT_WRITEABLE; + return GIF_ERROR; + } + + /* No need to dump code size as Compression set up does any for us: */ + /* + * Buf = CodeSize; + * if (InternalWrite(GifFile, &Buf, 1) != 1) { + * GifFile->Error = E_GIF_ERR_WRITE_FAILED; + * return GIF_ERROR; + * } + */ + + return EGifPutCodeNext(GifFile, CodeBlock); +} + +/****************************************************************************** + Continue to put the image code in compressed form. This routine should be + called with blocks of code as read via DGifGetCode/DGifGetCodeNext. If + given buffer pointer is NULL, empty block is written to mark end of code. +******************************************************************************/ +int +EGifPutCodeNext(GifFileType *GifFile, const GifByteType *CodeBlock) +{ + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *)GifFile->Private; + + if (CodeBlock != NULL) { + if (InternalWrite(GifFile, CodeBlock, CodeBlock[0] + 1) + != (unsigned)(CodeBlock[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + Buf = 0; + if (InternalWrite(GifFile, &Buf, 1) != 1) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Private->PixelCount = 0; /* And local info. indicate image read. */ + } + + return GIF_OK; +} + +/****************************************************************************** + This routine should be called last, to close the GIF file. +******************************************************************************/ +int +EGifCloseFile(GifFileType *GifFile, int *ErrorCode) +{ + GifByteType Buf; + GifFilePrivateType *Private; + FILE *File; + + if (GifFile == NULL) + return GIF_ERROR; + + Private = (GifFilePrivateType *) GifFile->Private; + if (Private == NULL) + return GIF_ERROR; + if (!IS_WRITEABLE(Private)) { + /* This file was NOT open for writing: */ + if (ErrorCode != NULL) + *ErrorCode = E_GIF_ERR_NOT_WRITEABLE; + free(GifFile); + return GIF_ERROR; + } + + File = Private->File; + + Buf = TERMINATOR_INTRODUCER; + InternalWrite(GifFile, &Buf, 1); + + if (GifFile->Image.ColorMap) { + GifFreeMapObject(GifFile->Image.ColorMap); + GifFile->Image.ColorMap = NULL; + } + if (GifFile->SColorMap) { + GifFreeMapObject(GifFile->SColorMap); + GifFile->SColorMap = NULL; + } + if (Private) { + if (Private->HashTable) { + free((char *) Private->HashTable); + } + free((char *) Private); + } + + if (File && fclose(File) != 0) { + if (ErrorCode != NULL) + *ErrorCode = E_GIF_ERR_CLOSE_FAILED; + free(GifFile); + return GIF_ERROR; + } + + free(GifFile); + if (ErrorCode != NULL) + *ErrorCode = E_GIF_SUCCEEDED; + return GIF_OK; +} + +/****************************************************************************** + Put 2 bytes (a word) into the given file in little-endian order: +******************************************************************************/ +static int +EGifPutWord(int Word, GifFileType *GifFile) +{ + unsigned char c[2]; + + c[0] = LOBYTE(Word); + c[1] = HIBYTE(Word); + if (InternalWrite(GifFile, c, 2) == 2) + return GIF_OK; + else + return GIF_ERROR; +} + +/****************************************************************************** + Setup the LZ compression for this image: +******************************************************************************/ +static int +EGifSetupCompress(GifFileType *GifFile) +{ + int BitsPerPixel; + GifByteType Buf; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + /* Test and see what color map to use, and from it # bits per pixel: */ + if (GifFile->Image.ColorMap) + BitsPerPixel = GifFile->Image.ColorMap->BitsPerPixel; + else if (GifFile->SColorMap) + BitsPerPixel = GifFile->SColorMap->BitsPerPixel; + else { + GifFile->Error = E_GIF_ERR_NO_COLOR_MAP; + return GIF_ERROR; + } + + Buf = BitsPerPixel = (BitsPerPixel < 2 ? 2 : BitsPerPixel); + InternalWrite(GifFile, &Buf, 1); /* Write the Code size to file. */ + + Private->Buf[0] = 0; /* Nothing was output yet. */ + Private->BitsPerPixel = BitsPerPixel; + Private->ClearCode = (1 << BitsPerPixel); + Private->EOFCode = Private->ClearCode + 1; + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = BitsPerPixel + 1; /* Number of bits per code. */ + Private->MaxCode1 = 1 << Private->RunningBits; /* Max. code + 1. */ + Private->CrntCode = FIRST_CODE; /* Signal that this is first one! */ + Private->CrntShiftState = 0; /* No information in CrntShiftDWord. */ + Private->CrntShiftDWord = 0; + + /* Clear hash table and send Clear to make sure the decoder do the same. */ + _ClearHashTable(Private->HashTable); + + if (EGifCompressOutput(GifFile, Private->ClearCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + return GIF_OK; +} + +/****************************************************************************** + The LZ compression routine: + This version compresses the given buffer Line of length LineLen. + This routine can be called a few times (one per scan line, for example), in + order to complete the whole image. +******************************************************************************/ +static int +EGifCompressLine(GifFileType *GifFile, + GifPixelType *Line, + const int LineLen) +{ + int i = 0, CrntCode, NewCode; + unsigned long NewKey; + GifPixelType Pixel; + GifHashTableType *HashTable; + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + + HashTable = Private->HashTable; + + if (Private->CrntCode == FIRST_CODE) /* Its first time! */ + CrntCode = Line[i++]; + else + CrntCode = Private->CrntCode; /* Get last code in compression. */ + + while (i < LineLen) { /* Decode LineLen items. */ + Pixel = Line[i++]; /* Get next pixel from stream. */ + /* Form a new unique key to search hash table for the code combines + * CrntCode as Prefix string with Pixel as postfix char. + */ + NewKey = (((uint32_t) CrntCode) << 8) + Pixel; + if ((NewCode = _ExistsHashTable(HashTable, NewKey)) >= 0) { + /* This Key is already there, or the string is old one, so + * simple take new code as our CrntCode: + */ + CrntCode = NewCode; + } else { + /* Put it in hash table, output the prefix code, and make our + * CrntCode equal to Pixel. + */ + if (EGifCompressOutput(GifFile, CrntCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + CrntCode = Pixel; + + /* If however the HashTable if full, we send a clear first and + * Clear the hash table. + */ + if (Private->RunningCode >= LZ_MAX_CODE) { + /* Time to do some clearance: */ + if (EGifCompressOutput(GifFile, Private->ClearCode) + == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + Private->RunningCode = Private->EOFCode + 1; + Private->RunningBits = Private->BitsPerPixel + 1; + Private->MaxCode1 = 1 << Private->RunningBits; + _ClearHashTable(HashTable); + } else { + /* Put this unique key with its relative Code in hash table: */ + _InsertHashTable(HashTable, NewKey, Private->RunningCode++); + } + } + + } + + /* Preserve the current state of the compression algorithm: */ + Private->CrntCode = CrntCode; + + if (Private->PixelCount == 0) { + /* We are done - output last Code and flush output buffers: */ + if (EGifCompressOutput(GifFile, CrntCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, Private->EOFCode) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + if (EGifCompressOutput(GifFile, FLUSH_OUTPUT) == GIF_ERROR) { + GifFile->Error = E_GIF_ERR_DISK_IS_FULL; + return GIF_ERROR; + } + } + + return GIF_OK; +} + +/****************************************************************************** + The LZ compression output routine: + This routine is responsible for the compression of the bit stream into + 8 bits (bytes) packets. + Returns GIF_OK if written successfully. +******************************************************************************/ +static int +EGifCompressOutput(GifFileType *GifFile, + const int Code) +{ + GifFilePrivateType *Private = (GifFilePrivateType *) GifFile->Private; + int retval = GIF_OK; + + if (Code == FLUSH_OUTPUT) { + while (Private->CrntShiftState > 0) { + /* Get Rid of what is left in DWord, and flush it. */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & 0xff) == GIF_ERROR) + retval = GIF_ERROR; + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + Private->CrntShiftState = 0; /* For next time. */ + if (EGifBufferedOutput(GifFile, Private->Buf, + FLUSH_OUTPUT) == GIF_ERROR) + retval = GIF_ERROR; + } else { + Private->CrntShiftDWord |= ((long)Code) << Private->CrntShiftState; + Private->CrntShiftState += Private->RunningBits; + while (Private->CrntShiftState >= 8) { + /* Dump out full bytes: */ + if (EGifBufferedOutput(GifFile, Private->Buf, + Private->CrntShiftDWord & 0xff) == GIF_ERROR) + retval = GIF_ERROR; + Private->CrntShiftDWord >>= 8; + Private->CrntShiftState -= 8; + } + } + + /* If code cannt fit into RunningBits bits, must raise its size. Note */ + /* however that codes above 4095 are used for special signaling. */ + if (Private->RunningCode >= Private->MaxCode1 && Code <= 4095) { + Private->MaxCode1 = 1 << ++Private->RunningBits; + } + + return retval; +} + +/****************************************************************************** + This routines buffers the given characters until 255 characters are ready + to be output. If Code is equal to -1 the buffer is flushed (EOF). + The buffer is Dumped with first byte as its size, as GIF format requires. + Returns GIF_OK if written successfully. +******************************************************************************/ +static int +EGifBufferedOutput(GifFileType *GifFile, + GifByteType *Buf, + int c) +{ + if (c == FLUSH_OUTPUT) { + /* Flush everything out. */ + if (Buf[0] != 0 + && InternalWrite(GifFile, Buf, Buf[0] + 1) != (unsigned)(Buf[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + /* Mark end of compressed data, by an empty block (see GIF doc): */ + Buf[0] = 0; + if (InternalWrite(GifFile, Buf, 1) != 1) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + } else { + if (Buf[0] == 255) { + /* Dump out this buffer - it is full: */ + if (InternalWrite(GifFile, Buf, Buf[0] + 1) != (unsigned)(Buf[0] + 1)) { + GifFile->Error = E_GIF_ERR_WRITE_FAILED; + return GIF_ERROR; + } + Buf[0] = 0; + } + Buf[++Buf[0]] = c; + } + + return GIF_OK; +} + +/****************************************************************************** + This routine writes to disk an in-core representation of a GIF previously + created by DGifSlurp(). +******************************************************************************/ + +static int +EGifWriteExtensions(GifFileType *GifFileOut, + ExtensionBlock *ExtensionBlocks, + int ExtensionBlockCount) +{ + if (ExtensionBlocks) { + ExtensionBlock *ep; + int j; + + for (j = 0; j < ExtensionBlockCount; j++) { + ep = &ExtensionBlocks[j]; + if (ep->Function != CONTINUE_EXT_FUNC_CODE) + if (EGifPutExtensionLeader(GifFileOut, ep->Function) == GIF_ERROR) + return (GIF_ERROR); + if (EGifPutExtensionBlock(GifFileOut, ep->ByteCount, ep->Bytes) == GIF_ERROR) + return (GIF_ERROR); + if (j == ExtensionBlockCount - 1 || (ep+1)->Function != CONTINUE_EXT_FUNC_CODE) + if (EGifPutExtensionTrailer(GifFileOut) == GIF_ERROR) + return (GIF_ERROR); + } + } + + return (GIF_OK); +} + +int +EGifSpew(GifFileType *GifFileOut) +{ + int i, j; + + if (EGifPutScreenDesc(GifFileOut, + GifFileOut->SWidth, + GifFileOut->SHeight, + GifFileOut->SColorResolution, + GifFileOut->SBackGroundColor, + GifFileOut->SColorMap) == GIF_ERROR) { + return (GIF_ERROR); + } + + for (i = 0; i < GifFileOut->ImageCount; i++) { + SavedImage *sp = &GifFileOut->SavedImages[i]; + int SavedHeight = sp->ImageDesc.Height; + int SavedWidth = sp->ImageDesc.Width; + + /* this allows us to delete images by nuking their rasters */ + if (sp->RasterBits == NULL) + continue; + + if (EGifWriteExtensions(GifFileOut, + sp->ExtensionBlocks, + sp->ExtensionBlockCount) == GIF_ERROR) + return (GIF_ERROR); + + if (EGifPutImageDesc(GifFileOut, + sp->ImageDesc.Left, + sp->ImageDesc.Top, + SavedWidth, + SavedHeight, + sp->ImageDesc.Interlace, + sp->ImageDesc.ColorMap) == GIF_ERROR) + return (GIF_ERROR); + + if (sp->ImageDesc.Interlace) { + /* + * The way an interlaced image should be written - + * offsets and jumps... + */ + int InterlacedOffset[] = { 0, 4, 2, 1 }; + int InterlacedJumps[] = { 8, 8, 4, 2 }; + int k; + /* Need to perform 4 passes on the images: */ + for (k = 0; k < 4; k++) + for (j = InterlacedOffset[k]; + j < SavedHeight; + j += InterlacedJumps[k]) { + if (EGifPutLine(GifFileOut, + sp->RasterBits + j * SavedWidth, + SavedWidth) == GIF_ERROR) + return (GIF_ERROR); + } + } else { + for (j = 0; j < SavedHeight; j++) { + if (EGifPutLine(GifFileOut, + sp->RasterBits + j * SavedWidth, + SavedWidth) == GIF_ERROR) + return (GIF_ERROR); + } + } + } + + if (EGifWriteExtensions(GifFileOut, + GifFileOut->ExtensionBlocks, + GifFileOut->ExtensionBlockCount) == GIF_ERROR) + return (GIF_ERROR); + + if (EGifCloseFile(GifFileOut, NULL) == GIF_ERROR) + return (GIF_ERROR); + + return (GIF_OK); +} + +/* end */ diff --git a/src/lib/gif/gif_err.c b/src/lib/gif/gif_err.c new file mode 100644 index 00000000..3ec2a56f --- /dev/null +++ b/src/lib/gif/gif_err.c @@ -0,0 +1,97 @@ +/***************************************************************************** + +gif_err.c - handle error reporting for the GIF library. + +****************************************************************************/ + +#include + +#include "gif_lib.h" +#include "gif_lib_private.h" + +/***************************************************************************** + Return a string description of the last GIF error +*****************************************************************************/ +const char * +GifErrorString(int ErrorCode) +{ + const char *Err; + + switch (ErrorCode) { + case E_GIF_ERR_OPEN_FAILED: + Err = "Failed to open given file"; + break; + case E_GIF_ERR_WRITE_FAILED: + Err = "Failed to write to given file"; + break; + case E_GIF_ERR_HAS_SCRN_DSCR: + Err = "Screen descriptor has already been set"; + break; + case E_GIF_ERR_HAS_IMAG_DSCR: + Err = "Image descriptor is still active"; + break; + case E_GIF_ERR_NO_COLOR_MAP: + Err = "Neither global nor local color map"; + break; + case E_GIF_ERR_DATA_TOO_BIG: + Err = "Number of pixels bigger than width * height"; + break; + case E_GIF_ERR_NOT_ENOUGH_MEM: + Err = "Failed to allocate required memory"; + break; + case E_GIF_ERR_DISK_IS_FULL: + Err = "Write failed (disk full?)"; + break; + case E_GIF_ERR_CLOSE_FAILED: + Err = "Failed to close given file"; + break; + case E_GIF_ERR_NOT_WRITEABLE: + Err = "Given file was not opened for write"; + break; + case D_GIF_ERR_OPEN_FAILED: + Err = "Failed to open given file"; + break; + case D_GIF_ERR_READ_FAILED: + Err = "Failed to read from given file"; + break; + case D_GIF_ERR_NOT_GIF_FILE: + Err = "Data is not in GIF format"; + break; + case D_GIF_ERR_NO_SCRN_DSCR: + Err = "No screen descriptor detected"; + break; + case D_GIF_ERR_NO_IMAG_DSCR: + Err = "No Image Descriptor detected"; + break; + case D_GIF_ERR_NO_COLOR_MAP: + Err = "Neither global nor local color map"; + break; + case D_GIF_ERR_WRONG_RECORD: + Err = "Wrong record type detected"; + break; + case D_GIF_ERR_DATA_TOO_BIG: + Err = "Number of pixels bigger than width * height"; + break; + case D_GIF_ERR_NOT_ENOUGH_MEM: + Err = "Failed to allocate required memory"; + break; + case D_GIF_ERR_CLOSE_FAILED: + Err = "Failed to close given file"; + break; + case D_GIF_ERR_NOT_READABLE: + Err = "Given file was not opened for read"; + break; + case D_GIF_ERR_IMAGE_DEFECT: + Err = "Image is defective, decoding aborted"; + break; + case D_GIF_ERR_EOF_TOO_SOON: + Err = "Image EOF detected before image complete"; + break; + default: + Err = NULL; + break; + } + return Err; +} + +/* end */ diff --git a/src/lib/gif/gif_font.c b/src/lib/gif/gif_font.c new file mode 100644 index 00000000..ba47b29e --- /dev/null +++ b/src/lib/gif/gif_font.c @@ -0,0 +1,252 @@ +/***************************************************************************** + +gif_font.c - utility font handling and simple drawing for the GIF library + +****************************************************************************/ + +#include + +#include "gif_lib.h" + +/***************************************************************************** + Ascii 8 by 8 regular font - only first 128 characters are supported. +*****************************************************************************/ + +/* + * Each array entry holds the bits for 8 horizontal scan lines, topmost + * first. The most significant bit of each constant is the leftmost bit of + * the scan line. + */ +/*@+charint@*/ +const unsigned char GifAsciiTable8x8[][GIF_FONT_WIDTH] = { + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* Ascii 0 */ + {0x3c, 0x42, 0xa5, 0x81, 0xbd, 0x42, 0x3c, 0x00}, /* Ascii 1 */ + {0x3c, 0x7e, 0xdb, 0xff, 0xc3, 0x7e, 0x3c, 0x00}, /* Ascii 2 */ + {0x00, 0xee, 0xfe, 0xfe, 0x7c, 0x38, 0x10, 0x00}, /* Ascii 3 */ + {0x10, 0x38, 0x7c, 0xfe, 0x7c, 0x38, 0x10, 0x00}, /* Ascii 4 */ + {0x00, 0x3c, 0x18, 0xff, 0xff, 0x08, 0x18, 0x00}, /* Ascii 5 */ + {0x10, 0x38, 0x7c, 0xfe, 0xfe, 0x10, 0x38, 0x00}, /* Ascii 6 */ + {0x00, 0x00, 0x18, 0x3c, 0x18, 0x00, 0x00, 0x00}, /* Ascii 7 */ + {0xff, 0xff, 0xe7, 0xc3, 0xe7, 0xff, 0xff, 0xff}, /* Ascii 8 */ + {0x00, 0x3c, 0x42, 0x81, 0x81, 0x42, 0x3c, 0x00}, /* Ascii 9 */ + {0xff, 0xc3, 0xbd, 0x7e, 0x7e, 0xbd, 0xc3, 0xff}, /* Ascii 10 */ + {0x1f, 0x07, 0x0d, 0x7c, 0xc6, 0xc6, 0x7c, 0x00}, /* Ascii 11 */ + {0x00, 0x7e, 0xc3, 0xc3, 0x7e, 0x18, 0x7e, 0x18}, /* Ascii 12 */ + {0x04, 0x06, 0x07, 0x04, 0x04, 0xfc, 0xf8, 0x00}, /* Ascii 13 */ + {0x0c, 0x0a, 0x0d, 0x0b, 0xf9, 0xf9, 0x1f, 0x1f}, /* Ascii 14 */ + {0x00, 0x92, 0x7c, 0x44, 0xc6, 0x7c, 0x92, 0x00}, /* Ascii 15 */ + {0x00, 0x00, 0x60, 0x78, 0x7e, 0x78, 0x60, 0x00}, /* Ascii 16 */ + {0x00, 0x00, 0x06, 0x1e, 0x7e, 0x1e, 0x06, 0x00}, /* Ascii 17 */ + {0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18}, /* Ascii 18 */ + {0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00}, /* Ascii 19 */ + {0xff, 0xb6, 0x76, 0x36, 0x36, 0x36, 0x36, 0x00}, /* Ascii 20 */ + {0x7e, 0xc1, 0xdc, 0x22, 0x22, 0x1f, 0x83, 0x7e}, /* Ascii 21 */ + {0x00, 0x00, 0x00, 0x7e, 0x7e, 0x00, 0x00, 0x00}, /* Ascii 22 */ + {0x18, 0x7e, 0x18, 0x18, 0x7e, 0x18, 0x00, 0xff}, /* Ascii 23 */ + {0x18, 0x7e, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00}, /* Ascii 24 */ + {0x18, 0x18, 0x18, 0x18, 0x18, 0x7e, 0x18, 0x00}, /* Ascii 25 */ + {0x00, 0x04, 0x06, 0xff, 0x06, 0x04, 0x00, 0x00}, /* Ascii 26 */ + {0x00, 0x20, 0x60, 0xff, 0x60, 0x20, 0x00, 0x00}, /* Ascii 27 */ + {0x00, 0x00, 0x00, 0xc0, 0xc0, 0xc0, 0xff, 0x00}, /* Ascii 28 */ + {0x00, 0x24, 0x66, 0xff, 0x66, 0x24, 0x00, 0x00}, /* Ascii 29 */ + {0x00, 0x00, 0x10, 0x38, 0x7c, 0xfe, 0x00, 0x00}, /* Ascii 30 */ + {0x00, 0x00, 0x00, 0xfe, 0x7c, 0x38, 0x10, 0x00}, /* Ascii 31 */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* */ + {0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x00}, /* ! */ + {0x66, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* " */ + {0x6c, 0x6c, 0xfe, 0x6c, 0xfe, 0x6c, 0x6c, 0x00}, /* # */ + {0x10, 0x7c, 0xd2, 0x7c, 0x86, 0x7c, 0x10, 0x00}, /* $ */ + {0xf0, 0x96, 0xfc, 0x18, 0x3e, 0x72, 0xde, 0x00}, /* % */ + {0x30, 0x48, 0x30, 0x78, 0xce, 0xcc, 0x78, 0x00}, /* & */ + {0x0c, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, /* ' */ + {0x10, 0x60, 0xc0, 0xc0, 0xc0, 0x60, 0x10, 0x00}, /* ( */ + {0x10, 0x0c, 0x06, 0x06, 0x06, 0x0c, 0x10, 0x00}, /* ) */ + {0x00, 0x54, 0x38, 0xfe, 0x38, 0x54, 0x00, 0x00}, /* * */ + {0x00, 0x18, 0x18, 0x7e, 0x18, 0x18, 0x00, 0x00}, /* + */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x70}, /* , */ + {0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00}, /* - */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00}, /* . */ + {0x02, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0x00}, /* / */ + {0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00}, /* 0 */ + {0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x3c, 0x00}, /* 1 */ + {0x7c, 0xc6, 0x06, 0x0c, 0x30, 0x60, 0xfe, 0x00}, /* 2 */ + {0x7c, 0xc6, 0x06, 0x3c, 0x06, 0xc6, 0x7c, 0x00}, /* 3 */ + {0x0e, 0x1e, 0x36, 0x66, 0xfe, 0x06, 0x06, 0x00}, /* 4 */ + {0xfe, 0xc0, 0xc0, 0xfc, 0x06, 0x06, 0xfc, 0x00}, /* 5 */ + {0x7c, 0xc6, 0xc0, 0xfc, 0xc6, 0xc6, 0x7c, 0x00}, /* 6 */ + {0xfe, 0x06, 0x0c, 0x18, 0x30, 0x60, 0x60, 0x00}, /* 7 */ + {0x7c, 0xc6, 0xc6, 0x7c, 0xc6, 0xc6, 0x7c, 0x00}, /* 8 */ + {0x7c, 0xc6, 0xc6, 0x7e, 0x06, 0xc6, 0x7c, 0x00}, /* 9 */ + {0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00}, /* : */ + {0x00, 0x30, 0x00, 0x00, 0x00, 0x30, 0x20, 0x00}, /* }, */ + {0x00, 0x1c, 0x30, 0x60, 0x30, 0x1c, 0x00, 0x00}, /* < */ + {0x00, 0x00, 0x7e, 0x00, 0x7e, 0x00, 0x00, 0x00}, /* = */ + {0x00, 0x70, 0x18, 0x0c, 0x18, 0x70, 0x00, 0x00}, /* > */ + {0x7c, 0xc6, 0x0c, 0x18, 0x30, 0x00, 0x30, 0x00}, /* ? */ + {0x7c, 0x82, 0x9a, 0xaa, 0xaa, 0x9e, 0x7c, 0x00}, /* @ */ + {0x38, 0x6c, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0x00}, /* A */ + {0xfc, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xfc, 0x00}, /* B */ + {0x7c, 0xc6, 0xc6, 0xc0, 0xc0, 0xc6, 0x7c, 0x00}, /* C */ + {0xf8, 0xcc, 0xc6, 0xc6, 0xc6, 0xcc, 0xf8, 0x00}, /* D */ + {0xfe, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xfe, 0x00}, /* E */ + {0xfe, 0xc0, 0xc0, 0xfc, 0xc0, 0xc0, 0xc0, 0x00}, /* F */ + {0x7c, 0xc6, 0xc0, 0xce, 0xc6, 0xc6, 0x7e, 0x00}, /* G */ + {0xc6, 0xc6, 0xc6, 0xfe, 0xc6, 0xc6, 0xc6, 0x00}, /* H */ + {0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00}, /* I */ + {0x1e, 0x06, 0x06, 0x06, 0xc6, 0xc6, 0x7c, 0x00}, /* J */ + {0xc6, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0xc6, 0x00}, /* K */ + {0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfe, 0x00}, /* L */ + {0xc6, 0xee, 0xfe, 0xd6, 0xc6, 0xc6, 0xc6, 0x00}, /* M */ + {0xc6, 0xe6, 0xf6, 0xde, 0xce, 0xc6, 0xc6, 0x00}, /* N */ + {0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00}, /* O */ + {0xfc, 0xc6, 0xc6, 0xfc, 0xc0, 0xc0, 0xc0, 0x00}, /* P */ + {0x7c, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x06}, /* Q */ + {0xfc, 0xc6, 0xc6, 0xfc, 0xc6, 0xc6, 0xc6, 0x00}, /* R */ + {0x78, 0xcc, 0x60, 0x30, 0x18, 0xcc, 0x78, 0x00}, /* S */ + {0xfc, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00}, /* T */ + {0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x7c, 0x00}, /* U */ + {0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00}, /* V */ + {0xc6, 0xc6, 0xc6, 0xd6, 0xfe, 0xee, 0xc6, 0x00}, /* W */ + {0xc6, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0xc6, 0x00}, /* X */ + {0xc3, 0xc3, 0x66, 0x3c, 0x18, 0x18, 0x18, 0x00}, /* Y */ + {0xfe, 0x0c, 0x18, 0x30, 0x60, 0xc0, 0xfe, 0x00}, /* Z */ + {0x3c, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3c, 0x00}, /* [ */ + {0xc0, 0x60, 0x30, 0x18, 0x0c, 0x06, 0x03, 0x00}, /* \ */ + {0x3c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x3c, 0x00}, /* ] */ + {0x00, 0x38, 0x6c, 0xc6, 0x00, 0x00, 0x00, 0x00}, /* ^ */ + {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff}, /* _ */ + {0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, /* ` */ + {0x00, 0x00, 0x7c, 0x06, 0x7e, 0xc6, 0x7e, 0x00}, /* a */ + {0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xe6, 0xdc, 0x00}, /* b */ + {0x00, 0x00, 0x7c, 0xc6, 0xc0, 0xc0, 0x7e, 0x00}, /* c */ + {0x06, 0x06, 0x7e, 0xc6, 0xc6, 0xce, 0x76, 0x00}, /* d */ + {0x00, 0x00, 0x7c, 0xc6, 0xfe, 0xc0, 0x7e, 0x00}, /* e */ + {0x1e, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x30, 0x00}, /* f */ + {0x00, 0x00, 0x7e, 0xc6, 0xce, 0x76, 0x06, 0x7c}, /* g */ + {0xc0, 0xc0, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00}, /* */ + {0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x3c, 0x00}, /* i */ + {0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0xf0}, /* j */ + {0xc0, 0xc0, 0xcc, 0xd8, 0xf0, 0xd8, 0xcc, 0x00}, /* k */ + {0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3c, 0x00}, /* l */ + {0x00, 0x00, 0xcc, 0xfe, 0xd6, 0xc6, 0xc6, 0x00}, /* m */ + {0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xc6, 0xc6, 0x00}, /* n */ + {0x00, 0x00, 0x7c, 0xc6, 0xc6, 0xc6, 0x7c, 0x00}, /* o */ + {0x00, 0x00, 0xfc, 0xc6, 0xc6, 0xe6, 0xdc, 0xc0}, /* p */ + {0x00, 0x00, 0x7e, 0xc6, 0xc6, 0xce, 0x76, 0x06}, /* q */ + {0x00, 0x00, 0x6e, 0x70, 0x60, 0x60, 0x60, 0x00}, /* r */ + {0x00, 0x00, 0x7c, 0xc0, 0x7c, 0x06, 0xfc, 0x00}, /* s */ + {0x30, 0x30, 0x7c, 0x30, 0x30, 0x30, 0x1c, 0x00}, /* t */ + {0x00, 0x00, 0xc6, 0xc6, 0xc6, 0xc6, 0x7e, 0x00}, /* u */ + {0x00, 0x00, 0xc6, 0xc6, 0xc6, 0x6c, 0x38, 0x00}, /* v */ + {0x00, 0x00, 0xc6, 0xc6, 0xd6, 0xfe, 0x6c, 0x00}, /* w */ + {0x00, 0x00, 0xc6, 0x6c, 0x38, 0x6c, 0xc6, 0x00}, /* x */ + {0x00, 0x00, 0xc6, 0xc6, 0xce, 0x76, 0x06, 0x7c}, /* y */ + {0x00, 0x00, 0xfc, 0x18, 0x30, 0x60, 0xfc, 0x00}, /* z */ + {0x0e, 0x18, 0x18, 0x70, 0x18, 0x18, 0x0e, 0x00}, /* { */ + {0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, /* | */ + {0xe0, 0x30, 0x30, 0x1c, 0x30, 0x30, 0xe0, 0x00}, /* } */ + {0x00, 0x00, 0x70, 0x9a, 0x0e, 0x00, 0x00, 0x00}, /* ~ */ + {0x00, 0x00, 0x18, 0x3c, 0x66, 0xff, 0x00, 0x00} /* Ascii 127 */ +}; +/*@=charint@*/ + +void +GifDrawText8x8(SavedImage *Image, + const int x, const int y, + const char *legend, + const int color) +{ + int i, j; + int base; + const char *cp; + + for (i = 0; i < GIF_FONT_HEIGHT; i++) { + base = Image->ImageDesc.Width * (y + i) + x; + + for (cp = legend; *cp; cp++) + for (j = 0; j < GIF_FONT_WIDTH; j++) { + if (GifAsciiTable8x8[(short)(*cp)][i] & (1 << (GIF_FONT_WIDTH - j))) + Image->RasterBits[base] = color; + base++; + } + } +} + +void +GifDrawBox(SavedImage *Image, + const int x, const int y, + const int w, const int d, + const int color) +{ + int j, base = Image->ImageDesc.Width * y + x; + + for (j = 0; j < w; j++) + Image->RasterBits[base + j] = + Image->RasterBits[base + (d * Image->ImageDesc.Width) + j] = color; + + for (j = 0; j < d; j++) + Image->RasterBits[base + j * Image->ImageDesc.Width] = + Image->RasterBits[base + j * Image->ImageDesc.Width + w] = color; +} + +void +GifDrawRectangle(SavedImage *Image, + const int x, const int y, + const int w, const int d, + const int color) +{ + unsigned char *bp = Image->RasterBits + Image->ImageDesc.Width * y + x; + int i; + + for (i = 0; i < d; i++) + memset(bp + (i * Image->ImageDesc.Width), color, (size_t)w); +} + +void +GifDrawBoxedText8x8(SavedImage *Image, + const int x, const int y, + const char *legend, + const int border, + const int bg, const int fg) +{ + int i, j = 0, LineCount = 0, TextWidth = 0; + const char *cp; + + /* compute size of text to box */ + for (cp = legend; *cp; cp++) + if (*cp == '\r') { + if (j > TextWidth) + TextWidth = j; + j = 0; + LineCount++; + } else if (*cp != '\t') + ++j; + LineCount++; /* count last line */ + if (j > TextWidth) /* last line might be longer than any previous */ + TextWidth = j; + + /* fill the box */ + GifDrawRectangle(Image, x + 1, y + 1, + border + TextWidth * GIF_FONT_WIDTH + border - 1, + border + LineCount * GIF_FONT_HEIGHT + border - 1, bg); + + /* draw the text */ + i = 0; + cp = strtok((char *)legend, "\r\n"); + do { + int leadspace = 0; + + if (cp[0] == '\t') + leadspace = (TextWidth - strlen(++cp)) / 2; + + GifDrawText8x8(Image, x + border + (leadspace * GIF_FONT_WIDTH), + y + border + (GIF_FONT_HEIGHT * i++), cp, fg); + cp = strtok((char *)NULL, "\r\n"); + } while (cp); + + /* outline the box */ + GifDrawBox(Image, x, y, border + TextWidth * GIF_FONT_WIDTH + border, + border + LineCount * GIF_FONT_HEIGHT + border, fg); +} + +/* end */ diff --git a/src/lib/gif/gif_hash.c b/src/lib/gif/gif_hash.c new file mode 100644 index 00000000..61a4d139 --- /dev/null +++ b/src/lib/gif/gif_hash.c @@ -0,0 +1,132 @@ +/***************************************************************************** + +gif_hash.c -- module to support the following operations: + +1. InitHashTable - initialize hash table. +2. ClearHashTable - clear the hash table to an empty state. +2. InsertHashTable - insert one item into data structure. +3. ExistsHashTable - test if item exists in data structure. + +This module is used to hash the GIF codes during encoding. + +*****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "gif_lib.h" +#include "gif_hash.h" +#include "gif_lib_private.h" + +/* #define DEBUG_HIT_RATE Debug number of misses per hash Insert/Exists. */ + +#ifdef DEBUG_HIT_RATE +static long NumberOfTests = 0, + NumberOfMisses = 0; +#endif /* DEBUG_HIT_RATE */ + +static int KeyItem(uint32_t Item); + +/****************************************************************************** + Initialize HashTable - allocate the memory needed and clear it. * +******************************************************************************/ +GifHashTableType *_InitHashTable(void) +{ + GifHashTableType *HashTable; + + if ((HashTable = (GifHashTableType *) malloc(sizeof(GifHashTableType))) + == NULL) + return NULL; + + _ClearHashTable(HashTable); + + return HashTable; +} + +/****************************************************************************** + Routine to clear the HashTable to an empty state. * + This part is a little machine depended. Use the commented part otherwise. * +******************************************************************************/ +void _ClearHashTable(GifHashTableType *HashTable) +{ + memset(HashTable -> HTable, 0xFF, HT_SIZE * sizeof(uint32_t)); +} + +/****************************************************************************** + Routine to insert a new Item into the HashTable. The data is assumed to be * + new one. * +******************************************************************************/ +void _InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code) +{ + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable -> HTable; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while (HT_GET_KEY(HTable[HKey]) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + HKey = (HKey + 1) & HT_KEY_MASK; + } + HTable[HKey] = HT_PUT_KEY(Key) | HT_PUT_CODE(Code); +} + +/****************************************************************************** + Routine to test if given Key exists in HashTable and if so returns its code * + Returns the Code if key was found, -1 if not. * +******************************************************************************/ +int _ExistsHashTable(GifHashTableType *HashTable, uint32_t Key) +{ + int HKey = KeyItem(Key); + uint32_t *HTable = HashTable -> HTable, HTKey; + +#ifdef DEBUG_HIT_RATE + NumberOfTests++; + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + + while ((HTKey = HT_GET_KEY(HTable[HKey])) != 0xFFFFFL) { +#ifdef DEBUG_HIT_RATE + NumberOfMisses++; +#endif /* DEBUG_HIT_RATE */ + if (Key == HTKey) return HT_GET_CODE(HTable[HKey]); + HKey = (HKey + 1) & HT_KEY_MASK; + } + + return -1; +} + +/****************************************************************************** + Routine to generate an HKey for the hashtable out of the given unique key. * + The given Key is assumed to be 20 bits as follows: lower 8 bits are the * + new postfix character, while the upper 12 bits are the prefix code. * + Because the average hit ratio is only 2 (2 hash references per entry), * + evaluating more complex keys (such as twin prime keys) does not worth it! * +******************************************************************************/ +static int KeyItem(uint32_t Item) +{ + return ((Item >> 12) ^ Item) & HT_KEY_MASK; +} + +#ifdef DEBUG_HIT_RATE +/****************************************************************************** + Debugging routine to print the hit ratio - number of times the hash table * + was tested per operation. This routine was used to test the KeyItem routine * +******************************************************************************/ +void HashTablePrintHitRatio(void) +{ + printf("Hash Table Hit Ratio is %ld/%ld = %ld%%.\n", + NumberOfMisses, NumberOfTests, + NumberOfMisses * 100 / NumberOfTests); +} +#endif /* DEBUG_HIT_RATE */ + +/* end */ diff --git a/src/lib/gif/gif_hash.h b/src/lib/gif/gif_hash.h new file mode 100644 index 00000000..ac20a43c --- /dev/null +++ b/src/lib/gif/gif_hash.h @@ -0,0 +1,39 @@ +/****************************************************************************** + +gif_hash.h - magfic constants and declarations for GIF LZW + +******************************************************************************/ + +#ifndef _GIF_HASH_H_ +#define _GIF_HASH_H_ + +#include +#include + +#define HT_SIZE 8192 /* 12bits = 4096 or twice as big! */ +#define HT_KEY_MASK 0x1FFF /* 13bits keys */ +#define HT_KEY_NUM_BITS 13 /* 13bits keys */ +#define HT_MAX_KEY 8191 /* 13bits - 1, maximal code possible */ +#define HT_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ + +/* The 32 bits of the long are divided into two parts for the key & code: */ +/* 1. The code is 12 bits as our compression algorithm is limited to 12bits */ +/* 2. The key is 12 bits Prefix code + 8 bit new char or 20 bits. */ +/* The key is the upper 20 bits. The code is the lower 12. */ +#define HT_GET_KEY(l) (l >> 12) +#define HT_GET_CODE(l) (l & 0x0FFF) +#define HT_PUT_KEY(l) (l << 12) +#define HT_PUT_CODE(l) (l & 0x0FFF) + +typedef struct GifHashTableType { + uint32_t HTable[HT_SIZE]; +} GifHashTableType; + +GifHashTableType *_InitHashTable(void); +void _ClearHashTable(GifHashTableType *HashTable); +void _InsertHashTable(GifHashTableType *HashTable, uint32_t Key, int Code); +int _ExistsHashTable(GifHashTableType *HashTable, uint32_t Key); + +#endif /* _GIF_HASH_H_ */ + +/* end */ diff --git a/src/lib/gif/gif_lib.h b/src/lib/gif/gif_lib.h new file mode 100644 index 00000000..b556bc69 --- /dev/null +++ b/src/lib/gif/gif_lib.h @@ -0,0 +1,309 @@ +/****************************************************************************** + +gif_lib.h - service library for decoding and encoding GIF images + +*****************************************************************************/ + +#ifndef _GIF_LIB_H_ +#define _GIF_LIB_H_ 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define GIFLIB_MAJOR 5 +#define GIFLIB_MINOR 1 +#define GIFLIB_RELEASE 0 + +#define GIF_ERROR 0 +#define GIF_OK 1 + +#include +#include + +#define GIF_STAMP "GIFVER" /* First chars in file - GIF stamp. */ +#define GIF_STAMP_LEN sizeof(GIF_STAMP) - 1 +#define GIF_VERSION_POS 3 /* Version first character in stamp. */ +#define GIF87_STAMP "GIF87a" /* First chars in file - GIF stamp. */ +#define GIF89_STAMP "GIF89a" /* First chars in file - GIF stamp. */ + +typedef unsigned char GifPixelType; +typedef unsigned char *GifRowType; +typedef unsigned char GifByteType; +typedef unsigned int GifPrefixType; +typedef int GifWord; + +typedef struct GifColorType { + GifByteType Red, Green, Blue; +} GifColorType; + +typedef struct ColorMapObject { + int ColorCount; + int BitsPerPixel; + bool SortFlag; + GifColorType *Colors; /* on malloc(3) heap */ +} ColorMapObject; + +typedef struct GifImageDesc { + GifWord Left, Top, Width, Height; /* Current image dimensions. */ + bool Interlace; /* Sequential/Interlaced lines. */ + ColorMapObject *ColorMap; /* The local color map */ +} GifImageDesc; + +typedef struct ExtensionBlock { + int ByteCount; + GifByteType *Bytes; /* on malloc(3) heap */ + int Function; /* The block function code */ +#define CONTINUE_EXT_FUNC_CODE 0x00 /* continuation subblock */ +#define COMMENT_EXT_FUNC_CODE 0xfe /* comment */ +#define GRAPHICS_EXT_FUNC_CODE 0xf9 /* graphics control (GIF89) */ +#define PLAINTEXT_EXT_FUNC_CODE 0x01 /* plaintext */ +#define APPLICATION_EXT_FUNC_CODE 0xff /* application block */ +} ExtensionBlock; + +typedef struct SavedImage { + GifImageDesc ImageDesc; + GifByteType *RasterBits; /* on malloc(3) heap */ + int ExtensionBlockCount; /* Count of extensions before image */ + ExtensionBlock *ExtensionBlocks; /* Extensions before image */ +} SavedImage; + +typedef struct GifFileType { + GifWord SWidth, SHeight; /* Size of virtual canvas */ + GifWord SColorResolution; /* How many colors can we generate? */ + GifWord SBackGroundColor; /* Background color for virtual canvas */ + GifByteType AspectByte; /* Used to compute pixel aspect ratio */ + ColorMapObject *SColorMap; /* Global colormap, NULL if nonexistent. */ + int ImageCount; /* Number of current image (both APIs) */ + GifImageDesc Image; /* Current image (low-level API) */ + SavedImage *SavedImages; /* Image sequence (high-level API) */ + int ExtensionBlockCount; /* Count extensions past last image */ + ExtensionBlock *ExtensionBlocks; /* Extensions past last image */ + int Error; /* Last error condition reported */ + void *UserData; /* hook to attach user data (TVT) */ + void *Private; /* Don't mess with this! */ +} GifFileType; + +#define GIF_ASPECT_RATIO(n) ((n)+15.0/64.0) + +typedef enum { + UNDEFINED_RECORD_TYPE, + SCREEN_DESC_RECORD_TYPE, + IMAGE_DESC_RECORD_TYPE, /* Begin with ',' */ + EXTENSION_RECORD_TYPE, /* Begin with '!' */ + TERMINATE_RECORD_TYPE /* Begin with ';' */ +} GifRecordType; + +/* func type to read gif data from arbitrary sources (TVT) */ +typedef int (*InputFunc) (GifFileType *, GifByteType *, int); + +/* func type to write gif data to arbitrary targets. + * Returns count of bytes written. (MRB) + */ +typedef int (*OutputFunc) (GifFileType *, const GifByteType *, int); + +/****************************************************************************** + GIF89 structures +******************************************************************************/ + +typedef struct GraphicsControlBlock { + int DisposalMode; +#define DISPOSAL_UNSPECIFIED 0 /* No disposal specified. */ +#define DISPOSE_DO_NOT 1 /* Leave image in place */ +#define DISPOSE_BACKGROUND 2 /* Set area too background color */ +#define DISPOSE_PREVIOUS 3 /* Restore to previous content */ + bool UserInputFlag; /* User confirmation required before disposal */ + int DelayTime; /* pre-display delay in 0.01sec units */ + int TransparentColor; /* Palette index for transparency, -1 if none */ +#define NO_TRANSPARENT_COLOR -1 +} GraphicsControlBlock; + +/****************************************************************************** + GIF encoding routines +******************************************************************************/ + +/* Main entry points */ +GifFileType *EGifOpenFileName(const char *GifFileName, + const bool GifTestExistence, int *Error); +GifFileType *EGifOpenFileHandle(const int GifFileHandle, int *Error); +GifFileType *EGifOpen(void *userPtr, OutputFunc writeFunc, int *Error); +int EGifSpew(GifFileType * GifFile); +const char *EGifGetGifVersion(GifFileType *GifFile); /* new in 5.x */ +int EGifCloseFile(GifFileType *GifFile, int *ErrorCode); + +#define E_GIF_SUCCEEDED 0 +#define E_GIF_ERR_OPEN_FAILED 1 /* And EGif possible errors. */ +#define E_GIF_ERR_WRITE_FAILED 2 +#define E_GIF_ERR_HAS_SCRN_DSCR 3 +#define E_GIF_ERR_HAS_IMAG_DSCR 4 +#define E_GIF_ERR_NO_COLOR_MAP 5 +#define E_GIF_ERR_DATA_TOO_BIG 6 +#define E_GIF_ERR_NOT_ENOUGH_MEM 7 +#define E_GIF_ERR_DISK_IS_FULL 8 +#define E_GIF_ERR_CLOSE_FAILED 9 +#define E_GIF_ERR_NOT_WRITEABLE 10 + +/* These are legacy. You probably do not want to call them directly */ +int EGifPutScreenDesc(GifFileType *GifFile, + const int GifWidth, const int GifHeight, + const int GifColorRes, + const int GifBackGround, + const ColorMapObject *GifColorMap); +int EGifPutImageDesc(GifFileType *GifFile, + const int GifLeft, const int GifTop, + const int GifWidth, const int GifHeight, + const bool GifInterlace, + const ColorMapObject *GifColorMap); +void EGifSetGifVersion(GifFileType *GifFile, const bool gif89); +int EGifPutLine(GifFileType *GifFile, GifPixelType *GifLine, + int GifLineLen); +int EGifPutPixel(GifFileType *GifFile, const GifPixelType GifPixel); +int EGifPutComment(GifFileType *GifFile, const char *GifComment); +int EGifPutExtensionLeader(GifFileType *GifFile, const int GifExtCode); +int EGifPutExtensionBlock(GifFileType *GifFile, + const int GifExtLen, const void *GifExtension); +int EGifPutExtensionTrailer(GifFileType *GifFile); +int EGifPutExtension(GifFileType *GifFile, const int GifExtCode, + const int GifExtLen, + const void *GifExtension); +int EGifPutCode(GifFileType *GifFile, int GifCodeSize, + const GifByteType *GifCodeBlock); +int EGifPutCodeNext(GifFileType *GifFile, + const GifByteType *GifCodeBlock); + +/****************************************************************************** + GIF decoding routines +******************************************************************************/ + +/* Main entry points */ +GifFileType *DGifOpenFileName(const char *GifFileName, int *Error); +GifFileType *DGifOpenFileHandle(int GifFileHandle, int *Error); +int DGifSlurp(GifFileType * GifFile); +GifFileType *DGifOpen(void *userPtr, InputFunc readFunc, int *Error); /* new one (TVT) */ + int DGifCloseFile(GifFileType * GifFile, int *ErrorCode); + +#define D_GIF_SUCCEEDED 0 +#define D_GIF_ERR_OPEN_FAILED 101 /* And DGif possible errors. */ +#define D_GIF_ERR_READ_FAILED 102 +#define D_GIF_ERR_NOT_GIF_FILE 103 +#define D_GIF_ERR_NO_SCRN_DSCR 104 +#define D_GIF_ERR_NO_IMAG_DSCR 105 +#define D_GIF_ERR_NO_COLOR_MAP 106 +#define D_GIF_ERR_WRONG_RECORD 107 +#define D_GIF_ERR_DATA_TOO_BIG 108 +#define D_GIF_ERR_NOT_ENOUGH_MEM 109 +#define D_GIF_ERR_CLOSE_FAILED 110 +#define D_GIF_ERR_NOT_READABLE 111 +#define D_GIF_ERR_IMAGE_DEFECT 112 +#define D_GIF_ERR_EOF_TOO_SOON 113 + +/* These are legacy. You probably do not want to call them directly */ +int DGifGetScreenDesc(GifFileType *GifFile); +int DGifGetRecordType(GifFileType *GifFile, GifRecordType *GifType); +int DGifGetImageDesc(GifFileType *GifFile); +int DGifGetLine(GifFileType *GifFile, GifPixelType *GifLine, int GifLineLen); +int DGifGetPixel(GifFileType *GifFile, GifPixelType GifPixel); +int DGifGetComment(GifFileType *GifFile, char *GifComment); +int DGifGetExtension(GifFileType *GifFile, int *GifExtCode, + GifByteType **GifExtension); +int DGifGetExtensionNext(GifFileType *GifFile, GifByteType **GifExtension); +int DGifGetCode(GifFileType *GifFile, int *GifCodeSize, + GifByteType **GifCodeBlock); +int DGifGetCodeNext(GifFileType *GifFile, GifByteType **GifCodeBlock); +int DGifGetLZCodes(GifFileType *GifFile, int *GifCode); + + +/****************************************************************************** + Color table quantization (deprecated) +******************************************************************************/ +int GifQuantizeBuffer(unsigned int Width, unsigned int Height, + int *ColorMapSize, GifByteType * RedInput, + GifByteType * GreenInput, GifByteType * BlueInput, + GifByteType * OutputBuffer, + GifColorType * OutputColorMap); + +/****************************************************************************** + Error handling and reporting. +******************************************************************************/ +extern const char *GifErrorString(int ErrorCode); /* new in 2012 - ESR */ + +/***************************************************************************** + Everything below this point is new after version 1.2, supporting `slurp + mode' for doing I/O in two big belts with all the image-bashing in core. +******************************************************************************/ + +/****************************************************************************** + Color map handling from gif_alloc.c +******************************************************************************/ + +extern ColorMapObject *GifMakeMapObject(int ColorCount, + const GifColorType *ColorMap); +extern void GifFreeMapObject(ColorMapObject *Object); +extern ColorMapObject *GifUnionColorMap(const ColorMapObject *ColorIn1, + const ColorMapObject *ColorIn2, + GifPixelType ColorTransIn2[]); +extern int GifBitSize(int n); + +/****************************************************************************** + Support for the in-core structures allocation (slurp mode). +******************************************************************************/ + +extern void GifApplyTranslation(SavedImage *Image, GifPixelType Translation[]); +extern int GifAddExtensionBlock(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks, + int Function, + unsigned int Len, unsigned char ExtData[]); +extern void GifFreeExtensions(int *ExtensionBlock_Count, + ExtensionBlock **ExtensionBlocks); +extern SavedImage *GifMakeSavedImage(GifFileType *GifFile, + const SavedImage *CopyFrom); +extern void GifFreeSavedImages(GifFileType *GifFile); + +/****************************************************************************** + 5.x functions for GIF89 graphics control blocks +******************************************************************************/ + +int DGifExtensionToGCB(const size_t GifExtensionLength, + const GifByteType *GifExtension, + GraphicsControlBlock *GCB); +size_t EGifGCBToExtension(const GraphicsControlBlock *GCB, + GifByteType *GifExtension); + +int DGifSavedExtensionToGCB(GifFileType *GifFile, + int ImageIndex, + GraphicsControlBlock *GCB); +int EGifGCBToSavedExtension(const GraphicsControlBlock *GCB, + GifFileType *GifFile, + int ImageIndex); + +/****************************************************************************** + The library's internal utility font +******************************************************************************/ + +#define GIF_FONT_WIDTH 8 +#define GIF_FONT_HEIGHT 8 +extern const unsigned char GifAsciiTable8x8[][GIF_FONT_WIDTH]; + +extern void GifDrawText8x8(SavedImage *Image, + const int x, const int y, + const char *legend, const int color); + +extern void GifDrawBox(SavedImage *Image, + const int x, const int y, + const int w, const int d, const int color); + +extern void GifDrawRectangle(SavedImage *Image, + const int x, const int y, + const int w, const int d, const int color); + +extern void GifDrawBoxedText8x8(SavedImage *Image, + const int x, const int y, + const char *legend, + const int border, const int bg, const int fg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* _GIF_LIB_H */ + +/* end */ diff --git a/src/lib/gif/gif_lib_private.h b/src/lib/gif/gif_lib_private.h new file mode 100644 index 00000000..adaf5571 --- /dev/null +++ b/src/lib/gif/gif_lib_private.h @@ -0,0 +1,59 @@ +/**************************************************************************** + +gif_lib_private.h - internal giflib routines and structures + +****************************************************************************/ + +#ifndef _GIF_LIB_PRIVATE_H +#define _GIF_LIB_PRIVATE_H + +#include "gif_lib.h" +#include "gif_hash.h" + +#define EXTENSION_INTRODUCER 0x21 +#define DESCRIPTOR_INTRODUCER 0x2c +#define TERMINATOR_INTRODUCER 0x3b + +#define LZ_MAX_CODE 4095 /* Biggest code possible in 12 bits. */ +#define LZ_BITS 12 + +#define FLUSH_OUTPUT 4096 /* Impossible code, to signal flush. */ +#define FIRST_CODE 4097 /* Impossible code, to signal first. */ +#define NO_SUCH_CODE 4098 /* Impossible code, to signal empty. */ + +#define FILE_STATE_WRITE 0x01 +#define FILE_STATE_SCREEN 0x02 +#define FILE_STATE_IMAGE 0x04 +#define FILE_STATE_READ 0x08 + +#define IS_READABLE(Private) (Private->FileState & FILE_STATE_READ) +#define IS_WRITEABLE(Private) (Private->FileState & FILE_STATE_WRITE) + +typedef struct GifFilePrivateType { + GifWord FileState, FileHandle, /* Where all this data goes to! */ + BitsPerPixel, /* Bits per pixel (Codes uses at least this + 1). */ + ClearCode, /* The CLEAR LZ code. */ + EOFCode, /* The EOF LZ code. */ + RunningCode, /* The next code algorithm can generate. */ + RunningBits, /* The number of bits required to represent RunningCode. */ + MaxCode1, /* 1 bigger than max. possible code, in RunningBits bits. */ + LastCode, /* The code before the current code. */ + CrntCode, /* Current algorithm code. */ + StackPtr, /* For character stack (see below). */ + CrntShiftState; /* Number of bits in CrntShiftDWord. */ + unsigned long CrntShiftDWord; /* For bytes decomposition into codes. */ + unsigned long PixelCount; /* Number of pixels in image. */ + FILE *File; /* File as stream. */ + InputFunc Read; /* function to read gif input (TVT) */ + OutputFunc Write; /* function to write gif output (MRB) */ + GifByteType Buf[256]; /* Compressed input is buffered here. */ + GifByteType Stack[LZ_MAX_CODE]; /* Decoded pixels are stacked here. */ + GifByteType Suffix[LZ_MAX_CODE + 1]; /* So we can trace the codes. */ + GifPrefixType Prefix[LZ_MAX_CODE + 1]; + GifHashTableType *HashTable; + bool gif89; +} GifFilePrivateType; + +#endif /* _GIF_LIB_PRIVATE_H */ + +/* end */ diff --git a/src/lib/gif/gifalloc.c b/src/lib/gif/gifalloc.c new file mode 100644 index 00000000..9fe3edf8 --- /dev/null +++ b/src/lib/gif/gifalloc.c @@ -0,0 +1,401 @@ +/***************************************************************************** + + GIF construction tools + +****************************************************************************/ + +#include +#include +#include + +#include "gif_lib.h" + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +/****************************************************************************** + Miscellaneous utility functions +******************************************************************************/ + +/* return smallest bitfield size n will fit in */ +int +GifBitSize(int n) +{ + register int i; + + for (i = 1; i <= 8; i++) + if ((1 << i) >= n) + break; + return (i); +} + +/****************************************************************************** + Color map object functions +******************************************************************************/ + +/* + * Allocate a color map of given size; initialize with contents of + * ColorMap if that pointer is non-NULL. + */ +ColorMapObject * +GifMakeMapObject(int ColorCount, const GifColorType *ColorMap) +{ + ColorMapObject *Object; + + /*** FIXME: Our ColorCount has to be a power of two. Is it necessary to + * make the user know that or should we automatically round up instead? */ + if (ColorCount != (1 << GifBitSize(ColorCount))) { + return ((ColorMapObject *) NULL); + } + + Object = (ColorMapObject *)malloc(sizeof(ColorMapObject)); + if (Object == (ColorMapObject *) NULL) { + return ((ColorMapObject *) NULL); + } + + Object->Colors = (GifColorType *)calloc(ColorCount, sizeof(GifColorType)); + if (Object->Colors == (GifColorType *) NULL) { + free(Object); + return ((ColorMapObject *) NULL); + } + + Object->ColorCount = ColorCount; + Object->BitsPerPixel = GifBitSize(ColorCount); + Object->SortFlag = false; + + if (ColorMap != NULL) { + memcpy((char *)Object->Colors, + (char *)ColorMap, ColorCount * sizeof(GifColorType)); + } + + return (Object); +} + +/******************************************************************************* +Free a color map object +*******************************************************************************/ +void +GifFreeMapObject(ColorMapObject *Object) +{ + if (Object != NULL) { + (void)free(Object->Colors); + (void)free(Object); + } +} + +#ifdef DEBUG +void +DumpColorMap(ColorMapObject *Object, + FILE * fp) +{ + if (Object != NULL) { + int i, j, Len = Object->ColorCount; + + for (i = 0; i < Len; i += 4) { + for (j = 0; j < 4 && j < Len; j++) { + (void)fprintf(fp, "%3d: %02x %02x %02x ", i + j, + Object->Colors[i + j].Red, + Object->Colors[i + j].Green, + Object->Colors[i + j].Blue); + } + (void)fprintf(fp, "\n"); + } + } +} +#endif /* DEBUG */ + +/******************************************************************************* + Compute the union of two given color maps and return it. If result can't + fit into 256 colors, NULL is returned, the allocated union otherwise. + ColorIn1 is copied as is to ColorUnion, while colors from ColorIn2 are + copied iff they didn't exist before. ColorTransIn2 maps the old + ColorIn2 into the ColorUnion color map table./ +*******************************************************************************/ +ColorMapObject * +GifUnionColorMap(const ColorMapObject *ColorIn1, + const ColorMapObject *ColorIn2, + GifPixelType ColorTransIn2[]) +{ + int i, j, CrntSlot, RoundUpTo, NewGifBitSize; + ColorMapObject *ColorUnion; + + /* + * We don't worry about duplicates within either color map; if + * the caller wants to resolve those, he can perform unions + * with an empty color map. + */ + + /* Allocate table which will hold the result for sure. */ + ColorUnion = GifMakeMapObject(MAX(ColorIn1->ColorCount, + ColorIn2->ColorCount) * 2, NULL); + + if (ColorUnion == NULL) + return (NULL); + + /* + * Copy ColorIn1 to ColorUnion. + */ + for (i = 0; i < ColorIn1->ColorCount; i++) + ColorUnion->Colors[i] = ColorIn1->Colors[i]; + CrntSlot = ColorIn1->ColorCount; + + /* + * Potentially obnoxious hack: + * + * Back CrntSlot down past all contiguous {0, 0, 0} slots at the end + * of table 1. This is very useful if your display is limited to + * 16 colors. + */ + while (ColorIn1->Colors[CrntSlot - 1].Red == 0 + && ColorIn1->Colors[CrntSlot - 1].Green == 0 + && ColorIn1->Colors[CrntSlot - 1].Blue == 0) + CrntSlot--; + + /* Copy ColorIn2 to ColorUnion (use old colors if they exist): */ + for (i = 0; i < ColorIn2->ColorCount && CrntSlot <= 256; i++) { + /* Let's see if this color already exists: */ + for (j = 0; j < ColorIn1->ColorCount; j++) + if (memcmp (&ColorIn1->Colors[j], &ColorIn2->Colors[i], + sizeof(GifColorType)) == 0) + break; + + if (j < ColorIn1->ColorCount) + ColorTransIn2[i] = j; /* color exists in Color1 */ + else { + /* Color is new - copy it to a new slot: */ + ColorUnion->Colors[CrntSlot] = ColorIn2->Colors[i]; + ColorTransIn2[i] = CrntSlot++; + } + } + + if (CrntSlot > 256) { + GifFreeMapObject(ColorUnion); + return ((ColorMapObject *) NULL); + } + + NewGifBitSize = GifBitSize(CrntSlot); + RoundUpTo = (1 << NewGifBitSize); + + if (RoundUpTo != ColorUnion->ColorCount) { + register GifColorType *Map = ColorUnion->Colors; + + /* + * Zero out slots up to next power of 2. + * We know these slots exist because of the way ColorUnion's + * start dimension was computed. + */ + for (j = CrntSlot; j < RoundUpTo; j++) + Map[j].Red = Map[j].Green = Map[j].Blue = 0; + + /* perhaps we can shrink the map? */ + if (RoundUpTo < ColorUnion->ColorCount) + ColorUnion->Colors = (GifColorType *)realloc(Map, + sizeof(GifColorType) * RoundUpTo); + } + + ColorUnion->ColorCount = RoundUpTo; + ColorUnion->BitsPerPixel = NewGifBitSize; + + return (ColorUnion); +} + +/******************************************************************************* + Apply a given color translation to the raster bits of an image +*******************************************************************************/ +void +GifApplyTranslation(SavedImage *Image, GifPixelType Translation[]) +{ + register int i; + register int RasterSize = Image->ImageDesc.Height * Image->ImageDesc.Width; + + for (i = 0; i < RasterSize; i++) + Image->RasterBits[i] = Translation[Image->RasterBits[i]]; +} + +/****************************************************************************** + Extension record functions +******************************************************************************/ +int +GifAddExtensionBlock(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks, + int Function, + unsigned int Len, + unsigned char ExtData[]) +{ + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) + *ExtensionBlocks=(ExtensionBlock *)malloc(sizeof(ExtensionBlock)); + else + *ExtensionBlocks = (ExtensionBlock *)realloc(*ExtensionBlocks, + sizeof(ExtensionBlock) * + (*ExtensionBlockCount + 1)); + + if (*ExtensionBlocks == NULL) + return (GIF_ERROR); + + ep = &(*ExtensionBlocks)[(*ExtensionBlockCount)++]; + + ep->Function = Function; + ep->ByteCount=Len; + ep->Bytes = (GifByteType *)malloc(ep->ByteCount); + if (ep->Bytes == NULL) + return (GIF_ERROR); + + if (ExtData != NULL) { + memcpy(ep->Bytes, ExtData, Len); + } + + return (GIF_OK); +} + +void +GifFreeExtensions(int *ExtensionBlockCount, + ExtensionBlock **ExtensionBlocks) +{ + ExtensionBlock *ep; + + if (*ExtensionBlocks == NULL) + return; + + for (ep = *ExtensionBlocks; + ep < (*ExtensionBlocks + *ExtensionBlockCount); + ep++) + (void)free((char *)ep->Bytes); + (void)free((char *)*ExtensionBlocks); + *ExtensionBlocks = NULL; + *ExtensionBlockCount = 0; +} + +/****************************************************************************** + Image block allocation functions +******************************************************************************/ + +/* Private Function: + * Frees the last image in the GifFile->SavedImages array + */ +void +FreeLastSavedImage(GifFileType *GifFile) +{ + SavedImage *sp; + + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) + return; + + /* Remove one SavedImage from the GifFile */ + GifFile->ImageCount--; + sp = &GifFile->SavedImages[GifFile->ImageCount]; + + /* Deallocate its Colormap */ + if (sp->ImageDesc.ColorMap != NULL) { + GifFreeMapObject(sp->ImageDesc.ColorMap); + sp->ImageDesc.ColorMap = NULL; + } + + /* Deallocate the image data */ + if (sp->RasterBits != NULL) + free((char *)sp->RasterBits); + + /* Deallocate any extensions */ + GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks); + + /*** FIXME: We could realloc the GifFile->SavedImages structure but is + * there a point to it? Saves some memory but we'd have to do it every + * time. If this is used in GifFreeSavedImages then it would be inefficient + * (The whole array is going to be deallocated.) If we just use it when + * we want to free the last Image it's convenient to do it here. + */ +} + +/* + * Append an image block to the SavedImages array + */ +SavedImage * +GifMakeSavedImage(GifFileType *GifFile, const SavedImage *CopyFrom) +{ + if (GifFile->SavedImages == NULL) + GifFile->SavedImages = (SavedImage *)malloc(sizeof(SavedImage)); + else + GifFile->SavedImages = (SavedImage *)realloc(GifFile->SavedImages, + sizeof(SavedImage) * (GifFile->ImageCount + 1)); + + if (GifFile->SavedImages == NULL) + return ((SavedImage *)NULL); + else { + SavedImage *sp = &GifFile->SavedImages[GifFile->ImageCount++]; + memset((char *)sp, '\0', sizeof(SavedImage)); + + if (CopyFrom != NULL) { + memcpy((char *)sp, CopyFrom, sizeof(SavedImage)); + + /* + * Make our own allocated copies of the heap fields in the + * copied record. This guards against potential aliasing + * problems. + */ + + /* first, the local color map */ + if (sp->ImageDesc.ColorMap != NULL) { + sp->ImageDesc.ColorMap = GifMakeMapObject( + CopyFrom->ImageDesc.ColorMap->ColorCount, + CopyFrom->ImageDesc.ColorMap->Colors); + if (sp->ImageDesc.ColorMap == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + } + + /* next, the raster */ + sp->RasterBits = (unsigned char *)malloc(sizeof(GifPixelType) * + CopyFrom->ImageDesc.Height * + CopyFrom->ImageDesc.Width); + if (sp->RasterBits == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + memcpy(sp->RasterBits, CopyFrom->RasterBits, + sizeof(GifPixelType) * CopyFrom->ImageDesc.Height * + CopyFrom->ImageDesc.Width); + + /* finally, the extension blocks */ + if (sp->ExtensionBlocks != NULL) { + sp->ExtensionBlocks = (ExtensionBlock *)malloc( + sizeof(ExtensionBlock) * + CopyFrom->ExtensionBlockCount); + if (sp->ExtensionBlocks == NULL) { + FreeLastSavedImage(GifFile); + return (SavedImage *)(NULL); + } + memcpy(sp->ExtensionBlocks, CopyFrom->ExtensionBlocks, + sizeof(ExtensionBlock) * CopyFrom->ExtensionBlockCount); + } + } + + return (sp); + } +} + +void +GifFreeSavedImages(GifFileType *GifFile) +{ + SavedImage *sp; + + if ((GifFile == NULL) || (GifFile->SavedImages == NULL)) { + return; + } + for (sp = GifFile->SavedImages; + sp < GifFile->SavedImages + GifFile->ImageCount; sp++) { + if (sp->ImageDesc.ColorMap != NULL) { + GifFreeMapObject(sp->ImageDesc.ColorMap); + sp->ImageDesc.ColorMap = NULL; + } + + if (sp->RasterBits != NULL) + free((char *)sp->RasterBits); + + GifFreeExtensions(&sp->ExtensionBlockCount, &sp->ExtensionBlocks); + } + free((char *)GifFile->SavedImages); + GifFile->SavedImages = NULL; +} + +/* end */ diff --git a/src/lib/gif/quantize.c b/src/lib/gif/quantize.c new file mode 100644 index 00000000..e5d4a590 --- /dev/null +++ b/src/lib/gif/quantize.c @@ -0,0 +1,330 @@ +/***************************************************************************** + + quantize.c - quantize a high resolution image into lower one + + Based on: "Color Image Quantization for frame buffer Display", by + Paul Heckbert SIGGRAPH 1982 page 297-307. + + This doesn't really belong in the core library, was undocumented, + and was removed in 4.2. Then it turned out some client apps were + actually using it, so it was restored in 5.0. + +******************************************************************************/ + +#include +#include +#include "gif_lib.h" +#include "gif_lib_private.h" + +#define ABS(x) ((x) > 0 ? (x) : (-(x))) + +#define COLOR_ARRAY_SIZE 32768 +#define BITS_PER_PRIM_COLOR 5 +#define MAX_PRIM_COLOR 0x1f + +static int SortRGBAxis; + +typedef struct QuantizedColorType { + GifByteType RGB[3]; + GifByteType NewColorIndex; + long Count; + struct QuantizedColorType *Pnext; +} QuantizedColorType; + +typedef struct NewColorMapType { + GifByteType RGBMin[3], RGBWidth[3]; + unsigned int NumEntries; /* # of QuantizedColorType in linked list below */ + unsigned long Count; /* Total number of pixels in all the entries */ + QuantizedColorType *QuantizedColors; +} NewColorMapType; + +static int SubdivColorMap(NewColorMapType * NewColorSubdiv, + unsigned int ColorMapSize, + unsigned int *NewColorMapSize); +static int SortCmpRtn(const void *Entry1, const void *Entry2); + +/****************************************************************************** + Quantize high resolution image into lower one. Input image consists of a + 2D array for each of the RGB colors with size Width by Height. There is no + Color map for the input. Output is a quantized image with 2D array of + indexes into the output color map. + Note input image can be 24 bits at the most (8 for red/green/blue) and + the output has 256 colors at the most (256 entries in the color map.). + ColorMapSize specifies size of color map up to 256 and will be updated to + real size before returning. + Also non of the parameter are allocated by this routine. + This function returns GIF_OK if successful, GIF_ERROR otherwise. +******************************************************************************/ +int +GifQuantizeBuffer(unsigned int Width, + unsigned int Height, + int *ColorMapSize, + GifByteType * RedInput, + GifByteType * GreenInput, + GifByteType * BlueInput, + GifByteType * OutputBuffer, + GifColorType * OutputColorMap) { + + unsigned int Index, NumOfEntries; + int i, j, MaxRGBError[3]; + unsigned int NewColorMapSize; + long Red, Green, Blue; + NewColorMapType NewColorSubdiv[256]; + QuantizedColorType *ColorArrayEntries, *QuantizedColor; + + ColorArrayEntries = (QuantizedColorType *)malloc( + sizeof(QuantizedColorType) * COLOR_ARRAY_SIZE); + if (ColorArrayEntries == NULL) { + return GIF_ERROR; + } + + for (i = 0; i < COLOR_ARRAY_SIZE; i++) { + ColorArrayEntries[i].RGB[0] = i >> (2 * BITS_PER_PRIM_COLOR); + ColorArrayEntries[i].RGB[1] = (i >> BITS_PER_PRIM_COLOR) & + MAX_PRIM_COLOR; + ColorArrayEntries[i].RGB[2] = i & MAX_PRIM_COLOR; + ColorArrayEntries[i].Count = 0; + } + + /* Sample the colors and their distribution: */ + for (i = 0; i < (int)(Width * Height); i++) { + Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) << + (2 * BITS_PER_PRIM_COLOR)) + + ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) << + BITS_PER_PRIM_COLOR) + + (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR)); + ColorArrayEntries[Index].Count++; + } + + /* Put all the colors in the first entry of the color map, and call the + * recursive subdivision process. */ + for (i = 0; i < 256; i++) { + NewColorSubdiv[i].QuantizedColors = NULL; + NewColorSubdiv[i].Count = NewColorSubdiv[i].NumEntries = 0; + for (j = 0; j < 3; j++) { + NewColorSubdiv[i].RGBMin[j] = 0; + NewColorSubdiv[i].RGBWidth[j] = 255; + } + } + + /* Find the non empty entries in the color table and chain them: */ + for (i = 0; i < COLOR_ARRAY_SIZE; i++) + if (ColorArrayEntries[i].Count > 0) + break; + QuantizedColor = NewColorSubdiv[0].QuantizedColors = &ColorArrayEntries[i]; + NumOfEntries = 1; + while (++i < COLOR_ARRAY_SIZE) + if (ColorArrayEntries[i].Count > 0) { + QuantizedColor->Pnext = &ColorArrayEntries[i]; + QuantizedColor = &ColorArrayEntries[i]; + NumOfEntries++; + } + QuantizedColor->Pnext = NULL; + + NewColorSubdiv[0].NumEntries = NumOfEntries; /* Different sampled colors */ + NewColorSubdiv[0].Count = ((long)Width) * Height; /* Pixels */ + NewColorMapSize = 1; + if (SubdivColorMap(NewColorSubdiv, *ColorMapSize, &NewColorMapSize) != + GIF_OK) { + free((char *)ColorArrayEntries); + return GIF_ERROR; + } + if (NewColorMapSize < *ColorMapSize) { + /* And clear rest of color map: */ + for (i = NewColorMapSize; i < *ColorMapSize; i++) + OutputColorMap[i].Red = OutputColorMap[i].Green = + OutputColorMap[i].Blue = 0; + } + + /* Average the colors in each entry to be the color to be used in the + * output color map, and plug it into the output color map itself. */ + for (i = 0; i < NewColorMapSize; i++) { + if ((j = NewColorSubdiv[i].NumEntries) > 0) { + QuantizedColor = NewColorSubdiv[i].QuantizedColors; + Red = Green = Blue = 0; + while (QuantizedColor) { + QuantizedColor->NewColorIndex = i; + Red += QuantizedColor->RGB[0]; + Green += QuantizedColor->RGB[1]; + Blue += QuantizedColor->RGB[2]; + QuantizedColor = QuantizedColor->Pnext; + } + OutputColorMap[i].Red = (Red << (8 - BITS_PER_PRIM_COLOR)) / j; + OutputColorMap[i].Green = (Green << (8 - BITS_PER_PRIM_COLOR)) / j; + OutputColorMap[i].Blue = (Blue << (8 - BITS_PER_PRIM_COLOR)) / j; + } + } + + /* Finally scan the input buffer again and put the mapped index in the + * output buffer. */ + MaxRGBError[0] = MaxRGBError[1] = MaxRGBError[2] = 0; + for (i = 0; i < (int)(Width * Height); i++) { + Index = ((RedInput[i] >> (8 - BITS_PER_PRIM_COLOR)) << + (2 * BITS_PER_PRIM_COLOR)) + + ((GreenInput[i] >> (8 - BITS_PER_PRIM_COLOR)) << + BITS_PER_PRIM_COLOR) + + (BlueInput[i] >> (8 - BITS_PER_PRIM_COLOR)); + Index = ColorArrayEntries[Index].NewColorIndex; + OutputBuffer[i] = Index; + if (MaxRGBError[0] < ABS(OutputColorMap[Index].Red - RedInput[i])) + MaxRGBError[0] = ABS(OutputColorMap[Index].Red - RedInput[i]); + if (MaxRGBError[1] < ABS(OutputColorMap[Index].Green - GreenInput[i])) + MaxRGBError[1] = ABS(OutputColorMap[Index].Green - GreenInput[i]); + if (MaxRGBError[2] < ABS(OutputColorMap[Index].Blue - BlueInput[i])) + MaxRGBError[2] = ABS(OutputColorMap[Index].Blue - BlueInput[i]); + } + +#ifdef DEBUG + fprintf(stderr, + "Quantization L(0) errors: Red = %d, Green = %d, Blue = %d.\n", + MaxRGBError[0], MaxRGBError[1], MaxRGBError[2]); +#endif /* DEBUG */ + + free((char *)ColorArrayEntries); + + *ColorMapSize = NewColorMapSize; + + return GIF_OK; +} + +/****************************************************************************** + Routine to subdivide the RGB space recursively using median cut in each + axes alternatingly until ColorMapSize different cubes exists. + The biggest cube in one dimension is subdivide unless it has only one entry. + Returns GIF_ERROR if failed, otherwise GIF_OK. +*******************************************************************************/ +static int +SubdivColorMap(NewColorMapType * NewColorSubdiv, + unsigned int ColorMapSize, + unsigned int *NewColorMapSize) { + + int MaxSize; + unsigned int i, j, Index = 0, NumEntries, MinColor, MaxColor; + long Sum, Count; + QuantizedColorType *QuantizedColor, **SortArray; + + while (ColorMapSize > *NewColorMapSize) { + /* Find candidate for subdivision: */ + MaxSize = -1; + for (i = 0; i < *NewColorMapSize; i++) { + for (j = 0; j < 3; j++) { + if ((((int)NewColorSubdiv[i].RGBWidth[j]) > MaxSize) && + (NewColorSubdiv[i].NumEntries > 1)) { + MaxSize = NewColorSubdiv[i].RGBWidth[j]; + Index = i; + SortRGBAxis = j; + } + } + } + + if (MaxSize == -1) + return GIF_OK; + + /* Split the entry Index into two along the axis SortRGBAxis: */ + + /* Sort all elements in that entry along the given axis and split at + * the median. */ + SortArray = (QuantizedColorType **)malloc( + sizeof(QuantizedColorType *) * + NewColorSubdiv[Index].NumEntries); + if (SortArray == NULL) + return GIF_ERROR; + for (j = 0, QuantizedColor = NewColorSubdiv[Index].QuantizedColors; + j < NewColorSubdiv[Index].NumEntries && QuantizedColor != NULL; + j++, QuantizedColor = QuantizedColor->Pnext) + SortArray[j] = QuantizedColor; + + /* + * Because qsort isn't stable, this can produce differing + * results for the order of tuples depending on platform + * details of how qsort() is implemented. + * + * We mitigate this problem by sorting on all three axes rather + * than only the one specied by SortRGBAxis; that way the instability + * can only become an issue if there are multiple color indices + * referring to identical RGB tuples. Older versions of this + * sorted on only the one axis. + */ + qsort(SortArray, NewColorSubdiv[Index].NumEntries, + sizeof(QuantizedColorType *), SortCmpRtn); + + /* Relink the sorted list into one: */ + for (j = 0; j < NewColorSubdiv[Index].NumEntries - 1; j++) + SortArray[j]->Pnext = SortArray[j + 1]; + SortArray[NewColorSubdiv[Index].NumEntries - 1]->Pnext = NULL; + NewColorSubdiv[Index].QuantizedColors = QuantizedColor = SortArray[0]; + free((char *)SortArray); + + /* Now simply add the Counts until we have half of the Count: */ + Sum = NewColorSubdiv[Index].Count / 2 - QuantizedColor->Count; + NumEntries = 1; + Count = QuantizedColor->Count; + while (QuantizedColor->Pnext != NULL && + (Sum -= QuantizedColor->Pnext->Count) >= 0 && + QuantizedColor->Pnext->Pnext != NULL) { + QuantizedColor = QuantizedColor->Pnext; + NumEntries++; + Count += QuantizedColor->Count; + } + /* Save the values of the last color of the first half, and first + * of the second half so we can update the Bounding Boxes later. + * Also as the colors are quantized and the BBoxes are full 0..255, + * they need to be rescaled. + */ + MaxColor = QuantizedColor->RGB[SortRGBAxis]; /* Max. of first half */ + /* coverity[var_deref_op] */ + MinColor = QuantizedColor->Pnext->RGB[SortRGBAxis]; /* of second */ + MaxColor <<= (8 - BITS_PER_PRIM_COLOR); + MinColor <<= (8 - BITS_PER_PRIM_COLOR); + + /* Partition right here: */ + NewColorSubdiv[*NewColorMapSize].QuantizedColors = + QuantizedColor->Pnext; + QuantizedColor->Pnext = NULL; + NewColorSubdiv[*NewColorMapSize].Count = Count; + NewColorSubdiv[Index].Count -= Count; + NewColorSubdiv[*NewColorMapSize].NumEntries = + NewColorSubdiv[Index].NumEntries - NumEntries; + NewColorSubdiv[Index].NumEntries = NumEntries; + for (j = 0; j < 3; j++) { + NewColorSubdiv[*NewColorMapSize].RGBMin[j] = + NewColorSubdiv[Index].RGBMin[j]; + NewColorSubdiv[*NewColorMapSize].RGBWidth[j] = + NewColorSubdiv[Index].RGBWidth[j]; + } + NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] = + NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] + + NewColorSubdiv[*NewColorMapSize].RGBWidth[SortRGBAxis] - MinColor; + NewColorSubdiv[*NewColorMapSize].RGBMin[SortRGBAxis] = MinColor; + + NewColorSubdiv[Index].RGBWidth[SortRGBAxis] = + MaxColor - NewColorSubdiv[Index].RGBMin[SortRGBAxis]; + + (*NewColorMapSize)++; + } + + return GIF_OK; +} + +/**************************************************************************** + Routine called by qsort to compare two entries. +*****************************************************************************/ + +static int +SortCmpRtn(const void *Entry1, + const void *Entry2) { + QuantizedColorType *entry1 = (*((QuantizedColorType **) Entry1)); + QuantizedColorType *entry2 = (*((QuantizedColorType **) Entry2)); + + /* sort on all axes of the color space! */ + int hash1 = entry1->RGB[SortRGBAxis] * 256 * 256 + + entry1->RGB[(SortRGBAxis+1) % 3] * 256 + + entry1->RGB[(SortRGBAxis+2) % 3]; + int hash2 = entry2->RGB[SortRGBAxis] * 256 * 256 + + entry2->RGB[(SortRGBAxis+1) % 3] * 256 + + entry2->RGB[(SortRGBAxis+2) % 3]; + + return hash1 - hash2; +} + +/* end */ From 20a9c9ad1d350c77c49ab5eed97291c788e9d000 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 10:10:10 +0800 Subject: [PATCH 02/63] start work on #44 - get pixel --- lib/Image.js | 4 ++++ src/image/image.cpp | 16 ++++++++++++++++ src/image/image.h | 1 + 3 files changed, 21 insertions(+) diff --git a/lib/Image.js b/lib/Image.js index 980f78f8..9d19213c 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -56,6 +56,10 @@ return this.__lwip.height(); }; + Image.prototype.getPixel = function(left, top) { + return this.__lwip.getPixel(left, top); + }; + Image.prototype.size = function() { return { width: this.__lwip.width(), diff --git a/src/image/image.cpp b/src/image/image.cpp index d06cacd4..e8bbafce 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -9,6 +9,7 @@ void LwipImage::Init(Handle exports) { tpl->SetClassName(NanNew("LwipImage")); NODE_SET_PROTOTYPE_METHOD(tpl, "width", width); NODE_SET_PROTOTYPE_METHOD(tpl, "height", height); + NODE_SET_PROTOTYPE_METHOD(tpl, "getPixel", getPixel); NODE_SET_PROTOTYPE_METHOD(tpl, "buffer", buffer); NODE_SET_PROTOTYPE_METHOD(tpl, "resize", resize); NODE_SET_PROTOTYPE_METHOD(tpl, "rotate", rotate); @@ -76,6 +77,21 @@ NAN_METHOD(LwipImage::height) { NanReturnValue(NanNew(obj->_cimg->height())); } +// image.getPixel(left, top): +// --------------- +NAN_METHOD(LwipImage::getPixel) { + NanScope(); + size_t left = (size_t) args[0].As()->Value(); + size_t top = (size_t) args[1].As()->Value(); + LwipImage * obj = ObjectWrap::Unwrap(args.Holder()); + int rgba[4] = {0, 0, 0, 100}; + rgba[0] = *(obj->_cimg)(left, top, 0, 0); // red + rgba[1] = *(obj->_cimg)(left, top, 0, 1); // green + rgba[2] = *(obj->_cimg)(left, top, 0, 2); // blue + rgba[3] = *(obj->_cimg)(left, top, 0, 3); // alpha + NanReturnValue(NanNew(rgba)); +} + // image.buffer(): // --------------- NAN_METHOD(LwipImage::buffer) { diff --git a/src/image/image.h b/src/image/image.h index d7cb9a13..ac2638ac 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -42,6 +42,7 @@ class LwipImage : public node::ObjectWrap { static NAN_METHOD(paste); static NAN_METHOD(width); static NAN_METHOD(height); + static NAN_METHOD(getPixel); static NAN_METHOD(buffer); LwipImage(unsigned char * data, size_t width, size_t height); ~LwipImage(); From 920c8281c1d13d1ddd82bba6b901d2a2cae43146 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 10:47:33 +0800 Subject: [PATCH 03/63] use v8 array as return type for getPixel --- src/image/image.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/image/image.cpp b/src/image/image.cpp index e8bbafce..c0cf60b5 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -84,12 +84,12 @@ NAN_METHOD(LwipImage::getPixel) { size_t left = (size_t) args[0].As()->Value(); size_t top = (size_t) args[1].As()->Value(); LwipImage * obj = ObjectWrap::Unwrap(args.Holder()); - int rgba[4] = {0, 0, 0, 100}; - rgba[0] = *(obj->_cimg)(left, top, 0, 0); // red - rgba[1] = *(obj->_cimg)(left, top, 0, 1); // green - rgba[2] = *(obj->_cimg)(left, top, 0, 2); // blue - rgba[3] = *(obj->_cimg)(left, top, 0, 3); // alpha - NanReturnValue(NanNew(rgba)); + Local rgba = NanNew(4); + rgba->Set(0, NanNew((*(obj->_cimg))(left, top, 0, 0))); // red + rgba->Set(1, NanNew((*(obj->_cimg))(left, top, 0, 1))); // green + rgba->Set(2, NanNew((*(obj->_cimg))(left, top, 0, 2))); // blue + rgba->Set(3, NanNew((*(obj->_cimg))(left, top, 0, 3))); // alpha + NanReturnValue(rgba); } // image.buffer(): From 2ae7a05bd557071c5bbac8bba7c4f645a4f6bfbf Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 12:18:27 +0800 Subject: [PATCH 04/63] validate getPixel arguments. return a color object --- lib/Image.js | 19 +++++++++++++++++-- lib/defs.js | 7 +++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/lib/Image.js b/lib/Image.js index 9d19213c..e7731fe6 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -30,6 +30,7 @@ clone: decree(defs.args.clone), extract: decree(defs.args.extract), toBuffer: decree(defs.args.toBuffer), + getPixel: decree(defs.args.getPixel), writeFile: decree(defs.args.writeFile) }; @@ -56,8 +57,22 @@ return this.__lwip.height(); }; - Image.prototype.getPixel = function(left, top) { - return this.__lwip.getPixel(left, top); + Image.prototype.getPixel = function() { + var args = judges.getPixel(arguments), + left = args[0], + top = args[1]; + + if (left >= this.width() || top >= this.height()) + throw Error("Coordinates exceed dimensions of image"); + + var rgba = this.__lwip.getPixel(left, top); + + return { + r: rgba[0], + g: rgba[1], + b: rgba[2], + a: rgba[3] + }; }; Image.prototype.size = function() { diff --git a/lib/defs.js b/lib/defs.js index ca820f07..1bd4a15f 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -253,6 +253,13 @@ name: 'callback', type: 'function' }], + getPixel: [{ + name: 'left', + type: 'nn-int' + }, { + name: 'top', + type: 'nn-int' + }], border: [{ name: 'width', type: 'nn-number' From 7094d3376e295c578cdcb7078f2f6b771a68efef Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 13:17:11 +0800 Subject: [PATCH 05/63] add tests for getPixel --- tests/00.argsValidation/110.image.getPixel.js | 25 ++++++++++++++++++ tests/01.getters/index.js | 26 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/00.argsValidation/110.image.getPixel.js diff --git a/tests/00.argsValidation/110.image.getPixel.js b/tests/00.argsValidation/110.image.getPixel.js new file mode 100644 index 00000000..803fadee --- /dev/null +++ b/tests/00.argsValidation/110.image.getPixel.js @@ -0,0 +1,25 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('image.getPixel arguments validation', function() { + + var image; + before(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + image = img; + done(err); + }); + }); + + describe('coordinates exceeding image dimensions', function() { + it('should throw an error', function() { + image.getPixel.bind(image, 99999, 0).should.throwError(); + image.getPixel.bind(image, 0, 99999).should.throwError(); + image.getPixel.bind(image, 99999, 99999).should.throwError(); + }); + }); + +}); diff --git a/tests/01.getters/index.js b/tests/01.getters/index.js index 9e9e47ca..854cf7b1 100644 --- a/tests/01.getters/index.js +++ b/tests/01.getters/index.js @@ -33,6 +33,32 @@ describe('lwip.size', function() { }); }); +describe('lwip.getPixel', function() { + it('should return correct color at 0,0', function() { + var color = image.getPixel(0, 0); + assert(color.r === 48); + assert(color.g === 86); + assert(color.b === 151); + assert(color.a === 100); + }); + + it('should return correct color at 418, 242', function() { + var color = image.getPixel(418, 242); + assert(color.r === 208); + assert(color.g === 228); + assert(color.b === 237); + assert(color.a === 100); + }); + + it('should return correct color at 499, 332', function() { + var color = image.getPixel(499, 332); + assert(color.r === 31); + assert(color.g === 27); + assert(color.b === 0); + assert(color.a === 100); + }); +}); + describe('lwip.clone', function() { it('should return a new image object', function(done) { image.clone(function(err, clonedImage) { From efae86ab74caa01326e609015b5753523e61f86c Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 13:46:44 +0800 Subject: [PATCH 06/63] add getPixel docs to the readme --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 886559c4..df4d2036 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) + 0. [Pixel](#get-pixel) 0. [Clone](#clone) 0. [Extract / Copy](#extract) 0. [Get as a Buffer](#get-as-a-buffer) @@ -477,6 +478,16 @@ Paste an image on top of this image. `image.height()` returns the image's height in pixels. +#### Get Pixel + +`image.getPixel(left, top)` returns the color of the pixel at the `(left, top)` +coordinate. + +0. `left {Integer>=0}` +0. `top {Integer>=0}` + +Color is returned as an object. See [colors specification](#colors-specification). + #### Clone Clone the image into a new image object. From 3bb46562c2575b86ed34b6d7a8b9784d100f615c Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 15:45:34 +0800 Subject: [PATCH 07/63] register 'color' type with decree, instead of manually validating --- lib/defs.js | 10 +++++----- lib/util.js | 37 +++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/lib/defs.js b/lib/defs.js index ca820f07..f89f3440 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -99,7 +99,7 @@ type: 'p-number' }, { name: 'color', - types: ['string', 'array', 'hash'], + type: 'color', optional: true, default: defaults.DEF_CREATE_COLOR }, { @@ -143,7 +143,7 @@ type: 'number' }, { name: 'color', - types: ['string', 'array', 'hash'], + type: 'color', optional: true, default: defaults.DEF_ROTATE_COLOR }, { @@ -258,7 +258,7 @@ type: 'nn-number' }, { name: 'color', - types: ['string', 'array', 'hash'], + type: 'color', optional: true, default: defaults.DEF_BORDER_COLOR }, { @@ -279,7 +279,7 @@ type: 'nn-number' }, { name: 'color', - types: ['string', 'array', 'hash'], + type: 'color', optional: true, default: defaults.DEF_PAD_COLOR }, { @@ -342,6 +342,6 @@ name: 'callback', type: 'function' }] - } + }; })(); diff --git a/lib/util.js b/lib/util.js index bc357ae6..0f7e6570 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,15 +1,17 @@ (function(undefined) { - var defs = require('./defs'); + var defs = require('./defs'), + decree = require('decree'); + + decree.register('color', validateColor); function undefinedFilter(v) { return v !== undefined; } - function normalizeColor(color) { + function validateColor(color) { if (typeof color === 'string') { - if (defs.colors[color]) color = defs.colors[color]; - else throw Error('Unknown color ' + color); + if (!defs.colors[color]) return false; } else { if (color instanceof Array) { color = { @@ -20,14 +22,25 @@ }; } if (color.a !== 0) color.a = color.a || defs.defaults.DEF_COLOR_ALPHA; - if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255) - throw Error('\'red\' color component is invalid'); - if (color.g != parseInt(color.g) || color.g < 0 || color.g > 255) - throw Error('\'green\' color component is invalid'); - if (color.b != parseInt(color.b) || color.b < 0 || color.b > 255) - throw Error('\'blue\' color component is invalid'); - if (color.a != parseInt(color.a) || color.a < 0 || color.a > 100) - throw Error('\'alpha\' color component is invalid'); + if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255 || + color.g != parseInt(color.g) || color.g < 0 || color.g > 255 || + color.b != parseInt(color.b) || color.b < 0 || color.b > 255 || + color.a != parseInt(color.a) || color.a < 0 || color.a > 100) + return false; + } + return true; + } + + function normalizeColor(color) { + if (typeof color === 'string') { + color = defs.colors[color]; + } else if (color instanceof Array) { + color = { + r: color[0], + g: color[1], + b: color[2], + a: color[3] + }; } return color; } From 322bc538fa97728f3e4c7f909087c86993adf0ca Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 16:19:19 +0800 Subject: [PATCH 08/63] start work on #45 - set pixel --- binding.gyp | 1 + src/image/image.cpp | 39 +++++++++++++++++++++++++++++++++++ src/image/image.h | 26 +++++++++++++++++++++++ src/image/setpixel_worker.cpp | 33 +++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 src/image/setpixel_worker.cpp diff --git a/binding.gyp b/binding.gyp index 6eef55e7..f5bace03 100644 --- a/binding.gyp +++ b/binding.gyp @@ -225,6 +225,7 @@ "src/image/hsla_worker.cpp", "src/image/opacify_worker.cpp", "src/image/paste_worker.cpp", + "src/image/setpixel_worker.cpp", ], 'include_dirs': [ ' exports) { NODE_SET_PROTOTYPE_METHOD(tpl, "hslaAdj", hslaAdj); NODE_SET_PROTOTYPE_METHOD(tpl, "opacify", opacify); NODE_SET_PROTOTYPE_METHOD(tpl, "paste", paste); + NODE_SET_PROTOTYPE_METHOD(tpl, "paste", setPixel); NanAssignPersistent(constructor, tpl); exports->Set( NanNew("LwipImage"), @@ -384,3 +385,41 @@ NAN_METHOD(LwipImage::paste) { NanReturnUndefined(); } + +// image.setPixel(left, top, color, callback): +// ------------------------------------------- + +// args[0] - left +// args[1] - top +// args[2] - red +// args[3] - green +// args[4] - blue +// args[5] - alpha +// args[6] - callback +NAN_METHOD(LwipImage::setPixel) { + NanScope(); + + size_t left = (size_t) args[0].As()->Value(); + size_t top = (size_t) args[1].As()->Value(); + unsigned char r = (unsigned char) args[2].As()->Value(); + unsigned char g = (unsigned char) args[3].As()->Value(); + unsigned char b = (unsigned char) args[4].As()->Value(); + unsigned char a = (unsigned char) args[5].As()->Value(); + NanCallback * callback = new NanCallback(args[6].As()); + CImg * cimg = ObjectWrap::Unwrap(args.This())->_cimg; + + NanAsyncQueueWorker( + new SetPixelWorker( + left, + top, + r, + g, + b, + a, + cimg, + callback + ) + ); + + NanReturnUndefined(); +} diff --git a/src/image/image.h b/src/image/image.h index d7cb9a13..f1f6aba8 100644 --- a/src/image/image.h +++ b/src/image/image.h @@ -43,6 +43,7 @@ class LwipImage : public node::ObjectWrap { static NAN_METHOD(width); static NAN_METHOD(height); static NAN_METHOD(buffer); + static NAN_METHOD(setPixel); LwipImage(unsigned char * data, size_t width, size_t height); ~LwipImage(); private: @@ -246,6 +247,31 @@ class PasteWorker : public NanAsyncWorker { CImg * _cimg; }; +class SetPixelWorker : public NanAsyncWorker { +public: + SetPixelWorker( + size_t left, + size_t top, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char a, + CImg * cimg, + NanCallback * callback + ); + ~SetPixelWorker(); + void Execute (); + void HandleOKCallback (); +private: + size_t _left; + size_t _top; + unsigned char _r; + unsigned char _g; + unsigned char _b; + unsigned char _a; + CImg * _cimg; +}; + void rgb_to_hsl(unsigned char r, unsigned char g, unsigned char b, float * h, float * s, float * l); void hsl_to_rgb(float h, float s, float l, unsigned char * r, unsigned char * g, diff --git a/src/image/setpixel_worker.cpp b/src/image/setpixel_worker.cpp new file mode 100644 index 00000000..4e72862f --- /dev/null +++ b/src/image/setpixel_worker.cpp @@ -0,0 +1,33 @@ +#include "image.h" + +SetPixelWorker::SetPixelWorker( + size_t left, + size_t top, + unsigned char r, + unsigned char g, + unsigned char b, + unsigned char a, + CImg * cimg, + NanCallback * callback +): NanAsyncWorker(callback), _left(left), _top(top), _r(r), _g(g), _b(b), _a(a), + _cimg(cimg) {} + +SetPixelWorker::~SetPixelWorker() {} + +void SetPixelWorker::Execute () { + try { + _cimg->fillC(_left, _top, 0, _r, _g, _b, _a); + } catch (CImgException e) { + SetErrorMessage("Unable to set pixel"); + return; + } + return; +} + +void SetPixelWorker::HandleOKCallback () { + NanScope(); + Local argv[] = { + NanNull() + }; + callback->Call(1, argv); +} From 04341f2d3e27fd3cfeff777ab35a2a2d06cfbe42 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 17:03:18 +0800 Subject: [PATCH 09/63] add setPixel tests --- tests/00.argsValidation/110.image.setPixel.js | 26 +++++++ tests/00.argsValidation/210.batch.setPixel.js | 43 +++++++++++ tests/02.operations/118.setPixel.js | 71 +++++++++++++++++++ tests/03.safety/00.locks.js | 7 ++ tests/03.safety/01.releases.js | 7 ++ tests/utils.js | 8 ++- 6 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 tests/00.argsValidation/110.image.setPixel.js create mode 100644 tests/00.argsValidation/210.batch.setPixel.js create mode 100644 tests/02.operations/118.setPixel.js diff --git a/tests/00.argsValidation/110.image.setPixel.js b/tests/00.argsValidation/110.image.setPixel.js new file mode 100644 index 00000000..110186e8 --- /dev/null +++ b/tests/00.argsValidation/110.image.setPixel.js @@ -0,0 +1,26 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('image.setPixel arguments validation', function() { + + var image; + before(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + image = img; + done(err); + }); + }); + + describe("pixel coordinates out of bounds", function(){ + it("should throw an error", function(done){ + image.setPixel.bind(image, 9999, 0, 'yellow', function(){}).should.throwError(); + image.setPixel.bind(image, 0, 9999, 'yellow', function(){}).should.throwError(); + image.setPixel.bind(image, 9999, 9999, 'yellow', function(){}).should.throwError(); + image.setPixel.bind(image, 9999, 0, 'yellow', function(){}).should.throwError(); + image.setPixel.bind(image, 0, 0, 'yellow', done).should.not.throwError(); // sanity check + }); + }); +}); diff --git a/tests/00.argsValidation/210.batch.setPixel.js b/tests/00.argsValidation/210.batch.setPixel.js new file mode 100644 index 00000000..ed85483c --- /dev/null +++ b/tests/00.argsValidation/210.batch.setPixel.js @@ -0,0 +1,43 @@ +// methods should throw errors when arguments are invalid + +var should = require("should"), + assert = require('assert'), + lwip = require('../../'), + imgs = require('../imgs'); + +describe('batch.setPixel arguments validation', function() { + + var batch; + beforeEach(function(done) { + lwip.open(imgs.jpg.rgb, function(err, img) { + batch = img.batch(); + done(err); + }); + }); + + describe('pixel exceeds dimensions', function() { + + describe('at the time of exec', function() { + it('should return an error', function(done) { + batch.setPixel.bind(batch, 999, 999, 'yellow').should.not.throwError(); + batch.exec(function(err) { + // there should be an error message + assert(!!err); + done(); + }); + }); + }); + + describe('before exec', function() { + it('should not return an error', function(done) { + batch.setPixel.bind(batch, 999, 999, 'yellow').should.not.throwError(); + batch.resize(1000, 1000); + batch.exec(function(err) { + // there should not be an error message + assert(!err); + done(); + }); + }); + }); + }); +}); diff --git a/tests/02.operations/118.setPixel.js b/tests/02.operations/118.setPixel.js new file mode 100644 index 00000000..932c7671 --- /dev/null +++ b/tests/02.operations/118.setPixel.js @@ -0,0 +1,71 @@ +var join = require('path').join, + should = require('should'), + assert = require('assert'), + mkdirp = require('mkdirp'), + lwip = require('../../'), + imgs = require('../imgs'); + +var tmpDir = join(__dirname, '../results'), + basename = 'setPixel', + current; + +describe('lwip.setPixel', function() { + + var image; + + before(function(done) { + mkdirp(tmpDir, done); + }); + + beforeEach(function(done) { + lwip.open(imgs.png.trans, function(err, img) { + image = img; + done(err); + }); + }); + + afterEach(function(done) { + image.writeFile(join(tmpDir, current.join('_') + '.jpg'), 'jpeg', { + quality: 100 + }, done); + }); + + beforeEach(function() { + current = [basename]; + }); + + describe('red pixel at 0,0', function() { + + it('pixel should have the correct color', function(done) { + current.push('0,0-red'); + image.setPixel(0, 0, 'red', function(err, im) { + if (err) return done(err); + var color = im.getPixel(0, 0); + assert(color.r === 255); + assert(color.g === 0); + assert(color.b === 0); + assert(color.a === 100); + done(); + }); + }); + + }); + + describe('red pixel at 100,100', function() { + + it('pixel should have the correct color', function(done) { + current.push('100,100-red'); + image.setPixel(100, 100, 'red', function(err, im) { + if (err) return done(err); + var color = im.getPixel(100, 100); + assert(color.r === 255); + assert(color.g === 0); + assert(color.b === 0); + assert(color.a === 100); + done(); + }); + }); + + }); + +}); diff --git a/tests/03.safety/00.locks.js b/tests/03.safety/00.locks.js index d88d87d6..fbcb3164 100644 --- a/tests/03.safety/00.locks.js +++ b/tests/03.safety/00.locks.js @@ -122,6 +122,13 @@ describe('simultaneous operations locks', function() { describe('image.hslaAdjust lock', function() { it('should lock image', function() { image.hslaAdjust.bind(image, 100, 1, 1, 0, function() {}).should.not.throwError(); + image.setPixel.bind(image, 0, 0, 'yellow', function() {}).should.throwError(); + }); + }); + + describe('image.setPixel lock', function() { + it('should lock image', function() { + image.setPixel.bind(image, 0, 0, 'yellow', function() {}).should.not.throwError(); image.resize.bind(image, 100, 100, function() {}).should.throwError(); }); }); diff --git a/tests/03.safety/01.releases.js b/tests/03.safety/01.releases.js index d7045953..0af5955c 100644 --- a/tests/03.safety/01.releases.js +++ b/tests/03.safety/01.releases.js @@ -122,6 +122,13 @@ describe('failed ops lock release', function() { describe('image.hslaAdjust release', function() { it('should release image lock', function() { image.hslaAdjust.bind(image, 'foo', 'foo', 'foo', 'foo', function() {}).should.throwError(); + image.setPixel.bind(image, 0, 0, 'yellow', function() {}).should.not.throwError(); + }); + }); + + describe('image.setPixel release', function() { + it('should release image lock', function() { + image.setPixel.bind(image, 'foo', 'foo', 'foo', function() {}).should.throwError(); image.resize.bind(image, 100, 100, function() {}).should.not.throwError(); }); }); diff --git a/tests/utils.js b/tests/utils.js index 2f9b44f0..ad5bd401 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -6,7 +6,7 @@ module.exports = { function generateRandomBatch(batch, n) { var ops = []; for (var i = 0; i < n; i++) { - var r = Math.floor(Math.random() * 14); + var r = Math.floor(Math.random() * 15); switch (r) { case 0: var sd = Math.floor(Math.random() * 20); @@ -81,6 +81,12 @@ function generateRandomBatch(batch, n) { batch = batch.opacify(); ops.push('opc'); break; + case 14: + var left = Math.floor(Math.random() * 100), + top = Math.floor(Math.random() * 100); + batch = batch.setPixel(left, top, getRandomColor()); + ops.push('stpx'); + break; } } return ops; From b8ec93e1b7d4ff337221474e0f66c2a3a8787e1e Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 17:21:26 +0800 Subject: [PATCH 10/63] make normalizeColor assign default alpha value instead of validateColor --- lib/util.js | 6 ++++-- tests/02.operations/104.rotate.js | 11 +++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/util.js b/lib/util.js index 0f7e6570..471c9281 100644 --- a/lib/util.js +++ b/lib/util.js @@ -21,11 +21,12 @@ a: color[3] }; } - if (color.a !== 0) color.a = color.a || defs.defaults.DEF_COLOR_ALPHA; + var a = color.a; + if (a !== 0) a = a || defs.defaults.DEF_COLOR_ALPHA; // (don't modify the original color object) if (color.r != parseInt(color.r) || color.r < 0 || color.r > 255 || color.g != parseInt(color.g) || color.g < 0 || color.g > 255 || color.b != parseInt(color.b) || color.b < 0 || color.b > 255 || - color.a != parseInt(color.a) || color.a < 0 || color.a > 100) + a != parseInt(a) || a < 0 || a > 100) return false; } return true; @@ -42,6 +43,7 @@ a: color[3] }; } + if (color.a !== 0) color.a = color.a || defs.defaults.DEF_COLOR_ALPHA; return color; } diff --git a/tests/02.operations/104.rotate.js b/tests/02.operations/104.rotate.js index bf3957bd..51cc0e1c 100644 --- a/tests/02.operations/104.rotate.js +++ b/tests/02.operations/104.rotate.js @@ -113,6 +113,17 @@ describe('lwip.rotate', function() { }); }); + describe('-5 degs, {r:200,g:110,b:220} fill', function() { + it('should succeed', function(done) { + current.push(-5, 'degs', 'r-200,g-110,b-220'); + image.rotate(-5, { + r: 200, + g: 110, + b: 220 + }, done); + }); + }); + describe('-5 degs, {r:200,g:110,b:220,a:50} fill', function() { it('should succeed', function(done) { current.push(-5, 'degs', 'r-200,g-110,b-220,a-50'); From c3d53e67964a08d0f56adbb0a29b90bb6e81cea5 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 18:03:02 +0800 Subject: [PATCH 11/63] add setPixel method to Image and Batch prototypes --- lib/Batch.js | 10 ++++++++++ lib/Image.js | 23 ++++++++++++++++++++++- lib/defs.js | 15 ++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/Batch.js b/lib/Batch.js index 26fac8f3..303b405f 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -30,6 +30,7 @@ paste: decree(defs.args.paste.slice(0, -1)), clone: decree(defs.args.clone.slice(0, -1)), extract: decree(defs.args.extract.slice(0, -1)), + setPixel: decree(defs.args.setPixel.slice(0, -1)), exec: decree(defs.args.exec), toBuffer: decree(defs.args.toBuffer), writeFile: decree(defs.args.writeFile) @@ -223,6 +224,15 @@ return this; }; + Batch.prototype.setPixel = function() { + var that = this; + judges.setPixel(arguments, function(left, top, color) { + color = normalizeColor(color); + that.__addOp(that.__image.setPixel, [left, top, color].filter(undefinedFilter)); + }); + return this; + }; + Batch.prototype.toBuffer = function() { var that = this; judges.toBuffer(arguments, function(type, params, callback) { diff --git a/lib/Image.js b/lib/Image.js index 980f78f8..42f9757c 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -30,7 +30,8 @@ clone: decree(defs.args.clone), extract: decree(defs.args.extract), toBuffer: decree(defs.args.toBuffer), - writeFile: decree(defs.args.writeFile) + writeFile: decree(defs.args.writeFile), + setPixel: decree(defs.args.setPixel) }; function Image(pixelsBuf, width, height, trans) { @@ -419,6 +420,26 @@ ); }; + Image.prototype.setPixel = function() { + this.__lock(); + var that = this; + judges.setPixel( + arguments, + function(left, top, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.setPixel(+left, +top, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + Image.prototype.clone = function() { // no need to lock the image. we don't modify the memory buffer. // just copy it. diff --git a/lib/defs.js b/lib/defs.js index ca820f07..722c615f 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -341,7 +341,20 @@ }, { name: 'callback', type: 'function' + }], + setPixel: [{ + name: 'left', + type: 'nn-int' + }, { + name: 'top', + type: 'nn-int' + }, { + name: 'color', + type: 'color' + }, { + name: 'callback', + type: 'function' }] - } + }; })(); From 76dab217a0d9507e127cc547b1cbf61d4cee2943 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 19:28:21 +0800 Subject: [PATCH 12/63] fix typo --- src/image/image.cpp | 2 +- tests/00.argsValidation/110.image.setPixel.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/image/image.cpp b/src/image/image.cpp index bf52252f..488e817f 100644 --- a/src/image/image.cpp +++ b/src/image/image.cpp @@ -20,7 +20,7 @@ void LwipImage::Init(Handle exports) { NODE_SET_PROTOTYPE_METHOD(tpl, "hslaAdj", hslaAdj); NODE_SET_PROTOTYPE_METHOD(tpl, "opacify", opacify); NODE_SET_PROTOTYPE_METHOD(tpl, "paste", paste); - NODE_SET_PROTOTYPE_METHOD(tpl, "paste", setPixel); + NODE_SET_PROTOTYPE_METHOD(tpl, "setPixel", setPixel); NanAssignPersistent(constructor, tpl); exports->Set( NanNew("LwipImage"), diff --git a/tests/00.argsValidation/110.image.setPixel.js b/tests/00.argsValidation/110.image.setPixel.js index 110186e8..41e57ecb 100644 --- a/tests/00.argsValidation/110.image.setPixel.js +++ b/tests/00.argsValidation/110.image.setPixel.js @@ -19,7 +19,6 @@ describe('image.setPixel arguments validation', function() { image.setPixel.bind(image, 9999, 0, 'yellow', function(){}).should.throwError(); image.setPixel.bind(image, 0, 9999, 'yellow', function(){}).should.throwError(); image.setPixel.bind(image, 9999, 9999, 'yellow', function(){}).should.throwError(); - image.setPixel.bind(image, 9999, 0, 'yellow', function(){}).should.throwError(); image.setPixel.bind(image, 0, 0, 'yellow', done).should.not.throwError(); // sanity check }); }); From 8a94d43b914e8c6a573f0a4daf175f5faa228d7e Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 19:29:48 +0800 Subject: [PATCH 13/63] add coordinates validation to setPixel --- lib/Image.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Image.js b/lib/Image.js index 42f9757c..6474f15d 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -426,6 +426,10 @@ judges.setPixel( arguments, function(left, top, color, callback) { + if (left >= that.width() || top >= that.height()){ + that.__release(); + throw Error("Coordinates exceed dimensions of image"); + } color = normalizeColor(color); if (color.a < 100) that.__trans = true; that.__lwip.setPixel(+left, +top, +color.r, +color.g, +color.b, +color.a, function(err) { From 9ed00ee8badf1f4357722a042c18ce614d61599a Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 20:05:34 +0800 Subject: [PATCH 14/63] add 'interpolation' decree custom type --- lib/Batch.js | 2 -- lib/Image.js | 2 -- lib/defs.js | 14 +++++++------- lib/util.js | 5 +++++ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/Batch.js b/lib/Batch.js index 26fac8f3..189c79d8 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -81,7 +81,6 @@ Batch.prototype.scale = function() { var that = this; judges.scale(arguments, function(wRatio, hRatio, inter) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); that.__addOp(that.__image.scale, [wRatio, hRatio, inter].filter(undefinedFilter)); }); return this; @@ -90,7 +89,6 @@ Batch.prototype.resize = function() { var that = this; judges.resize(arguments, function(width, height, inter) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); that.__addOp(that.__image.resize, [width, height, inter].filter(undefinedFilter)); }); return this; diff --git a/lib/Image.js b/lib/Image.js index 980f78f8..ad205ba7 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -69,7 +69,6 @@ judges.scale( arguments, function(wRatio, hRatio, inter, callback) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); hRatio = hRatio || wRatio; var width = +wRatio * that.width(), height = +hRatio * that.height(); @@ -91,7 +90,6 @@ judges.resize( arguments, function(width, height, inter, callback) { - if (!defs.interpolations[inter]) throw Error("Unknown interpolation " + inter); height = height || width; that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) { that.__release(); diff --git a/lib/defs.js b/lib/defs.js index f89f3440..e1ecbb7f 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -13,7 +13,7 @@ DEF_CREATE_COLOR: [0, 0, 0, 0] }; - exports.interpolations = { + var interpolations = exports.interpolations = { 'nearest-neighbor': 1, 'moving-average': 2, 'linear': 3, @@ -114,10 +114,10 @@ type: 'p-number', optional: true }, { - name: 'inter', - type: 'string', + name: 'interpolation', + type: 'interpolation', optional: true, - default: defaults.DEF_INTERPOLATION + default: interpolations[defaults.DEF_INTERPOLATION] }, { name: 'callback', type: 'function' @@ -130,10 +130,10 @@ type: 'p-number', optional: true }, { - name: 'inter', - type: 'string', + name: 'interpolation', + type: 'interpolation', optional: true, - default: defaults.DEF_INTERPOLATION + default: interpolations[defaults.DEF_INTERPOLATION] }, { name: 'callback', type: 'function' diff --git a/lib/util.js b/lib/util.js index 471c9281..baf9f335 100644 --- a/lib/util.js +++ b/lib/util.js @@ -4,11 +4,16 @@ decree = require('decree'); decree.register('color', validateColor); + decree.register('interpolation', validateInterpolation); function undefinedFilter(v) { return v !== undefined; } + function validateInterpolation(inter){ + return defs.interpolations.hasOwnProperty(inter) || ([1,2,3,4,5,6].indexOf(inter) !== -1); + } + function validateColor(color) { if (typeof color === 'string') { if (!defs.colors[color]) return false; From 9b3e04147211637188266dd159d4dc37af4765ac Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 20:14:52 +0800 Subject: [PATCH 15/63] add 'axes' decree custom type this commit makes axes case sensitive --- README.md | 2 +- lib/Batch.js | 2 -- lib/Image.js | 5 ----- lib/defs.js | 2 +- lib/util.js | 8 +++++++- tests/02.operations/107.mirror.js | 18 +++++++++--------- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 886559c4..efe9175f 100644 --- a/README.md +++ b/README.md @@ -334,7 +334,7 @@ Mirror an image along the 'x' axis, 'y' axis or both. `image.mirror(axes, callback)` -0. `axes {String}`: `'x'`, `'y'` or `'xy'`. +0. `axes {String}`: `'x'`, `'y'` or `'xy'` (case sensitive). 0. `callback {Function(err, image)}` #### Flip diff --git a/lib/Batch.js b/lib/Batch.js index 189c79d8..dc61815f 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -175,8 +175,6 @@ Batch.prototype.mirror = function() { var that = this; judges.mirror(arguments, function(axes) { - axes = axes.toLowerCase(); - if (['x', 'y', 'xy', 'yx'].indexOf(axes) === -1) throw Error('Invalid axes'); that.__addOp(that.__image.mirror, [axes].filter(undefinedFilter)); }); return this; diff --git a/lib/Image.js b/lib/Image.js index ad205ba7..dc316259 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -304,17 +304,12 @@ function(axes, callback) { var xaxis = false, yaxis = false; - axes = axes.toLowerCase(); if ('x' === axes) xaxis = true; if ('y' === axes) yaxis = true; if ('xy' === axes || 'yx' === axes) { xaxis = true; yaxis = true; } - if (!(xaxis || yaxis)) { - that.__release(); - throw Error('Invalid axes'); - } that.__lwip.mirror(xaxis, yaxis, function(err) { that.__release(); callback(err, that); diff --git a/lib/defs.js b/lib/defs.js index e1ecbb7f..92d4efd7 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -232,7 +232,7 @@ }], mirror: [{ name: 'axes', - type: 'string' + type: 'axes' }, { name: 'callback', type: 'function' diff --git a/lib/util.js b/lib/util.js index baf9f335..5302835a 100644 --- a/lib/util.js +++ b/lib/util.js @@ -5,13 +5,19 @@ decree.register('color', validateColor); decree.register('interpolation', validateInterpolation); + decree.register('axes', validateAxes); function undefinedFilter(v) { return v !== undefined; } function validateInterpolation(inter){ - return defs.interpolations.hasOwnProperty(inter) || ([1,2,3,4,5,6].indexOf(inter) !== -1); + return defs.interpolations.hasOwnProperty(inter) || + [1, 2, 3, 4, 5, 6].indexOf(inter) !== -1; + } + + function validateAxes(axes){ + return ['x', 'y', 'xy', 'yx'].indexOf(axes) !== -1; } function validateColor(color) { diff --git a/tests/02.operations/107.mirror.js b/tests/02.operations/107.mirror.js index d0a7ac2e..0c95e3df 100644 --- a/tests/02.operations/107.mirror.js +++ b/tests/02.operations/107.mirror.js @@ -43,10 +43,10 @@ describe('lwip.mirror (/flip)', function() { }); }); - describe('Y', function() { + describe('y', function() { it('should succeed', function(done) { - current.push('Y'); - image.flip('Y', done); + current.push('y'); + image.flip('y', done); }); }); @@ -58,17 +58,17 @@ describe('lwip.mirror (/flip)', function() { current = [basename, 'axes']; }); - describe('Xy', function() { + describe('xy', function() { it('should succeed', function(done) { - current.push('Xy'); - image.flip('Xy', done); + current.push('xy'); + image.flip('xy', done); }); }); - describe('YX', function() { + describe('yx', function() { it('should succeed', function(done) { - current.push('YX'); - image.mirror('YX', done); + current.push('yx'); + image.mirror('yx', done); }); }); From e092fbcc40447e686fa8dc94864c562d5874cd7e Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 23:43:17 +0800 Subject: [PATCH 16/63] add 'image' decree custom type --- lib/Batch.js | 245 ------------------ lib/BatchPrototypeInit.js | 247 ++++++++++++++++++ lib/Image.js | 523 +------------------------------------ lib/ImagePrototypeInit.js | 525 ++++++++++++++++++++++++++++++++++++++ lib/defs.js | 2 +- lib/util.js | 8 +- 6 files changed, 781 insertions(+), 769 deletions(-) create mode 100644 lib/BatchPrototypeInit.js create mode 100644 lib/ImagePrototypeInit.js diff --git a/lib/Batch.js b/lib/Batch.js index dc61815f..6ff8afd8 100644 --- a/lib/Batch.js +++ b/lib/Batch.js @@ -1,48 +1,5 @@ (function(undefined) { - var path = require('path'), - fs = require('fs'), - async = require('async'), - decree = require('decree'), - defs = require('./defs'), - util = require('./util'), - Image = require('./Image'); - - var judges = { - // slice(0,-1) cuts the callback declaration, which is not needed in - // batch mode. - scale: decree(defs.args.scale.slice(0, -1)), - resize: decree(defs.args.resize.slice(0, -1)), - rotate: decree(defs.args.rotate.slice(0, -1)), - blur: decree(defs.args.blur.slice(0, -1)), - hslaAdjust: decree(defs.args.hslaAdjust.slice(0, -1)), - saturate: decree(defs.args.saturate.slice(0, -1)), - lighten: decree(defs.args.lighten.slice(0, -1)), - darken: decree(defs.args.darken.slice(0, -1)), - fade: decree(defs.args.fade.slice(0, -1)), - opacify: decree(defs.args.opacify.slice(0, -1)), - hue: decree(defs.args.hue.slice(0, -1)), - crop: decree(defs.args.crop.slice(0, -1)), - mirror: decree(defs.args.mirror.slice(0, -1)), - pad: decree(defs.args.pad.slice(0, -1)), - border: decree(defs.args.border.slice(0, -1)), - sharpen: decree(defs.args.sharpen.slice(0, -1)), - paste: decree(defs.args.paste.slice(0, -1)), - clone: decree(defs.args.clone.slice(0, -1)), - extract: decree(defs.args.extract.slice(0, -1)), - exec: decree(defs.args.exec), - toBuffer: decree(defs.args.toBuffer), - writeFile: decree(defs.args.writeFile) - }; - - var undefinedFilter = util.undefinedFilter, - normalizeColor = util.normalizeColor; - - // Extend Image with image.batch() - Image.prototype.batch = function() { - return new Batch(this); - }; - function Batch(image) { this.__image = image; this.__queue = []; @@ -55,208 +12,6 @@ }; } - Batch.prototype.exec = function() { - var that = this; - judges.exec(arguments, function(callback) { - if (that.__running) throw Error("Batch is already running"); - that.__running = true; - async.eachSeries(that.__queue, function(op, done) { - op.args.push(done); - // if an exception is thrown here, it should be caught (because we - // are in the middle of async process) and translated to an 'err' - // parameter. - try { - op.handle.apply(that.__image, op.args); - } catch (e) { - done(e); - } - }, function(err) { - that.__queue.length = 0; // queue is now empty - that.__running = false; - callback(err, that.__image); - }); - }); - }; - - Batch.prototype.scale = function() { - var that = this; - judges.scale(arguments, function(wRatio, hRatio, inter) { - that.__addOp(that.__image.scale, [wRatio, hRatio, inter].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.resize = function() { - var that = this; - judges.resize(arguments, function(width, height, inter) { - that.__addOp(that.__image.resize, [width, height, inter].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.rotate = function() { - var that = this; - judges.rotate(arguments, function(degs, color) { - color = normalizeColor(color); - that.__addOp(that.__image.rotate, [degs, color].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.blur = function() { - var that = this; - judges.blur(arguments, function(sigma) { - that.__addOp(that.__image.blur, [sigma].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.hslaAdjust = function() { - var that = this; - judges.hslaAdjust(arguments, function(hs, sd, ld, ad) { - that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, ad].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.saturate = function() { - var that = this; - judges.saturate(arguments, function(delta) { - that.__addOp(that.__image.saturate, [delta].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.lighten = function() { - var that = this; - judges.lighten(arguments, function(delta) { - that.__addOp(that.__image.lighten, [delta].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.darken = function() { - var that = this; - judges.darken(arguments, function(delta) { - that.__addOp(that.__image.darken, [delta].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.fade = function() { - var that = this; - judges.fade(arguments, function(delta) { - that.__addOp(that.__image.fade, [delta].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.opacify = function() { - this.__addOp(this.__image.opacify, []); - return this; - }; - - Batch.prototype.hue = function() { - var that = this; - judges.hue(arguments, function(shift) { - that.__addOp(that.__image.hue, [shift].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.crop = function() { - var that = this; - judges.crop(arguments, function(left, top, right, bottom) { - that.__addOp(that.__image.crop, [left, top, right, bottom].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.mirror = function() { - var that = this; - judges.mirror(arguments, function(axes) { - that.__addOp(that.__image.mirror, [axes].filter(undefinedFilter)); - }); - return this; - }; - - // mirror alias: - Batch.prototype.flip = Batch.prototype.mirror; - - Batch.prototype.pad = function() { - var that = this; - judges.pad(arguments, function(left, top, right, bottom, color) { - color = normalizeColor(color); - that.__addOp(that.__image.pad, [left, top, right, bottom, color].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.border = function() { - var that = this; - judges.border(arguments, function(width, color) { - color = normalizeColor(color); - that.__addOp(that.__image.border, [width, color].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.sharpen = function() { - var that = this; - judges.sharpen(arguments, function(amplitude) { - that.__addOp(that.__image.sharpen, [amplitude].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.paste = function() { - var that = this; - judges.paste(arguments, function(left, top, img) { - if (!(img instanceof Image)) - throw Error("Pasted image is not a valid Image object"); - that.__addOp(that.__image.paste, [left, top, img].filter(undefinedFilter)); - }); - return this; - }; - - Batch.prototype.toBuffer = function() { - var that = this; - judges.toBuffer(arguments, function(type, params, callback) { - if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); - } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (['none', 'fast', 'high'].indexOf(params.compression) === -1) - throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') - throw Error('PNG \'transparency\' must be boolean or \'auto\''); - } else throw Error('Unknown type \'' + type + '\''); - that.exec(function(err, image) { - if (err) return callback(err); - image.toBuffer(type, params, callback); - }); - }); - }; - - Batch.prototype.writeFile = function(outpath, type, params, callback) { - var that = this; - judges.writeFile(arguments, function(outpath, type, params, callback) { - type = type || path.extname(outpath).slice(1).toLowerCase(); - that.toBuffer(type, params, function(err, buffer) { - if (err) return callback(err); - fs.writeFile(outpath, buffer, { - encoding: 'binary' - }, callback); - }); - }); - }; - // EXPORTS // ------- module.exports = Batch; diff --git a/lib/BatchPrototypeInit.js b/lib/BatchPrototypeInit.js new file mode 100644 index 00000000..7df163f9 --- /dev/null +++ b/lib/BatchPrototypeInit.js @@ -0,0 +1,247 @@ +(function(undefined) { + + var path = require('path'), + fs = require('fs'), + async = require('async'), + decree = require('decree'), + defs = require('./defs'), + util = require('./util'), + Batch = require('./Batch'), + Image = require('./Image'); + + var judges = { + // slice(0,-1) cuts the callback declaration, which is not needed in + // batch mode. + scale: decree(defs.args.scale.slice(0, -1)), + resize: decree(defs.args.resize.slice(0, -1)), + rotate: decree(defs.args.rotate.slice(0, -1)), + blur: decree(defs.args.blur.slice(0, -1)), + hslaAdjust: decree(defs.args.hslaAdjust.slice(0, -1)), + saturate: decree(defs.args.saturate.slice(0, -1)), + lighten: decree(defs.args.lighten.slice(0, -1)), + darken: decree(defs.args.darken.slice(0, -1)), + fade: decree(defs.args.fade.slice(0, -1)), + opacify: decree(defs.args.opacify.slice(0, -1)), + hue: decree(defs.args.hue.slice(0, -1)), + crop: decree(defs.args.crop.slice(0, -1)), + mirror: decree(defs.args.mirror.slice(0, -1)), + pad: decree(defs.args.pad.slice(0, -1)), + border: decree(defs.args.border.slice(0, -1)), + sharpen: decree(defs.args.sharpen.slice(0, -1)), + paste: decree(defs.args.paste.slice(0, -1)), + clone: decree(defs.args.clone.slice(0, -1)), + extract: decree(defs.args.extract.slice(0, -1)), + exec: decree(defs.args.exec), + toBuffer: decree(defs.args.toBuffer), + writeFile: decree(defs.args.writeFile) + }; + + var undefinedFilter = util.undefinedFilter, + normalizeColor = util.normalizeColor; + + // Extend Image with image.batch() + Image.prototype.batch = function() { + return new Batch(this); + }; + + Batch.prototype.exec = function() { + var that = this; + judges.exec(arguments, function(callback) { + if (that.__running) throw Error("Batch is already running"); + that.__running = true; + async.eachSeries(that.__queue, function(op, done) { + op.args.push(done); + // if an exception is thrown here, it should be caught (because we + // are in the middle of async process) and translated to an 'err' + // parameter. + try { + op.handle.apply(that.__image, op.args); + } catch (e) { + done(e); + } + }, function(err) { + that.__queue.length = 0; // queue is now empty + that.__running = false; + callback(err, that.__image); + }); + }); + }; + + Batch.prototype.scale = function() { + var that = this; + judges.scale(arguments, function(wRatio, hRatio, inter) { + that.__addOp(that.__image.scale, [wRatio, hRatio, inter].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.resize = function() { + var that = this; + judges.resize(arguments, function(width, height, inter) { + that.__addOp(that.__image.resize, [width, height, inter].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.rotate = function() { + var that = this; + judges.rotate(arguments, function(degs, color) { + color = normalizeColor(color); + that.__addOp(that.__image.rotate, [degs, color].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.blur = function() { + var that = this; + judges.blur(arguments, function(sigma) { + that.__addOp(that.__image.blur, [sigma].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.hslaAdjust = function() { + var that = this; + judges.hslaAdjust(arguments, function(hs, sd, ld, ad) { + that.__addOp(that.__image.hslaAdjust, [hs, sd, ld, ad].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.saturate = function() { + var that = this; + judges.saturate(arguments, function(delta) { + that.__addOp(that.__image.saturate, [delta].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.lighten = function() { + var that = this; + judges.lighten(arguments, function(delta) { + that.__addOp(that.__image.lighten, [delta].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.darken = function() { + var that = this; + judges.darken(arguments, function(delta) { + that.__addOp(that.__image.darken, [delta].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.fade = function() { + var that = this; + judges.fade(arguments, function(delta) { + that.__addOp(that.__image.fade, [delta].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.opacify = function() { + this.__addOp(this.__image.opacify, []); + return this; + }; + + Batch.prototype.hue = function() { + var that = this; + judges.hue(arguments, function(shift) { + that.__addOp(that.__image.hue, [shift].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.crop = function() { + var that = this; + judges.crop(arguments, function(left, top, right, bottom) { + that.__addOp(that.__image.crop, [left, top, right, bottom].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.mirror = function() { + var that = this; + judges.mirror(arguments, function(axes) { + that.__addOp(that.__image.mirror, [axes].filter(undefinedFilter)); + }); + return this; + }; + + // mirror alias: + Batch.prototype.flip = Batch.prototype.mirror; + + Batch.prototype.pad = function() { + var that = this; + judges.pad(arguments, function(left, top, right, bottom, color) { + color = normalizeColor(color); + that.__addOp(that.__image.pad, [left, top, right, bottom, color].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.border = function() { + var that = this; + judges.border(arguments, function(width, color) { + color = normalizeColor(color); + that.__addOp(that.__image.border, [width, color].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.sharpen = function() { + var that = this; + judges.sharpen(arguments, function(amplitude) { + that.__addOp(that.__image.sharpen, [amplitude].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.paste = function() { + var that = this; + judges.paste(arguments, function(left, top, img) { + that.__addOp(that.__image.paste, [left, top, img].filter(undefinedFilter)); + }); + return this; + }; + + Batch.prototype.toBuffer = function() { + var that = this; + judges.toBuffer(arguments, function(type, params, callback) { + if (type === 'jpg' || type === 'jpeg') { + if (params.quality != 0) + params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; + if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) + throw Error('Invalid JPEG quality'); + } else if (type === 'png') { + params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; + if (['none', 'fast', 'high'].indexOf(params.compression) === -1) + throw Error('Invalid PNG compression'); + params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; + if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); + } else throw Error('Unknown type \'' + type + '\''); + that.exec(function(err, image) { + if (err) return callback(err); + image.toBuffer(type, params, callback); + }); + }); + }; + + Batch.prototype.writeFile = function(outpath, type, params, callback) { + var that = this; + judges.writeFile(arguments, function(outpath, type, params, callback) { + type = type || path.extname(outpath).slice(1).toLowerCase(); + that.toBuffer(type, params, function(err, buffer) { + if (err) return callback(err); + fs.writeFile(outpath, buffer, { + encoding: 'binary' + }, callback); + }); + }); + }; + +})(void 0); diff --git a/lib/Image.js b/lib/Image.js index dc316259..74083061 100644 --- a/lib/Image.js +++ b/lib/Image.js @@ -1,37 +1,6 @@ (function(undefined) { - var path = require('path'), - fs = require('fs'), - decree = require('decree'), - defs = require('./defs'), - util = require('./util'), - encoder = require('../build/Release/lwip_encoder'), - lwip_image = require('../build/Release/lwip_image'), - normalizeColor = util.normalizeColor; - - var judges = { - scale: decree(defs.args.scale), - resize: decree(defs.args.resize), - rotate: decree(defs.args.rotate), - blur: decree(defs.args.blur), - hslaAdjust: decree(defs.args.hslaAdjust), - saturate: decree(defs.args.saturate), - lighten: decree(defs.args.lighten), - darken: decree(defs.args.darken), - fade: decree(defs.args.fade), - opacify: decree(defs.args.opacify), - hue: decree(defs.args.hue), - crop: decree(defs.args.crop), - mirror: decree(defs.args.mirror), - pad: decree(defs.args.pad), - border: decree(defs.args.border), - sharpen: decree(defs.args.sharpen), - paste: decree(defs.args.paste), - clone: decree(defs.args.clone), - extract: decree(defs.args.extract), - toBuffer: decree(defs.args.toBuffer), - writeFile: decree(defs.args.writeFile) - }; + var lwip_image = require('../build/Release/lwip_image'); function Image(pixelsBuf, width, height, trans) { this.__lwip = new lwip_image.LwipImage(pixelsBuf, width, height); @@ -39,496 +8,6 @@ this.__trans = trans; } - Image.prototype.__lock = function() { - if (!this.__locked) this.__locked = true; - else throw Error("Another image operation already in progress"); - }; - - Image.prototype.__release = function() { - this.__locked = false; - }; - - Image.prototype.width = function() { - return this.__lwip.width(); - }; - - Image.prototype.height = function() { - return this.__lwip.height(); - }; - - Image.prototype.size = function() { - return { - width: this.__lwip.width(), - height: this.__lwip.height() - }; - }; - - Image.prototype.scale = function() { - this.__lock(); - var that = this; - judges.scale( - arguments, - function(wRatio, hRatio, inter, callback) { - hRatio = hRatio || wRatio; - var width = +wRatio * that.width(), - height = +hRatio * that.height(); - that.__lwip.resize(width, height, defs.interpolations[inter], function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.resize = function() { - this.__lock(); - var that = this; - judges.resize( - arguments, - function(width, height, inter, callback) { - height = height || width; - that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.rotate = function() { - this.__lock(); - var that = this; - judges.rotate( - arguments, - function(degs, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.blur = function() { - this.__lock(); - var that = this; - judges.blur( - arguments, - function(sigma, callback) { - that.__lwip.blur(+sigma, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.hslaAdjust = function() { - this.__lock(); - var that = this; - judges.hslaAdjust( - arguments, - function(hs, sd, ld, ad, callback) { - that.__lwip.hslaAdj(+hs, +sd, +ld, +ad, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.saturate = function() { - this.__lock(); - var that = this; - judges.saturate( - arguments, - function(delta, callback) { - that.__lwip.hslaAdj(0, +delta, 0, 0, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.lighten = function() { - this.__lock(); - var that = this; - judges.lighten( - arguments, - function(delta, callback) { - that.__lwip.hslaAdj(0, 0, +delta, 0, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.darken = function() { - this.__lock(); - var that = this; - judges.darken( - arguments, - function(delta, callback) { - that.__lwip.hslaAdj(0, 0, -delta, 0, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.fade = function() { - this.__lock(); - var that = this; - judges.fade( - arguments, - function(delta, callback) { - that.__lwip.hslaAdj(0, 0, 0, -delta, function(err) { - if (+delta > 0) that.__trans = true; - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.opacify = function() { - this.__lock(); - var that = this; - judges.opacify( - arguments, - function(callback) { - that.__lwip.opacify(function(err) { - that.__trans = false; - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.hue = function() { - this.__lock(); - var that = this; - judges.hue( - arguments, - function(shift, callback) { - that.__lwip.hslaAdj(+shift, 0, 0, 0, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.crop = function() { - this.__lock(); - var that = this; - judges.crop( - arguments, - function(left, top, right, bottom, callback) { - if (!right && !bottom) { - var size = that.size(), - width = left, - height = top; - left = 0 | (size.width - width) / 2; - top = 0 | (size.height - height) / 2; - right = left + width - 1; - bottom = top + height - 1; - } - that.__lwip.crop(left, top, right, bottom, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.mirror = function() { - this.__lock(); - var that = this; - judges.mirror( - arguments, - function(axes, callback) { - var xaxis = false, - yaxis = false; - if ('x' === axes) xaxis = true; - if ('y' === axes) yaxis = true; - if ('xy' === axes || 'yx' === axes) { - xaxis = true; - yaxis = true; - } - that.__lwip.mirror(xaxis, yaxis, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - // mirror alias: - Image.prototype.flip = Image.prototype.mirror; - - Image.prototype.pad = function() { - this.__lock(); - var that = this; - judges.pad( - arguments, - function(left, top, right, bottom, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.border = function() { - this.__lock(); - var that = this; - judges.border( - arguments, - function(width, color, callback) { - color = normalizeColor(color); - if (color.a < 100) that.__trans = true; - // we can just use image.pad... - that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.sharpen = function() { - this.__lock(); - var that = this; - judges.sharpen( - arguments, - function(amplitude, callback) { - that.__lwip.sharpen(+amplitude, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.paste = function() { - this.__lock(); - var that = this; - judges.paste( - arguments, - function(left, top, img, callback) { - if (!(img instanceof Image)) - throw Error("Pasted image is not a valid Image object"); - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the pasted image doesn't have a chance - // to be changed - var pixbuff = img.__lwip.buffer(), - width = img.__lwip.width(), - height = img.__lwip.height(); - if (left + width > that.__lwip.width() || top + height > that.__lwip.height()) - throw Error("Pasted image exceeds dimensions of base image"); - that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.clone = function() { - // no need to lock the image. we don't modify the memory buffer. - // just copy it. - var that = this; - judges.clone( - arguments, - function(callback) { - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the original image doesn't have a chance - // to be changed (remember, we don't lock it); and only then call - // the callback asynchronously. - var pixbuff = that.__lwip.buffer(), - width = that.__lwip.width(), - height = that.__lwip.height(), - trans = that.__trans; - setImmediate(function() { - callback(null, new Image(pixbuff, width, height, trans)); - }); - } - ); - }; - - Image.prototype.extract = function() { - // no need to lock the image. we don't modify the memory buffer. - // just copy it and then crop it. - var that = this; - judges.extract( - arguments, - function(left, top, right, bottom, callback) { - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the original image doesn't have a chance - // to be changed (remember, we don't lock it); then we crop it and - // only call the callback asynchronously. - var pixbuff = that.__lwip.buffer(), - width = that.__lwip.width(), - height = that.__lwip.height(), - trans = that.__trans, - eximg = new Image(pixbuff, width, height, trans); - eximg.__lwip.crop(left, top, right, bottom, function(err) { - callback(err, eximg); - }); - } - ); - }; - - Image.prototype.toBuffer = function() { - this.__lock(); - var that = this; - judges.toBuffer( - arguments, - function(type, params, callback) { - if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); - return encoder.jpeg( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.quality, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (params.compression === 'none') params.compression = 0; - else if (params.compression === 'fast') params.compression = 1; - else if (params.compression === 'high') params.compression = 2; - else throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean'){ - if (typeof params.transparency === 'string' && params.transparency.toLowerCase() === 'auto') - params.transparency = that.__trans; - else throw Error('PNG \'transparency\' must be boolean or \'auto\''); - } - return encoder.png( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.compression, - params.interlaced, - params.transparency, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else throw Error('Unknown type \'' + type + '\''); - }, - function(err) { - that.__release(); - throw err; - } - ); - }; - - Image.prototype.writeFile = function() { - var that = this; - judges.writeFile( - arguments, - function(outpath, type, params, callback) { - type = type || path.extname(outpath).slice(1).toLowerCase(); - that.toBuffer(type, params, function(err, buffer) { - if (err) return callback(err); - fs.writeFile(outpath, buffer, { - encoding: 'binary' - }, callback); - }); - } - ); - }; - // EXPORTS // ------- module.exports = Image; diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js new file mode 100644 index 00000000..3dbcea0f --- /dev/null +++ b/lib/ImagePrototypeInit.js @@ -0,0 +1,525 @@ +(function(undefined) { + + var Image = require('./Image'), + path = require('path'), + fs = require('fs'), + decree = require('decree'), + defs = require('./defs'), + util = require('./util'), + encoder = require('../build/Release/lwip_encoder'), + lwip_image = require('../build/Release/lwip_image'), + normalizeColor = util.normalizeColor; + + var judges = { + scale: decree(defs.args.scale), + resize: decree(defs.args.resize), + rotate: decree(defs.args.rotate), + blur: decree(defs.args.blur), + hslaAdjust: decree(defs.args.hslaAdjust), + saturate: decree(defs.args.saturate), + lighten: decree(defs.args.lighten), + darken: decree(defs.args.darken), + fade: decree(defs.args.fade), + opacify: decree(defs.args.opacify), + hue: decree(defs.args.hue), + crop: decree(defs.args.crop), + mirror: decree(defs.args.mirror), + pad: decree(defs.args.pad), + border: decree(defs.args.border), + sharpen: decree(defs.args.sharpen), + paste: decree(defs.args.paste), + clone: decree(defs.args.clone), + extract: decree(defs.args.extract), + toBuffer: decree(defs.args.toBuffer), + writeFile: decree(defs.args.writeFile) + }; + + Image.prototype.__lock = function() { + if (!this.__locked) this.__locked = true; + else throw Error("Another image operation already in progress"); + }; + + Image.prototype.__release = function() { + this.__locked = false; + }; + + Image.prototype.width = function() { + return this.__lwip.width(); + }; + + Image.prototype.height = function() { + return this.__lwip.height(); + }; + + Image.prototype.size = function() { + return { + width: this.__lwip.width(), + height: this.__lwip.height() + }; + }; + + Image.prototype.scale = function() { + this.__lock(); + var that = this; + judges.scale( + arguments, + function(wRatio, hRatio, inter, callback) { + hRatio = hRatio || wRatio; + var width = +wRatio * that.width(), + height = +hRatio * that.height(); + that.__lwip.resize(width, height, defs.interpolations[inter], function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.resize = function() { + this.__lock(); + var that = this; + judges.resize( + arguments, + function(width, height, inter, callback) { + height = height || width; + that.__lwip.resize(+width, +height, defs.interpolations[inter], function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.rotate = function() { + this.__lock(); + var that = this; + judges.rotate( + arguments, + function(degs, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.rotate(+degs, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.blur = function() { + this.__lock(); + var that = this; + judges.blur( + arguments, + function(sigma, callback) { + that.__lwip.blur(+sigma, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.hslaAdjust = function() { + this.__lock(); + var that = this; + judges.hslaAdjust( + arguments, + function(hs, sd, ld, ad, callback) { + that.__lwip.hslaAdj(+hs, +sd, +ld, +ad, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.saturate = function() { + this.__lock(); + var that = this; + judges.saturate( + arguments, + function(delta, callback) { + that.__lwip.hslaAdj(0, +delta, 0, 0, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.lighten = function() { + this.__lock(); + var that = this; + judges.lighten( + arguments, + function(delta, callback) { + that.__lwip.hslaAdj(0, 0, +delta, 0, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.darken = function() { + this.__lock(); + var that = this; + judges.darken( + arguments, + function(delta, callback) { + that.__lwip.hslaAdj(0, 0, -delta, 0, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.fade = function() { + this.__lock(); + var that = this; + judges.fade( + arguments, + function(delta, callback) { + that.__lwip.hslaAdj(0, 0, 0, -delta, function(err) { + if (+delta > 0) that.__trans = true; + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.opacify = function() { + this.__lock(); + var that = this; + judges.opacify( + arguments, + function(callback) { + that.__lwip.opacify(function(err) { + that.__trans = false; + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.hue = function() { + this.__lock(); + var that = this; + judges.hue( + arguments, + function(shift, callback) { + that.__lwip.hslaAdj(+shift, 0, 0, 0, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.crop = function() { + this.__lock(); + var that = this; + judges.crop( + arguments, + function(left, top, right, bottom, callback) { + if (!right && !bottom) { + var size = that.size(), + width = left, + height = top; + left = 0 | (size.width - width) / 2; + top = 0 | (size.height - height) / 2; + right = left + width - 1; + bottom = top + height - 1; + } + that.__lwip.crop(left, top, right, bottom, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.mirror = function() { + this.__lock(); + var that = this; + judges.mirror( + arguments, + function(axes, callback) { + var xaxis = false, + yaxis = false; + if ('x' === axes) xaxis = true; + if ('y' === axes) yaxis = true; + if ('xy' === axes || 'yx' === axes) { + xaxis = true; + yaxis = true; + } + that.__lwip.mirror(xaxis, yaxis, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + // mirror alias: + Image.prototype.flip = Image.prototype.mirror; + + Image.prototype.pad = function() { + this.__lock(); + var that = this; + judges.pad( + arguments, + function(left, top, right, bottom, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + that.__lwip.pad(+left, +top, +right, +bottom, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.border = function() { + this.__lock(); + var that = this; + judges.border( + arguments, + function(width, color, callback) { + color = normalizeColor(color); + if (color.a < 100) that.__trans = true; + // we can just use image.pad... + that.__lwip.pad(+width, +width, +width, +width, +color.r, +color.g, +color.b, +color.a, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.sharpen = function() { + this.__lock(); + var that = this; + judges.sharpen( + arguments, + function(amplitude, callback) { + that.__lwip.sharpen(+amplitude, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.paste = function() { + this.__lock(); + var that = this; + judges.paste( + arguments, + function(left, top, img, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the pasted image doesn't have a chance + // to be changed + var pixbuff = img.__lwip.buffer(), + width = img.__lwip.width(), + height = img.__lwip.height(); + if (left + width > that.__lwip.width() || top + height > that.__lwip.height()) + throw Error("Pasted image exceeds dimensions of base image"); + that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) { + that.__release(); + callback(err, that); + }); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.clone = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it. + var that = this; + judges.clone( + arguments, + function(callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); and only then call + // the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans; + setImmediate(function() { + callback(null, new Image(pixbuff, width, height, trans)); + }); + } + ); + }; + + Image.prototype.extract = function() { + // no need to lock the image. we don't modify the memory buffer. + // just copy it and then crop it. + var that = this; + judges.extract( + arguments, + function(left, top, right, bottom, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the original image doesn't have a chance + // to be changed (remember, we don't lock it); then we crop it and + // only call the callback asynchronously. + var pixbuff = that.__lwip.buffer(), + width = that.__lwip.width(), + height = that.__lwip.height(), + trans = that.__trans, + eximg = new Image(pixbuff, width, height, trans); + eximg.__lwip.crop(left, top, right, bottom, function(err) { + callback(err, eximg); + }); + } + ); + }; + + Image.prototype.toBuffer = function() { + this.__lock(); + var that = this; + judges.toBuffer( + arguments, + function(type, params, callback) { + if (type === 'jpg' || type === 'jpeg') { + if (params.quality != 0) + params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; + if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) + throw Error('Invalid JPEG quality'); + return encoder.jpeg( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.quality, + function(err, buffer) { + that.__release(); + callback(err, buffer); + } + ); + } else if (type === 'png') { + params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; + if (params.compression === 'none') params.compression = 0; + else if (params.compression === 'fast') params.compression = 1; + else if (params.compression === 'high') params.compression = 2; + else throw Error('Invalid PNG compression'); + params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; + if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean'){ + if (typeof params.transparency === 'string' && params.transparency.toLowerCase() === 'auto') + params.transparency = that.__trans; + else throw Error('PNG \'transparency\' must be boolean or \'auto\''); + } + return encoder.png( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.compression, + params.interlaced, + params.transparency, + function(err, buffer) { + that.__release(); + callback(err, buffer); + } + ); + } else throw Error('Unknown type \'' + type + '\''); + }, + function(err) { + that.__release(); + throw err; + } + ); + }; + + Image.prototype.writeFile = function() { + var that = this; + judges.writeFile( + arguments, + function(outpath, type, params, callback) { + type = type || path.extname(outpath).slice(1).toLowerCase(); + that.toBuffer(type, params, function(err, buffer) { + if (err) return callback(err); + fs.writeFile(outpath, buffer, { + encoding: 'binary' + }, callback); + }); + } + ); + }; + +})(void 0); diff --git a/lib/defs.js b/lib/defs.js index 92d4efd7..118e6783 100644 --- a/lib/defs.js +++ b/lib/defs.js @@ -305,7 +305,7 @@ type: 'nn-number' }, { name: 'image', - type: '*' + type: 'image' }, { name: 'callback', type: 'function' diff --git a/lib/util.js b/lib/util.js index 5302835a..7201213f 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,11 +1,13 @@ (function(undefined) { var defs = require('./defs'), - decree = require('decree'); + decree = require('decree'), + Image = require('./Image'); decree.register('color', validateColor); decree.register('interpolation', validateInterpolation); decree.register('axes', validateAxes); + decree.register('image', validateImage); function undefinedFilter(v) { return v !== undefined; @@ -20,6 +22,10 @@ return ['x', 'y', 'xy', 'yx'].indexOf(axes) !== -1; } + function validateImage(img){ + return img instanceof Image; + } + function validateColor(color) { if (typeof color === 'string') { if (!defs.colors[color]) return false; From 14ce65d4be71af47ffc5eef331fcee063fe0db84 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Fri, 28 Nov 2014 23:44:16 +0800 Subject: [PATCH 17/63] init prototypes from index.js --- index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 93ce339a..0e1cfe5b 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,3 @@ -require('./lib/Batch'); // Extend Image object with .batch() function +require('./lib/ImagePrototypeInit'); +require('./lib/BatchPrototypeInit'); module.exports = require('./lib/obtain'); From f1e9b7d3670e818873ccd362813d1a0e6833bfcd Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 01:21:55 +0800 Subject: [PATCH 18/63] add png and jpg params validators --- lib/BatchPrototypeInit.js | 14 +---- lib/ImagePrototypeInit.js | 128 +++++++++++++++++--------------------- lib/util.js | 30 ++++++++- 3 files changed, 88 insertions(+), 84 deletions(-) diff --git a/lib/BatchPrototypeInit.js b/lib/BatchPrototypeInit.js index 7df163f9..6d14e5c9 100644 --- a/lib/BatchPrototypeInit.js +++ b/lib/BatchPrototypeInit.js @@ -210,19 +210,9 @@ var that = this; judges.toBuffer(arguments, function(type, params, callback) { if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); + util.normalizeJpegParams(params); } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (['none', 'fast', 'high'].indexOf(params.compression) === -1) - throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean' && params.transparency.toLowerCase() !== 'auto') - throw Error('PNG \'transparency\' must be boolean or \'auto\''); + util.normalizePngParams(params); } else throw Error('Unknown type \'' + type + '\''); that.exec(function(err, image) { if (err) return callback(err); diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js index 3dbcea0f..4bfcde79 100644 --- a/lib/ImagePrototypeInit.js +++ b/lib/ImagePrototypeInit.js @@ -382,27 +382,28 @@ Image.prototype.paste = function() { this.__lock(); var that = this; - judges.paste( - arguments, - function(left, top, img, callback) { - // first we retrieve what we need (buffer, dimensions, ...) - // synchronously so that the pasted image doesn't have a chance - // to be changed - var pixbuff = img.__lwip.buffer(), - width = img.__lwip.width(), - height = img.__lwip.height(); - if (left + width > that.__lwip.width() || top + height > that.__lwip.height()) - throw Error("Pasted image exceeds dimensions of base image"); - that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) { - that.__release(); - callback(err, that); - }); - }, - function(err) { - that.__release(); - throw err; - } - ); + try{ + judges.paste( + arguments, + function(left, top, img, callback) { + // first we retrieve what we need (buffer, dimensions, ...) + // synchronously so that the pasted image doesn't have a chance + // to be changed + var pixbuff = img.__lwip.buffer(), + width = img.__lwip.width(), + height = img.__lwip.height(); + if (left + width > that.__lwip.width() || top + height > that.__lwip.height()) + throw Error("Pasted image exceeds dimensions of base image"); + that.__lwip.paste(+left, +top, pixbuff, +width, +height, function(err) { + that.__release(); + callback(err, that); + }); + } + ); + } catch(err){ + that.__release(); + throw err; + } }; Image.prototype.clone = function() { @@ -453,57 +454,42 @@ Image.prototype.toBuffer = function() { this.__lock(); var that = this; - judges.toBuffer( - arguments, - function(type, params, callback) { - if (type === 'jpg' || type === 'jpeg') { - if (params.quality != 0) - params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; - if (params.quality != parseInt(params.quality) || params.quality < 0 || params.quality > 100) - throw Error('Invalid JPEG quality'); - return encoder.jpeg( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.quality, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else if (type === 'png') { - params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; - if (params.compression === 'none') params.compression = 0; - else if (params.compression === 'fast') params.compression = 1; - else if (params.compression === 'high') params.compression = 2; - else throw Error('Invalid PNG compression'); - params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; - if (typeof params.interlaced !== 'boolean') throw Error('PNG \'interlaced\' must be boolean'); - params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; - if (typeof params.transparency !== 'boolean'){ - if (typeof params.transparency === 'string' && params.transparency.toLowerCase() === 'auto') - params.transparency = that.__trans; - else throw Error('PNG \'transparency\' must be boolean or \'auto\''); + try{ + judges.toBuffer( + arguments, + function(type, params, callback) { + if (type === 'jpg' || type === 'jpeg') { + util.normalizeJpegParams(params); + return encoder.jpeg( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.quality, + encoderCb + ); + } else if (type === 'png') { + util.normalizePngParams(params); + return encoder.png( + that.__lwip.buffer(), + that.__lwip.width(), + that.__lwip.height(), + params.compression, + params.interlaced, + params.transparency === 'auto' ? that.__trans : params.transparency, + encoderCb + ); + } else throw Error('Unknown type \'' + type + '\''); + + function encoderCb(err, buffer) { + that.__release(); + callback(err, buffer); } - return encoder.png( - that.__lwip.buffer(), - that.__lwip.width(), - that.__lwip.height(), - params.compression, - params.interlaced, - params.transparency, - function(err, buffer) { - that.__release(); - callback(err, buffer); - } - ); - } else throw Error('Unknown type \'' + type + '\''); - }, - function(err) { - that.__release(); - throw err; - } - ); + } + ); + } catch (err){ + that.__release(); + throw err; + } }; Image.prototype.writeFile = function() { diff --git a/lib/util.js b/lib/util.js index 7201213f..3424e211 100644 --- a/lib/util.js +++ b/lib/util.js @@ -64,9 +64,37 @@ return color; } + function normalizePngParams(params){ + params.compression = params.compression || defs.defaults.PNG_DEF_COMPRESSION; + if (params.compression === 'none') params.compression = 0; + else if (params.compression === 'fast') params.compression = 1; + else if (params.compression === 'high') params.compression = 2; + else if ([0, 1, 2].indexOf(params.compression) === -1) + throw Error('Invalid PNG compression'); + params.interlaced = params.interlaced || defs.defaults.PNG_DEF_INTERLACED; + if (typeof params.interlaced !== 'boolean') + throw Error('PNG \'interlaced\' must be boolean'); + params.transparency = params.transparency || defs.defaults.PNG_DEF_TRANSPARENT; + if (typeof params.transparency !== 'boolean'){ + if (typeof params.transparency !== 'string' || + params.transparency.toLowerCase() !== 'auto') + throw Error('PNG \'transparency\' must be boolean or \'auto\''); + } + } + + function normalizeJpegParams(params){ + if (params.quality !== 0) + params.quality = params.quality || defs.defaults.DEF_JPEG_QUALITY; + if (params.quality != parseInt(params.quality) || + params.quality < 0 || params.quality > 100) + throw Error('Invalid JPEG quality'); + } + module.exports = { undefinedFilter: undefinedFilter, - normalizeColor: normalizeColor + normalizeColor: normalizeColor, + normalizePngParams: normalizePngParams, + normalizeJpegParams: normalizeJpegParams }; })(void 0); From 076e5ff5514bb388365aff5d700300b91af68691 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 10:28:20 +0800 Subject: [PATCH 19/63] fix previous merge 'getPixel' was accidentally removed --- lib/ImagePrototypeInit.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/ImagePrototypeInit.js b/lib/ImagePrototypeInit.js index 4bfcde79..93ca46b2 100644 --- a/lib/ImagePrototypeInit.js +++ b/lib/ImagePrototypeInit.js @@ -31,6 +31,7 @@ clone: decree(defs.args.clone), extract: decree(defs.args.extract), toBuffer: decree(defs.args.toBuffer), + getPixel: decree(defs.args.getPixel), writeFile: decree(defs.args.writeFile) }; @@ -58,6 +59,24 @@ }; }; + Image.prototype.getPixel = function() { + var args = judges.getPixel(arguments), + left = args[0], + top = args[1]; + + if (left >= this.width() || top >= this.height()) + throw Error("Coordinates exceed dimensions of image"); + + var rgba = this.__lwip.getPixel(left, top); + + return { + r: rgba[0], + g: rgba[1], + b: rgba[2], + a: rgba[3] + }; + }; + Image.prototype.scale = function() { this.__lock(); var that = this; From 986d47117e249bf58e454e53dcbecf3d5410baef Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 15:39:21 +0800 Subject: [PATCH 20/63] fix setPixel args validation test --- tests/00.argsValidation/210.batch.setPixel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/00.argsValidation/210.batch.setPixel.js b/tests/00.argsValidation/210.batch.setPixel.js index ed85483c..3eaa1660 100644 --- a/tests/00.argsValidation/210.batch.setPixel.js +++ b/tests/00.argsValidation/210.batch.setPixel.js @@ -30,8 +30,8 @@ describe('batch.setPixel arguments validation', function() { describe('before exec', function() { it('should not return an error', function(done) { - batch.setPixel.bind(batch, 999, 999, 'yellow').should.not.throwError(); batch.resize(1000, 1000); + batch.setPixel.bind(batch, 999, 999, 'yellow').should.not.throwError(); batch.exec(function(err) { // there should not be an error message assert(!err); From 8615f1f5d6adf8a9db69c61924dd8ebfe0ef329a Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 15:57:42 +0800 Subject: [PATCH 21/63] remove 'setPixel' from random batch test generator ...as it may sometimes fail due to our of bounds coordinates when image is randomly resized (for example). --- tests/utils.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/utils.js b/tests/utils.js index ad5bd401..2f9b44f0 100644 --- a/tests/utils.js +++ b/tests/utils.js @@ -6,7 +6,7 @@ module.exports = { function generateRandomBatch(batch, n) { var ops = []; for (var i = 0; i < n; i++) { - var r = Math.floor(Math.random() * 15); + var r = Math.floor(Math.random() * 14); switch (r) { case 0: var sd = Math.floor(Math.random() * 20); @@ -81,12 +81,6 @@ function generateRandomBatch(batch, n) { batch = batch.opacify(); ops.push('opc'); break; - case 14: - var left = Math.floor(Math.random() * 100), - top = Math.floor(Math.random() * 100); - batch = batch.setPixel(left, top, getRandomColor()); - ops.push('stpx'); - break; } } return ops; From 03d1782cdca7b4ea9c2fdb9bf4a85e52b39ba7de Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 17:08:03 +0800 Subject: [PATCH 22/63] adds readme docs and an example for setPixel --- README.md | 19 +++++++++++ examples/set_pixel.js | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 examples/set_pixel.js diff --git a/README.md b/README.md index 432b2e65..31c50f99 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ 0. [Fade (adjust transparency)](#fade) 0. [Opacify](#opacify) 0. [Paste](#paste) + 0. [Set pixel](#set-pixel) 0. [Getters](#getters) 0. [Width](#width) 0. [Height](#height) @@ -468,6 +469,24 @@ Paste an image on top of this image. 0. Extra caution is required when using this method in batch mode, as the images may change by the time this operation is called. +#### Set Pixel + +Set the color of a pixel. + +`image.setPixel(left, top, color, callback)` + +0. `left, top {Integer}`: Coordinates of the pixel from the left-top corner of + the image. +0. `color {String / Array / Object}`: Color of the pixel to set. + See [colors specification](#colors-specification). +0. `callback {Function(err, image)}` + +**Notes:** + +0. If the coordinates exceed the bounds of the image, an exception is thrown. +0. Extra caution is required when using this method in batch mode, as the + dimensions of the image may change by the time this operation is called. + ### Getters #### Width diff --git a/examples/set_pixel.js b/examples/set_pixel.js new file mode 100644 index 00000000..fddb7c99 --- /dev/null +++ b/examples/set_pixel.js @@ -0,0 +1,73 @@ +/** + * Example for using LWIP to manually setting pixels in an image. + * + * Creates a canvas with a rainbow gradient. + */ + +var path = require('path'), + lwip = require('../'); + +var output = 'rainbow.png', + width = 360, + height = 360, + color = {h: 0, s: 100, l: 50}; + +function nextColor(){ + color.h++; + if (color.h > 360) color.h = 0; + return hslToRgb(color); +} + +// create an empty image canvas +lwip.create(width, height, function(err, image){ + if (err) return console.log(err); + + var x, y, c, + batch = image.batch(); + + // set the same color for each columns in the image + for (x = 0; x < width ; x++){ + c = nextColor(); + for (y = 0; y < height; y++){ + batch.setPixel(x, y, c); + } + } + + batch.writeFile(output, function(err){ + if (err) console.log(err); + }); +}); + +// adapted from http://stackoverflow.com/a/9493060/1365324 +function hslToRgb(hsl){ + var h = hsl.h / 360, + s = hsl.s / 100, + l = hsl.l / 100; + + var r, g, b; + + if(s == 0){ + r = g = b = l; // achromatic + }else{ + function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + return { + r: Math.round(r * 255), + g: Math.round(g * 255), + b: Math.round(b * 255) + }; +} From 9a126b40966bebf4a99f878813e596e49b5bda02 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sat, 29 Nov 2014 17:28:34 +0800 Subject: [PATCH 23/63] closes #73 - add 'paste' lock and release tests --- tests/03.safety/00.locks.js | 16 +++++++++++++++- tests/03.safety/01.releases.js | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/03.safety/00.locks.js b/tests/03.safety/00.locks.js index fbcb3164..bb8a8df4 100644 --- a/tests/03.safety/00.locks.js +++ b/tests/03.safety/00.locks.js @@ -5,7 +5,7 @@ var join = require('path').join, describe('simultaneous operations locks', function() { - var image; + var image, tmpImage; beforeEach(function(done) { lwip.open(imgs.jpg.rgb, function(err, img) { @@ -14,6 +14,13 @@ describe('simultaneous operations locks', function() { }); }); + before(function(done){ + lwip.create(10, 10, function(err, img){ + tmpImage = img; + done(err); + }); + }); + describe('image.resize lock', function() { it('should lock image', function() { image.resize.bind(image, 100, 100, function() {}).should.not.throwError(); @@ -129,6 +136,13 @@ describe('simultaneous operations locks', function() { describe('image.setPixel lock', function() { it('should lock image', function() { image.setPixel.bind(image, 0, 0, 'yellow', function() {}).should.not.throwError(); + image.paste.bind(image, 0, 0, tmpImage, function() {}).should.throwError(); + }); + }); + + describe('image.paste lock', function() { + it('should lock image', function() { + image.paste.bind(image, 0, 0, tmpImage, function() {}).should.not.throwError(); image.resize.bind(image, 100, 100, function() {}).should.throwError(); }); }); diff --git a/tests/03.safety/01.releases.js b/tests/03.safety/01.releases.js index 0af5955c..455e49b5 100644 --- a/tests/03.safety/01.releases.js +++ b/tests/03.safety/01.releases.js @@ -5,7 +5,7 @@ var join = require('path').join, describe('failed ops lock release', function() { - var image; + var image, tmpImage; beforeEach(function(done) { lwip.open(imgs.jpg.rgb, function(err, img) { @@ -14,6 +14,13 @@ describe('failed ops lock release', function() { }); }); + before(function(done){ + lwip.create(10, 10, function(err, img){ + tmpImage = img; + done(err); + }); + }); + describe('image.resize release', function() { it('should release image lock', function() { image.resize.bind(image, 'foo', 'bar', function() {}).should.throwError(); @@ -129,6 +136,13 @@ describe('failed ops lock release', function() { describe('image.setPixel release', function() { it('should release image lock', function() { image.setPixel.bind(image, 'foo', 'foo', 'foo', function() {}).should.throwError(); + image.paste.bind(image, 0, 0, tmpImage, function() {}).should.not.throwError(); + }); + }); + + describe('image.paste release', function() { + it('should release image lock', function() { + image.paste.bind(image, 'foo', 'foo', 'foo', function() {}).should.throwError(); image.resize.bind(image, 100, 100, function() {}).should.not.throwError(); }); }); From ac36ef9e372f9e9db9bc4ad700d86d223a3bf8c9 Mon Sep 17 00:00:00 2001 From: Eyal Arubas Date: Sun, 30 Nov 2014 00:55:18 +0800 Subject: [PATCH 24/63] progress writing a gif decoder --- binding.gyp | 7 +++- examples/lena.gif | Bin 0 -> 179551 bytes examples/lena_t.gif | Bin 0 -> 146845 bytes lib/obtain.js | 3 ++ src/decoder/decoder.h | 11 ++++++ src/decoder/gif_decoder.cpp | 71 ++++++++++++++++++++++++++++++++++++ src/decoder/init.cpp | 12 ++++++ 7 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 examples/lena.gif create mode 100644 examples/lena_t.gif create mode 100644 src/decoder/gif_decoder.cpp diff --git a/binding.gyp b/binding.gyp index 0ac4613e..d6987cd7 100644 --- a/binding.gyp +++ b/binding.gyp @@ -9,6 +9,7 @@ "src/decoder/buffer_worker.cpp", "src/decoder/jpeg_decoder.cpp", "src/decoder/png_decoder.cpp", + "src/decoder/gif_decoder.cpp", # LIB JPEG: ########### "src/lib/jpeg/jmemnobs.c", @@ -79,7 +80,8 @@ 'src/lib/zlib', 'src/lib/jpeg', 'src/lib/cimg', - 'src/lib/png' + 'src/lib/png', + 'src/lib/gif' ], 'conditions': [ ['OS=="freebsd"', { @@ -185,7 +187,8 @@ 'src/lib/zlib', 'src/lib/jpeg', 'src/lib/cimg', - 'src/lib/png' + 'src/lib/png', + 'src/lib/gif' ], 'conditions': [ ['OS=="freebsd"', { diff --git a/examples/lena.gif b/examples/lena.gif new file mode 100644 index 0000000000000000000000000000000000000000..c6a0bdc5c51cb8f6d965c5d60f3b2248010b0709 GIT binary patch literal 179551 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
  • 8G-J%_^)D1%Y+9Ducb)*VHOL?f+(=Y75XEP~;e~?j7jNKAnx-A3cJ5 zeSf(A`$=5(=y#jx5wpeBvNbyltUfQpu{5pTW^mgo@HRm?@7ynPXuSWWLG5Xsj5c*K z-!XmunfyC}LF7#Rl;+GIPvcG%ZADEQ{5>$xmszEqAAI+%`Il%;(|5zlHOM_p<8&GC z-)R$nUrBaOhIJsNijPeM<%dWQgB%P_jinhR>o$y4^^c3L9%~7alt~4oRoE&&E~?FA zHV}%&>GFU+&IXR;^PycPt(GMHi*Y73p>d`|)`b%0A&Q&Z#;=uYJQvyM~UiPWYiR!jj}`y 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 Q4ddUV?|c>m0s;E}18a*(w*UYD literal 0 HcmV?d00001 diff --git a/examples/lena_t.gif b/examples/lena_t.gif new file mode 100644 index 0000000000000000000000000000000000000000..c037824c3d96791abf862d8d624cac6b99d260a6 GIT binary patch literal 146845 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