Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 53 additions & 1 deletion packages/skia/cpp/api/JsiSkColor.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ class JsiSkColor : public RNJsi::JsiHostObject {

static SkColor fromValue(jsi::Runtime &runtime, const jsi::Value &obj) {
const auto &object = obj.asObject(runtime);

// Handle regular JavaScript arrays
if (object.isArray(runtime)) {
auto array = object.asArray(runtime);
if (array.size(runtime) != 4) {
throw jsi::JSError(runtime,
"Expected array of length 4 for color, got " +
std::to_string(array.size(runtime)));
}
auto r = array.getValueAtIndex(runtime, 0).asNumber();
auto g = array.getValueAtIndex(runtime, 1).asNumber();
auto b = array.getValueAtIndex(runtime, 2).asNumber();
auto a = array.getValueAtIndex(runtime, 3).asNumber();
return SkColorSetARGB(a * 255, r * 255, g * 255, b * 255);
}

// Handle Float32Array (has buffer property)
jsi::ArrayBuffer buffer =
object
.getProperty(runtime, jsi::PropNameID::forAscii(runtime, "buffer"))
Expand Down Expand Up @@ -76,7 +93,42 @@ 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);

// Check if it's a regular array - convert to Float32Array
if (obj.isArray(runtime)) {
auto arr = obj.getArray(runtime);
if (arr.size(runtime) != 4) {
throw jsi::JSError(runtime,
"Expected array of length 4 for color, got " +
std::to_string(arr.size(runtime)));
}
auto r = static_cast<float>(arr.getValueAtIndex(runtime, 0).asNumber());
auto g = static_cast<float>(arr.getValueAtIndex(runtime, 1).asNumber());
auto b = static_cast<float>(arr.getValueAtIndex(runtime, 2).asNumber());
auto a = static_cast<float>(arr.getValueAtIndex(runtime, 3).asNumber());

// Create Float32Array and populate
auto result = runtime.global()
.getPropertyAsFunction(runtime, "Float32Array")
.callAsConstructor(runtime, 4)
.getObject(runtime);
jsi::ArrayBuffer buffer =
result
.getProperty(runtime,
jsi::PropNameID::forAscii(runtime, "buffer"))
.asObject(runtime)
.getArrayBuffer(runtime);
auto bfrPtr = reinterpret_cast<float *>(buffer.data(runtime));
bfrPtr[0] = r;
bfrPtr[1] = g;
bfrPtr[2] = b;
bfrPtr[3] = a;
return result;
}

// Already a Float32Array or similar - return as-is
return obj;
}
return jsi::Value::undefined();
};
Expand Down
106 changes: 106 additions & 0 deletions packages/skia/src/renderer/__tests__/e2e/Color.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { surface } from "../setup";

describe("Skia.Color", () => {
it("should convert regular array to Float32Array", async () => {
const result = await surface.eval((Skia) => {
const color = Skia.Color([1, 0, 0, 1]); // Red
return Array.from(color);
});
expect(result[0]).toBeCloseTo(1);
expect(result[1]).toBeCloseTo(0);
expect(result[2]).toBeCloseTo(0);
expect(result[3]).toBeCloseTo(1);
});

it("should handle CSS color names", async () => {
const result = await surface.eval((Skia) => {
const color = Skia.Color("cyan");
return Array.from(color);
});
expect(result[0]).toBeCloseTo(0); // R
expect(result[1]).toBeCloseTo(1); // G
expect(result[2]).toBeCloseTo(1); // B
expect(result[3]).toBeCloseTo(1); // A
});

it("should handle hex colors", async () => {
const result = await surface.eval((Skia) => {
const color = Skia.Color("#ff0000");
return Array.from(color);
});
expect(result[0]).toBeCloseTo(1);
expect(result[1]).toBeCloseTo(0);
expect(result[2]).toBeCloseTo(0);
});

it("should handle hex colors with alpha", async () => {
const result = await surface.eval((Skia) => {
const color = Skia.Color("#ff000080");
return Array.from(color);
});
expect(result[0]).toBeCloseTo(1);
expect(result[3]).toBeCloseTo(0.5, 1); // ~128/255
});

it("should work with paint.setColor using array", async () => {
// This should not throw
const result = await surface.eval((Skia) => {
const paint = Skia.Paint();
paint.setColor(Skia.Color([1, 0, 0, 1]));
return true;
});
expect(result).toBe(true);
});

it("should work in createPicture callback", async () => {
// This tests the original bug from issue #2200
const result = await surface.eval((Skia) => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording({
x: 0,
y: 0,
width: 100,
height: 100,
});
const paint = Skia.Paint();
paint.setColor(Skia.Color("cyan"));
canvas.drawCircle(50, 50, 25, paint);
const picture = recorder.finishRecordingAsPicture();
return picture !== null;
});
expect(result).toBe(true);
});

it("should handle array colors in createPicture callback", async () => {
// This tests the specific scenario from issue #2200 with array colors
const result = await surface.eval((Skia) => {
const recorder = Skia.PictureRecorder();
const canvas = recorder.beginRecording({
x: 0,
y: 0,
width: 100,
height: 100,
});
const paint = Skia.Paint();
// Use array color like in the original bug report
paint.setColor(Skia.Color([0, 1, 1, 1])); // cyan as array
canvas.drawCircle(50, 50, 25, paint);
const picture = recorder.finishRecordingAsPicture();
return picture !== null;
});
expect(result).toBe(true);
});

it("should pass through Float32Array unchanged", async () => {
const result = await surface.eval((Skia) => {
const input = Float32Array.of(0.5, 0.5, 0.5, 1);
const color = Skia.Color(input);
// Check that values are preserved
return Array.from(color);
});
expect(result[0]).toBeCloseTo(0.5);
expect(result[1]).toBeCloseTo(0.5);
expect(result[2]).toBeCloseTo(0.5);
expect(result[3]).toBeCloseTo(1);
});
});
Loading