Skip to content

Commit

Permalink
Initial work to fix detail loss with additional canvas needed on draw…
Browse files Browse the repository at this point in the history
…Image (#1255)

* save work

* middle-way

* fixed

* swap a line

* saved tests

* ,aybe mergeable

* ,aybe mergeable

* removed commented code, added a comment

* fixed comment

* removed float for doubles

* removed extra comment

* removed extra boolean

* added tests

* no semicolons
  • Loading branch information
asturur authored and LinusU committed Mar 28, 2019
1 parent fefcda7 commit 39aa8ab
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 26 deletions.
107 changes: 81 additions & 26 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ Context2d::Context2d(Canvas *canvas) {
_context = canvas->createCairoContext();
_layout = pango_cairo_create_layout(_context);
state = states[stateno = 0] = (canvas_state_t *) malloc(sizeof(canvas_state_t));

resetState(true);
}

Expand Down Expand Up @@ -1170,6 +1170,20 @@ NAN_METHOD(Context2d::CreateImageData){
info.GetReturnValue().Set(instance);
}

/*
* Take a transform matrix and return its components
* 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
*/
void decompose_matrix(cairo_matrix_t matrix, double *destination) {
double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
destination[0] = atan2(matrix.yx, matrix.xx);
destination[1] = sqrt(denom);
destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
destination[4] = matrix.x0;
destination[5] = matrix.y0;
}

/*
* Draw image src image to the destination (context).
*
Expand All @@ -1191,7 +1205,7 @@ NAN_METHOD(Context2d::DrawImage) {
if(!checkArgs(info, args, infoLen - 1, 1))
return;

float sx = 0
double sx = 0
, sy = 0
, sw = 0
, sh = 0
Expand Down Expand Up @@ -1266,41 +1280,73 @@ NAN_METHOD(Context2d::DrawImage) {
// Start draw
cairo_save(ctx);

// Scale src
float fx = (float) dw / sw;
float fy = (float) dh / sh;
cairo_matrix_t matrix;
double transforms[6];
cairo_get_matrix(context->context(), &matrix);
decompose_matrix(matrix, transforms);
// extract the scale value from the current transform so that we know how many pixels we
// need for our extra canvas in the drawImage operation.
double current_scale_x = abs(transforms[1]);
double current_scale_y = abs(transforms[2]);
double extra_dx = 0;
double extra_dy = 0;
double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
double fy = dh / sh * current_scale_y; // transforms[2] is scale on X
bool needScale = dw != sw || dh != sh;
bool needCut = sw != source_w || sh != source_h || sx < 0 || sy < 0;
bool needCairoClip = sx < 0 || sy < 0 || sw > source_w || sh > source_h;

bool sameCanvas = surface == context->canvas()->surface();
bool needsExtraSurface = sameCanvas || needCut || needScale || needCairoClip;
bool needsExtraSurface = sameCanvas || needCut || needScale;
cairo_surface_t *surfTemp = NULL;
cairo_t *ctxTemp = NULL;

if (needsExtraSurface) {
surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw, dh);
// we want to create the extra surface as small as possible.
// fx and fy are the total scaling we need to apply to sw, sh.
// from sw and sh we want to remove the part that is outside the source_w and soruce_h
double real_w = sw;
double real_h = sh;
double translate_x = 0;
double translate_y = 0;
// if sx or sy are negative, a part of the area represented by sw and sh is empty
// because there are empty pixels, so we cut it out.
// On the other hand if sx or sy are positive, but sw and sh extend outside the real
// source pixels, we cut the area in that case too.
if (sx < 0) {
extra_dx = -sx * fx;
real_w = sw + sx;
} else if (sx + sw > source_w) {
real_w = sw - (sx + sw - source_w);
}
if (sy < 0) {
extra_dy = -sy * fy;
real_h = sh + sy;
} else if (sy + sh > source_h) {
real_h = sh - (sy + sh - source_h);
}
// if after cutting we are still bigger than source pixels, we restrict again
if (real_w > source_w) {
real_w = source_w;
}
if (real_h > source_h) {
real_h = source_h;
}
// TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
// there are no more pixel than the one available in the source, no need to create a bigger surface.
surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
ctxTemp = cairo_create(surfTemp);
cairo_scale(ctxTemp, fx, fy);
if (needCairoClip) {
float clip_w = (std::min)(sw, source_w);
float clip_h = (std::min)(sh, source_h);
if (sx > 0) {
clip_w -= sx;
}
if (sy > 0) {
clip_h -= sy;
}
cairo_rectangle(ctxTemp, -sx , -sy , clip_w, clip_h);
cairo_clip(ctxTemp);
if (sx > 0) {
translate_x = sx;
}
cairo_set_source_surface(ctxTemp, surface, -sx, -sy);
if (sy > 0) {
translate_y = sy;
}
cairo_set_source_surface(ctxTemp, surface, -translate_x, -translate_y);
cairo_pattern_set_filter(cairo_get_source(ctxTemp), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
cairo_pattern_set_extend(cairo_get_source(ctxTemp), CAIRO_EXTEND_REFLECT);
cairo_paint_with_alpha(ctxTemp, 1);
surface = surfTemp;
}

// apply shadow if there is one
if (context->hasShadow()) {
if(context->state->shadowBlur) {
Expand Down Expand Up @@ -1334,8 +1380,17 @@ NAN_METHOD(Context2d::DrawImage) {
}
}

double scaled_dx = dx;
double scaled_dy = dy;

if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
// in this case our surface contains already current_scale_x, we need to scale back
cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
scaled_dx *= current_scale_x;
scaled_dy *= current_scale_y;
}
// Paint
cairo_set_source_surface(ctx, surface, dx, dy);
cairo_set_source_surface(ctx, surface, scaled_dx + extra_dx, scaled_dy + extra_dy);
cairo_pattern_set_filter(cairo_get_source(ctx), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
cairo_pattern_set_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
cairo_paint_with_alpha(ctx, context->state->globalAlpha);
Expand Down Expand Up @@ -1767,7 +1822,7 @@ NAN_SETTER(Context2d::SetFillStyle) {
if (Nan::New(Gradient::constructor)->HasInstance(value) ||
Nan::New(Pattern::constructor)->HasInstance(value)) {
context->_fillStyle.Reset(value);

Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
if (Nan::New(Gradient::constructor)->HasInstance(obj)){
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
Expand Down Expand Up @@ -1813,7 +1868,7 @@ NAN_SETTER(Context2d::SetStrokeStyle) {
if (Nan::New(Gradient::constructor)->HasInstance(value) ||
Nan::New(Pattern::constructor)->HasInstance(value)) {
context->_strokeStyle.Reset(value);

Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
if (Nan::New(Gradient::constructor)->HasInstance(obj)){
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
Expand Down Expand Up @@ -2438,7 +2493,7 @@ NAN_GETTER(Context2d::GetFont) {
* - size
* - unit
* - family
*/
*/

NAN_SETTER(Context2d::SetFont) {
if (!value->IsString()) return;
Expand Down
49 changes: 49 additions & 0 deletions test/public/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,6 +1287,23 @@ tests['drawImage issue #1249'] = function (ctx, done) {
img1.src = imageSrc('chrome.jpg')
}

tests['drawImage 9 arguments big numbers'] = function (ctx, done) {
var img = new Image()
ctx.imageSmoothingEnabled = false
img.onload = function () {
// we use big numbers because is over the max canvas allowed
ctx.drawImage(img, -90000, -90000, 90080, 90080, -180000, -18000, 180160, 18016)
ctx.drawImage(img, -90000, -90000, 90040, 90040, -179930, -179930, 180060, 180060)
ctx.drawImage(img, -90000, -90000, 90080, 90080, -18000, -180000, 18016, 180160)
ctx.drawImage(img, 475, 380, 90000, 90000, 20, 20, 180000, 720000)
done(null)
}
img.onerror = function () {
done(new Error('Failed to load image'))
}
img.src = imageSrc('face.jpeg')
}

tests['known bug #416'] = function (ctx, done) {
var img1 = new Image()
var img2 = new Image()
Expand Down Expand Up @@ -1934,6 +1951,17 @@ tests['drawImage(img,x,y,w,h) scale down'] = function (ctx, done) {
img.src = imageSrc('state.png')
}

tests['drawImage(img,x,y,w,h) scale down in a scaled up context'] = function (ctx, done) {
var img = new Image()
img.onload = function () {
ctx.scale(20, 20)
ctx.drawImage(img, 0, 0, 10, 10)
done(null)
}
img.onerror = done
img.src = imageSrc('state.png')
}

tests['drawImage(img,x,y,w,h) scale up'] = function (ctx, done) {
var img = new Image()
img.onload = function () {
Expand Down Expand Up @@ -2457,3 +2485,24 @@ tests['drawImage reflection bug'] = function (ctx, done) {
}
img1.src = imageSrc('chrome.jpg')
}

tests['drawImage reflection bug with skewing'] = function (ctx, done) {
var img1 = new Image()
img1.onload = function () {
ctx.transform(1.2, 1, 1.8, 1.3, 0, 0)
ctx.drawImage(img1, 60, 30, 150, 150, 0, 0, 200, 200)
ctx.setTransform(1.2, 1.8, 0.3, 0.8, 0, 0)
ctx.drawImage(img1, 30, 60, 150, 150, -5, -5, 200, 200)
done()
}
img1.src = imageSrc('chrome.jpg')
}

tests['transformed drawimage'] = function (ctx) {
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, 200, 200)
ctx.fillStyle = 'black'
ctx.fillRect(5, 5, 50, 50)
ctx.transform(1.2, 1, 1.8, 1.3, 0, 0)
ctx.drawImage(ctx.canvas, 0, 0)
}

0 comments on commit 39aa8ab

Please sign in to comment.