[canvaskit] Add option for readPixels to use pre-malloc'd data

Bug: skia:10565
Change-Id: I777f887794cd0524ced4d65e86bd285a854386e5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/331858
Reviewed-by: Nathaniel Nifong <nifong@google.com>
This commit is contained in:
Kevin Lubick 2020-11-03 17:13:09 -05:00
parent 9fe83916e0
commit c4ab08710d
5 changed files with 80 additions and 16 deletions

View File

@ -11,7 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`CanvasKit.Shader`.
- `MakeRasterDirectSurface` for giving the user direct access to drawn pixels.
- `getLineMetrics` to Paragraph.
- `Canvas.saveLayerPaint` as an experimental, undocumented "fast path" if one only needs to pass the paint.
- `Canvas.saveLayerPaint` as an experimental, undocumented "fast path" if one only needs to pass
the paint.
### Breaking
- `CanvasKit.MakePathFromSVGString` was renamed to `CanvasKit.Path.MakeFromSVGString`
@ -23,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `CanvasKit.Shader.Blend`, `...Color`, and `...Lerp` have been renamed to
`CanvasKit.Shader.MakeBlend`, `...MakeColor` and `...MakeLerp` to align with naming conventions.
The old names will be removed in an upcoming release.
- `readPixels` now takes a malloc'd object as the last parameter. If provided, the data will be
copied into there instead of allocating a new buffer.
### Removed
- `CanvasKit.MakePathFromCmds`; Was deprecated in favor of `CanvasKit.Path.MakeFromCmds`.

View File

@ -141,6 +141,9 @@ function canvasTests(CK: CanvasKit, canvas?: Canvas, paint?: Paint, path?: Path,
const pixels = canvas.readPixels(0, 1, 2, 3); // $ExpectType Uint8Array
const pixelsTwo = canvas.readPixels(4, 5, 6, 7, CK.AlphaType.Opaque, CK.ColorType.RGBA_1010102,
CK.ColorSpace.DISPLAY_P3, 16);
const m = CK.Malloc(Uint8Array, 20);
canvas.readPixels(4, 5, 6, 7, CK.AlphaType.Opaque, CK.ColorType.RGBA_1010102,
CK.ColorSpace.DISPLAY_P3, 16, m);
canvas.restore();
canvas.restoreToCount(2);
canvas.rotate(1, 2, 3);
@ -239,6 +242,14 @@ function imageTests(CK: CanvasKit, imgElement?: HTMLImageElement) {
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
}, 85, 1000);
const m = CK.Malloc(Uint8Array, 10);
img.readPixels({
width: 79,
height: 205,
colorType: CK.ColorType.RGBA_8888,
alphaType: CK.AlphaType.Unpremul,
colorSpace: CK.ColorSpace.SRGB,
}, 85, 1000, m);
img.delete();
}

View File

@ -1241,10 +1241,13 @@ export interface Canvas extends EmbindObject<Canvas> {
* @param alphaType - defaults to Unpremul
* @param colorType - defaults to RGBA_8888
* @param colorSpace - defaults to SRGB
* @param dest - If provided, the pixels will be copied into the allocated buffer allowing access to the
* pixels without allocating a new TypedArray.
* @param dstRowBytes
*/
readPixels(x: number, y: number, w: number, h: number, alphaType?: AlphaType,
colorType?: ColorType, colorSpace?: ColorSpace, dstRowBytes?: number): Uint8Array;
colorType?: ColorType, colorSpace?: ColorSpace, dstRowBytes?: number,
dest?: MallocObj): Uint8Array;
/**
* Removes changes to the current matrix and clip since Canvas state was
@ -1566,9 +1569,13 @@ export interface Image extends EmbindObject<Image> {
* @param imageInfo - describes the destination format of the pixels.
* @param srcX
* @param srcY
* @returns a Uint8Array if RGB_8888 was requested, Float32Array if RGBA_F32 was requested.
* @param dest - If provided, the pixels will be copied into the allocated buffer allowing access to the
* pixels without allocating a new TypedArray.
* @returns a Uint8Array if RGB_8888 was requested, Float32Array if RGBA_F32 was requested. null will be returned
* on any error.
*
*/
readPixels(imageInfo: ImageInfo, srcX: number, srcY: number): Uint8Array | Float32Array | null;
readPixels(imageInfo: ImageInfo, srcX: number, srcY: number, dest?: MallocObj): Uint8Array | Float32Array | null;
/**
* Return the width in pixels of the image.

View File

@ -920,7 +920,7 @@ CanvasKit.onRuntimeInitialized = function() {
return this._makeShader(xTileMode, yTileMode, localMatrixPtr);
};
CanvasKit.Image.prototype.readPixels = function(imageInfo, srcX, srcY) {
CanvasKit.Image.prototype.readPixels = function(imageInfo, srcX, srcY, destMallocObj) {
var rowBytes;
// Important to use ['string'] notation here, otherwise the closure compiler will
// minify away the colorType.
@ -936,13 +936,26 @@ CanvasKit.onRuntimeInitialized = function() {
return;
}
var pBytes = rowBytes * imageInfo.height;
var pPtr = CanvasKit._malloc(pBytes);
var pPtr;
if (destMallocObj) {
pPtr = destMallocObj['byteOffset'];
} else {
pPtr = CanvasKit._malloc(pBytes);
}
if (!this._readPixels(imageInfo, pPtr, rowBytes, srcX, srcY)) {
Debug('Could not read pixels with the given inputs');
if (!destMallocObj) {
CanvasKit._free(pPtr);
}
return null;
}
// If the user provided us a buffer to copy into, we don't need to allocate a new TypedArray.
if (destMallocObj) {
return destMallocObj['toTypedArray'](); // Return the typed array wrapper w/o allocating.
}
// Put those pixels into a typed array of the right format and then
// make a copy with slice() that we can return.
var retVal = null;
@ -1163,9 +1176,10 @@ CanvasKit.onRuntimeInitialized = function() {
return rv;
};
// returns Uint8Array
// TODO(kjlubick) align this API with Image.readPixels
CanvasKit.Canvas.prototype.readPixels = function(x, y, w, h, alphaType,
colorType, colorSpace, dstRowBytes) {
colorType, colorSpace, dstRowBytes,
destMallocObj) {
// supply defaults (which are compatible with HTMLCanvas's getImageData)
alphaType = alphaType || CanvasKit.AlphaType.Unpremul;
colorType = colorType || CanvasKit.ColorType.RGBA_8888;
@ -1176,24 +1190,37 @@ CanvasKit.onRuntimeInitialized = function() {
}
dstRowBytes = dstRowBytes || (pixBytes * w);
var len = h * dstRowBytes
var pptr = CanvasKit._malloc(len);
var len = h * dstRowBytes;
var pPtr;
if (destMallocObj) {
pPtr = destMallocObj['byteOffset'];
} else {
pPtr = CanvasKit._malloc(len);
}
var ok = this._readPixels({
'width': w,
'height': h,
'colorType': colorType,
'alphaType': alphaType,
'colorSpace': colorSpace,
}, pptr, dstRowBytes, x, y);
}, pPtr, dstRowBytes, x, y);
if (!ok) {
CanvasKit._free(pptr);
if (!destMallocObj) {
CanvasKit._free(pPtr);
}
return null;
}
// If the user provided us a buffer to copy into, we don't need to allocate a new TypedArray.
if (destMallocObj) {
return destMallocObj['toTypedArray'](); // Return the typed array wrapper w/o allocating.
}
// The first typed array is just a view into memory. Because we will
// be free-ing that, we call slice to make a persistent copy.
var pixels = new Uint8Array(CanvasKit.HEAPU8.buffer, pptr, len).slice();
CanvasKit._free(pptr);
var pixels = new Uint8Array(CanvasKit.HEAPU8.buffer, pPtr, len).slice();
CanvasKit._free(pPtr);
return pixels;
};

View File

@ -152,7 +152,15 @@ describe('Core canvas behavior', () => {
// requires 4 bytes (R, G, B, A).
expect(pixels.length).toEqual(512 * 512 * 4);
// Make enough space for a 5x5 8888 surface (4 bytes for R, G, B, A)
const rdsData = CanvasKit.Malloc(Uint8Array, 512 * 5*512 * 4);
const pixels2 = rdsData.toTypedArray();
pixels2[0] = 127; // sentinel value, should be overwritten by readPixels.
img.readPixels(imageInfo, 0, 0, rdsData);
expect(rdsData.toTypedArray()[0]).toEqual(pixels[0]);
img.delete();
CanvasKit.Free(rdsData);
done();
})();
});
@ -888,9 +896,17 @@ describe('Core canvas behavior', () => {
expect(CanvasKit.ColorSpace.Equals(info.colorSpace, colorSpace))
.toBeTruthy("Surface not created with correct color space.");
const pixels = surface.getCanvas().readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888, colorSpace);
const mObj = CanvasKit.Malloc(Uint8Array, CANVAS_WIDTH * CANVAS_HEIGHT * 4);
mObj.toTypedArray()[0] = 127; // sentinel value. Should be overwritten by readPixels.
const canvas = surface.getCanvas();
canvas.clear(CanvasKit.TRANSPARENT);
const pixels = canvas.readPixels(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT,
CanvasKit.AlphaType.Unpremul, CanvasKit.ColorType.RGBA_8888, colorSpace, null, mObj);
expect(pixels).toBeTruthy('Could not read pixels from surface');
expect(pixels[0] !== 127).toBeTruthy();
expect(pixels[0]).toEqual(mObj.toTypedArray()[0]);
CanvasKit.Free(mObj);
surface.delete();
});
it('Can create a Display P3 surface', () => {
const colorSpace = CanvasKit.ColorSpace.DISPLAY_P3;