Browse Source

Merge branch 'integration'

v1.x
Tj Holowaychuk 13 years ago
parent
commit
d284e489ac
  1. 1
      .gitignore
  2. 6
      Readme.md
  3. 41
      examples/crop.js
  4. 36
      lib/canvas.js
  5. 67
      lib/jpegstream.js
  6. 37
      src/Canvas.cc
  7. 1
      src/Canvas.h
  8. 142
      src/JPEGStream.h

1
.gitignore

@ -3,6 +3,7 @@ build
.lock-wscript
test/images/*.png
examples/*.png
examples/*.jpg
testing
test.png
.pomo

6
Readme.md

@ -66,7 +66,7 @@ ctx.drawImage(img, 100, 0, 50, 50);
### Canvas#createPNGStream()
To create a `PNGStream` simple 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.
```javascript
var fs = require('fs')
@ -84,6 +84,10 @@ stream.on('end', function(){
Currently _only_ sync streaming is supported, however we plan on supporting async streaming as well (of course :) ). Until then the `Canvas#toBuffer(callback)` alternative is async utilizing `eio_custom()`.
### Canvas#createJPEGStream()
You can likewise create a `JPEGStream` by calling `canvas.createJPEGStream()` with some optional parameters; functionality is otherwise identical to `createPNGStream()`. See `examples/crop.js` for an example.
### Canvas#toBuffer()
A call to `Canvas#toBuffer()` will return a node `Buffer` instance containing all of the PNG data.

41
examples/crop.js

@ -0,0 +1,41 @@
/**
* Module dependencies.
*/
var Canvas = require('../lib/canvas')
, Image = Canvas.Image
, fs = require('fs');
var img = new Image
, start = new Date;
img.onerror = function(err){
throw err;
};
img.onload = function(){
var w = img.width / 2
, h = img.height / 2
, canvas = new Canvas(w, h)
, ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, w, h, 0, 0, w, h);
var out = fs.createWriteStream(__dirname + '/crop.jpg')
, stream = canvas.createJPEGStream({
bufsize : 2048,
quality : 80
});
stream.on('data', function(chunk, len){
out.write(chunk);
});
stream.on('end', function(){
console.log('Cropped and saved in %dms', new Date - start);
});
};
img.src = __dirname + '/images/squid.png';

36
lib/canvas.js

@ -16,6 +16,7 @@ var canvas = require('./bindings')
, PixelArray = canvas.PixelArray
, Context2d = require('./context2d')
, PNGStream = require('./pngstream')
, JPEGStream = require('./jpegstream')
, fs = require('fs');
/**
@ -42,6 +43,7 @@ exports.cairoVersion = cairoVersion;
exports.Context2d = Context2d;
exports.PNGStream = PNGStream;
exports.JPEGStream = JPEGStream;
exports.PixelArray = PixelArray;
exports.Image = Image;
@ -113,6 +115,40 @@ Canvas.prototype.createSyncPNGStream = function(){
return new PNGStream(this, true);
};
/**
* Create a `JPEGStream` for `this` canvas.
*
* @param {Object} [opts] Can optionally contain bufsize or quality settings
* @return {JPEGStream}
* @api public
*/
Canvas.prototype.createJPEGStream = function(opts){
opts = opts || {};
options = {
bufsize : opts.bufsize || 4096,
quality : opts.quality || 60,
};
return new JPEGStream(this, options);
};
/**
* Create a synchronous `JPEGStream` for `this` canvas.
*
* @param {Object} [opts] Can optionally contain bufsize or quality settings
* @return {JPEGStream}
* @api public
*/
Canvas.prototype.createSyncJPEGStream = function(opts){
opts = opts || {};
options = {
bufsize : opts.bufsize || 4096,
quality : opts.quality || 60,
};
return new JPEGStream(this, options, true);
};
/**
* Return a data url. Pass a function for async support.
*

67
lib/jpegstream.js

@ -0,0 +1,67 @@
/*!
* Canvas - JPEGStream
* Copyright (c) 2010 LearnBoost <tj@learnboost.com>
* MIT Licensed
*/
/**
* Module dependencies.
*/
var Stream = require('stream').Stream;
/**
* Initialize a `JPEGStream` with the given `canvas`.
*
* "data" events are emitted with `Buffer` chunks, once complete the
* "end" event is emitted. The following example will stream to a file
* named "./my.jpeg".
*
* var out = fs.createWriteStream(__dirname + '/my.jpeg')
* , stream = canvas.createJPEGStream();
*
* stream.on('data', function(chunk){
* out.write(chunk);
* });
*
* stream.on('end', function(){
* out.end();
* });
*
* @param {Canvas} canvas
* @param {Boolean} sync
* @api public
*/
var JPEGStream = module.exports = function JPEGStream(canvas, options, sync) {
var self = this
, method = sync
? 'streamJPEGSync'
: 'streamJPEG';
this.options = options;
this.sync = sync;
this.canvas = canvas;
this.readable = true;
// TODO: implement async
if ('streamJPEG' == method) method = 'streamJPEGSync';
process.nextTick(function(){
canvas[method](options.bufsize, options.quality, function(err, chunk, len){
if (err) {
self.emit('error', err);
self.readable = false;
} else if (len) {
self.emit('data', chunk, len);
} else {
self.emit('end');
self.readable = false;
}
});
});
};
/**
* Inherit from `EventEmitter`.
*/
JPEGStream.prototype.__proto__ = Stream.prototype;

37
src/Canvas.cc

@ -14,6 +14,10 @@
#include <node_version.h>
#include "closure.h"
#ifdef HAVE_JPEG
#include "JPEGStream.h"
#endif
Persistent<FunctionTemplate> Canvas::constructor;
/*
@ -33,6 +37,9 @@ Canvas::Initialize(Handle<Object> target) {
Local<ObjectTemplate> proto = constructor->PrototypeTemplate();
NODE_SET_PROTOTYPE_METHOD(constructor, "toBuffer", ToBuffer);
NODE_SET_PROTOTYPE_METHOD(constructor, "streamPNGSync", StreamPNGSync);
#ifdef HAVE_JPEG
NODE_SET_PROTOTYPE_METHOD(constructor, "streamJPEGSync", StreamJPEGSync);
#endif
proto->SetAccessor(String::NewSymbol("width"), GetWidth, SetWidth);
proto->SetAccessor(String::NewSymbol("height"), GetHeight, SetHeight);
target->Set(String::NewSymbol("Canvas"), constructor->GetFunction());
@ -287,6 +294,36 @@ Canvas::StreamPNGSync(const Arguments &args) {
return Undefined();
}
/*
* Stream JPEG data synchronously.
*/
#ifdef HAVE_JPEG
Handle<Value>
Canvas::StreamJPEGSync(const Arguments &args) {
HandleScope scope;
// TODO: async as well
if (!args[0]->IsNumber())
return ThrowException(Exception::TypeError(String::New("buffer size required")));
if (!args[1]->IsNumber())
return ThrowException(Exception::TypeError(String::New("quality setting required")));
if (!args[2]->IsFunction())
return ThrowException(Exception::TypeError(String::New("callback function required")));
Canvas *canvas = ObjectWrap::Unwrap<Canvas>(args.This());
closure_t closure;
closure.fn = Handle<Function>::Cast(args[2]);
TryCatch try_catch;
write_to_jpeg_stream(canvas->surface(), args[0]->NumberValue(), args[1]->NumberValue(), &closure);
if (try_catch.HasCaught()) return try_catch.ReThrow();
return Undefined();
}
#endif
/*
* Initialize cairo surface.
*/

1
src/Canvas.h

@ -43,6 +43,7 @@ class Canvas: public node::ObjectWrap {
static void SetWidth(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static void SetHeight(Local<String> prop, Local<Value> val, const AccessorInfo &info);
static Handle<Value> StreamPNGSync(const Arguments &args);
static Handle<Value> StreamJPEGSync(const Arguments &args);
static Local<Value> Error(cairo_status_t status);
static
#if NODE_VERSION_AT_LEAST(0, 5, 4)

142
src/JPEGStream.h

@ -0,0 +1,142 @@
//
// JPEGStream.h
//
#ifndef __NODE_JPEG_STREAM_H__
#define __NODE_JPEG_STREAM_H__
#include "Canvas.h"
#include <jpeglib.h>
#include <jerror.h>
/*
* Expanded data destination object for closure output,
* inspired by IJG's jdatadst.c
*/
typedef struct {
struct jpeg_destination_mgr pub;
closure_t *closure;
JOCTET *buffer;
int bufsize;
} closure_destination_mgr;
void
init_closure_destination(j_compress_ptr cinfo){
// we really don't have to do anything here
}
boolean
empty_closure_output_buffer(j_compress_ptr cinfo){
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
Local<Buffer> buf = Buffer::New(dest->bufsize);
memcpy(Buffer::Data(buf->handle_), dest->buffer, dest->bufsize);
Local<Value> argv[3] = {
Local<Value>::New(Null())
, Local<Value>::New(buf->handle_)
, Integer::New(dest->bufsize)
};
dest->closure->fn->Call(Context::GetCurrent()->Global(), 3, argv);
cinfo->dest->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize;
return true;
}
void
term_closure_destination(j_compress_ptr cinfo){
closure_destination_mgr *dest = (closure_destination_mgr *) cinfo->dest;
/* emit remaining data */
size_t remaining = dest->bufsize - cinfo->dest->free_in_buffer;
Local<Buffer> buf = Buffer::New(remaining);
memcpy(Buffer::Data(buf->handle_), dest->buffer, remaining);
Local<Value> data_argv[3] = {
Local<Value>::New(Null())
, Local<Value>::New(buf->handle_)
, Integer::New(remaining)
};
dest->closure->fn->Call(Context::GetCurrent()->Global(), 3, data_argv);
// emit "end"
Local<Value> end_argv[3] = {
Local<Value>::New(Null())
, Local<Value>::New(Null())
, Integer::New(0)
};
dest->closure->fn->Call(Context::GetCurrent()->Global(), 3, end_argv);
}
void
jpeg_closure_dest(j_compress_ptr cinfo, closure_t * closure, int bufsize){
closure_destination_mgr * dest;
/* The destination object is made permanent so that multiple JPEG images
* can be written to the same buffer without re-executing jpeg_mem_dest.
*/
if (cinfo->dest == NULL) { /* first time for this JPEG object? */
cinfo->dest = (struct jpeg_destination_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(closure_destination_mgr));
}
dest = (closure_destination_mgr *) cinfo->dest;
cinfo->dest->init_destination = &init_closure_destination;
cinfo->dest->empty_output_buffer = &empty_closure_output_buffer;
cinfo->dest->term_destination = &term_closure_destination;
dest->closure = closure;
dest->bufsize = bufsize;
dest->buffer = (JOCTET *)malloc(bufsize);
cinfo->dest->next_output_byte = dest->buffer;
cinfo->dest->free_in_buffer = dest->bufsize;
}
void
write_to_jpeg_stream(cairo_surface_t *surface, int bufsize, int quality, closure_t *closure){
int w = cairo_image_surface_get_width(surface);
int h = cairo_image_surface_get_height(surface);
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
JSAMPROW slr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
cinfo.in_color_space = JCS_RGB;
cinfo.input_components = 3;
cinfo.image_width = w;
cinfo.image_height = h;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, (quality<25)?0:1);
jpeg_closure_dest(&cinfo, closure, bufsize);
jpeg_start_compress(&cinfo, TRUE);
unsigned char *dst;
unsigned int *src = (unsigned int *) cairo_image_surface_get_data(surface);
int sl = 0;
dst = (unsigned char *) malloc(w * 3);
while (sl < h) {
unsigned char *dp = dst;
int x = 0;
while (x < w) {
dp[0] = (*src >> 16) & 255;
dp[1] = (*src >> 8) & 255;
dp[2] = *src & 255;
src++;
dp += 3;
x++;
}
slr = dst;
jpeg_write_scanlines(&cinfo, &slr, 1);
sl++;
}
free(dst);
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
}
#endif
Loading…
Cancel
Save