Skip to content

Commit 2402410

Browse files
Philippe Plantierchearon
authored andcommitted
return transparent black pixels when getting image data outside the canvas
1 parent bacf048 commit 2402410

File tree

4 files changed

+587
-90
lines changed

4 files changed

+587
-90
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ project adheres to [Semantic Versioning](http://semver.org/).
1616

1717
### Fixed
1818
* Fix a crash in `getImageData` when the rectangle is entirely outside the canvas. ([#2024](https://github.com/Automattic/node-canvas/issues/2024))
19+
* Fix `getImageData` cropping the resulting `ImageData` when the given rectangle is partly outside the canvas. ([#1849](https://github.com/Automattic/node-canvas/issues/1849))
1920

2021
3.0.1
2122
==================

src/CanvasRenderingContext2d.cc

Lines changed: 90 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,17 +1011,22 @@ Context2d::GetImageData(const Napi::CallbackInfo& info) {
10111011
sh = -sh;
10121012
}
10131013

1014-
if (sx + sw > width) sw = width - sx;
1015-
if (sy + sh > height) sh = height - sy;
1014+
int cw = sw;
1015+
int ch = sh;
1016+
int ox = 0;
1017+
int oy = 0;
1018+
1019+
if (sx + sw > width) cw = width - sx;
1020+
if (sy + sh > height) ch = height - sy;
10161021

1017-
// Non-compliant. "Pixels outside the canvas must be returned as transparent
1018-
// black." This instead clips the returned array to the canvas area.
10191022
if (sx < 0) {
1020-
sw += sx;
1023+
ox = -sx;
1024+
cw += sx;
10211025
sx = 0;
10221026
}
10231027
if (sy < 0) {
1024-
sh += sy;
1028+
oy = -sy;
1029+
ch += sy;
10251030
sy = 0;
10261031
}
10271032

@@ -1047,95 +1052,98 @@ Context2d::GetImageData(const Napi::CallbackInfo& info) {
10471052

10481053
uint8_t *dst = (uint8_t *)buffer.Data();
10491054

1050-
switch (canvas->backend()->getFormat()) {
1051-
case CAIRO_FORMAT_ARGB32: {
1052-
// Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
1053-
// and store in big-endian format
1054-
for (int y = 0; y < sh; ++y) {
1055-
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1056-
for (int x = 0; x < sw; ++x) {
1057-
int bx = x * 4;
1058-
uint32_t *pixel = row + x + sx;
1059-
uint8_t a = *pixel >> 24;
1060-
uint8_t r = *pixel >> 16;
1061-
uint8_t g = *pixel >> 8;
1062-
uint8_t b = *pixel;
1063-
dst[bx + 3] = a;
1064-
1065-
// Performance optimization: fully transparent/opaque pixels can be
1066-
// processed more efficiently.
1067-
if (a == 0 || a == 255) {
1055+
if (cw > 0 && ch > 0) {
1056+
switch (canvas->backend()->getFormat()) {
1057+
case CAIRO_FORMAT_ARGB32: {
1058+
dst += oy * dstStride + ox * 4;
1059+
// Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
1060+
// and store in big-endian format
1061+
for (int y = 0; y < ch; ++y) {
1062+
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1063+
for (int x = 0; x < cw; ++x) {
1064+
int bx = x * 4;
1065+
uint32_t *pixel = row + x + sx;
1066+
uint8_t a = *pixel >> 24;
1067+
uint8_t r = *pixel >> 16;
1068+
uint8_t g = *pixel >> 8;
1069+
uint8_t b = *pixel;
1070+
dst[bx + 3] = a;
1071+
1072+
// Performance optimization: fully transparent/opaque pixels can be
1073+
// processed more efficiently.
1074+
if (a == 0 || a == 255) {
1075+
dst[bx + 0] = r;
1076+
dst[bx + 1] = g;
1077+
dst[bx + 2] = b;
1078+
} else {
1079+
// Undo alpha pre-multiplication
1080+
float alphaR = (float)255 / a;
1081+
dst[bx + 0] = (int)((float)r * alphaR);
1082+
dst[bx + 1] = (int)((float)g * alphaR);
1083+
dst[bx + 2] = (int)((float)b * alphaR);
1084+
}
1085+
}
1086+
dst += dstStride;
1087+
}
1088+
break;
1089+
}
1090+
case CAIRO_FORMAT_RGB24: {
1091+
dst += oy * dstStride + ox * 4;
1092+
// Rearrange alpha (argb -> rgba) and store in big-endian format
1093+
for (int y = 0; y < ch; ++y) {
1094+
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1095+
for (int x = 0; x < cw; ++x) {
1096+
int bx = x * 4;
1097+
uint32_t *pixel = row + x + sx;
1098+
uint8_t r = *pixel >> 16;
1099+
uint8_t g = *pixel >> 8;
1100+
uint8_t b = *pixel;
1101+
10681102
dst[bx + 0] = r;
10691103
dst[bx + 1] = g;
10701104
dst[bx + 2] = b;
1071-
} else {
1072-
// Undo alpha pre-multiplication
1073-
float alphaR = (float)255 / a;
1074-
dst[bx + 0] = (int)((float)r * alphaR);
1075-
dst[bx + 1] = (int)((float)g * alphaR);
1076-
dst[bx + 2] = (int)((float)b * alphaR);
1105+
dst[bx + 3] = 255;
10771106
}
1078-
1107+
dst += dstStride;
10791108
}
1080-
dst += dstStride;
1081-
}
1082-
break;
1083-
}
1084-
case CAIRO_FORMAT_RGB24: {
1085-
// Rearrange alpha (argb -> rgba) and store in big-endian format
1086-
for (int y = 0; y < sh; ++y) {
1087-
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
1088-
for (int x = 0; x < sw; ++x) {
1089-
int bx = x * 4;
1090-
uint32_t *pixel = row + x + sx;
1091-
uint8_t r = *pixel >> 16;
1092-
uint8_t g = *pixel >> 8;
1093-
uint8_t b = *pixel;
1094-
1095-
dst[bx + 0] = r;
1096-
dst[bx + 1] = g;
1097-
dst[bx + 2] = b;
1098-
dst[bx + 3] = 255;
1109+
break;
10991110
}
1100-
dst += dstStride;
1111+
case CAIRO_FORMAT_A8: {
1112+
dst += oy * dstStride + ox;
1113+
for (int y = 0; y < ch; ++y) {
1114+
uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
1115+
memcpy(dst, row + sx, cw);
1116+
dst += dstStride;
1117+
}
1118+
break;
11011119
}
1102-
break;
1103-
}
1104-
case CAIRO_FORMAT_A8: {
1105-
for (int y = 0; y < sh; ++y) {
1106-
uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
1107-
memcpy(dst, row + sx, dstStride);
1108-
dst += dstStride;
1120+
case CAIRO_FORMAT_A1: {
1121+
// TODO Should this be totally packed, or maintain a stride divisible by 4?
1122+
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();
1123+
break;
11091124
}
1110-
break;
1111-
}
1112-
case CAIRO_FORMAT_A1: {
1113-
// TODO Should this be totally packed, or maintain a stride divisible by 4?
1114-
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();
1115-
1116-
break;
1117-
}
1118-
case CAIRO_FORMAT_RGB16_565: {
1119-
for (int y = 0; y < sh; ++y) {
1120-
uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
1121-
memcpy(dst, row + sx, dstStride);
1122-
dst += dstStride;
1125+
case CAIRO_FORMAT_RGB16_565: {
1126+
dst += oy * dstStride + ox * 2;
1127+
for (int y = 0; y < ch; ++y) {
1128+
uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
1129+
memcpy(dst, row + sx, cw * 2);
1130+
dst += dstStride;
1131+
}
1132+
break;
11231133
}
1124-
break;
1125-
}
11261134
#ifdef CAIRO_FORMAT_RGB30
1127-
case CAIRO_FORMAT_RGB30: {
1135+
case CAIRO_FORMAT_RGB30: {
11281136
// TODO
11291137
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException();
1130-
1131-
break;
1132-
}
1138+
break;
1139+
}
11331140
#endif
1134-
default: {
1135-
// Unlikely
1136-
Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
1137-
return env.Null();
1138-
}
1141+
default: {
1142+
// Unlikely
1143+
Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
1144+
return;
1145+
}
1146+
}
11391147
}
11401148

11411149
Napi::Number swHandle = Napi::Number::New(env, sw);

0 commit comments

Comments
 (0)