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, element: ExcalidrawElement,
allElements: ExcalidrawElementsIncludingDeleted, allElements: ExcalidrawElementsIncludingDeleted,
appState: StaticCanvasAppState, appState: StaticCanvasAppState,
targetFrame?: ExcalidrawFrameElement,
) => { ) => {
const frame = getTargetFrame(element, appState); const frame = targetFrame ?? getTargetFrame(element, appState);
const _element = isTextElement(element) const _element = isTextElement(element)
? getContainerElement(element) || element ? getContainerElement(element) || element
: element; : element;

View File

@ -357,6 +357,36 @@ const renderLinearElementPointHighlight = (
context.restore(); 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 = ( const frameClip = (
frame: ExcalidrawFrameElement, frame: ExcalidrawFrameElement,
context: CanvasRenderingContext2D, context: CanvasRenderingContext2D,
@ -941,45 +971,25 @@ const _renderStaticScene = ({
); );
} }
// Paint visible elements // Paint visible elements with embeddables on top
visibleElements const visibleNonEmbeddableOrLabelElements = visibleElements.filter(
.filter((el) => !isEmbeddableOrLabel(el)) (el) => !isEmbeddableOrLabel(el),
.forEach((element) => { );
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 { 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); renderElement(element, rc, context, renderConfig, appState);
if ( if (
@ -991,36 +1001,39 @@ const _renderStaticScene = ({
const label = createPlaceholderEmbeddableLabel(element); const label = createPlaceholderEmbeddableLabel(element);
renderElement(label, rc, context, renderConfig, appState); renderElement(label, rc, context, renderConfig, appState);
} }
if (!isExporting) { if (!isExporting) {
renderLinkIcon(element, context, appState); 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 for (const contiguousElements of contiguousElementsArray) {
// if the containing frame is not selected, apply clipping const firstElement = contiguousElements[0];
const frameId = element.frameId || appState.frameToHighlight?.id;
if (firstElement) {
context.save();
const frameId = firstElement.frameId || appState.frameToHighlight?.id;
if ( if (
frameId && frameId &&
appState.frameRendering.enabled && appState.frameRendering.enabled &&
appState.frameRendering.clip appState.frameRendering.clip
) { ) {
context.save(); const frame = getTargetFrame(firstElement, appState);
const frame = getTargetFrame(element, appState); if (frame && isElementInFrame(firstElement, elements, appState)) {
if (frame && isElementInFrame(element, elements, appState)) {
frameClip(frame, context, renderConfig, appState); frameClip(frame, context, renderConfig, appState);
} }
render(); }
renderContiguousElements(contiguousElements);
context.restore(); context.restore();
} else {
render();
} }
} catch (error: any) {
console.error(error);
} }
});
}; };
/** throttled to animation framerate */ /** throttled to animation framerate */