색상표현을 중심으로 비트마스킹, 시프트연산자를 활용하면 가장 효율적으로 색상의 표현을 변경할 수 있습니다. 이를 통해 쉽게 접할 수 없었던 비트마스킹과 시프트연산자에 대해 이해해볼 수 있습니다.
들어가며
프로젝트 내에서 색상을 palette
변수에 저장해서 필요한 곳에서 사용하고 있었습니다. 주로 Hexadecimal(16진수)로 표현된 값으로 저장하였는데요, 예를 들어 palette.white = '#ffffff'
이런식입니다.
특정 상황에서 엘리먼트에 색상이 제대로 반영되었는지 확인하기 위한 테스트 코드도 존재하였습니다. 아래의 예시에서는 element
의 글자색상이 #ffffff
인지 검사합니다.
// 엘리먼트에 포함된 CSS 속성들을 가져옵니다.
const styles = getComputedStyle(element)
expect(styles.color).toBe(palette.white)
그러나 테스트는 실패하게 되는데요. 왜냐하면 브라우저에서는 주어진 색상을 RGB로 변환하여 화면에 렌더링하기 때문입니다. 따라서 선언한 값(palette.white
)과 계산된 값(styles.color
)이 다르다는 테스트 결과를 받게됩니다.
Expected: "#FFFFFF"
Received: "rgb(255, 255, 255)"
따라서 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에서 RGB로 변환
위에서 설명한 색상표현에 근거하면, Hex로 표현된 문자 두자리씩을 10진수로 진법변환을 진행하면 RGB의 Red, Green, Blue에 대한 값을 얻을 수 있습니다. 변환 함수는 다음과 같습니다.
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
}
const rgb = {
r: (hexInt >> 16) & 255,
g: (hexInt >> 8) & 255,
b: hexInt & 255,
}
return `rgb(${rgb.r}, ${rgb.b}, ${rgb.g})`
}
- CSS에서 16진수로 컬러를 표현할때 #가 맨앞에 오기 때문에 16진수의 "숫자"만 뽑아오기 위해 #를 제거합니다.
- CSS에서 16진수로 컬러를 표현할때 #FFF처럼 세자리로도 표현가능하기 때문에 세자리를 여섯자리로 바꿔줍니다.
- 6자리 16진수로 표현된 문자열을 parseInt(num, 16)로 16진수 정수형으로 변환합니다.
- 원하는 컬러 자리 값만 추출하기 위해 우측 시프트 연산을 실행하고 RGB 컬러 값에 딱 맞게 가져오도록 255로 & 연산합니다.
비트마스크 이해하기
비트의 특성인 2진수 표현을 자료구조로 사용하는 방법을 비트마스크라고합니다. 즉 4번에서 사용한 우측 시프트 연산은 2진수의 표현 방식을 이용한 변환 방식입니다. 이에 대해 보충해서 설명하겠습니다.
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비트 만큼 오른쪽으로 시프트합니다. 결과적으로 다음과 같은 2진수 값으로 표현됩니다.
0000 0000 0000 0000 0000 0000 1111 1111
& 255
로 연산하게 되면 8비트를 제외한 나머지를 0으로 만들고 10진수의 값을 반환합니다. 앞쪽의 비트연산을 실행하면서 아래 처럼 예기치 않은 비트가 생길 수 있습니다. 이때 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바이트 짜리 값을 얻을 수 있습니다.
정리
비트마스크는 실무에서 자주 사용되는 자료구조는 아닙니다만, 해당 방법에 대해서 이해함으로써 변환 또는 다른 활용부분에 있어서 컴퓨터의 2진 구조를 활용할 방법이 있다는 것을 염두하는 것은 좋은 접근이 될 것입니다.
이 방법이 아니였다면, hex로 표현된 string의 문자하나씩 반복문을 돌면서 10진수로 변환했어야 하니까요.