월간 보관물: 2013 10월

[TECH] 벡터 그래픽(vector graphics)

컴퓨터 환경에서 2D 그래픽을 표현하는 방법은 크게 두 가지가 있다. 먼저, 가장 흔히 사용되는 비트맵 방식은 이미지의 각 픽셀에 해당하는 값을 모두 가지고 있는 방식이다. 즉, 비트맵 방식을 이용하여 1080×1024 크기의 이미지를 4 바이트 RGBA 로 저장하면 1080x1024x4 bytes 의 저장 공간이 필요하게 된다. (실사 사진을 저장할 때 주로 사용되는 JPEG 는 손실압축을 하기 때문에 훨씬 적은 용량이 필요하다.) 그리고 오늘 얘기할 벡터 방식은 수학적 표현을 이용하여 선과 면을 표현하는 방식이다. 예를 들어, 기본 크기가 1080×1024 인 이미지 위에 점 (0x0) 에서 점 (1080×1024) 로 직선을 연결하기 위해서는 아래와 같이 한줄로 표현할 수 있다.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<line x1="0" y1="0" x2="1080" y2="1024" style="stroke:rgb(255,0,0);stroke-width:2"/>
</svg>

위의 코드는 대표적인 벡터 방식의 이미지 포맷인 SVG 를 이용하여 위에서 설명한 직선을 표현한 예이다. 위의 단편적인 예를 보면 비트맵 방식보다 벡터 방식이 저장 공간 측면에서 훨씬 유리하다고 생각할 수 있겠지만, 이건 경우에 따라 굉장히 다르다. 벡터 방식은 수학적으로 간단히 표현되는 이미지를 크기 상관없이 표현할 수 있는 장점이 있지만, 무수히 많은 작은 점과 같은 도형으로 이루어진 이미지는 오히려 비트맵보다 훨씬 비효율적이다. 예를 들어 실사 사진을 벡터 방식으로 표현할 수 있을까? 말도 안되는 일이다. 이처럼 비트맵과 벡터 방식은 각기 장단점을 가지고 자기만의 영역을 구축해가고 있다.

최근 애플이 iOS7 을 공개하면서 효과적인 애니메이션이 가능한 벡터 아이콘 기능을(움직이는 시계 아이콘) 소개하였다. 사실 기존의 비트맵 아이콘에서 벡터 아이콘으로 넘어가게 되면 여러 가지 장점이 생기긴 한다. 일단 대부분의 아이콘들은 벡터 방식에 더 어울리는 깔끔하고 딱 떨어지는 느낌이 강하다. 그리고 프레임이 높은 애니메이션을 표현하기 위해서는 벡터 방식이 유리한 것도 사실이다. 비트맵 방식의 애니메이션은 프레임당 한 장씩의 이미지를 가지고 있어야 하기 때문에 프레임이 높아질 수록 문제가 될 수 있다. 이에 반해 SVG 포맷에 이미 포함되어 있는 애니메이션 기능을 보면 아래 코드와 같이 간단히 한줄을 추가함으로써 원하는 프레임으로 애니메이션을 표현하는 것이 가능하다.

<svg>
<rect width="200" height="200" fill="slategrey">
<animate attributeName="height" from="0" to="200" dur="3s"/>
</rect>
</svg>

그리고 벡터 방식의 가장 중요한 장점은 바로 해상도에 자유롭다는 것이다. 현재의 스마트폰도 여러 가지 크기의 해상도를 지원하고 있고, 앞으로 다른 수많은 스마트 장비들이 나오면 서로 다른 해상도 때문에 소프트웨어 개발자들은 굉장히 힘든 상황을 맞이하게 될 것이다. 이 때, 만약에 벡터 방식의 이미지를 사용하게 되면 한 장의 벡터 이미지를 공유하여 각 장비의 해상도에 맞게 렌더링해서 사용할 수 있다. 물론 벡터 이미지는 최종적으로 비트맵으로 렌더링하는 비용이 들지만 현재의 스마트 장비들의 컴퓨팅 파워를 생각해보면 중요한 문제는 아니다. 필자는 앞으로의 UX 환경은 어쩔 수 없이 벡터 방식으로 넘어갈 것으로 생각하고 있다. 하나의 앱으로 다양한 해상도의 장비들을 한 방에 깔끔하게 지원하기 위해서는 벡터 방식이 유일한 해결책이다. 물론 벡터 방식을 적용한다고 모든 문제가 한 방에 해결되는 것은 아니지만, 아마 UX 그래픽 환경은 현재의 비트맵 방식에서 벡터 방식으로 전환될 것으로 예상된다.

리눅스 환경에서도 벡터 그래픽 관련 기술들은 꾸준히 발전하고 있다. 여기에 대해 간단히 소개하는 시간을 갖도록 하겠다.

벡터 그래픽 렌더링 엔진

벡터 그래픽 렌더링 엔진은 말 그대로 벡터 그래픽으로 표현된 데이타를 사람의 눈으로 볼 수 있는 이미지로 변환해주는 엔진을 의미한다. 대표적인 렌더링 엔진들은 다음과 같다.

  • CAIRO .. 대표적인 오픈 소스 벡터 그래픽 엔진으로 가장 널리 사용되고 있다. 다양한 백엔드와 프론트엔드를 지원하고 있기 때문에 오픈 소스 환경에서 가장 편리하고 효과적으로 사용할 수 있다.
  • SKIA .. 2005년경에 구글에 인수합병된 회사에서 개발한 벡터 그래픽 엔진이다. 현재 안드로이드와 크롬에서 주로 사용되고 있지만, 다른 오픈 소스 환경에서 사용되는 사례를 본적이 없다.
  • FOG .. 최근 우연히 알게 된 프로젝트로 자체 개발 중인 벡터 그래픽 엔진을 이용하여 완벽한 벡터 방식의 UI Toolkit 까지 제공하는 것을 목표로 하고 있는 프로젝트이다. 현재 하드코어 리팩토링 중으로 조만간 결과물을 공개한다고 해서 기대 중이다.

찾아보면 이외에도 더 있겠지만 현재 시점에서 가장 중요한 렌더링 엔진은 위의 세 가지라고 할 수 있겠다.

SVG 렌더링 엔진

SVG 렌더링 엔진은 대표적인 벡터 방식의 이미지 포맷인 SVG 를 출력해주는 엔진을 의미한다.

  • LIBRSVG .. 오픈 소스 환경에서 가장 일반적으로 사용되는 프로젝트이다. 백엔드는 cairo 를 사용하고, SVG 의 기본 스펙만을 지원한다. 현재는 거의 개발이 진행되고 있지 않은 상황이다.
  • LASEM .. SVG 와 MathML 을 지원하는 오픈 소스 프로젝트로 librsvg 에 비해 소스가 훨씬 깔끔하고, 다양한 백엔드와 CSS stylesheet 을 지원하고 있다. 현재도 활발히 개발되고 있기 때문에 미래가 기대되는 프로젝트이다. 하지만 아직 애니메이션은 지원하지 않고 있다.

이외에 웹환경에서 사용 가능한 자바스크립트 형태의 간단한 SVG 렌더링 엔진들이 여러 개 있지만 여기서는 언급하지 않도록 하겠다.

지금까지 설명한 것처럼 벡터 방식의 그래픽 기술이 점차 활용 범위를 넓혀갈 것으로 기대된다. 아직까지 리눅스 진영의 오픈 소스 환경에서는 관련 기술에 대한 연구가 약간 미흡한 상태이지만, 앞으로 다양한 벡터 방식의 그래픽 기술을 지원하는 프로젝트가 나와서 좋은 결과가 나왔으면 하는 바램이다.

[WAYLAND] 3. 쉘(shell)

지난 글에서 설명한 것처럼 일반적인 윈도우 환경은 컴포지터와 쉘 두 가지로 구성된다. 각각의 윈도우 화면을 갱신하고, 겹쳐진 윈도우들을 하나의 화면으로 보여주는 것은 컴포지터의 역할이고, 나머지 윈도우를 옮기거나 크기를 변경하고 패널이나 배경화면을 보여주는 것은 모두 쉘의 역할이다. 쉽게 말해 우리가 일반적으로 윈도우 환경에서 사용하는 대부분의 기능들이 쉘에 의해 결정된다고 보면 된다. 오늘은 수많은 쉘의 기능 중에 기본적인 기능들 몇 가지만 살펴보도록 하겠다.

윈도우 생성 및 쉘 등록 과정

지난 글에서 wl_compositor 인터페이스의 create_surface 리퀘스트를 이용하여 윈도우를 생성한다고 설명하였다. 여기에 추가적으로 해당 윈도우를 화면 상의 원하는 위치에 배치시키기 위해서는 아래와 같이 wl_shell 인터페이스의 get_shell_surface 리퀘스트를 이용하여 쉘에 등록하는 과정이 필요하다.

...
window->surface = wl_compositor_create_surface(display->compositor);
window->shell_surface = wl_shell_get_shell_surface(display->shell, window->surface);
...

위의 코드처럼 컴포지터를 통해 생성된 윈도우(surface)를 인자로 쉘에 등록하면 쉘 서페이스(shell surface) 객체를 반환해준다. 우리는 이 객체를 이용하여 해당 윈도우를 마우스로 드래그 하여 옮기거나 크기를 변경하거나 최대/최소화를 수행할 수 있다. 

윈도우 이동 과정

우리가 일반적으로 윈도우를 생성하면 윈도우의 외부 프레임에 최대/최소/종료 버튼이 있고, 외부 프레임을 마우스로 클릭하여 드래그하면 윈도우가 옮겨지거나 크기가 변경된다. 이 중에서 윈도우를 이동시키는 과정에 대해서 살펴보도록 하겠다.

지난 글에서 설명한 것처럼 wayland/weston 은 기본적으로 CSD(Client-Side Decoration) 을 지원한다. 즉, 윈도우의 외부 프레임을 클라이언트 측에서 직접 그린다는 것이다. 그렇기 때문에 윈도우의 최대, 최소화 및 이동/크기 변경은 클라이언트 측에서 판단해서 쉘로 요청해야 한다. 이 과정은 아래와 같다.

사용자가 마우스를 이동하여 윈도우의 외부 프레임을 클릭하는 동안 클라이언트는 wl_pointer 인터페이스를 통해 enter, motion, button 이벤트를 순서대로 받게 된다. (입력 장치 관련 내용은 다음에 자세히 다루도록 하겠다.) 그리고 마우스를 클릭하는 순간 받게되는 button 이벤트 핸들러에서 해당 위치가 외부 프레임인지, 그리고 최대/최소/종료 버튼인지를 판단한다. 이때 외부 프레임의 비어있는 공간이면 윈도우를 마우스로 드래그하여 옮기겠다는 표시이므로 위에서 생성해두었던 쉘 서페이스 객체를 이용하여(정확히는 wl_shell_surface 인터페이스의 move 리퀘스트) 쉘에게 알린다. 그러면 쉘은 해당 윈도우를 마우스가 눌러져있는 동안 위치를 자유롭게 옮길 수 있도록 해준다. (마우스를 클릭하고 있는 동안 마우스를 움직여서 윈도우의 위치를 변경하는 것은 클라이언트의 개입없이 쉘이 알아서 처리해준다.)

패널 및 백그라운드 화면 설정

weston 을 실행하면 기본적인 컴포지터 기능을 초기화한 후, 쉘 모듈을 로딩한다. 현재 weston 에 구현되어 있는 쉘 모듈은 desktop shell 과 tablet shell 이다. desktop shell 은 초기화를 마무리한 후 desktop-shell 프로세스를 실행한다. 이 desktop-shell 프로세스의 기본적인 역할 중에 하나가 바로 패널과 백그라운드 윈도우를 생성하는 것이다. 이들은 일반적인 윈도우와 마찬가지로 wl_compositor 인터페이스를 이용하여 생성되지만, wl_shell 인터페이스를 이용하여 쉘 서페이스 객체를 할당받지 않고, desktop_shell 인터페이스의 set_panel/set_background 리퀘스트를 이용하여 등록된다. 즉, 패널과 백그라운드로 등록된 윈도우들은 일반적인 윈도우처럼 동작하는 것이 아니라, desktop shell 에 의해 특별 관리를 받게 된다. 예를 들면, 백그라운드로 등록된 윈도우는 마우스로 클릭하여도 맨 앞으로 위치가 변경되지 않고 항상 맨 뒤에 위치하게 된다.

이 외에도 쉘은 워크 스페이스 관리나 스크린 세이버, 단축키 등등 수많은 일들을 수행한다. 하지만, 컴포지터와 쉘, 그리고 클라이언트가 어떤 식으로 역할분담을 하여 하나하나의 기능을 완성해나가는지를 먼저 완벽히 이해하는 것이 중요하다.