Skip to content

Commit

Permalink
Merge pull request #1992 from Shopify/integration2
Browse files Browse the repository at this point in the history
---------

Co-authored-by: William Candillon <[email protected]>
Co-authored-by: gtbl2012 <[email protected]>
Co-authored-by: Hans <[email protected]>
  • Loading branch information
3 people authored Nov 20, 2023
2 parents 0b818a2 + b816245 commit 83c7386
Show file tree
Hide file tree
Showing 11 changed files with 353 additions and 15 deletions.
2 changes: 2 additions & 0 deletions docs/docs/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,5 +133,7 @@ const ImageDemo = () => {
| :-------------- | :-------------------------------------------------------------------- |
| `height` | Returns the possibly scaled height of the image. |
| `width` | Returns the possibly scaled width of the image. |
| `getImageInfo` | Returns the image info for the image. |
| `encodeToBytes` | Encodes the image pixels, returning the result as a `UInt8Array`. |
| `encodeToBase64`| Encodes the image pixels, returning the result as a base64-encoded string. |
| `readPixels` | Reads the image pixels, returning result as UInt8Array or Float32Array |
39 changes: 38 additions & 1 deletion package/cpp/api/JsiSkCanvas.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "JsiSkFont.h"
#include "JsiSkHostObjects.h"
#include "JsiSkImage.h"
#include "JsiSkImageInfo.h"
#include "JsiSkMatrix.h"
#include "JsiSkPaint.h"
#include "JsiSkPath.h"
Expand All @@ -17,6 +18,8 @@
#include "JsiSkTextBlob.h"
#include "JsiSkVertices.h"

#include "RNSkTypedArray.h"

#include <jsi/jsi.h>

#pragma clang diagnostic push
Expand Down Expand Up @@ -491,6 +494,39 @@ class JsiSkCanvas : public JsiSkHostObject {
return jsi::Value::undefined();
}

JSI_HOST_FUNCTION(readPixels) {
auto srcX = static_cast<int>(arguments[0].asNumber());
auto srcY = static_cast<int>(arguments[1].asNumber());
auto info = JsiSkImageInfo::fromValue(runtime, arguments[2]);
if (!info) {
return jsi::Value::null();
}
size_t bytesPerRow = 0;
if (count > 4 && !arguments[4].isUndefined()) {
bytesPerRow = static_cast<size_t>(arguments[4].asNumber());
} else {
bytesPerRow = info->minRowBytes();
}
auto dest =
count > 3
? RNSkTypedArray::getTypedArray(runtime, arguments[3], *info)
: RNSkTypedArray::getTypedArray(runtime, jsi::Value::null(), *info);
if (!dest.isObject()) {
return jsi::Value::null();
}
jsi::ArrayBuffer buffer =
dest.asObject(runtime)
.getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer"))
.asObject(runtime)
.getArrayBuffer(runtime);
auto bfrPtr = reinterpret_cast<void *>(buffer.data(runtime));

if (!_canvas->readPixels(*info, bfrPtr, bytesPerRow, srcX, srcY)) {
return jsi::Value::null();
}
return std::move(dest);
}

JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkCanvas, drawPaint),
JSI_EXPORT_FUNC(JsiSkCanvas, drawLine),
JSI_EXPORT_FUNC(JsiSkCanvas, drawRect),
Expand Down Expand Up @@ -529,7 +565,8 @@ class JsiSkCanvas : public JsiSkHostObject {
JSI_EXPORT_FUNC(JsiSkCanvas, drawColor),
JSI_EXPORT_FUNC(JsiSkCanvas, clear),
JSI_EXPORT_FUNC(JsiSkCanvas, concat),
JSI_EXPORT_FUNC(JsiSkCanvas, drawPicture))
JSI_EXPORT_FUNC(JsiSkCanvas, drawPicture),
JSI_EXPORT_FUNC(JsiSkCanvas, readPixels))

explicit JsiSkCanvas(std::shared_ptr<RNSkPlatformContext> context)
: JsiSkHostObject(std::move(context)) {}
Expand Down
50 changes: 50 additions & 0 deletions package/cpp/api/JsiSkImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
#include <utility>

#include "JsiSkHostObjects.h"
#include "JsiSkImageInfo.h"
#include "JsiSkMatrix.h"
#include "JsiSkShader.h"

#include "RNSkTypedArray.h"

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"

Expand All @@ -34,6 +37,11 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
return static_cast<double>(getObject()->height());
}

JSI_HOST_FUNCTION(getImageInfo) {
return JsiSkImageInfo::toValue(runtime, getContext(),
getObject()->imageInfo());
}

JSI_HOST_FUNCTION(makeShaderOptions) {
auto tmx = (SkTileMode)arguments[0].asNumber();
auto tmy = (SkTileMode)arguments[1].asNumber();
Expand Down Expand Up @@ -117,6 +125,46 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {
return jsi::String::createFromAscii(runtime, buffer);
}

JSI_HOST_FUNCTION(readPixels) {
int srcX = 0;
int srcY = 0;
if (count > 0 && !arguments[0].isUndefined()) {
srcX = static_cast<int>(arguments[0].asNumber());
}
if (count > 1 && !arguments[1].isUndefined()) {
srcY = static_cast<int>(arguments[1].asNumber());
}
SkImageInfo info =
(count > 2 && !arguments[2].isUndefined())
? *JsiSkImageInfo::fromValue(runtime, arguments[2])
: SkImageInfo::MakeN32(getObject()->width(), getObject()->height(),
getObject()->imageInfo().alphaType());
size_t bytesPerRow = 0;
if (count > 4 && !arguments[4].isUndefined()) {
bytesPerRow = static_cast<size_t>(arguments[4].asNumber());
} else {
bytesPerRow = info.minRowBytes();
}
auto dest =
count > 3
? RNSkTypedArray::getTypedArray(runtime, arguments[3], info)
: RNSkTypedArray::getTypedArray(runtime, jsi::Value::null(), info);
if (!dest.isObject()) {
return jsi::Value::null();
}
jsi::ArrayBuffer buffer =
dest.asObject(runtime)
.getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer"))
.asObject(runtime)
.getArrayBuffer(runtime);
auto bfrPtr = reinterpret_cast<void *>(buffer.data(runtime));

if (!getObject()->readPixels(info, bfrPtr, bytesPerRow, srcX, srcY)) {
return jsi::Value::null();
}
return std::move(dest);
}

JSI_HOST_FUNCTION(makeNonTextureImage) {
auto image = getObject()->makeNonTextureImage();
return jsi::Object::createFromHostObject(
Expand All @@ -127,10 +175,12 @@ class JsiSkImage : public JsiSkWrappingSkPtrHostObject<SkImage> {

JSI_EXPORT_FUNCTIONS(JSI_EXPORT_FUNC(JsiSkImage, width),
JSI_EXPORT_FUNC(JsiSkImage, height),
JSI_EXPORT_FUNC(JsiSkImage, getImageInfo),
JSI_EXPORT_FUNC(JsiSkImage, makeShaderOptions),
JSI_EXPORT_FUNC(JsiSkImage, makeShaderCubic),
JSI_EXPORT_FUNC(JsiSkImage, encodeToBytes),
JSI_EXPORT_FUNC(JsiSkImage, encodeToBase64),
JSI_EXPORT_FUNC(JsiSkImage, readPixels),
JSI_EXPORT_FUNC(JsiSkImage, makeNonTextureImage),
JSI_EXPORT_FUNC(JsiSkImage, dispose))

Expand Down
19 changes: 19 additions & 0 deletions package/cpp/api/JsiSkImageInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,24 @@ class JsiSkImageInfo : public JsiSkWrappingSharedPtrHostObject<SkImageInfo> {
runtime,
std::make_shared<JsiSkImageInfo>(std::move(context), imageInfo));
}

JSI_PROPERTY_GET(width) { return static_cast<double>(getObject()->width()); }
JSI_PROPERTY_GET(height) {
return static_cast<double>(getObject()->height());
}
JSI_PROPERTY_GET(colorType) {
return static_cast<double>(getObject()->colorType());
}
JSI_PROPERTY_GET(alphaType) {
return static_cast<double>(getObject()->alphaType());
}

JSI_API_TYPENAME(ImageInfo);

JSI_EXPORT_PROPERTY_GETTERS(JSI_EXPORT_PROP_GET(JsiSkImageInfo, width),
JSI_EXPORT_PROP_GET(JsiSkImageInfo, height),
JSI_EXPORT_PROP_GET(JsiSkImageInfo, colorType),
JSI_EXPORT_PROP_GET(JsiSkImageInfo, alphaType),
JSI_EXPORT_PROP_GET(JsiSkImageInfo, __typename__))
};
} // namespace RNSkia
41 changes: 41 additions & 0 deletions package/cpp/utils/RNSkTypedArray.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#pragma once

#include "SkImage.h"
#include <jsi/jsi.h>

namespace RNSkia {

namespace jsi = facebook::jsi;

class RNSkTypedArray {
public:
static jsi::Value getTypedArray(jsi::Runtime &runtime,
const jsi::Value &value, SkImageInfo &info) {
auto reqSize = info.computeMinByteSize();
if (reqSize > 0) {
if (value.isObject()) {
auto typedArray = value.asObject(runtime);
auto size = static_cast<size_t>(
typedArray.getProperty(runtime, "byteLength").asNumber());
if (size >= reqSize) {
return typedArray;
}
} else {
if (info.colorType() == kRGBA_F32_SkColorType) {
auto arrayCtor =
runtime.global().getPropertyAsFunction(runtime, "Float32Array");
return arrayCtor.callAsConstructor(runtime,
static_cast<double>(reqSize / 4));
} else {
auto arrayCtor =
runtime.global().getPropertyAsFunction(runtime, "Uint8Array");
return arrayCtor.callAsConstructor(runtime,
static_cast<double>(reqSize));
}
}
}
return jsi::Value::null();
}
};

} // namespace RNSkia
105 changes: 104 additions & 1 deletion package/src/renderer/__tests__/e2e/Image.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React from "react";

import { checkImage } from "../../../__tests__/setup";
import { images, surface } from "../setup";
import { images, loadImage, surface } from "../setup";
import { Fill, Image as SkiaImage } from "../../components";
import { AlphaType, ColorType } from "../../../skia/types";

describe("Image loading from bundles", () => {
it("should render png, jpg from bundle", async () => {
Expand All @@ -24,6 +25,108 @@ describe("Image loading from bundles", () => {
);
checkImage(image, `snapshots/images/bundle-${surface.OS}.png`);
});

it("should read pixels from an image", async () => {
const pixels = await surface.eval(
(Skia, { data }) => {
const image = Skia.Image.MakeImageFromEncoded(
Skia.Data.fromBytes(new Uint8Array(data))
)!;
return Array.from(
image.readPixels(0, 0, {
width: 2,
height: 2,
colorType: image.getImageInfo().colorType,
alphaType: image.getImageInfo().alphaType,
})!
);
},
{
data: Array.from(
loadImage("skia/__tests__/assets/oslo.jpg").encodeToBytes()
),
}
);
expect(pixels).toBeDefined();
expect(pixels).toEqual([
170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199,
255,
]);
});

// it("should read pixels from an image using a preallocated buffer", async () => {
// const pixels = await surface.eval(
// (Skia, { colorType, alphaType, data }) => {
// const image = Skia.Image.MakeImageFromEncoded(
// Skia.Data.fromBytes(new Uint8Array(data))
// )!;
// const result = new Uint8Array(16);
// image.readPixels(
// 0,
// 0,
// {
// width: 2,
// height: 2,
// colorType,
// alphaType,
// },
// result
// );
// return result;
// },
// {
// colorType: ColorType.RGBA_8888,
// alphaType: AlphaType.Unpremul,
// data: Array.from(
// loadImage("skia/__tests__/assets/oslo.jpg").encodeToBytes()
// ),
// }
// );
// expect(pixels).toBeDefined();
// expect(Array.from(pixels!)).toEqual([
// 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199, 255, 170, 186, 199,
// 255,
// ]);
// });
it("should read pixels from a canvas", async () => {
const pixels = await surface.eval(
(Skia, { colorType, alphaType }) => {
const offscreen = Skia.Surface.MakeOffscreen(10, 10)!;
const canvas = offscreen.getCanvas();
canvas.drawColor(Skia.Color("red"));
return Array.from(
canvas.readPixels(0, 0, {
width: 1,
height: 1,
colorType,
alphaType,
})!
);
},
{ colorType: ColorType.RGBA_8888, alphaType: AlphaType.Unpremul }
);
expect(pixels).toBeDefined();
expect(Array.from(pixels!)).toEqual([255, 0, 0, 255]);
});
// it("should read pixels from a canvas using a preallocated buffer", async () => {
// const pixels = await surface.eval(
// (Skia, { colorType, alphaType }) => {
// const offscreen = Skia.Surface.MakeOffscreen(10, 10)!;
// const canvas = offscreen.getCanvas();
// canvas.drawColor(Skia.Color("red"));
// const result = new Uint8Array(4);
// canvas.readPixels(0, 0, {
// width: 1,
// height: 1,
// colorType,
// alphaType,
// }, result);
// },
// { colorType: ColorType.RGBA_8888, alphaType: AlphaType.Unpremul }
// );
// expect(pixels).toBeDefined();
// expect(Array.from(pixels!)).toEqual([255, 0, 0, 255]);
// });
// This test should only run on CI because it will trigger a redbox.
// While this is fine on CI, it is undesirable on local dev.
// it("should not crash with an invalid viewTag", async () => {
Expand Down
15 changes: 14 additions & 1 deletion package/src/skia/types/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { SkPaint } from "./Paint";
import type { SkRect } from "./Rect";
import type { SkFont } from "./Font";
import type { SkPath } from "./Path";
import type { SkImage, MipmapMode, FilterMode } from "./Image";
import type { SkImage, MipmapMode, FilterMode, ImageInfo } from "./Image";
import type { SkSVG } from "./SVG";
import type { SkColor } from "./Color";
import type { SkRRect } from "./RRect";
Expand Down Expand Up @@ -492,4 +492,17 @@ export interface SkCanvas {
* @param skp
*/
drawPicture(skp: SkPicture): void;

/** Read Image pixels
*
* @param srcX - x-axis upper left corner of the rectangle to read from
* @param srcY - y-axis upper left corner of the rectangle to read from
* @param imageInfo - describes the pixel format and dimensions of the data to read into
* @return Float32Array or Uint8Array with data or null if the read failed.
*/
readPixels(
srcX: number,
srcY: number,
imageInfo: ImageInfo
): Float32Array | Uint8Array | null;
}
Loading

0 comments on commit 83c7386

Please sign in to comment.