diff --git a/ts/services/backups/export.ts b/ts/services/backups/export.ts index 88bcbe3c68b..6962820e8cf 100644 --- a/ts/services/backups/export.ts +++ b/ts/services/backups/export.ts @@ -110,7 +110,7 @@ import { AdhocCallStatus, } from '../../types/CallDisposition'; import { isAciString } from '../../util/isAciString'; -import { hslToRGB } from '../../util/hslToRGB'; +import { hslToRGBInt } from '../../util/hslToRGB'; import type { AboutMe, LocalChatStyle } from './types'; import { BackupType } from './types'; import { messageHasPaymentEvent } from '../../messages/helpers'; @@ -2588,10 +2588,10 @@ export class BackupExportStream extends Readable { const id = Long.fromNumber(result.length + 1); this.customColorIdByUuid.set(uuid, id); - const start = hslToRGBInt( + const start = desktopHslToRgbInt( color.start.hue, color.start.saturation, - color.start.luminance + color.start.lightness ); if (color.end == null) { @@ -2600,10 +2600,10 @@ export class BackupExportStream extends Readable { solid: start, }); } else { - const end = hslToRGBInt( + const end = desktopHslToRgbInt( color.end.hue, color.end.saturation, - color.end.luminance + color.end.lightness ); result.push({ @@ -2776,10 +2776,13 @@ function checkServiceIdEquivalence( return leftConvo && rightConvo && leftConvo === rightConvo; } -function hslToRGBInt(hue: number, saturation: number, luminance = 1): number { - const { r, g, b } = hslToRGB(hue, saturation, luminance); - // eslint-disable-next-line no-bitwise - return ((0xff << 24) | (r << 16) | (g << 8) | b) >>> 0; +function desktopHslToRgbInt( + hue: number, + saturation: number, + lightness = 1 +): number { + // Desktop stores saturation not as 0.123 (0 to 1.0) but 12.3 (percentage) + return hslToRGBInt(hue, saturation / 100, lightness); } function toGroupCallStateProto(state: CallStatus): Backups.GroupCall.State { diff --git a/ts/services/backups/import.ts b/ts/services/backups/import.ts index a59b62f1b0f..85ef597bf6d 100644 --- a/ts/services/backups/import.ts +++ b/ts/services/backups/import.ts @@ -89,7 +89,7 @@ import type { GroupV2ChangeDetailType } from '../../groups'; import { queueAttachmentDownloads } from '../../util/queueAttachmentDownloads'; import { isNotNil } from '../../util/isNotNil'; import { isGroup } from '../../util/whatTypeOfConversation'; -import { rgbToHSL } from '../../util/rgbToHSL'; +import { rgbIntToHSL } from '../../util/rgbToHSL'; import { convertBackupMessageAttachmentToAttachment, convertFilePointerToAttachment, @@ -3061,7 +3061,7 @@ export class BackupImportStream extends Writable { if (color.solid) { value = { - start: rgbIntToHSL(color.solid), + start: rgbIntToDesktopHSL(color.solid), }; } else { strictAssert(color.gradient != null, 'Either solid or gradient'); @@ -3076,8 +3076,8 @@ export class BackupImportStream extends Writable { strictAssert(deg != null, 'Missing angle'); value = { - start: rgbIntToHSL(start), - end: rgbIntToHSL(end), + start: rgbIntToDesktopHSL(start), + end: rgbIntToDesktopHSL(end), deg, }; } @@ -3226,20 +3226,15 @@ export class BackupImportStream extends Writable { } } -function rgbIntToHSL(intValue: number): { +function rgbIntToDesktopHSL(intValue: number): { hue: number; saturation: number; - luminance: number; + lightness: number; } { - // eslint-disable-next-line no-bitwise - const r = (intValue >>> 16) & 0xff; - // eslint-disable-next-line no-bitwise - const g = (intValue >>> 8) & 0xff; - // eslint-disable-next-line no-bitwise - const b = intValue & 0xff; - const { h: hue, s: saturation, l: luminance } = rgbToHSL(r, g, b); + const { h: hue, s: saturation, l: lightness } = rgbIntToHSL(intValue); - return { hue, saturation, luminance }; + // Desktop stores saturation not as 0.123 (0 to 1.0) but 12.3 (percentage) + return { hue, saturation: saturation * 100, lightness }; } function fromGroupCallStateProto( diff --git a/ts/test-node/util/hslToRGB_test.ts b/ts/test-node/util/hslToRGB_test.ts new file mode 100644 index 00000000000..e9b7c54257b --- /dev/null +++ b/ts/test-node/util/hslToRGB_test.ts @@ -0,0 +1,35 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { assert } from 'chai'; + +import { hslToRGB, hslToRGBInt } from '../../util/hslToRGB'; + +describe('hslToRGB', () => { + it('converts pure rgb colors', () => { + assert.deepStrictEqual(hslToRGB(0, 1, 0.5), { r: 255, g: 0, b: 0 }); + assert.deepStrictEqual(hslToRGB(120, 1, 0.5), { r: 0, g: 255, b: 0 }); + assert.deepStrictEqual(hslToRGB(240, 1, 0.5), { r: 0, g: 0, b: 255 }); + }); + + it('converts random sampled hsl colors', () => { + assert.deepStrictEqual(hslToRGB(50, 0.233333, 0.41), { + r: 129, + g: 121, + b: 80, + }); + assert.deepStrictEqual(hslToRGB(170, 0.97, 0.1), { + r: 1, + g: 50, + b: 42, + }); + }); +}); + +describe('hslToRGBInt', () => { + it('converts pure rgb colors', () => { + assert.equal(hslToRGBInt(0, 1, 0.5), 4294901760); + assert.equal(hslToRGBInt(120, 1, 0.5), 4278255360); + assert.equal(hslToRGBInt(240, 1, 0.5), 4278190335); + }); +}); diff --git a/ts/test-node/util/rgbToHSL_test.ts b/ts/test-node/util/rgbToHSL_test.ts index b22391759ec..f3a5139f560 100644 --- a/ts/test-node/util/rgbToHSL_test.ts +++ b/ts/test-node/util/rgbToHSL_test.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; -import { rgbToHSL } from '../../util/rgbToHSL'; +import { rgbIntToHSL, rgbToHSL } from '../../util/rgbToHSL'; describe('rgbToHSL', () => { it('converts pure rgb colors', () => { @@ -40,3 +40,11 @@ describe('rgbToHSL', () => { }); }); }); + +describe('rgbIntToHSL', () => { + it('converts pure rgb colors', () => { + assert.deepStrictEqual(rgbIntToHSL(4294901760), { h: 0, s: 1, l: 0.5 }); + assert.deepStrictEqual(rgbIntToHSL(4278255360), { h: 120, s: 1, l: 0.5 }); + assert.deepStrictEqual(rgbIntToHSL(4278190335), { h: 240, s: 1, l: 0.5 }); + }); +}); diff --git a/ts/types/Colors.ts b/ts/types/Colors.ts index 8ec256fcf85..01510d7171c 100644 --- a/ts/types/Colors.ts +++ b/ts/types/Colors.ts @@ -159,8 +159,8 @@ export const ContactNameColors = [ export type ContactNameColorType = (typeof ContactNameColors)[number]; export type CustomColorType = { - start: { hue: number; saturation: number; luminance?: number }; - end?: { hue: number; saturation: number; luminance?: number }; + start: { hue: number; saturation: number; lightness?: number }; + end?: { hue: number; saturation: number; lightness?: number }; deg?: number; }; diff --git a/ts/util/getHSL.ts b/ts/util/getHSL.ts index 49cbf028f88..fc35d432559 100644 --- a/ts/util/getHSL.ts +++ b/ts/util/getHSL.ts @@ -46,17 +46,17 @@ export function getHSL( { hue, saturation, - luminance, + lightness, }: { hue: number; saturation: number; - luminance?: number; + lightness?: number; }, adjustedLightness = 0 ): string { return `hsl(${hue}, ${saturation}%, ${ - luminance == null + lightness == null ? adjustLightnessValue(calculateLightness(hue), adjustedLightness) - : luminance * 100 + : lightness * 100 }%)`; } diff --git a/ts/util/hslToRGB.ts b/ts/util/hslToRGB.ts index a02362e0d6c..f6f269d47d6 100644 --- a/ts/util/hslToRGB.ts +++ b/ts/util/hslToRGB.ts @@ -24,3 +24,13 @@ export function hslToRGB( b: Math.round(255 * f(4)), }; } + +export function hslToRGBInt( + hue: number, + saturation: number, + lightness: number +): number { + const { r, g, b } = hslToRGB(hue, saturation, lightness); + // eslint-disable-next-line no-bitwise + return ((0xff << 24) | (r << 16) | (g << 8) | b) >>> 0; +} diff --git a/ts/util/rgbToHSL.ts b/ts/util/rgbToHSL.ts index 964d193a337..64bf42937be 100644 --- a/ts/util/rgbToHSL.ts +++ b/ts/util/rgbToHSL.ts @@ -26,7 +26,7 @@ export function rgbToHSL( if (c === 0) { h = 0; } else if (v === rn) { - h = 60 * (((gn - bn) / c) % 6); + h = 60 * (((gn - bn) / c + 6) % 6); } else if (v === gn) { h = 60 * ((bn - rn) / c + 2); } else { @@ -43,3 +43,17 @@ export function rgbToHSL( return { h, s, l }; } + +export function rgbIntToHSL(intValue: number): { + h: number; + s: number; + l: number; +} { + // eslint-disable-next-line no-bitwise + const r = (intValue >>> 16) & 0xff; + // eslint-disable-next-line no-bitwise + const g = (intValue >>> 8) & 0xff; + // eslint-disable-next-line no-bitwise + const b = intValue & 0xff; + return rgbToHSL(r, g, b); +}