Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions crates/canvas-2d/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,14 @@ pub fn bytes_to_data_url(
"data:,".to_string()
}

// use native format
pub fn bytes_to_data_n32_url(
/// `bytes` are RGBA, or BGRA when `bgra`: the source texture's format sets the order, not the
/// platform.
pub fn bytes_to_data_rgba8_url(
width: i32,
height: i32,
bytes: &[u8],
row_bytes: usize,
bgra: bool,
format: &str,
quality: c_uint,
) -> String {
Expand All @@ -101,11 +103,7 @@ pub fn bytes_to_data_n32_url(
encoded_prefix.push_str(format);
encoded_prefix.push_str(";base64,");

#[cfg(any(target_os = "ios", target_os = "macos", target_os = "visionos", target_os = "tvos"))]
let fmt = ColorType::BGRA8888;

#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "visionos", target_os = "tvos")))]
let fmt = ColorType::RGBA8888;
let fmt = if bgra { ColorType::BGRA8888 } else { ColorType::RGBA8888 };

let image_info = ImageInfo::new((width, height), fmt, AlphaType::Unpremul, None);
if let Some(image) = images::raster_from_data(&image_info, data, row_bytes) {
Expand Down
3 changes: 2 additions & 1 deletion crates/canvas-c/src/webgpu/gpu_canvas_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,11 +482,12 @@ fn to_data_url_with_texture(
let row_bytes = round_up_to_256_u64((4 * width) as u64) as usize;

#[cfg(feature = "2d")]
let encoded = Some(canvas_2d::bytes_to_data_n32_url(
let encoded = Some(canvas_2d::bytes_to_data_rgba8_url(
width as i32,
height as i32,
bytes,
row_bytes,
is_bgra,
format,
quality,
));
Expand Down
3 changes: 2 additions & 1 deletion crates/canvas-webgl/src/webgl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2303,7 +2303,8 @@ pub fn canvas_native_webgl_read_drawing_buffer(source: &mut WebGLState) -> (i32,
gl_bindings::GetIntegerv(gl_bindings::PIXEL_PACK_BUFFER_BINDING, &mut pack_buffer);
gl_bindings::BindBuffer(gl_bindings::PIXEL_PACK_BUFFER, 0);
}
gl_bindings::BindFramebuffer(gl_bindings::FRAMEBUFFER, 0);
// The drawing buffer: framebuffer 0, or on iOS the view's own (what binding null means).
canvas_native_webgl_bind_frame_buffer(gl_bindings::FRAMEBUFFER, 0, source);
gl_bindings::PixelStorei(gl_bindings::PACK_ALIGNMENT, 4);
gl_bindings::ReadPixels(
0,
Expand Down
4 changes: 3 additions & 1 deletion packages/canvas-polyfill/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { XMLDocument } from './DOM/XMLDocument';
import { DOMPointReadOnly, DOMPoint } from './DOM/DOMPointReadOnly';
import { Device, fromObject, View } from '@nativescript/core';
import { AbortController, AbortSignal } from '@nativescript/core/abortcontroller';
import { CanvasRenderingContext2D, WebGLRenderingContext, WebGL2RenderingContext, ImageData, ImageBitmap } from '@nativescript/canvas';
import { CanvasRenderingContext2D, WebGLRenderingContext, WebGL2RenderingContext, ImageData, ImageBitmap, OffscreenCanvas, OffscreenCanvasRenderingContext2D } from '@nativescript/canvas';
import { HTMLCollection } from './DOM/HTMLCollection';
import { HTMLUnknownElement } from './DOM/HTMLUnknownElement';
import { Navigator } from './navigator';
Expand All @@ -24,6 +24,8 @@ import { hookGamepadListeners } from './gamepad';
(global as any).window.HTMLImageElement = (global as any).HTMLImageElement = (global as any).HTMLImageElement || HTMLImageElement;
(global as any).window.Image = (global as any).Image = (global as any).Image || HTMLImageElement;
(global as any).window.ImageBitmap = (global as any).ImageBitmap = (global as any).ImageBitmap || ImageBitmap;
(global as any).window.OffscreenCanvas = (global as any).OffscreenCanvas = (global as any).OffscreenCanvas || OffscreenCanvas;
(global as any).window.OffscreenCanvasRenderingContext2D = (global as any).OffscreenCanvasRenderingContext2D = (global as any).OffscreenCanvasRenderingContext2D || OffscreenCanvasRenderingContext2D;
(global as any).window.HTMLVideoElement = (global as any).HTMLVideoElement = (global as any).HTMLVideoElement || HTMLVideoElement;
(global as any).window.Video = (global as any).Video = (global as any).Video || HTMLVideoElement;
(global as any).window.HTMLAudioElement = (global as any).HTMLAudioElement = (global as any).HTMLAudioElement || HTMLAudioElement;
Expand Down
55 changes: 54 additions & 1 deletion packages/canvas/Canvas/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,12 @@ export function lengthToDevicePixels(value: any, parent: any, isWidth: boolean):

let warnedReady = false;

function domError(name: string, message: string) {
const error: any = new Error(message);
error.name = name;
return error;
}

@CSSType('Canvas')
export abstract class CanvasBase extends ContainerView implements ICanvasBase {
/** @deprecated The canvas is usable as soon as it exists; call getContext() directly. */
Expand Down Expand Up @@ -1308,7 +1314,54 @@ export abstract class CanvasBase extends ContainerView implements ICanvasBase {
this[attrib] = value ?? attrib;
}

public abstract getContext(type: string, contextAttributes?: any): CanvasRenderingContext | null;
/** Set by `transferControlToOffscreen()`: the canvas then only shows its OffscreenCanvas's frames. */
protected _transferredToOffscreen = false;

/** Builds the OffscreenCanvas a transferred canvas hands out; set by the OffscreenCanvas module. */
static _offscreenFromPlaceholder: ((canvas: CanvasBase) => any) | null = null;

/** getContext() without the transfer check, for this canvas's OffscreenCanvas. */
public abstract _getContext(type: string, contextAttributes?: any): CanvasRenderingContext | null;

/** @internal */
public abstract _hasContext(): boolean;

/** Sets the drawing buffer's size, as `width`/`height` do but without the transfer check. */
public abstract _resizeBitmap(width: number, height: number): void;

public getContext(type: string, contextAttributes?: any): CanvasRenderingContext | null {
if (this._transferredToOffscreen) {
throw domError('InvalidStateError', "Failed to execute 'getContext' on 'HTMLCanvasElement': Cannot get context from a canvas that has transferred its control to offscreen.");
}
return this._getContext(type, contextAttributes);
}

/**
* Hands drawing over to an OffscreenCanvas, which draws into this canvas's surface so its frames
* still show here. The canvas can't have a context, and can't be transferred twice.
*/
transferControlToOffscreen() {
if (this._transferredToOffscreen) {
throw domError('InvalidStateError', "Failed to execute 'transferControlToOffscreen' on 'HTMLCanvasElement': Cannot transfer control from a canvas for more than one time.");
}
if (this._hasContext()) {
throw domError('InvalidStateError', "Failed to execute 'transferControlToOffscreen' on 'HTMLCanvasElement': Cannot transfer control from a canvas that has a rendering context.");
}
const create = CanvasBase._offscreenFromPlaceholder;
if (!create) {
throw new Error('OffscreenCanvas is unavailable: import it from @nativescript/canvas.');
}
const offscreen = create(this);
this._transferredToOffscreen = true;
return offscreen;
}

/** A transferred canvas's size belongs to its OffscreenCanvas. */
protected __assertCanResize(name: 'width' | 'height') {
if (this._transferredToOffscreen) {
throw domError('InvalidStateError', `Failed to set the '${name}' property on 'HTMLCanvasElement': Cannot resize canvas after call to transferControlToOffscreen().`);
}
}

public abstract getBoundingClientRect(): {
x: number;
Expand Down
13 changes: 12 additions & 1 deletion packages/canvas/Canvas/index.android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,13 @@ export class Canvas extends CanvasBase {
}

set width(value: any) {
this.__assertCanResize('width');
this.__setSurfaceWidth(value);
this.__resetAfterResize();
}

set height(value: any) {
this.__assertCanResize('height');
this.__setSurfaceHeight(value);
this.__resetAfterResize();
}
Expand Down Expand Up @@ -532,7 +534,16 @@ export class Canvas extends CanvasBase {
}
}

getContext(type: string, contextAttributes?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
_hasContext(): boolean {
return this._contextType !== ContextType.None;
}

_resizeBitmap(width: number, height: number) {
this.__setSurfaceWidth(width);
this.__setSurfaceHeight(height);
}

_getContext(type: string, contextAttributes?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
if (!this._canvas) {
return null;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/canvas/Canvas/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { WebGLRenderingContext } from '../WebGL/WebGLRenderingContext';
import { WebGL2RenderingContext } from '../WebGL2/WebGL2RenderingContext';
import { GPUCanvasContext } from '../WebGPU';
import { ImageBitmapRenderingContext } from '../ImageBitmapRenderingContext';
import type { OffscreenCanvas } from '../OffscreenCanvas';
import { LengthPercentage } from '@nativescript/core/css/parser';

export declare function createSVGMatrix(): DOMMatrix;
Expand Down Expand Up @@ -41,6 +42,9 @@ export declare class Canvas extends CanvasBase {
/** Default for `getContext('2d', { threaded })`: rasterize on a shared render thread (Android, iOS, Windows). */
static threaded2D: boolean;

/** Default for `getContext('webgl' | 'webgl2', { threaded })`: run the context on the WebGL thread (Android). */
static threadedWebGL: boolean;

surfaceOnTop: boolean;

static forceGL: boolean;
Expand All @@ -55,6 +59,13 @@ export declare class Canvas extends CanvasBase {

toDataURL(type?: string, encoderOptions?: number): any;

/**
* Hands drawing over to an OffscreenCanvas that draws into this canvas's surface. Throws an
* `InvalidStateError` if the canvas already has a context or was transferred; afterwards its
* getContext() and width/height setters throw too.
*/
transferControlToOffscreen(): OffscreenCanvas;

getContext(type: '2d', options?: any): CanvasRenderingContext2D | null;

getContext(type: 'bitmaprenderer', options?: { alpha?: boolean }): ImageBitmapRenderingContext | null;
Expand Down
13 changes: 12 additions & 1 deletion packages/canvas/Canvas/index.ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,12 +198,14 @@ export class Canvas extends CanvasBase {

// @ts-ignore
set width(value: any) {
this.__assertCanResize('width');
this.__setSurfaceWidth(value);
this.__resetAfterResize();
}

// @ts-ignore
set height(value: any) {
this.__assertCanResize('height');
this.__setSurfaceHeight(value);
this.__resetAfterResize();
}
Expand Down Expand Up @@ -519,7 +521,16 @@ export class Canvas extends CanvasBase {
return context;
}

getContext(type: string, options?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
_hasContext(): boolean {
return this._contextType !== ContextType.None;
}

_resizeBitmap(width: number, height: number) {
this.__setSurfaceWidth(width);
this.__setSurfaceHeight(height);
}

_getContext(type: string, options?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
if (!this._canvas) {
return null;
}
Expand Down
13 changes: 12 additions & 1 deletion packages/canvas/Canvas/napi-host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ export abstract class NapiCanvas extends CanvasBase {

// @ts-ignore
set width(value: any) {
this.__assertCanResize('width');
this.__setSurfaceWidth(value);
this.__resetAfterResize();
}
Expand All @@ -278,6 +279,7 @@ export abstract class NapiCanvas extends CanvasBase {

// @ts-ignore
set height(value: any) {
this.__assertCanResize('height');
this.__setSurfaceHeight(value);
this.__resetAfterResize();
}
Expand Down Expand Up @@ -518,7 +520,16 @@ export abstract class NapiCanvas extends CanvasBase {
return context;
}

getContext(type: string, options?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
_hasContext(): boolean {
return this._contextType !== ContextType.None;
}

_resizeBitmap(width: number, height: number) {
this.__setSurfaceWidth(width);
this.__setSurfaceHeight(height);
}

_getContext(type: string, options?: any): CanvasRenderingContext2D | WebGLRenderingContext | WebGL2RenderingContext | GPUCanvasContext | null {
if (!this._canvas || typeof type !== 'string') {
return null;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/canvas/Canvas/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,12 @@ export function holdBackAnimationFramesWhileBehind() {
target.window.cancelAnimationFrame = cancelAnimationFrame;
}
}

/**
* An OffscreenCanvas, which image sources (drawImage, createPattern, createImageBitmap, texImage2D,
* copyExternalImageToTexture) take like a Canvas: both hand their context's native object as `native`.
* Checked by tag so the consumers need not import the OffscreenCanvas module.
*/
export function isOffscreenCanvas(value: any): boolean {
return !!value && typeof value === 'object' && value[Symbol.toStringTag] === 'OffscreenCanvas';
}
7 changes: 4 additions & 3 deletions packages/canvas/Canvas2D/CanvasRenderingContext2D/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DOMMatrix } from '../DOMMatrix';
import type { CanvasRenderingContext } from '../../common';
import { NAPI_HOST } from '../../platform';
import { imageAssetFor } from '../../platform/index';
import { isOffscreenCanvas } from '../../Canvas/utils';
declare const NSCCanvasRenderingContext2D;

const REPETITIONS = ['repeat', 'repeat-x', 'repeat-y', 'no-repeat'];
Expand Down Expand Up @@ -789,7 +790,7 @@ export class CanvasRenderingContext2D implements CanvasRenderingContext {
return null;
} else if (image instanceof ImageAsset) {
img = image.native;
} else if (image instanceof Canvas) {
} else if ((image instanceof Canvas || isOffscreenCanvas(image))) {
img = (image as any).native;
} else if (image && typeof image.tagName === 'string' && (image.tagName === 'IMG' || image.tagName === 'IMAGE')) {
if (__ANDROID__ && image._image instanceof android.graphics.Bitmap) {
Expand Down Expand Up @@ -907,7 +908,7 @@ export class CanvasRenderingContext2D implements CanvasRenderingContext {
} else if (__APPLE__ && image instanceof UIImage) {
drawNativeImage(args, image, this);
return;
} else if (image instanceof Canvas) {
} else if ((image instanceof Canvas || isOffscreenCanvas(image))) {
image = (image as any).native;
} else if (image && typeof image.tagName === 'string' && (image.tagName === 'IMG' || image.tagName === 'IMAGE')) {
if (image._imageSource instanceof ImageSource && !NAPI_HOST) {
Expand Down Expand Up @@ -1032,7 +1033,7 @@ export class CanvasRenderingContext2D implements CanvasRenderingContext {
isNativeSource = true;
} else if (__APPLE__ && image instanceof UIImage) {
isNativeSource = true;
} else if (image instanceof Canvas) {
} else if ((image instanceof Canvas || isOffscreenCanvas(image))) {
if (__APPLE__) {
image = _canvasToImageAssetIOS(image as Canvas);
} else {
Expand Down
3 changes: 2 additions & 1 deletion packages/canvas/ImageBitmap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ImageSource } from '@nativescript/core';
import { Helpers } from '../helpers';
import { NAPI_HOST } from '../platform';
import { imageAssetFor } from '../platform/index';
import { isOffscreenCanvas } from '../Canvas/utils';

function parseOptions(options) {
if (__ANDROID__) {
Expand Down Expand Up @@ -61,7 +62,7 @@ function invalidSource(source: any) {
/** `undefined` for an unrecognised source, so the caller rejects instead of hanging. */
function resolveSource(source: any): any {
source = fromSvgSource(source);
if (source instanceof Canvas) {
if ((source instanceof Canvas || isOffscreenCanvas(source))) {
return (source as any).native;
}
if (source instanceof ImageBitmap) {
Expand Down
Loading
Loading