Merge pull request #1564 from lightpanda-io/nikneym/create-image-data

Add `createImageData` and `putImageData` to `CanvasRenderingContext2D`
This commit is contained in:
Karl Seguin
2026-02-19 17:56:02 +08:00
committed by GitHub
4 changed files with 100 additions and 6 deletions

View File

@@ -33,3 +33,58 @@
testing.expectEqual(ctx.fillStyle, "rgba(255, 0, 0, 0.06)");
}
</script>
<script id="CanvasRenderingContext2D#createImageData(width, height)">
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
const imageData = ctx.createImageData(100, 200);
testing.expectEqual(true, imageData instanceof ImageData);
testing.expectEqual(imageData.width, 100);
testing.expectEqual(imageData.height, 200);
testing.expectEqual(imageData.data.length, 100 * 200 * 4);
testing.expectEqual(true, imageData.data instanceof Uint8ClampedArray);
// All pixels should be initialized to 0.
testing.expectEqual(imageData.data[0], 0);
testing.expectEqual(imageData.data[1], 0);
testing.expectEqual(imageData.data[2], 0);
testing.expectEqual(imageData.data[3], 0);
}
</script>
<script id="CanvasRenderingContext2D#createImageData(imageData)">
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
const source = ctx.createImageData(50, 75);
const imageData = ctx.createImageData(source);
testing.expectEqual(true, imageData instanceof ImageData);
testing.expectEqual(imageData.width, 50);
testing.expectEqual(imageData.height, 75);
testing.expectEqual(imageData.data.length, 50 * 75 * 4);
}
</script>
<script id="CanvasRenderingContext2D#putImageData">
{
const element = document.createElement("canvas");
const ctx = element.getContext("2d");
const imageData = ctx.createImageData(10, 10);
testing.expectEqual(true, imageData instanceof ImageData);
// Modify some pixel data.
imageData.data[0] = 255;
imageData.data[1] = 0;
imageData.data[2] = 0;
imageData.data[3] = 255;
// putImageData should not throw.
ctx.putImageData(imageData, 0, 0);
ctx.putImageData(imageData, 10, 20);
// With dirty rect parameters.
ctx.putImageData(imageData, 0, 0, 0, 0, 5, 5);
}
</script>

View File

@@ -59,10 +59,6 @@
}
</script>
<script id=constructor-invalid-colorspace>
testing.expectError("TypeError", () => {
new ImageData(5, 5, { colorSpace: "display-p3" });
});
</script>
<script id=single-pixel>
@@ -73,3 +69,7 @@
testing.expectEqual(1, img.height);
}
</script>
<script id=too-large>
testing.expectError("IndexSizeError", () => new ImageData(2_147_483_648, 2_147_483_648));
</script>

View File

@@ -58,7 +58,10 @@ pub fn constructor(
maybe_settings: ?ConstructorSettings,
page: *Page,
) !*ImageData {
if (width == 0 or height == 0) {
// Though arguments are unsigned long, these are capped to max. i32 on Chrome.
// https://github.com/chromium/chromium/blob/main/third_party/blink/renderer/core/html/canvas/image_data.cc#L61
const max_i32 = std.math.maxInt(i32);
if (width == 0 or width > max_i32 or height == 0 or height > max_i32) {
return error.IndexSizeError;
}
@@ -70,7 +73,11 @@ pub fn constructor(
return error.TypeError;
}
const size = width * height * 4;
var size, var overflown = @mulWithOverflow(width, height);
if (overflown == 1) return error.IndexSizeError;
size, overflown = @mulWithOverflow(size, 4);
if (overflown == 1) return error.IndexSizeError;
return page._factory.create(ImageData{
._width = width,
._height = height,

View File

@@ -23,6 +23,8 @@ const js = @import("../../js/js.zig");
const color = @import("../../color.zig");
const Page = @import("../../Page.zig");
const ImageData = @import("../ImageData.zig");
/// This class doesn't implement a `constructor`.
/// It can be obtained with a call to `HTMLCanvasElement#getContext`.
/// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D
@@ -85,6 +87,33 @@ pub fn getTextBaseline(_: *const CanvasRenderingContext2D) []const u8 {
return "alphabetic";
}
const WidthOrImageData = union(enum) {
width: u32,
image_data: *ImageData,
};
pub fn createImageData(
_: *const CanvasRenderingContext2D,
width_or_image_data: WidthOrImageData,
/// If `ImageData` variant preferred, this is null.
maybe_height: ?u32,
/// Can be used if width and height provided.
maybe_settings: ?ImageData.ConstructorSettings,
page: *Page,
) !*ImageData {
switch (width_or_image_data) {
.width => |width| {
const height = maybe_height orelse return error.TypeError;
return ImageData.constructor(width, height, maybe_settings, page);
},
.image_data => |image_data| {
return ImageData.constructor(image_data._width, image_data._height, null, page);
},
}
}
pub fn putImageData(_: *const CanvasRenderingContext2D, _: *ImageData, _: f64, _: f64, _: ?f64, _: ?f64, _: ?f64, _: ?f64) void {}
pub fn save(_: *CanvasRenderingContext2D) void {}
pub fn restore(_: *CanvasRenderingContext2D) void {}
pub fn scale(_: *CanvasRenderingContext2D, _: f64, _: f64) void {}
@@ -131,6 +160,9 @@ pub const JsApi = struct {
pub var class_id: bridge.ClassId = undefined;
};
pub const createImageData = bridge.function(CanvasRenderingContext2D.createImageData, .{ .dom_exception = true });
pub const putImageData = bridge.function(CanvasRenderingContext2D.putImageData, .{});
pub const save = bridge.function(CanvasRenderingContext2D.save, .{});
pub const restore = bridge.function(CanvasRenderingContext2D.restore, .{});