이미지 색상 팔레트 추출기

사진을 업로드하면 히스토그램 기반 색상 양자화를 사용해 주요 색상을 자동으로 추출합니다 — 헥스 코드가 포함된 팔레트를 브라우저에서 전적으로 계산하며, 어떤 것도 서버에 업로드되지 않습니다.

조회수 249회

주요 색상을 추출할 사진을 업로드하세요 — 모든 과정이 브라우저에서 처리됩니다.

픽셀을 단순히 세는 것만으로는 부족한 이유

사진의 주요 색상을 찾는 가장 직관적인 방법은 간단해 보입니다. 정확히 같은 색상을 가진 픽셀 수를 세어 가장 많이 나타나는 색상을 남기는 것입니다. 하지만 실제로는 이 방법이 거의 즉시 실패합니다. 예를 들어 파란 하늘을 찍은 디지털 사진은 하나의 균일한 파란색이 아니라, 센서 노이즈와 자연광의 미묘한 변화, JPEG 압축 아티팩트로 인해 픽셀마다 조금씩 다른 수천 가지 파란색으로 이루어져 있습니다. 인접한 두 하늘 픽셀이 rgb(91, 148, 214)와 rgb(92, 149, 213)일 수 있는데, 눈으로 보기에는 완전히 같지만 수치상으로는 다릅니다. 정확한 값으로 셀 경우 둘 다 "가장 흔한 색상"이 되지 못합니다 — 각각 한두 번만 등장하며, 눈에는 거의 동일하지만 기술적으로는 서로 다른 수천 가지 파란색 사이에 묻혀버립니다. 이런 단순한 접근 방식은 팔레트가 아니라 노이즈를 반환합니다.

해결책은 정확한 픽셀 값을 세는 대신 비슷한 색상들의 버킷(bucket)을 세는 것입니다. 이 도구는 보통 0에서 255까지 범위를 갖는 빨강, 초록, 파랑 채널 각각을 Math.floor(value / 32) * 32 연산을 통해 32개 값 단위의 동일한 8개 구간으로 나눕니다. 이렇게 하면 1,670만 개에 달하는 가능한 RGB 조합이 8 × 8 × 8 = 512개의 관리 가능한 버킷 격자로 축소됩니다. 크기가 조정된 이미지의 모든 픽셀은 빨강, 초록, 파랑 값이 어느 구간에 속하는지에 따라 정확히 하나의 버킷에 배정되며, 해시 맵 역할을 하는 단순한 객체를 이용해 각 버킷에 몇 개의 픽셀이 들어갔는지, 그리고 그 픽셀들의 실제 빨강, 초록, 파랑 값의 합계가 계속 누적됩니다.

버킷에서 팔레트로

모든 픽셀을 센 뒤에는 버킷을 픽셀 수 기준으로 정렬하여 가장 많은 6~8개의 버킷만 남깁니다. 다만 각 버킷에 표시되는 색상은 버킷의 수치적 경계(예: "빨강 224-255, 초록 128-159, 파랑 0-31")가 아닙니다 — 그렇게 하면 32×32×32 큐브의 다소 임의적인 한쪽 구석에 불과하게 됩니다. 대신 이 도구는 실제로 해당 버킷에 속한 모든 픽셀의 빨강, 초록, 파랑 값의 실제 평균을 계산하며, 이는 사람의 눈이 그 이미지 영역을 대표하는 색상으로 인식하는 것에 훨씬 더 가깝습니다.

이러한 히스토그램 비닝(binning) 방식은 큰 색상 집합을 작고 대표성 있는 집합으로 줄이는 색상 양자화(color quantization)의 의도적으로 단순한 형태입니다. 더 정교한 고전적 대안으로는 폴 헤크버트(Paul Heckbert)가 1980년 MIT 석사 논문에서 정의한 중앙값 절단(median-cut) 기법이 있는데, 이는 고정 크기 상자를 사용하는 대신 색 공간을 가장 넓은 차원을 따라 재귀적으로 분할하는 방식이며, 지금도 GIF 이미지를 256색 팔레트로 축소하는 데 쓰이는 기술의 근간입니다. 중앙값 절단은 복잡한 이미지에 대해 일반적으로 더 지각적으로 정확한 팔레트를 만들어내지만, 그만큼 구현이 복잡해집니다. 이 도구가 사용하는 고정 격자 히스토그램 비닝 방식은 이해하기 더 쉽고, 브라우저에서 즉시 실행될 만큼 빠르다는 점에서 절충점을 택한 것입니다.

계산이 시작되기 전에, 업로드된 이미지는 HTML5 캔버스 API를 이용해 먼저 너비가 200픽셀을 넘지 않도록 축소됩니다. 이 과정은 읽고 버킷에 배정해야 할 픽셀 수를 크게 줄이며, 색상 버킷이 세부 디테일이 아니라 넓은 색조 영역을 추적하기 때문에 결과 팔레트에는 거의 영향을 주지 않습니다. 이미지를 그리고, 크기를 조정하고, getImageData()로 픽셀 데이터를 읽고, 버킷 평균을 계산하는 전 과정은 브라우저 메모리 안에서 로컬로 이루어집니다. 사진은 어떤 서버에도 업로드되지 않습니다.

자주 묻는 질문

이 도구는 왜 가장 흔한 단일 색상을 바로 찾지 않나요?

실제 사진에서는 완전히 동일한 RGB 값을 가진 픽셀이 거의 없기 때문입니다 — 자연광, 센서 노이즈, 압축으로 인해 눈에는 같아 보이는 색상도 수치상으로는 조금씩 다릅니다. 그래서 어떤 정확한 값도 의미 있을 만큼 자주 반복되지 않습니다. 먼저 비슷한 색상을 버킷으로 묶는 것이 이 계산을 가능하게 만드는 핵심입니다.

팔레트는 몇 가지 색상을 보여주나요?

최대 8가지입니다 — 이 도구는 모든 픽셀을 512개의 가능한 색상 버킷(빨강, 초록, 파랑 각각 8단계)으로 묶은 뒤, 가장 많은 픽셀을 포함한 6~8개의 버킷을 보여줍니다. 매우 단순하고 대부분 단색에 가까운 이미지는 채워진 버킷이 몇 개뿐이라면 더 적은 색상을 반환할 수 있습니다.

GIF 이미지를 만들 때 쓰이는 기법과 같은 건가요?

정확히 같지는 않지만 관련이 있습니다. GIF는 보통 고정 격자 대신 색 공간을 재귀적으로 분할하는, 1980년에 등장한 더 정교한 양자화 알고리즘인 중앙값 절단을 이용해 256색으로 줄어듭니다. 이 도구는 계산이 더 빠르고 이해하기 쉬운 대신, 복잡한 이미지에서는 지각적 정확도가 다소 떨어지는 단순한 고정 크기 히스토그램 비닝을 사용합니다.

여기에 사진을 업로드하면 서버로 전송되나요?

아니요 — 이미지는 브라우저 안에서만 디코딩되어 캔버스 요소에 그려지며, 모든 픽셀은 캔버스의 getImageData() API를 이용해 로컬에서 읽히고 분석됩니다. 사진과 관련된 그 어떤 것도 어디로도 전송되지 않습니다.

이미지를 분석하기 전에 왜 크기를 줄이나요?

순전히 속도 때문입니다. 전체 해상도의 사진에서 모든 픽셀을 읽고 버킷에 배정하려면 수천만 개의 값을 처리해야 할 수 있습니다. 이미지를 먼저 최대 너비 200px로 줄이면 이를 크게 줄이면서도 결과에는 거의 영향을 주지 않는데, 주요 색상은 세부 디테일이 아니라 넓은 영역을 나타내기 때문입니다.

댓글

아직 댓글이 없습니다 — 첫 댓글을 남겨보세요!

비슷한 도구