Skip to content

Commit 39ce0b6

Browse files
Philippe Plantierchearon
authored andcommitted
getImageData fixes when rectangle is outside of canvas
fix a crash in getImageData if the rectangle is outside the canvas return transparent black pixels when getting image data outside the canvas remove dead code, add comments
1 parent da33bbe commit 39ce0b6

File tree

4 files changed

+597
-87
lines changed

4 files changed

+597
-87
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ project adheres to [Semantic Versioning](http://semver.org/).
1515
* Support for accessibility and links in PDFs
1616

1717
### Fixed
18+
* 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))
1820

1921
3.0.1
2022
==================

src/CanvasRenderingContext2d.cc

Lines changed: 95 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1011,21 +1011,26 @@ 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;
1016-
1017-
// WebKit/moz functionality. node-canvas used to return in either case.
1018-
if (sw <= 0) sw = 1;
1019-
if (sh <= 0) sh = 1;
1020-
1021-
// Non-compliant. "Pixels outside the canvas must be returned as transparent
1022-
// black." This instead clips the returned array to the canvas area.
1014+
// Width and height to actually copy
1015+
int cw = sw;
1016+
int ch = sh;
1017+
// Offsets in the destination image
1018+
int ox = 0;
1019+
int oy = 0;
1020+
1021+
// Clamp the copy width and height if the copy would go outside the image
1022+
if (sx + sw > width) cw = width - sx;
1023+
if (sy + sh > height) ch = height - sy;
1024+
1025+
// Clamp the copy origin if the copy would go outside the image
10231026
if (sx < 0) {
1024-
sw += sx;
1027+
ox = -sx;
1028+
cw += sx;
10251029
sx = 0;
10261030
}
10271031
if (sy < 0) {
1028-
sh += sy;
1032+
oy = -sy;
1033+
ch += sy;
10291034
sy = 0;
10301035
}
10311036

@@ -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)