@shopify/react-native-skia
Version:
High-performance React Native Graphics using Skia
157 lines (142 loc) • 3.9 kB
text/typescript
import type {
CanvasKit,
ImageInfo as CKImageInfo,
Image,
} from "canvaskit-wasm";
import type {
FilterMode,
MipmapMode,
SkImage,
SkMatrix,
SkShader,
TileMode,
ImageFormat,
ImageInfo,
} from "../types";
import { ckEnum, getCkEnum, HostObject } from "./Host";
import { JsiSkMatrix } from "./JsiSkMatrix";
import { JsiSkShader } from "./JsiSkShader";
// https://github.com/google/skia/blob/1f193df9b393d50da39570dab77a0bb5d28ec8ef/modules/canvaskit/htmlcanvas/util.js
export const toBase64String = (bytes: Uint8Array) => {
if (typeof Buffer !== "undefined") {
// Are we on node?
return Buffer.from(bytes).toString("base64");
} else {
// From https://stackoverflow.com/a/25644409
// because the naive solution of
// btoa(String.fromCharCode.apply(null, bytes));
// would occasionally throw "Maximum call stack size exceeded"
var CHUNK_SIZE = 0x8000; //arbitrary number
var index = 0;
var { length } = bytes;
var result = "";
var slice;
while (index < length) {
slice = bytes.slice(index, Math.min(index + CHUNK_SIZE, length));
// eslint-disable-next-line @typescript-eslint/no-explicit-any
result += String.fromCharCode.apply(null, slice as any);
index += CHUNK_SIZE;
}
return btoa(result);
}
};
export class JsiSkImage extends HostObject<Image, "Image"> implements SkImage {
constructor(CanvasKit: CanvasKit, ref: Image) {
super(CanvasKit, ref, "Image");
}
height() {
return this.ref.height();
}
width() {
return this.ref.width();
}
getImageInfo(): ImageInfo {
const info = this.ref.getImageInfo();
return {
width: info.width,
height: info.height,
colorType: info.colorType.value,
alphaType: info.alphaType.value,
};
}
makeShaderOptions(
tx: TileMode,
ty: TileMode,
fm: FilterMode,
mm: MipmapMode,
localMatrix?: SkMatrix
): SkShader {
return new JsiSkShader(
this.CanvasKit,
this.ref.makeShaderOptions(
ckEnum(tx),
ckEnum(ty),
ckEnum(fm),
ckEnum(mm),
localMatrix ? JsiSkMatrix.fromValue(localMatrix) : undefined
)
);
}
makeShaderCubic(
tx: TileMode,
ty: TileMode,
B: number,
C: number,
localMatrix?: SkMatrix
): SkShader {
return new JsiSkShader(
this.CanvasKit,
this.ref.makeShaderCubic(
ckEnum(tx),
ckEnum(ty),
B,
C,
localMatrix ? JsiSkMatrix.fromValue(localMatrix) : undefined
)
);
}
encodeToBytes(fmt?: ImageFormat, quality?: number) {
let result: Uint8Array | null;
if (fmt && quality) {
result = this.ref.encodeToBytes(ckEnum(fmt), quality);
} else if (fmt) {
result = this.ref.encodeToBytes(ckEnum(fmt));
} else {
result = this.ref.encodeToBytes();
}
if (!result) {
throw new Error("encodeToBytes failed");
}
return result;
}
encodeToBase64(fmt?: ImageFormat, quality?: number) {
const bytes = this.encodeToBytes(fmt, quality);
return toBase64String(bytes);
}
readPixels(srcX?: number, srcY?: number, imageInfo?: ImageInfo) {
const info = this.getImageInfo();
const pxInfo: CKImageInfo = {
colorSpace: this.CanvasKit.ColorSpace.SRGB,
width: imageInfo?.width ?? info.width,
height: imageInfo?.height ?? info.height,
alphaType: getCkEnum(
this.CanvasKit.AlphaType,
(imageInfo ?? info).alphaType
),
colorType: getCkEnum(
this.CanvasKit.ColorType,
(imageInfo ?? info).colorType
),
};
return this.ref.readPixels(srcX ?? 0, srcY ?? 0, pxInfo);
}
dispose = () => {
this.ref.delete();
};
makeNonTextureImage(): SkImage {
return new JsiSkImage(
this.CanvasKit,
this.CanvasKit.MakeImageFromEncoded(this.encodeToBytes())!
);
}
}