excalidraw/src/scene/fancyBackground.ts

242 lines
6.4 KiB
TypeScript

import {
DEFAULT_EXPORT_PADDING,
FANCY_BACKGROUND_IMAGES,
FANCY_BG_BORDER_RADIUS,
FANCY_BG_PADDING,
IMAGE_INVERT_FILTER,
SVG_NS,
THEME,
THEME_FILTER,
} from "../constants";
import { loadHTMLImageElement, loadSVGElement } from "../element/image";
import { getScaleToFill } from "../packages/utils";
import { roundRect } from "../renderer/roundRect";
import { AppState, Dimensions } from "../types";
export const getFancyBackgroundPadding = (
exportPadding = DEFAULT_EXPORT_PADDING,
) => FANCY_BG_PADDING + FANCY_BG_BORDER_RADIUS + exportPadding;
const addImageBackground = (
context: CanvasRenderingContext2D,
canvasDimensions: Dimensions,
fancyBackgroundImage: HTMLImageElement,
exportScale: AppState["exportScale"],
) => {
context.save();
context.beginPath();
if (context.roundRect) {
context.roundRect(
0,
0,
canvasDimensions.width,
canvasDimensions.height,
FANCY_BG_BORDER_RADIUS * exportScale,
);
} else {
roundRect(
context,
0,
0,
canvasDimensions.width,
canvasDimensions.height,
FANCY_BG_BORDER_RADIUS * exportScale,
);
}
const scale = getScaleToFill(
{ width: fancyBackgroundImage.width, height: fancyBackgroundImage.height },
{ width: canvasDimensions.width, height: canvasDimensions.height },
);
const x = (canvasDimensions.width - fancyBackgroundImage.width * scale) / 2;
const y = (canvasDimensions.height - 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,
normalizedDimensions: Dimensions,
contentBackgroundColor: string,
exportScale: AppState["exportScale"],
theme: AppState["theme"],
contentSize: Dimensions,
) => {
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 * exportScale;
context.shadowOffsetX = shadow.offsetX * exportScale;
context.shadowOffsetY = shadow.offsetY * exportScale;
const x =
(normalizedDimensions.width - contentSize.width) / 2 - FANCY_BG_PADDING;
const y =
(normalizedDimensions.height - contentSize.height) / 2 - FANCY_BG_PADDING;
if (context.roundRect) {
context.roundRect(
x,
y,
(contentSize.width +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
(contentSize.height * exportScale +
(DEFAULT_EXPORT_PADDING + FANCY_BG_BORDER_RADIUS) * 2) *
exportScale,
FANCY_BG_BORDER_RADIUS * exportScale,
);
} else {
roundRect(
context,
FANCY_BG_PADDING * exportScale,
FANCY_BG_PADDING * exportScale,
normalizedDimensions.width - FANCY_BG_PADDING * 2 * exportScale,
normalizedDimensions.height - FANCY_BG_PADDING * 2 * exportScale,
FANCY_BG_BORDER_RADIUS * exportScale,
);
}
if (index === shadows.length - 1) {
if (theme === THEME.DARK) {
context.filter = THEME_FILTER;
}
context.fillStyle = contentBackgroundColor;
context.fill();
}
context.closePath();
context.restore();
});
};
export const applyFancyBackgroundOnCanvas = async ({
canvas,
fancyBackgroundImageKey,
backgroundColor,
exportScale,
theme,
contentSize,
}: {
canvas: HTMLCanvasElement;
fancyBackgroundImageKey: Exclude<
keyof typeof FANCY_BACKGROUND_IMAGES,
"solid"
>;
backgroundColor: string;
exportScale: AppState["exportScale"];
theme: AppState["theme"];
contentSize: Dimensions;
}) => {
const context = canvas.getContext("2d")!;
const fancyBackgroundImageUrl =
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
const fancyBackgroundImage = await loadHTMLImageElement(
fancyBackgroundImageUrl,
);
const canvasDimensions: Dimensions = {
width: canvas.width,
height: canvas.height,
};
addImageBackground(
context,
canvasDimensions,
fancyBackgroundImage,
exportScale,
);
addContentBackground(
context,
canvasDimensions,
backgroundColor,
exportScale,
theme,
contentSize,
);
};
export const applyFancyBackgroundOnSvg = async ({
svgRoot,
fancyBackgroundImageKey,
backgroundColor,
dimensions,
exportScale,
theme,
}: {
svgRoot: SVGSVGElement;
fancyBackgroundImageKey: Exclude<
keyof typeof FANCY_BACKGROUND_IMAGES,
"solid"
>;
backgroundColor: string;
dimensions: Dimensions;
exportScale: AppState["exportScale"];
theme: AppState["theme"];
}) => {
// Image background
const fancyBackgroundImageUrl =
FANCY_BACKGROUND_IMAGES[fancyBackgroundImageKey][theme];
const fancyBackgroundImage = await loadSVGElement(fancyBackgroundImageUrl);
fancyBackgroundImage.setAttribute("x", "0");
fancyBackgroundImage.setAttribute("y", "0");
fancyBackgroundImage.setAttribute("width", `${dimensions.width}`);
fancyBackgroundImage.setAttribute("height", `${dimensions.height}`);
fancyBackgroundImage.setAttribute("preserveAspectRatio", "none");
if (theme === THEME.DARK) {
fancyBackgroundImage.setAttribute("filter", IMAGE_INVERT_FILTER);
}
svgRoot.appendChild(fancyBackgroundImage);
// Solid color background
const rect = svgRoot.ownerDocument!.createElementNS(SVG_NS, "rect");
rect.setAttribute("x", (FANCY_BG_PADDING * exportScale).toString());
rect.setAttribute("y", (FANCY_BG_PADDING * exportScale).toString());
rect.setAttribute(
"width",
`${dimensions.width - FANCY_BG_PADDING * 2 * exportScale}`,
);
rect.setAttribute(
"height",
`${dimensions.height - FANCY_BG_PADDING * 2 * exportScale}`,
);
rect.setAttribute("rx", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
rect.setAttribute("ry", (FANCY_BG_BORDER_RADIUS * exportScale).toString());
rect.setAttribute("fill", backgroundColor);
svgRoot.appendChild(rect);
};