Browse Source

Merge pull request #167 from c-spencer/MimeData

Image mime data tracking
v1.x
TJ Holowaychuk 13 years ago
parent
commit
20153ebe5b
  1. 15
      Readme.md
  2. 21
      src/CanvasRenderingContext2d.cc
  3. 336
      src/Image.cc
  4. 20
      src/Image.h

15
Readme.md

@ -64,6 +64,21 @@ ctx.drawImage(img, 50, 0, 50, 50);
ctx.drawImage(img, 100, 0, 50, 50); ctx.drawImage(img, 100, 0, 50, 50);
``` ```
### Image#dataMode
node-canvas adds `Image#dataMode` support, which can be used to opt-in to mime data tracking of images (currently only JPEGs).
When mime data is tracked, in PDF mode JPEGs can be embedded directly into the output, rather than being re-encoded into PNG. This can drastically reduce filesize, and speed up rendering.
```javascript
var img = new Image;
img.dataMode = Image.MODE_IMAGE; // Only image data tracked
img.dataMode = Image.MODE_MIME; // Only mime data tracked
img.dataMode = Image.MODE_MIME | Image.MODE_IMAGE; // Both are tracked
```
If image data is not tracked, and the Image is drawn to an image rather than a PDF canvas, the output will be junk. Enabling mime data tracking has no benefits (only a slow down) unless you are generating a PDF.
### Canvas#createPNGStream() ### Canvas#createPNGStream()
To create a `PNGStream` simply call `canvas.createPNGStream()`, and the stream will start to emit _data_ events, finally emitting _end_ when finished. If an exception occurs the _error_ event is emitted. To create a `PNGStream` simply call `canvas.createPNGStream()`, and the stream will start to emit _data_ events, finally emitting _end_ when finished. If an exception occurs the _error_ event is emitted.

21
src/CanvasRenderingContext2d.cc

@ -538,10 +538,6 @@ Context2d::DrawImage(const Arguments &args) {
if (args.Length() < 3) if (args.Length() < 3)
return ThrowException(Exception::TypeError(String::New("invalid arguments"))); return ThrowException(Exception::TypeError(String::New("invalid arguments")));
#if CAIRO_VERSION_MINOR < 10
return ThrowException(Exception::Error(String::New("drawImage() needs cairo >= 1.10.0")));
#else
int sx = 0 int sx = 0
, sy = 0 , sy = 0
, sw = 0 , sw = 0
@ -611,14 +607,10 @@ Context2d::DrawImage(const Arguments &args) {
// Start draw // Start draw
cairo_save(ctx); cairo_save(ctx);
// Source surface context->savePath();
// TODO: only works with cairo >= 1.10.0 cairo_rectangle(ctx, dx, dy, dw, dh);
cairo_surface_t *src = cairo_surface_create_for_rectangle( cairo_clip(ctx);
surface context->restorePath();
, sx
, sy
, sw
, sh);
// Scale src // Scale src
if (dw != sw || dh != sh) { if (dw != sw || dh != sh) {
@ -630,14 +622,11 @@ Context2d::DrawImage(const Arguments &args) {
} }
// Paint // Paint
cairo_set_source_surface(ctx, src, dx, dy); cairo_set_source_surface(ctx, surface, dx - sx, dy - sy);
cairo_pattern_set_filter(cairo_get_source(ctx), context->state->patternQuality); cairo_pattern_set_filter(cairo_get_source(ctx), context->state->patternQuality);
cairo_paint_with_alpha(ctx, context->state->globalAlpha); cairo_paint_with_alpha(ctx, context->state->globalAlpha);
cairo_restore(ctx); cairo_restore(ctx);
cairo_surface_destroy(src);
#endif
return Undefined(); return Undefined();
} }

336
src/Image.cc

@ -12,11 +12,6 @@
#include <errno.h> #include <errno.h>
#include <node_buffer.h> #include <node_buffer.h>
#ifdef HAVE_JPEG
#include <jpeglib.h>
#include <jerror.h>
#endif
#ifdef HAVE_GIF #ifdef HAVE_GIF
#include <gif_lib.h> #include <gif_lib.h>
typedef struct { typedef struct {
@ -58,6 +53,11 @@ Image::Initialize(Handle<Object> target) {
proto->SetAccessor(String::NewSymbol("height"), GetHeight); proto->SetAccessor(String::NewSymbol("height"), GetHeight);
proto->SetAccessor(String::NewSymbol("onload"), GetOnload, SetOnload); proto->SetAccessor(String::NewSymbol("onload"), GetOnload, SetOnload);
proto->SetAccessor(String::NewSymbol("onerror"), GetOnerror, SetOnerror); proto->SetAccessor(String::NewSymbol("onerror"), GetOnerror, SetOnerror);
#if CAIRO_VERSION_MINOR >= 10
proto->SetAccessor(String::NewSymbol("dataMode"), GetDataMode, SetDataMode);
constructor->Set(String::NewSymbol("MODE_IMAGE"), Number::New(1));
constructor->Set(String::NewSymbol("MODE_MIME"), Number::New(2));
#endif
target->Set(String::NewSymbol("Image"), constructor->GetFunction()); target->Set(String::NewSymbol("Image"), constructor->GetFunction());
} }
@ -69,6 +69,7 @@ Handle<Value>
Image::New(const Arguments &args) { Image::New(const Arguments &args) {
HandleScope scope; HandleScope scope;
Image *img = new Image; Image *img = new Image;
img->data_mode = DATA_IMAGE;
img->Wrap(args.This()); img->Wrap(args.This());
return args.This(); return args.This();
} }
@ -84,6 +85,44 @@ Image::GetComplete(Local<String>, const AccessorInfo &info) {
return scope.Close(Boolean::New(Image::COMPLETE == img->state)); return scope.Close(Boolean::New(Image::COMPLETE == img->state));
} }
#if CAIRO_VERSION_MINOR >= 10
/*
* Get dataMode.
*/
Handle<Value>
Image::GetDataMode(Local<String>, const AccessorInfo &info) {
HandleScope scope;
Image *img = ObjectWrap::Unwrap<Image>(info.This());
return scope.Close(Number::New(img->data_mode));
}
/*
* Set dataMode.
*/
void
Image::SetDataMode(Local<String>, Local<Value> val, const AccessorInfo &info) {
if (val->IsNumber()) {
Image *img = ObjectWrap::Unwrap<Image>(info.This());
int mode = val->Uint32Value();
switch (mode) {
case 1:
img->data_mode = DATA_IMAGE;
break;
case 2:
img->data_mode = DATA_MIME;
break;
case 3:
img->data_mode = DATA_IMAGE_AND_MIME;
break;
}
}
}
#endif
/* /*
* Get width. * Get width.
*/ */
@ -116,6 +155,30 @@ Image::GetSource(Local<String>, const AccessorInfo &info) {
return scope.Close(String::New(img->filename ? img->filename : "")); return scope.Close(String::New(img->filename ? img->filename : ""));
} }
/*
* Clean up assets and variables.
*/
void
Image::clearData() {
if (_surface) {
cairo_surface_destroy(_surface);
V8::AdjustAmountOfExternalAllocatedMemory(-_data_len);
_data_len = 0;
_surface = NULL;
}
free(_data);
_data = NULL;
width = height = 0;
free(filename);
filename = NULL;
state = DEFAULT;
}
/* /*
* Set src path. * Set src path.
*/ */
@ -126,6 +189,8 @@ Image::SetSource(Local<String>, Local<Value> val, const AccessorInfo &info) {
Image *img = ObjectWrap::Unwrap<Image>(info.This()); Image *img = ObjectWrap::Unwrap<Image>(info.This());
cairo_status_t status = CAIRO_STATUS_READ_ERROR; cairo_status_t status = CAIRO_STATUS_READ_ERROR;
img->clearData();
// url string // url string
if (val->IsString()) { if (val->IsString()) {
String::AsciiValue src(val); String::AsciiValue src(val);
@ -159,7 +224,23 @@ Image::loadFromBuffer(uint8_t *buf, unsigned len) {
if (isGIF(buf)) return loadGIFFromBuffer(buf, len); if (isGIF(buf)) return loadGIFFromBuffer(buf, len);
#endif #endif
#ifdef HAVE_JPEG #ifdef HAVE_JPEG
if (isJPEG(buf)) return loadJPEGFromBuffer(buf, len); #if CAIRO_VERSION_MINOR < 10
if (isJPEG(buf)) return loadJPEGFromBuffer(buf, len);
#else
if (isJPEG(buf)) {
switch (data_mode) {
case DATA_IMAGE:
return loadJPEGFromBuffer(buf, len);
case DATA_MIME:
return decodeJPEGBufferIntoMimeSurface(buf, len);
case DATA_IMAGE_AND_MIME:
cairo_status_t status;
status = loadJPEGFromBuffer(buf, len);
if (status) return status;
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
}
}
#endif
#endif #endif
return CAIRO_STATUS_READ_ERROR; return CAIRO_STATUS_READ_ERROR;
} }
@ -242,6 +323,7 @@ Image::SetOnerror(Local<String>, Local<Value> val, const AccessorInfo &info) {
Image::Image() { Image::Image() {
filename = NULL; filename = NULL;
_data = NULL; _data = NULL;
_data_len = 0;
_surface = NULL; _surface = NULL;
width = height = 0; width = height = 0;
state = DEFAULT; state = DEFAULT;
@ -252,13 +334,7 @@ Image::Image() {
*/ */
Image::~Image() { Image::~Image() {
if (_surface) { clearData();
V8::AdjustAmountOfExternalAllocatedMemory(-4 * width * height);
cairo_surface_destroy(_surface);
}
free(_data);
free(filename);
} }
/* /*
@ -285,8 +361,8 @@ Image::loaded() {
width = cairo_image_surface_get_width(_surface); width = cairo_image_surface_get_width(_surface);
height = cairo_image_surface_get_height(_surface); height = cairo_image_surface_get_height(_surface);
// TODO: adjust accordingly when re-assigned src _data_len = height * cairo_image_surface_get_stride(_surface);
V8::AdjustAmountOfExternalAllocatedMemory(4 * width * height); V8::AdjustAmountOfExternalAllocatedMemory(_data_len);
if (!onload.IsEmpty()) { if (!onload.IsEmpty()) {
TryCatch try_catch; TryCatch try_catch;
@ -549,6 +625,7 @@ Image::loadGIFFromBuffer(uint8_t *buf, unsigned len) {
} }
_data = data; _data = data;
return CAIRO_STATUS_SUCCESS; return CAIRO_STATUS_SUCCESS;
} }
#endif /* HAVE_GIF */ #endif /* HAVE_GIF */
@ -602,44 +679,32 @@ static void jpeg_mem_src (j_decompress_ptr cinfo, void* buffer, long nbytes) {
#endif #endif
/* /*
* Load jpeg from buffer. * Takes an initialised jpeg_decompress_struct and decodes the
* data into _surface.
*/ */
cairo_status_t cairo_status_t
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) { Image::decodeJPEGIntoSurface(jpeg_decompress_struct *info) {
// TODO: remove this duplicate logic
// JPEG setup
struct jpeg_decompress_struct info;
struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info);
jpeg_mem_src(&info, buf, len);
jpeg_read_header(&info, 1);
jpeg_start_decompress(&info);
width = info.output_width;
height = info.output_height;
// Data alloc
int stride = width * 4; int stride = width * 4;
uint8_t *data = (uint8_t *) malloc(width * height * 4); cairo_status_t status;
uint8_t *data = (uint8_t *) malloc(width * height * 4);
if (!data) { if (!data) {
jpeg_finish_decompress(&info); jpeg_abort_decompress(info);
jpeg_destroy_decompress(&info); jpeg_destroy_decompress(info);
return CAIRO_STATUS_NO_MEMORY; return CAIRO_STATUS_NO_MEMORY;
} }
uint8_t *src = (uint8_t *) malloc(width * 3); uint8_t *src = (uint8_t *) malloc(width * 3);
if (!src) { if (!src) {
free(data); free(data);
jpeg_finish_decompress(&info); jpeg_abort_decompress(info);
jpeg_destroy_decompress(&info); jpeg_destroy_decompress(info);
return CAIRO_STATUS_NO_MEMORY; return CAIRO_STATUS_NO_MEMORY;
} }
// Copy RGB -> ARGB
for (int y = 0; y < height; ++y) { for (int y = 0; y < height; ++y) {
jpeg_read_scanlines(&info, &src, 1); jpeg_read_scanlines(info, &src, 1);
uint32_t *row = (uint32_t *)(data + stride * y); uint32_t *row = (uint32_t *)(data + stride * y);
for (int x = 0; x < width; ++x) { for (int x = 0; x < width; ++x) {
int bx = 3 * x; int bx = 3 * x;
@ -651,7 +716,6 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
} }
} }
// New image surface
_surface = cairo_image_surface_create_for_data( _surface = cairo_image_surface_create_for_data(
data data
, CAIRO_FORMAT_ARGB32 , CAIRO_FORMAT_ARGB32
@ -659,85 +723,62 @@ Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
, height , height
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)); , cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width));
// Cleanup jpeg_finish_decompress(info);
free(src); jpeg_destroy_decompress(info);
jpeg_finish_decompress(&info); status = cairo_surface_status(_surface);
jpeg_destroy_decompress(&info);
cairo_status_t status = cairo_surface_status(_surface);
if (status) { if (status) {
free(data); free(data);
free(src);
return status; return status;
} }
free(src);
_data = data; _data = data;
return CAIRO_STATUS_SUCCESS; return CAIRO_STATUS_SUCCESS;
} }
#if CAIRO_VERSION_MINOR >= 10
/* /*
* Load JPEG, convert RGB to ARGB. * Takes a jpeg data buffer and assigns it as mime data to a
* dummy surface
*/ */
cairo_status_t cairo_status_t
Image::loadJPEG(FILE *stream) { Image::decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len) {
// TODO: remove this duplicate logic
// JPEG setup // JPEG setup
struct jpeg_decompress_struct info; struct jpeg_decompress_struct info;
struct jpeg_error_mgr err; struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err); info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info); jpeg_create_decompress(&info);
jpeg_stdio_src(&info, stream);
jpeg_mem_src(&info, buf, len);
jpeg_read_header(&info, 1); jpeg_read_header(&info, 1);
jpeg_start_decompress(&info); jpeg_start_decompress(&info);
width = info.output_width; width = info.output_width;
height = info.output_height; height = info.output_height;
// Data alloc // Data alloc
int stride = width * 4; // 8 pixels per byte using Alpha Channel format to reduce memory requirement.
uint8_t *data = (uint8_t *) malloc(width * height * 4); int buf_size = height * cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
uint8_t *data = (uint8_t *) malloc(buf_size);
if (!data) { if (!data) return CAIRO_STATUS_NO_MEMORY;
fclose(stream);
jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info);
return CAIRO_STATUS_NO_MEMORY;
}
uint8_t *src = (uint8_t *) malloc(width * 3);
if (!src) {
free(data);
fclose(stream);
jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info);
return CAIRO_STATUS_NO_MEMORY;
}
// Copy RGB -> ARGB
for (int y = 0; y < height; ++y) {
jpeg_read_scanlines(&info, &src, 1);
uint32_t *row = (uint32_t *)(data + stride * y);
for (int x = 0; x < width; ++x) {
int bx = 3 * x;
uint32_t *pixel = row + x;
*pixel = 255 << 24
| src[bx + 0] << 16
| src[bx + 1] << 8
| src[bx + 2];
}
}
// New image surface // New image surface
_surface = cairo_image_surface_create_for_data( _surface = cairo_image_surface_create_for_data(
data data
, CAIRO_FORMAT_ARGB32 , CAIRO_FORMAT_A1
, width , width
, height , height
, cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width)); , cairo_format_stride_for_width(CAIRO_FORMAT_A1, width));
// Cleanup // Cleanup
free(src); jpeg_abort_decompress(&info);
fclose(stream);
jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info); jpeg_destroy_decompress(&info);
cairo_status_t status = cairo_surface_status(_surface); cairo_status_t status = cairo_surface_status(_surface);
@ -746,7 +787,130 @@ Image::loadJPEG(FILE *stream) {
return status; return status;
} }
return CAIRO_STATUS_SUCCESS; _data = data;
return assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
}
/*
* Helper function for disposing of a mime data closure.
*/
void
clearMimeData(void *closure) {
V8::AdjustAmountOfExternalAllocatedMemory(-((read_closure_t *)closure)->len);
free(((read_closure_t *) closure)->buf);
free(closure);
}
/*
* Assign a given buffer as mime data against the surface.
* The provided buffer will be copied, and the copy will
* be automatically freed when the surface is destroyed.
*/
cairo_status_t
Image::assignDataAsMime(uint8_t *data, int len, const char *mime_type) {
uint8_t *mime_data = (uint8_t *) malloc(len);
if (!mime_data) return CAIRO_STATUS_NO_MEMORY;
read_closure_t *mime_closure = (read_closure_t *) malloc(sizeof(read_closure_t));
if (!mime_closure) {
free(mime_data);
return CAIRO_STATUS_NO_MEMORY;
}
memcpy(mime_data, data, len);
mime_closure->buf = mime_data;
mime_closure->len = len;
V8::AdjustAmountOfExternalAllocatedMemory(len);
return cairo_surface_set_mime_data(_surface, mime_type, mime_data, len, clearMimeData, mime_closure);
}
#endif
/*
* Load jpeg from buffer.
*/
cairo_status_t
Image::loadJPEGFromBuffer(uint8_t *buf, unsigned len) {
// TODO: remove this duplicate logic
// JPEG setup
struct jpeg_decompress_struct info;
struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info);
jpeg_mem_src(&info, buf, len);
jpeg_read_header(&info, 1);
jpeg_start_decompress(&info);
width = info.output_width;
height = info.output_height;
return decodeJPEGIntoSurface(&info);
}
/*
* Load JPEG, convert RGB to ARGB.
*/
cairo_status_t
Image::loadJPEG(FILE *stream) {
cairo_status_t status;
if (data_mode == DATA_IMAGE) { // Can lazily read in the JPEG.
// JPEG setup
struct jpeg_decompress_struct info;
struct jpeg_error_mgr err;
info.err = jpeg_std_error(&err);
jpeg_create_decompress(&info);
jpeg_stdio_src(&info, stream);
jpeg_read_header(&info, 1);
jpeg_start_decompress(&info);
width = info.output_width;
height = info.output_height;
status = decodeJPEGIntoSurface(&info);
fclose(stream);
} else { // We'll need the actual source jpeg data, so read fully.
#if CAIRO_VERSION_MINOR >= 10
uint8_t *buf;
unsigned len;
fseek(stream, 0, SEEK_END);
len = ftell(stream);
fseek(stream, 0, SEEK_SET);
buf = (uint8_t *) malloc(len);
if (!buf) return CAIRO_STATUS_NO_MEMORY;
fread(buf, len, 1, stream);
fclose(stream);
switch (data_mode) {
case DATA_IMAGE: // Can't be this, but compiler warning.
case DATA_IMAGE_AND_MIME:
status = loadJPEGFromBuffer(buf, len);
if (status) break;
status = assignDataAsMime(buf, len, CAIRO_MIME_TYPE_JPEG);
break;
case DATA_MIME:
status = decodeJPEGBufferIntoMimeSurface(buf, len);
break;
}
free(buf);
#endif
}
return status;
} }
#endif /* HAVE_JPEG */ #endif /* HAVE_JPEG */

20
src/Image.h

@ -10,6 +10,11 @@
#include "Canvas.h" #include "Canvas.h"
#ifdef HAVE_JPEG
#include <jpeglib.h>
#include <jerror.h>
#endif
class Image: public node::ObjectWrap { class Image: public node::ObjectWrap {
public: public:
char *filename; char *filename;
@ -25,9 +30,11 @@ class Image: public node::ObjectWrap {
static Handle<Value> GetComplete(Local<String> prop, const AccessorInfo &info); static Handle<Value> GetComplete(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetWidth(Local<String> prop, const AccessorInfo &info); static Handle<Value> GetWidth(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetHeight(Local<String> prop, const AccessorInfo &info); static Handle<Value> GetHeight(Local<String> prop, const AccessorInfo &info);
static Handle<Value> GetDataMode(Local<String> prop, const AccessorInfo &info);
static void SetSource(Local<String> prop, Local<Value> val, const AccessorInfo &info); static void SetSource(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetOnload(Local<String> prop, Local<Value> val, const AccessorInfo &info); static void SetOnload(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetOnerror(Local<String> prop, Local<Value> val, const AccessorInfo &info); static void SetOnerror(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetDataMode(Local<String> prop, Local<Value> val, const AccessorInfo &info);
inline cairo_surface_t *surface(){ return _surface; } inline cairo_surface_t *surface(){ return _surface; }
inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); } inline uint8_t *data(){ return cairo_image_surface_get_data(_surface); }
inline int stride(){ return cairo_image_surface_get_stride(_surface); } inline int stride(){ return cairo_image_surface_get_stride(_surface); }
@ -40,6 +47,7 @@ class Image: public node::ObjectWrap {
cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadPNGFromBuffer(uint8_t *buf); cairo_status_t loadPNGFromBuffer(uint8_t *buf);
cairo_status_t loadPNG(); cairo_status_t loadPNG();
void clearData();
#ifdef HAVE_GIF #ifdef HAVE_GIF
cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadGIFFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadGIF(FILE *stream); cairo_status_t loadGIF(FILE *stream);
@ -47,6 +55,11 @@ class Image: public node::ObjectWrap {
#ifdef HAVE_JPEG #ifdef HAVE_JPEG
cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len); cairo_status_t loadJPEGFromBuffer(uint8_t *buf, unsigned len);
cairo_status_t loadJPEG(FILE *stream); cairo_status_t loadJPEG(FILE *stream);
cairo_status_t decodeJPEGIntoSurface(jpeg_decompress_struct *info);
#if CAIRO_VERSION_MINOR >= 10
cairo_status_t decodeJPEGBufferIntoMimeSurface(uint8_t *buf, unsigned len);
cairo_status_t assignDataAsMime(uint8_t *data, int len, const char *mime_type);
#endif
#endif #endif
void error(Local<Value> error); void error(Local<Value> error);
void loaded(); void loaded();
@ -59,6 +72,12 @@ class Image: public node::ObjectWrap {
, COMPLETE , COMPLETE
} state; } state;
enum {
DATA_IMAGE = 1,
DATA_MIME,
DATA_IMAGE_AND_MIME
} data_mode;
typedef enum { typedef enum {
UNKNOWN UNKNOWN
, GIF , GIF
@ -71,6 +90,7 @@ class Image: public node::ObjectWrap {
private: private:
cairo_surface_t *_surface; cairo_surface_t *_surface;
uint8_t *_data; uint8_t *_data;
int _data_len;
~Image(); ~Image();
}; };

Loading…
Cancel
Save