mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 10:58:04 +08:00
Render artwork at correct resolution when using vello on wasm (#3416)
* Work on fixing rendering for wasm+vello * Render vello canvas in wasm at the correct resolution * Cleanup unused surface rendering code * Remove vector to raster conversion * Remove desktop changes * Revert window.rs changes * Don't round logical coordinates * Fix desktop compilation + don't round logical coordinates for svg rendering * Further cleanup * Compute logical size from acutal physical sizes
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { getContext, onMount, tick } from "svelte";
|
||||
import { getContext, onMount, onDestroy, tick } from "svelte";
|
||||
|
||||
import type { Editor } from "@graphite/editor";
|
||||
import {
|
||||
@@ -20,7 +20,7 @@
|
||||
import type { DocumentState } from "@graphite/state-providers/document";
|
||||
import { textInputCleanup } from "@graphite/utility-functions/keyboard-entry";
|
||||
import { extractPixelData, rasterizeSVGCanvas } from "@graphite/utility-functions/rasterization";
|
||||
import { updateBoundsOfViewports as updateViewport } from "@graphite/utility-functions/viewports";
|
||||
import { setupViewportResizeObserver, cleanupViewportResizeObserver } from "@graphite/utility-functions/viewports";
|
||||
|
||||
import EyedropperPreview, { ZOOM_WINDOW_DIMENSIONS } from "@graphite/components/floating-menus/EyedropperPreview.svelte";
|
||||
import LayoutCol from "@graphite/components/layout/LayoutCol.svelte";
|
||||
@@ -203,9 +203,18 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let canvas = (window as any).imageCanvases[canvasName];
|
||||
|
||||
if (canvasName !== "0" && canvas.parentElement) {
|
||||
var newCanvas = window.document.createElement("canvas");
|
||||
var context = newCanvas.getContext("2d");
|
||||
// Get logical dimensions from foreignObject parent (set by backend)
|
||||
const foreignObject = placeholder.parentElement;
|
||||
if (!foreignObject) return;
|
||||
const logicalWidth = parseFloat(foreignObject.getAttribute("width") || "0");
|
||||
const logicalHeight = parseFloat(foreignObject.getAttribute("height") || "0");
|
||||
|
||||
// Clone canvas for repeated instances (layers that appear multiple times)
|
||||
// Viewport canvas is marked with data-is-viewport and should never be cloned
|
||||
const isViewport = placeholder.hasAttribute("data-is-viewport");
|
||||
if (!isViewport && canvas.parentElement) {
|
||||
const newCanvas = window.document.createElement("canvas");
|
||||
const context = newCanvas.getContext("2d");
|
||||
|
||||
newCanvas.width = canvas.width;
|
||||
newCanvas.height = canvas.height;
|
||||
@@ -215,6 +224,10 @@
|
||||
canvas = newCanvas;
|
||||
}
|
||||
|
||||
// Set CSS size to logical resolution (for correct display size)
|
||||
canvas.style.width = `${logicalWidth}px`;
|
||||
canvas.style.height = `${logicalHeight}px`;
|
||||
|
||||
placeholder.replaceWith(canvas);
|
||||
});
|
||||
}
|
||||
@@ -393,8 +406,8 @@
|
||||
rulerHorizontal?.resize();
|
||||
rulerVertical?.resize();
|
||||
|
||||
// Send the new bounds of the viewports to the backend
|
||||
if (viewport.parentElement) updateViewport(editor);
|
||||
// Note: Viewport bounds are now sent to the backend by the ResizeObserver in viewports.ts
|
||||
// which provides pixel-perfect physical dimensions via devicePixelContentBoxSize
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
@@ -473,14 +486,21 @@
|
||||
displayRemoveEditableTextbox();
|
||||
});
|
||||
|
||||
// Once this component is mounted, we want to resend the document bounds to the backend via the resize event handler which does that
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
// Setup ResizeObserver for pixel-perfect viewport tracking with physical dimensions
|
||||
// This must happen in onMount to ensure the viewport container element exists
|
||||
setupViewportResizeObserver(editor);
|
||||
|
||||
// Also observe the inner viewport for canvas sizing and ruler updates
|
||||
const viewportResizeObserver = new ResizeObserver(() => {
|
||||
updateViewportInfo();
|
||||
});
|
||||
if (viewport) viewportResizeObserver.observe(viewport);
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
// Cleanup the viewport resize observer
|
||||
cleanupViewportResizeObserver();
|
||||
});
|
||||
</script>
|
||||
|
||||
<LayoutCol class="document" on:dragover={(e) => e.preventDefault()} on:drop={dropFile}>
|
||||
|
||||
@@ -10,7 +10,6 @@ import { makeKeyboardModifiersBitfield, textInputCleanup, getLocalizedScanCode }
|
||||
import { operatingSystem } from "@graphite/utility-functions/platform";
|
||||
import { extractPixelData } from "@graphite/utility-functions/rasterization";
|
||||
import { stripIndents } from "@graphite/utility-functions/strip-indents";
|
||||
import { updateBoundsOfViewports } from "@graphite/utility-functions/viewports";
|
||||
|
||||
const BUTTON_LEFT = 0;
|
||||
const BUTTON_MIDDLE = 1;
|
||||
@@ -43,7 +42,6 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const listeners: { target: EventListenerTarget; eventName: EventName; action: (event: any) => void; options?: AddEventListenerOptions }[] = [
|
||||
{ target: window, eventName: "resize", action: () => updateBoundsOfViewports(editor) },
|
||||
{ target: window, eventName: "beforeunload", action: (e: BeforeUnloadEvent) => onBeforeUnload(e) },
|
||||
{ target: window, eventName: "keyup", action: (e: KeyboardEvent) => onKeyUp(e) },
|
||||
{ target: window, eventName: "keydown", action: (e: KeyboardEvent) => onKeyDown(e) },
|
||||
@@ -529,8 +527,6 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
|
||||
|
||||
// Bind the event listeners
|
||||
bindListeners();
|
||||
// Resize on creation
|
||||
updateBoundsOfViewports(editor);
|
||||
|
||||
// Return the destructor
|
||||
return unbindListeners;
|
||||
|
||||
@@ -1,12 +1,57 @@
|
||||
import { type Editor } from "@graphite/editor";
|
||||
|
||||
export function updateBoundsOfViewports(editor: Editor) {
|
||||
const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]"));
|
||||
let resizeObserver: ResizeObserver | undefined;
|
||||
|
||||
export function setupViewportResizeObserver(editor: Editor) {
|
||||
// Clean up existing observer if any
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
}
|
||||
|
||||
const viewports = Array.from(window.document.querySelectorAll("[data-viewport-container]"));
|
||||
if (viewports.length <= 0) return;
|
||||
|
||||
const bounds = viewports[0].getBoundingClientRect();
|
||||
const scale = window.devicePixelRatio || 1;
|
||||
const viewport = viewports[0] as HTMLElement;
|
||||
|
||||
editor.handle.updateViewport(bounds.x, bounds.y, bounds.width, bounds.height, scale);
|
||||
resizeObserver = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||
|
||||
// Get exact device pixel dimensions from the browser
|
||||
// Use devicePixelContentBoxSize for pixel-perfect rendering with fallback for Safari
|
||||
let physicalWidth: number;
|
||||
let physicalHeight: number;
|
||||
|
||||
if (entry.devicePixelContentBoxSize && entry.devicePixelContentBoxSize.length > 0) {
|
||||
// Modern browsers (Chrome, Firefox): get exact device pixels from the browser
|
||||
physicalWidth = entry.devicePixelContentBoxSize[0].inlineSize;
|
||||
physicalHeight = entry.devicePixelContentBoxSize[0].blockSize;
|
||||
} else {
|
||||
// Fallback for Safari: calculate from contentBoxSize and devicePixelRatio
|
||||
physicalWidth = entry.contentBoxSize[0].inlineSize * devicePixelRatio;
|
||||
physicalHeight = entry.contentBoxSize[0].blockSize * devicePixelRatio;
|
||||
}
|
||||
|
||||
// Compute the logical size which corresponds to the physical size
|
||||
const logicalWidth = physicalWidth / devicePixelRatio;
|
||||
const logicalHeight = physicalHeight / devicePixelRatio;
|
||||
|
||||
// Get viewport position
|
||||
const bounds = entry.target.getBoundingClientRect();
|
||||
|
||||
// TODO: Consider passing physical sizes as well to eliminate pixel inaccuracies since width and height could be rounded differently
|
||||
const scale = physicalWidth / logicalWidth;
|
||||
|
||||
editor.handle.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale);
|
||||
}
|
||||
});
|
||||
|
||||
resizeObserver.observe(viewport);
|
||||
}
|
||||
|
||||
export function cleanupViewportResizeObserver() {
|
||||
if (resizeObserver) {
|
||||
resizeObserver.disconnect();
|
||||
resizeObserver = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user