Skip to content

Commit

Permalink
Add setting to toggle color correction
Browse files Browse the repository at this point in the history
  • Loading branch information
chrismaltby committed Jan 20, 2025
1 parent c320ef0 commit c9be556
Show file tree
Hide file tree
Showing 27 changed files with 319 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add ability to search and add scripts by name when adding events instead of needing to add a "Call Script" event and selecting the script manually from the dropdown each time [@pau-tomas](https://github.com/pau-tomas)
- Add ability to quickly create "Comment" events by typing the comment text in the Add Event search field and choosing "Comment" menu item [@pau-tomas](https://github.com/pau-tomas)
- Add text code `!Wait` to allow pausing dialogue until an amount of time/frames has elapsed or a selected button has been pressed
- Add setting to toggle GBC color correction. Disable color correction to closer match how colors will appear on modern hardware

### Changed

Expand Down
63 changes: 63 additions & 0 deletions src/components/forms/ColorCorrectionSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from "react";
import { Select, SelectCommonProps } from "ui/form/Select";
import l10n from "shared/lib/lang/l10n";
import { SingleValue } from "react-select";
import { ColorCorrectionSetting } from "shared/lib/resources/types";

interface ColorCorrectionSelectProps extends SelectCommonProps {
name: string;
value?: ColorCorrectionSetting;
onChange?: (newId: ColorCorrectionSetting) => void;
}

export interface ColorCorrectionOption {
value: ColorCorrectionSetting;
label: string;
}

export const ColorCorrectionSelect: FC<ColorCorrectionSelectProps> = ({
value,
onChange,
}) => {
const [currentValue, setCurrentValue] = useState<ColorCorrectionOption>();

const colorCorrectionOptions: ColorCorrectionOption[] = useMemo(
() => [
{
value: "default",
label: l10n("FIELD_COLOR_CORRECTION_ENABLED_DEFAULT"),
},
{
value: "none",
label: l10n("FIELD_COLOR_CORRECTION_NONE"),
},
],
[]
);

useEffect(() => {
const currentColorCorrection = colorCorrectionOptions.find(
(e) => e.value === value
);
if (currentColorCorrection) {
setCurrentValue(currentColorCorrection);
}
}, [colorCorrectionOptions, value]);

const onSelectChange = useCallback(
(newValue: SingleValue<ColorCorrectionOption>) => {
if (newValue) {
onChange?.(newValue.value);
}
},
[onChange]
);

return (
<Select
value={currentValue}
options={colorCorrectionOptions}
onChange={onSelectChange}
/>
);
};
101 changes: 58 additions & 43 deletions src/components/forms/CustomPalettePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { NumberField } from "ui/form/NumberField";
import { FixedSpacer } from "ui/spacing/Spacing";
import API from "renderer/lib/api";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { GBCHexToClosestHex } from "shared/lib/color/gbcColors";
import { GBCHexToColorCorrectedHex } from "shared/lib/color/colorCorrection";
import { hex2GBChex, rgb5BitToGBCHex } from "shared/lib/helpers/color";
import { getSettings } from "store/features/settings/settingsState";

const DEFAULT_WHITE = "E8F8E0";
const DEFAULT_LIGHT = "B0F088";
Expand Down Expand Up @@ -111,29 +113,6 @@ const clamp31 = (value: number) => {
return clamp(value, 0, 31);
};

// 5-bit rgb value => GBC representative hex value
const rgbToGBCHex = (red: number, green: number, blue: number) => {
const value = (blue << 10) + (green << 5) + red;
const r = value & 0x1f;
const g = (value >> 5) & 0x1f;
const b = (value >> 10) & 0x1f;
return (
(((r * 13 + g * 2 + b) >> 1) << 16) |
((g * 3 + b) << 9) |
((r * 3 + g * 2 + b * 11) >> 1)
)
.toString(16)
.padStart(6, "0");
};

// 24-bit hex value => GBC representative Hex value
const hexToGBCHex = (hex: string) => {
const r = clamp31(Math.floor(hexToDecimal(hex.substring(0, 2)) / 8));
const g = clamp31(Math.floor(hexToDecimal(hex.substring(2, 4)) / 8));
const b = clamp31(Math.floor(hexToDecimal(hex.substring(4)) / 8));
return rgbToGBCHex(r, g, b).toUpperCase();
};

const decimalToHexString = (number: number) => {
const ret = number.toString(16).toUpperCase();
return ret.length === 1 ? `0${ret}` : ret;
Expand Down Expand Up @@ -237,6 +216,10 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
paletteSelectors.selectById(state, paletteId)
);

const colorCorrection = useAppSelector(
(state) => getSettings(state).colorCorrection
);

const [selectedColor, setSelectedColor] = useState<ColorIndex>(0);
const [colorR, setColorR] = useState(0);
const [colorG, setColorG] = useState(0);
Expand Down Expand Up @@ -317,9 +300,9 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
const updateColorFromRGB = useCallback(
(r: number, g: number, b: number) => {
const hexString =
decimalToHexString(r * 8) +
decimalToHexString(g * 8) +
decimalToHexString(b * 8);
decimalToHexString(Math.round((r / 31) * 255)) +
decimalToHexString(Math.round((g / 31) * 255)) +
decimalToHexString(Math.round((b / 31) * 255));

updateCurrentColor(hexString);

Expand All @@ -328,9 +311,11 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
setColorH(Math.floor(hsv.h * 360));
setColorS(Math.floor(hsv.s * 100));
setColorV(Math.floor(hsv.v * 100));
setCurrentCustomHex("#" + hexToGBCHex(hexString).toLowerCase());
setCurrentCustomHex(
"#" + hex2GBChex(hexString, colorCorrection).toLowerCase()
);
},
[updateCurrentColor]
[updateCurrentColor, colorCorrection]
);

const updateColorFromHSV = useCallback(
Expand All @@ -354,9 +339,11 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
setColorR(r);
setColorG(g);
setColorB(b);
setCurrentCustomHex("#" + hexToGBCHex(hexString).toLowerCase());
setCurrentCustomHex(
"#" + hex2GBChex(hexString, colorCorrection).toLowerCase()
);
},
[updateCurrentColor]
[updateCurrentColor, colorCorrection]
);

const applyHexToState = useCallback((hex: string) => {
Expand Down Expand Up @@ -395,10 +382,19 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
editHex = getBlackHex();
}
setSelectedColor(colorIndex);
setCurrentCustomHex("#" + hexToGBCHex(editHex).toLowerCase());
setCurrentCustomHex(
"#" + hex2GBChex(editHex, colorCorrection).toLowerCase()
);
applyHexToState(editHex);
},
[applyHexToState, getBlackHex, getDarkHex, getLightHex, getWhiteHex]
[
applyHexToState,
getBlackHex,
getDarkHex,
getLightHex,
getWhiteHex,
colorCorrection,
]
);

const onHexChange = useCallback(
Expand All @@ -413,12 +409,14 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
if (hex.length === 6) {
hex = GBCHexToClosestHex(hex);
if (colorCorrection === "default") {
hex = GBCHexToColorCorrectedHex(hex);
}
applyHexToState(hex);
updateCurrentColor(hex);
}
},
[applyHexToState, updateCurrentColor]
[applyHexToState, updateCurrentColor, colorCorrection]
);

const onChangeR = useCallback(
Expand Down Expand Up @@ -524,10 +522,12 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
const initialiseColorValues = useCallback(
(color: string | undefined, paletteIndex: number) => {
const editHex = color || defaultColors[paletteIndex];
setCurrentCustomHex("#" + hexToGBCHex(editHex).toLowerCase());
setCurrentCustomHex(
"#" + hex2GBChex(editHex, colorCorrection).toLowerCase()
);
applyHexToState(editHex);
},
[applyHexToState]
[applyHexToState, colorCorrection]
);

const onPaste = useCallback(
Expand Down Expand Up @@ -606,7 +606,10 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
className="focus-visible"
onClick={() => onColorSelect(index)}
style={{
background: `#${hexToGBCHex(palette.colors[index])}`,
background: `#${hex2GBChex(
palette.colors[index],
colorCorrection
)}`,
}}
>
{index === 0 && <span>{l10n("FIELD_COLOR_LIGHTEST")}</span>}
Expand All @@ -633,7 +636,11 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
value={(colorR || 0) / 31}
onChange={(value) => onChangeR(Math.round(Number(value) * 31))}
colorAtValue={(value) => {
return `#${rgbToGBCHex(Math.round(value * 31), colorG, colorB)}`;
return `#${rgb5BitToGBCHex(
Math.round(value * 31),
colorG,
colorB
)}`;
}}
/>
</ColorValueFormItem>
Expand All @@ -655,7 +662,11 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
value={(colorG || 0) / 31}
onChange={(value) => onChangeG(Math.round(value * 31))}
colorAtValue={(value) => {
return `#${rgbToGBCHex(colorR, Math.round(value * 31), colorB)}`;
return `#${rgb5BitToGBCHex(
colorR,
Math.round(value * 31),
colorB
)}`;
}}
/>
</ColorValueFormItem>
Expand All @@ -677,7 +688,11 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
value={(colorB || 0) / 31}
onChange={(value) => onChangeB(Math.round(value * 31))}
colorAtValue={(value) => {
return `#${rgbToGBCHex(colorR, colorG, Math.round(value * 31))}`;
return `#${rgb5BitToGBCHex(
colorR,
colorG,
Math.round(value * 31)
)}`;
}}
/>
</ColorValueFormItem>
Expand Down Expand Up @@ -729,7 +744,7 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
if (r > 31) r = 31;
if (g > 31) g = 31;
if (b > 31) b = 31;
return `#${rgbToGBCHex(r, g, b)}`;
return `#${rgb5BitToGBCHex(r, g, b)}`;
}}
/>
</ColorValueFormItem>
Expand Down Expand Up @@ -758,7 +773,7 @@ const CustomPalettePicker = ({ paletteId }: CustomPalettePickerProps) => {
if (r > 31) r = 31;
if (g > 31) g = 31;
if (b > 31) b = 31;
return `#${rgbToGBCHex(r, g, b)}`;
return `#${rgb5BitToGBCHex(r, g, b)}`;
}}
/>
</ColorValueFormItem>
Expand Down
31 changes: 31 additions & 0 deletions src/components/pages/SettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { FixedSpacer } from "ui/spacing/Spacing";
import { useAppDispatch, useAppSelector } from "store/hooks";
import { ColorModeSelect } from "components/forms/ColorModeSelect";
import { CompilerPresetSelect } from "components/forms/CompilerPresetSelect";
import { ColorCorrectionSetting } from "shared/lib/resources/types";
import { ColorCorrectionSelect } from "components/forms/ColorCorrectionSelect";

const SettingsPage: FC = () => {
const dispatch = useAppDispatch();
Expand Down Expand Up @@ -77,6 +79,7 @@ const SettingsPage: FC = () => {

const {
colorMode,
colorCorrection,
sgbEnabled,
customHead,
defaultBackgroundPaletteIds,
Expand Down Expand Up @@ -121,6 +124,11 @@ const SettingsPage: FC = () => {
[onChangeSettingProp]
);

const onChangeColorCorrection = useCallback(
(e: ColorCorrectionSetting) => onChangeSettingProp("colorCorrection", e),
[onChangeSettingProp]
);

const onChangeSGBEnabled = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) =>
onChangeSettingProp("sgbEnabled", castEventToBool(e)),
Expand Down Expand Up @@ -288,6 +296,7 @@ const SettingsPage: FC = () => {
l10n("FIELD_EXPORT_IN_COLOR"),
l10n("FIELD_DEFAULT_BACKGROUND_PALETTES"),
l10n("FIELD_DEFAULT_SPRITE_PALETTES"),
l10n("FIELD_COLOR_CORRECTION"),
]}
>
<CardAnchor id="settingsColor" />
Expand Down Expand Up @@ -339,6 +348,28 @@ const SettingsPage: FC = () => {
</SearchableSettingRow>
{colorEnabled && (
<>
<SearchableSettingRow
searchTerm={searchTerm}
searchMatches={[l10n("FIELD_COLOR_CORRECTION")]}
>
<SettingRowLabel>
{l10n("FIELD_COLOR_CORRECTION")}
</SettingRowLabel>
<SettingRowInput>
<ColorCorrectionSelect
name="colorCorrection"
value={colorCorrection}
onChange={onChangeColorCorrection}
/>
<FormInfo>
{colorCorrection === "default" &&
l10n("FIELD_COLOR_CORRECTION_ENABLED_DEFAULT_INFO")}
{colorCorrection === "none" &&
l10n("FIELD_COLOR_CORRECTION_NONE_INFO")}
</FormInfo>
</SettingRowInput>
</SearchableSettingRow>

<SearchableSettingRow
searchTerm={searchTerm}
searchMatches={[l10n("FIELD_DEFAULT_BACKGROUND_PALETTES")]}
Expand Down
7 changes: 7 additions & 0 deletions src/components/sprites/preview/MetaspriteCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import MetaspriteCanvasWorker, {
MetaspriteCanvasResult,
} from "./MetaspriteCanvas.worker";
import { assetURL } from "shared/lib/helpers/assets";
import { getSettings } from "store/features/settings/settingsState";

interface MetaspriteCanvasProps {
spriteSheetId: string;
Expand Down Expand Up @@ -44,6 +45,10 @@ export const MetaspriteCanvas = memo(
const tilesLookup = useAppSelector((state) =>
metaspriteTileSelectors.selectEntities(state)
);
const colorCorrection = useAppSelector(
(state) => getSettings(state).colorCorrection
);

const width = spriteSheet?.canvasWidth || 0;
const height = spriteSheet?.canvasHeight || 0;

Expand Down Expand Up @@ -113,6 +118,7 @@ export const MetaspriteCanvas = memo(
palette: DMG_PALETTE.colors,
palettes: paletteColors,
previewAsMono,
colorCorrection,
});
}, [
canvasRef,
Expand All @@ -124,6 +130,7 @@ export const MetaspriteCanvas = memo(
flipX,
workerId,
previewAsMono,
colorCorrection,
]);

return (
Expand Down
Loading

0 comments on commit c9be556

Please sign in to comment.