mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-30 18:34:22 +01:00 
			
		
		
		
	feat: separate fancyBackground from renderScene
This commit is contained in:
		| @@ -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, | ||||
|   }; | ||||
| }; | ||||
|   | ||||
| @@ -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 && ( | ||||
|           <div className="ImageExportModal__settings__filename"> | ||||
|             <input | ||||
|               type="text" | ||||
|               className="TextInput" | ||||
| @@ -177,8 +177,8 @@ const ImageExportModal = ({ | ||||
|                 ); | ||||
|               }} | ||||
|             /> | ||||
|           )} | ||||
|           </div> | ||||
|         )} | ||||
|         {someElementIsSelected && ( | ||||
|           <ExportSetting | ||||
|             label={t("imageExportDialog.label.onlySelected")} | ||||
|   | ||||
| @@ -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 = | ||||
|   | ||||
| @@ -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
									
								
								src/scene/fancyBackground.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								src/scene/fancyBackground.ts
									
									
									
									
									
										Normal 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); | ||||
| }; | ||||
| @@ -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< | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Arnošt Pleskot
					Arnošt Pleskot