diff options
author | Jamie <5964236+jamsinclair@users.noreply.github.com> | 2023-12-06 22:20:28 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-12-06 14:20:28 +0100 |
commit | 8c0fb9003d874fcbee0b1a6f6ee30dfb58c668bc (patch) | |
tree | 643966c1543346f803e373329d6c8bb28e199621 /ext | |
parent | dadd8b3d660fd2fd56803f29e1d8b6dd7a2adde9 (diff) |
feat(ext/web): add ImageData Web API (#21183)
Fixes #19288
Adds the `ImageData` Web API.
This would be beneficial to projects using `ImageData` as a convenient
transport layer for pixel data. This is common in Web Assembly projects
that manipulate images. Having this global available in Deno would
improve compatibility of existing JS libraries.
**References**
- [MDN ImageData Web
API](https://developer.mozilla.org/en-US/docs/Web/API/ImageData)
- [whatwg HTML Standard Canvas
Spec](https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation)
Diffstat (limited to 'ext')
-rw-r--r-- | ext/web/16_image_data.js | 215 | ||||
-rw-r--r-- | ext/web/internal.d.ts | 4 | ||||
-rw-r--r-- | ext/web/lib.deno_web.d.ts | 28 | ||||
-rw-r--r-- | ext/web/lib.rs | 1 |
4 files changed, 248 insertions, 0 deletions
diff --git a/ext/web/16_image_data.js b/ext/web/16_image_data.js new file mode 100644 index 000000000..a753a34cf --- /dev/null +++ b/ext/web/16_image_data.js @@ -0,0 +1,215 @@ +// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. + +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"; +const primordials = globalThis.__bootstrap.primordials; +const { + ObjectPrototypeIsPrototypeOf, + SymbolFor, + TypedArrayPrototypeGetLength, + TypedArrayPrototypeGetSymbolToStringTag, + Uint8ClampedArray, +} = primordials; + +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 }; diff --git a/ext/web/internal.d.ts b/ext/web/internal.d.ts index a07f4b814..e151806a1 100644 --- a/ext/web/internal.d.ts +++ b/ext/web/internal.d.ts @@ -111,3 +111,7 @@ declare module "ext:deno_web/13_message_port.js" { transferables: Transferable[]; } } + +declare module "ext:deno_web/16_image_data.js" { + const ImageData: typeof ImageData; +} diff --git a/ext/web/lib.deno_web.d.ts b/ext/web/lib.deno_web.d.ts index 9b9a55c23..9a70e1501 100644 --- a/ext/web/lib.deno_web.d.ts +++ b/ext/web/lib.deno_web.d.ts @@ -1237,3 +1237,31 @@ 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; +}; diff --git a/ext/web/lib.rs b/ext/web/lib.rs index a68b6344e..a5b77d6b8 100644 --- a/ext/web/lib.rs +++ b/ext/web/lib.rs @@ -117,6 +117,7 @@ deno_core::extension!(deno_web, "13_message_port.js", "14_compression.js", "15_performance.js", + "16_image_data.js", ], options = { blob_store: Arc<BlobStore>, |