Skip to content

Commit

Permalink
Add GIF support
Browse files Browse the repository at this point in the history
Signed-off-by: Tj Holowaychuk <tj@vision-media.ca>
  • Loading branch information
tritonrc authored and tj committed Jul 12, 2011
1 parent f4fc9ca commit 63626fa
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 3 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ testing
test.png
.pomo
node_modules

# Vim cruft
*.swp
*.un~
189 changes: 189 additions & 0 deletions src/Image.cc
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,9 @@ Image::SetSrc(Local<String>, Local<Value> 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
Expand Down Expand Up @@ -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();
Expand All @@ -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

/*
Expand Down Expand Up @@ -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;
Expand Down
21 changes: 20 additions & 1 deletion src/Image.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#define __NODE_IMAGE_H__

#include "Canvas.h"
#ifdef HAVE_GIF
#include <gif_lib.h>
#endif

class Image: public node::ObjectWrap {
public:
Expand Down Expand Up @@ -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();
Expand All @@ -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
7 changes: 5 additions & 2 deletions wscript
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand All @@ -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']
obj.uselib = ['CAIRO', 'GIF', 'JPEG']

0 comments on commit 63626fa

Please sign in to comment.