
느린 네트워크에서 큰 이미지를 열면 가끔 화면이 위에서 아래로 채워진다. 마치 누군가 아주 성실하게 롤러로 이미지를 칠하는 것처럼 보인다. 물론 브라우저 안에 그런 직원은 없다. 있다면 우리보다 야근을 더 많이 하고 있을 것이다.
이 현상은 단순한 시각 효과가 아니라 네트워크 전송, 브라우저 버퍼링, 이미지 디코딩이 맞물린 결과다. 이미지는 완성된 파일이 모두 도착한 뒤에만 그려지는 것이 아니라, 형식과 인코딩 방식에 따라 도착한 데이터부터 점진적으로 해석될 수 있다. 이 부록에서는 이미지 로딩을 stream 관점에서 바라보며, 왜 어떤 이미지는 조금씩 보이고 어떤 이미지는 한 번에 나타나는지 살펴본다.
데이터는 처음부터 완성된 파일이 아니다
HTTP로 이미지를 다운로드할 때 브라우저는 완성된 파일을 한 번에 받지 않는다. 네트워크는 데이터를 여러 조각으로 나누어 전달한다. 브라우저는 이 데이터를 스트림 형태로 받는다. fetch()의 Response.body가 ReadableStream<Uint8Array> 타입인 것도 바로 이 때문이다. 즉 이미지 다운로드는 내부적으로 다음과 같은 구조를 가진다.
network
↓
byte stream
↓
browser buffer여기까지는 일반적인 HTTP 응답과 크게 다르지 않다. 하지만 이미지의 경우 이 다음 단계가 조금 다르다.
브라우저는 이미지 데이터를 점진적으로 해석할 수 있다
이미지 파일은 단순한 바이트 덩어리가 아니다. 내부에는 픽셀 데이터를 어떻게 해석해야 하는지에 대한 구조가 존재한다. 브라우저의 이미지 디코더는 이 구조를 이용해 파일의 일부만 도착한 상태에서도 이미지를 해석할 수 있는 경우가 있다.
많은 이미지 포맷은 **스캔라인(scanline)**이라는 구조를 가진다. 이미지는 내부적으로 다음과 같은 방식으로 저장되는 경우가 많다.
row 1
row 2
row 3
row 4
...즉 이미지 데이터가 위에서 아래로 이어지는 행(row) 단위로 저장된다. 브라우저는 네트워크에서 데이터가 도착하는 즉시 이 행 단위 데이터를 해석할 수 있고, 해석된 부분을 화면에 그릴 수 있다. 그래서 다운로드가 진행되는 동안 화면에서는 다음과 같은 현상이 나타난다.
████████
████████
████████
--------
--------
--------이미지의 윗부분은 이미 렌더링되고, 아랫부분은 아직 도착하지 않은 상태다. 데이터가 더 도착하면 브라우저는 다음 행을 해석하고 다시 화면을 업데이트한다.
이미지가 항상 이렇게 나타나는 것은 아니다
하지만 모든 이미지가 항상 위에서 아래로 나타나는 것은 아니다. 어떤 이미지는 다운로드가 끝날 때까지 아무것도 보이지 않다가 갑자기 한 번에 나타나기도 한다. 이 차이는 이미지 포맷과 브라우저 디코딩 방식에 따라 달라진다. JPEG에는 대표적으로 두 가지 저장 방식이 있다.
baseline JPEG
progressive JPEG
baseline JPEG에서는 이미지가 비교적 단순한 순서로 디코딩된다. 일부 데이터가 도착하면 부분적으로 이미지를 그릴 수 있지만, 브라우저가 충분한 데이터가 쌓일 때까지 렌더링을 미루는 경우도 있다. 반면 progressive JPEG에서는 이미지가 여러 단계의 패스로 나누어 저장된다. 처음에는 전체 이미지의 대략적인 형태가 나타나고, 이후 데이터가 더 도착하면서 점점 더 선명해지는 방식이다. 그래서 progressive JPEG에서는 이미지가 흐릿하게 먼저 나타났다가 점점 선명해지는 장면을 볼 수도 있다.
Streams API 관점에서 보면
Streams API 관점에서 보면 이 현상은 매우 자연스럽다. 브라우저는 네트워크에서 바이트 스트림을 받고, 이 데이터를 내부 버퍼에 쌓는다. 이미지 디코더는 이 버퍼를 읽어 해석할 수 있는 부분이 있는지 확인하고, 해석 가능한 데이터가 생기면 즉시 화면에 반영한다. 이 구조를 단순화하면 다음과 같다.
network stream
↓
browser buffer
↓
image decoder
↓
partial rendering즉 이미지 렌더링은 완성된 파일을 기다리는 과정이 아니라, 스트림 위에서 점진적으로 이루어지는 해석 과정이다.
댓글
댓글을 불러오는 중...