그런 적 있나요? 내가 잘하고 있는지도 잘 모르겠고 개발에 대한 방향성과 갈피를 못잡고 있는 것 같은 때. 저도 요즘 비슷한 느낌인 것 같네요. 요즘 스터디를 통해 파이브라인스오브 코드라는 책을 다시 읽고 있는데 직장을 다니면서 스터디를 한다는 것이 참 쉽지 않은 것 같습니다.
하지만 남들을 다 한다는거죠..........
근데 저는 왜 이렇게 힘이 들까요...( ˃̣̣̥᷄⌓˂̣̣̥᷅ ). .........
.........네..
클럽을 좀 다녀오니 스트레스가 풀리는 것 같습니다.
그럼 제가 어떻게 클럽을 다녀왔는지 여러분에게 알려드리겠습니다.
일단 class 를 만들어줍니다.
class CanvasImageEditor{
constructor(canvasId, imagePath) {
this.setupCanvas(canvasId);
this.loadAndSetupImage(imagePath);
}
}
처음 CanvasImageEditor 요녀석을 생성할때 canvas를 setup해줄겁니다. setup 코드를 한번 볼까요 ?
setupCanvas(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext("2d");
}
그리기 기능을 담당합니다. 이걸 통해서 fillRect(), drawImage(), rotate(), translate(), scale() 등의 메서드와 색상, 선 스타일, 폰트 설정 등을 조작할 수 있는 속성들이 포함되어 있습니다.
세팅이 이렇게 간단하다니 놀랍지 않나요?
getElementById 를 통해 canvasId 를 가져와 canvas를 찾고, getContext 함수를 이용해 2D그래픽을 위한 컨텍스트를 생성하고 ctx 필드에 할당했습니다. 이것을 통해 선 그리기, 사각형 그리기, 텍스트 추가, 이미지 표시 등의 작업을 수행할 수 있습니다. 밑에 예시 코드를 보면 제가 ctx로 많은 것을 합니다.
loadAndSetupImage(imagePath) {
this.image = new Image();
this.image.onload = () => {
this.drawImageOnCanvas();
this.updateCanvasColors();
};
this.image.src = imagePath;
}
loadAndSetupImage 는 이미지 객체를 만든다음 src에 제 로컬 폴더에 있는 이미지 path를 넣어 load될때 이미지가 생성되게 만들어주었습니다. 이어서 updateCanvasColors 함수가 하는 역할을 한번 봅시다.
updateCanvasColors() {
const imageData = this.ctx.getImageData(
0,
0,
this.canvas.width,
this.canvas.height
);
let data = imageData.data;
this.applyImageDataUpdates(data, imageData);
}
updateCanvasColors는 캔버스에서 현재 표시 중인 이미지 데이터를 가져와서 색상을 업데이트하는 작업을 수행합니다.
this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
이 코드는 캔버스의 2D 컨텍스트(ctx)에서 getImageData 메서드를 사용하여 캔버스의 전체 영역(좌표 (0,0)에서 시작하여 canvas.width와 canvas.height까지의 크기)에 대한 이미지 데이터를 추출합니다. 반환된 ImageData 객체는 해당 영역의 픽셀 데이터를 담고 있습니다.
let data = imageData.data;
ImageData 객체의 data 속성은 RGBA 형식의 픽셀 데이터를 포함하는 Uint8ClampedArray 배열입니다. 각 픽셀은 이 배열에 R(빨간색), G(녹색), B(파란색), A(알파 채널) 순서로 4개의 요소로 표현됩니다.
여러분을 위해 imageData.data의 형태를 보여드리겠습니다.
Uint8ClampedArray(1000000) [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, …]
대충 이렇게 요란하게 생겼습니다.
applyImageDataUpdates(data, imageData) {
this.updateAllPixelColors(data);
this.ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(this.updateCanvasColors.bind(this));
}
함수명을 주석처럼 읽어볼까요 ?
setInterval 이 있는데 requestAnimationFrame을 사용하는 이유는 무엇일까요? 사실 구현해보면 압니다 ......... setInterval로도 해봤는데 엄청 느렸습니다.
특성 | requestAnimationFrame | setInterval |
---|---|---|
설계 목적 | 애니메이션을 위해 특별히 설계됨 | 일반적인 시간 간격으로 함수를 실행하기 위해 설계됨 |
호출 타이밍 | 브라우저의 다음 리페인트가 일어날 때 자동으로 콜백이 호출됨 | 지정된 시간 간격마다 함수 호출 |
성능 최적화 | 브라우저의 리페인트 주기와 동기화되어 불필요한 렌더링을 방지하고 성능 최적화를 제공함 | 리페인트 주기와 무관하게 작동하며 필요 이상의 렌더링을 유발할 수 있음 |
프레임 속도 | 대부분의 브라우저에서 현재 모니터의 출력 주기와 같게, 보통 초당 60프레임에 해당하는 주기로 호출됨 | 사용자가 지정한 시간 간격에 따라 호출되며, 초당 프레임 수를 정확히 제어하기 어려움 |
적용 상황 | 애니메이션, 게임 렌더링, UI 효과 등 화면 갱신을 필요로 하는 모든 시나리오에 적합 | 주기적인 작업, 타이머, 간단한 반복 작업 등에 적합 |
부하 관리 및 자원 사용 | 비활성 탭에서는 프레임 업데이트를 중지하여 CPU 및 배터리 자원을 절약할 수 있음 | 비활성 탭에서도 지정된 간격으로 코드 실행을 계속하여 자원 사용량이 더 높을 수 있음 |
해당 표를 참고하시길 바랍니다.
updateAllPixelColors(data) {
for (let i = 0; i < data.length; i += 4) {
this.togglePixelColor(data, i);
}
}
togglePixelColor(data, i) {
if (data[i] === 255 && data[i + 1] === 255 && data[i + 2] === 255) {
data[i] = 128; // R
data[i + 1] = 128; // G
data[i + 2] = 128; // B
} else if (
data[i] === 128 &&
data[i + 1] === 128 &&
data[i + 2] === 128
) {
data[i] = 255; // R
data[i + 1] = 255; // G
data[i + 2] = 255; // B
}
}
는 이미지의 모든 픽셀을 순회합니다. 각 픽셀에 대해 togglePixelColor 메서드를 호출하여 특정 조건에 따라 픽셀의 색을 변경합니다. i += 4를 하는 이유는, 각 픽셀은 4개의 연속된 값으로 구성되기 때문에 4씩 증가시키는 것입니다.
if 조건: 현재 픽셀의 색상이 흰색일 경우 (R=255, G=255, B=255), 이를 회색 (R=128, G=128, B=128)으로 변경합니다.
else if 조건: 현재 픽셀의 색상이 회색일 경우 (R=128, G=128, B=128), 이를 다시 흰색으로 변경합니다.
,, _
/ `、
/ ● ╮
/ ● 💧 l
l 💧 し 💧 |
l 💧 __,,ノ 💧l
\💧_´' ̄´_💧/
. /💧  ̄ 💧 ╮
/ 💧 💧 .╮
. | 💧 .|
정말 간단하죠?
사실 이 프로젝트는 파이브라인스오브 코드를 읽고 실습을 위해 조금씩 책의 리팩토링 기법을 적용해본 아주 작은 프로젝트입니다.
이 정도를 신경쓰고 개발하며 함수를 나누어보았습니다.
저는 책을 읽은 후 제가 작성한 코드를 직접 리팩토링을 시도해보면서 함수명은 곧 주석의 역할을 대신한다, 이말에 엄청나게 공감했던 것 같네요. 함수를 나누면서 "이렇게까지 해야할까?" 라는 마음보다 "익숙해지니 생각보다 정말 괜찮고 함수가 진짜 하나의 역할만 하네!" 라는 느낌을 받았습니다. 특히 캔버스같은 복잡한 로직은 함수를 잘게 쪼개면 쪼갤수록 제가 보기에 편해지는것 같았습니다.
함수 다섯줄 ? 물론.. 넘었습니다 ............... 하지만 파이브라인스오브 코드 저자도 무조건 다섯줄을이하의 코드를 작성하는 것을 원하는 것이 아니었을 것이라 생각합니다.
모두 행복한 개발 하세요...
그럼 20000
오 캔버스로 만든 클럽 멋지네여, 혹시 왜 클래스로 만드셨을까요?