Improve auto background color

Colors need to be normalized per border so that the multiplier is applied to the sum of the normalized colors.
Normalize the returned color with adjusted thresholds compared to the normalization for the color extraction.
During the extraction the normalization shouldn't be too aggressive to prevent messing up the detection for the color with the highest proportion. Once we have detected the color, we want to be more aggressive about normalizing white and black colors to ensure the background color matches true white and black instead of being slightly off
This commit is contained in:
schroda
2026-07-18 00:40:53 +02:00
parent 06f0768395
commit e53d064c92

View File

@@ -25,6 +25,7 @@ import {
isContinuousReadingMode,
isContinuousVerticalReadingMode,
} from '@/features/reader/settings/ReaderSettings.utils.tsx';
import { Colors } from '@/lib/Colors.ts';
export const getInitialReaderPageIndex = (
resumeMode: ReaderResumeMode,
@@ -157,52 +158,69 @@ const getPageBackgroundColor = (
return [top, right, bottom, left];
})();
const blackThreshold = 10;
const whiteThreshold = 246;
const considerBlack = (color: ColorThief.Color | null): boolean => {
if (!color) {
// oxlint-disable-next-line unicorn/consistent-function-scoping
const considerBlack = (hex: string | undefined | null, threshold: number): boolean => {
if (!hex) {
return true;
}
const { r, g, b } = color.rgb();
return r <= blackThreshold && g <= blackThreshold && b <= blackThreshold;
const { r, g, b } = Colors.hexToRgb(hex);
return r <= threshold && g <= threshold && b <= threshold;
};
const considerWhite = (color: ColorThief.Color | null): boolean => {
if (!color) {
// oxlint-disable-next-line unicorn/consistent-function-scoping
const considerWhite = (hex: string | undefined | null, threshold: number): boolean => {
if (!hex) {
return true;
}
const { r, g, b } = color.rgb();
return r >= whiteThreshold && g >= whiteThreshold && b >= whiteThreshold;
const { r, g, b } = Colors.hexToRgb(hex);
return r >= threshold && g >= threshold && b >= threshold;
};
const getHexValue = (color: ColorThief.Color | null) => {
if (considerBlack(color)) {
return '#000000';
}
if (considerWhite(color)) {
const getNormalizedColor = (hex: string | undefined | null, threshold: { white: number; black: number }) => {
if (!hex || considerWhite(hex, threshold.white)) {
return '#ffffff';
}
return color!.hex();
if (considerBlack(hex, threshold.black)) {
return '#000000';
}
return hex;
};
const colorsByHexValue = groupBy((color) => getHexValue(color), borderColorPalettes.flat().filter(Boolean));
const proportionByHexValue = mapValues(
(colors) =>
sumBy((color) => {
const fillsWholeBorder = color!.proportion >= 0.97;
const determinationThresholds = { white: 235, black: 10 };
const normalizedBorderColorPalettes = borderColorPalettes.map((borderPalette) => {
const borderPaletteColorsByHexValue = groupBy(
(color) => getNormalizedColor(color?.hex(), determinationThresholds),
borderPalette?.filter(Boolean),
);
return Object.values(
mapValues((colors) => {
const proportionSum = sumBy((color) => color!.proportion, colors);
const populationSum = sumBy((color) => color!.population, colors);
const fillsWholeBorder = proportionSum >= 0.97;
const multiplier = fillsWholeBorder ? borderColorPalettes.length : 1;
return color!.proportion * multiplier;
}, colors),
colorsByHexValue,
);
const [hexValue] = maxBy(([_hex, proportion]) => proportion, Object.entries(proportionByHexValue))!;
const [color] = colors;
const { r, g, b } = color.rgb();
return hexValue;
return ColorThief.createColor(r, g, b, populationSum, proportionSum * multiplier, color.gamut);
}, borderPaletteColorsByHexValue),
);
});
const colorsByHexValue = groupBy(
(color) => getNormalizedColor(color?.hex(), determinationThresholds),
normalizedBorderColorPalettes.flat().filter(Boolean),
);
const proportionByHexValue = mapValues((colors) => sumBy((color) => color!.proportion, colors), colorsByHexValue);
const [hex] = maxBy(([_hex, proportion]) => proportion, Object.entries(proportionByHexValue))!;
return getNormalizedColor(hex, { white: 235, black: 30 });
};
const updatePageBackgroundColor = async (