From 1e7e09b7aaa2e2923d107ad2494b8c26329a9b97 Mon Sep 17 00:00:00 2001 From: ggolda Date: Wed, 16 Aug 2023 14:44:24 -0700 Subject: [PATCH] Added functionality to load images from decoded pixel buffers --- README.md | 56 +++++++++++++++++------- lib/index.d.ts | 19 +++++++- lib/index.js | 19 ++++++-- src/image.rs | 33 ++++++++++++++ src/lib.rs | 1 + test/assets/pentagon.raw | 94 ++++++++++++++++++++++++++++++++++++++++ test/media.test.js | 57 +++++++++++++++++++++++- 7 files changed, 257 insertions(+), 22 deletions(-) create mode 100644 test/assets/pentagon.raw diff --git a/README.md b/README.md index cc195ece..8dddbb71 100644 --- a/README.md +++ b/README.md @@ -833,7 +833,7 @@ Its attributes and methods include: | [**left**][win_layout] | [**background**][win_background] | [**title**][title] | [**visible**][visible] | [on()][win_bind] / [once()][win_bind] | | [**top**][win_layout] | [**canvas**][win_canvas] | [**cursor**][cursor] | [**fullscreen**][fullscreen] | [off()][win_bind] | | [**width**][win_layout] | [**ctx**][win_ctx] | [**fit**][fit] | | [close()][close] | -| [**height**][win_layout] | [**page**][win_page] | | | | +| [**height**][win_layout] | [**page**][win_page] | | | | [win_background]: #background [win_canvas]: #canvas-1 @@ -867,7 +867,7 @@ All of the other window properties can be customized by passing an options objec ```js let orange = new Window(1024, 768, {background:"orange"}) -let titled = new Window({title:"Canvas Window"}) // use default 512×512 size +let titled = new Window({title:"Canvas Window"}) // use default 512×512 size ``` After creating the window, you can modify these properties through simple assignment: @@ -900,7 +900,7 @@ console.log([win.canvas.width, win.canvas.height]) // [1024, 32] ``` -> When the window and canvas sizes don’t perfectly match, the canvas will be scaled using the approach selected via the window’s [`fit`][fit] property. +> When the window and canvas sizes don’t perfectly match, the canvas will be scaled using the approach selected via the window’s [`fit`][fit] property. #### Drawing to a Window @@ -941,7 +941,7 @@ win.on('keydown', e => { #### Responding to Events -Once you've created a `Window` object, Node will wait for your current function to end and then switch over to an OS-controlled event loop for the rest of your program’s runtime. This means it can actively redraw your canvas when you resize the window or update its contents, but also means the Node interpreter will be frozen for the duration. +Once you've created a `Window` object, Node will wait for your current function to end and then switch over to an OS-controlled event loop for the rest of your program’s runtime. This means it can actively redraw your canvas when you resize the window or update its contents, but also means the Node interpreter will be frozen for the duration. As a result, you cannot rely upon Node's traditional asynchrononous behavior for structuring your program. In particular, the usual methods for scheduling callbacks like `setTimeout`, `setImmediate`, and `setInterval` **will not work**. @@ -978,7 +978,7 @@ const closeWindow = (e) => { e.target.close() } -let win1 = new Window(), +let win1 = new Window(), win2 = new Window(); win1.on('mousedown', closeWindow) win2.on('mousedown', closeWindow) @@ -995,7 +995,7 @@ function closeWindow(e){ #### Events for Animation -In the previous example you may have noticed that the canvas’s contents were preserved in between events and the screen was only being updated in response to user interaction. In general, this is the behavior you want for UI-driven graphics. +In the previous example you may have noticed that the canvas’s contents were preserved in between events and the screen was only being updated in response to user interaction. In general, this is the behavior you want for UI-driven graphics. But another common case is creating animations in which you redraw the canvas at regular intervals (quite possibly from scratch rather than layering atop the previous contents). In these situations you’ll want to use a set of events that are driven by *timing* rather than interaction: - [`setup`][setup] fires once, just before your window is first drawn to the screen @@ -1028,7 +1028,7 @@ win.on("draw", e => { This specifies the color of the window's background which is drawn behind your canvas content. It supports all the same CSS color formats as the `fillStyle` and `strokeStyle` properties. Defaults to white. #### `canvas` -The `Canvas` object associated with the window. By default the window will create a canvas with the same size as the window dimensions, but the canvas can also be replaced at any time by assigning a new one to this property. +The `Canvas` object associated with the window. By default the window will create a canvas with the same size as the window dimensions, but the canvas can also be replaced at any time by assigning a new one to this property. #### `ctx` The rendering context of the window's canvas. This is a shortcut to calling `win.canvas.getContext("2d")`. If the canvas has multiple pages, this will point to the most recent (i.e., the ‘topmost’ page in the stack). @@ -1036,8 +1036,8 @@ The rendering context of the window's canvas. This is a shortcut to calling `win #### `page` A 1-based index into the canvas's pages array. If the canvas has multiple pages, this property allows you to select which one to display (potentially allowing for pre-rendering a canvas then animating it as a flip-book). Page `1` is the earliest (or ‘bottommost’) page created. Negative page numbers also work, counting backward from `-1` (the ‘topmost’ page). -#### `left` / `top` / `width` / `height` -The current location and size of the window as specified in resolution-independent ‘points’. Defaults to a 512 × 512 pt window in the center of the screen. Note that the window and the canvas have independent sizes: the window will scale the canvas's content to fit its current dimensions (using the `fit` property to determine how to deal with differences in aspect ratio). +#### `left` / `top` / `width` / `height` +The current location and size of the window as specified in resolution-independent ‘points’. Defaults to a 512 × 512 pt window in the center of the screen. Note that the window and the canvas have independent sizes: the window will scale the canvas's content to fit its current dimensions (using the `fit` property to determine how to deal with differences in aspect ratio). #### `title` The string that is displayed in the window's title bar. @@ -1046,7 +1046,7 @@ The string that is displayed in the window's title bar. The icon used for the mouse pointer. By default an arrow cursor is used, but other styles can be selected by setting the property to one of the standard [CSS cursor][mdn_cursor] values. #### `fit` -When the window is resized, it is likely that it will not perfectly match the aspect ratio of the underlying canvas. This property selects how the layout should adapt—whether it should add margins, allow portions of the canvas to be cropped, or stretch the image to fit. It supports the standard [CSS modes][mdn_object_fit] (`"none"`, `"contain"`, `"cover"`, `"fill"`, and `"scale-down"`) plus some additions: +When the window is resized, it is likely that it will not perfectly match the aspect ratio of the underlying canvas. This property selects how the layout should adapt—whether it should add margins, allow portions of the canvas to be cropped, or stretch the image to fit. It supports the standard [CSS modes][mdn_object_fit] (`"none"`, `"contain"`, `"cover"`, `"fill"`, and `"scale-down"`) plus some additions: - `contain-x` and `contain-y` extend the `contain` mode to choose which axis to use when fitting the canvas - `resize` will modify the window's canvas to match the new window size (you'll probably also want to define an `.on("resize")` handler to update the contents) @@ -1065,8 +1065,8 @@ Removes the window from the screen permanently. References to the `Window` objec #### `on()` / `off()` / `once()` The `Window` object is an [Event Emitter][event_emitter] subclass and supports all the standard methods for adding and removing event listeners. The supported events are mostly consistent with browser-based DOM events, but include some non-standard additions (⚡) specific to Skia Canvas: -| Mouse | Keyboard | Window | Focus | Animation | -| -- | -- | -- | -- | -- | +| Mouse | Keyboard | Window | Focus | Animation | +| -- | -- | -- | -- | -- | | [`mousedown`][mousedown] | [`keydown`][keydown] | [`fullscreen`](#fullscreen-event) ⚡ | [`blur`][blur] | [`setup`][setup] ⚡| | [`mouseup`][mouseup] | [`keyup`][keyup] | [`move`](#move-event) ⚡ | [`focus`][focus] | [`frame`][frame] ⚡| | [`mousemove`][mousemove] | [`input`][input] | [`resize`][resize] | | [`draw`][draw] ⚡ | @@ -1085,7 +1085,7 @@ The `setup` event is emitted just before a newly created window is displayed on Similar to the `requestAnimationFrame` callback system in browsers, the `frame` event allows you to schedule redrawing your canvas to maintain a constant frame rate. The event object provides a window-specific frame counter that begins ticking upward from zero as soon as the window appears. ##### `draw` event -The `draw` event fires immediately after `frame` and has the potentially convenient side effect of automatically erasing the window's canvas before calling your event handler. +The `draw` event fires immediately after `frame` and has the potentially convenient side effect of automatically erasing the window's canvas before calling your event handler. > Note that this canvas-clearing behavior depends upon your having set up an event handler using `.on("draw", …)` and will continue until (and unless) you delete the window's `draw` event handlers using `.off()` or [`removeAllListeners()`][remove_all]. @@ -1119,11 +1119,11 @@ The `App` global variable is a static class which does not need to be instantiat ##### PROPERTIES #### `fps` -By default, each window will attempt to update its display 60 times per second. You can reduce this by setting `App.fps` to a smaller integer value. You can raise it as well but on the majority of LCD monitors you won't see any benefit and are likely to get worse performance as you begin to swamp the CPU with your rendering code. +By default, each window will attempt to update its display 60 times per second. You can reduce this by setting `App.fps` to a smaller integer value. You can raise it as well but on the majority of LCD monitors you won't see any benefit and are likely to get worse performance as you begin to swamp the CPU with your rendering code. > This setting is only relevant if you are listening for `frame` or `draw` events on your windows. Otherwise the canvas will only be updated when responding to UI interactions like keyboard and mouse events. #### `running` -A read-only boolean flagging whether the GUI event loop has taken control away from Node in order to display your windows. +A read-only boolean flagging whether the GUI event loop has taken control away from Node in order to display your windows. #### `windows` An array of references to all of the `Window` objects that have been created and not yet [closed][close]. @@ -1131,7 +1131,7 @@ An array of references to all of the `Window` objects that have been created and ##### METHODS #### `launch()` -Any `Window` you create will schedule the `App` to begin running as soon as the current function returns. You can make this happen sooner by calling `App.launch` within your code. The `launch()` method will not return until the last window is closed so you may find it handy to place ‘clean up’ code after the `launch()` invocation. +Any `Window` you create will schedule the `App` to begin running as soon as the current function returns. You can make this happen sooner by calling `App.launch` within your code. The `launch()` method will not return until the last window is closed so you may find it handy to place ‘clean up’ code after the `launch()` invocation. >Note, however, that the `App` **cannot be launched a second time** once it terminates due to limitiations in the underlying platform libraries. #### `quit()` @@ -1165,6 +1165,30 @@ ctx.drawImage(img, 100, 100) In addition to HTTP URLs, both `loadImage()` and the `Image.src` attribute will also accept [data URLs][DataURL], local file paths, and [Buffer][Buffer] objects. +#### Loading decoded pixel buffers +Both `Image` and `loadImage()` supports loading raw decoded pixel buffers. In this case API consumer needs to provide a `Buffer` with pixel data and needs to know image resolution and color type beforehand. + +```js +let img = await loadImage(pixelBuffer, { + raw: { + width: 1920, + height: 1080, + colorType: 'rgba' + } +}) +ctx.drawImage(img, 100, 100) +``` +```js +let img = new Image({ + raw: { + width: 1920, + height: 1080, + colorType: 'rgba' + } +}) +img.src = pixelBuffer +ctx.drawImage(img, 100, 100) +``` ### FontLibrary The `FontLibrary` is a static class which does not need to be instantiated with `new`. Instead you can access the properties and methods on the global `FontLibrary` you import from the module and its contents will be shared across all canvases you create. diff --git a/lib/index.d.ts b/lib/index.d.ts index 9e4884a3..bc56987e 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -12,9 +12,26 @@ export class CanvasTexture {} // Images // -export function loadImage(src: string | Buffer): Promise +export type ColorType = "rgba" | "rgb" | "bgra" | "argb" + +export interface ImageInfo { + /** Image width */ + width: number + /** Image height */ + height: number + /** Color type of pixel */ + colorType: ColorType +} + +export interface ImageOptions { + /** Describes how to process raw image buffer with decoded pixels */ + raw?: ImageInfo | undefined +} + +export function loadImage(src: string | Buffer, options: ImageOptions? = null): Promise export class ImageData extends globalThis.ImageData {} export class Image extends globalThis.Image { + constructor(options: ImageOptions? = null) get src(): string set src(src: string | Buffer) } diff --git a/lib/index.js b/lib/index.js index 8262aefa..8c7bd6b0 100644 --- a/lib/index.js +++ b/lib/index.js @@ -837,8 +837,11 @@ class FontLibrary extends RustClass { } class Image extends RustClass { - constructor(){ + #options; + + constructor(options = null){ super(Image).alloc() + this.#options = options; } get complete(){ return this.prop('complete') } @@ -886,8 +889,16 @@ class Image extends RustClass { } this.prop("src", src) - if (data){ - if (this.prop("data", data)) onload(this) + if (data) { + let success; + // if data contains raw pixel buffer and was decoded previously already + if (this.#options?.raw) { + success = this.ƒ('load_pixel_data', data, this.#options.raw) + } else { + success = this.prop("data", data) + } + + if (success) onload(this) else onerror(new Error("Could not decode image data")) } @@ -1060,7 +1071,7 @@ class TextMetrics{ } } -const loadImage = src => Object.assign(new Image(), {src}).decode() +const loadImage = (src, options = null) => Object.assign(new Image(options), {src}).decode() module.exports = { Canvas, CanvasGradient, CanvasPattern, CanvasRenderingContext2D, CanvasTexture, diff --git a/src/image.rs b/src/image.rs index 138dce9a..1fe67b58 100644 --- a/src/image.rs +++ b/src/image.rs @@ -70,6 +70,29 @@ pub fn set_data(mut cx: FunctionContext) -> JsResult { Ok(cx.boolean(this.image.is_some())) } +pub fn load_pixel_data(mut cx: FunctionContext) -> JsResult { + let this = cx.argument::(0)?; + let mut this = this.borrow_mut(); + + let buffer = cx.argument::(1)?; + let data = Data::new_copy(buffer.as_slice(&mut cx)); + + let image_parameters = cx.argument::(2)?; + let js_width: Handle = image_parameters.get(&mut cx, "width")?; + let js_height: Handle = image_parameters.get(&mut cx, "height")?; + let js_color_type: Handle = image_parameters.get(&mut cx, "colorType")?; + + let color_type = map_color_type(js_color_type.value(&mut cx).as_str()); + let width = js_width.value(&mut cx) as i32; + let height = js_height.value(&mut cx) as i32; + let row_bytes = (width as usize) * color_type.bytes_per_pixel(); + + let image_info = ImageInfo::new((width, height), color_type, AlphaType::Unpremul, None); + this.image = SkImage::from_raster_data(&image_info, data, row_bytes); + + Ok(cx.boolean(this.image.is_some())) +} + pub fn get_width(mut cx: FunctionContext) -> JsResult { let this = cx.argument::(0)?; let this = this.borrow(); @@ -95,3 +118,13 @@ pub fn get_complete(mut cx: FunctionContext) -> JsResult { let this = this.borrow(); Ok(cx.boolean(this.image.is_some())) } + +fn map_color_type(color_type: &str) -> ColorType { + match color_type { + "rgba" => ColorType::RGBA8888, + "rgb" => ColorType::RGB888x, + "bgra" => ColorType::BGRA8888, + "argb" => ColorType::ARGB4444, + _ => ColorType::RGBA8888, + } +} diff --git a/src/lib.rs b/src/lib.rs index 448e9061..27cab97d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> { cx.export_function("Image_get_src", image::get_src)?; cx.export_function("Image_set_src", image::set_src)?; cx.export_function("Image_set_data", image::set_data)?; + cx.export_function("Image_load_pixel_data", image::load_pixel_data)?; cx.export_function("Image_get_width", image::get_width)?; cx.export_function("Image_get_height", image::get_height)?; cx.export_function("Image_get_complete", image::get_complete)?; diff --git a/test/assets/pentagon.raw b/test/assets/pentagon.raw new file mode 100644 index 00000000..cb981753 --- /dev/null +++ b/test/assets/pentagon.raw @@ -0,0 +1,94 @@ +պЭơǐϰ}KÛ}ܸ}wʧ|33沣ʇmC& u[ddǁ{mCHH&[[E#ddddǁ{mCHHHH&##,ddddddǁ{mCHHHHHH&HE'ddddddddǁ{mCHHHHHHHH&EEնz'ddddddddddǁ{mCHHHHHHHHHH&rro'ddddddddddddǁ{mCHHHHHHHHHHHH& r|;In'ddddddddddddddǁ{mCHHHHHHHHHHHHHH& 2@Ƞp "mnPh +ddddddddddddddcWIHHHHHHHHHHHHHHM +z? Yhimsnʘʔh +ddddddddddddc/-IHHHHHHHHHHHHM +KSJYe}!6imsnʘʘʘʔh +ddddddddddc/-IHHHHHHHHHHM +KSSSJ!0limsnʘʘʘʘʘʔh +ddddddddc/-IHHHHHHHHM +KSSSSSJCTiimsnʘʘʘʘʘʘʘʔh +ddddddc/-IHHHHHHM +KSSSSSSSJCPӴu*iimsnʘʘʘʘʘʘʘʘʘʔh +ddddc/-IHHHHM +KSSSSSSSSSJ,촴u~iiimsnʘʘʘʘʘʘʘʘʘʘʘʔh +ddc/-IHHM +KSSSSSSSSSSSJ +oox00X^ +imsnʘʘʘʘʘʘʘʘʘʘʘʘʘʔh +c/-IM +KSSSSSSSSSSSSSJ +00` XXXb utʘʘʘʘʘʘʘʘʘʘʘʘʘʘʘ̔53LSSSSSSSSSSSSSSSR  杝WWXXXX\Cʚʘʘʘʘʘʘʘʘʘʘʘʘʘ̘yL_TSSSSSSSSSSSSS[+WWm XXXX\Cʚʘʘʘʘʘʘʘʘʘʘʘ̘yTTL_~~TSSSSSSSSSSS[+ [XXXX\Cʚʘʘʘʘʘʘʘʘʘ̘yTTTTL_~~~~TSSSSSSSSS[+߅AAXXXXX\Cʚʘʘʘʘʘʘʘ̘yTTTTTTL_~~~~~~TSSSSSSS[+ +IjѶeXXXXX\Cʚʘʘʘʘʘ̘yTTTTTTTTL_~~~~~~~~TSSSSS[+ + +7VcmhWd +?X +XXX\Cʚʘʘʘ̘yTTTTTTTTTTL_~~~~~~~~~~TSSS[+ + +7VVVVm.vhWhWhWd +?X +X\Cʚʘ̘yTTTTTTTTTTTTL_~~~~~~~~~~~~TS[+ + +7VVVVVV.uěn +^hWhWhWhWhWd +?\ C̘yTTTTTTTTTTTTTTL _~~~~~~~~~~~~~~]+ + +7VVVVVVVV +]ThWhWhWhWhWhWhWlW4 &VTTTTTTTTTTTTTTY ~~~~~~~~~~~~~~|& 4kWVVVVVVVVVVU{lhWhWhWhWhWhWhWlWNl VTTTTTTTTTTTTY~~~~~~~~~~~~| TunTWVVVVVVVVVVkϭjYhWhWhWhWhWhWhWlWNl VTTTTTTTTTTY~~~~~~~~~~| TunnnTWVVVVVVVVVV[hWhWhWhWhWhWhWlWNl VTTTTTTTTY~~~~~~~~| TunnnnnTWVVVVVVVVY wghWhWhWhWhWlWNl VTTTTTTY~~~~~~| TunnnnnnnTWVVVVVVY mhWhWhWhWlWNl VTTTTY~~~~| TunnnnnnnnnTWVVVVY jjjg[hWhWlWNl VTTY~~| TunnnnnnnnnnnTWVVY ")g[lWNl VY| TunnnnnnnnnnnnnTWY ddd Sl  UvnnnnnnnnnnnnnnnXddd +T ztɃU)$YonnnnnnnnnnnnnrH~: +^ ztɃU++)$44YonnnnnnnnnnnrH~:ww +^ ]hztɃU++++)$4444YonnnnnnnnnrH~:wwww +^ ^`^ٶztɃU++++++)$444444YonnnnnnnrH~:wwwwwwγ ztɃU++++++++)$44444444YonnnnnrH~:wwwwww }VbztɃU++++++++++)$4444444444YonnnrH~:wwwwwwwVYְztɃU++++++++++++)$444444444444YonrH~:wwwwwwwwҬzt˂U++++++++++++++ )$ +44444444444444YsH~:wwwwwwww| P\e0++++++++++++++0j%  Y$9444444444444448jkwwwwwwwwwPSӪ[3=0++++++++++++0}  axr94444444444448tH3@'cwwwwwwwwΥ[333=0++++++++++0}  axxxr944444444448tH3@3@3@'cwwwwwwz IV[33333=0++++++++0}  axxxxxr9444444448tH3@3@3@3@3@'cwwwwwILУ[3333333=0++++++0}  axxxxxxxr94444448tH3@3@3@3@3@3@3@'cwwww˞] 333333333=0++++0}  axxxxxxxxxr944448tH3@3@3@3@3@3@3@3@3@'cwwyiC3333333333=0++0}  axxxxxxxxxxxr9448tH3@3@3@3@3@3@3@3@3@3@3@'cwCF33333333333=00}  axxxxxxxxxxxxxr98tH3@3@3@3@3@3@3@3@3@3@3@3@3@'cǙ433333333333B# + + +bxxxxxxxxxxxxxxxw#tL3@3@3@3@3@3@3@3@3@3@3@3@3@3@4Ac<33333333338Ղyxxxxxxxxxxxxx|s2z3E3@3@3@3@3@3@3@3@3@3@3@3@3@pq<3333333338Ղyxxxxxxxxxxx|s112z3E3@3@3@3@3@3@3@3@3@3@3@DE֏333333338Ղyxxxxxxxxx|s11112z3E3@3@3@3@3@3@3@3@3@DE^53333338Ղyxxxxxxx|s1111112z3E3@3@3@3@3@3@3@DE5333338Ղyxxxxx|s111111112z3E3@3@3@3@3@DE93338Ղyxxx|s11111111112z3E3@3@3@DE/938Ղyx|s1111111111112z3E3@DE/h?Ղ~!s111111111111112zDJr" & + +(( + +g&111111111111111B #= s + +76XU?> + +_$1111111111111B '{ sss + +76XUXUXU?> + +_$11111111111B { sssss + +76XUXUXUXUXU?> + +_$111111111B ! sssssss + +76XUXUXUXUXUXUXU?> + +_$1111111B !u sssssssss + +76XUXUXUXUXUXUXUXUXU?> + +_$11111B u sssssssssss + +76XUXUXUXUXUXUXUXUXUXUXU?> + +_$111B  sssssssssssss + +76XUXUXUXUXUXUXUXUXUXUXUXUXU?> + +_$1B n#sssssssssssssss <;XUXUXUXUXUXUXUXUXUXUXUXUXUXUXUCA_ 5#nH|sssssssssssssj +?f\YXUXUXUXUXUXUXUXUXUXUXUXUXU[Xqi+J ++H|sssssssssssj +?^^f\YXUXUXUXUXUXUXUXUXUXUXU[Xqi0b0b+J ++gH|sssssssssj +?^^^^f\YXUXUXUXUXUXUXUXUXU[Xqi0b0b0b0b+J ++gH|sssssssj +?^^^^^^f\YXUXUXUXUXUXUXU[Xqi0b0b0b0b0b0b+J ++H|sssssj +?^^^^^^^^f\YXUXUXUXUXU[Xqi0b0b0b0b0b0b0b0b+J ++p|sssj +?^^^^^^^^^^f\YXUXUXU[Xqi0b0b0b0b0b0b0b0b0b0b+J ++a|sj +?^^^^^^^^^^^^f\YXU[Xqi0b0b0b0b0b0b0b0b0b0b0b0b+J ++t ?^^^^^^^^^^^^^^f_]qi0b0b0b0b0b0b0b0b0b0b0b0b0b0b+J, %ZZ v%c ^^^^^^^^^^^^^^aw/f0b0b0b0b0b0b0b0b0b0b0b0b0b0bAg % \\ {C6c ^^^^^^^^^^^^aoho/f0b0b0b0b0b0b0b0b0b0b0b0bAg /<   + + {CCC6c ^^^^^^^^^^aohhho/f0b0b0b0b0b0b0b0b0b0bAg /<<<  ? SS {CCCCC6c ^^^^^^^^aohhhhho/f0b0b0b0b0b0b0b0bAg /<<<<<  7VSS {CCCCCCC6c ^^^^^^aohhhhhhho/f0b0b0b0b0b0bAg /<<<<<<<  7 + {CCCCCCCCC6c ^^^^aohhhhhhhhho/f0b0b0b0bAg /<<<<<<<<<  7 +PP {CCCCCCCCCCC6c ^^aohhhhhhhhhhho/f0b0bAg /<<<<<<<<<<<  7PMMˎ쓑Ě̫̫̫̫̫̫̫̫̫̫̫̫̫ʥݢٓۍ \ No newline at end of file diff --git a/test/media.test.js b/test/media.test.js index 32ce68d1..bb5b999a 100644 --- a/test/media.test.js +++ b/test/media.test.js @@ -48,6 +48,20 @@ describe("Image", () => { expect(img).toMatchObject(LOADED) }) + test("pixel buffer", () => { + const buffer = fs.readFileSync('test/assets/pentagon.raw'); + let rawImage = new Image({ + raw: { + width: 125, + height: 125, + colorType: 'rgba', + } + }); + + rawImage.src = buffer; + expect(rawImage).toMatchObject(LOADED); + }) + test("local file", () => { expect(img).toMatchObject(FRESH) img.src = PATH @@ -79,6 +93,15 @@ describe("Image", () => { img = await loadImage(PATH) expect(img).toMatchObject(LOADED) + img = await loadImage(PATH.replace('.png', '.raw'), { + raw: { + width: 125, + height: 125, + colorType: 'rgba' + } + }) + expect(img).toMatchObject(LOADED) + expect(async () => { await loadImage('http://nonesuch') }).rejects.toEqual("HTTP_ERROR_404") }) }) @@ -91,6 +114,21 @@ describe("Image", () => { expect(img.complete).toEqual(true) }) + test(".complete flag false with incorrect pixel buffer", () => { + const buffer = Buffer.alloc(10); + let rawImage = new Image({ + raw: { + width: 60, + height: 60, + colorType: 'rgba', + } + }) + + rawImage.src = buffer + + expect(rawImage.complete).toEqual(false) + }) + test(".onload callback", done => { // ensure that the fetch process can be overwritten while in flight img.onload = loaded => { throw Error("should not be called") } @@ -108,6 +146,24 @@ describe("Image", () => { img.src = 'http://nonesuch' }) + test(".onerror callback with incorrect image data", done => { + const buffer = fs.readFileSync('test/assets/pentagon.raw'); + let rawImage = new Image({ + raw: { + width: 700, + height: 700, + colorType: 'rgba', + } + }) + + rawImage.onerror = err => { + expect(err).toBeInstanceOf(Error) + done() + } + + rawImage.src = buffer; + }) + test(".decode promise", async () => { expect(()=> img.decode() ).rejects.toEqual(new Error('Missing Source URL')) @@ -207,4 +263,3 @@ describe("FontLibrary", ()=>{ expect(FontLibrary.has(alias)).toBe(false) }) }) -