mirror of
https://github.com/denoland/deno.git
synced 2024-11-23 15:16:54 -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"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
@ -628,6 +634,12 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
|
@ -1124,6 +1136,17 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_canvas"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"deno_core",
|
||||
"deno_webgpu",
|
||||
"image",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deno_config"
|
||||
version = "0.8.1"
|
||||
|
@ -1621,6 +1644,7 @@ dependencies = [
|
|||
"deno_ast",
|
||||
"deno_broadcast_channel",
|
||||
"deno_cache",
|
||||
"deno_canvas",
|
||||
"deno_console",
|
||||
"deno_core",
|
||||
"deno_cron",
|
||||
|
@ -2402,6 +2426,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "ff"
|
||||
version = "0.13.0"
|
||||
|
@ -3168,6 +3201,20 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "import_map"
|
||||
version = "0.18.2"
|
||||
|
@ -3713,6 +3760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||
dependencies = [
|
||||
"adler",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3947,6 +3995,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "num-traits"
|
||||
version = "0.2.17"
|
||||
|
@ -4381,6 +4440,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "polyval"
|
||||
version = "0.6.1"
|
||||
|
@ -5332,6 +5404,12 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
|
||||
|
||||
[[package]]
|
||||
name = "simd-json"
|
||||
version = "0.13.4"
|
||||
|
|
|
@ -12,6 +12,7 @@ members = [
|
|||
"test_util",
|
||||
"ext/broadcast_channel",
|
||||
"ext/cache",
|
||||
"ext/canvas",
|
||||
"ext/console",
|
||||
"ext/cron",
|
||||
"ext/crypto",
|
||||
|
@ -58,6 +59,7 @@ denokv_remote = "0.5.0"
|
|||
# exts
|
||||
deno_broadcast_channel = { version = "0.126.0", path = "./ext/broadcast_channel" }
|
||||
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_cron = { version = "0.12.0", path = "./ext/cron" }
|
||||
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.websocket", deno_websocket::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.broadcast_channel",
|
||||
|
|
|
@ -42,6 +42,7 @@ util::unit_test_factory!(
|
|||
globals_test,
|
||||
headers_test,
|
||||
http_test,
|
||||
image_bitmap_test,
|
||||
image_data_test,
|
||||
internals_test,
|
||||
intl_test,
|
||||
|
|
|
@ -34,6 +34,7 @@ let knownGlobals = [
|
|||
closed,
|
||||
confirm,
|
||||
console,
|
||||
createImageBitmap,
|
||||
crypto,
|
||||
Deno,
|
||||
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.url" />
|
||||
/// <reference lib="deno.web" />
|
||||
/// <reference lib="deno.webgpu" />
|
||||
/// <reference lib="deno.canvas" />
|
||||
/// <reference lib="deno.fetch" />
|
||||
/// <reference lib="deno.websocket" />
|
||||
/// <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 lib="deno.ns" />
|
||||
/// <reference lib="deno.shared_globals" />
|
||||
/// <reference lib="deno.webgpu" />
|
||||
/// <reference lib="deno.webstorage" />
|
||||
/// <reference lib="esnext" />
|
||||
/// <reference lib="deno.cache" />
|
||||
|
|
|
@ -94,6 +94,7 @@ pub fn get_types_declaration_file_text() -> String {
|
|||
"deno.webgpu",
|
||||
"deno.websocket",
|
||||
"deno.webstorage",
|
||||
"deno.canvas",
|
||||
"deno.crypto",
|
||||
"deno.broadcast_channel",
|
||||
"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,
|
||||
StringPrototypeReplaceAll,
|
||||
StringPrototypeToLowerCase,
|
||||
StringPrototypeEndsWith,
|
||||
Uint8Array,
|
||||
TypedArrayPrototypeGetLength,
|
||||
TypedArrayPrototypeIncludes,
|
||||
} = primordials;
|
||||
|
||||
import {
|
||||
assert,
|
||||
collectHttpQuotedString,
|
||||
collectSequenceOfCodepoints,
|
||||
HTTP_QUOTED_STRING_TOKEN_POINT_RE,
|
||||
|
@ -251,4 +256,194 @@ function extractMimeType(headerValues) {
|
|||
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[];
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
error: any,
|
||||
): 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",
|
||||
"14_compression.js",
|
||||
"15_performance.js",
|
||||
"16_image_data.js",
|
||||
],
|
||||
options = {
|
||||
blob_store: Arc<BlobStore>,
|
||||
|
|
|
@ -42,6 +42,7 @@ path = "examples/extension_with_ops/main.rs"
|
|||
deno_ast.workspace = true
|
||||
deno_broadcast_channel.workspace = true
|
||||
deno_cache.workspace = true
|
||||
deno_canvas.workspace = true
|
||||
deno_console.workspace = true
|
||||
deno_core.workspace = true
|
||||
deno_cron.workspace = true
|
||||
|
@ -73,6 +74,7 @@ winapi.workspace = true
|
|||
deno_ast.workspace = true
|
||||
deno_broadcast_channel.workspace = true
|
||||
deno_cache.workspace = true
|
||||
deno_canvas.workspace = true
|
||||
deno_console.workspace = true
|
||||
deno_core.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 { DOMException } from "ext:deno_web/01_dom_exception.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 * as webgpuSurface from "ext:deno_webgpu/02_surface.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
|
||||
const windowOrWorkerGlobalScope = {
|
||||
AbortController: util.nonEnumerable(abortSignal.AbortController),
|
||||
|
@ -60,7 +116,8 @@ const windowOrWorkerGlobalScope = {
|
|||
FileReader: util.nonEnumerable(fileReader.FileReader),
|
||||
FormData: util.nonEnumerable(formData.FormData),
|
||||
Headers: util.nonEnumerable(headers.Headers),
|
||||
ImageData: util.nonEnumerable(imageData.ImageData),
|
||||
ImageData: ImageNonEnumerable(() => image.ImageData),
|
||||
ImageBitmap: ImageNonEnumerable(() => image.ImageBitmap),
|
||||
MessageEvent: util.nonEnumerable(event.MessageEvent),
|
||||
Performance: util.nonEnumerable(performance.Performance),
|
||||
PerformanceEntry: util.nonEnumerable(performance.PerformanceEntry),
|
||||
|
@ -110,6 +167,7 @@ const windowOrWorkerGlobalScope = {
|
|||
),
|
||||
atob: util.writable(base64.atob),
|
||||
btoa: util.writable(base64.btoa),
|
||||
createImageBitmap: ImageWritable(() => image.createImageBitmap),
|
||||
clearInterval: util.writable(timers.clearInterval),
|
||||
clearTimeout: util.writable(timers.clearTimeout),
|
||||
caches: {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub use deno_broadcast_channel;
|
||||
pub use deno_cache;
|
||||
pub use deno_canvas;
|
||||
pub use deno_console;
|
||||
pub use deno_core;
|
||||
pub use deno_cron;
|
||||
|
|
|
@ -212,6 +212,7 @@ pub fn create_runtime_snapshot(
|
|||
Default::default(),
|
||||
),
|
||||
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_cache::deno_cache::init_ops_and_esm::<SqliteBackedCache>(None),
|
||||
deno_websocket::deno_websocket::init_ops_and_esm::<Permissions>(
|
||||
|
|
|
@ -411,6 +411,7 @@ impl WebWorker {
|
|||
Some(main_module.clone()),
|
||||
),
|
||||
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::Options {
|
||||
user_agent: options.bootstrap.user_agent.clone(),
|
||||
|
|
|
@ -346,6 +346,7 @@ impl MainWorker {
|
|||
options.bootstrap.location.clone(),
|
||||
),
|
||||
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::Options {
|
||||
user_agent: options.bootstrap.user_agent.clone(),
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"imports": {
|
||||
"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_canvas/01_image.js": "../ext/canvas/01_image.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_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/14_compression.js": "../ext/web/14_compression.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_websocket/01_websocket.js": "../ext/websocket/01_websocket.js",
|
||||
"ext:deno_websocket/02_websocketstream.js": "../ext/websocket/02_websocketstream.js",
|
||||
|
|
|
@ -8250,7 +8250,6 @@
|
|||
"interface-objects": {
|
||||
"001.worker.html": [
|
||||
"The SharedWorker interface object should be exposed.",
|
||||
"The ImageBitmap interface object should be exposed.",
|
||||
"The CanvasGradient interface object should be exposed.",
|
||||
"The CanvasPattern interface object should be exposed.",
|
||||
"The CanvasPath interface object should be exposed.",
|
||||
|
@ -10971,4 +10970,4 @@
|
|||
"eventsource-reconnect.window.html": false,
|
||||
"request-status-error.window.html": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue