fix: Regression - invert SVGs in Dark Mode (#10695)

* initial implementation

* lint

* removed separate getThemeFilterValue function from renderElement

* removed BinaryFileData changes

* filter instead of css filter
This commit is contained in:
zsviczian
2026-01-24 13:00:14 +01:00
committed by GitHub
parent d5e37cda81
commit 60759d314d
3 changed files with 27 additions and 1 deletions
+2
View File
@@ -190,6 +190,8 @@ export const THEME = {
DARK: "dark", DARK: "dark",
} as const; } as const;
export const DARK_THEME_FILTER = "invert(93%) hue-rotate(180deg)";
export const FRAME_STYLE = { export const FRAME_STYLE = {
strokeColor: "#bbb" as ExcalidrawElement["strokeColor"], strokeColor: "#bbb" as ExcalidrawElement["strokeColor"],
strokeWidth: 2 as ExcalidrawElement["strokeWidth"], strokeWidth: 2 as ExcalidrawElement["strokeWidth"],
+16 -1
View File
@@ -14,6 +14,7 @@ import {
DEFAULT_REDUCED_GLOBAL_ALPHA, DEFAULT_REDUCED_GLOBAL_ALPHA,
ELEMENT_READY_TO_ERASE_OPACITY, ELEMENT_READY_TO_ERASE_OPACITY,
FRAME_STYLE, FRAME_STYLE,
DARK_THEME_FILTER,
MIME_TYPES, MIME_TYPES,
THEME, THEME,
distance, distance,
@@ -433,9 +434,22 @@ const drawElementOnCanvas = (
break; break;
} }
case "image": { case "image": {
context.save();
const cacheEntry =
element.fileId !== null
? renderConfig.imageCache.get(element.fileId)
: null;
const img = isInitializedImageElement(element) const img = isInitializedImageElement(element)
? renderConfig.imageCache.get(element.fileId)?.image ? cacheEntry?.image
: undefined; : 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 (img != null && !(img instanceof Promise)) {
if (element.roundness && context.roundRect) { if (element.roundness && context.roundRect) {
context.beginPath(); context.beginPath();
@@ -472,6 +486,7 @@ const drawElementOnCanvas = (
} else { } else {
drawImagePlaceholder(element, context); drawImagePlaceholder(element, context);
} }
context.restore();
break; break;
} }
default: { default: {
@@ -3,11 +3,13 @@ import {
MAX_DECIMALS_FOR_SVG_EXPORT, MAX_DECIMALS_FOR_SVG_EXPORT,
SVG_NS, SVG_NS,
THEME, THEME,
DARK_THEME_FILTER,
getFontFamilyString, getFontFamilyString,
isRTL, isRTL,
isTestEnv, isTestEnv,
getVerticalOffset, getVerticalOffset,
applyDarkModeFilter, applyDarkModeFilter,
MIME_TYPES,
} from "@excalidraw/common"; } from "@excalidraw/common";
import { normalizeLink, toValidURL } from "@excalidraw/common"; import { normalizeLink, toValidURL } from "@excalidraw/common";
import { hashString } from "@excalidraw/element"; import { hashString } from "@excalidraw/element";
@@ -520,6 +522,13 @@ const renderElementToSvg = (
const g = svgRoot.ownerDocument.createElementNS(SVG_NS, "g"); const g = svgRoot.ownerDocument.createElementNS(SVG_NS, "g");
if (
renderConfig.theme === THEME.DARK &&
fileData.mimeType === MIME_TYPES.svg
) {
g.setAttribute("filter", DARK_THEME_FILTER);
}
if (element.crop) { if (element.crop) {
const mask = svgRoot.ownerDocument.createElementNS(SVG_NS, "mask"); const mask = svgRoot.ownerDocument.createElementNS(SVG_NS, "mask");
mask.setAttribute("id", `mask-image-crop-${element.id}`); mask.setAttribute("id", `mask-image-crop-${element.id}`);