Binary data to RGB in canvas :: 마이구미
이 글은 javascript 로 원시 이진 데이터(raw binary data)를 조작하여 canvas 를 통해 이미지를 출력한다.
흔하게 경험하는 것도 아니고, 사용하는 일도 많지는 않다.
하지만 글을 통해 기본적인 canvas 원리와 binary 이해에 도움이 되리라 생각한다.
raw 데이터로써, 바이너리 형태인 파일이 존재한다.
즉, 이 raw 데이터의 파일은 이미지 포맷(png, jpg, etc..) 형태도 아닌, RGB 형태도 아니다.
이러한 raw 데이터를 가지고, 흑백 이미지를 출력할 것이다.
대부분 이미지 포맷이 된 형태의 파일을 넘겨받기 때문에, 앞단에서는 대부분 이러한 행위를 하지 않는다.
하지만 자바스크립트에서 raw 데이터를 조작하는 것이 점차 늘어가고 있고, 늘어갈 것이다.
아무튼 이 글에서는 이미지 포맷 형태가 아닌 raw 데이터 형태를 이미지로 만드는 것을 의미한다.
우선 이러한 행위를 위해서는 이진 데이터를 조작해야한다.
자바스크립트에서는 이진 데이터를 접근하기 위해 형식화 배열(typed array) 을 제공한다.
그리고 이진 데이터 접근을 위한 API 인 FileReader.prototype.readAsArrayBuffer() 를 사용할 수 있다.
다음 예제처럼 사용할 수 있다.
See the Pen readAsArrayBuffer by leejunghyun (@mygumi) on CodePen.
파일 업로드 시 콘솔창을 통해 typedArray 형태의 값이 출력되는 것을 볼 수 있다.
지금부터 실제 예제를 통해 진행해본다.
본인이 사용할 raw 데이터 파일을 업로드할 경우, 아래와 같은 형태의 이진 데이터를 볼 수 있다.
이 때 raw 데이터가 있는 파일은 어떻게 쓰여졌는 지 알고 있어야, 정확하게 읽을 수 있다.
raw 데이터를 위한 파일은 생성할 당시, 다음과 같이 쓰여졌다고 가정한다.
- 이미지 사이즈 추출을 위해 먼저 width, height, datasize 를 int 형(4byte)으로 쓴다.
- 그 후, 이미지 데이터는 width * height * unsigned short 형(2byte)으로 쓴다.
파일 업로드 시 다음과 같이 형식화 배열 뷰를 출력된다.
다음과 같이 형식화 배열 뷰를 제공하여 쉽게 조작할 수 있다. (공식 문서 참고)
타입에 따라 이진 데이터를 확인할 수 있고, 맨 앞 요소들은 width, height, datasize 를 표현하고 있는 걸 확인할 수 있다.
예를 들어, Int16Array 유형에서는 width - [352, 0], height - [286, 0], datasize - [2, 0].
Int32Array 유형에서는 width - 352, height - 286, datasize - 2 인 것을 확인할 수 있다.
이전 raw 데이터를 위한 파일이 쓰여질 때 사용한 타입인 unsigned short 형을 위해 Uint16Array 를 사용한다.
실제 이미지 raw 데이터만을 사용하기 위해 6byte(width, height, datasize) 를 짜른 후 사용한다.
const raw = fr.result; const rawBytes = new Uint16Array(raw).slice(6);
결과적으로 실제 이미지를 위한 raw 데이터인 352 * 286 = 100672 크기의 배열(rawBytes)이 준비되었다.
각 배열의 요소의 값은 1 픽셀에 대한 값을 의미한다.
그렇기에, raw 데이터 값을 이미지 출력 형태의 값으로 변환하면 된다.
이 과정을 canvas 를 이용하여 RGB 형태의 값으로 변환하여 이미지를 출력할 수 있다.
const canvas = document.getElementById('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); const imgData = ctx.createImageData(width, height); const len = imgData.data.length; // width * height * 4;
const min = rawBytes.reduce((p, v) => { return (p < v ? p : v); }); const max = rawBytes.reduce((p, v) => { return (p > v ? p : v); });
for (let i = 0, k = 0; i < len; i += 4, k++) { const n = rawBytes[k] / (max - min); // normalize imgData.data[i + 0] = imgData.data[i + 1] = imgData.data[i + 2] = 255 * n; imgData.data[i + 3] = 255; } ctx.putImageData(imgData, 0, 0);
1 픽셀은 RGBA 형태의 값을 가져야함으로써, 전체 크기는 기존 rawBytes * 4 를 가지게 된다.
이 RGB의 값은 0 ~ 255 로 표현되기 때문에, 255 * 노멀라이즈 한 값을 넣는다.
결과적으로 raw 데이터를 RGB 형태로 변환한 후, 이미지를 출력할 수 있다.
형식화 배열 - https://developer.mozilla.org/ko/docs/Web/JavaScript/Typed_arrays
canvas - https://www.w3schools.com/tags/canvas_createimagedata.asp