@@ -224,18 +224,69 @@ Canvas::ToBufferAsyncAfter(uv_work_t *req) {
224224 free (closure);
225225}
226226
227+ inline static uint32_t parsePngCompressionLevel (Local<Value> arg) {
228+ // See quote below from spec section 4.12.5.5.
229+ if (arg->IsObject ()) {
230+ Local<Object> obj = arg->ToObject ();
231+ Local<Value> cLevelStr = Nan::New (" compressionLevel" ).ToLocalChecked ();
232+ if (obj->Has (cLevelStr)) {
233+ uint32_t compression_level = obj->Get (cLevelStr)->Uint32Value ();
234+ if (compression_level >= 0 && compression_level <= 9 ) return compression_level;
235+ }
236+ }
237+ return 6 ;
238+ }
239+
240+ inline static uint32_t parsePngFilter (Local<Value> arg) {
241+ if (arg->IsObject ()) {
242+ Local<Object> obj = arg->ToObject ();
243+ Local<Value> cLevelStr = Nan::New (" filter" ).ToLocalChecked ();
244+ if (obj->Has (cLevelStr)) {
245+ return obj->Get (cLevelStr)->Uint32Value ();
246+ }
247+ }
248+ return PNG_ALL_FILTERS;
249+ }
250+
251+ inline static uint32_t parseJpegQuality (Local<Value> arg) {
252+ // "If Type(quality) is not Number, or if quality is outside that range, the
253+ // user agent must use its default quality value, as if the quality argument
254+ // had not been given." - 4.12.5.5
255+ if (arg->IsNumber ()) {
256+ double quality = arg->NumberValue ();
257+ if (quality >= 0.0 && quality <= 1.0 ) return static_cast <uint32_t >(100.0 * quality);
258+ }
259+ return 75 ; // spec doesn't say what the default should be
260+ }
261+
227262/*
228- * Convert PNG data to a node::Buffer, async when a
229- * callback function is passed.
263+ * Converts/encodes data to a Buffer. Async when a callback function is passed.
264+
265+ * PDF/SVG canvases:
266+ () => Buffer
267+
268+ * ARGB data:
269+ ("raw") => Buffer
270+ ((err: null|Error, buffer) => any, "raw") // ? There's no async work to do here.
271+
272+ * PNG-encoded
273+ () => Buffer
274+ (undefined|"image/png", {compressionLevel?: number, filter?: number}) => Buffer
275+ ((err: null|Error, buffer) => any)
276+ ((err: null|Error, buffer) => any, undefined|"image/png", {compressionLevel?: number, filter?: number})
277+
278+ * JPEG-encoded
279+ ("image/jpeg") => Buffer
280+ ("image/jpeg", quality) => Buffer
281+ ((err: null|Error, buffer) => any, "image/jpeg")
282+ ((err: null|Error, buffer) => any, "image/jpeg", quality)
230283 */
231284
232285NAN_METHOD (Canvas::ToBuffer) {
233286 cairo_status_t status;
234- uint32_t compression_level = 6 ;
235- uint32_t filter = PNG_ALL_FILTERS;
236287 Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This ());
237288
238- // TODO: async / move this out
289+ // Vector canvases, sync only
239290 const string name = canvas->backend ()->getName ();
240291 if (name == " pdf" || name == " svg" ) {
241292 cairo_surface_finish (canvas->surface ());
@@ -246,8 +297,8 @@ NAN_METHOD(Canvas::ToBuffer) {
246297 return ;
247298 }
248299
249- if (info. Length () >= 1 && info[ 0 ]-> StrictEquals (Nan::New<String>( " raw " ). ToLocalChecked ())) {
250- // Return raw ARGB data -- just a memcpy()
300+ // Raw ARGB data -- just a memcpy()
301+ if (info[ 0 ]-> StrictEquals (Nan::New<String>( " raw " ). ToLocalChecked ())) {
251302 cairo_surface_t *surface = canvas->surface ();
252303 cairo_surface_flush (surface);
253304 const unsigned char *data = cairo_image_surface_get_data (surface);
@@ -256,46 +307,44 @@ NAN_METHOD(Canvas::ToBuffer) {
256307 return ;
257308 }
258309
259- if (info.Length () > 1 && !(info[1 ]->IsUndefined () && info[2 ]->IsUndefined ())) {
260- if (!info[1 ]->IsUndefined ()) {
261- bool good = true ;
262- if (info[1 ]->IsNumber ()) {
263- compression_level = info[1 ]->Uint32Value ();
264- } else if (info[1 ]->IsString ()) {
265- if (info[1 ]->StrictEquals (Nan::New<String>(" 0" ).ToLocalChecked ())) {
266- compression_level = 0 ;
267- } else {
268- uint32_t tmp = info[1 ]->Uint32Value ();
269- if (tmp == 0 ) {
270- good = false ;
271- } else {
272- compression_level = tmp;
273- }
274- }
275- } else {
276- good = false ;
277- }
310+ // Sync PNG, default
311+ if (info[0 ]->IsUndefined () || info[0 ]->StrictEquals (Nan::New<String>(" image/png" ).ToLocalChecked ())) {
312+ uint32_t compression_level = parsePngCompressionLevel (info[1 ]);
313+ uint32_t filter = parsePngFilter (info[1 ]);
278314
279- if (good) {
280- if ( compression_level > 9 ) {
281- return Nan::ThrowRangeError ( " Allowed compression levels lie in the range [0, 9]. " );
282- }
283- } else {
284- return Nan::ThrowTypeError ( " Compression level must be a number. " );
285- }
315+ closure_t closure;
316+ status = closure_init (&closure, canvas, compression_level, filter);
317+
318+ // ensure closure is ok
319+ if (status) {
320+ closure_destroy (&closure );
321+ return Nan::ThrowError ( Canvas::Error (status));
286322 }
287323
288- if (!info[2 ]->IsUndefined ()) {
289- if (info[2 ]->IsUint32 ()) {
290- filter = info[2 ]->Uint32Value ();
291- } else {
292- return Nan::ThrowTypeError (" Invalid filter value." );
293- }
324+ Nan::TryCatch try_catch;
325+ status = canvas_write_to_png_stream (canvas->surface (), toBuffer, &closure);
326+
327+ if (try_catch.HasCaught ()) {
328+ closure_destroy (&closure);
329+ try_catch.ReThrow ();
330+ return ;
331+ } else if (status) {
332+ closure_destroy (&closure);
333+ return Nan::ThrowError (Canvas::Error (status));
334+ } else {
335+ Local<Object> buf = Nan::CopyBuffer ((char *)closure.data , closure.len ).ToLocalChecked ();
336+ closure_destroy (&closure);
337+ info.GetReturnValue ().Set (buf);
338+ return ;
294339 }
295340 }
296341
297- // Async
298- if (info[0 ]->IsFunction ()) {
342+ // Async PNG
343+ if (info[0 ]->IsFunction () &&
344+ (info[1 ]->IsUndefined () || info[1 ]->StrictEquals (Nan::New<String>(" image/png" ).ToLocalChecked ()))) {
345+ uint32_t compression_level = parsePngCompressionLevel (info[2 ]);
346+ uint32_t filter = parsePngFilter (info[2 ]);
347+
299348 closure_t *closure = (closure_t *) malloc (sizeof (closure_t ));
300349 status = closure_init (closure, canvas, compression_level, filter);
301350
@@ -317,34 +366,42 @@ NAN_METHOD(Canvas::ToBuffer) {
317366 uv_queue_work (uv_default_loop (), req, ToBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
318367
319368 return ;
320- // Sync
321- } else {
322- closure_t closure;
323- status = closure_init (&closure, canvas, compression_level, filter);
369+ }
324370
325- // ensure closure is ok
326- if (status) {
327- closure_destroy (&closure);
328- return Nan::ThrowError (Canvas::Error (status));
329- }
371+ #ifdef HAVE_JPEG
372+ // JPEG
373+ Local<Value> jpegStr = Nan::New<String>(" image/jpeg" ).ToLocalChecked ();
374+ if (info[0 ]->StrictEquals (jpegStr) ||
375+ (info[0 ]->IsFunction () && info[1 ]->StrictEquals (jpegStr))) {
376+ uint32_t quality = parseJpegQuality (info[0 ]->IsFunction () ? info[1 ] : info[0 ]);
330377
331378 Nan::TryCatch try_catch;
332- status = canvas_write_to_png_stream (canvas->surface (), toBuffer, &closure);
379+ unsigned char *outbuff = NULL ;
380+ unsigned long outsize = 0 ;
381+ // TODO 2, 2 are chromaHSampFactor/chromaVSampFactor. Accept from arguments.
382+ write_to_jpeg_buffer (canvas->surface (), quality, false , 2 , 2 , &outbuff, &outsize);
333383
334384 if (try_catch.HasCaught ()) {
335- closure_destroy (&closure);
336- try_catch.ReThrow () ;
337- return ;
338- } else if (status) {
339- closure_destroy (&closure );
340- return Nan::ThrowError ( Canvas::Error (status));
385+ if (info[ 0 ]-> IsFunction ()) {
386+ Local<Value> argv[ 1 ] = { try_catch.Exception () } ;
387+ info[ 0 ]. As <Function>()-> Call ( Isolate::GetCurrent ()-> GetCurrentContext ()-> Global (), 1 , argv) ;
388+ } else {
389+ try_catch. ReThrow ( );
390+ }
341391 } else {
342- Local<Object> buf = Nan::CopyBuffer ((char *)closure.data , closure.len ).ToLocalChecked ();
343- closure_destroy (&closure);
344- info.GetReturnValue ().Set (buf);
345- return ;
392+ char *signedOutBuff = reinterpret_cast <char *>(outbuff);
393+ Local<Object> buf = Nan::CopyBuffer (signedOutBuff, outsize).ToLocalChecked ();
394+ if (info[0 ]->IsFunction ()) {
395+ Local<Value> argv[2 ] = { Nan::Null (), buf };
396+ info[0 ].As <Function>()->Call (Isolate::GetCurrent ()->GetCurrentContext ()->Global (), 2 , argv);
397+ } else {
398+ info.GetReturnValue ().Set (buf);
399+ }
346400 }
401+
402+ return ;
347403 }
404+ #endif
348405}
349406
350407/*
0 commit comments