mirror of
https://github.com/denoland/deno.git
synced 2024-11-27 16:10:57 -05:00
feat(web): ImageBitmap (#21898)
This commit is contained in:
parent
b4990d1aa2
commit
8f76762793
27 changed files with 1258 additions and 256 deletions
78
Cargo.lock
generated
78
Cargo.lock
generated
|
@ -491,6 +491,12 @@ version = "3.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
|
@ -628,6 +634,12 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "color_quant"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "colorchoice"
|
name = "colorchoice"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
|
@ -1124,6 +1136,17 @@ dependencies = [
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deno_canvas"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"deno_core",
|
||||||
|
"deno_webgpu",
|
||||||
|
"image",
|
||||||
|
"serde",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deno_config"
|
name = "deno_config"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
@ -1621,6 +1644,7 @@ dependencies = [
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
"deno_broadcast_channel",
|
"deno_broadcast_channel",
|
||||||
"deno_cache",
|
"deno_cache",
|
||||||
|
"deno_canvas",
|
||||||
"deno_console",
|
"deno_console",
|
||||||
"deno_core",
|
"deno_core",
|
||||||
"deno_cron",
|
"deno_cron",
|
||||||
|
@ -2402,6 +2426,15 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "209098dd6dfc4445aa6111f0e98653ac323eaa4dfd212c9ca3931bf9955c31bd"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -3168,6 +3201,20 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.24.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder",
|
||||||
|
"color_quant",
|
||||||
|
"num-rational",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "import_map"
|
name = "import_map"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
|
@ -3713,6 +3760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler",
|
||||||
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3947,6 +3995,17 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
|
@ -4381,6 +4440,19 @@ dependencies = [
|
||||||
"syn 2.0.48",
|
"syn 2.0.48",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.17.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd75bf2d8dd3702b9707cdbc56a5b9ef42cec752eb8b3bafc01234558442aa64"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "polyval"
|
name = "polyval"
|
||||||
version = "0.6.1"
|
version = "0.6.1"
|
||||||
|
@ -5332,6 +5404,12 @@ dependencies = [
|
||||||
"rand_core",
|
"rand_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simd-adler32"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "simd-json"
|
name = "simd-json"
|
||||||
version = "0.13.4"
|
version = "0.13.4"
|
||||||
|
|
|
@ -12,6 +12,7 @@ members = [
|
||||||
"test_util",
|
"test_util",
|
||||||
"ext/broadcast_channel",
|
"ext/broadcast_channel",
|
||||||
"ext/cache",
|
"ext/cache",
|
||||||
|
"ext/canvas",
|
||||||
"ext/console",
|
"ext/console",
|
||||||
"ext/cron",
|
"ext/cron",
|
||||||
"ext/crypto",
|
"ext/crypto",
|
||||||
|
@ -58,6 +59,7 @@ denokv_remote = "0.5.0"
|
||||||
# exts
|
# exts
|
||||||
deno_broadcast_channel = { version = "0.126.0", path = "./ext/broadcast_channel" }
|
deno_broadcast_channel = { version = "0.126.0", path = "./ext/broadcast_channel" }
|
||||||
deno_cache = { version = "0.64.0", path = "./ext/cache" }
|
deno_cache = { version = "0.64.0", path = "./ext/cache" }
|
||||||
|
deno_canvas = { version = "0.1.0", path = "./ext/canvas" }
|
||||||
deno_console = { version = "0.132.0", path = "./ext/console" }
|
deno_console = { version = "0.132.0", path = "./ext/console" }
|
||||||
deno_cron = { version = "0.12.0", path = "./ext/cron" }
|
deno_cron = { version = "0.12.0", path = "./ext/cron" }
|
||||||
deno_crypto = { version = "0.146.0", path = "./ext/crypto" }
|
deno_crypto = { version = "0.146.0", path = "./ext/crypto" }
|
||||||
|
|
|
@ -152,6 +152,7 @@ mod ts {
|
||||||
op_crate_libs.insert("deno.webgpu", deno_webgpu_get_declaration());
|
op_crate_libs.insert("deno.webgpu", deno_webgpu_get_declaration());
|
||||||
op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
|
op_crate_libs.insert("deno.websocket", deno_websocket::get_declaration());
|
||||||
op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
|
op_crate_libs.insert("deno.webstorage", deno_webstorage::get_declaration());
|
||||||
|
op_crate_libs.insert("deno.canvas", deno_canvas::get_declaration());
|
||||||
op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
|
op_crate_libs.insert("deno.crypto", deno_crypto::get_declaration());
|
||||||
op_crate_libs.insert(
|
op_crate_libs.insert(
|
||||||
"deno.broadcast_channel",
|
"deno.broadcast_channel",
|
||||||
|
|
|
@ -42,6 +42,7 @@ util::unit_test_factory!(
|
||||||
globals_test,
|
globals_test,
|
||||||
headers_test,
|
headers_test,
|
||||||
http_test,
|
http_test,
|
||||||
|
image_bitmap_test,
|
||||||
image_data_test,
|
image_data_test,
|
||||||
internals_test,
|
internals_test,
|
||||||
intl_test,
|
intl_test,
|
||||||
|
|
|
@ -34,6 +34,7 @@ let knownGlobals = [
|
||||||
closed,
|
closed,
|
||||||
confirm,
|
confirm,
|
||||||
console,
|
console,
|
||||||
|
createImageBitmap,
|
||||||
crypto,
|
crypto,
|
||||||
Deno,
|
Deno,
|
||||||
dispatchEvent,
|
dispatchEvent,
|
||||||
|
|
92
cli/tests/unit/image_bitmap_test.ts
Normal file
92
cli/tests/unit/image_bitmap_test.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { assertEquals } from "./test_util.ts";
|
||||||
|
|
||||||
|
function generateNumberedData(n: number): Uint8ClampedArray {
|
||||||
|
return new Uint8ClampedArray(
|
||||||
|
Array.from({ length: n }, (_, i) => [i + 1, 0, 0, 1]).flat(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapDirect() {
|
||||||
|
const data = generateNumberedData(3);
|
||||||
|
const imageData = new ImageData(data, 3, 1);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData);
|
||||||
|
assertEquals(
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
Deno[Deno.internal].getBitmapData(imageBitmap),
|
||||||
|
new Uint8Array(data.buffer),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapCrop() {
|
||||||
|
const data = generateNumberedData(3 * 3);
|
||||||
|
const imageData = new ImageData(data, 3, 3);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData, 1, 1, 1, 1);
|
||||||
|
assertEquals(
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
Deno[Deno.internal].getBitmapData(imageBitmap),
|
||||||
|
new Uint8Array([5, 0, 0, 1]),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapCropPartialNegative() {
|
||||||
|
const data = generateNumberedData(3 * 3);
|
||||||
|
const imageData = new ImageData(data, 3, 3);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData, -1, -1, 2, 2);
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
// deno-fmt-ignore
|
||||||
|
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 0, 1
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapCropGreater() {
|
||||||
|
const data = generateNumberedData(3 * 3);
|
||||||
|
const imageData = new ImageData(data, 3, 3);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData, -1, -1, 5, 5);
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
// deno-fmt-ignore
|
||||||
|
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 4, 0, 0, 1, 5, 0, 0, 1, 6, 0, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 7, 0, 0, 1, 8, 0, 0, 1, 9, 0, 0, 1, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapScale() {
|
||||||
|
const data = generateNumberedData(3);
|
||||||
|
const imageData = new ImageData(data, 3, 1);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData, {
|
||||||
|
resizeHeight: 5,
|
||||||
|
resizeWidth: 5,
|
||||||
|
resizeQuality: "pixelated",
|
||||||
|
});
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
// deno-fmt-ignore
|
||||||
|
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([
|
||||||
|
1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1, 3, 0, 0, 1
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Deno.test(async function imageBitmapFlipY() {
|
||||||
|
const data = generateNumberedData(9);
|
||||||
|
const imageData = new ImageData(data, 3, 3);
|
||||||
|
const imageBitmap = await createImageBitmap(imageData, {
|
||||||
|
imageOrientation: "flipY",
|
||||||
|
});
|
||||||
|
// @ts-ignore: Deno[Deno.internal].core allowed
|
||||||
|
// deno-fmt-ignore
|
||||||
|
assertEquals(Deno[Deno.internal].getBitmapData(imageBitmap), new Uint8Array([
|
||||||
|
7, 0, 0, 1, 8, 0, 0, 1, 9, 0, 0, 1,
|
||||||
|
4, 0, 0, 1, 5, 0, 0, 1, 6, 0, 0, 1,
|
||||||
|
1, 0, 0, 1, 2, 0, 0, 1, 3, 0, 0, 1,
|
||||||
|
]));
|
||||||
|
});
|
2
cli/tsc/dts/lib.deno.shared_globals.d.ts
vendored
2
cli/tsc/dts/lib.deno.shared_globals.d.ts
vendored
|
@ -8,6 +8,8 @@
|
||||||
/// <reference lib="deno.console" />
|
/// <reference lib="deno.console" />
|
||||||
/// <reference lib="deno.url" />
|
/// <reference lib="deno.url" />
|
||||||
/// <reference lib="deno.web" />
|
/// <reference lib="deno.web" />
|
||||||
|
/// <reference lib="deno.webgpu" />
|
||||||
|
/// <reference lib="deno.canvas" />
|
||||||
/// <reference lib="deno.fetch" />
|
/// <reference lib="deno.fetch" />
|
||||||
/// <reference lib="deno.websocket" />
|
/// <reference lib="deno.websocket" />
|
||||||
/// <reference lib="deno.crypto" />
|
/// <reference lib="deno.crypto" />
|
||||||
|
|
1
cli/tsc/dts/lib.deno.window.d.ts
vendored
1
cli/tsc/dts/lib.deno.window.d.ts
vendored
|
@ -3,7 +3,6 @@
|
||||||
/// <reference no-default-lib="true" />
|
/// <reference no-default-lib="true" />
|
||||||
/// <reference lib="deno.ns" />
|
/// <reference lib="deno.ns" />
|
||||||
/// <reference lib="deno.shared_globals" />
|
/// <reference lib="deno.shared_globals" />
|
||||||
/// <reference lib="deno.webgpu" />
|
|
||||||
/// <reference lib="deno.webstorage" />
|
/// <reference lib="deno.webstorage" />
|
||||||
/// <reference lib="esnext" />
|
/// <reference lib="esnext" />
|
||||||
/// <reference lib="deno.cache" />
|
/// <reference lib="deno.cache" />
|
||||||
|
|
|
@ -94,6 +94,7 @@ pub fn get_types_declaration_file_text() -> String {
|
||||||
"deno.webgpu",
|
"deno.webgpu",
|
||||||
"deno.websocket",
|
"deno.websocket",
|
||||||
"deno.webstorage",
|
"deno.webstorage",
|
||||||
|
"deno.canvas",
|
||||||
"deno.crypto",
|
"deno.crypto",
|
||||||
"deno.broadcast_channel",
|
"deno.broadcast_channel",
|
||||||
"deno.net",
|
"deno.net",
|
||||||
|
|
552
ext/canvas/01_image.js
Normal file
552
ext/canvas/01_image.js
Normal file
|
@ -0,0 +1,552 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
import { core, internals, primordials } from "ext:core/mod.js";
|
||||||
|
const ops = core.ops;
|
||||||
|
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
||||||
|
import { DOMException } from "ext:deno_web/01_dom_exception.js";
|
||||||
|
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
|
||||||
|
import { BlobPrototype } from "ext:deno_web/09_file.js";
|
||||||
|
import { sniffImage } from "ext:deno_web/01_mimesniff.js";
|
||||||
|
const {
|
||||||
|
ObjectPrototypeIsPrototypeOf,
|
||||||
|
Symbol,
|
||||||
|
SymbolFor,
|
||||||
|
TypeError,
|
||||||
|
TypedArrayPrototypeGetBuffer,
|
||||||
|
TypedArrayPrototypeGetLength,
|
||||||
|
TypedArrayPrototypeGetSymbolToStringTag,
|
||||||
|
Uint8Array,
|
||||||
|
Uint8ClampedArray,
|
||||||
|
MathCeil,
|
||||||
|
PromiseResolve,
|
||||||
|
PromiseReject,
|
||||||
|
RangeError,
|
||||||
|
} = primordials;
|
||||||
|
|
||||||
|
webidl.converters["PredefinedColorSpace"] = webidl.createEnumConverter(
|
||||||
|
"PredefinedColorSpace",
|
||||||
|
[
|
||||||
|
"srgb",
|
||||||
|
"display-p3",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["ImageDataSettings"] = webidl.createDictionaryConverter(
|
||||||
|
"ImageDataSettings",
|
||||||
|
[
|
||||||
|
{ key: "colorSpace", converter: webidl.converters["PredefinedColorSpace"] },
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["ImageOrientation"] = webidl.createEnumConverter(
|
||||||
|
"ImageOrientation",
|
||||||
|
[
|
||||||
|
"from-image",
|
||||||
|
"flipY",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["PremultiplyAlpha"] = webidl.createEnumConverter(
|
||||||
|
"PremultiplyAlpha",
|
||||||
|
[
|
||||||
|
"none",
|
||||||
|
"premultiply",
|
||||||
|
"default",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["ColorSpaceConversion"] = webidl.createEnumConverter(
|
||||||
|
"ColorSpaceConversion",
|
||||||
|
[
|
||||||
|
"none",
|
||||||
|
"default",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["ResizeQuality"] = webidl.createEnumConverter(
|
||||||
|
"ResizeQuality",
|
||||||
|
[
|
||||||
|
"pixelated",
|
||||||
|
"low",
|
||||||
|
"medium",
|
||||||
|
"high",
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
webidl.converters["ImageBitmapOptions"] = webidl.createDictionaryConverter(
|
||||||
|
"ImageBitmapOptions",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
key: "imageOrientation",
|
||||||
|
converter: webidl.converters["ImageOrientation"],
|
||||||
|
defaultValue: "from-image",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "premultiplyAlpha",
|
||||||
|
converter: webidl.converters["PremultiplyAlpha"],
|
||||||
|
defaultValue: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "colorSpaceConversion",
|
||||||
|
converter: webidl.converters["ColorSpaceConversion"],
|
||||||
|
defaultValue: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "resizeWidth",
|
||||||
|
converter: (v, prefix, context, opts) =>
|
||||||
|
webidl.converters["unsigned long"](v, prefix, context, {
|
||||||
|
...opts,
|
||||||
|
enforceRange: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "resizeHeight",
|
||||||
|
converter: (v, prefix, context, opts) =>
|
||||||
|
webidl.converters["unsigned long"](v, prefix, context, {
|
||||||
|
...opts,
|
||||||
|
enforceRange: true,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "resizeQuality",
|
||||||
|
converter: webidl.converters["ResizeQuality"],
|
||||||
|
defaultValue: "low",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const _data = Symbol("[[data]]");
|
||||||
|
const _width = Symbol("[[width]]");
|
||||||
|
const _height = Symbol("[[height]]");
|
||||||
|
class ImageData {
|
||||||
|
/** @type {number} */
|
||||||
|
[_width];
|
||||||
|
/** @type {height} */
|
||||||
|
[_height];
|
||||||
|
/** @type {Uint8Array} */
|
||||||
|
[_data];
|
||||||
|
/** @type {'srgb' | 'display-p3'} */
|
||||||
|
#colorSpace;
|
||||||
|
|
||||||
|
constructor(arg0, arg1, arg2 = undefined, arg3 = undefined) {
|
||||||
|
webidl.requiredArguments(
|
||||||
|
arguments.length,
|
||||||
|
2,
|
||||||
|
'Failed to construct "ImageData"',
|
||||||
|
);
|
||||||
|
this[webidl.brand] = webidl.brand;
|
||||||
|
|
||||||
|
let sourceWidth;
|
||||||
|
let sourceHeight;
|
||||||
|
let data;
|
||||||
|
let settings;
|
||||||
|
const prefix = "Failed to construct 'ImageData'";
|
||||||
|
|
||||||
|
// Overload: new ImageData(data, sw [, sh [, settings ] ])
|
||||||
|
if (
|
||||||
|
arguments.length > 3 ||
|
||||||
|
TypedArrayPrototypeGetSymbolToStringTag(arg0) === "Uint8ClampedArray"
|
||||||
|
) {
|
||||||
|
data = webidl.converters.Uint8ClampedArray(arg0, prefix, "Argument 1");
|
||||||
|
sourceWidth = webidl.converters["unsigned long"](
|
||||||
|
arg1,
|
||||||
|
prefix,
|
||||||
|
"Argument 2",
|
||||||
|
);
|
||||||
|
const dataLength = TypedArrayPrototypeGetLength(data);
|
||||||
|
|
||||||
|
if (webidl.type(arg2) !== "Undefined") {
|
||||||
|
sourceHeight = webidl.converters["unsigned long"](
|
||||||
|
arg2,
|
||||||
|
prefix,
|
||||||
|
"Argument 3",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
settings = webidl.converters["ImageDataSettings"](
|
||||||
|
arg3,
|
||||||
|
prefix,
|
||||||
|
"Argument 4",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (dataLength === 0) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The input data has zero elements.",
|
||||||
|
"InvalidStateError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataLength % 4 !== 0) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The input data length is not a multiple of 4.",
|
||||||
|
"InvalidStateError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceWidth < 1) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The source width is zero or not a number.",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webidl.type(sourceHeight) !== "Undefined" && sourceHeight < 1) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The source height is zero or not a number.",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataLength / 4 % sourceWidth !== 0) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The input data length is not a multiple of (4 * width).",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
webidl.type(sourceHeight) !== "Undefined" &&
|
||||||
|
(sourceWidth * sourceHeight * 4 !== dataLength)
|
||||||
|
) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The input data length is not equal to (4 * width * height).",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webidl.type(sourceHeight) === "Undefined") {
|
||||||
|
this[_height] = dataLength / 4 / sourceWidth;
|
||||||
|
} else {
|
||||||
|
this[_height] = sourceHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#colorSpace = settings.colorSpace ?? "srgb";
|
||||||
|
this[_width] = sourceWidth;
|
||||||
|
this[_data] = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overload: new ImageData(sw, sh [, settings])
|
||||||
|
sourceWidth = webidl.converters["unsigned long"](
|
||||||
|
arg0,
|
||||||
|
prefix,
|
||||||
|
"Argument 1",
|
||||||
|
);
|
||||||
|
sourceHeight = webidl.converters["unsigned long"](
|
||||||
|
arg1,
|
||||||
|
prefix,
|
||||||
|
"Argument 2",
|
||||||
|
);
|
||||||
|
|
||||||
|
settings = webidl.converters["ImageDataSettings"](
|
||||||
|
arg2,
|
||||||
|
prefix,
|
||||||
|
"Argument 3",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sourceWidth < 1) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The source width is zero or not a number.",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceHeight < 1) {
|
||||||
|
throw new DOMException(
|
||||||
|
"Failed to construct 'ImageData': The source height is zero or not a number.",
|
||||||
|
"IndexSizeError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#colorSpace = settings.colorSpace ?? "srgb";
|
||||||
|
this[_width] = sourceWidth;
|
||||||
|
this[_height] = sourceHeight;
|
||||||
|
this[_data] = new Uint8ClampedArray(sourceWidth * sourceHeight * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
webidl.assertBranded(this, ImageDataPrototype);
|
||||||
|
return this[_width];
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
webidl.assertBranded(this, ImageDataPrototype);
|
||||||
|
return this[_height];
|
||||||
|
}
|
||||||
|
|
||||||
|
get data() {
|
||||||
|
webidl.assertBranded(this, ImageDataPrototype);
|
||||||
|
return this[_data];
|
||||||
|
}
|
||||||
|
|
||||||
|
get colorSpace() {
|
||||||
|
webidl.assertBranded(this, ImageDataPrototype);
|
||||||
|
return this.#colorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
|
||||||
|
return inspect(
|
||||||
|
createFilteredInspectProxy({
|
||||||
|
object: this,
|
||||||
|
evaluate: ObjectPrototypeIsPrototypeOf(ImageDataPrototype, this),
|
||||||
|
keys: [
|
||||||
|
"data",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"colorSpace",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
inspectOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageDataPrototype = ImageData.prototype;
|
||||||
|
|
||||||
|
const _bitmapData = Symbol("[[bitmapData]]");
|
||||||
|
const _detached = Symbol("[[detached]]");
|
||||||
|
class ImageBitmap {
|
||||||
|
[_width];
|
||||||
|
[_height];
|
||||||
|
[_bitmapData];
|
||||||
|
[_detached];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
webidl.illegalConstructor();
|
||||||
|
}
|
||||||
|
|
||||||
|
get width() {
|
||||||
|
webidl.assertBranded(this, ImageBitmapPrototype);
|
||||||
|
if (this[_detached]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this[_width];
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
webidl.assertBranded(this, ImageBitmapPrototype);
|
||||||
|
if (this[_detached]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this[_height];
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
webidl.assertBranded(this, ImageBitmapPrototype);
|
||||||
|
this[_detached] = true;
|
||||||
|
this[_bitmapData] = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
|
||||||
|
return inspect(
|
||||||
|
createFilteredInspectProxy({
|
||||||
|
object: this,
|
||||||
|
evaluate: ObjectPrototypeIsPrototypeOf(ImageBitmapPrototype, this),
|
||||||
|
keys: [
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
inspectOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ImageBitmapPrototype = ImageBitmap.prototype;
|
||||||
|
|
||||||
|
function createImageBitmap(
|
||||||
|
image,
|
||||||
|
sxOrOptions = undefined,
|
||||||
|
sy = undefined,
|
||||||
|
sw = undefined,
|
||||||
|
sh = undefined,
|
||||||
|
options = undefined,
|
||||||
|
) {
|
||||||
|
const prefix = "Failed to call 'createImageBitmap'";
|
||||||
|
|
||||||
|
// Overload: createImageBitmap(image [, options ])
|
||||||
|
if (arguments.length < 3) {
|
||||||
|
options = webidl.converters["ImageBitmapOptions"](
|
||||||
|
sxOrOptions,
|
||||||
|
prefix,
|
||||||
|
"Argument 2",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Overload: createImageBitmap(image, sx, sy, sw, sh [, options ])
|
||||||
|
sxOrOptions = webidl.converters["long"](sxOrOptions, prefix, "Argument 2");
|
||||||
|
sy = webidl.converters["long"](sy, prefix, "Argument 3");
|
||||||
|
sw = webidl.converters["long"](sw, prefix, "Argument 4");
|
||||||
|
sh = webidl.converters["long"](sh, prefix, "Argument 5");
|
||||||
|
options = webidl.converters["ImageBitmapOptions"](
|
||||||
|
options,
|
||||||
|
prefix,
|
||||||
|
"Argument 6",
|
||||||
|
);
|
||||||
|
|
||||||
|
if (sw === 0) {
|
||||||
|
return PromiseReject(new RangeError("sw has to be greater than 0"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sh === 0) {
|
||||||
|
return PromiseReject(new RangeError("sh has to be greater than 0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.resizeWidth === 0) {
|
||||||
|
return PromiseReject(
|
||||||
|
new DOMException(
|
||||||
|
"options.resizeWidth has to be greater than 0",
|
||||||
|
"InvalidStateError",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (options.resizeHeight === 0) {
|
||||||
|
return PromiseReject(
|
||||||
|
new DOMException(
|
||||||
|
"options.resizeWidth has to be greater than 0",
|
||||||
|
"InvalidStateError",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageBitmap = webidl.createBranded(ImageBitmap);
|
||||||
|
|
||||||
|
if (ObjectPrototypeIsPrototypeOf(ImageDataPrototype, image)) {
|
||||||
|
const processedImage = processImage(
|
||||||
|
image[_data],
|
||||||
|
image[_width],
|
||||||
|
image[_height],
|
||||||
|
sxOrOptions,
|
||||||
|
sy,
|
||||||
|
sw,
|
||||||
|
sh,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
imageBitmap[_bitmapData] = processedImage.data;
|
||||||
|
imageBitmap[_width] = processedImage.outputWidth;
|
||||||
|
imageBitmap[_height] = processedImage.outputHeight;
|
||||||
|
return PromiseResolve(imageBitmap);
|
||||||
|
}
|
||||||
|
if (ObjectPrototypeIsPrototypeOf(BlobPrototype, image)) {
|
||||||
|
return (async () => {
|
||||||
|
const data = await image.arrayBuffer();
|
||||||
|
const mimetype = sniffImage(image.type);
|
||||||
|
if (mimetype !== "image/png") {
|
||||||
|
throw new DOMException(
|
||||||
|
`Unsupported type '${image.type}'`,
|
||||||
|
"InvalidStateError",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { data: imageData, width, height } = ops.op_image_decode_png(data);
|
||||||
|
const processedImage = processImage(
|
||||||
|
imageData,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
sxOrOptions,
|
||||||
|
sy,
|
||||||
|
sw,
|
||||||
|
sh,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
imageBitmap[_bitmapData] = processedImage.data;
|
||||||
|
imageBitmap[_width] = processedImage.outputWidth;
|
||||||
|
imageBitmap[_height] = processedImage.outputHeight;
|
||||||
|
return imageBitmap;
|
||||||
|
})();
|
||||||
|
} else {
|
||||||
|
return PromiseReject(new TypeError("Invalid or unsupported image value"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processImage(input, width, height, sx, sy, sw, sh, options) {
|
||||||
|
let sourceRectangle;
|
||||||
|
|
||||||
|
if (
|
||||||
|
sx !== undefined && sy !== undefined && sw !== undefined && sh !== undefined
|
||||||
|
) {
|
||||||
|
sourceRectangle = [
|
||||||
|
[sx, sy],
|
||||||
|
[sx + sw, sy],
|
||||||
|
[sx + sw, sy + sh],
|
||||||
|
[sx, sy + sh],
|
||||||
|
];
|
||||||
|
} else {
|
||||||
|
sourceRectangle = [
|
||||||
|
[0, 0],
|
||||||
|
[width, 0],
|
||||||
|
[width, height],
|
||||||
|
[0, height],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
const widthOfSourceRect = sourceRectangle[1][0] - sourceRectangle[0][0];
|
||||||
|
const heightOfSourceRect = sourceRectangle[3][1] - sourceRectangle[0][1];
|
||||||
|
|
||||||
|
let outputWidth;
|
||||||
|
if (options.resizeWidth !== undefined) {
|
||||||
|
outputWidth = options.resizeWidth;
|
||||||
|
} else if (options.resizeHeight !== undefined) {
|
||||||
|
outputWidth = MathCeil(
|
||||||
|
(widthOfSourceRect * options.resizeHeight) / heightOfSourceRect,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
outputWidth = widthOfSourceRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
let outputHeight;
|
||||||
|
if (options.resizeHeight !== undefined) {
|
||||||
|
outputHeight = options.resizeHeight;
|
||||||
|
} else if (options.resizeWidth !== undefined) {
|
||||||
|
outputHeight = MathCeil(
|
||||||
|
(heightOfSourceRect * options.resizeWidth) / widthOfSourceRect,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
outputHeight = heightOfSourceRect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.colorSpaceConversion === "none") {
|
||||||
|
throw new TypeError("options.colorSpaceConversion 'none' is not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The cropping works differently than the spec specifies:
|
||||||
|
* The spec states to create an infinite surface and place the top-left corner
|
||||||
|
* of the image a 0,0 and crop based on sourceRectangle.
|
||||||
|
*
|
||||||
|
* We instead create a surface the size of sourceRectangle, and position
|
||||||
|
* the image at the correct location, which is the inverse of the x & y of
|
||||||
|
* sourceRectangle's top-left corner.
|
||||||
|
*/
|
||||||
|
const data = ops.op_image_process(
|
||||||
|
new Uint8Array(TypedArrayPrototypeGetBuffer(input)),
|
||||||
|
{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
surfaceWidth: widthOfSourceRect,
|
||||||
|
surfaceHeight: heightOfSourceRect,
|
||||||
|
inputX: sourceRectangle[0][0] * -1, // input_x
|
||||||
|
inputY: sourceRectangle[0][1] * -1, // input_y
|
||||||
|
outputWidth,
|
||||||
|
outputHeight,
|
||||||
|
resizeQuality: options.resizeQuality,
|
||||||
|
flipY: options.imageOrientation === "flipY",
|
||||||
|
premultiply: options.premultiplyAlpha === "default"
|
||||||
|
? null
|
||||||
|
: (options.premultiplyAlpha === "premultiply"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
outputWidth,
|
||||||
|
outputHeight,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getBitmapData(imageBitmap) {
|
||||||
|
return imageBitmap[_bitmapData];
|
||||||
|
}
|
||||||
|
|
||||||
|
internals.getBitmapData = getBitmapData;
|
||||||
|
|
||||||
|
export { _bitmapData, _detached, createImageBitmap, ImageBitmap, ImageData };
|
21
ext/canvas/Cargo.toml
Normal file
21
ext/canvas/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "deno_canvas"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
readme = "README.md"
|
||||||
|
repository.workspace = true
|
||||||
|
description = "OffscreenCanvas implementation for Deno"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
deno_core.workspace = true
|
||||||
|
deno_webgpu.workspace = true
|
||||||
|
image = { version = "0.24.7", default-features = false, features = ["png"] }
|
||||||
|
serde = { workspace = true, features = ["derive"] }
|
||||||
|
tokio = { workspace = true, features = ["full"] }
|
3
ext/canvas/README.md
Normal file
3
ext/canvas/README.md
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
# deno_canvas
|
||||||
|
|
||||||
|
Extension that implements various OffscreenCanvas related APIs.
|
87
ext/canvas/lib.deno_canvas.d.ts
vendored
Normal file
87
ext/canvas/lib.deno_canvas.d.ts
vendored
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
// deno-lint-ignore-file no-var
|
||||||
|
|
||||||
|
/// <reference no-default-lib="true" />
|
||||||
|
/// <reference lib="esnext" />
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type PredefinedColorSpace = "srgb" | "display-p3";
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare interface ImageDataSettings {
|
||||||
|
readonly colorSpace?: PredefinedColorSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare interface ImageData {
|
||||||
|
readonly colorSpace: PredefinedColorSpace;
|
||||||
|
readonly data: Uint8ClampedArray;
|
||||||
|
readonly height: number;
|
||||||
|
readonly width: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare var ImageData: {
|
||||||
|
prototype: ImageData;
|
||||||
|
new (sw: number, sh: number, settings?: ImageDataSettings): ImageData;
|
||||||
|
new (
|
||||||
|
data: Uint8ClampedArray,
|
||||||
|
sw: number,
|
||||||
|
sh?: number,
|
||||||
|
settings?: ImageDataSettings,
|
||||||
|
): ImageData;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type ColorSpaceConversion = "default" | "none";
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type ImageOrientation = "flipY" | "from-image" | "none";
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type PremultiplyAlpha = "default" | "none" | "premultiply";
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type ResizeQuality = "high" | "low" | "medium" | "pixelated";
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare type ImageBitmapSource = Blob | ImageData;
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
interface ImageBitmapOptions {
|
||||||
|
colorSpaceConversion?: ColorSpaceConversion;
|
||||||
|
imageOrientation?: ImageOrientation;
|
||||||
|
premultiplyAlpha?: PremultiplyAlpha;
|
||||||
|
resizeHeight?: number;
|
||||||
|
resizeQuality?: ResizeQuality;
|
||||||
|
resizeWidth?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare function createImageBitmap(
|
||||||
|
image: ImageBitmapSource,
|
||||||
|
options?: ImageBitmapOptions,
|
||||||
|
): Promise<ImageBitmap>;
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare function createImageBitmap(
|
||||||
|
image: ImageBitmapSource,
|
||||||
|
sx: number,
|
||||||
|
sy: number,
|
||||||
|
sw: number,
|
||||||
|
sh: number,
|
||||||
|
options?: ImageBitmapOptions,
|
||||||
|
): Promise<ImageBitmap>;
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
interface ImageBitmap {
|
||||||
|
readonly height: number;
|
||||||
|
readonly width: number;
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @category Web APIs */
|
||||||
|
declare var ImageBitmap: {
|
||||||
|
prototype: ImageBitmap;
|
||||||
|
new (): ImageBitmap;
|
||||||
|
};
|
153
ext/canvas/lib.rs
Normal file
153
ext/canvas/lib.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
|
use deno_core::error::type_error;
|
||||||
|
use deno_core::error::AnyError;
|
||||||
|
use deno_core::op2;
|
||||||
|
use deno_core::ToJsBuffer;
|
||||||
|
use image::imageops::FilterType;
|
||||||
|
use image::ColorType;
|
||||||
|
use image::ImageDecoder;
|
||||||
|
use image::Pixel;
|
||||||
|
use image::RgbaImage;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
enum ImageResizeQuality {
|
||||||
|
Pixelated,
|
||||||
|
Low,
|
||||||
|
Medium,
|
||||||
|
High,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct ImageProcessArgs {
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
surface_width: u32,
|
||||||
|
surface_height: u32,
|
||||||
|
input_x: i64,
|
||||||
|
input_y: i64,
|
||||||
|
output_width: u32,
|
||||||
|
output_height: u32,
|
||||||
|
resize_quality: ImageResizeQuality,
|
||||||
|
flip_y: bool,
|
||||||
|
premultiply: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op2]
|
||||||
|
#[serde]
|
||||||
|
fn op_image_process(
|
||||||
|
#[buffer] buf: &[u8],
|
||||||
|
#[serde] args: ImageProcessArgs,
|
||||||
|
) -> Result<ToJsBuffer, AnyError> {
|
||||||
|
let view =
|
||||||
|
RgbaImage::from_vec(args.width, args.height, buf.to_vec()).unwrap();
|
||||||
|
|
||||||
|
let surface = if !(args.width == args.surface_width
|
||||||
|
&& args.height == args.surface_height
|
||||||
|
&& args.input_x == 0
|
||||||
|
&& args.input_y == 0)
|
||||||
|
{
|
||||||
|
let mut surface = RgbaImage::new(args.surface_width, args.surface_height);
|
||||||
|
|
||||||
|
image::imageops::overlay(&mut surface, &view, args.input_x, args.input_y);
|
||||||
|
|
||||||
|
surface
|
||||||
|
} else {
|
||||||
|
view
|
||||||
|
};
|
||||||
|
|
||||||
|
let filter_type = match args.resize_quality {
|
||||||
|
ImageResizeQuality::Pixelated => FilterType::Nearest,
|
||||||
|
ImageResizeQuality::Low => FilterType::Triangle,
|
||||||
|
ImageResizeQuality::Medium => FilterType::CatmullRom,
|
||||||
|
ImageResizeQuality::High => FilterType::Lanczos3,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut image_out = image::imageops::resize(
|
||||||
|
&surface,
|
||||||
|
args.output_width,
|
||||||
|
args.output_height,
|
||||||
|
filter_type,
|
||||||
|
);
|
||||||
|
|
||||||
|
if args.flip_y {
|
||||||
|
image::imageops::flip_vertical_in_place(&mut image_out);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ignore 9.
|
||||||
|
|
||||||
|
if let Some(premultiply) = args.premultiply {
|
||||||
|
let is_not_premultiplied = image_out.pixels().any(|pixel| {
|
||||||
|
(pixel.0[0].max(pixel.0[1]).max(pixel.0[2])) > (255 * pixel.0[3])
|
||||||
|
});
|
||||||
|
|
||||||
|
if premultiply {
|
||||||
|
if is_not_premultiplied {
|
||||||
|
for pixel in image_out.pixels_mut() {
|
||||||
|
let alpha = pixel.0[3];
|
||||||
|
pixel.apply_without_alpha(|channel| {
|
||||||
|
(channel as f32 * (alpha as f32 / 255.0)) as u8
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if !is_not_premultiplied {
|
||||||
|
for pixel in image_out.pixels_mut() {
|
||||||
|
let alpha = pixel.0[3];
|
||||||
|
pixel.apply_without_alpha(|channel| {
|
||||||
|
(channel as f32 / (alpha as f32 / 255.0)) as u8
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(image_out.to_vec().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct DecodedPng {
|
||||||
|
data: ToJsBuffer,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[op2]
|
||||||
|
#[serde]
|
||||||
|
fn op_image_decode_png(#[buffer] buf: &[u8]) -> Result<DecodedPng, AnyError> {
|
||||||
|
let png = image::codecs::png::PngDecoder::new(buf)?;
|
||||||
|
|
||||||
|
let (width, height) = png.dimensions();
|
||||||
|
|
||||||
|
// TODO(@crowlKats): maybe use DynamicImage https://docs.rs/image/0.24.7/image/enum.DynamicImage.html ?
|
||||||
|
if png.color_type() != ColorType::Rgba8 {
|
||||||
|
return Err(type_error(format!(
|
||||||
|
"Color type '{:?}' not supported",
|
||||||
|
png.color_type()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut png_data = Vec::with_capacity(png.total_bytes() as usize);
|
||||||
|
|
||||||
|
png.read_image(&mut png_data)?;
|
||||||
|
|
||||||
|
Ok(DecodedPng {
|
||||||
|
data: png_data.into(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
deno_core::extension!(
|
||||||
|
deno_canvas,
|
||||||
|
deps = [deno_webidl, deno_web, deno_webgpu],
|
||||||
|
ops = [op_image_process, op_image_decode_png],
|
||||||
|
lazy_loaded_esm = ["01_image.js"],
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn get_declaration() -> PathBuf {
|
||||||
|
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("lib.deno_canvas.d.ts")
|
||||||
|
}
|
|
@ -18,9 +18,14 @@ const {
|
||||||
SafeMapIterator,
|
SafeMapIterator,
|
||||||
StringPrototypeReplaceAll,
|
StringPrototypeReplaceAll,
|
||||||
StringPrototypeToLowerCase,
|
StringPrototypeToLowerCase,
|
||||||
|
StringPrototypeEndsWith,
|
||||||
|
Uint8Array,
|
||||||
|
TypedArrayPrototypeGetLength,
|
||||||
|
TypedArrayPrototypeIncludes,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
assert,
|
||||||
collectHttpQuotedString,
|
collectHttpQuotedString,
|
||||||
collectSequenceOfCodepoints,
|
collectSequenceOfCodepoints,
|
||||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||||
|
@ -251,4 +256,194 @@ function extractMimeType(headerValues) {
|
||||||
return mimeType;
|
return mimeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { essence, extractMimeType, parseMimeType, serializeMimeType };
|
/**
|
||||||
|
* Ref: https://mimesniff.spec.whatwg.org/#xml-mime-type
|
||||||
|
* @param {MimeType} mimeType
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isXML(mimeType) {
|
||||||
|
return StringPrototypeEndsWith(mimeType.subtype, "+xml") ||
|
||||||
|
essence(mimeType) === "text/xml" || essence(mimeType) === "application/xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://mimesniff.spec.whatwg.org/#pattern-matching-algorithm
|
||||||
|
* @param {Uint8Array} input
|
||||||
|
* @param {Uint8Array} pattern
|
||||||
|
* @param {Uint8Array} mask
|
||||||
|
* @param {Uint8Array} ignored
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function patternMatchingAlgorithm(input, pattern, mask, ignored) {
|
||||||
|
assert(
|
||||||
|
TypedArrayPrototypeGetLength(pattern) ===
|
||||||
|
TypedArrayPrototypeGetLength(mask),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
TypedArrayPrototypeGetLength(input) < TypedArrayPrototypeGetLength(pattern)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let s = 0;
|
||||||
|
for (; s < TypedArrayPrototypeGetLength(input); s++) {
|
||||||
|
if (!TypedArrayPrototypeIncludes(ignored, input[s])) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let p = 0;
|
||||||
|
for (; p < TypedArrayPrototypeGetLength(pattern); p++, s++) {
|
||||||
|
const maskedData = input[s] & mask[p];
|
||||||
|
if (maskedData !== pattern[p]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ImageTypePatternTable = [
|
||||||
|
// A Windows Icon signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x00, 0x00, 0x01, 0x00]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/x-icon",
|
||||||
|
],
|
||||||
|
// A Windows Cursor signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x00, 0x00, 0x02, 0x00]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/x-icon",
|
||||||
|
],
|
||||||
|
// The string "BM", a BMP signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x42, 0x4D]),
|
||||||
|
new Uint8Array([0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/bmp",
|
||||||
|
],
|
||||||
|
// The string "GIF87a", a GIF signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x37, 0x61]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/gif",
|
||||||
|
],
|
||||||
|
// The string "GIF89a", a GIF signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/gif",
|
||||||
|
],
|
||||||
|
// The string "RIFF" followed by four bytes followed by the string "WEBPVP".
|
||||||
|
[
|
||||||
|
new Uint8Array([
|
||||||
|
0x52,
|
||||||
|
0x49,
|
||||||
|
0x46,
|
||||||
|
0x46,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x57,
|
||||||
|
0x45,
|
||||||
|
0x42,
|
||||||
|
0x50,
|
||||||
|
0x56,
|
||||||
|
0x50,
|
||||||
|
]),
|
||||||
|
new Uint8Array([
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
0xFF,
|
||||||
|
]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/webp",
|
||||||
|
],
|
||||||
|
// An error-checking byte followed by the string "PNG" followed by CR LF SUB LF, the PNG signature.
|
||||||
|
[
|
||||||
|
new Uint8Array([0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/png",
|
||||||
|
],
|
||||||
|
// The JPEG Start of Image marker followed by the indicator byte of another marker.
|
||||||
|
[
|
||||||
|
new Uint8Array([0xFF, 0xD8, 0xFF]),
|
||||||
|
new Uint8Array([0xFF, 0xFF, 0xFF]),
|
||||||
|
new Uint8Array(),
|
||||||
|
"image/jpeg",
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://mimesniff.spec.whatwg.org/#image-type-pattern-matching-algorithm
|
||||||
|
* @param {Uint8Array} input
|
||||||
|
* @returns {string | undefined}
|
||||||
|
*/
|
||||||
|
function imageTypePatternMatchingAlgorithm(input) {
|
||||||
|
for (let i = 0; i < ImageTypePatternTable.length; i++) {
|
||||||
|
const row = ImageTypePatternTable[i];
|
||||||
|
const patternMatched = patternMatchingAlgorithm(
|
||||||
|
input,
|
||||||
|
row[0],
|
||||||
|
row[1],
|
||||||
|
row[2],
|
||||||
|
);
|
||||||
|
if (patternMatched) {
|
||||||
|
return row[3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://mimesniff.spec.whatwg.org/#rules-for-sniffing-images-specifically
|
||||||
|
* @param {string} mimeTypeString
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function sniffImage(mimeTypeString) {
|
||||||
|
const mimeType = parseMimeType(mimeTypeString);
|
||||||
|
if (mimeType === null) {
|
||||||
|
return mimeTypeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isXML(mimeType)) {
|
||||||
|
return mimeTypeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageTypeMatched = imageTypePatternMatchingAlgorithm(
|
||||||
|
new TextEncoder().encode(mimeTypeString),
|
||||||
|
);
|
||||||
|
if (imageTypeMatched !== undefined) {
|
||||||
|
return imageTypeMatched;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mimeTypeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
essence,
|
||||||
|
extractMimeType,
|
||||||
|
parseMimeType,
|
||||||
|
serializeMimeType,
|
||||||
|
sniffImage,
|
||||||
|
};
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
|
||||||
|
|
||||||
import { primordials } from "ext:core/mod.js";
|
|
||||||
const {
|
|
||||||
ObjectPrototypeIsPrototypeOf,
|
|
||||||
SymbolFor,
|
|
||||||
TypedArrayPrototypeGetLength,
|
|
||||||
TypedArrayPrototypeGetSymbolToStringTag,
|
|
||||||
Uint8ClampedArray,
|
|
||||||
} = primordials;
|
|
||||||
|
|
||||||
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
|
||||||
import { DOMException } from "ext:deno_web/01_dom_exception.js";
|
|
||||||
import { createFilteredInspectProxy } from "ext:deno_console/01_console.js";
|
|
||||||
|
|
||||||
webidl.converters["PredefinedColorSpace"] = webidl.createEnumConverter(
|
|
||||||
"PredefinedColorSpace",
|
|
||||||
[
|
|
||||||
"srgb",
|
|
||||||
"display-p3",
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
webidl.converters["ImageDataSettings"] = webidl.createDictionaryConverter(
|
|
||||||
"ImageDataSettings",
|
|
||||||
[
|
|
||||||
{ key: "colorSpace", converter: webidl.converters["PredefinedColorSpace"] },
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
class ImageData {
|
|
||||||
/** @type {number} */
|
|
||||||
#width;
|
|
||||||
/** @type {height} */
|
|
||||||
#height;
|
|
||||||
/** @type {Uint8Array} */
|
|
||||||
#data;
|
|
||||||
/** @type {'srgb' | 'display-p3'} */
|
|
||||||
#colorSpace;
|
|
||||||
|
|
||||||
constructor(arg0, arg1, arg2 = undefined, arg3 = undefined) {
|
|
||||||
webidl.requiredArguments(
|
|
||||||
arguments.length,
|
|
||||||
2,
|
|
||||||
'Failed to construct "ImageData"',
|
|
||||||
);
|
|
||||||
this[webidl.brand] = webidl.brand;
|
|
||||||
|
|
||||||
let sourceWidth;
|
|
||||||
let sourceHeight;
|
|
||||||
let data;
|
|
||||||
let settings;
|
|
||||||
const prefix = "Failed to construct 'ImageData'";
|
|
||||||
|
|
||||||
// Overload: new ImageData(data, sw [, sh [, settings ] ])
|
|
||||||
if (
|
|
||||||
arguments.length > 3 ||
|
|
||||||
TypedArrayPrototypeGetSymbolToStringTag(arg0) === "Uint8ClampedArray"
|
|
||||||
) {
|
|
||||||
data = webidl.converters.Uint8ClampedArray(arg0, prefix, "Argument 1");
|
|
||||||
sourceWidth = webidl.converters["unsigned long"](
|
|
||||||
arg1,
|
|
||||||
prefix,
|
|
||||||
"Argument 2",
|
|
||||||
);
|
|
||||||
const dataLength = TypedArrayPrototypeGetLength(data);
|
|
||||||
|
|
||||||
if (webidl.type(arg2) !== "Undefined") {
|
|
||||||
sourceHeight = webidl.converters["unsigned long"](
|
|
||||||
arg2,
|
|
||||||
prefix,
|
|
||||||
"Argument 3",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
settings = webidl.converters["ImageDataSettings"](
|
|
||||||
arg3,
|
|
||||||
prefix,
|
|
||||||
"Argument 4",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (dataLength === 0) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The input data has zero elements.",
|
|
||||||
"InvalidStateError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataLength % 4 !== 0) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The input data length is not a multiple of 4.",
|
|
||||||
"InvalidStateError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceWidth < 1) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The source width is zero or not a number.",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (webidl.type(sourceHeight) !== "Undefined" && sourceHeight < 1) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The source height is zero or not a number.",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dataLength / 4 % sourceWidth !== 0) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The input data length is not a multiple of (4 * width).",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
webidl.type(sourceHeight) !== "Undefined" &&
|
|
||||||
(sourceWidth * sourceHeight * 4 !== dataLength)
|
|
||||||
) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The input data length is not equal to (4 * width * height).",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (webidl.type(sourceHeight) === "Undefined") {
|
|
||||||
this.#height = dataLength / 4 / sourceWidth;
|
|
||||||
} else {
|
|
||||||
this.#height = sourceHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#colorSpace = settings.colorSpace ?? "srgb";
|
|
||||||
this.#width = sourceWidth;
|
|
||||||
this.#data = data;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overload: new ImageData(sw, sh [, settings])
|
|
||||||
sourceWidth = webidl.converters["unsigned long"](
|
|
||||||
arg0,
|
|
||||||
prefix,
|
|
||||||
"Argument 1",
|
|
||||||
);
|
|
||||||
sourceHeight = webidl.converters["unsigned long"](
|
|
||||||
arg1,
|
|
||||||
prefix,
|
|
||||||
"Argument 2",
|
|
||||||
);
|
|
||||||
|
|
||||||
settings = webidl.converters["ImageDataSettings"](
|
|
||||||
arg2,
|
|
||||||
prefix,
|
|
||||||
"Argument 3",
|
|
||||||
);
|
|
||||||
|
|
||||||
if (sourceWidth < 1) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The source width is zero or not a number.",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sourceHeight < 1) {
|
|
||||||
throw new DOMException(
|
|
||||||
"Failed to construct 'ImageData': The source height is zero or not a number.",
|
|
||||||
"IndexSizeError",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#colorSpace = settings.colorSpace ?? "srgb";
|
|
||||||
this.#width = sourceWidth;
|
|
||||||
this.#height = sourceHeight;
|
|
||||||
this.#data = new Uint8ClampedArray(sourceWidth * sourceHeight * 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
get width() {
|
|
||||||
webidl.assertBranded(this, ImageDataPrototype);
|
|
||||||
return this.#width;
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
webidl.assertBranded(this, ImageDataPrototype);
|
|
||||||
return this.#height;
|
|
||||||
}
|
|
||||||
|
|
||||||
get data() {
|
|
||||||
webidl.assertBranded(this, ImageDataPrototype);
|
|
||||||
return this.#data;
|
|
||||||
}
|
|
||||||
|
|
||||||
get colorSpace() {
|
|
||||||
webidl.assertBranded(this, ImageDataPrototype);
|
|
||||||
return this.#colorSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
[SymbolFor("Deno.privateCustomInspect")](inspect, inspectOptions) {
|
|
||||||
return inspect(
|
|
||||||
createFilteredInspectProxy({
|
|
||||||
object: this,
|
|
||||||
evaluate: ObjectPrototypeIsPrototypeOf(ImageDataPrototype, this),
|
|
||||||
keys: [
|
|
||||||
"data",
|
|
||||||
"width",
|
|
||||||
"height",
|
|
||||||
"colorSpace",
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
inspectOptions,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const ImageDataPrototype = ImageData.prototype;
|
|
||||||
|
|
||||||
export { ImageData };
|
|
4
ext/web/internal.d.ts
vendored
4
ext/web/internal.d.ts
vendored
|
@ -111,7 +111,3 @@ declare module "ext:deno_web/13_message_port.js" {
|
||||||
transferables: Transferable[];
|
transferables: Transferable[];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "ext:deno_web/16_image_data.js" {
|
|
||||||
const ImageData: typeof ImageData;
|
|
||||||
}
|
|
||||||
|
|
28
ext/web/lib.deno_web.d.ts
vendored
28
ext/web/lib.deno_web.d.ts
vendored
|
@ -1237,31 +1237,3 @@ declare var DecompressionStream: {
|
||||||
declare function reportError(
|
declare function reportError(
|
||||||
error: any,
|
error: any,
|
||||||
): void;
|
): void;
|
||||||
|
|
||||||
/** @category Web APIs */
|
|
||||||
type PredefinedColorSpace = "srgb" | "display-p3";
|
|
||||||
|
|
||||||
/** @category Web APIs */
|
|
||||||
interface ImageDataSettings {
|
|
||||||
readonly colorSpace?: PredefinedColorSpace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @category Web APIs */
|
|
||||||
interface ImageData {
|
|
||||||
readonly colorSpace: PredefinedColorSpace;
|
|
||||||
readonly data: Uint8ClampedArray;
|
|
||||||
readonly height: number;
|
|
||||||
readonly width: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @category Web APIs */
|
|
||||||
declare var ImageData: {
|
|
||||||
prototype: ImageData;
|
|
||||||
new (sw: number, sh: number, settings?: ImageDataSettings): ImageData;
|
|
||||||
new (
|
|
||||||
data: Uint8ClampedArray,
|
|
||||||
sw: number,
|
|
||||||
sh?: number,
|
|
||||||
settings?: ImageDataSettings,
|
|
||||||
): ImageData;
|
|
||||||
};
|
|
||||||
|
|
|
@ -117,7 +117,6 @@ deno_core::extension!(deno_web,
|
||||||
"13_message_port.js",
|
"13_message_port.js",
|
||||||
"14_compression.js",
|
"14_compression.js",
|
||||||
"15_performance.js",
|
"15_performance.js",
|
||||||
"16_image_data.js",
|
|
||||||
],
|
],
|
||||||
options = {
|
options = {
|
||||||
blob_store: Arc<BlobStore>,
|
blob_store: Arc<BlobStore>,
|
||||||
|
|
|
@ -42,6 +42,7 @@ path = "examples/extension_with_ops/main.rs"
|
||||||
deno_ast.workspace = true
|
deno_ast.workspace = true
|
||||||
deno_broadcast_channel.workspace = true
|
deno_broadcast_channel.workspace = true
|
||||||
deno_cache.workspace = true
|
deno_cache.workspace = true
|
||||||
|
deno_canvas.workspace = true
|
||||||
deno_console.workspace = true
|
deno_console.workspace = true
|
||||||
deno_core.workspace = true
|
deno_core.workspace = true
|
||||||
deno_cron.workspace = true
|
deno_cron.workspace = true
|
||||||
|
@ -73,6 +74,7 @@ winapi.workspace = true
|
||||||
deno_ast.workspace = true
|
deno_ast.workspace = true
|
||||||
deno_broadcast_channel.workspace = true
|
deno_broadcast_channel.workspace = true
|
||||||
deno_cache.workspace = true
|
deno_cache.workspace = true
|
||||||
|
deno_canvas.workspace = true
|
||||||
deno_console.workspace = true
|
deno_console.workspace = true
|
||||||
deno_core.workspace = true
|
deno_core.workspace = true
|
||||||
deno_cron.workspace = true
|
deno_cron.workspace = true
|
||||||
|
|
|
@ -31,11 +31,67 @@ import * as messagePort from "ext:deno_web/13_message_port.js";
|
||||||
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
import * as webidl from "ext:deno_webidl/00_webidl.js";
|
||||||
import { DOMException } from "ext:deno_web/01_dom_exception.js";
|
import { DOMException } from "ext:deno_web/01_dom_exception.js";
|
||||||
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
|
import * as abortSignal from "ext:deno_web/03_abort_signal.js";
|
||||||
import * as imageData from "ext:deno_web/16_image_data.js";
|
|
||||||
import { webgpu, webGPUNonEnumerable } from "ext:deno_webgpu/00_init.js";
|
import { webgpu, webGPUNonEnumerable } from "ext:deno_webgpu/00_init.js";
|
||||||
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
|
import * as webgpuSurface from "ext:deno_webgpu/02_surface.js";
|
||||||
import { unstableIds } from "ext:runtime/90_deno_ns.js";
|
import { unstableIds } from "ext:runtime/90_deno_ns.js";
|
||||||
|
|
||||||
|
const { op_lazy_load_esm } = core.ensureFastOps(true);
|
||||||
|
let image;
|
||||||
|
|
||||||
|
function ImageNonEnumerable(getter) {
|
||||||
|
let valueIsSet = false;
|
||||||
|
let value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
if (valueIsSet) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return getter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
valueIsSet = true;
|
||||||
|
value = v;
|
||||||
|
},
|
||||||
|
enumerable: false,
|
||||||
|
configurable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function ImageWritable(getter) {
|
||||||
|
let valueIsSet = false;
|
||||||
|
let value;
|
||||||
|
|
||||||
|
return {
|
||||||
|
get() {
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
if (valueIsSet) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return getter();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
loadImage();
|
||||||
|
|
||||||
|
valueIsSet = true;
|
||||||
|
value = v;
|
||||||
|
},
|
||||||
|
enumerable: true,
|
||||||
|
configurable: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function loadImage() {
|
||||||
|
if (!image) {
|
||||||
|
image = op_lazy_load_esm("ext:deno_canvas/01_image.js");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
|
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope
|
||||||
const windowOrWorkerGlobalScope = {
|
const windowOrWorkerGlobalScope = {
|
||||||
AbortController: util.nonEnumerable(abortSignal.AbortController),
|
AbortController: util.nonEnumerable(abortSignal.AbortController),
|
||||||
|
@ -60,7 +116,8 @@ const windowOrWorkerGlobalScope = {
|
||||||
FileReader: util.nonEnumerable(fileReader.FileReader),
|
FileReader: util.nonEnumerable(fileReader.FileReader),
|
||||||
FormData: util.nonEnumerable(formData.FormData),
|
FormData: util.nonEnumerable(formData.FormData),
|
||||||
Headers: util.nonEnumerable(headers.Headers),
|
Headers: util.nonEnumerable(headers.Headers),
|
||||||
ImageData: util.nonEnumerable(imageData.ImageData),
|
ImageData: ImageNonEnumerable(() => image.ImageData),
|
||||||
|
ImageBitmap: ImageNonEnumerable(() => image.ImageBitmap),
|
||||||
MessageEvent: util.nonEnumerable(event.MessageEvent),
|
MessageEvent: util.nonEnumerable(event.MessageEvent),
|
||||||
Performance: util.nonEnumerable(performance.Performance),
|
Performance: util.nonEnumerable(performance.Performance),
|
||||||
PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
|
PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
|
||||||
|
@ -110,6 +167,7 @@ const windowOrWorkerGlobalScope = {
|
||||||
),
|
),
|
||||||
atob: util.writable(base64.atob),
|
atob: util.writable(base64.atob),
|
||||||
btoa: util.writable(base64.btoa),
|
btoa: util.writable(base64.btoa),
|
||||||
|
createImageBitmap: ImageWritable(() => image.createImageBitmap),
|
||||||
clearInterval: util.writable(timers.clearInterval),
|
clearInterval: util.writable(timers.clearInterval),
|
||||||
clearTimeout: util.writable(timers.clearTimeout),
|
clearTimeout: util.writable(timers.clearTimeout),
|
||||||
caches: {
|
caches: {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
pub use deno_broadcast_channel;
|
pub use deno_broadcast_channel;
|
||||||
pub use deno_cache;
|
pub use deno_cache;
|
||||||
|
pub use deno_canvas;
|
||||||
pub use deno_console;
|
pub use deno_console;
|
||||||
pub use deno_core;
|
pub use deno_core;
|
||||||
pub use deno_cron;
|
pub use deno_cron;
|
||||||
|
|
|
@ -212,6 +212,7 @@ pub fn create_runtime_snapshot(
|
||||||
Default::default(),
|
Default::default(),
|
||||||
),
|
),
|
||||||
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
||||||
|
deno_canvas::deno_canvas::init_ops_and_esm(),
|
||||||
deno_fetch::deno_fetch::init_ops_and_esm::<Permissions>(Default::default()),
|
deno_fetch::deno_fetch::init_ops_and_esm::<Permissions>(Default::default()),
|
||||||
deno_cache::deno_cache::init_ops_and_esm::<SqliteBackedCache>(None),
|
deno_cache::deno_cache::init_ops_and_esm::<SqliteBackedCache>(None),
|
||||||
deno_websocket::deno_websocket::init_ops_and_esm::<Permissions>(
|
deno_websocket::deno_websocket::init_ops_and_esm::<Permissions>(
|
||||||
|
|
|
@ -411,6 +411,7 @@ impl WebWorker {
|
||||||
Some(main_module.clone()),
|
Some(main_module.clone()),
|
||||||
),
|
),
|
||||||
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
||||||
|
deno_canvas::deno_canvas::init_ops_and_esm(),
|
||||||
deno_fetch::deno_fetch::init_ops_and_esm::<PermissionsContainer>(
|
deno_fetch::deno_fetch::init_ops_and_esm::<PermissionsContainer>(
|
||||||
deno_fetch::Options {
|
deno_fetch::Options {
|
||||||
user_agent: options.bootstrap.user_agent.clone(),
|
user_agent: options.bootstrap.user_agent.clone(),
|
||||||
|
|
|
@ -346,6 +346,7 @@ impl MainWorker {
|
||||||
options.bootstrap.location.clone(),
|
options.bootstrap.location.clone(),
|
||||||
),
|
),
|
||||||
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
deno_webgpu::deno_webgpu::init_ops_and_esm(),
|
||||||
|
deno_canvas::deno_canvas::init_ops_and_esm(),
|
||||||
deno_fetch::deno_fetch::init_ops_and_esm::<PermissionsContainer>(
|
deno_fetch::deno_fetch::init_ops_and_esm::<PermissionsContainer>(
|
||||||
deno_fetch::Options {
|
deno_fetch::Options {
|
||||||
user_agent: options.bootstrap.user_agent.clone(),
|
user_agent: options.bootstrap.user_agent.clone(),
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"imports": {
|
"imports": {
|
||||||
"ext:deno_broadcast_channel/01_broadcast_channel.js": "../ext/broadcast_channel/01_broadcast_channel.js",
|
"ext:deno_broadcast_channel/01_broadcast_channel.js": "../ext/broadcast_channel/01_broadcast_channel.js",
|
||||||
"ext:deno_cache/01_cache.js": "../ext/cache/01_cache.js",
|
"ext:deno_cache/01_cache.js": "../ext/cache/01_cache.js",
|
||||||
|
"ext:deno_canvas/01_image.js": "../ext/canvas/01_image.js",
|
||||||
"ext:deno_console/01_console.js": "../ext/console/01_console.js",
|
"ext:deno_console/01_console.js": "../ext/console/01_console.js",
|
||||||
"ext:deno_cron/01_cron.ts": "../ext/cron/01_cron.ts",
|
"ext:deno_cron/01_cron.ts": "../ext/cron/01_cron.ts",
|
||||||
"ext:deno_crypto/00_crypto.js": "../ext/crypto/00_crypto.js",
|
"ext:deno_crypto/00_crypto.js": "../ext/crypto/00_crypto.js",
|
||||||
|
@ -222,7 +223,6 @@
|
||||||
"ext:deno_web/13_message_port.js": "../ext/web/13_message_port.js",
|
"ext:deno_web/13_message_port.js": "../ext/web/13_message_port.js",
|
||||||
"ext:deno_web/14_compression.js": "../ext/web/14_compression.js",
|
"ext:deno_web/14_compression.js": "../ext/web/14_compression.js",
|
||||||
"ext:deno_web/15_performance.js": "../ext/web/15_performance.js",
|
"ext:deno_web/15_performance.js": "../ext/web/15_performance.js",
|
||||||
"ext:deno_web/16_image_data.js": "../ext/web/16_image_data.js",
|
|
||||||
"ext:deno_webidl/00_webidl.js": "../ext/webidl/00_webidl.js",
|
"ext:deno_webidl/00_webidl.js": "../ext/webidl/00_webidl.js",
|
||||||
"ext:deno_websocket/01_websocket.js": "../ext/websocket/01_websocket.js",
|
"ext:deno_websocket/01_websocket.js": "../ext/websocket/01_websocket.js",
|
||||||
"ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js",
|
"ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js",
|
||||||
|
|
|
@ -8250,7 +8250,6 @@
|
||||||
"interface-objects": {
|
"interface-objects": {
|
||||||
"001.worker.html": [
|
"001.worker.html": [
|
||||||
"The SharedWorker interface object should be exposed.",
|
"The SharedWorker interface object should be exposed.",
|
||||||
"The ImageBitmap interface object should be exposed.",
|
|
||||||
"The CanvasGradient interface object should be exposed.",
|
"The CanvasGradient interface object should be exposed.",
|
||||||
"The CanvasPattern interface object should be exposed.",
|
"The CanvasPattern interface object should be exposed.",
|
||||||
"The CanvasPath interface object should be exposed.",
|
"The CanvasPath interface object should be exposed.",
|
||||||
|
|
Loading…
Reference in a new issue