컴퓨터공학 300 주제 시리즈의 281번째 글이다. 전체 지도는 여기.

한 줄 요약

래스터는 “어느 칸에 무슨 색이 있는가”를 저장하고, 벡터는 “무엇을 어떻게 그리는가”를 저장한다. 화면에 보이는 순간에는 결국 둘 다 픽셀이 된다.

왜 필요한가

웹 페이지의 로고가 흐릿하게 깨져 보이는 경우가 있다. 대개 작은 PNG 를 고해상도 화면에서 늘려 그렸기 때문이다. 반대로 사진을 SVG 로 바꾸려 하면 파일이 터무니없이 커진다. 두 현상은 같은 질문에서 나온다. 그림을 픽셀 격자로 저장할 것인가, 도형의 기술(description)로 저장할 것인가.

이 구분을 알면 다음 판단이 쉬워진다.

  • 아이콘·로고·도표는 어떤 형식으로 내보낼지
  • 고해상도(HiDPI) 화면 대응을 어떻게 할지
  • 차트 라이브러리가 Canvas 를 쓰는지 SVG 를 쓰는지에 따라 성능이 어떻게 달라지는지
  • 그래픽스 파이프라인의 “래스터화” 단계가 무엇을 하는지

핵심 개념

래스터(raster) 이미지

래스터 이미지는 픽셀의 2차원 배열이다. 각 픽셀은 색 값을 가진다. 흔한 형태는 채널당 8비트, RGB 세 채널이다. 투명도(알파)를 더하면 네 채널이 된다.

너비 W, 높이 H, 채널 C, 채널당 b 비트
압축 전 크기 = W x H x C x b / 8 바이트
예) 1920 x 1080 x 4 x 8 / 8 = 8,294,400 바이트 (약 7.9 MiB)

PNG·JPEG·WebP·GIF 가 대표적인 래스터 형식이다. 해상도가 저장 시점에 고정된다. 확대하면 새 정보가 생기지 않는다. 확대 알고리즘은 없는 픽셀 값을 이웃에서 추정할 뿐이다.

  • 최근접 이웃(nearest neighbor): 가장 가까운 픽셀을 복제한다. 계단이 커진다.
  • 쌍선형(bilinear)·쌍삼차(bicubic): 주변 픽셀을 가중 평균한다. 계단 대신 흐림이 생긴다.

사진처럼 색이 연속적으로 변하고 경계가 복잡한 그림은 래스터가 자연스럽다.

벡터(vector) 그래픽

벡터 그래픽은 도형을 수학적으로 기술한다. 선분, 원, 베지어 곡선, 채우기 규칙, 선 굵기, 변환 행렬 같은 것이다. 웹에서 가장 흔한 벡터 형식은 W3C 표준인 SVG 다.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
  <circle cx="50" cy="50" r="40" fill="steelblue"/>
  <path d="M10 90 L90 10" stroke="black" stroke-width="2"/>
</svg>

viewBox 는 사용자 좌표계를 정한다. 실제로 몇 픽셀로 그려질지는 표시 시점에 결정된다. 그래서 같은 SVG 가 16픽셀 아이콘으로도, 인쇄용 포스터로도 선명하게 나온다. 확대할 때마다 수식에서 다시 픽셀을 계산하기 때문이다.

래스터화(rasterization)

모니터는 픽셀 격자다. 벡터 그림도 화면에 나오려면 어느 순간 픽셀로 바뀌어야 한다. 이 변환을 래스터화라고 한다. 기본 질문은 단순하다. “이 픽셀의 중심(또는 표본점)이 도형 안에 있는가?”

  픽셀 중심이 원 안이면 칠한다
  . . . . . . . .
  . . # # # # . .
  . # # # # # # .
  . # # # # # # .      <- 경계에서 계단(aliasing)이 생긴다
  . # # # # # # .
  . # # # # # # .
  . . # # # # . .
  . . . . . . . .

픽셀당 한 점만 검사하면 경계가 계단처럼 보인다. 이것을 앨리어싱(aliasing)이라 한다. 해결책은 픽셀당 여러 점을 검사해 덮인 비율만큼 색을 섞는 것이다. 이를 안티앨리어싱이라 하고, 다중 표본(MSAA)이나 덮임 비율(coverage) 계산이 대표적이다.

둘을 비교하면

항목 래스터 벡터
저장 내용 픽셀 값 배열 도형·경로·속성
확대 정보가 늘지 않는다 다시 그려서 선명하다
파일 크기 결정 요인 해상도와 압축 도형의 수와 복잡도
잘 맞는 대상 사진, 스캔, 텍스처 로고, 아이콘, 도표, 글꼴
그리기 비용 복사·합성 위주 매번 경로 계산 필요
대표 형식 PNG, JPEG, WebP SVG, PDF 의 경로, 폰트 외곽선

글꼴도 벡터다. TrueType·OpenType 글리프는 곡선 외곽선으로 저장되고, 화면 크기에 맞춰 그때그때 래스터화된다.

브라우저의 두 그리기 모델: Canvas 와 SVG

웹에는 두 API 가 공존한다.

  • <canvas> 는 즉시 모드(immediate mode)다. fillRect 를 호출하면 픽셀 버퍼에 그려지고 끝이다. 무엇을 그렸는지 브라우저는 기억하지 않는다. 다시 그리려면 코드가 전부 다시 호출해야 한다.
  • SVG 는 유지 모드(retained mode)다. 도형이 DOM 노드로 남는다. CSS 로 꾸미고, 이벤트를 붙이고, 속성만 바꿔도 브라우저가 알아서 다시 그린다.

도형이 수천 개를 넘어가면 DOM 노드 비용 때문에 SVG 가 느려지고, Canvas 가 유리해진다. 도형이 적고 상호작용이 많으면 SVG 가 편하다. 차트 라이브러리가 렌더러를 고를 수 있게 해 두는 이유다.

직접 해 보기

원 하나를 수식으로 정의하고 두 해상도로 래스터화해 본다. 그리고 작은 래스터를 확대한 결과와 큰 해상도로 다시 래스터화한 결과를 비교한다. 표준 라이브러리만 쓴다.

# 벡터(수식)로 정의한 원을 서로 다른 해상도로 래스터화한다.
def rasterize_circle(size, cx=0.5, cy=0.5, r=0.4):
    rows = []
    for y in range(size):
        row = ""
        for x in range(size):
            # 픽셀 중심 좌표를 0~1 범위로 정규화
            px, py = (x + 0.5) / size, (y + 0.5) / size
            inside = (px - cx) ** 2 + (py - cy) ** 2 <= r * r
            row += "#" if inside else "."
        rows.append(row)
    return rows

def upscale_nearest(rows, k):
    # 이미 만든 래스터를 k배로 확대: 각 픽셀을 k x k 블록으로 복제
    out = []
    for row in rows:
        wide = "".join(ch * k for ch in row)
        out.extend([wide] * k)
    return out

small = rasterize_circle(8)
print("\n".join(upscale_nearest(small, 2)))
print()
print("\n".join(rasterize_circle(16)))

실행 결과의 일부다. 왼쪽은 8x8 을 2배로 늘린 것, 오른쪽은 수식에서 16x16 으로 다시 계산한 것이다.

8x8 을 2배 확대            16x16 재래스터화
................           ................
................           ................
....########....           .....######.....
....########....           ...##########...
..############..           ...##########...
..############..           ..############..
..############..           ..############..

확대본은 2픽셀 단위 계단이 그대로 남는다. 재래스터화한 쪽은 1픽셀 단위로 윤곽이 다듬어진다. 같은 크기의 결과물이지만 담긴 정보량이 다르다. 이것이 “벡터는 확대해도 깨지지 않는다”의 실제 의미다.

연습으로 inside 를 픽셀당 4개 표본(예: 0.25, 0.75 오프셋)의 평균으로 바꾸고, 덮인 비율에 따라 " .:#" 중 하나를 고르게 해 보라. 간단한 안티앨리어싱이 된다.

현업에서는

  • 프런트엔드 아이콘은 SVG 로 관리하는 경우가 많다. 색을 currentColor 로 두면 CSS 글자색을 따라간다. 사진·썸네일은 WebP·JPEG 같은 래스터를 쓰고, srcset 으로 화면 밀도별 해상도를 따로 준다.
  • 모니터링 대시보드(예: Grafana)는 시계열 점이 수만 개가 될 수 있다. 이런 차트는 Canvas 계열 렌더링이 흔하다. 노드 몇 개의 상태를 그리는 작은 홈랩 대시보드라면 SVG 로도 충분하다.
  • PDF 보고서를 만들 때 차트를 PNG 로 넣으면 인쇄 시 흐려진다. 벡터(SVG·PDF)로 넣으면 선명하지만, 점이 수십만 개인 산점도는 PDF 가 무거워진다. 이럴 땐 점 영역만 래스터로 굽고 축·글자는 벡터로 두는 혼합 방식을 쓴다.
  • 게임과 3D 는 삼각형(벡터적 기술)을 GPU 가 매 프레임 래스터화한다. 다음 글들에서 다룰 렌더링 파이프라인의 핵심 단계가 바로 이 래스터화다.

확인 문제

  1. 2560x1440, RGBA 채널당 8비트 이미지의 압축 전 크기는 몇 바이트인가?
  2. 벡터 로고를 64픽셀로 래스터화한 PNG 를 512픽셀로 늘렸다. 원본 SVG 를 512픽셀로 그린 것과 무엇이 다른가?
  3. 앨리어싱이 생기는 근본 원인을 한 문장으로 말하라.
  4. 도형 5만 개를 매 프레임 갱신하는 시각화에 SVG 와 Canvas 중 무엇이 일반적으로 유리한가? 이유는?

풀이

  1. 2560 x 1440 x 4 = 14,745,600 바이트다.
  2. 늘린 PNG 는 64픽셀 시절의 정보만 가지고 추정한 값이라 계단이나 흐림이 보인다. SVG 를 512픽셀로 그리면 경계를 512픽셀 해상도로 새로 계산하므로 선명하다.
  3. 연속적인 도형 경계를 픽셀당 유한한 표본으로만 판정하기 때문에, 표본 간격보다 세밀한 정보가 계단 모양으로 왜곡된다.
  4. Canvas 다. SVG 는 도형마다 DOM 노드를 유지하므로 노드 생성·스타일 계산·레이아웃 비용이 쌓인다. Canvas 는 픽셀 버퍼에 직접 그리고 상태를 유지하지 않는다.

더 읽을거리 (References)