Skip to content

Commit

Permalink
Merge pull request #31 from ProgSoc/CVS-30
Browse files Browse the repository at this point in the history
Fix: Broken QR code due to oklch()
  • Loading branch information
VanillaViking authored Oct 23, 2024
2 parents cb795c8 + 0606f25 commit 2b49532
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 2 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions src/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@types/qrcode": "^1.5.0",
"@types/react-transition-group": "^4.4.5",
"autoprefixer": "^10.4.12",
"color-parse": "^2.0.2",
"daisyui": "^4.12.13",
"formik": "^2.2.9",
"postcss": "^8.4.17",
Expand Down
19 changes: 17 additions & 2 deletions src/web/src/components/QRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import parse from 'color-parse';
import QRCode from 'qrcode';
import { useEffect, useRef } from 'react';
import { oklch2rgb } from 'utils/oklch2rgb';

// from https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
function rgb2hex(c: string) {
Expand All @@ -10,6 +12,19 @@ function rgb2hex(c: string) {
.join('')}`;
}

// function to handle cases when the computed style returns inconsistent colour spaces
function getHexColour(colour_str: string): string | null {
const col = parse(colour_str);
switch (col.space) {
case 'rgb':
return rgb2hex(colour_str);
case 'oklch':
return rgb2hex(oklch2rgb(col.values).map(v => Math.round(v * 255)).toString());
default:
return null;
}
}

export function QRCodeRender(props: { content: string; size?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null);
const styleDivRef = useRef<HTMLDivElement>(null);
Expand All @@ -26,8 +41,8 @@ export function QRCodeRender(props: { content: string; size?: number }) {
width: props.size ?? 300,
errorCorrectionLevel: 'low',
color: {
light: rgb2hex(style.backgroundColor),
dark: rgb2hex(style.color),
light: getHexColour(style.backgroundColor) || '#ffffff',
dark: getHexColour(style.color) || '#0000ff',
},
});
}, [canvasRef.current, styleDivRef.current, props.size, props.content]);
Expand Down
70 changes: 70 additions & 0 deletions src/web/src/utils/oklch2rgb.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function multiplyMatrices(A: number[], B: number[]) {
return [
A[0] * B[0] + A[1] * B[1] + A[2] * B[2],
A[3] * B[0] + A[4] * B[1] + A[5] * B[2],
A[6] * B[0] + A[7] * B[1] + A[8] * B[2],
];
}

function oklch2oklab([l, c, h]: number[]) {
return [
l,
Number.isNaN(h) ? 0 : c * Math.cos(h * Math.PI / 180),
Number.isNaN(h) ? 0 : c * Math.sin(h * Math.PI / 180),
];
}

function srgbLinear2rgb(rgb: number[]): number[] {
return rgb.map(c =>
Math.abs(c) > 0.0031308
? (c < 0 ? -1 : 1) * (1.055 * (Math.abs(c) ** (1 / 2.4)) - 0.055)
: 12.92 * c,
);
}

function oklab2xyz(lab: number[]): number[] {
const LMSg = multiplyMatrices([
1,
0.3963377773761749,
0.2158037573099136,
1,
-0.1055613458156586,
-0.0638541728258133,
1,
-0.0894841775298119,
-1.2914855480194092,
], lab);
const LMS = LMSg.map(val => val ** 3);
return multiplyMatrices([
1.2268798758459243,
-0.5578149944602171,
0.2813910456659647,
-0.0405757452148008,
1.1122868032803170,
-0.0717110580655164,
-0.0763729366746601,
-0.4214933324022432,
1.5869240198367816,
], LMS);
}
function xyz2rgbLinear(xyz: number[]): number[] {
return multiplyMatrices([
3.2409699419045226,
-1.537383177570094,
-0.4986107602930034,
-0.9692436362808796,
1.8759675015077202,
0.04155505740717559,
0.05563007969699366,
-0.20397695888897652,
1.0569715142428786,
], xyz);
}

export function oklch2rgb(lch: number[]): number[] {
const rgb = srgbLinear2rgb(xyz2rgbLinear(oklab2xyz(oklch2oklab(lch))));
// clamp to between 0,1
return rgb.map(v => Math.max(Math.min(v, 1), 0));
}

// taken from https://gist.github.com/dkaraush/65d19d61396f5f3cd8ba7d1b4b3c9432

0 comments on commit 2b49532

Please sign in to comment.