Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle SkColor creation from rgba tuple #2144

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 18 additions & 6 deletions package/cpp/api/JsiSkColor.h
Expand Up @@ -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<float *>(buffer.data(runtime));
if (bfrPtr[0] > 1 || bfrPtr[1] > 1 || bfrPtr[2] > 1 || bfrPtr[3] > 1) {
return SK_ColorBLACK;
Expand Down Expand Up @@ -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,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes that's fair

"Skia.Color expected number, Float32Array, number[], "
"or string and got: " +
arguments[0].toString(runtime).utf8(runtime));
};
}
};
Expand Down
76 changes: 76 additions & 0 deletions 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]);
}
}
});
});
9 changes: 7 additions & 2 deletions 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;
Expand Down Expand Up @@ -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}`
);
}
};