mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1553 from lightpanda-io/nikneym/image-data
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
e2e-test / browser fetch (push) Has been cancelled
zig-test / zig test using v8 in debug mode (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Basic support for `ImageData`
This commit is contained in:
@@ -1045,6 +1045,12 @@ pub const FinalizerCallback = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Creates a new typed array. Memory is owned by JS context.
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Typed_arrays
|
||||||
|
pub fn createTypedArray(self: *Context, comptime array_type: js.ArrayType, size: usize) js.ArrayBufferRef(array_type) {
|
||||||
|
return .init(self.isolate, size);
|
||||||
|
}
|
||||||
|
|
||||||
// == Profiler ==
|
// == Profiler ==
|
||||||
pub fn startCpuProfiler(self: *Context) void {
|
pub fn startCpuProfiler(self: *Context) void {
|
||||||
if (comptime !IS_DEBUG) {
|
if (comptime !IS_DEBUG) {
|
||||||
|
|||||||
@@ -306,6 +306,14 @@ pub fn zigValueToJs(self: *const Local, value: anytype, comptime opts: CallOpts)
|
|||||||
js.Value => return value,
|
js.Value => return value,
|
||||||
js.Exception => return .{ .local = self, .handle = isolate.throwException(value.handle) },
|
js.Exception => return .{ .local = self, .handle = isolate.throwException(value.handle) },
|
||||||
|
|
||||||
|
js.ArrayBufferRef(.int8), js.ArrayBufferRef(.uint8), js.ArrayBufferRef(.uint8_clamped),
|
||||||
|
js.ArrayBufferRef(.int16), js.ArrayBufferRef(.uint16),
|
||||||
|
js.ArrayBufferRef(.int32), js.ArrayBufferRef(.uint32),
|
||||||
|
js.ArrayBufferRef(.float16), js.ArrayBufferRef(.float32), js.ArrayBufferRef(.float64),
|
||||||
|
=> {
|
||||||
|
return .{ .local = self, .handle = value.handle };
|
||||||
|
},
|
||||||
|
|
||||||
inline
|
inline
|
||||||
js.Function,
|
js.Function,
|
||||||
js.Object,
|
js.Object,
|
||||||
|
|||||||
@@ -864,4 +864,5 @@ pub const JsApis = flattenTypes(&.{
|
|||||||
@import("../webapi/canvas/WebGLRenderingContext.zig"),
|
@import("../webapi/canvas/WebGLRenderingContext.zig"),
|
||||||
@import("../webapi/SubtleCrypto.zig"),
|
@import("../webapi/SubtleCrypto.zig"),
|
||||||
@import("../webapi/Selection.zig"),
|
@import("../webapi/Selection.zig"),
|
||||||
|
@import("../webapi/ImageData.zig"),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,14 +77,69 @@ pub const ArrayBuffer = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// `ref` indicates bytes are not copied by `simpleZigValueToJs`;
|
pub const ArrayType = enum(u8) {
|
||||||
/// instead, `values` references an already allocated memory. Note that
|
int8,
|
||||||
/// this variant assumes memory is (de)allocated by an arena allocator.
|
uint8,
|
||||||
///
|
uint8_clamped,
|
||||||
/// `copy` behaves the same as `TypedArray(T)`.
|
int16,
|
||||||
pub fn Uint8ClampedArray(comptime state: enum(u1) { ref, copy }) type {
|
uint16,
|
||||||
|
int32,
|
||||||
|
uint32,
|
||||||
|
float16,
|
||||||
|
float32,
|
||||||
|
float64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn ArrayBufferRef(comptime kind: ArrayType) type {
|
||||||
return struct {
|
return struct {
|
||||||
values: if (state == .ref) []u8 else []const u8,
|
const Self = @This();
|
||||||
|
|
||||||
|
const BackingInt = switch (kind) {
|
||||||
|
.int8 => i8,
|
||||||
|
.uint8, .uint8_clamped => u8,
|
||||||
|
.int16 => i16,
|
||||||
|
.uint16 => u16,
|
||||||
|
.int32 => i32,
|
||||||
|
.uint32 => u32,
|
||||||
|
.float16 => f16,
|
||||||
|
.float32 => f32,
|
||||||
|
.float64 => f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
handle: *const v8.Value,
|
||||||
|
|
||||||
|
pub fn init(isolate: Isolate, size: usize) Self {
|
||||||
|
const bits = switch (@typeInfo(BackingInt)) {
|
||||||
|
.int => |n| n.bits,
|
||||||
|
.float => |f| f.bits,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
|
var array_buffer: *const v8.ArrayBuffer = undefined;
|
||||||
|
if (size == 0) {
|
||||||
|
array_buffer = v8.v8__ArrayBuffer__New(isolate.handle, 0).?;
|
||||||
|
} else {
|
||||||
|
const buffer_len = size * bits / 8;
|
||||||
|
const backing_store = v8.v8__ArrayBuffer__NewBackingStore(isolate.handle, buffer_len).?;
|
||||||
|
const backing_store_ptr = v8.v8__BackingStore__TO_SHARED_PTR(backing_store);
|
||||||
|
array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handle: *const v8.Value = switch (comptime kind) {
|
||||||
|
.int8 => @ptrCast(v8.v8__Int8Array__New(array_buffer, 0, size).?),
|
||||||
|
.uint8 => @ptrCast(v8.v8__Uint8Array__New(array_buffer, 0, size).?),
|
||||||
|
.uint8_clamped => @ptrCast(v8.v8__Uint8ClampedArray__New(array_buffer, 0, size).?),
|
||||||
|
.int16 => @ptrCast(v8.v8__Int16Array__New(array_buffer, 0, size).?),
|
||||||
|
.uint16 => @ptrCast(v8.v8__Uint16Array__New(array_buffer, 0, size).?),
|
||||||
|
.int32 => @ptrCast(v8.v8__Int32Array__New(array_buffer, 0, size).?),
|
||||||
|
.uint32 => @ptrCast(v8.v8__Uint32Array__New(array_buffer, 0, size).?),
|
||||||
|
.float16 => @ptrCast(v8.v8__Float16Array__New(array_buffer, 0, size).?),
|
||||||
|
.float32 => @ptrCast(v8.v8__Float32Array__New(array_buffer, 0, size).?),
|
||||||
|
.float64 => @ptrCast(v8.v8__Float64Array__New(array_buffer, 0, size).?),
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{ .handle = handle };
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,40 +262,6 @@ pub fn simpleZigValueToJs(isolate: Isolate, value: anytype, comptime fail: bool,
|
|||||||
// but this can never be valid.
|
// but this can never be valid.
|
||||||
@compileError("Invalid TypeArray type: " ++ @typeName(value_type));
|
@compileError("Invalid TypeArray type: " ++ @typeName(value_type));
|
||||||
},
|
},
|
||||||
Uint8ClampedArray(.ref) => {
|
|
||||||
const values = value.values;
|
|
||||||
const len = values.len;
|
|
||||||
var array_buffer: *const v8.ArrayBuffer = undefined;
|
|
||||||
if (len == 0) {
|
|
||||||
array_buffer = v8.v8__ArrayBuffer__New(isolate.handle, 0).?;
|
|
||||||
} else {
|
|
||||||
// `deleter` cannot be null.
|
|
||||||
const empty_deleter = struct {
|
|
||||||
fn deleter(_: ?*anyopaque, _: usize, _: ?*anyopaque) callconv(.c) void {}
|
|
||||||
}.deleter;
|
|
||||||
const backing_store = v8.v8__ArrayBuffer__NewBackingStore2(values.ptr, len, empty_deleter, null);
|
|
||||||
const backing_store_ptr = v8.v8__BackingStore__TO_SHARED_PTR(backing_store);
|
|
||||||
// Attach store to array buffer.
|
|
||||||
array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?;
|
|
||||||
}
|
|
||||||
return @ptrCast(v8.v8__Uint8ClampedArray__New(array_buffer, 0, len));
|
|
||||||
},
|
|
||||||
Uint8ClampedArray(.copy) => {
|
|
||||||
const values = value.values;
|
|
||||||
const len = values.len;
|
|
||||||
var array_buffer: *const v8.ArrayBuffer = undefined;
|
|
||||||
if (len == 0) {
|
|
||||||
array_buffer = v8.v8__ArrayBuffer__New(isolate.handle, 0).?;
|
|
||||||
} else {
|
|
||||||
const backing_store = v8.v8__ArrayBuffer__NewBackingStore(isolate.handle, len);
|
|
||||||
const data: [*]u8 = @ptrCast(@alignCast(v8.v8__BackingStore__Data(backing_store)));
|
|
||||||
@memcpy(data[0..len], @as([]const u8, @ptrCast(values))[0..len]);
|
|
||||||
const backing_store_ptr = v8.v8__BackingStore__TO_SHARED_PTR(backing_store);
|
|
||||||
// Attach store to array buffer.
|
|
||||||
array_buffer = v8.v8__ArrayBuffer__New2(isolate.handle, &backing_store_ptr).?;
|
|
||||||
}
|
|
||||||
return @ptrCast(v8.v8__Uint8ClampedArray__New(array_buffer, 0, len));
|
|
||||||
},
|
|
||||||
inline String, BigInt, Integer, Number, Value, Object => return value.handle,
|
inline String, BigInt, Integer, Number, Value, Object => return value.handle,
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/browser/tests/image_data.html
Normal file
75
src/browser/tests/image_data.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<script src="testing.js"></script>
|
||||||
|
|
||||||
|
<script id=constructor-basic>
|
||||||
|
{
|
||||||
|
const img = new ImageData(10, 20);
|
||||||
|
testing.expectEqual(10, img.width);
|
||||||
|
testing.expectEqual(20, img.height);
|
||||||
|
testing.expectEqual("srgb", img.colorSpace);
|
||||||
|
testing.expectEqual("rgba-unorm8", img.pixelFormat);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=data-property>
|
||||||
|
{
|
||||||
|
const img = new ImageData(2, 3);
|
||||||
|
const data = img.data;
|
||||||
|
testing.expectEqual(true, data instanceof Uint8ClampedArray);
|
||||||
|
// 2 * 3 * 4 (RGBA) = 24 bytes
|
||||||
|
testing.expectEqual(24, data.length);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=data-initialized-to-zero>
|
||||||
|
{
|
||||||
|
const img = new ImageData(2, 2);
|
||||||
|
const data = img.data;
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
testing.expectEqual(0, data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=data-mutability>
|
||||||
|
{
|
||||||
|
const img = new ImageData(1, 1);
|
||||||
|
const data = img.data;
|
||||||
|
// Set pixel to red (RGBA)
|
||||||
|
data[0] = 255;
|
||||||
|
data[1] = 0;
|
||||||
|
data[2] = 0;
|
||||||
|
data[3] = 255;
|
||||||
|
|
||||||
|
// Read back through the same accessor
|
||||||
|
const data2 = img.data;
|
||||||
|
testing.expectEqual(255, data2[0]);
|
||||||
|
testing.expectEqual(0, data2[1]);
|
||||||
|
testing.expectEqual(0, data2[2]);
|
||||||
|
testing.expectEqual(255, data2[3]);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=constructor-with-settings>
|
||||||
|
{
|
||||||
|
const img = new ImageData(5, 5, { colorSpace: "srgb" });
|
||||||
|
testing.expectEqual(5, img.width);
|
||||||
|
testing.expectEqual(5, img.height);
|
||||||
|
testing.expectEqual("srgb", img.colorSpace);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=constructor-invalid-colorspace>
|
||||||
|
testing.expectError("TypeError", () => {
|
||||||
|
new ImageData(5, 5, { colorSpace: "display-p3" });
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id=single-pixel>
|
||||||
|
{
|
||||||
|
const img = new ImageData(1, 1);
|
||||||
|
testing.expectEqual(4, img.data.length);
|
||||||
|
testing.expectEqual(1, img.width);
|
||||||
|
testing.expectEqual(1, img.height);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
122
src/browser/webapi/ImageData.zig
Normal file
122
src/browser/webapi/ImageData.zig
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (C) 2023-2026 Lightpanda (Selecy SAS)
|
||||||
|
//
|
||||||
|
// Francis Bouvier <francis@lightpanda.io>
|
||||||
|
// Pierre Tachoire <pierre@lightpanda.io>
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as
|
||||||
|
// published by the Free Software Foundation, either version 3 of the
|
||||||
|
// License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const String = @import("../../string.zig").String;
|
||||||
|
const log = @import("../../log.zig");
|
||||||
|
|
||||||
|
const js = @import("../js/js.zig");
|
||||||
|
const color = @import("../color.zig");
|
||||||
|
const Page = @import("../Page.zig");
|
||||||
|
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/API/ImageData/ImageData
|
||||||
|
const ImageData = @This();
|
||||||
|
_width: u32,
|
||||||
|
_height: u32,
|
||||||
|
_data: js.ArrayBufferRef(.uint8_clamped),
|
||||||
|
|
||||||
|
pub const ConstructorSettings = struct {
|
||||||
|
/// Specifies the color space of the image data.
|
||||||
|
/// Can be set to "srgb" for the sRGB color space or "display-p3" for the display-p3 color space.
|
||||||
|
colorSpace: String = .wrap("srgb"),
|
||||||
|
/// Specifies the pixel format.
|
||||||
|
/// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createImageData#pixelformat
|
||||||
|
pixelFormat: String = .wrap("rgba-unorm8"),
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This has many constructors:
|
||||||
|
///
|
||||||
|
/// ```js
|
||||||
|
/// new ImageData(width, height)
|
||||||
|
/// new ImageData(width, height, settings)
|
||||||
|
///
|
||||||
|
/// new ImageData(dataArray, width)
|
||||||
|
/// new ImageData(dataArray, width, height)
|
||||||
|
/// new ImageData(dataArray, width, height, settings)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// We currently support only the first 2.
|
||||||
|
pub fn constructor(
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
maybe_settings: ?ConstructorSettings,
|
||||||
|
page: *Page,
|
||||||
|
) !*ImageData {
|
||||||
|
if (width == 0 or height == 0) {
|
||||||
|
return error.IndexSizeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings: ConstructorSettings = maybe_settings orelse .{};
|
||||||
|
if (settings.colorSpace.eql(comptime .wrap("srgb")) == false) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
if (settings.pixelFormat.eql(comptime .wrap("rgba-unorm8")) == false) {
|
||||||
|
return error.TypeError;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = width * height * 4;
|
||||||
|
return page._factory.create(ImageData{
|
||||||
|
._width = width,
|
||||||
|
._height = height,
|
||||||
|
._data = page.js.createTypedArray(.uint8_clamped, size),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getWidth(self: *const ImageData) u32 {
|
||||||
|
return self._width;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getHeight(self: *const ImageData) u32 {
|
||||||
|
return self._height;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPixelFormat(_: *const ImageData) String {
|
||||||
|
return comptime .wrap("rgba-unorm8");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getColorSpace(_: *const ImageData) String {
|
||||||
|
return comptime .wrap("srgb");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getData(self: *const ImageData) js.ArrayBufferRef(.uint8_clamped) {
|
||||||
|
return self._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const JsApi = struct {
|
||||||
|
pub const bridge = js.Bridge(ImageData);
|
||||||
|
|
||||||
|
pub const Meta = struct {
|
||||||
|
pub const name = "ImageData";
|
||||||
|
pub const prototype_chain = bridge.prototypeChain();
|
||||||
|
pub var class_id: bridge.ClassId = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const constructor = bridge.constructor(ImageData.constructor, .{ .dom_exception = true });
|
||||||
|
|
||||||
|
pub const width = bridge.accessor(ImageData.getWidth, null, .{});
|
||||||
|
pub const height = bridge.accessor(ImageData.getHeight, null, .{});
|
||||||
|
pub const pixelFormat = bridge.accessor(ImageData.getPixelFormat, null, .{});
|
||||||
|
pub const colorSpace = bridge.accessor(ImageData.getColorSpace, null, .{});
|
||||||
|
pub const data = bridge.accessor(ImageData.getData, null, .{});
|
||||||
|
};
|
||||||
|
|
||||||
|
const testing = @import("../../testing.zig");
|
||||||
|
test "WebApi: ImageData" {
|
||||||
|
try testing.htmlRunner("image_data.html", .{});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user