@@ -224,18 +224,69 @@ Canvas::ToBufferAsyncAfter(uv_work_t *req) {
224
224
free (closure);
225
225
}
226
226
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
+
227
262
/*
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)
230
283
*/
231
284
232
285
NAN_METHOD (Canvas::ToBuffer) {
233
286
cairo_status_t status;
234
- uint32_t compression_level = 6 ;
235
- uint32_t filter = PNG_ALL_FILTERS;
236
287
Canvas *canvas = Nan::ObjectWrap::Unwrap<Canvas>(info.This ());
237
288
238
- // TODO: async / move this out
289
+ // Vector canvases, sync only
239
290
const string name = canvas->backend ()->getName ();
240
291
if (name == " pdf" || name == " svg" ) {
241
292
cairo_surface_finish (canvas->surface ());
@@ -246,8 +297,8 @@ NAN_METHOD(Canvas::ToBuffer) {
246
297
return ;
247
298
}
248
299
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 ())) {
251
302
cairo_surface_t *surface = canvas->surface ();
252
303
cairo_surface_flush (surface);
253
304
const unsigned char *data = cairo_image_surface_get_data (surface);
@@ -256,46 +307,44 @@ NAN_METHOD(Canvas::ToBuffer) {
256
307
return ;
257
308
}
258
309
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 ]);
278
314
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));
286
322
}
287
323
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 ;
294
339
}
295
340
}
296
341
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
+
299
348
closure_t *closure = (closure_t *) malloc (sizeof (closure_t ));
300
349
status = closure_init (closure, canvas, compression_level, filter);
301
350
@@ -317,34 +366,42 @@ NAN_METHOD(Canvas::ToBuffer) {
317
366
uv_queue_work (uv_default_loop (), req, ToBufferAsync, (uv_after_work_cb)ToBufferAsyncAfter);
318
367
319
368
return ;
320
- // Sync
321
- } else {
322
- closure_t closure;
323
- status = closure_init (&closure, canvas, compression_level, filter);
369
+ }
324
370
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 ]);
330
377
331
378
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);
333
383
334
384
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
+ }
341
391
} 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
+ }
346
400
}
401
+
402
+ return ;
347
403
}
404
+ #endif
348
405
}
349
406
350
407
/*
0 commit comments