batch clipping

This commit is contained in:
Ryan Di 2023-11-10 19:24:32 +08:00
parent 71ba0a3f26
commit a30e46b756
2 changed files with 78 additions and 64 deletions

View File

@ -593,8 +593,9 @@ export const isElementInFrame = (
element: ExcalidrawElement,
allElements: ExcalidrawElementsIncludingDeleted,
appState: StaticCanvasAppState,
targetFrame?: ExcalidrawFrameElement,
) => {
const frame = getTargetFrame(element, appState);
const frame = targetFrame ?? getTargetFrame(element, appState);
const _element = isTextElement(element)
? getContainerElement(element) || element
: element;

View File

@ -357,6 +357,36 @@ const renderLinearElementPointHighlight = (
context.restore();
};
const getContiguousElements = (
elements: readonly ExcalidrawElement[],
appState: StaticCanvasAppState,
) => {
const contiguousElementsArray: ExcalidrawElement[][] = [];
let previousFrameId: string | null | undefined = null;
const contiguousElements: ExcalidrawElement[] = [];
for (const element of elements) {
const frameId = element.frameId || appState.frameToHighlight?.id;
if (previousFrameId !== frameId) {
if (contiguousElements.length > 0) {
contiguousElementsArray.push([...contiguousElements]);
contiguousElements.length = 0;
}
previousFrameId = frameId;
}
contiguousElements.push(element);
}
if (contiguousElements.length > 0) {
contiguousElementsArray.push([...contiguousElements]);
}
return contiguousElementsArray;
};
const frameClip = (
frame: ExcalidrawFrameElement,
context: CanvasRenderingContext2D,
@ -941,45 +971,25 @@ const _renderStaticScene = ({
);
}
// Paint visible elements
visibleElements
.filter((el) => !isEmbeddableOrLabel(el))
.forEach((element) => {
// Paint visible elements with embeddables on top
const visibleNonEmbeddableOrLabelElements = visibleElements.filter(
(el) => !isEmbeddableOrLabel(el),
);
const visibleEmbeddableOrLabelElements = visibleElements.filter((el) =>
isEmbeddableOrLabel(el),
);
const contiguousElementsArray = [
...getContiguousElements(visibleNonEmbeddableOrLabelElements, appState),
...getContiguousElements(visibleEmbeddableOrLabelElements, appState),
];
const renderContiguousElements = (
contiguousElements: ExcalidrawElement[],
) => {
for (const element of contiguousElements) {
try {
const frameId = element.frameId || appState.frameToHighlight?.id;
if (
frameId &&
appState.frameRendering.enabled &&
appState.frameRendering.clip
) {
context.save();
const frame = getTargetFrame(element, appState);
// TODO do we need to check isElementInFrame here?
if (frame && isElementInFrame(element, elements, appState)) {
frameClip(frame, context, renderConfig, appState);
}
renderElement(element, rc, context, renderConfig, appState);
context.restore();
} else {
renderElement(element, rc, context, renderConfig, appState);
}
if (!isExporting) {
renderLinkIcon(element, context, appState);
}
} catch (error: any) {
console.error(error);
}
});
// render embeddables on top
visibleElements
.filter((el) => isEmbeddableOrLabel(el))
.forEach((element) => {
try {
const render = () => {
renderElement(element, rc, context, renderConfig, appState);
if (
@ -991,36 +1001,39 @@ const _renderStaticScene = ({
const label = createPlaceholderEmbeddableLabel(element);
renderElement(label, rc, context, renderConfig, appState);
}
if (!isExporting) {
renderLinkIcon(element, context, appState);
}
} catch (error: any) {
console.error(error);
}
}
};
// - when exporting the whole canvas, we DO NOT apply clipping
// - when we are exporting a particular frame, apply clipping
// if the containing frame is not selected, apply clipping
const frameId = element.frameId || appState.frameToHighlight?.id;
for (const contiguousElements of contiguousElementsArray) {
const firstElement = contiguousElements[0];
if (firstElement) {
context.save();
const frameId = firstElement.frameId || appState.frameToHighlight?.id;
if (
frameId &&
appState.frameRendering.enabled &&
appState.frameRendering.clip
) {
context.save();
const frame = getTargetFrame(firstElement, appState);
const frame = getTargetFrame(element, appState);
if (frame && isElementInFrame(element, elements, appState)) {
if (frame && isElementInFrame(firstElement, elements, appState)) {
frameClip(frame, context, renderConfig, appState);
}
render();
}
renderContiguousElements(contiguousElements);
context.restore();
} else {
render();
}
} catch (error: any) {
console.error(error);
}
});
};
/** throttled to animation framerate */