본문 바로가기
C.W.K.
Stream
Lesson 03 of 05 · published

건드리지 않고 떠 오기

~12 min · uxp, capture, imaging-api, memory, non-destructive

Level 0툴 빌려 쓰는 사람
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"작가의 캔버스를 다른 창에 띄우려면 픽셀을 읽어야 해. 그것도 레이어를 합치거나 병합하거나, 지금 작업 중인 document 를 건드리지 않고서."

쉬워 보이는 함정

Cinder 는 포토샵 캔버스에 지금 뭐가 있는지 실시간으로 알아야 해. 별것 아니게 들리지. 그냥 이미지를 가져오면 되잖아. 그런데 그 document 는 작가가 지금 작업 중인 파일이야. 레이어가 잔뜩 있고 계속 고쳐지고 있는. 레이어를 합치거나 병합해 버리는 순진한 '이미지 가져오기' 는 작가가 그리고 있는 바로 그것을 바꿔 놔. 진짜 할 일은 이거야. document 를 전혀 안 바꾸고 캔버스가 합쳐진 모습만 떠 오기.

맞는 도구: 읽기 전용 imaging 경로

플러그인 플랫폼이 document 의 합쳐진 픽셀 — 보이는 레이어가 전부 한 장으로 합쳐진 모습 — 을 document 를 안 바꾸고 읽어서 돌려주는 imaging API 를 줘. document 는 레이어를 전부 그대로 갖고 있고, 너는 그것들이 합쳐져 보이는 모습만 받는 거지. '지금 이거 어떻게 보여?'(읽기)랑 'document 를 합쳐'(파괴적 변경)의 차이야. 눈에 보이는 결과는 같은데 작가 파일에 남는 결과는 완전히 달라.

상태를 바꾸는 연산보다 사진을 찍는 읽기를 골라. 살아 있는 데이터의 합쳐진 모습이 필요하면, 원본을 그 모양으로 바꾸는 API 말고 그 모습을 계산해서 돌려주는 API 를 찾아. 읽기는 다음 읽기를 위해 원본을 온전히 남겨 주고, 변경은 돌아올 길이 없는 문이야.

일부러 더 작은 이미지를 달라고 해

떠 올 때 목표 크기를 같이 넘겨. 전체 해상도 캔버스가 아니라 줄어든 미리보기로. 이건 대역폭 아끼자는 얘기가 아니야. 더 작은 크기를 달라고 하면 플랫폼이 내부에 갖고 있는 이미지 층 중에 이미 줄여 둔 걸 내줄 수 있거든. 매번 전체를 읽어서 줄이는 것보다 훨씬 빨라. 작가가 그리는 동안 계속 갱신되는 실시간 미리보기에서는 빠르고 작은 쪽이 완벽하고 느린 쪽을 이겨. 그 일에 맞는 크기는 목적을 만족시키는 제일 작은 크기야.

덜 달라고 하는 게 가볍기만 한 게 아니라 빠를 수도 있어. 줄어든 결과를 달라고 하면 짐이 작아지는 것만이 아니야. 많은 시스템이 캐시나 미리 줄여 둔 층에서 축소본을 전체본 만드는 것보다 훨씬 빨리 내줄 수 있거든. 미리보기만 필요하면 미리보기 해상도로 달라고 하는 게 양쪽에서 다 이기는 거야. 품질을 포기하는 타협이 아니라.

빼먹으면 안 되는 정리

날 선 데가 하나 있어. 떠 온 이미지 데이터가 호스트 앱의 메모리를 붙들고 있어서, 뜰 때마다 명시적으로 놓아 줘야 해. 놓아 주는 걸 건너뛰면, 그것도 계속 다시 뜨는 실시간 미리보기에서 건너뛰면, 플러그인이 꾸준히 메모리를 새다가 호스트가 한도를 넘었다고 경고할 때까지 가. 떠 오기는 바이트를 손에 쥔 순간 끝난 게 아니야. 메모리를 도로 돌려준 순간 끝나. 모든 떠 오기는 잡고, 쓰고, 놓는 거야. 그리고 놓는 건 선택이 아니야.

반복되는 연산에서 정리를 빠뜨리면 천천히 진행되는 크래시야. 한 번만 뜨는 경우라면 눈치도 못 챌 누수가, 수백 번 도는 실시간 미리보기에서는 치명적이 돼. 위험이 반복 횟수만큼 커지거든. 그 경로가 자주 돌수록 놓아 주기를 빠뜨린 값이 빨리 쌓여. 잡는 코드를 쓰는 그 순간에 놓는 코드도 같이 써. 반복해서 부르는 쪽이 나중에 봐주지 않으니까.

피파의 고백

처음에 양쪽 다 틀렸어. 레이어를 합쳐 버리는 연산에 손을 뻗었지. 작가가 붓질하고 있는 document 를 바꿔 놨을 거라는 걸 깨닫기 전까지는. 그리고 놓아 주는 것도 까먹었어. 한 번만 돌려 본 첫 시험에서는 티가 안 났으니까. 누수는 미리보기가 실시간으로 계속 돌기 시작하고서야 나타났고. 두 실수가 같은 걸 가르쳐 줬어. 살아 있고 반복되는 상태를 떠 오는 건 값 하나 가져오는 것보다 훨씬 조심해야 한다고. 반복이 대충 한 읽기를 크래시로 바꿔 놓는 거야.

Code

잡고, 쓰고, 반드시 놓기·javascript
// 작가의 document 를 안 바꾸고 합쳐진 모습만 읽어 와.
const { app, imaging } = require("photoshop");

// 1. 잡기: 합쳐진 픽셀을 읽기로만. layerID 를 안 주면 전체 합본
//    (보이는 레이어가 전부 합쳐진 모습으로 돌아와. document 는 안 건드림)
//    targetSize 로 내부에 이미 줄여 둔 층을 달라고 하는 거라
//    -> 전체를 읽고 줄이는 것보다 훨씬 빨라.
const obj = await imaging.getPixels({
  documentID: app.activeDocument.id,
  targetSize: { width: 512 },        // 일부러 미리보기 해상도로
});

// 2. 쓰기: 작업실로 보낼 작은 형태로 인코딩.
const jpegBase64 = await imaging.encodeImageData({
  imageData: obj.imageData,
  base64: true,
});

// 3. 놓기: 필수야. 호스트 메모리를 즉시 돌려줘.
obj.imageData.dispose();             // 실시간 미리보기에서 이걸 건너뛰면 -> 누수 -> 크래시

// document 는 여전히 레이어를 다 갖고 있어. 아무것도 합쳐지거나 병합되지 않았고.

External links

Exercise

네 코드에서 살아 있는 원본으로부터 요약된 상태를 읽는 자리를 찾아봐. 썸네일이든 요약이든 스냅샷이든. 두 가지를 확인해. (1) 그 읽기가 원본을 바꿔, 아니면 그대로 둬? (2) 그 읽기가 명시적으로 놓아 줘야 하는 자원을 잡아? 그렇다면 반복되는 경로에서도 놓아 주는 게 보장돼? 빠진 걸 고쳐.
Hint
(2)번은 반복될 때만 드러나. 한 번만 돌리는 시험은 누수가 있어도 통과하거든. 한 번 말고 천 번 돌면 무슨 일이 생길지 따져 봐.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.