Skip to content

Commit 39aa8ab

Browse files
astururLinusU
authored andcommitted
Initial work to fix detail loss with additional canvas needed on drawImage (#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
1 parent fefcda7 commit 39aa8ab

File tree

2 files changed

+130
-26
lines changed

2 files changed

+130
-26
lines changed

src/CanvasRenderingContext2d.cc

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ Context2d::Context2d(Canvas *canvas) {
187187
_context = canvas->createCairoContext();
188188
_layout = pango_cairo_create_layout(_context);
189189
state = states[stateno = 0] = (canvas_state_t *) malloc(sizeof(canvas_state_t));
190-
190+
191191
resetState(true);
192192
}
193193

@@ -1170,6 +1170,20 @@ NAN_METHOD(Context2d::CreateImageData){
11701170
info.GetReturnValue().Set(instance);
11711171
}
11721172

1173+
/*
1174+
* Take a transform matrix and return its components
1175+
* 0: angle, 1: scaleX, 2: scaleY, 3: skewX, 4: translateX, 5: translateY
1176+
*/
1177+
void decompose_matrix(cairo_matrix_t matrix, double *destination) {
1178+
double denom = pow(matrix.xx, 2) + pow(matrix.yx, 2);
1179+
destination[0] = atan2(matrix.yx, matrix.xx);
1180+
destination[1] = sqrt(denom);
1181+
destination[2] = (matrix.xx * matrix.yy - matrix.xy * matrix.yx) / destination[1];
1182+
destination[3] = atan2(matrix.xx * matrix.xy + matrix.yx * matrix.yy, denom);
1183+
destination[4] = matrix.x0;
1184+
destination[5] = matrix.y0;
1185+
}
1186+
11731187
/*
11741188
* Draw image src image to the destination (context).
11751189
*
@@ -1191,7 +1205,7 @@ NAN_METHOD(Context2d::DrawImage) {
11911205
if(!checkArgs(info, args, infoLen - 1, 1))
11921206
return;
11931207

1194-
float sx = 0
1208+
double sx = 0
11951209
, sy = 0
11961210
, sw = 0
11971211
, sh = 0
@@ -1266,41 +1280,73 @@ NAN_METHOD(Context2d::DrawImage) {
12661280
// Start draw
12671281
cairo_save(ctx);
12681282

1269-
// Scale src
1270-
float fx = (float) dw / sw;
1271-
float fy = (float) dh / sh;
1283+
cairo_matrix_t matrix;
1284+
double transforms[6];
1285+
cairo_get_matrix(context->context(), &matrix);
1286+
decompose_matrix(matrix, transforms);
1287+
// extract the scale value from the current transform so that we know how many pixels we
1288+
// need for our extra canvas in the drawImage operation.
1289+
double current_scale_x = abs(transforms[1]);
1290+
double current_scale_y = abs(transforms[2]);
1291+
double extra_dx = 0;
1292+
double extra_dy = 0;
1293+
double fx = dw / sw * current_scale_x; // transforms[1] is scale on X
1294+
double fy = dh / sh * current_scale_y; // transforms[2] is scale on X
12721295
bool needScale = dw != sw || dh != sh;
12731296
bool needCut = sw != source_w || sh != source_h || sx < 0 || sy < 0;
1274-
bool needCairoClip = sx < 0 || sy < 0 || sw > source_w || sh > source_h;
1275-
12761297
bool sameCanvas = surface == context->canvas()->surface();
1277-
bool needsExtraSurface = sameCanvas || needCut || needScale || needCairoClip;
1298+
bool needsExtraSurface = sameCanvas || needCut || needScale;
12781299
cairo_surface_t *surfTemp = NULL;
12791300
cairo_t *ctxTemp = NULL;
12801301

12811302
if (needsExtraSurface) {
1282-
surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, dw, dh);
1303+
// we want to create the extra surface as small as possible.
1304+
// fx and fy are the total scaling we need to apply to sw, sh.
1305+
// from sw and sh we want to remove the part that is outside the source_w and soruce_h
1306+
double real_w = sw;
1307+
double real_h = sh;
1308+
double translate_x = 0;
1309+
double translate_y = 0;
1310+
// if sx or sy are negative, a part of the area represented by sw and sh is empty
1311+
// because there are empty pixels, so we cut it out.
1312+
// On the other hand if sx or sy are positive, but sw and sh extend outside the real
1313+
// source pixels, we cut the area in that case too.
1314+
if (sx < 0) {
1315+
extra_dx = -sx * fx;
1316+
real_w = sw + sx;
1317+
} else if (sx + sw > source_w) {
1318+
real_w = sw - (sx + sw - source_w);
1319+
}
1320+
if (sy < 0) {
1321+
extra_dy = -sy * fy;
1322+
real_h = sh + sy;
1323+
} else if (sy + sh > source_h) {
1324+
real_h = sh - (sy + sh - source_h);
1325+
}
1326+
// if after cutting we are still bigger than source pixels, we restrict again
1327+
if (real_w > source_w) {
1328+
real_w = source_w;
1329+
}
1330+
if (real_h > source_h) {
1331+
real_h = source_h;
1332+
}
1333+
// TODO: find a way to limit the surfTemp to real_w and real_h if fx and fy are bigger than 1.
1334+
// there are no more pixel than the one available in the source, no need to create a bigger surface.
1335+
surfTemp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, round(real_w * fx), round(real_h * fy));
12831336
ctxTemp = cairo_create(surfTemp);
12841337
cairo_scale(ctxTemp, fx, fy);
1285-
if (needCairoClip) {
1286-
float clip_w = (std::min)(sw, source_w);
1287-
float clip_h = (std::min)(sh, source_h);
1288-
if (sx > 0) {
1289-
clip_w -= sx;
1290-
}
1291-
if (sy > 0) {
1292-
clip_h -= sy;
1293-
}
1294-
cairo_rectangle(ctxTemp, -sx , -sy , clip_w, clip_h);
1295-
cairo_clip(ctxTemp);
1338+
if (sx > 0) {
1339+
translate_x = sx;
12961340
}
1297-
cairo_set_source_surface(ctxTemp, surface, -sx, -sy);
1341+
if (sy > 0) {
1342+
translate_y = sy;
1343+
}
1344+
cairo_set_source_surface(ctxTemp, surface, -translate_x, -translate_y);
12981345
cairo_pattern_set_filter(cairo_get_source(ctxTemp), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
12991346
cairo_pattern_set_extend(cairo_get_source(ctxTemp), CAIRO_EXTEND_REFLECT);
13001347
cairo_paint_with_alpha(ctxTemp, 1);
13011348
surface = surfTemp;
13021349
}
1303-
13041350
// apply shadow if there is one
13051351
if (context->hasShadow()) {
13061352
if(context->state->shadowBlur) {
@@ -1334,8 +1380,17 @@ NAN_METHOD(Context2d::DrawImage) {
13341380
}
13351381
}
13361382

1383+
double scaled_dx = dx;
1384+
double scaled_dy = dy;
1385+
1386+
if (needsExtraSurface && (current_scale_x != 1 || current_scale_y != 1)) {
1387+
// in this case our surface contains already current_scale_x, we need to scale back
1388+
cairo_scale(ctx, 1 / current_scale_x, 1 / current_scale_y);
1389+
scaled_dx *= current_scale_x;
1390+
scaled_dy *= current_scale_y;
1391+
}
13371392
// Paint
1338-
cairo_set_source_surface(ctx, surface, dx, dy);
1393+
cairo_set_source_surface(ctx, surface, scaled_dx + extra_dx, scaled_dy + extra_dy);
13391394
cairo_pattern_set_filter(cairo_get_source(ctx), context->state->imageSmoothingEnabled ? context->state->patternQuality : CAIRO_FILTER_NEAREST);
13401395
cairo_pattern_set_extend(cairo_get_source(ctx), CAIRO_EXTEND_NONE);
13411396
cairo_paint_with_alpha(ctx, context->state->globalAlpha);
@@ -1767,7 +1822,7 @@ NAN_SETTER(Context2d::SetFillStyle) {
17671822
if (Nan::New(Gradient::constructor)->HasInstance(value) ||
17681823
Nan::New(Pattern::constructor)->HasInstance(value)) {
17691824
context->_fillStyle.Reset(value);
1770-
1825+
17711826
Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
17721827
if (Nan::New(Gradient::constructor)->HasInstance(obj)){
17731828
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
@@ -1813,7 +1868,7 @@ NAN_SETTER(Context2d::SetStrokeStyle) {
18131868
if (Nan::New(Gradient::constructor)->HasInstance(value) ||
18141869
Nan::New(Pattern::constructor)->HasInstance(value)) {
18151870
context->_strokeStyle.Reset(value);
1816-
1871+
18171872
Local<Object> obj = Nan::To<Object>(value).ToLocalChecked();
18181873
if (Nan::New(Gradient::constructor)->HasInstance(obj)){
18191874
Gradient *grad = Nan::ObjectWrap::Unwrap<Gradient>(obj);
@@ -2438,7 +2493,7 @@ NAN_GETTER(Context2d::GetFont) {
24382493
* - size
24392494
* - unit
24402495
* - family
2441-
*/
2496+
*/
24422497

24432498
NAN_SETTER(Context2d::SetFont) {
24442499
if (!value->IsString()) return;

test/public/tests.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,23 @@ tests['drawImage issue #1249'] = function (ctx, done) {
12871287
img1.src = imageSrc('chrome.jpg')
12881288
}
12891289

1290+
tests['drawImage 9 arguments big numbers'] = function (ctx, done) {
1291+
var img = new Image()
1292+
ctx.imageSmoothingEnabled = false
1293+
img.onload = function () {
1294+
// we use big numbers because is over the max canvas allowed
1295+
ctx.drawImage(img, -90000, -90000, 90080, 90080, -180000, -18000, 180160, 18016)
1296+
ctx.drawImage(img, -90000, -90000, 90040, 90040, -179930, -179930, 180060, 180060)
1297+
ctx.drawImage(img, -90000, -90000, 90080, 90080, -18000, -180000, 18016, 180160)
1298+
ctx.drawImage(img, 475, 380, 90000, 90000, 20, 20, 180000, 720000)
1299+
done(null)
1300+
}
1301+
img.onerror = function () {
1302+
done(new Error('Failed to load image'))
1303+
}
1304+
img.src = imageSrc('face.jpeg')
1305+
}
1306+
12901307
tests['known bug #416'] = function (ctx, done) {
12911308
var img1 = new Image()
12921309
var img2 = new Image()
@@ -1934,6 +1951,17 @@ tests['drawImage(img,x,y,w,h) scale down'] = function (ctx, done) {
19341951
img.src = imageSrc('state.png')
19351952
}
19361953

1954+
tests['drawImage(img,x,y,w,h) scale down in a scaled up context'] = function (ctx, done) {
1955+
var img = new Image()
1956+
img.onload = function () {
1957+
ctx.scale(20, 20)
1958+
ctx.drawImage(img, 0, 0, 10, 10)
1959+
done(null)
1960+
}
1961+
img.onerror = done
1962+
img.src = imageSrc('state.png')
1963+
}
1964+
19371965
tests['drawImage(img,x,y,w,h) scale up'] = function (ctx, done) {
19381966
var img = new Image()
19391967
img.onload = function () {
@@ -2457,3 +2485,24 @@ tests['drawImage reflection bug'] = function (ctx, done) {
24572485
}
24582486
img1.src = imageSrc('chrome.jpg')
24592487
}
2488+
2489+
tests['drawImage reflection bug with skewing'] = function (ctx, done) {
2490+
var img1 = new Image()
2491+
img1.onload = function () {
2492+
ctx.transform(1.2, 1, 1.8, 1.3, 0, 0)
2493+
ctx.drawImage(img1, 60, 30, 150, 150, 0, 0, 200, 200)
2494+
ctx.setTransform(1.2, 1.8, 0.3, 0.8, 0, 0)
2495+
ctx.drawImage(img1, 30, 60, 150, 150, -5, -5, 200, 200)
2496+
done()
2497+
}
2498+
img1.src = imageSrc('chrome.jpg')
2499+
}
2500+
2501+
tests['transformed drawimage'] = function (ctx) {
2502+
ctx.fillStyle = 'white'
2503+
ctx.fillRect(0, 0, 200, 200)
2504+
ctx.fillStyle = 'black'
2505+
ctx.fillRect(5, 5, 50, 50)
2506+
ctx.transform(1.2, 1, 1.8, 1.3, 0, 0)
2507+
ctx.drawImage(ctx.canvas, 0, 0)
2508+
}

0 commit comments

Comments
 (0)