Skip to content

Commit

Permalink
return transparent black pixels when getting image data outside the c…
Browse files Browse the repository at this point in the history
…anvas
  • Loading branch information
Philippe Plantier authored and chearon committed Jan 11, 2025
1 parent bacf048 commit 2402410
Show file tree
Hide file tree
Showing 4 changed files with 587 additions and 90 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ project adheres to [Semantic Versioning](http://semver.org/).

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

3.0.1
==================
Expand Down
172 changes: 90 additions & 82 deletions src/CanvasRenderingContext2d.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1011,17 +1011,22 @@ Context2d::GetImageData(const Napi::CallbackInfo& info) {
sh = -sh;
}

if (sx + sw > width) sw = width - sx;
if (sy + sh > height) sh = height - sy;
int cw = sw;
int ch = sh;
int ox = 0;
int oy = 0;

if (sx + sw > width) cw = width - sx;
if (sy + sh > height) ch = height - sy;

// Non-compliant. "Pixels outside the canvas must be returned as transparent
// black." This instead clips the returned array to the canvas area.
if (sx < 0) {
sw += sx;
ox = -sx;
cw += sx;
sx = 0;
}
if (sy < 0) {
sh += sy;
oy = -sy;
ch += sy;
sy = 0;
}

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

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

switch (canvas->backend()->getFormat()) {
case CAIRO_FORMAT_ARGB32: {
// Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
// and store in big-endian format
for (int y = 0; y < sh; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < sw; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t a = *pixel >> 24;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;
dst[bx + 3] = a;

// Performance optimization: fully transparent/opaque pixels can be
// processed more efficiently.
if (a == 0 || a == 255) {
if (cw > 0 && ch > 0) {
switch (canvas->backend()->getFormat()) {
case CAIRO_FORMAT_ARGB32: {
dst += oy * dstStride + ox * 4;
// Rearrange alpha (argb -> rgba), undo alpha pre-multiplication,
// and store in big-endian format
for (int y = 0; y < ch; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < cw; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t a = *pixel >> 24;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;
dst[bx + 3] = a;

// Performance optimization: fully transparent/opaque pixels can be
// processed more efficiently.
if (a == 0 || a == 255) {
dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
} else {
// Undo alpha pre-multiplication
float alphaR = (float)255 / a;
dst[bx + 0] = (int)((float)r * alphaR);
dst[bx + 1] = (int)((float)g * alphaR);
dst[bx + 2] = (int)((float)b * alphaR);
}
}
dst += dstStride;
}
break;
}
case CAIRO_FORMAT_RGB24: {
dst += oy * dstStride + ox * 4;
// Rearrange alpha (argb -> rgba) and store in big-endian format
for (int y = 0; y < ch; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < cw; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;

dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
} else {
// Undo alpha pre-multiplication
float alphaR = (float)255 / a;
dst[bx + 0] = (int)((float)r * alphaR);
dst[bx + 1] = (int)((float)g * alphaR);
dst[bx + 2] = (int)((float)b * alphaR);
dst[bx + 3] = 255;
}

dst += dstStride;
}
dst += dstStride;
}
break;
}
case CAIRO_FORMAT_RGB24: {
// Rearrange alpha (argb -> rgba) and store in big-endian format
for (int y = 0; y < sh; ++y) {
uint32_t *row = (uint32_t *)(src + srcStride * (y + sy));
for (int x = 0; x < sw; ++x) {
int bx = x * 4;
uint32_t *pixel = row + x + sx;
uint8_t r = *pixel >> 16;
uint8_t g = *pixel >> 8;
uint8_t b = *pixel;

dst[bx + 0] = r;
dst[bx + 1] = g;
dst[bx + 2] = b;
dst[bx + 3] = 255;
break;
}
dst += dstStride;
case CAIRO_FORMAT_A8: {
dst += oy * dstStride + ox;
for (int y = 0; y < ch; ++y) {
uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
memcpy(dst, row + sx, cw);
dst += dstStride;
}
break;
}
break;
}
case CAIRO_FORMAT_A8: {
for (int y = 0; y < sh; ++y) {
uint8_t *row = (uint8_t *)(src + srcStride * (y + sy));
memcpy(dst, row + sx, dstStride);
dst += dstStride;
case CAIRO_FORMAT_A1: {
// TODO Should this be totally packed, or maintain a stride divisible by 4?
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();
break;
}
break;
}
case CAIRO_FORMAT_A1: {
// TODO Should this be totally packed, or maintain a stride divisible by 4?
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_A1 is not yet implemented").ThrowAsJavaScriptException();

break;
}
case CAIRO_FORMAT_RGB16_565: {
for (int y = 0; y < sh; ++y) {
uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
memcpy(dst, row + sx, dstStride);
dst += dstStride;
case CAIRO_FORMAT_RGB16_565: {
dst += oy * dstStride + ox * 2;
for (int y = 0; y < ch; ++y) {
uint16_t *row = (uint16_t *)(src + srcStride * (y + sy));
memcpy(dst, row + sx, cw * 2);
dst += dstStride;
}
break;
}
break;
}
#ifdef CAIRO_FORMAT_RGB30
case CAIRO_FORMAT_RGB30: {
case CAIRO_FORMAT_RGB30: {
// TODO
Napi::Error::New(env, "getImageData for CANVAS_FORMAT_RGB30 is not yet implemented").ThrowAsJavaScriptException();

break;
}
break;
}
#endif
default: {
// Unlikely
Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
return env.Null();
}
default: {
// Unlikely
Napi::Error::New(env, "Invalid pixel format or not an image canvas").ThrowAsJavaScriptException();
return;
}
}
}

Napi::Number swHandle = Napi::Number::New(env, sw);
Expand Down
Loading

0 comments on commit 2402410

Please sign in to comment.