feat: scale up small exports with fancy background

This commit is contained in:
Arnošt Pleskot 2023-08-15 23:41:52 +02:00
parent d27856a967
commit 4c8cf9c91c
No known key found for this signature in database
6 changed files with 64 additions and 14 deletions

View File

@ -5,16 +5,11 @@ import {
DEFAULT_FONT_FAMILY, DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE, DEFAULT_FONT_SIZE,
DEFAULT_TEXT_ALIGN, DEFAULT_TEXT_ALIGN,
EXPORT_SCALES,
THEME, THEME,
} from "./constants"; } from "./constants";
import { t } from "./i18n"; import { t } from "./i18n";
import { AppState, NormalizedZoomValue } from "./types"; import { AppState, NormalizedZoomValue } from "./types";
import { getDateTime } from "./utils"; import { getDateTime, defaultExportScale } from "./utils";
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
? devicePixelRatio
: 1;
export const getDefaultAppState = (): Omit< export const getDefaultAppState = (): Omit<
AppState, AppState,

View File

@ -26,7 +26,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
import { NonDeletedExcalidrawElement } from "../element/types"; import { NonDeletedExcalidrawElement } from "../element/types";
import { t } from "../i18n"; import { t } from "../i18n";
import { getSelectedElements, isSomeElementSelected } from "../scene"; import { getSelectedElements, isSomeElementSelected } from "../scene";
import { exportToCanvas } from "../packages/utils"; import { exportToCanvas, getScaleToFit } from "../packages/utils";
import { copyIcon, downloadIcon, helpIcon } from "./icons"; import { copyIcon, downloadIcon, helpIcon } from "./icons";
import { Dialog } from "./Dialog"; import { Dialog } from "./Dialog";
@ -38,6 +38,9 @@ import "./ImageExportDialog.scss";
import { useAppProps } from "./App"; import { useAppProps } from "./App";
import { FilledButton } from "./FilledButton"; import { FilledButton } from "./FilledButton";
import Select, { convertToSelectItems } from "./Select"; import Select, { convertToSelectItems } from "./Select";
import { getCommonBounds } from "../element";
import { defaultExportScale, distance } from "../utils";
import { getFancyBackgroundPadding } from "../scene/fancyBackground";
const supportsContextFilters = const supportsContextFilters =
"filter" in document.createElement("canvas").getContext("2d")!; "filter" in document.createElement("canvas").getContext("2d")!;
@ -98,6 +101,7 @@ const ImageExportModal = ({
); );
const [embedScene, setEmbedScene] = useState(appState.exportEmbedScene); const [embedScene, setEmbedScene] = useState(appState.exportEmbedScene);
const [exportScale, setExportScale] = useState(appState.exportScale); const [exportScale, setExportScale] = useState(appState.exportScale);
const [exportBaseScale, setExportBaseScale] = useState(appState.exportScale);
const previewRef = useRef<HTMLDivElement>(null); const previewRef = useRef<HTMLDivElement>(null);
const [renderError, setRenderError] = useState<Error | null>(null); const [renderError, setRenderError] = useState<Error | null>(null);
@ -109,6 +113,44 @@ const ImageExportModal = ({
}) })
: elements; : elements;
useEffect(() => {
if (
exportedElements.length > 0 &&
exportWithBackground &&
exportBackgroundImage !== "solid"
) {
const previewNode = previewRef.current;
if (!previewNode) {
return;
}
const [minX, minY, maxX, maxY] = getCommonBounds(exportedElements);
const maxWidth = previewNode.offsetWidth;
const maxHeight = previewNode.offsetHeight;
const scale =
Math.floor(
(getScaleToFit(
{
width: distance(minX, maxX) + getFancyBackgroundPadding() * 2,
height: distance(minY, maxY) + getFancyBackgroundPadding() * 2,
},
{ width: maxWidth, height: maxHeight },
) +
Number.EPSILON) *
100,
) / 100;
if (scale > 1) {
actionManager.executeAction(actionChangeExportScale, "ui", scale);
setExportBaseScale(scale);
} else {
setExportBaseScale(defaultExportScale);
}
} else {
setExportBaseScale(defaultExportScale);
}
}, [actionManager, exportedElements, previewRef]);
useEffect(() => { useEffect(() => {
const previewNode = previewRef.current; const previewNode = previewRef.current;
if (!previewNode) { if (!previewNode) {
@ -117,6 +159,8 @@ const ImageExportModal = ({
const maxWidth = previewNode.offsetWidth; const maxWidth = previewNode.offsetWidth;
const maxHeight = previewNode.offsetHeight; const maxHeight = previewNode.offsetHeight;
const maxWidthOrHeight = Math.min(maxWidth, maxHeight);
if (!maxWidth) { if (!maxWidth) {
return; return;
} }
@ -125,7 +169,7 @@ const ImageExportModal = ({
appState, appState,
files, files,
exportPadding: DEFAULT_EXPORT_PADDING, exportPadding: DEFAULT_EXPORT_PADDING,
maxWidthOrHeight: Math.max(maxWidth, maxHeight), maxWidthOrHeight,
}) })
.then((canvas) => { .then((canvas) => {
setRenderError(null); setRenderError(null);
@ -283,8 +327,8 @@ const ImageExportModal = ({
actionManager.executeAction(actionChangeExportScale, "ui", scale); actionManager.executeAction(actionChangeExportScale, "ui", scale);
}} }}
choices={EXPORT_SCALES.map((scale) => ({ choices={EXPORT_SCALES.map((scale) => ({
value: scale, value: scale * exportBaseScale,
label: `${scale}\u00d7`, label: `${scale * exportBaseScale}\u00d7`,
}))} }))}
/> />
</ExportSetting> </ExportSetting>

View File

@ -7,8 +7,6 @@ import { AppState, BinaryFiles } from "../types";
import { import {
DEFAULT_EXPORT_PADDING, DEFAULT_EXPORT_PADDING,
FANCY_BACKGROUND_IMAGES, FANCY_BACKGROUND_IMAGES,
FANCY_BG_BORDER_RADIUS,
FANCY_BG_PADDING,
SVG_NS, SVG_NS,
THEME, THEME,
THEME_FILTER, THEME_FILTER,
@ -23,6 +21,7 @@ import Scene from "./Scene";
import { import {
applyFancyBackgroundOnCanvas, applyFancyBackgroundOnCanvas,
applyFancyBackgroundOnSvg, applyFancyBackgroundOnSvg,
getFancyBackgroundPadding,
} from "./fancyBackground"; } from "./fancyBackground";
export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`; export const SVG_EXPORT_TAG = `<!-- svg-source:excalidraw -->`;
@ -57,7 +56,7 @@ export const exportToCanvas = async (
elements.length > 0; elements.length > 0;
const padding = !exportWithFancyBackground const padding = !exportWithFancyBackground
? exportPadding ? exportPadding
: exportPadding + FANCY_BG_PADDING + FANCY_BG_BORDER_RADIUS; : getFancyBackgroundPadding(exportPadding);
const [minX, minY, width, height] = getCanvasSize(elements, padding); const [minX, minY, width, height] = getCanvasSize(elements, padding);
@ -169,7 +168,7 @@ export const exportToSvg = async (
const padding = !exportWithFancyBackground const padding = !exportWithFancyBackground
? exportPadding ? exportPadding
: (exportPadding + FANCY_BG_PADDING + FANCY_BG_BORDER_RADIUS) * exportScale; : getFancyBackgroundPadding(exportPadding) * exportScale;
let metadata = ""; let metadata = "";
if (exportEmbedScene) { if (exportEmbedScene) {

View File

@ -1,4 +1,5 @@
import { import {
DEFAULT_EXPORT_PADDING,
FANCY_BACKGROUND_IMAGES, FANCY_BACKGROUND_IMAGES,
FANCY_BG_BORDER_RADIUS, FANCY_BG_BORDER_RADIUS,
FANCY_BG_PADDING, FANCY_BG_PADDING,
@ -12,6 +13,10 @@ import { getScaleToFill } from "../packages/utils";
import { roundRect } from "../renderer/roundRect"; import { roundRect } from "../renderer/roundRect";
import { AppState, Dimensions } from "../types"; import { AppState, Dimensions } from "../types";
export const getFancyBackgroundPadding = (
exportPadding = DEFAULT_EXPORT_PADDING,
) => FANCY_BG_PADDING + FANCY_BG_BORDER_RADIUS + exportPadding;
const addImageBackground = ( const addImageBackground = (
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
canvasDimensions: Dimensions, canvasDimensions: Dimensions,

View File

@ -657,3 +657,5 @@ export type FrameNameBoundsCache = {
} }
>; >;
}; };
export type Dimensions = { width: number; height: number };

View File

@ -4,6 +4,7 @@ import {
CURSOR_TYPE, CURSOR_TYPE,
DEFAULT_VERSION, DEFAULT_VERSION,
EVENT, EVENT,
EXPORT_SCALES,
FONT_FAMILY, FONT_FAMILY,
isDarwin, isDarwin,
MIME_TYPES, MIME_TYPES,
@ -1002,3 +1003,7 @@ export const isRenderThrottlingEnabled = (() => {
return false; return false;
}; };
})(); })();
export const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
? devicePixelRatio
: 1;