Tj Holowaychuk
13 years ago
8 changed files with 330 additions and 1 deletions
@ -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'; |
|||
|
|||
|
@ -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; |
@ -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…
Reference in new issue