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
4 changes: 4 additions & 0 deletions src/core/CoreTextureManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,10 @@ export class CoreTextureManager extends EventEmitter {

public platform: Platform;

get pixelRatio(): number {
return this.stage.pixelRatio;
}

imageWorkerManager: ImageWorkerManager | null = null;
hasCreateImageBitmap = false;
imageBitmapSupported = {
Expand Down
88 changes: 60 additions & 28 deletions src/core/lib/textureSvg.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { assertTruthy } from '../../utils.js';
import { type TextureData } from '../textures/Texture.js';
import { isBase64Image } from './utils.js';

/**
* Tests if the given location is a SVG
Expand All @@ -14,46 +15,77 @@ export function isSvgImage(url: string): boolean {
}

/**
* Loads a SVG image
* @param url
* @returns
* Loads a SVG image and rasterizes it for use as a texture.
*
* @remarks
* Rasterizes at `pixelRatio` to keep the texture sharp on HiDPI / 4K displays.
* `width`/`height` are interpreted as the logical (CSS-pixel) target size; the
* backing canvas is allocated at `width * pixelRatio` × `height * pixelRatio`.
*
* When `sw`/`sh` are provided they describe a source-region crop on the SVG
* (not a crop of the destination canvas) and are sampled via the 9-arg form of
* drawImage.
*
* Returns an `ImageBitmap` when available (zero CPU readback, transferable),
* falling back to `ImageData` on older browsers without `createImageBitmap`.
*/
export const loadSvg = (
export const loadSvg = async (
url: string,
width: number | null,
height: number | null,
sx: number | null,
sy: number | null,
sw: number | null,
sh: number | null,
pixelRatio: number,
): Promise<TextureData> => {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
assertTruthy(ctx);

ctx.imageSmoothingEnabled = true;
const img = new Image();
img.onload = () => {
const x = sx ?? 0;
const y = sy ?? 0;
const w = width || img.width;
const h = height || img.height;

canvas.width = w;
canvas.height = h;
ctx.drawImage(img, 0, 0, w, h);

resolve({
data: ctx.getImageData(x, y, sw ?? w, sh ?? h),
premultiplyAlpha: false,
});
};
const img = new Image();
if (isBase64Image(url) === false) {
img.crossOrigin = 'anonymous';
}

await new Promise<void>((resolve, reject) => {
img.onload = () => resolve();
img.onerror = (err) => {
reject(err);
reject(
err instanceof Error ? err : new Error(`SVG loading failed: ${url}`),
);
};

img.src = url;
});

const targetW = width || img.naturalWidth || img.width;
const targetH = height || img.naturalHeight || img.height;
const ratio = pixelRatio > 0 ? pixelRatio : 1;
const physW = Math.max(1, Math.ceil(targetW * ratio));
const physH = Math.max(1, Math.ceil(targetH * ratio));

const canvas = document.createElement('canvas');
canvas.width = physW;
canvas.height = physH;
const ctx = canvas.getContext('2d');
assertTruthy(ctx);

if (sw !== null && sh !== null) {
ctx.drawImage(img, sx ?? 0, sy ?? 0, sw, sh, 0, 0, physW, physH);
} else {
ctx.drawImage(img, 0, 0, physW, physH);
}

if (typeof createImageBitmap === 'function') {
try {
const bitmap = await createImageBitmap(canvas);
return {
data: bitmap,
premultiplyAlpha: false,
};
} catch {
// fall through to ImageData
}
}

return {
data: ctx.getImageData(0, 0, physW, physH),
premultiplyAlpha: false,
};
};
15 changes: 2 additions & 13 deletions src/core/textures/ImageTexture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,19 +314,7 @@ export class ImageTexture extends Texture {
return this.loadImage(absoluteSrc);
}

if (type === 'svg') {
return loadSvg(
absoluteSrc,
this.props.w,
this.props.h,
this.props.sx,
this.props.sy,
this.props.sw,
this.props.sh,
);
}

if (isSvgImage(src) === true) {
if (type === 'svg' || isSvgImage(src) === true) {
return loadSvg(
absoluteSrc,
this.props.w,
Expand All @@ -335,6 +323,7 @@ export class ImageTexture extends Texture {
this.props.sy,
this.props.sw,
this.props.sh,
this.txManager.pixelRatio,
);
}

Expand Down
Loading