fix: export scale quality regression (#4316)

This commit is contained in:
David Luzar 2021-11-25 14:05:22 +01:00 committed by GitHub
parent f9d2d537a2
commit 8ff159e76e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 179 additions and 172 deletions

View file

@ -22,7 +22,7 @@ import { RoughCanvas } from "roughjs/bin/canvas";
import { Drawable, Options } from "roughjs/bin/core";
import { RoughSVG } from "roughjs/bin/svg";
import { RoughGenerator } from "roughjs/bin/generator";
import { SceneState } from "../scene/types";
import { RenderConfig } from "../scene/types";
import { distance, getFontString, getFontFamilyString, isRTL } from "../utils";
import { isPathALoop } from "../math";
import rough from "roughjs/bin/rough";
@ -41,10 +41,22 @@ const defaultAppState = getDefaultAppState();
const isPendingImageElement = (
element: ExcalidrawElement,
sceneState: SceneState,
renderConfig: RenderConfig,
) =>
isInitializedImageElement(element) &&
!sceneState.imageCache.has(element.fileId);
!renderConfig.imageCache.has(element.fileId);
const shouldResetImageFilter = (
element: ExcalidrawElement,
renderConfig: RenderConfig,
) => {
return (
renderConfig.theme === "dark" &&
isInitializedImageElement(element) &&
!isPendingImageElement(element, renderConfig) &&
renderConfig.imageCache.get(element.fileId)?.mimeType !== MIME_TYPES.svg
);
};
const getDashArrayDashed = (strokeWidth: number) => [8, 8 + strokeWidth];
@ -56,7 +68,7 @@ const getCanvasPadding = (element: ExcalidrawElement) =>
export interface ExcalidrawElementWithCanvas {
element: ExcalidrawElement | ExcalidrawTextElement;
canvas: HTMLCanvasElement;
theme: SceneState["theme"];
theme: RenderConfig["theme"];
canvasZoom: Zoom["value"];
canvasOffsetX: number;
canvasOffsetY: number;
@ -65,7 +77,7 @@ export interface ExcalidrawElementWithCanvas {
const generateElementCanvas = (
element: NonDeletedExcalidrawElement,
zoom: Zoom,
sceneState: SceneState,
renderConfig: RenderConfig,
): ExcalidrawElementWithCanvas => {
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d")!;
@ -123,22 +135,17 @@ const generateElementCanvas = (
const rc = rough.canvas(canvas);
// in dark theme, revert the image color filter
if (
sceneState.theme === "dark" &&
isInitializedImageElement(element) &&
!isPendingImageElement(element, sceneState) &&
sceneState.imageCache.get(element.fileId)?.mimeType !== MIME_TYPES.svg
) {
if (shouldResetImageFilter(element, renderConfig)) {
context.filter = IMAGE_INVERT_FILTER;
}
drawElementOnCanvas(element, rc, context, sceneState);
drawElementOnCanvas(element, rc, context, renderConfig);
context.restore();
return {
element,
canvas,
theme: sceneState.theme,
theme: renderConfig.theme,
canvasZoom: zoom.value,
canvasOffsetX,
canvasOffsetY,
@ -185,7 +192,7 @@ const drawElementOnCanvas = (
element: NonDeletedExcalidrawElement,
rc: RoughCanvas,
context: CanvasRenderingContext2D,
sceneState: SceneState,
renderConfig: RenderConfig,
) => {
context.globalAlpha = element.opacity / 100;
switch (element.type) {
@ -222,7 +229,7 @@ const drawElementOnCanvas = (
}
case "image": {
const img = isInitializedImageElement(element)
? sceneState.imageCache.get(element.fileId)?.image
? renderConfig.imageCache.get(element.fileId)?.image
: undefined;
if (img != null && !(img instanceof Promise)) {
context.drawImage(
@ -233,7 +240,7 @@ const drawElementOnCanvas = (
element.height,
);
} else {
drawImagePlaceholder(element, context, sceneState.zoom.value);
drawImagePlaceholder(element, context, renderConfig.zoom.value);
}
break;
}
@ -566,21 +573,25 @@ const generateElementShape = (
const generateElementWithCanvas = (
element: NonDeletedExcalidrawElement,
sceneState: SceneState,
renderConfig: RenderConfig,
) => {
const zoom: Zoom = sceneState ? sceneState.zoom : defaultAppState.zoom;
const zoom: Zoom = renderConfig ? renderConfig.zoom : defaultAppState.zoom;
const prevElementWithCanvas = elementWithCanvasCache.get(element);
const shouldRegenerateBecauseZoom =
prevElementWithCanvas &&
prevElementWithCanvas.canvasZoom !== zoom.value &&
!sceneState?.shouldCacheIgnoreZoom;
!renderConfig?.shouldCacheIgnoreZoom;
if (
!prevElementWithCanvas ||
shouldRegenerateBecauseZoom ||
prevElementWithCanvas.theme !== sceneState.theme
prevElementWithCanvas.theme !== renderConfig.theme
) {
const elementWithCanvas = generateElementCanvas(element, zoom, sceneState);
const elementWithCanvas = generateElementCanvas(
element,
zoom,
renderConfig,
);
elementWithCanvasCache.set(element, elementWithCanvas);
@ -593,7 +604,7 @@ const drawElementFromCanvas = (
elementWithCanvas: ExcalidrawElementWithCanvas,
rc: RoughCanvas,
context: CanvasRenderingContext2D,
sceneState: SceneState,
renderConfig: RenderConfig,
) => {
const element = elementWithCanvas.element;
const padding = getCanvasPadding(element);
@ -607,10 +618,10 @@ const drawElementFromCanvas = (
y2 = Math.ceil(y2);
}
const cx = ((x1 + x2) / 2 + sceneState.scrollX) * window.devicePixelRatio;
const cy = ((y1 + y2) / 2 + sceneState.scrollY) * window.devicePixelRatio;
const cx = ((x1 + x2) / 2 + renderConfig.scrollX) * window.devicePixelRatio;
const cy = ((y1 + y2) / 2 + renderConfig.scrollY) * window.devicePixelRatio;
const _isPendingImageElement = isPendingImageElement(element, sceneState);
const _isPendingImageElement = isPendingImageElement(element, renderConfig);
const scaleXFactor =
"scale" in elementWithCanvas.element && !_isPendingImageElement
@ -647,16 +658,15 @@ export const renderElement = (
element: NonDeletedExcalidrawElement,
rc: RoughCanvas,
context: CanvasRenderingContext2D,
renderOptimizations: boolean,
sceneState: SceneState,
renderConfig: RenderConfig,
) => {
const generator = rc.generator;
switch (element.type) {
case "selection": {
context.save();
context.translate(
element.x + sceneState.scrollX,
element.y + sceneState.scrollY,
element.x + renderConfig.scrollX,
element.y + renderConfig.scrollY,
);
context.fillStyle = "rgba(0, 0, 255, 0.10)";
context.fillRect(0, 0, element.width, element.height);
@ -666,23 +676,23 @@ export const renderElement = (
case "freedraw": {
generateElementShape(element, generator);
if (renderOptimizations) {
if (renderConfig.isExporting) {
const elementWithCanvas = generateElementWithCanvas(
element,
sceneState,
renderConfig,
);
drawElementFromCanvas(elementWithCanvas, rc, context, sceneState);
drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig);
} else {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2 + sceneState.scrollX;
const cy = (y1 + y2) / 2 + sceneState.scrollY;
const cx = (x1 + x2) / 2 + renderConfig.scrollX;
const cy = (y1 + y2) / 2 + renderConfig.scrollY;
const shiftX = (x2 - x1) / 2 - (element.x - x1);
const shiftY = (y2 - y1) / 2 - (element.y - y1);
context.save();
context.translate(cx, cy);
context.rotate(element.angle);
context.translate(-shiftX, -shiftY);
drawElementOnCanvas(element, rc, context, sceneState);
drawElementOnCanvas(element, rc, context, renderConfig);
context.restore();
}
@ -696,24 +706,31 @@ export const renderElement = (
case "image":
case "text": {
generateElementShape(element, generator);
if (renderOptimizations) {
const elementWithCanvas = generateElementWithCanvas(
element,
sceneState,
);
drawElementFromCanvas(elementWithCanvas, rc, context, sceneState);
} else {
if (renderConfig.isExporting) {
const [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
const cx = (x1 + x2) / 2 + sceneState.scrollX;
const cy = (y1 + y2) / 2 + sceneState.scrollY;
const cx = (x1 + x2) / 2 + renderConfig.scrollX;
const cy = (y1 + y2) / 2 + renderConfig.scrollY;
const shiftX = (x2 - x1) / 2 - (element.x - x1);
const shiftY = (y2 - y1) / 2 - (element.y - y1);
context.save();
context.translate(cx, cy);
context.rotate(element.angle);
context.translate(-shiftX, -shiftY);
drawElementOnCanvas(element, rc, context, sceneState);
if (shouldResetImageFilter(element, renderConfig)) {
context.filter = "none";
}
drawElementOnCanvas(element, rc, context, renderConfig);
context.restore();
// not exporting → optimized rendering (cache & render from element
// canvases)
} else {
const elementWithCanvas = generateElementWithCanvas(
element,
renderConfig,
);
drawElementFromCanvas(elementWithCanvas, rc, context, renderConfig);
}
break;
}