feat: render scene after loading bcg image in export

This commit is contained in:
Arnošt Pleskot 2023-08-02 16:39:38 +02:00
parent fe17d88b74
commit e6ae8177ab
No known key found for this signature in database

View File

@ -22,7 +22,7 @@ import {
} from "../element";
import { roundRect } from "./roundRect";
import { RenderConfig } from "../scene/types";
import { RenderConfig, ScrollBars } from "../scene/types";
import {
getScrollBars,
SCROLLBAR_COLOR,
@ -386,7 +386,7 @@ const frameClip = (
};
const addExportBackground = (
canvas: HTMLCanvasElement,
context: CanvasRenderingContext2D,
normalizedCanvasWidth: number,
normalizedCanvasHeight: number,
svgUrl: string,
@ -396,18 +396,16 @@ const addExportBackground = (
const MARGIN = 24;
const BORDER_RADIUS = 12;
const ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
// Create a new image object
const img = new Image();
// When the image has loaded
img.onload = (): void => {
// Scale image to fill canvas and draw it onto the canvas
ctx.save();
ctx.beginPath();
if (ctx.roundRect) {
ctx.roundRect(
context.save();
context.beginPath();
if (context.roundRect) {
context.roundRect(
0,
0,
normalizedCanvasWidth,
@ -416,7 +414,7 @@ const addExportBackground = (
);
} else {
roundRect(
ctx,
context,
0,
0,
normalizedCanvasWidth,
@ -430,10 +428,10 @@ const addExportBackground = (
);
const x = (normalizedCanvasWidth - img.width * scale) / 2;
const y = (normalizedCanvasHeight - img.height * scale) / 2;
ctx.clip();
ctx.drawImage(img, x, y, img.width * scale, img.height * scale);
ctx.closePath();
ctx.restore();
context.clip();
context.drawImage(img, x, y, img.width * scale, img.height * scale);
context.closePath();
context.restore();
// Create shadow similar to the CSS box-shadow
const shadows = [
@ -459,15 +457,15 @@ const addExportBackground = (
];
shadows.forEach((shadow, index): void => {
ctx.save();
ctx.beginPath();
ctx.shadowColor = `rgba(0, 0, 0, ${shadow.alpha})`;
ctx.shadowBlur = shadow.blur;
ctx.shadowOffsetX = shadow.offsetX;
ctx.shadowOffsetY = shadow.offsetY;
context.save();
context.beginPath();
context.shadowColor = `rgba(0, 0, 0, ${shadow.alpha})`;
context.shadowBlur = shadow.blur;
context.shadowOffsetX = shadow.offsetX;
context.shadowOffsetY = shadow.offsetY;
if (ctx.roundRect) {
ctx.roundRect(
if (context.roundRect) {
context.roundRect(
MARGIN,
MARGIN,
normalizedCanvasWidth - MARGIN * 2,
@ -476,7 +474,7 @@ const addExportBackground = (
);
} else {
roundRect(
ctx,
context,
MARGIN,
MARGIN,
normalizedCanvasWidth - MARGIN * 2,
@ -486,18 +484,18 @@ const addExportBackground = (
}
if (index === shadows.length - 1) {
ctx.fillStyle = rectangleColor;
ctx.fill();
context.fillStyle = rectangleColor;
context.fill();
}
ctx.closePath();
ctx.restore();
context.closePath();
context.restore();
});
// Reset shadow properties for future drawings
ctx.shadowColor = "transparent";
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
context.shadowColor = "transparent";
context.shadowBlur = 0;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
resolve();
};
@ -511,6 +509,52 @@ const addExportBackground = (
});
};
export const paintBackground = async (
context: CanvasRenderingContext2D,
normalizedCanvasWidth: number,
normalizedCanvasHeight: number,
{
viewBackgroundColor,
isExporting,
exportBackgroundImage,
}: Pick<
RenderConfig,
"viewBackgroundColor" | "isExporting" | "exportBackgroundImage"
>,
): Promise<void> => {
if (typeof viewBackgroundColor === "string") {
const hasTransparence =
viewBackgroundColor === "transparent" ||
viewBackgroundColor.length === 5 || // #RGBA
viewBackgroundColor.length === 9 || // #RRGGBBA
/(hsla|rgba)\(/.test(viewBackgroundColor);
if (hasTransparence) {
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
context.save();
if (isExporting && exportBackgroundImage) {
try {
await addExportBackground(
context,
normalizedCanvasWidth,
normalizedCanvasHeight,
exportBackgroundImage,
viewBackgroundColor,
);
} catch (error) {
console.error("Failed to add background:", error);
}
} else {
context.fillStyle = viewBackgroundColor;
context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
context.restore();
} else {
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
};
export const _renderScene = ({
elements,
appState,
@ -536,6 +580,7 @@ export const _renderScene = ({
renderSelection = true,
renderGrid = true,
isExporting,
viewBackgroundColor,
exportBackgroundImage,
} = renderConfig;
@ -554,41 +599,19 @@ export const _renderScene = ({
context.filter = THEME_FILTER;
}
// Paint background
if (typeof renderConfig.viewBackgroundColor === "string") {
const hasTransparence =
renderConfig.viewBackgroundColor === "transparent" ||
renderConfig.viewBackgroundColor.length === 5 || // #RGBA
renderConfig.viewBackgroundColor.length === 9 || // #RRGGBBA
/(hsla|rgba)\(/.test(renderConfig.viewBackgroundColor);
if (hasTransparence) {
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
context.save();
if (isExporting && exportBackgroundImage) {
(async () => {
try {
await addExportBackground(
canvas,
normalizedCanvasWidth,
normalizedCanvasHeight,
let renderOutput: {
atLeastOneVisibleElement: boolean;
scrollBars: ScrollBars | undefined;
} = {
atLeastOneVisibleElement: false,
scrollBars: undefined,
};
paintBackground(context, normalizedCanvasWidth, normalizedCanvasHeight, {
isExporting,
viewBackgroundColor,
exportBackgroundImage,
renderConfig.viewBackgroundColor!,
);
} catch (error) {
console.error("Failed to add background:", error);
}
})();
} else {
context.fillStyle = renderConfig.viewBackgroundColor;
context.fillRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
context.restore();
} else {
context.clearRect(0, 0, normalizedCanvasWidth, normalizedCanvasHeight);
}
}).then(() => {
// Apply zoom
context.save();
context.scale(renderConfig.zoom.value, renderConfig.zoom.value);
@ -608,13 +631,18 @@ export const _renderScene = ({
// Paint visible elements
const visibleElements = elements.filter((element) =>
isVisibleElement(element, normalizedCanvasWidth, normalizedCanvasHeight, {
isVisibleElement(
element,
normalizedCanvasWidth,
normalizedCanvasHeight,
{
zoom: renderConfig.zoom,
offsetLeft: appState.offsetLeft,
offsetTop: appState.offsetTop,
scrollX: renderConfig.scrollX,
scrollY: renderConfig.scrollY,
}),
},
),
);
const groupsToBeAddedToFrame = new Set<string>();
@ -635,8 +663,9 @@ export const _renderScene = ({
}
});
let editingLinearElement: NonDeleted<ExcalidrawLinearElement> | undefined =
undefined;
let editingLinearElement:
| NonDeleted<ExcalidrawLinearElement>
| undefined = undefined;
visibleElements
.filter((el) => !isEmbeddableOrFrameLabel(el))
@ -790,7 +819,8 @@ export const _renderScene = ({
// correct element from visible elements
if (
locallySelectedElements.length === 1 &&
appState.editingLinearElement?.elementId === locallySelectedElements[0].id
appState.editingLinearElement?.elementId ===
locallySelectedElements[0].id
) {
renderLinearPointHandles(
context,
@ -1139,7 +1169,13 @@ export const _renderScene = ({
context.restore();
return { atLeastOneVisibleElement: visibleElements.length > 0, scrollBars };
renderOutput = {
atLeastOneVisibleElement: visibleElements.length > 0,
scrollBars,
};
});
return renderOutput;
};
const renderSceneThrottled = throttleRAF(