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