refboard: add native image copy and export renderer

This commit is contained in:
Hiren Kangad
2026-03-13 15:48:12 +05:30
parent ae9b1ecefe
commit 7b2fcfd6a8
3 changed files with 269 additions and 174 deletions
+10 -43
View File
@@ -9,8 +9,12 @@
import type { Viewport } from 'pixi-viewport';
import type { Application } from 'pixi.js';
import type { SceneManager, SceneItem } from './SceneManager';
import { getItemWorldBounds } from './SceneManager';
import { uploadImage } from '../api';
import {
renderCanvasSnapshot,
renderNativeImageSelection,
supportsNativeImageExport,
} from './snapshotRenderer';
/**
* Write selected items (or full viewport) to system clipboard as PNG.
@@ -24,53 +28,16 @@ export async function writeCanvasToClipboard(
if (!viewport) throw new Error('Viewport not available');
if (!app) throw new Error('Renderer not available');
const directCanvas = app.canvas as unknown as HTMLCanvasElement | undefined;
const sourceCanvas = directCanvas
?? (app.renderer?.extract
? (app.renderer.extract.canvas(viewport) as HTMLCanvasElement)
: undefined);
if (!sourceCanvas || sourceCanvas.width <= 0 || sourceCanvas.height <= 0) {
throw new Error('Rendered canvas unavailable');
}
let outputCanvas: HTMLCanvasElement;
if (items && items.length > 0) {
// Compute world-space bounding box, then convert to visible screen-space crop.
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
for (const item of items) {
const { x, y, w, h } = getItemWorldBounds(item);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + w);
maxY = Math.max(maxY, y + h);
if (supportsNativeImageExport(items)) {
outputCanvas = await renderNativeImageSelection(items, { background: null, paddingPx: 10 });
} else {
outputCanvas = renderCanvasSnapshot(app, viewport, items, { background: null, paddingPx: 12 });
}
const screenTL = viewport.toScreen(minX, minY);
const screenBR = viewport.toScreen(maxX, maxY);
const rect = sourceCanvas.getBoundingClientRect();
const scaleX = rect.width > 0 ? sourceCanvas.width / rect.width : 1;
const scaleY = rect.height > 0 ? sourceCanvas.height / rect.height : 1;
const pad = 12;
const sx = Math.max(0, Math.floor(Math.min(screenTL.x, screenBR.x) * scaleX) - pad);
const sy = Math.max(0, Math.floor(Math.min(screenTL.y, screenBR.y) * scaleY) - pad);
const sw = Math.max(1, Math.min(sourceCanvas.width - sx, Math.ceil(Math.abs(screenBR.x - screenTL.x) * scaleX) + pad * 2));
const sh = Math.max(1, Math.min(sourceCanvas.height - sy, Math.ceil(Math.abs(screenBR.y - screenTL.y) * scaleY) + pad * 2));
outputCanvas = document.createElement('canvas');
outputCanvas.width = sw;
outputCanvas.height = sh;
const ctx2d = outputCanvas.getContext('2d')!;
ctx2d.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
ctx2d.drawImage(sourceCanvas, sx, sy, sw, sh, 0, 0, sw, sh);
} else {
// Full current view copy.
outputCanvas = document.createElement('canvas');
outputCanvas.width = sourceCanvas.width;
outputCanvas.height = sourceCanvas.height;
const ctx2d = outputCanvas.getContext('2d')!;
ctx2d.clearRect(0, 0, outputCanvas.width, outputCanvas.height);
ctx2d.drawImage(sourceCanvas, 0, 0);
outputCanvas = renderCanvasSnapshot(app, viewport, undefined, { background: null });
}
const blob = await new Promise<Blob>((resolve, reject) => {