-
글을 시작하기 전에 나름대로 많이 찾아보고 정리했으나 정확하지 않은 부분이 있을 수 있다는 점..! 미리 말씀드리며 잘못 작성한 부분이 있다면 지적해주시면 감사하겠습니다 🙇♀️
이번 프로젝트에서 캔버스로 그래프를 그리다가 유난히 캔버스로 그린 게 좀 깨져 보인다고 해야 할까.. 흐릿하게 보이는 것 같아서 찾아보다가 DPR이라는 개념을 알게 돼서 간략하게 정리해보려고 한다.
픽셀 (Pixel) & 해상도
우선 이 주제를 위한 기본적인 개념인 픽셀과 해상도에 대해 먼저 살펴보자.
픽셀은 이미지를 이루는 가장 작은 단위를 말한다. 이미지를 계속 확대하다보면 네모 모양의 점들로 이루어진 것을 확인할 수 있는데, 이 점들을 픽셀이라고 한다.
이러한 픽셀들의 수가 늘어날수록 이미지를 더 자연스럽게 표현할 수 있게 되고, 우리가 흔히 말하는 해상도가 높아지게 된다. 해상도는 PPI (Pixel Per Inch), DPI (Dots Per Inch) 이렇게 두 가지 단위가 존재한다. 크게 중요하지는 않지만 둘의 차이가 궁금해서 간단하게 정리해봤다.
- PPI (Pixel Per Inch)
- 1인치 정사각형 안에 들어가는 픽셀의 수 (ex. 10개의 점 ⇒ 10PPI)
- 디지털 이미지의 해상도를 나타내는 단위 (디지털 이미지는 픽셀로 이루어짐)
- DPI (Dots Per Inch)
- 1인치 정사각형 안에 들어가는 점의 수 (ex. 72개의 점 ⇒ 72DPI)
- 인쇄되는 이미지의 해상도를 나타내는 단위 (프린터로 출력되는 이미지는 작은 점들로 이루어짐, 점들이 모여 이미지처럼 보이는 것!)
논리적 픽셀 & 물리적 픽셀
픽셀은 크게 논리적 픽셀과 물리적 픽셀로 구분할 수 있다. 이것들이 뭔지는 간단하게 컴퓨터의 해상도와 브라우저의 해상도를 비교해보면 알 수 있다.
현재 내가 사용하고 있는 모니터의 해상도와 모니터에서 브라우저 창을 전체 화면으로 띄운 후 윈도우의 가로 크기를 캡처한 사진이다. 사진을 보면 분명 가로 3840 픽셀의 해상도를 가진 모니터에서 브라우저 창을 띄웠음에도 1920픽셀로 나타나는 것을 확인해볼 수 있다.
이렇게 차이가 나는 이유는 브라우저에서 요소들의 스타일을 입히는 CSS의 픽셀 단위가 실제 우리가 사용하는 모니터가 표현할 수 있는 물리적인 픽셀 단위와는 다르기 때문이다.
우리가 사용하는 모니터 등의 디바이스에서 실제로 표현할 수 있는 픽셀 단위가 물리적 픽셀이고, 물리 픽셀과 무관하게 CSS에서 표현할 수 있는 픽셀 단위를 논리적 픽셀이라고 한다. 하드웨어적인 개념의 픽셀을 물리 픽셀, 소프트웨어적인 개념의 픽셀을 논리 픽셀이라고 표현한다고 보면 될 것 같다.
브라우저는 사용자가 96DPI 디스플레이를 가지고 있다고 가정하고 있고, 마찬가지로 CSS의 픽셀도 가상의 96DPI 디스플레이를 기준으로 계산된 단위이다. 이러한 CSS 픽셀 단위는 실제 렌더링이 일어나기 전에 모니터의 해상도에 맞게 변환되는데, 이때 필요한 값이 DPR이다.
DPR (Device Pixel Ratio)
Device Pixel Ratio는 window 객체에 있는 읽기 전용 속성으로, 현재 화면을 표시하고 있는 장치의 물리적 픽셀과 CSS 픽셀의 비율을 반환한다. 즉, CSS 픽셀을 구성하는 데에 필요한 물리적 픽셀 수를 나타내며, DPR 값이 2라면 CSS의 1픽셀이 물리적인 2픽셀과 대응된다는 것을 의미한다.
브라우저 해상도 대비 디바이스 해상도가 높을수록 물리적인 픽셀 수가 많아지므로 DPR 값이 커지고, 하나의 CSS 픽셀에 더 많은 물리 픽셀이 들어가게 된다. 다시 말해 더 선명하게 화면을 표현할 수 있게 된다.
이러한 DPR 개념을 이용하면 흐릿했던 캔버스 요소를 보다 선명하게 만들어줄 수 있다.
캔버스에 적용하기
우리는 캔버스를 사용할 때 CSS를 사용해서 캔버스의 가로 길이와 세로 길이를 지정해준 뒤 그 위에 요소들을 그리게 된다. 이때, 그려지는 요소들은 CSS 픽셀을 기준으로 그려지게 된다. 예컨대 가로 세로의 길이가 5px인 사각형을 그린다면, 브라우저 해상도와 모니터 해상도가 2배 차이나는 내 모니터 기준으로는 물리적인 10px의 크기를 가지는 사각형이 그려진다. 즉, CSS 기준 1픽셀이 모니터 기준 2픽셀로 표현되니 위에 그렸던 그래프처럼 흐릿하게 보여지게 되는 것이다. 이 값을 DPR을 사용해서 보정해줄 수 있다.
const canvas = canvasRef.current; const ctx = canvas?.getContext('2d'); const devicePixelRatio = window.devicePixelRatio ?? 1;
먼저 현재 DPR 값을 가져와 devicePixelRatio라는 변수에 저장한다. 지금 예시에서는 DPR 값이 2라고 가정한다.
canvas.style.width = `${canvasWidth}px`; canvas.style.height = `${canvasHeight}px`;
그리고 CSS 속성을 사용해 캔버스의 가로, 세로 크기를 지정해준다. 이 코드에서 지정해준 크기로 캔버스가 브라우저에 표시되게 된다.
canvas.width = canvasWidth * devicePixelRatio; canvas.height = canvasHeight * devicePixelRatio;
다음으로 캔버스의 width, height 속성에 위에서 지정해준 가로, 세로 크기에 devicePixelRatio를 곱한 값을 지정해준다. CSS로 캔버스 크기를 지정해준 후 캔버스의 width, height 속성을 사용하면 캔버스 내에서 사용할 픽셀 개수를 지정할 수 있다. 현재 브라우저의 해상도와 모니터의 해상도가 2배 차이가 나므로 CSS 픽셀 값에 DPR 값인 2를 곱해주면 캔버스가 해당 영역을 두배로 더 잘게 쪼개서 사용하므로 모니터에 표시되는 캔버스의 픽셀 수와 같아지게 된다.
이렇게까지 해주면 캔버스에서 사용하는 픽셀의 개수는 많아졌는데 그려지는 요소의 크기는 그대로이므로 실제보다 작게 표시되게 된다. 예로, 가로 세로 100픽셀 크기의 캔버스에 50픽셀 크기의 사각형을 그리려고 하는데, 캔버스가 영역을 더 잘게 쪼개서 200픽셀로 늘리게 되면 원래 차지하던 캔버스 영역의 1/4밖에 차지하지 못하는 사각형이 그려지게 되는 것이다.
ctx.scale(devicePixelRatio, devicePixelRatio);
이를 해결하기 위해서는 캔버스의 배율을 변경해주는 scale 메소드를 사용해주면 된다. 첫번째, 두번째 인수로 devicePixelRatio를 전달해주면 가로, 세로 크기가 전달된 값만큼 확대되어 원래 크기대로 표시된다.
요약
위에서 설명한 내용을 간략하게 정리해보면 다음과 같다.
- 브라우저에서 사용하는 픽셀(논리 픽셀)과 모니터와 같은 장치에서 사용하는 픽셀(물리 픽셀)은 다르다.
- DPR은 물리적 픽셀과 논리적 픽셀의 비율을 나타내는 값이다.
- DPR 값을 사용해 캔버스에서 사용하는 픽셀을 물리적인 픽셀과 맞춰주고, scale() 메소드를 사용해 캔버스에 그려지는 요소 또한 같은 비율로 맞춰준다.
결과 비교
차례대로 DPR 값을 사용하기 전, 후 사진이다. DPR 값을 사용해 보정해주었을 때가 훨씬 더 선명한 것을 확인할 수 있다.
참고 & 출처
- https://developer.mozilla.org/ko/docs/Web/API/Window/devicePixelRatio
- https://velog.io/@vnfdusdl/DPRDevice-pixel-ratio의-이해
- https://medium.com/watcha/watcha-개발-지식-px-em-rem-f569c6e76e66
- https://ui.toast.com/weekly-pick/ko_20200728
- https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=eirene100999&logNo=221652853751
- https://m.blog.naver.com/pjh445/220786859708
댓글
- PPI (Pixel Per Inch)