사용법
파일 선택기에 이미지를 떨어뜨립니다. JPEG·PNG·WebP·GIF·AVIF 모두 동작합니다. 브라우저가 `<img>` 요소로 디코딩하기 때문입니다. 본 도구는 이미지를 긴 변 64 px로 축소하고 선택한 X/Y 컴포넌트 수(기본 4×3)로 Wolt의 `blurhash` 인코더를 실행한 뒤 결과 문자열(약 28자)과 256 px 디코딩 미리보기 캔버스를 출력합니다. 컴포넌트 수를 늘리면 세부를 더 잘 잡지만 해시는 길어집니다. 4×3은 28자, 9×9는 67자입니다. 4×3은 Unsplash가 공개한 기본값이며 거의 항상 충분합니다.
인코딩된 BlurHash는 base83 문자열이라 데이터베이스 컬럼, API 응답, JSX prop에 들어갑니다. 렌더링 측에서는 같은 `blurhash` 라이브러리(또는 드롭인용 `react-blurhash`)로 실제 플레이스홀더 크기에 디코딩해 `<canvas>`에 그리거나 Next.js Image 컴포넌트의 `placeholder="blur"`로 사용합니다. 대안과의 트레이드오프: 원본 64 px 썸네일은 1~4 KB로 외관이 거의 같지만 BlurHash는 30 바이트라 다른 이미지 메타데이터와 같은 JSON 응답에 들어갑니다. 페이지당 100개 이상의 썸네일을 내려보낼 때 차이가 누적됩니다. 인코더는 브라우저 안에서 완결되며 이미지 데이터는 네트워크 엔드포인트에 도달하지 않습니다.
예제
Unsplash 스타일 4×3 기본
입력
image: landscape-photo.jpg (4032 × 3024 px, 2.8 MB)
encode: 4 × 3 components (default)
use case: general-purpose blur placeholder
출력
hash: LEHLk~WB2yk8pyo0adR*.7kCMdnj
hash length: 28 characters
bytes: 30 (UTF-8) — fits in a single PostgreSQL TEXT cell
decoded preview: 256 × 192 canvas, looks like a heavily blurred version of the input
4×3은 Unsplash의 운영 설정이며 BlurHash 공개 블로그가 권장한 기본값입니다. 첫 문자(`L`)는 컴포넌트 수를 인코딩하고, 다음 두 문자는 최대 AC 성분(정규화에 사용)을, 나머지는 DC 성분(평균색)과 각 채널의 AC 성분(그래디언트 항)을 인코딩합니다. 4×3 해시는 이미지의 전체 형태와 지배색을 충분히 포착하므로 풀해상도 이미지가 스트리밍되는 동안 사용자는 즉각적인 피드백을 받습니다. 해시는 로케일 불변이라 macOS와 Linux에서 같은 이미지를 줘도 바이트 단위로 동일한 출력을 만듭니다.
고해상도 9×9 포트레이트 해시
입력
image: portrait.jpg (face occupies center 60% of frame)
encode: 9 × 9 components
use case: profile photo blur — face shape should be discernible
출력
hash: LKO2?V%2Tw=w]~RBVZRi};RPxuwH%3t8WC0L0~Lo~SRP$|JE,N9d%6gP0ZJ%R
hash length: 67 characters
bytes: 69
perceptual quality: skin tone, hair shape, background color all distinguishable
encoder time: ~120 ms in browser (vs. ~5 ms for 4×3)
포트레이트, 프로필 사진, 상품 히어로 이미지처럼 풀이미지가 로드되기 전 시청자가 피사체를 알아채길 원할 때는 컴포넌트를 6×6이나 9×9로 올립니다. 비용: 인코더 시간은 O(width × height × components_x × components_y)이므로 9×9 해시 인코딩은 4×3보다 약 30배 걸립니다. 합리적인 입력 크기라면 1초 미만이지만 서버 측에서 수천 장을 일괄 인코딩한다면 알아둘 가치가 있습니다. 최대 컴포넌트는 BlurHash 사양상 9×9입니다. 인코더는 더 큰 값도 받지만 입력이 내부적으로 32 × 32로 다운스케일되므로 정보량은 더 늘지 않습니다.
단색 배경 — 최소 1×1
입력
image: solid-blue-sky.jpg (almost no detail)
encode: 1 × 1 components
use case: placeholder for known-uniform images (sky, gradient, brand backdrop)
출력
hash: 00OG^x
hash length: 6 characters
bytes: 6
decoded preview: single solid color matching the image's average
encoder time: <1 ms
1×1은 BlurHash를 DC 성분만으로 축약합니다. 이미지의 평균색뿐입니다. 해시는 6자이고 결과는 CSS의 단색 `background-color`와 구분되지 않습니다. 실이미지를 기다리는 동안 대표색으로 레이아웃 공간만 확보하면 되는 플레이스홀더에 유용합니다. 브라우저 힌트 `<img style="background-color: #...">`이 절반 비용으로 같은 일을 하지만 BlurHash는 기존 이미지 메타데이터 파이프라인에 색을 일원화합니다. 의미 있는 구조(로고, 제품, 얼굴, 풍경)가 있는 이미지에는 최소 3×3을 사용하세요.
자주 묻는 질문
BlurHash는 누가 왜 만들었나요?
BlurHash는 2018년에 **Dag Ågren**이 공개했습니다. 그는 **Wolt**(음식 배달, 이후 DoorDash가 인수)의 핀란드 엔지니어였습니다. Wolt 모바일 앱은 리스트 스크롤 중 이미지 플레이스홀더가 필요했습니다. 사용자가 느린 모바일 회선으로 식당 메뉴를 둘러볼 때 사진이 로드되는 빈 회색 박스가 거슬렸기 때문입니다. 단색 평균색 블록(너무 단순), 작은 사전 디코딩 썸네일(JSON 페이로드에서 너무 큼), SQIP / Polished 스타일 SVG 근사(너무 깨지기 쉬움)를 시도한 뒤 BlurHash는 중간을 택했습니다. 인식 가능한 흐릿함으로 디코딩되는 ~30 바이트입니다. 참조 구현은 C이고 TypeScript, Swift, Kotlin, Python, Go, Rust 포팅이 있습니다. 알고리즘 자체는 base83에 패킹된 작은 이산 코사인 변환입니다. **MIT 라이선스**라 상용 앱에 자유롭게 출시할 수 있습니다.
BlurHash는 어떻게 이미지를 30바이트로 압축하나요?
**이산 코사인 변환**(DCT)입니다. JPEG의 핵심과 같은 수학이지만 고주파 계수를 적극적으로 잘라냅니다. 인코더는 입력 이미지를 축소하고 작은 이미지의 DCT를 계산해 각 RGB 채널에서 components_x × components_y개의 가장 낮은 주파수 계수만 보관한 뒤 값을 base83 ASCII로 패킹합니다. 디코딩은 역과정입니다. 계수를 읽고 IDCT, 캔버스에 그립니다. 압축비는 DCT 계수의 약 99%를 버리는 데서 나옵니다. 결과에는 몇 개의 부드러운 컬러 그래디언트 이상의 세부가 없지만, 그 그래디언트는 작은 크기에서 이미지의 "느낌"을 전달합니다. 인코더는 내부적으로 선형 광 sRGB를 사용해 색 혼합을 자연스럽게 보이게 하고 디코더는 렌더링 전에 sRGB로 되돌립니다.
BlurHash 대 LQIP / SQIP / ThumbHash — 어느 것을 써야 하나요?
**LQIP**(Low-Quality Image Placeholder)는 그냥 다운스케일된 JPEG(품질 5~10%, 보통 긴 변 16~32 px)입니다. data URI로 인라인하면 200~500 바이트로, BlurHash보다 크지만 JavaScript 없이 렌더링되고 Next.js의 `placeholder="blur"`로 네이티브 지원됩니다. **SQIP**는 이미지를 근사하는 SVG 프리미티브를 생성합니다. 시각적으로 독특하지만 1~3 KB이고 인코딩이 느립니다. **ThumbHash**(2023년, Figma의 Evan Wallace 작)는 BlurHash의 현대적 경쟁자입니다. ~25 바이트, 약간 더 나은 시각 품질, 투명도 지원, 더 빠른 디코딩. 최대한의 에코시스템 지원이 필요하면 **BlurHash**(주요 모든 언어에 포팅 있음, Unsplash 통합). 오늘 시작하는 신규 프로젝트에는 **ThumbHash**(같은 아이디어, 약간 더 나은 트레이드오프, 다만 에코시스템은 더 어림). 렌더링 경로에서 JavaScript를 실행하지 않거나 CDN이 이미지를 이미 최적화한다면 **LQIP**.
Next.js나 React에서 BlurHash를 어떻게 사용하나요?
React에서는 **`react-blurhash`** 패키지가 가장 간단합니다. `<Blurhash hash={hash} width={400} height={300} />`이 클라이언트 측에서 canvas를 그립니다. 서버 렌더링 React라면 서버 측에서 해시를 RGBA 픽셀로 디코딩해 base64 데이터 URL을 만들고 그것을 `<img src=...>`이나 CSS `background-image`로 사용합니다. Next.js의 `placeholder="blur"`가 정적 이미지에서 내부적으로 하는 일입니다. Next.js Image에 BlurHash를 끼우려면 페이지의 `getStaticProps` / Server Component fetch에서 해시를 데이터 URL로 디코딩해 `blurDataURL`로 전달하고 `placeholder="blur"`를 설정하세요. 주의: Next.js의 내장 blur는 BlurHash 문자열이 아니라 base64 JPEG/PNG 데이터 URL을 기대합니다. 먼저 디코딩해야 합니다. Vue, Svelte, Solid 모두 유사 패키지가 있습니다. `vue-blurhash`, `svelte-blurhash`, `solid-blurhash`. 순수 HTML로도 됩니다. `blurhash` npm 패키지를 로드해 ImageData로 디코딩한 뒤 canvas에 그리세요.
BlurHash를 브라우저가 아닌 서버에서 인코딩할 수 있나요?
네, 운영에서는 보통 그렇게 합니다. Node.js의 `blurhash` 패키지는 본 도구가 쓰는 것과 같은 라이브러리이며 브라우저 밖에서 동작할 뿐입니다. 임의의 이미지 형식을 디코딩하고 인코딩 전에 축소하려면 `sharp`와 결합합니다. Python: `blurhash-python`. Go: `github.com/buckket/go-blurhash`. Rust: `blurhash` 크레이트. 전형적인 파이프라인: 이미지 업로드 → `sharp({raw}).resize(64).raw().toBuffer()` → `encode(buffer, 64, 64*h/w, 4, 3)` → DB의 이미지 URL 옆에 해시 저장. 여기 브라우저 도구는 일회성 인코딩, 컴포넌트 수 설계 검토, 이미지 업로드 파이프라인을 직접 제어하지 못할 때를 위한 것입니다. 서버 측 인코딩은 유사성 쿼리용으로 해시를 인덱싱할 수 있다는 이점도 있습니다. DC 성분만으로도 "지배색이 비슷한 이미지 찾기"용의 작은 퍼셉추얼 해시로 작동합니다.
디코딩 미리보기가 원본보다 채도가 높아 보이는 이유는?
BlurHash는 선형 sRGB 색공간으로 인코딩됩니다. 알고리즘은 감마 곡선을 제거한 *후에* 색을 평균화하고 DCT 변환하므로 지각적으로 올바른 색 혼합을 얻습니다. 디코더에 `punch` 값을 1보다 크게 지정하면(`react-blurhash`의 기본은 1.0이지만 일부 통합은 1.5를 기본으로 합니다) AC 성분이 증폭되어 색이 원본보다 선명해집니다. 충실한 재현에는 `punch: 1.0`으로 디코딩하세요. 실이미지 도착 전에 플레이스홀더를 "생동감 있게" 만들고 싶을 때만 더 높은 punch를 사용하고 스왑 시점에 짧은 색 변화가 일어남을 감수하세요. 일부 BlurHash 통합은 선형 광 보정을 빠뜨려 대신 색이 빠진 출력을 내기도 합니다. 색이 이상하면 참조 구현과 비교해 보세요.
관련 개념
BlurHash는 **플레이스홀더 이미지** 또는 **점진적 이미지 로딩**이라는 범주에 속하며 그 계보는 인터레이스 GIF / JPEG 점진 디코딩(1986년 CompuServe를 위한 Marc Adler의 작업, 1994년 JFIF progressive baseline)까지 거슬러 올라갑니다. 공통 목표: 페이지가 아무것도 로드하지 않는 것처럼 보이지 않도록 시청자에게 *무엇인가*를 즉시 표시하고 세부는 시간을 두고 도착시키기. 현대 웹은 2014년경 이 아이디어를 다시 꺼냈습니다. **Medium**이 작은 임베드 썸네일로 CSS blur 필터를 구동하는 유명한 블로그 글을 발표했습니다. 지금 우리가 **LQIP**라 부르는 것입니다. 이후: Cloudinary의 **벡터화 SVG** 플레이스홀더(2016), Tobias Baldauf의 **SQIP**(2017), Wolt의 **BlurHash**(2018), Figma 엔지니어 Evan Wallace의 **ThumbHash**(2023). 각 반복은 바이트 수와 지각 품질, 에코시스템 성숙도의 트레이드오프를 바꿨습니다. 2024년 이후 세대에는 **AVIF / WebP 점진 로드**가 포함되며 이는 브라우저가 풀이미지 도착 전에 같은 파일의 저세부 버전을 렌더링할 수 있게 합니다.
바닥의 수학은 1974년 Nasir Ahmed가 도입한 **이산 코사인 변환**(DCT)이며 이는 JPEG, MP3, MPEG, H.264 비디오를 떠받치는 같은 변환입니다. DCT는 공간 주파수를 분리합니다. 저주파 성분은 넓은 그래디언트와 색 전환을 잡고, 고주파 성분은 날카로운 에지와 가는 텍스처를 잡습니다. 고주파를 버리면 이미지는 부드럽게 열화합니다. 인간은 저주파 콘텐츠를 훨씬 더 강하게 지각하기 때문입니다. BlurHash는 이를 논리적 극한까지 밀어 RGB 채널당 9~81개의 DCT 계수만 보관해 빽빽이 패킹합니다. JPEG는 수천 개를 보관합니다. 같은 통찰은 역이미지 검색 엔진에 쓰이는 썸네일 생성과 이미지 해싱 기법(pHash, dHash 같은 지각 해시는 비슷하게 DCT 계수를 잘라냅니다)의 기반이기도 합니다.
인접한 세 가지 관심사가 BlurHash와 교차합니다. **누적 레이아웃 시프트**(CLS)는 콘텐츠가 로드되며 요소가 이동하는 페이지를 벌점하는 Core Web Vitals 지표입니다. 이미지 플레이스홀더는 정확한 픽셀 공간을 예약해 CLS를 막습니다. 플레이스홀더가 최종 이미지와 같은 너비 × 높이를 차지하므로 BlurHash는 여기서 잘 동작합니다. **접근성**: BlurHash 플레이스홀더 자체에는 alt 텍스트나 의미 콘텐츠가 없습니다. `<img alt="...">`과 짝지어 스크린 리더가 canvas가 아닌 alt를 읽도록 하세요. **콘텐츠 보안 정책**: BlurHash 디코딩은 `<canvas>`와 인라인 base64 데이터 URI를 사용하며 두 가지 모두 엄격한 CSP의 `script-src`나 `img-src` 규칙으로 제한될 수 있습니다. 디코딩된 BlurHash 썸네일을 데이터 URL로 서빙한다면 `default-src`가 `data:` URI를 허용하는지 확인하세요. 어느 것도 BlurHash 채택의 차단 요소는 아니며 통합 시 검증할 운영 세부일 뿐입니다.