Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Color Rounding Logic for Enhanced Flexibility and Consistency #161

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 47 additions & 20 deletions stores/current.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,29 +159,51 @@ function round2(value: number): number {
}

function round4(value: number): number {
if (typeof value !== 'number') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you remove this check? We use TypeScript to check argument type.

This warning take big part of JS bundle and never will be called.

throw new TypeError(`Expected a number but received ${typeof value}`);
}

return parseFloat(value.toFixed(4))
}

function roundValue<V extends Partial<LchValue>>(
value: V,
type: 'lch' | 'oklch'
): V {
let rounded = { ...value }
if (typeof rounded.l !== 'undefined') {
rounded.l = round2(rounded.l)
}
if (typeof rounded.c !== 'undefined') {
rounded.c = type === 'oklch' ? round4(rounded.c) : round2(rounded.c)
}
if (typeof rounded.h !== 'undefined') {
rounded.h = round2(rounded.h)
}
if (typeof rounded.a !== 'undefined') {
rounded.a = round2(rounded.a)

function incrementalRoundValue(value: LchValue, COLOR_FN: string): LchValue {
const maxIterations = 6;

for (let i = 0; i <= maxIterations; i++) {
let precision = i === 0 ? 0 : Math.min(i, maxIterations);
let roundedValue: LchValue = {
a: round2(value.a),
c: round2(value.c),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure, that we should use the same round on each L, C, H on every step.

The problem is that L is 0-100, while C is 0-1. So it better to use different round level for C on every step.

Otherwise, the incrementalRoundValue will keep too many digits after , for L, because algorithm can’t round C anymore.

h: round2(value.h),
l: round2(value.l),
};

roundedValue.l = parseFloat(roundedValue.l.toFixed(precision));
roundedValue.c = parseFloat(roundedValue.c.toFixed(precision));
roundedValue.h = parseFloat(roundedValue.h.toFixed(precision));
roundedValue.a = parseFloat(roundedValue.a.toFixed(precision));

if (isMatch(roundedValue)) {
return roundedValue;
}
}
return rounded

return value;
}

function isMatch(value: LchValue): boolean {
Copy link
Member

@ai ai Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. You check only the fact that sRGB didn’t go to P3. But we need also to check that P3 color will not go to Rec2020. Check the origin algorithm https://github.com/evilmartians/oklch-picker/blob/main/stores/current.ts#L196
  2. Do we need to replace the origin algorithm? Seems like right now we have 2 different checks and spend resources twice https://github.com/evilmartians/oklch-picker/blob/main/stores/current.ts#L195-L200

let color = valueToColor(value);
let rgb = toRgb(color);

return (
rgb.r >= 0 && rgb.r <= 255 &&
rgb.g >= 0 && rgb.g <= 255 &&
rgb.b >= 0 && rgb.b <= 255
);
}


export function setCurrentFromColor(origin: Color): void {
if (origin.mode === COLOR_FN) {
current.set(colorToValue(origin as AnyLch))
Expand All @@ -192,7 +214,7 @@ export function setCurrentFromColor(origin: Color): void {
let rgbAccurate = toRgb(accurate)
accurate = LCH ? lch(rgbAccurate) : oklch(rgbAccurate)
}
let rounded = roundValue(colorToValue(accurate), COLOR_FN)
let rounded = incrementalRoundValue(colorToValue(accurate), COLOR_FN)
if (getSpace(valueToColor(rounded)) === originSpace) {
current.set(rounded)
} else {
Expand Down Expand Up @@ -222,12 +244,17 @@ export function toOtherValue(from: LchValue): LchValue {
} else {
to.l *= 100
}
return roundValue(to, LCH ? 'oklch' : 'lch')
return {
a: round4(to.a),
c: round4(to.c),
h: round4(to.h),
l: round4(to.l)
}
}

export function setCurrentComponents(parts: Partial<LchValue>): void {
let value = current.get()
let rounded = roundValue(parts, COLOR_FN)
let rounded = incrementalRoundValue({ ...value, ...parts }, COLOR_FN)
current.set({
a: value.a,
c: typeof rounded.c === 'undefined' ? value.c : rounded.c,
Expand Down
Loading