Sfoglia il codice sorgente

feat: separate fancyBackground from renderScene

Arnošt Pleskot 2 anni fa
parent
commit
d6515e26b9

+ 2 - 2
src/appState.ts

@@ -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,
   };
 };

+ 6 - 6
src/components/ImageExportDialog.tsx

@@ -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")}

+ 9 - 6
src/constants.ts

@@ -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 =

+ 9 - 0
src/scene/export.ts

@@ -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),

+ 147 - 0
src/scene/fancyBackground.ts

@@ -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);
+};

+ 1 - 1
src/types.ts

@@ -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<