Add "blend mode" to "custom color filter"

This commit is contained in:
schroda
2025-02-07 15:09:25 +01:00
parent 9d3aeafe0e
commit cae52fd782
6 changed files with 127 additions and 45 deletions

View File

@@ -869,6 +869,15 @@
"invert": "Invert", "invert": "Invert",
"rgba": { "rgba": {
"alpha": "Alpha", "alpha": "Alpha",
"blend_mode": {
"darken": "Darken",
"default": "$t(global.label.default)",
"lighten": "Lighten",
"multiply": "Multiply",
"overlay": "Overlay",
"screen": "Screen",
"title": "Color filter blend mode"
},
"blue": "Blue", "blue": "Blue",
"green": "Green", "green": "Green",
"red": "Red", "red": "Red",

View File

@@ -17,7 +17,7 @@ const BaseReaderRGBAFilter = ({
readerNavBarWidth, readerNavBarWidth,
customFilter: { customFilter: {
rgba: { rgba: {
value: { red, green, blue, alpha }, value: { red, green, blue, alpha, blendMode },
enabled, enabled,
}, },
}, },
@@ -36,6 +36,7 @@ const BaseReaderRGBAFilter = ({
bottom: 0, bottom: 0,
pointerEvents: 'none', pointerEvents: 'none',
background: `rgba(${red} ${green} ${blue} / ${alpha}%)`, background: `rgba(${red} ${green} ${blue} / ${alpha}%)`,
mixBlendMode: `${blendMode}`,
}} }}
/> />
); );

View File

@@ -13,9 +13,14 @@ import { CheckboxInput } from '@/modules/core/components/inputs/CheckboxInput.ts
import { TranslationKey } from '@/Base.types.ts'; import { TranslationKey } from '@/Base.types.ts';
import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx'; import { SliderInput } from '@/modules/core/components/inputs/SliderInput.tsx';
import { DEFAULT_READER_SETTINGS } from '@/modules/reader/constants/ReaderSettings.constants.tsx'; import {
DEFAULT_READER_SETTINGS,
READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA,
READER_BLEND_MODE_VALUES,
} from '@/modules/reader/constants/ReaderSettings.constants.tsx';
import { ButtonSelectInput } from '@/modules/core/components/inputs/ButtonSelectInput.tsx';
type RGBAType = keyof IReaderSettings['customFilter']['rgba']['value']; type RGBAType = Exclude<keyof IReaderSettings['customFilter']['rgba']['value'], 'blendMode'>;
const RGBA_TYPE_TO_TRANSLATION_KEY: Record<RGBAType, TranslationKey> = { const RGBA_TYPE_TO_TRANSLATION_KEY: Record<RGBAType, TranslationKey> = {
red: 'reader.settings.custom_filter.rgba.red', red: 'reader.settings.custom_filter.rgba.red',
@@ -52,49 +57,71 @@ export const ReaderSettingRGBA = ({
/> />
{rgba.enabled && ( {rgba.enabled && (
<Stack sx={{ gap: 1 }}> <Stack sx={{ gap: 1 }}>
{Object.entries(rgba.value).map(([key, value]) => ( {Object.entries(rgba.value).map(([key, value]) => {
<SliderInput if (key === 'blendMode') {
key={key} return null;
label={t(RGBA_TYPE_TO_TRANSLATION_KEY[key as RGBAType])} }
value={value}
onDefault={() => return (
updateSetting( <SliderInput
'rgba', key={key}
{ label={t(RGBA_TYPE_TO_TRANSLATION_KEY[key as RGBAType])}
...rgba, value={value}
value: { onDefault={() =>
...rgba.value, updateSetting(
[key]: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType], 'rgba',
{
...rgba,
value: {
...rgba.value,
[key]: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType],
},
},
true,
)
}
slotProps={{
slider: {
value,
defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType],
step: 1,
min: 0,
max: RGBA_TYPE_TO_MAX_VALUE[key as RGBAType],
onChange: (_, newValue) => {
updateSetting(
'rgba',
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
false,
);
},
onChangeCommitted: (_, newValue) => {
updateSetting(
'rgba',
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
true,
);
}, },
}, },
true, }}
) />
} );
slotProps={{ })}
slider: { <ButtonSelectInput
value, label={t('reader.settings.custom_filter.rgba.blend_mode.title')}
defaultValue: DEFAULT_READER_SETTINGS.customFilter.rgba.value[key as RGBAType], value={rgba.value.blendMode}
step: 1, values={READER_BLEND_MODE_VALUES}
min: 0, setValue={(value) =>
max: RGBA_TYPE_TO_MAX_VALUE[key as RGBAType], updateSetting(
onChange: (_, newValue) => { 'rgba',
updateSetting( {
'rgba', ...rgba,
{ ...rgba, value: { ...rgba.value, [key]: newValue } }, value: { ...rgba.value, blendMode: value },
false,
);
},
onChangeCommitted: (_, newValue) => {
updateSetting(
'rgba',
{ ...rgba, value: { ...rgba.value, [key]: newValue } },
true,
);
},
}, },
}} true,
/> )
))} }
valueToDisplayData={READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA}
/>
</Stack> </Stack>
)} )}
</Stack> </Stack>

View File

@@ -20,6 +20,7 @@ import {
ProgressBarPosition, ProgressBarPosition,
ProgressBarType, ProgressBarType,
ReaderBackgroundColor, ReaderBackgroundColor,
ReaderBlendMode,
ReaderExitMode, ReaderExitMode,
ReaderHotkey, ReaderHotkey,
ReaderOverlayMode, ReaderOverlayMode,
@@ -108,6 +109,7 @@ export const DEFAULT_READER_SETTINGS: IReaderSettings = {
green: 0, green: 0,
blue: 0, blue: 0,
alpha: 0, alpha: 0,
blendMode: ReaderBlendMode.DEFAULT,
}, },
enabled: false, enabled: false,
}, },
@@ -300,3 +302,32 @@ export const AUTO_SCROLL_SPEED = {
max: 60, max: 60,
step: 0.5, step: 0.5,
}; };
export const READER_BLEND_MODE_VALUE_TO_DISPLAY_DATA = {
[ReaderBlendMode.DEFAULT]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.default',
icon: null,
},
[ReaderBlendMode.MULTIPLY]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.multiply',
icon: null,
},
[ReaderBlendMode.SCREEN]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.screen',
icon: null,
},
[ReaderBlendMode.OVERLAY]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.overlay',
icon: null,
},
[ReaderBlendMode.DARKEN]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.darken',
icon: null,
},
[ReaderBlendMode.LIGHTEN]: {
title: 'reader.settings.custom_filter.rgba.blend_mode.lighten',
icon: null,
},
} satisfies ValueToDisplayData<ReaderBlendMode>;
export const READER_BLEND_MODE_VALUES = Object.values(ReaderBlendMode);

View File

@@ -7,6 +7,8 @@
*/ */
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
// eslint-disable-next-line import/no-extraneous-dependencies,no-restricted-imports
import { deepmerge } from '@mui/utils';
import { requestManager } from '@/lib/requests/RequestManager.ts'; import { requestManager } from '@/lib/requests/RequestManager.ts';
import { import {
requestUpdateMangaMetadata, requestUpdateMangaMetadata,
@@ -109,9 +111,11 @@ const convertMetadataToSettings = (
tapZoneInvertMode: tapZoneInvertMode:
jsonSaveParse<IReaderSettings['tapZoneInvertMode']>((metadata.tapZoneInvertMode as string) ?? '') ?? jsonSaveParse<IReaderSettings['tapZoneInvertMode']>((metadata.tapZoneInvertMode as string) ?? '') ??
defaultSettings.tapZoneInvertMode, defaultSettings.tapZoneInvertMode,
customFilter: customFilter: deepmerge(
jsonSaveParse<IReaderSettings['customFilter']>((metadata.customFilter as string) ?? '') ??
defaultSettings.customFilter, defaultSettings.customFilter,
jsonSaveParse<IReaderSettings['customFilter']>((metadata.customFilter as string) ?? '') ??
defaultSettings.customFilter,
),
readerWidth: readerWidth:
jsonSaveParse<IReaderSettings['readerWidth']>((metadata.readerWidth as string) ?? '') ?? jsonSaveParse<IReaderSettings['readerWidth']>((metadata.readerWidth as string) ?? '') ??
defaultSettings.readerWidth, defaultSettings.readerWidth,

View File

@@ -79,6 +79,16 @@ export interface ReaderFilterRGBA {
green: number; green: number;
blue: number; blue: number;
alpha: number; alpha: number;
blendMode: ReaderBlendMode;
}
export enum ReaderBlendMode {
DEFAULT = 'normal',
MULTIPLY = 'multiply',
SCREEN = 'screen',
OVERLAY = 'overlay',
DARKEN = 'darken',
LIGHTEN = 'lighten',
} }
export interface ReaderCustomFilter { export interface ReaderCustomFilter {