diff --git a/src/CanvasPattern.cc b/src/CanvasPattern.cc index e3c97a129..a2c7671d3 100644 --- a/src/CanvasPattern.cc +++ b/src/CanvasPattern.cc @@ -77,7 +77,7 @@ NAN_METHOD(Pattern::New) { } /* - * Initialize linear gradient. + * Initialize pattern. */ Pattern::Pattern(cairo_surface_t *surface, repeat_type_t repeat) { diff --git a/src/CanvasRenderingContext2d.cc b/src/CanvasRenderingContext2d.cc index 792ba40a5..5bb089be8 100755 --- a/src/CanvasRenderingContext2d.cc +++ b/src/CanvasRenderingContext2d.cc @@ -292,6 +292,67 @@ Context2d::restorePath() { cairo_path_destroy(_path); } +/* + * Create temporary surface for gradient or pattern transparency + */ +cairo_pattern_t* +create_transparent_gradient(cairo_pattern_t *source, float alpha) { + double x0; + double y0; + double x1; + double y1; + double r0; + double r1; + int count; + int i; + double offset; + double r; + double g; + double b; + double a; + cairo_pattern_t *newGradient; + cairo_pattern_type_t type = cairo_pattern_get_type(source); + cairo_pattern_get_color_stop_count(source, &count); + if (type == CAIRO_PATTERN_TYPE_LINEAR) { + cairo_pattern_get_linear_points (source, &x0, &y0, &x1, &y1); + newGradient = cairo_pattern_create_linear(x0, y0, x1, y1); + } else if (type == CAIRO_PATTERN_TYPE_RADIAL) { + cairo_pattern_get_radial_circles(source, &x0, &y0, &r0, &x1, &y1, &r1); + newGradient = cairo_pattern_create_radial(x0, y0, r0, x1, y1, r1); + } else { + Nan::ThrowError("Unexpected gradient type"); + return NULL; + } + for ( i = 0; i < count; i++ ) { + cairo_pattern_get_color_stop_rgba(source, i, &offset, &r, &g, &b, &a); + cairo_pattern_add_color_stop_rgba(newGradient, offset, r, g, b, a * alpha); + } + return newGradient; +} + +cairo_pattern_t* +create_transparent_pattern(cairo_pattern_t *source, float alpha) { + cairo_surface_t *surface; + cairo_pattern_get_surface(source, &surface); + int width = cairo_image_surface_get_width(surface); + int height = cairo_image_surface_get_height(surface); + cairo_surface_t *mask_surface = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, + width, + height); + cairo_t *mask_context = cairo_create(mask_surface); + if (cairo_status(mask_context) != CAIRO_STATUS_SUCCESS) { + Nan::ThrowError("Failed to initialize context"); + return NULL; + } + cairo_set_source(mask_context, source); + cairo_paint_with_alpha(mask_context, alpha); + cairo_destroy(mask_context); + cairo_pattern_t* newPattern = cairo_pattern_create_for_surface(mask_surface); + cairo_surface_destroy(mask_surface); + return newPattern; +} + /* * Fill and apply shadow. */ @@ -310,22 +371,42 @@ Context2d::setFillRule(v8::Local value) { void Context2d::fill(bool preserve) { + cairo_pattern_t *new_pattern; if (state->fillPattern) { - cairo_set_source(_context, state->fillPattern); + if (state->globalAlpha < 1) { + new_pattern = create_transparent_pattern(state->fillPattern, state->globalAlpha); + if (new_pattern == NULL) { + // failed to allocate; Nan::ThrowError has already been called, so return from this fn. + return; + } + cairo_set_source(_context, new_pattern); + cairo_pattern_destroy(new_pattern); + } else { + cairo_set_source(_context, state->fillPattern); + } repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->fillPattern); if (NO_REPEAT == repeat) { cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE); } else { cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT); } - // TODO repeat-x/repeat-y } else if (state->fillGradient) { - cairo_pattern_set_filter(state->fillGradient, state->patternQuality); - cairo_set_source(_context, state->fillGradient); + if (state->globalAlpha < 1) { + new_pattern = create_transparent_gradient(state->fillGradient, state->globalAlpha); + if (new_pattern == NULL) { + // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn. + return; + } + cairo_pattern_set_filter(new_pattern, state->patternQuality); + cairo_set_source(_context, new_pattern); + cairo_pattern_destroy(new_pattern); + } else { + cairo_pattern_set_filter(state->fillGradient, state->patternQuality); + cairo_set_source(_context, state->fillGradient); + } } else { setSourceRGBA(state->fill); } - if (preserve) { hasShadow() ? shadow(cairo_fill_preserve) @@ -343,8 +424,19 @@ Context2d::fill(bool preserve) { void Context2d::stroke(bool preserve) { + cairo_pattern_t *new_pattern; if (state->strokePattern) { - cairo_set_source(_context, state->strokePattern); + if (state->globalAlpha < 1) { + new_pattern = create_transparent_pattern(state->strokePattern, state->globalAlpha); + if (new_pattern == NULL) { + // failed to allocate; Nan::ThrowError has already been called, so return from this fn. + return; + } + cairo_set_source(_context, new_pattern); + cairo_pattern_destroy(new_pattern); + } else { + cairo_set_source(_context, state->strokePattern); + } repeat_type_t repeat = Pattern::get_repeat_type_for_cairo_pattern(state->strokePattern); if (NO_REPEAT == repeat) { cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_NONE); @@ -352,8 +444,19 @@ Context2d::stroke(bool preserve) { cairo_pattern_set_extend(cairo_get_source(_context), CAIRO_EXTEND_REPEAT); } } else if (state->strokeGradient) { - cairo_pattern_set_filter(state->strokeGradient, state->patternQuality); - cairo_set_source(_context, state->strokeGradient); + if (state->globalAlpha < 1) { + new_pattern = create_transparent_gradient(state->strokeGradient, state->globalAlpha); + if (new_pattern == NULL) { + // failed to recognize gradient; Nan::ThrowError has already been called, so return from this fn. + return; + } + cairo_pattern_set_filter(new_pattern, state->patternQuality); + cairo_set_source(_context, new_pattern); + cairo_pattern_destroy(new_pattern); + } else { + cairo_pattern_set_filter(state->strokeGradient, state->patternQuality); + cairo_set_source(_context, state->strokeGradient); + } } else { setSourceRGBA(state->stroke); } diff --git a/test/public/tests.js b/test/public/tests.js index 6a8bd2a1b..528810273 100644 --- a/test/public/tests.js +++ b/test/public/tests.js @@ -371,6 +371,38 @@ tests['clip() 2'] = function(ctx){ } }; +tests['createPattern()'] = function(ctx, done) { + var img = new Image; + img.onload = function(){ + var pattern = ctx.createPattern(img, 'repeat'); + ctx.scale(0.1, 0.1); + ctx.fillStyle = pattern; + ctx.fillRect(100, 100, 800, 800); + ctx.strokeStyle = pattern; + ctx.lineWidth = 200 + ctx.strokeRect(1100, 1100, 800, 800); + done(); + }; + img.src = 'face.jpeg'; +}; + +tests['createPattern() with globalAlpha'] = function(ctx, done) { + var img = new Image; + img.onload = function(){ + var pattern = ctx.createPattern(img, 'repeat'); + ctx.scale(0.1, 0.1); + ctx.globalAlpha = 0.6; + ctx.fillStyle = pattern; + ctx.fillRect(100, 100, 800, 800); + ctx.globalAlpha = 0.2; + ctx.strokeStyle = pattern; + ctx.lineWidth = 200 + ctx.strokeRect(1100, 1100, 800, 800); + done(); + }; + img.src = 'face.jpeg'; +}; + tests['createPattern() no-repeat'] = function(ctx, done) { var img = new Image; img.onload = function(){ @@ -408,6 +440,56 @@ tests['createLinearGradient()'] = function(ctx){ ctx.fillStyle = '#13b575'; ctx.fillStyle = ctx.fillStyle; ctx.fillRect(65,65,20,20); + + var lingrad = ctx.createLinearGradient(0,0,200,0); + lingrad.addColorStop(0, 'rgba(0,255,0,0.5)'); + lingrad.addColorStop(0.33, 'rgba(255,255,0,0.5)'); + lingrad.addColorStop(0.66, 'rgba(0,255,255,0.5)'); + lingrad.addColorStop(1, 'rgba(255,0,255,0.5)'); + ctx.fillStyle = lingrad; + ctx.fillRect(0,170,200,30); +}; + +tests['createLinearGradient() with opacity'] = function(ctx){ + var lingrad = ctx.createLinearGradient(0,0,0,200); + lingrad.addColorStop(0, '#00FF00'); + lingrad.addColorStop(0.33, '#FF0000'); + lingrad.addColorStop(0.66, '#0000FF'); + lingrad.addColorStop(1, '#00FFFF'); + ctx.fillStyle = lingrad; + ctx.strokeStyle = lingrad; + ctx.lineWidth = 10; + ctx.globalAlpha = 0.4; + ctx.strokeRect(5,5,190,190); + ctx.fillRect(0,0,50,50); + ctx.globalAlpha = 0.6; + ctx.strokeRect(35,35,130,130); + ctx.fillRect(50,50,50,50); + ctx.globalAlpha = 0.8; + ctx.strokeRect(65,65,70,70); + ctx.fillRect(100,100,50,50); + ctx.globalAlpha = 0.95; + ctx.fillRect(150,150,50,50); +}; + +tests['createLinearGradient() and transforms'] = function(ctx){ + var lingrad = ctx.createLinearGradient(0,-100,0,100); + lingrad.addColorStop(0, '#00FF00'); + lingrad.addColorStop(0.33, '#FF0000'); + lingrad.addColorStop(0.66, '#0000FF'); + lingrad.addColorStop(1, '#00FFFF'); + ctx.fillStyle = lingrad; + ctx.translate(100, 100); + ctx.beginPath(); + ctx.moveTo(-100, -100); + ctx.lineTo(100, -100); + ctx.lineTo(100, 100); + ctx.lineTo(-100, 100); + ctx.closePath(); + ctx.globalAlpha = 0.5; + ctx.rotate(1.570795); + ctx.scale(0.6, 0.6); + ctx.fill(); }; tests['createRadialGradient()'] = function(ctx){ @@ -447,6 +529,17 @@ tests['globalAlpha'] = function(ctx){ ctx.globalAlpha = 0.5; ctx.fillStyle = 'rgba(0,0,0,0.5)'; ctx.strokeRect(0,0,50,50); + ctx.fillRect(70,0,50,50); + + ctx.globalAlpha = 0.25; + ctx.fillStyle = 'rgba(0,0,0,1)'; + ctx.fillRect(70,70,50,50); + + ctx.globalAlpha = 1; + ctx.fillStyle = 'rgba(0,0,0,0.25)'; + ctx.fillRect(70,140,50,50); + + ctx.globalAlpha = 0.8; ctx.fillRect(20,20,20,20);