Skip to content

Commit

Permalink
Add support for context globalAlpha for gradients and patterns (#1064)
Browse files Browse the repository at this point in the history
Fixes #1061
  • Loading branch information
asturur authored and zbjornson committed Dec 28, 2017
1 parent 08a70e2 commit a815257
Show file tree
Hide file tree
Showing 3 changed files with 205 additions and 9 deletions.
2 changes: 1 addition & 1 deletion src/CanvasPattern.cc
Expand Up @@ -77,7 +77,7 @@ NAN_METHOD(Pattern::New) {
}

/*
* Initialize linear gradient.
* Initialize pattern.
*/

Pattern::Pattern(cairo_surface_t *surface, repeat_type_t repeat) {
Expand Down
119 changes: 111 additions & 8 deletions src/CanvasRenderingContext2d.cc
Expand Up @@ -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.
*/
Expand All @@ -310,22 +371,42 @@ Context2d::setFillRule(v8::Local<v8::Value> 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)
Expand All @@ -343,17 +424,39 @@ 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);
} else {
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);
}
Expand Down
93 changes: 93 additions & 0 deletions test/public/tests.js
Expand Up @@ -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(){
Expand Down Expand Up @@ -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){
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit a815257

Please sign in to comment.