Skip to content

Commit 18c05f1

Browse files
committed
Use Uint16Array for RGB16_565 format
1 parent 989a1d4 commit 18c05f1

7 files changed

Lines changed: 83 additions & 34 deletions

File tree

Readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ These additional pixel formats have experimental support:
337337
Some hardware devices and frame buffers use this format. Note that PNG does
338338
not support this format; when creating a PNG, the image will be converted to
339339
24-bit RGB. This format is thus suboptimal for generating PNGs.
340+
`ImageData` instances for this mode use a `Uint16Array` instead of a `Uint8ClampedArray`.
340341
* `A1` Each pixel is 1 bit, and pixels are packed together into 32-bit
341342
quantities. The ordering of the bits matches the endianness of the
342343
platform: on a little-endian machine, the first pixel is the least-

lib/context2d.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,10 @@ Context2d.prototype.createImageData = function(width, height){
339339
height = width.height;
340340
width = width.width;
341341
}
342+
var ArrayCtor = this.pixelFormat === "RGB16_565" ? Uint16Array : Uint8ClampedArray;
343+
// NB: for some pixelFormats, the stride is not equal to BPP * width.
344+
// For example, A1 always pads out to a multiple of 4.
342345
var Bpp = this.canvas.stride / this.canvas.width;
343346
var nBytes = Bpp * width * height;
344-
return new ImageData(new Uint8ClampedArray(nBytes), width, height);
347+
return new ImageData(new ArrayCtor(nBytes), width, height);
345348
};

src/CanvasRenderingContext2d.cc

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -813,9 +813,15 @@ NAN_METHOD(Context2d::GetImageData) {
813813
uint8_t *src = canvas->data();
814814

815815
Local<ArrayBuffer> buffer = ArrayBuffer::New(Isolate::GetCurrent(), size);
816-
Local<Uint8ClampedArray> clampedArray = Uint8ClampedArray::New(buffer, 0, size);
816+
Local<TypedArray> dataArray;
817817

818-
Nan::TypedArrayContents<uint8_t> typedArrayContents(clampedArray);
818+
if (canvas->backend()->getFormat() == CAIRO_FORMAT_RGB16_565) {
819+
dataArray = Uint16Array::New(buffer, 0, size);
820+
} else {
821+
dataArray = Uint8ClampedArray::New(buffer, 0, size);
822+
}
823+
824+
Nan::TypedArrayContents<uint8_t> typedArrayContents(dataArray);
819825
uint8_t* dst = *typedArrayContents;
820826

821827
switch (canvas->backend()->getFormat()) {
@@ -910,7 +916,7 @@ NAN_METHOD(Context2d::GetImageData) {
910916
const int argc = 3;
911917
Local<Int32> swHandle = Nan::New(sw);
912918
Local<Int32> shHandle = Nan::New(sh);
913-
Local<Value> argv[argc] = { clampedArray, swHandle, shHandle };
919+
Local<Value> argv[argc] = { dataArray, swHandle, shHandle };
914920

915921
Local<Function> ctor = Nan::GetFunction(Nan::New(ImageData::constructor)).ToLocalChecked();
916922
Local<Object> instance = Nan::NewInstance(ctor, argc, argv).ToLocalChecked();

src/ImageData.cc

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ NAN_METHOD(ImageData::New) {
3939
return Nan::ThrowTypeError("Class constructors cannot be invoked without 'new'");
4040
}
4141

42-
Local<Uint8ClampedArray> clampedArray;
42+
Local<TypedArray> dataArray;
4343
uint32_t width;
4444
uint32_t height;
4545
int length;
@@ -57,12 +57,12 @@ NAN_METHOD(ImageData::New) {
5757
}
5858
length = width * height * 4; // ImageData(w, h) constructor assumes 4 BPP; documented.
5959

60-
clampedArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
60+
dataArray = Uint8ClampedArray::New(ArrayBuffer::New(Isolate::GetCurrent(), length), 0, length);
6161

6262
} else if (info[0]->IsUint8ClampedArray() && info[1]->IsUint32()) {
63-
clampedArray = info[0].As<Uint8ClampedArray>();
63+
dataArray = info[0].As<Uint8ClampedArray>();
6464

65-
length = clampedArray->Length();
65+
length = dataArray->Length();
6666
if (length == 0) {
6767
Nan::ThrowRangeError("The input data has a zero byte length.");
6868
return;
@@ -86,16 +86,38 @@ NAN_METHOD(ImageData::New) {
8686
height = size / width;
8787
}
8888

89+
} else if (info[0]->IsUint16Array() && info[1]->IsUint32()) { // Intended for RGB16_565 format
90+
dataArray = info[0].As<Uint16Array>();
91+
92+
length = dataArray->Length();
93+
if (length == 0) {
94+
Nan::ThrowRangeError("The input data has a zero byte length.");
95+
return;
96+
}
97+
98+
width = info[1]->Uint32Value();
99+
if (width == 0) {
100+
Nan::ThrowRangeError("The source width is zero.");
101+
return;
102+
}
103+
104+
if (info[2]->IsUint32()) { // Explicit height given
105+
height = info[2]->Uint32Value();
106+
} else { // Calculate height assuming 2 BPP
107+
int size = length / 2;
108+
height = size / width;
109+
}
110+
89111
} else {
90-
Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]) or (width, height)");
112+
Nan::ThrowTypeError("Expected (Uint8ClampedArray, width[, height]), (Uint16Array, width[, height]) or (width, height)");
91113
return;
92114
}
93115

94-
Nan::TypedArrayContents<uint8_t> dataPtr(clampedArray);
116+
Nan::TypedArrayContents<uint8_t> dataPtr(dataArray);
95117

96118
ImageData *imageData = new ImageData(reinterpret_cast<uint8_t*>(*dataPtr), width, height);
97119
imageData->Wrap(info.This());
98-
info.This()->Set(Nan::New("data").ToLocalChecked(), clampedArray);
120+
info.This()->Set(Nan::New("data").ToLocalChecked(), dataArray);
99121
info.GetReturnValue().Set(info.This());
100122
}
101123

test/canvas.test.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ describe('Canvas', function () {
730730
, ctx = canvas.getContext('2d', {pixelFormat: "RGB16_565"});
731731

732732
var imageData = ctx.createImageData(2,6);
733+
assert(imageData.data instanceof Uint16Array);
733734
assert.equal(2, imageData.width);
734735
assert.equal(6, imageData.height);
735736
assert.equal(2 * 6 * 2, imageData.data.length);
@@ -829,16 +830,13 @@ describe('Canvas', function () {
829830
assert.equal(6, imageData.height);
830831
assert.equal(3 * 6 * 2, imageData.data.length);
831832

832-
// TODO should be a Uint16Array already?
833-
var uint16data = new Uint16Array(imageData.data.buffer, imageData.data.byteOffset, 18);
833+
assert.equal((255 & 0b11111) << 11, imageData.data[0]);
834+
assert.equal((255 & 0b111111) << 5, imageData.data[1]);
835+
assert.equal((255 & 0b11111), imageData.data[2]);
834836

835-
assert.equal((255 & 0b11111) << 11, uint16data[0]);
836-
assert.equal((255 & 0b111111) << 5, uint16data[1]);
837-
assert.equal((255 & 0b11111), uint16data[2]);
838-
839-
assert.equal((255 & 0b11111) << 11, uint16data[3]);
840-
assert.equal((255 & 0b111111) << 5, uint16data[4]);
841-
assert.equal((255 & 0b11111), uint16data[5]);
837+
assert.equal((255 & 0b11111) << 11, imageData.data[3]);
838+
assert.equal((255 & 0b111111) << 5, imageData.data[4]);
839+
assert.equal((255 & 0b11111), imageData.data[5]);
842840
});
843841

844842
it("works, full width, A8", function () {
@@ -904,11 +902,8 @@ describe('Canvas', function () {
904902
assert.equal(1, imageData.height);
905903
assert.equal(2 * 1 * 2, imageData.data.length);
906904

907-
// TODO should be a Uint16Array already?
908-
var uint16data = new Uint16Array(imageData.data.buffer, imageData.data.byteOffset, 2);
909-
910-
assert.equal((255 & 0b11111) << 11, uint16data[0]);
911-
assert.equal((255 & 0b111111) << 5, uint16data[1]);
905+
assert.equal((255 & 0b11111) << 11, imageData.data[0]);
906+
assert.equal((255 & 0b111111) << 5, imageData.data[1]);
912907
});
913908

914909
it("works, slice, A8", function () {

test/imageData.test.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ describe('ImageData', function () {
4040
// because our ImageData can support different BPPs.
4141
});
4242

43-
it('should construct with typed array', function () {
43+
it('should construct with Uint8ClampedArray', function () {
4444
var data = new Uint8ClampedArray(2 * 3 * 4);
4545
var imagedata = new ImageData(data, 2);
4646
assert.strictEqual(imagedata.width, 2);
@@ -55,4 +55,20 @@ describe('ImageData', function () {
5555
assert(imagedata.data instanceof Uint8ClampedArray);
5656
assert.strictEqual(imagedata.data.length, 48);
5757
});
58+
59+
it('should construct with Uint16Array', function () {
60+
var data = new Uint16Array(2 * 3 * 2);
61+
var imagedata = new ImageData(data, 2);
62+
assert.strictEqual(imagedata.width, 2);
63+
assert.strictEqual(imagedata.height, 3);
64+
assert(imagedata.data instanceof Uint16Array);
65+
assert.strictEqual(imagedata.data.length, 12);
66+
67+
data = new Uint16Array(3 * 4 * 2);
68+
imagedata = new ImageData(data, 3, 4);
69+
assert.strictEqual(imagedata.width, 3);
70+
assert.strictEqual(imagedata.height, 4);
71+
assert(imagedata.data instanceof Uint16Array);
72+
assert.strictEqual(imagedata.data.length, 24);
73+
});
5874
});

test/public/tests.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,8 +1912,10 @@ tests['putImageData() png data'] = function (ctx, done) {
19121912
ctx.drawImage(img, 0, 0, 200, 200)
19131913
var imageData = ctx.getImageData(0, 0, 50, 50)
19141914
var data = imageData.data
1915-
for (var i = 0, len = data.length; i < len; i += 4) {
1916-
data[i + 3] = 80
1915+
if (data instanceof Uint8ClampedArray) {
1916+
for (var i = 0, len = data.length; i < len; i += 4) {
1917+
data[i + 3] = 80
1918+
}
19171919
}
19181920
ctx.putImageData(imageData, 50, 50)
19191921
done(null)
@@ -1933,8 +1935,10 @@ tests['putImageData() png data 2'] = function (ctx, done) {
19331935
ctx.drawImage(img, 0, 0, 200, 200)
19341936
var imageData = ctx.getImageData(0, 0, 50, 50)
19351937
var data = imageData.data
1936-
for (var i = 0, len = data.length; i < len; i += 4) {
1937-
data[i + 3] = 80
1938+
if (data instanceof Uint8ClampedArray) {
1939+
for (var i = 0, len = data.length; i < len; i += 4) {
1940+
data[i + 3] = 80
1941+
}
19381942
}
19391943
ctx.putImageData(imageData, 50, 50, 10, 10, 20, 20)
19401944
done(null)
@@ -1954,10 +1958,12 @@ tests['putImageData() png data 3'] = function (ctx, done) {
19541958
ctx.drawImage(img, 0, 0, 200, 200)
19551959
var imageData = ctx.getImageData(0, 0, 50, 50)
19561960
var data = imageData.data
1957-
for (var i = 0, len = data.length; i < len; i += 4) {
1958-
data[i + 0] = data[i + 0] * 0.2
1959-
data[i + 1] = data[i + 1] * 0.2
1960-
data[i + 2] = data[i + 2] * 0.2
1961+
if (data instanceof Uint8ClampedArray) {
1962+
for (var i = 0, len = data.length; i < len; i += 4) {
1963+
data[i + 0] = data[i + 0] * 0.2
1964+
data[i + 1] = data[i + 1] * 0.2
1965+
data[i + 2] = data[i + 2] * 0.2
1966+
}
19611967
}
19621968
ctx.putImageData(imageData, 50, 50)
19631969
done(null)

0 commit comments

Comments
 (0)