fix: SVG Inversion on Safari (#10712)

* invert image on safari

* lint

* Inversion to match theme filter

* cleanup

* Adjust canvas dimensions for device pixel ratio when inverting on Safari

* revert inversion algo & handle darkMode placeholder

---------

Co-authored-by: dwelle <5153846+dwelle@users.noreply.github.com>
This commit is contained in:
zsviczian
2026-01-31 16:28:21 +01:00
committed by GitHub
parent b552c60714
commit dfa1ce572b
+74 -20
View File
@@ -23,6 +23,7 @@ import {
getVerticalOffset,
invariant,
applyDarkModeFilter,
isSafari,
} from "@excalidraw/common";
import type {
@@ -360,8 +361,9 @@ IMAGE_ERROR_PLACEHOLDER_IMG.src = `data:${MIME_TYPES.svg},${encodeURIComponent(
const drawImagePlaceholder = (
element: ExcalidrawImageElement,
context: CanvasRenderingContext2D,
theme: StaticCanvasRenderConfig["theme"],
) => {
context.fillStyle = "#E7E7E7";
context.fillStyle = theme === THEME.DARK ? "#2E2E2E" : "#E7E7E7";
context.fillRect(0, 0, element.width, element.height);
const imageMinWidthOrHeight = Math.min(element.width, element.height);
@@ -443,13 +445,6 @@ const drawElementOnCanvas = (
? cacheEntry?.image
: undefined;
const shouldInvertImage =
renderConfig.theme === THEME.DARK &&
cacheEntry?.mimeType === MIME_TYPES.svg;
if (shouldInvertImage) {
context.filter = DARK_THEME_FILTER;
}
if (img != null && !(img instanceof Promise)) {
if (element.roundness && context.roundRect) {
context.beginPath();
@@ -472,19 +467,78 @@ const drawElementOnCanvas = (
height: img.naturalHeight,
};
context.drawImage(
img,
x,
y,
width,
height,
0 /* hardcoded for the selection box*/,
0,
element.width,
element.height,
);
const shouldInvertImage =
renderConfig.theme === THEME.DARK &&
cacheEntry?.mimeType === MIME_TYPES.svg;
if (shouldInvertImage && isSafari) {
const devicePixelRatio = window.devicePixelRatio || 1;
const tempCanvas = document.createElement("canvas");
tempCanvas.width = element.width * devicePixelRatio;
tempCanvas.height = element.height * devicePixelRatio;
const tempContext = tempCanvas.getContext("2d");
if (tempContext) {
tempContext.scale(devicePixelRatio, devicePixelRatio);
tempContext.drawImage(
img,
x,
y,
width,
height,
0,
0,
element.width,
element.height,
);
const imageData = tempContext.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height,
);
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
tempContext.putImageData(imageData, 0, 0);
context.drawImage(
tempCanvas,
0,
0,
tempCanvas.width,
tempCanvas.height,
0,
0,
element.width,
element.height,
);
}
} else {
if (shouldInvertImage) {
context.filter = DARK_THEME_FILTER;
}
context.drawImage(
img,
x,
y,
width,
height,
0 /* hardcoded for the selection box*/,
0,
element.width,
element.height,
);
}
} else {
drawImagePlaceholder(element, context);
drawImagePlaceholder(element, context, renderConfig.theme);
}
context.restore();
break;