JS, TS

자바스크립트 비트연산자 시프트 활용하기 (Hex to RGB 컬러변환)

Mitchell 2023. 9. 8. 09:00

문제의 발견

프로젝트 내에서는 사용되는 컬러값을 pallete 상수에 저장해서 CSS in JS로 불러오고 있었습니다.  특정 상황에서 엘리먼트의 CSS 컬러 값이 제대로 반영되었는지 테스트하였는데요.

// 테스트코드 중 일부

const styles = getComputedStyle(element)

await expect(styles.backgroundColor).toBe(theme.pallete.white)

 

그러나 다음과 같이 테스트는 실패하게 됩니다.

테스트 결과

브라우저에서는 컬러값을 RGB로 변한 후 화면에 렌더링하기 때문에 #FFFFFF(흰색)이 RGB(255, 255, 255)로 변환되어 테스트가 진행된 것입니다. 따라서 Hexadecimal(16진수)로 표현된 컬러 값을 RGB로 변환 후에 테스트를 진행해야 통과하는 결과를 얻을 수 있겠습니다.

 

색상표현에 대하여

변환 함수 작성 이전에 컴퓨터에서 컬러가 어떻게 표현되는 지 간략하게 정리하겠습니다.

 

색상표현 방법

컴퓨터는 컬러를 빛의 3원색(Red, Green, Blue)을 조합하여 필요한 색을 표현하게 되는데요. 각 원색은 0부터 255까지 총 256단계로 명도를 조절하여 조합합니다. 256가지인 이유는 각 컬러마다 1바이트씩 주어지기 때문입니다.

 

RGB

컴퓨터는 기본적으로 RGB로 표현한다고 앞에서 이야기 하였습니다. CSS에서는 "rgb(255, 255, 255)"와 같이 표현하며 왼쪽부터 Red, Green, Blue의 값입니다.

 

HEX

16진수를 사용하여 조금더 간결하게 표현하는 방법입니다. CSS에서는 "#FFFFFF"와 같이 표현하여 마찬가지로 왼쪽부터 두자리씩 Red, Green, Blue입니다.

 

Hex to RGB

위의 원리대로라면 Hex(16진수)로 표현된 숫자를 각 RGB의 10진수로 진법 변환을 하면 올바르게 변환된 값을 얻을 수 있을 것입니다.

const hexToRgb = (hexColor: string) => {
    const hex = hexColor.charAt(0) === '#' ? hexColor.slice(1) : hexColor

    let hexInt
    if (hex.length === 3) {
        hexInt = parseInt(
            hex.charAt(0) +
                hex.charAt(0) +
                hex.charAt(1) +
                hex.charAt(1) +
                hex.charAt(2) +
                hex.charAt(2),
            16,
        )
    } else if (hex.length === 6) {
        hexInt = parseInt(hex, 16)
    } else {
        return null // Invalid hex color
    }

    const rgb = {
        r: (hexInt >> 16) & 255,
        g: (hexInt >> 8) & 255,
        b: hexInt & 255,
    }

    return `rgb(${rgb.r}, ${rgb.b}, ${rgb.g})`
}
  1. CSS에서 16진수로 컬러를 표현할때 #가 맨앞에 오기 때문에 16진수의 "숫자"만 뽑아오기 위해 #를 제거합니다.
  2. CSS에서 16진수로 컬러를 표현할때 #FFF처럼 세자리로도 표현가능하기 때문에 세자리를 여섯자리로 바꿔줍니다.
  3. 6자리 16진수로 표현된 문자열을 parseInt(num, 16)로 16진수 정수형으로 변환합니다.
  4. 원하는 컬러 자리 값만 추출하기 위해 우측 시프트 연산을 실행하고 RGB 컬러 값에 딱 맞게 가져오도록 255로 & 연산합니다.

 

비트마스킹 이해하기

조금은 생소할 수 있는 비트연산자 사용에 대해서 자세히 이해해보도록 하겠습니다. 4번의 설명을 보충해서 해볼게요.

const rgb = {
    r: (hexInt >> 16) & 255,
    g: (hexInt >> 8) & 255,
    b: hexInt & 255,
}

우선 자바스크립트에서는 정수를 32bit로 표현한다는 지식을 먼저 이해하고 있어야 합니다.  예를 들어 hexInt가 "0xFFFFFF"라고 한다면 2진수로는 아래와 같이 표현되겠습니다.

0000 0000 1111 1111 1111 1111 1111 1111

 

"hexInt >> 16"은 Red만 추출하기 위해서 hexInt를 16비트만큼 오른쪽으로 시프트합니다. 결과적으로 아래와 같은 값이 오게됩니다.

0000 0000 0000 0000 0000 0000 1111 1111

 

"& 255"로 연산하게 되면 8비트를 제외한 나머지를 0으로 만들고 십진수의 값을 반환합니다. 앞쪽의 비트연산을 실행하면서 예기치 않은 비트가 생길 수 있습니다. 이때 255로 &연산을 함으로써 원하는 값만 뽑아올 수 있게 됩니다.

1000 0000 0000 0000 0000 0000 1111 1111 (hexInt >> 16)
0000 0000 0000 0000 0000 0000 1111 1111 (255)
&
0000 0000 0000 0000 0000 0000 1111 1111 (결과 값)

 

다른 자리수도 마찬가지로 원하는 자릿수를 8비트 자리까지만 시프트 하고 & 255 연산을 통해 1바이트 짜리 값을 얻을 수 있을 것입니다.