From acba6ae15f56a38a593566c9bbfcfd89070e8f8c Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Fri, 23 Jan 2015 11:49:26 +0000 Subject: [PATCH 01/12] New test illustrating that canvas implementation doesn't translate the shadowOffset. --- test/public/tests.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/public/tests.js b/test/public/tests.js index 7768d16..0b18c13 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -1327,6 +1327,38 @@ tests['shadowOffset{X,Y} negative'] = function(ctx){ ctx.fillRect(150,150,20,20); }; +tests['shadowOffset{X,Y} transform'] = function(ctx){ + ctx.translate(100, 0); + ctx.scale(.75,.75); + ctx.rotate(Math.PI/4); + + ctx.fillRect(150,10,20,20); + + ctx.lineTo(20,5); + ctx.lineTo(100,5); + ctx.stroke(); + + ctx.shadowColor = '#c00'; + ctx.shadowBlur = 5; + ctx.shadowOffsetX = 10; + ctx.shadowOffsetY = 10; + ctx.fillRect(20,20,100,100); + + ctx.beginPath(); + ctx.lineTo(20,150); + ctx.lineTo(100,150); + ctx.stroke(); + + ctx.shadowBlur = 0; + + ctx.beginPath(); + ctx.lineTo(20,180); + ctx.lineTo(100,180); + ctx.stroke(); + + ctx.fillRect(150,150,20,20); +}; + tests['shadowBlur values'] = function(ctx){ ctx.fillRect(150,10,20,20); From 95e342d2593a32f412fd7b087de992a88dc9d301 Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Fri, 23 Jan 2015 11:49:52 +0000 Subject: [PATCH 02/12] Fix to ensure shadowOffset is unaffected by the current transform. --- src/CanvasRenderingContext2d.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 57ca468..77504c7 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -349,11 +349,15 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { cairo_path_t *path = cairo_copy_path_flat(_context); cairo_save(_context); - // Offset + // Offset (always unaffected by current transform) + cairo_matrix_t current_matrix; + cairo_get_matrix(_context, ¤t_matrix); + cairo_identity_matrix(_context); cairo_translate( _context , state->shadowOffsetX , state->shadowOffsetY); + cairo_transform(_context, ¤t_matrix); // Apply shadow cairo_push_group(_context); From cbf3da4900e6a3e017a28d1162ed92c01050f40d Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Fri, 23 Jan 2015 16:09:52 +0000 Subject: [PATCH 03/12] Add PDF button to test page to easily generate PDF version of the test image. --- test/public/app.js | 27 +++++++++++++++++++++++++++ test/server.js | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/test/public/app.js b/test/public/app.js index c7f5692..555171f 100644 --- a/test/public/app.js +++ b/test/public/app.js @@ -26,6 +26,32 @@ function text(str) { return document.createTextNode(str); } +function pdfForm(fn, canvas) { + var form = create('form') + , input = create('input') + , submit = create('input'); + + form.setAttribute('action', '/pdf'); + form.setAttribute('method', 'post'); + form.setAttribute('target', '_blank'); + + input.setAttribute('type', 'hidden'); + input.setAttribute('name', 'json'); + input.setAttribute('value', JSON.stringify({ + fn: fn.toString() + , width: canvas.width + , height: canvas.height + })); + + submit.setAttribute('type', 'submit'); + submit.setAttribute('value', 'PDF'); + + form.appendChild(input); + form.appendChild(submit); + + return form; +} + function clearTests() { var table = get('tests'); table.removeChild(table.children[1]); @@ -47,6 +73,7 @@ function runTests() { tds[1].appendChild(canvas); tds[2].appendChild(create('h3', name)); + tds[2].appendChild(pdfForm(fn, canvas)); tr.appendChild(tds[0]); tr.appendChild(tds[1]); diff --git a/test/server.js b/test/server.js index e25572c..3e8c21f 100644 --- a/test/server.js +++ b/test/server.js @@ -28,7 +28,7 @@ app.get('/', function(req, res){ res.render('tests'); }); -app.post('/render', function(req, res, next){ +function testFn(req){ // Normalize state.png as ./public/state.png // no good way around this at the moment req.body.fn = req.body.fn @@ -36,10 +36,18 @@ app.post('/render', function(req, res, next){ .replace("'face.jpeg'", "'" + __dirname + "/public/face.jpeg'"); // Do not try this at home :) - var fn = eval('(' + req.body.fn + ')') - , width = req.body.width - , height = req.body.height - , canvas = new Canvas(width, height) + return eval('(' + req.body.fn + ')'); +} + +function createCanvas(req, type){ + var width = req.body.width + , height = req.body.height; + return new Canvas(width, height, type); +} + +app.post('/render', function(req, res, next){ + var fn = testFn(req) + , canvas = createCanvas(req) , ctx = canvas.getContext('2d') , start = new Date; @@ -55,6 +63,24 @@ app.post('/render', function(req, res, next){ : fn(ctx), done(); }); +app.post('/pdf', function(req, res, next){ + req.body = JSON.parse(req.body.json); + var fn = testFn(req) + , canvas = createCanvas(req, 'pdf') + , ctx = canvas.getContext('2d'); + + function done(){ + res.writeHead(200, {'Content-Type' : 'application/pdf'}); + res.write(canvas.toBuffer()); + res.end(); + } + + 2 == fn.length + ? fn(ctx, done) + : fn(ctx), done(); +}); + + var port = parseInt(process.argv[2] || '4000', 10); app.listen(port); console.log('Test server listening on port %d', port); From 5d9582b81e057c8fc8077c92a4bc0be28f887f24 Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Sat, 24 Jan 2015 09:08:22 +0000 Subject: [PATCH 04/12] Add recommended calls to flush and dirty buffer, as per http://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-image-surface-get-data. --- src/CanvasRenderingContext2d.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 57ca468..c43f64d 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -418,6 +418,7 @@ Context2d::blur(cairo_surface_t *surface, int radius) { int height = cairo_image_surface_get_height( surface ); unsigned* precalc = (unsigned*)malloc(width*height*sizeof(unsigned)); + cairo_surface_flush( surface ); unsigned char* src = cairo_image_surface_get_data( surface ); double mul=1.f/((radius*2)*(radius*2)); int channel; @@ -465,6 +466,7 @@ Context2d::blur(cairo_surface_t *surface, int radius) { } } + cairo_surface_mark_dirty(surface); free(precalc); } From 0dcc70b2b339ae404e571327e6898731b582aa7a Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Sat, 24 Jan 2015 09:09:23 +0000 Subject: [PATCH 05/12] Create a new image surface to render blurred shadows to, this means that vector formats like PDF will now render blurs. --- src/CanvasRenderingContext2d.cc | 70 +++++++++++++++++++++++++++------ 1 file changed, 58 insertions(+), 12 deletions(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index c43f64d..81c5415 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -349,22 +349,68 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { cairo_path_t *path = cairo_copy_path_flat(_context); cairo_save(_context); - // Offset - cairo_translate( - _context - , state->shadowOffsetX - , state->shadowOffsetY); - - // Apply shadow cairo_push_group(_context); - cairo_new_path(_context); - cairo_append_path(_context, path); - setSourceRGBA(state->shadow); - fn(_context); // No need to invoke blur if shadowBlur is 0 if (state->shadowBlur) { - blur(cairo_get_group_target(_context), state->shadowBlur); + // find out extent of path + double x1, y1, x2, y2; + if (fn == cairo_fill || fn == cairo_fill_preserve) { + cairo_fill_extents(_context, &x1, &y1, &x2, &y2); + } else { + cairo_stroke_extents(_context, &x1, &y1, &x2, &y2); + } + + // create new image surface that size + padding for blurring + int pad = state->shadowBlur * 2; + int resolution = 4; // approx 300px/72pt + cairo_surface_t *surface = cairo_get_group_target(_context); + cairo_surface_t *shadow_surface = cairo_surface_create_similar_image(surface, + CAIRO_FORMAT_ARGB32, + (x2-x1 + 2*pad)*resolution, + (y2-y1 + 2*pad)*resolution); + cairo_t *shadow_context = cairo_create(shadow_surface); + + // transform path to the right place + cairo_matrix_t matrix; + cairo_get_matrix(_context, &matrix); + cairo_scale(shadow_context, resolution, resolution); + cairo_set_matrix(shadow_context, &matrix); + cairo_translate(shadow_context, pad-x1, pad-y1); + + // draw the path and blur + cairo_new_path(shadow_context); + cairo_append_path(shadow_context, path); + cairo_set_source_rgba( + shadow_context + , state->shadow.r + , state->shadow.g + , state->shadow.b + , state->shadow.a * state->globalAlpha); + fn(shadow_context); + blur(shadow_surface, state->shadowBlur); // *resolution? + + // paint to original context + cairo_set_source_surface(_context, shadow_surface, + x1 - pad + state->shadowOffsetX, + y1 - pad + state->shadowOffsetY); + cairo_scale(shadow_context, 1/resolution, 1/resolution); + cairo_paint(_context); + cairo_destroy(shadow_context); + cairo_surface_destroy(shadow_surface); + } else { + // Offset + cairo_translate( + _context + , state->shadowOffsetX + , state->shadowOffsetY); + + // Apply shadow + cairo_new_path(_context); + cairo_append_path(_context, path); + setSourceRGBA(state->shadow); + + fn(_context); } // Paint the shadow From 617aabc6ae6b49850233833e385645cb7d2e8528 Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Sat, 24 Jan 2015 09:27:25 +0000 Subject: [PATCH 06/12] Make the shadow radius more accurately match the browser's, making use of sigma scale as used in SKIA: https://github.com/google/skia/blob/master/src/effects/SkBlurMask.cpp#L26. --- src/CanvasRenderingContext2d.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 57ca468..4398058 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -412,7 +412,7 @@ void Context2d::blur(cairo_surface_t *surface, int radius) { // Steve Hanov, 2009 // Released into the public domain. - --radius; + radius = radius * 0.57735f + 0.5f; // get width, height int width = cairo_image_surface_get_width( surface ); int height = cairo_image_surface_get_height( surface ); From 2b366c684e16fda05ca55e4da532445efeaaed8d Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Sat, 24 Jan 2015 10:26:41 +0000 Subject: [PATCH 07/12] The +1 on the offset seems to match the browser's output better, but I can't work out why it would be needed (unless it's pixel alignment related). --- src/CanvasRenderingContext2d.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index bd1c3d0..0445877 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -394,8 +394,8 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { // paint to original context cairo_set_source_surface(_context, shadow_surface, - (x1 - pad + state->shadowOffsetX), - (y1 - pad + state->shadowOffsetY)); + x1 - pad + state->shadowOffsetX + 1, + y1 - pad + state->shadowOffsetY + 1); cairo_paint(_context); cairo_destroy(shadow_context); cairo_surface_destroy(shadow_surface); From 9187d4535aef385934da1729b13fc44e8f92ae8b Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Sat, 24 Jan 2015 16:12:32 +0000 Subject: [PATCH 08/12] Test showing issue #133, that images don't have shadows. --- test/public/star.png | Bin 0 -> 4785 bytes test/public/tests.js | 14 ++++++++++++++ test/server.js | 3 ++- 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/public/star.png diff --git a/test/public/star.png b/test/public/star.png new file mode 100644 index 0000000000000000000000000000000000000000..2ae3b4dafa16160cb8effe5c8b10abfd7ae2c759 GIT binary patch literal 4785 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 b0v^a=Aa5SOtg`OEKPZ5 Date: Sat, 24 Jan 2015 17:48:29 +0000 Subject: [PATCH 09/12] Simple image shadow (no blurring or handling current transforms) based on image's alpha channel. --- src/CanvasRenderingContext2d.cc | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 0445877..3a1d7a1 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -725,11 +725,6 @@ NAN_METHOD(Context2d::DrawImage) { // Start draw cairo_save(ctx); - context->savePath(); - cairo_rectangle(ctx, dx, dy, dw, dh); - cairo_clip(ctx); - context->restorePath(); - // Scale src if (dw != sw || dh != sh) { float fx = (float) dw / sw; @@ -739,6 +734,18 @@ NAN_METHOD(Context2d::DrawImage) { dy /= fy; } + if (context->hasShadow()) { + context->setSourceRGBA(context->state->shadow); + cairo_mask_surface(ctx, surface, + dx - sx + context->state->shadowOffsetX, + dy - sy + context->state->shadowOffsetY); + } + + context->savePath(); + cairo_rectangle(ctx, dx, dy, dw, dh); + cairo_clip(ctx); + context->restorePath(); + // Paint cairo_set_source_surface(ctx, surface, dx - sx, dy - sy); cairo_pattern_set_filter(cairo_get_source(ctx), context->state->patternQuality); From 822d822066c865107f47e2e394ab1da2cb8aef3b Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Mon, 26 Jan 2015 08:26:48 +0000 Subject: [PATCH 10/12] Refactor setSourceRGBA to allow the context to be supplied. --- src/CanvasRenderingContext2d.cc | 20 ++++++++++++-------- src/CanvasRenderingContext2d.h | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 3a1d7a1..5fb2c69 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -383,12 +383,7 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { // draw the path and blur cairo_new_path(shadow_context); cairo_append_path(shadow_context, path); - cairo_set_source_rgba( - shadow_context - , state->shadow.r - , state->shadow.g - , state->shadow.b - , state->shadow.a * state->globalAlpha); + setSourceRGBA(shadow_context, state->shadow); fn(shadow_context); blur(shadow_surface, state->shadowBlur); @@ -429,13 +424,22 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { } /* - * Set source RGBA. + * Set source RGBA for the current context */ void Context2d::setSourceRGBA(rgba_t color) { + setSourceRGBA(_context, color); +} + +/* + * Set source RGBA + */ + +void +Context2d::setSourceRGBA(cairo_t *ctx, rgba_t color) { cairo_set_source_rgba( - _context + ctx , color.r , color.g , color.b diff --git a/src/CanvasRenderingContext2d.h b/src/CanvasRenderingContext2d.h index c29ae3b..b5920a5 100644 --- a/src/CanvasRenderingContext2d.h +++ b/src/CanvasRenderingContext2d.h @@ -150,6 +150,7 @@ class Context2d: public node::ObjectWrap { inline Canvas *canvas(){ return _canvas; } inline bool hasShadow(); void inline setSourceRGBA(rgba_t color); + void inline setSourceRGBA(cairo_t *ctx, rgba_t color); void setTextPath(const char *str, double x, double y); void blur(cairo_surface_t *surface, int radius); void shadow(void (fn)(cairo_t *cr)); From 6e829af121e86fd6dcb4cfd59f60a678d6088c37 Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Mon, 26 Jan 2015 09:09:03 +0000 Subject: [PATCH 11/12] Add another shadow/transform test. --- test/public/tests.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/public/tests.js b/test/public/tests.js index 19b15cf..92aa1ba 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -1510,6 +1510,19 @@ tests['shadow strokeText()'] = function(ctx){ ctx.strokeText("Shadow", 100, 100); }; +tests['shadow transform text'] = function(ctx){ + ctx.shadowColor = '#c0c'; + ctx.shadowBlur = 4; + ctx.shadowOffsetX = 6; + ctx.shadowOffsetY = 10; + ctx.textAlign = 'center'; + ctx.font = '35px Arial'; + ctx.scale(2, 2); + ctx.strokeText("Sha", 33, 40); + ctx.rotate(Math.PI/2); + ctx.fillText("dow", 50, -72); +}; + tests['shadow image'] = function(ctx, done){ var img = new Image; img.onload = function(){ From 8c6984a0999253a22fbd74713d4823da8b2cdd48 Mon Sep 17 00:00:00 2001 From: Andy Wood Date: Mon, 26 Jan 2015 09:09:49 +0000 Subject: [PATCH 12/12] Fix issue with line width not being correct in stroked shadows. --- src/CanvasRenderingContext2d.cc | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 5fb2c69..dfc55ee 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -368,12 +368,14 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { } // create new image surface that size + padding for blurring + double dx = x2-x1, dy = y2-y1; + cairo_user_to_device_distance(_context, &dx, &dy); int pad = state->shadowBlur * 2; cairo_surface_t *surface = cairo_get_group_target(_context); cairo_surface_t *shadow_surface = cairo_surface_create_similar_image(surface, CAIRO_FORMAT_ARGB32, - x2-x1 + 2*pad, - y2-y1 + 2*pad); + dx + 2 * pad, + dy + 2 * pad); cairo_t *shadow_context = cairo_create(shadow_surface); // transform path to the right place @@ -381,6 +383,7 @@ Context2d::shadow(void (fn)(cairo_t *cr)) { cairo_transform(shadow_context, &path_matrix); // draw the path and blur + cairo_set_line_width(shadow_context, cairo_get_line_width(_context)); cairo_new_path(shadow_context); cairo_append_path(shadow_context, path); setSourceRGBA(shadow_context, state->shadow);