feat: separate fancyBackground from renderScene

This commit is contained in:
Arnošt Pleskot 2023-08-10 17:40:29 +02:00
parent 3c56efb9fe
commit d6515e26b9
No known key found for this signature in database
6 changed files with 174 additions and 15 deletions

View File

@ -72,7 +72,7 @@ export const getDefaultAppState = (): Omit<
openMenu: null,
openPopup: null,
openSidebar: null,
openDialog: null,
openDialog: "imageExport",
pasteDialog: { shown: false, data: null },
previousSelectedElementIds: {},
resizingElement: null,
@ -101,7 +101,7 @@ export const getDefaultAppState = (): Omit<
pendingImageElementId: null,
showHyperlinkPopup: false,
selectedLinearElement: null,
exportBackgroundImage:
fancyBackgroundImageUrl:
EXPORT_BACKGROUND_IMAGES[DEFAULT_EXPORT_BACKGROUND_IMAGE].path,
};
};

View File

@ -138,7 +138,7 @@ const ImageExportModal = ({
}, [
appState,
appState.exportBackground,
appState.exportBackgroundImage,
appState.fancyBackgroundImageUrl,
files,
exportedElements,
]);
@ -150,7 +150,7 @@ const ImageExportModal = ({
<div
className={clsx("ImageExportModal__preview__canvas", {
"ImageExportModal__preview__canvas--img-bcg":
appState.exportBackground && appState.exportBackgroundImage,
appState.exportBackground && appState.fancyBackgroundImageUrl,
})}
ref={previewRef}
>
@ -159,8 +159,8 @@ const ImageExportModal = ({
</div>
<div className="ImageExportModal__settings">
<h3>{t("imageExportDialog.header")}</h3>
<div className="ImageExportModal__settings__filename">
{!nativeFileSystemSupported && (
{!nativeFileSystemSupported && (
<div className="ImageExportModal__settings__filename">
<input
type="text"
className="TextInput"
@ -177,8 +177,8 @@ const ImageExportModal = ({
);
}}
/>
)}
</div>
</div>
)}
{someElementIsSelected && (
<ExportSetting
label={t("imageExportDialog.label.onlySelected")}

View File

@ -1,5 +1,5 @@
import cssVariables from "./css/variables.module.scss";
import { AppProps } from "./types";
import { AppProps, DataURL } from "./types";
import { ExcalidrawElement, FontFamilyValues } from "./element/types";
import { COLOR_PALETTE } from "./colors";
@ -321,11 +321,14 @@ export const LIBRARY_DISABLED_TYPES = new Set(["embeddable", "image"] as const);
export const EXPORT_BACKGROUND_IMAGES = {
solid: { path: null, label: "solid color" },
bubbles: { path: "/backgrounds/bubbles.svg", label: "bubbles" },
bubbles2: { path: "/backgrounds/bubbles2.svg", label: "bubbles 2" },
bricks: { path: "/backgrounds/bricks.svg", label: "bricks" },
lines: { path: "/backgrounds/lines.svg", label: "lines" },
lines2: { path: "/backgrounds/lines2.svg", label: "lines 2" },
bubbles: { path: "/backgrounds/bubbles.svg" as DataURL, label: "bubbles" },
bubbles2: {
path: "/backgrounds/bubbles2.svg" as DataURL,
label: "bubbles 2",
},
bricks: { path: "/backgrounds/bricks.svg" as DataURL, label: "bricks" },
lines: { path: "/backgrounds/lines.svg" as DataURL, label: "lines" },
lines2: { path: "/backgrounds/lines2.svg" as DataURL, label: "lines 2" },
} as const;
export const DEFAULT_EXPORT_BACKGROUND_IMAGE: keyof typeof EXPORT_BACKGROUND_IMAGES =

View File

@ -12,6 +12,7 @@ import {
updateImageCache,
} from "../element/image";
import Scene from "./Scene";
import { applyFancyBackground } from "./fancyBackground";
export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
@ -54,6 +55,14 @@ export const exportToCanvas = async (
const onlyExportingSingleFrame = isOnlyExportingSingleFrame(elements);
if (appState.fancyBackgroundImageUrl) {
await applyFancyBackground(
canvas,
appState.fancyBackgroundImageUrl,
viewBackgroundColor,
);
}
renderStaticScene({
canvas,
rc: rough.canvas(canvas),

View File

@ -0,0 +1,147 @@
import { EXPORT_BG_BORDER_RADIUS, EXPORT_BG_PADDING } from "../constants";
import { loadHTMLImageElement } from "../element/image";
import { roundRect } from "../renderer/roundRect";
import { DataURL } from "../types";
type Dimensions = { w: number; h: number };
const getScaleToFill = (contentSize: Dimensions, containerSize: Dimensions) => {
const scale = Math.max(
containerSize.w / contentSize.w,
containerSize.h / contentSize.h,
);
return scale;
};
const getScaleToFit = (contentSize: Dimensions, containerSize: Dimensions) => {
const scale = Math.min(
containerSize.w / contentSize.w,
containerSize.h / contentSize.h,
);
return scale;
};
const addImageBackground = (
context: CanvasRenderingContext2D,
canvasWidth: number,
canvasHeight: number,
fancyBackgroundImage: HTMLImageElement,
) => {
context.save();
context.beginPath();
if (context.roundRect) {
context.roundRect(0, 0, canvasWidth, canvasHeight, EXPORT_BG_BORDER_RADIUS);
} else {
roundRect(
context,
0,
0,
canvasWidth,
canvasHeight,
EXPORT_BG_BORDER_RADIUS,
);
}
const scale = getScaleToFill(
{ w: fancyBackgroundImage.width, h: fancyBackgroundImage.height },
{ w: canvasWidth, h: canvasHeight },
);
const x = (canvasWidth - fancyBackgroundImage.width * scale) / 2;
const y = (canvasHeight - fancyBackgroundImage.height * scale) / 2;
context.clip();
context.drawImage(
fancyBackgroundImage,
x,
y,
fancyBackgroundImage.width * scale,
fancyBackgroundImage.height * scale,
);
context.closePath();
context.restore();
};
const addContentBackground = (
context: CanvasRenderingContext2D,
canvasWidth: number,
canvasHeight: number,
contentBackgroundColor: string,
) => {
const shadows = [
{
offsetX: 0,
offsetY: 0.7698959708213806,
blur: 1.4945039749145508,
alpha: 0.02,
},
{
offsetX: 0,
offsetY: 1.1299999952316284,
blur: 4.1321120262146,
alpha: 0.04,
},
{
offsetX: 0,
offsetY: 4.130000114440918,
blur: 9.94853401184082,
alpha: 0.05,
},
{ offsetX: 0, offsetY: 13, blur: 33, alpha: 0.07 },
];
shadows.forEach((shadow, index): void => {
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 (context.roundRect) {
context.roundRect(
EXPORT_BG_PADDING,
EXPORT_BG_PADDING,
canvasWidth - EXPORT_BG_PADDING * 2,
canvasHeight - EXPORT_BG_PADDING * 2,
EXPORT_BG_BORDER_RADIUS,
);
} else {
roundRect(
context,
EXPORT_BG_PADDING,
EXPORT_BG_PADDING,
canvasWidth - EXPORT_BG_PADDING * 2,
canvasHeight - EXPORT_BG_PADDING * 2,
EXPORT_BG_BORDER_RADIUS,
);
}
if (index === shadows.length - 1) {
context.fillStyle = contentBackgroundColor;
context.fill();
}
context.closePath();
context.restore();
});
};
export const applyFancyBackground = async (
canvas: HTMLCanvasElement,
fancyBackgroundImageUrl: DataURL,
backgroundColor: string,
) => {
const context = canvas.getContext("2d")!;
const fancyBackgroundImage = await loadHTMLImageElement(
fancyBackgroundImageUrl,
);
addImageBackground(
context,
canvas.width,
canvas.height,
fancyBackgroundImage,
);
addContentBackground(context, canvas.width, canvas.height, backgroundColor);
};

View File

@ -287,7 +287,7 @@ export type AppState = {
pendingImageElementId: ExcalidrawImageElement["id"] | null;
showHyperlinkPopup: false | "info" | "editor";
selectedLinearElement: LinearElementEditor | null;
exportBackgroundImage: string | null;
fancyBackgroundImageUrl: DataURL | null;
};
export type UIAppState = Omit<