diff --git a/package/cpp/api/JsiSkColor.h b/package/cpp/api/JsiSkColor.h index 3edda89563..5e1892ab25 100644 --- a/package/cpp/api/JsiSkColor.h +++ b/package/cpp/api/JsiSkColor.h @@ -45,10 +45,7 @@ class JsiSkColor : public RNJsi::JsiHostObject { static SkColor fromValue(jsi::Runtime &runtime, const jsi::Value &obj) { const auto &object = obj.asObject(runtime); jsi::ArrayBuffer buffer = - object - .getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer")) - .asObject(runtime) - .getArrayBuffer(runtime); + object.getPropertyAsObject(runtime, "buffer").getArrayBuffer(runtime); auto bfrPtr = reinterpret_cast(buffer.data(runtime)); if (bfrPtr[0] > 1 || bfrPtr[1] > 1 || bfrPtr[2] > 1 || bfrPtr[3] > 1) { return SK_ColorBLACK; @@ -76,9 +73,24 @@ class JsiSkColor : public RNJsi::JsiHostObject { return JsiSkColor::toValue( runtime, SkColorSetARGB(color.a * 255, color.r, color.g, color.b)); } else if (arguments[0].isObject()) { - return arguments[0].getObject(runtime); + auto obj = arguments[0].getObject(runtime); + if (obj.isArray(runtime)) { + auto array = obj.asArray(runtime); + // turn the array into a Float32Array + return runtime.global() + .getPropertyAsFunction(runtime, "Float32Array") + .callAsConstructor(runtime, array) + .getObject(runtime); + } else if (obj.hasProperty(runtime, "buffer") && + obj.getPropertyAsObject(runtime, "buffer") + .isArrayBuffer(runtime)) { + return obj; + } } - return jsi::Value::undefined(); + throw jsi::JSError(runtime, + "Skia.Color expected number, Float32Array, number[], " + "or string and got: " + + arguments[0].toString(runtime).utf8(runtime)); }; } }; diff --git a/package/src/renderer/__tests__/e2e/Colors.spec.tsx b/package/src/renderer/__tests__/e2e/Colors.spec.tsx new file mode 100644 index 0000000000..231a907eba --- /dev/null +++ b/package/src/renderer/__tests__/e2e/Colors.spec.tsx @@ -0,0 +1,76 @@ +import { surface } from "../setup"; + +describe("Colors", () => { + it("should support int, string, array, and Float32Array", async () => { + const result = await surface.eval((Skia) => { + const areEqualFloat32Arrays = (...arrays: Float32Array[]) => { + // Check if all arrays have the same length + const allSameLength = arrays.every( + (array) => array.length === arrays[0].length + ); + if (!allSameLength) { + return false; + } + + // Compare elements across all arrays for each index + for (let i = 0; i < arrays[0].length; i++) { + if (!arrays.every((array) => array[i] === arrays[0][i])) { + return false; + } + } + + return true; + }; + + const c1 = Skia.Color("cyan"); + const c2 = Skia.Color([0, 1, 1, 1]); + const c3 = Skia.Color(0xff00ffff); + const c4 = Skia.Color(Float32Array.of(0, 1, 1, 1)); + + const r = areEqualFloat32Arrays(c1, c2, c3, c4); + if (!r) { + console.log({ c1, c2, c3, c4 }); + } + return r; + }); + expect(result).toBe(true); + }); + it("should render the same color regardless of constructor input types", async () => { + // given (different inputs representing the same color + const colors = [ + { kind: "string" as const, value: "red" }, + { kind: "float32Array" as const, value: [1, 0, 0, 1] }, + { kind: "number" as const, value: 0xffff0000 }, + { kind: "array" as const, value: [1, 0, 0, 1] }, + ]; + + // when (for each input we draw a colored canvas and encode it to bytes) + const buffers = await Promise.all( + colors.map((color) => + surface + .drawOffscreen( + (Skia, canvas, ctx) => { + const c = + ctx.color.kind === "float32Array" + ? // we cannot pass in a Float32Array via ctx, need to construct it inside + new Float32Array(ctx.color.value) + : ctx.color.value; + canvas.drawColor(Skia.Color(c)); + }, + { color } + ) + .then((image) => image.encodeToBytes()) + ) + ); + + // then (expect the encoded bytes are equal) + for (let i = 1; i < buffers.length; i++) { + const prev = buffers[i - 1]; + const curr = buffers[i]; + expect(prev.length).toBe(curr.length); + for (let j = 0; j < prev.length; j++) { + expect(prev[j]).toBe(curr[j]); + } + } + }); +}); diff --git a/package/src/skia/web/JsiSkColor.ts b/package/src/skia/web/JsiSkColor.ts index 9ab43036e5..1b8258a1ff 100644 --- a/package/src/skia/web/JsiSkColor.ts +++ b/package/src/skia/web/JsiSkColor.ts @@ -1,4 +1,4 @@ -import type { SkColor, Color as InputColor } from "../types"; +import type { Color as InputColor, SkColor } from "../types"; const alphaf = (c: number) => ((c >> 24) & 255) / 255; const red = (c: number) => (c >> 16) & 255; @@ -329,12 +329,17 @@ export const Color = (color: InputColor): SkColor => { rgba[2] / 255, rgba[3] ); - } else { + } else if (typeof color === "number") { return Float32Array.of( red(color) / 255, green(color) / 255, blue(color) / 255, alphaf(color) ); + } else { + // Exhaustive case (though not possible on the type level, could be possible at runtime) + throw new Error( + `Skia.Color expected number, Float32Array, number[], or string and got: ${color}` + ); } };