diff --git a/.gitignore b/.gitignore index ca525db..b5f101c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,7 @@ testing test.png .pomo node_modules + +# Vim cruft +*.swp +*.un~ diff --git a/src/Image.cc b/src/Image.cc index 7c48af3..3839b53 100644 --- a/src/Image.cc +++ b/src/Image.cc @@ -145,6 +145,9 @@ Image::SetSrc(Local, Local val, const AccessorInfo &info) { cairo_status_t Image::loadFromBuffer(uint8_t *buf, unsigned len) { if (isPNG(buf)) return loadPNGFromBuffer(buf); +#ifdef HAVE_GIF + if (isGIF(buf)) return loadGIFFromBuffer(buf, len); +#endif #ifdef HAVE_JPEG if (isJPEG(buf)) return loadJPEGFromBuffer(buf, len); #endif @@ -313,6 +316,10 @@ Image::loadSurface() { switch (extension(filename)) { case Image::PNG: return loadPNG(); +#ifdef HAVE_GIF + case Image::GIF: + return loadGIF(); +#endif #ifdef HAVE_JPEG case Image::JPEG: return loadJPEG(); @@ -332,6 +339,187 @@ Image::loadPNG() { return cairo_surface_status(_surface); } +#ifdef HAVE_GIF + +int +getGIFTransparentColor(GifFileType * gft, int framenum) +{ + ExtensionBlock *ext = gft->SavedImages[framenum].ExtensionBlocks; + + for (int ix = 0; ix < gft->SavedImages[framenum].ExtensionBlockCount; ix++, ext++) { + if ((ext->Function == GRAPHICS_EXT_FUNC_CODE) && (ext->Bytes[0] & 1)) { + return ext->Bytes[3] == 0 ? 0 : (uint8_t) ext->Bytes[3]; + } + } + + return -1; +} + +int +readGIFFromMemory(GifFileType *gft, GifByteType *buf, int length) { + struct GIFInputFuncData *gifd = (struct GIFInputFuncData*)gft->UserData; + // Make sure we don't read past our buffer + if((gifd->cpos + length) > gifd->length) length = gifd->length - gifd->cpos; + memcpy(buf, gifd->cpos + gifd->buf, length); + gifd->cpos += length; + return length; +} + +cairo_status_t +Image::loadGIF() { + FILE *stream = fopen(filename, "r"); + if (!stream) return CAIRO_STATUS_READ_ERROR; + + fseek(stream, 0L, SEEK_END); + unsigned int length = ftell(stream); + fseek(stream, 0L, SEEK_SET); + + uint8_t *buf = (uint8_t*)malloc(length); + if(!buf) { + fclose(stream); + return CAIRO_STATUS_NO_MEMORY; + } + + size_t obj_read = fread(buf, length, 1, stream); + fclose(stream); + + cairo_status_t result = CAIRO_STATUS_READ_ERROR; + + if(obj_read == 1) + result = loadGIFFromBuffer(buf, length); + + free(buf); + return result; +} + +cairo_status_t +Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) { + int imageIdx = 0; + GifFileType* gft; + + struct GIFInputFuncData gifd = + { + buf: buf, + length: len, + cpos: 0 + }; + + if((gft = DGifOpen((void*) &gifd, readGIFFromMemory)) == NULL) + return CAIRO_STATUS_READ_ERROR; + + if(DGifSlurp(gft) != GIF_OK) { + DGifCloseFile(gft); + return CAIRO_STATUS_READ_ERROR; + } + + width = gft->SWidth; + height = gft->SHeight; + + uint8_t *data = (uint8_t *) malloc(width * height * 4); + if (!data) { + DGifCloseFile(gft); + return CAIRO_STATUS_NO_MEMORY; + } + + GifImageDesc *img = &gft->SavedImages[imageIdx].ImageDesc; + // Local colormap takes precedence over global + ColorMapObject *colormap = img->ColorMap ? img->ColorMap : gft->SColorMap; + + int bgColor = 0; + int alphaColor = getGIFTransparentColor(gft, imageIdx); + + if(gft->SColorMap) + bgColor = (uint8_t) gft->SBackGroundColor; + else if(alphaColor >= 0) + bgColor = alphaColor; + + uint8_t *src_data = (uint8_t*) gft->SavedImages[imageIdx].RasterBits; + uint32_t *dst_data = (uint32_t*) data; + + if(!gft->Image.Interlace) { + if((width == img->Width) && (height == img->Height)) { + for(int iy = 0; iy < height; iy++) { + for(int ix = 0; ix < width; ix++) { + *dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24 + | (colormap->Colors[*src_data].Red) << 16 + | (colormap->Colors[*src_data].Green) << 8 + | (colormap->Colors[*src_data].Blue); + + dst_data++; + src_data++; + } + } + } else { + // Image does not take up whole "screen" so we need to fill-in the background + int bottom = img->Top + img->Height; + int right = img->Left + img->Width; + + for(int iy = 0; iy < height; iy++) { + for(int ix = 0; ix < width; ix++) { + if((iy < img->Top) || (iy >= bottom) || (ix < img->Left) || (ix >= right)) { + *dst_data = ((bgColor == alphaColor) ? 0 : 255) << 24 + | (colormap->Colors[bgColor].Red) << 16 + | (colormap->Colors[bgColor].Green) << 8 + | (colormap->Colors[bgColor].Blue); + } else { + *dst_data = ((*src_data == alphaColor) ? 0 : 255) << 24 + | (colormap->Colors[*src_data].Red) << 16 + | (colormap->Colors[*src_data].Green) << 8 + | (colormap->Colors[*src_data].Blue); + } + + dst_data++; + src_data++; + } + } + } + } else { + // Image is interlaced so that it streams nice over 14.4k and 28.8k modems :) + // We first load in 1/8 of the image, followed by another 1/8, followed by + // 1/4 and finally the remaining 1/2. + int ioffs[] = { 0, 4, 2, 1 }; + int ijumps[] = { 8, 8, 4, 2 }; + + uint8_t *src_ptr = src_data; + uint32_t *dst_ptr; + + for(int iz = 0; iz < 4; iz++) { + for(int iy = ioffs[iz]; iy < height; iy += ijumps[iz]) { + dst_ptr = dst_data + width * iy; + for(int ix = 0; ix < width; ix++) { + *dst_ptr = ((*src_ptr == alphaColor) ? 0 : 255) << 24 + | (colormap->Colors[*src_ptr].Red) << 16 + | (colormap->Colors[*src_ptr].Green) << 8 + | (colormap->Colors[*src_ptr].Blue); + + dst_ptr++; + src_ptr++; + } + } + } + } + + DGifCloseFile(gft); + + // New image surface + _surface = cairo_image_surface_create_for_data( + data + , CAIRO_FORMAT_ARGB32 + , width + , height + , cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)); + + cairo_status_t status = cairo_surface_status(_surface); + + if (status) { + free(data); + return status; + } + + return CAIRO_STATUS_SUCCESS; +} +#endif + #ifdef HAVE_JPEG /* @@ -478,6 +666,7 @@ Image::extension(const char *filename) { size_t len = strlen(filename); filename += len; if (len >= 5 && 0 == strcmp(".jpeg", filename - 5)) return Image::JPEG; + if (len >= 4 && 0 == strcmp(".gif", filename - 4)) return Image::GIF; if (len >= 4 && 0 == strcmp(".jpg", filename - 4)) return Image::JPEG; if (len >= 4 && 0 == strcmp(".png", filename - 4)) return Image::PNG; return Image::UNKNOWN; diff --git a/src/Image.h b/src/Image.h index 8f1fbfd..06ecfb7 100644 --- a/src/Image.h +++ b/src/Image.h @@ -9,6 +9,9 @@ #define __NODE_IMAGE_H__ #include "Canvas.h" +#ifdef HAVE_GIF +#include +#endif class Image: public node::ObjectWrap { public: @@ -40,6 +43,10 @@ class Image: public node::ObjectWrap { cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadPNGFromBuffer(uint8_t *buf); cairo_status_t loadPNG(); +#ifdef HAVE_GIF + cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len); + cairo_status_t loadGIF(); +#endif #ifdef HAVE_JPEG cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadJPEG(); @@ -57,15 +64,27 @@ class Image: public node::ObjectWrap { typedef enum { UNKNOWN + , GIF , JPEG , PNG } type; static type extension(const char *filename); - + private: cairo_surface_t *_surface; ~Image(); }; +#ifdef HAVE_GIF + struct GIFInputFuncData { + uint8_t *buf; + unsigned int length; + unsigned int cpos; + }; + + int readGIFFromMemory(GifFileType *gft, GifByteType *buf, int length); + int getGIFTransparentColor(GifFileType *gft, int framenum); +#endif + #endif diff --git a/wscript b/wscript index 94e251b..e0d1b7b 100644 --- a/wscript +++ b/wscript @@ -16,7 +16,10 @@ def configure(conf): conf.check_tool('compiler_cxx') conf.check_tool('node_addon') conf.env.append_value('CPPFLAGS', '-DNDEBUG') - + + if conf.check(lib='gif', uselib_store='GIF', mandatory=False): + conf.env.append_value('CPPFLAGS', '-DHAVE_GIF=1') + if conf.check(lib='jpeg', uselib_store='JPEG', mandatory=False): conf.env.append_value('CPPFLAGS', '-DHAVE_JPEG=1') @@ -33,4 +36,4 @@ def build(bld): obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') obj.target = 'canvas' obj.source = bld.glob('src/*.cc') - obj.uselib = ['CAIRO', 'JPEG'] \ No newline at end of file + obj.uselib = ['CAIRO', 'GIF', 'JPEG']