무료 포토샵 개발기 3편: GitHub 코드에 레벨·히스토그램 연결하기
무료 포토샵의 레벨 기능은 검은점·흰점·감마를 계산하는 식 하나로 끝나지 않았다. GIMP 문서로 동작을 확인하고, AI와 작성한 계산·손잡이·히스토그램 코드를 기존 조정 레이어에 연결했다. GitHub에 공개한 세 파일을 펼쳐서 조합 과정을 설명한다.
출처 대조 · 원고 갱신 2026.10.16
원고 읽기 ↓

GIMP는 동작 참고, 구현은 포토샷 코드
입력 검은점·흰점과 출력 범위, 감마, 개별 채널이 어떻게 나뉘는지는 GIMP Levels 문서에서 확인했다. 이 자료는 기능의 의미를 정리하는 참고 문서다. GIMP의 C 코드를 가져와 포토샷에 이식했다고 확인되는 기록은 없다.
대신 기존 adjustments.ts에 레벨 계산을 넣고, levels-controls.ts에 손잡이 좌표를, tone-analysis.ts에 히스토그램 집계를 나눠 작성했다. 이번 편에서 GitHub로 따라갈 코드는 이 세 포토샷 파일이다.
기존 LUT 경로에 레벨 식을 넣기
코드를 읽는 순서는 입력 범위 정규화 → 감마 → 출력 범위다. 검은점과 흰점 사이의 입력을 0~1로 바꾸고, 감마의 역수로 거듭제곱한 뒤 출력 검은점·흰점 사이로 다시 옮긴다. for문의 rgb는 공통 채널, channel은 개별 R·G·B다.
이 계산은 앞 편에서 만든 256칸 조회표 생성 안에 들어간다. AI에게 레벨만 따로 그리게 한 것이 아니라, 기존 레이어의 마스크·불투명도·저장 흐름을 이용하도록 연결했다.
if (t === 'levels')
for (const c of ['rgb', channel])
x =
(v[c + 'OutBlack'] +
(v[c + 'OutWhite'] - v[c + 'OutBlack']) *
clamp((x * 255 - v[c + 'Black']) / (v[c + 'White'] - v[c + 'Black'])) **
(1 / v[c + 'Gamma'])) /
255;가운데 손잡이도 계산과 맞춰야 했다
감마 손잡이를 화면의 정확한 중앙에만 두면 계산과 UI가 어긋난다. 가운데 손잡이는 감마를 적용한 뒤 50%가 되는 입력 위치를 나타내야 한다. 그래서 gammaPosition은 0.5를 감마만큼 거듭제곱하고, gammaAtPosition은 로그로 반대 변환을 한다.
두 함수를 함께 두면 숫자를 입력할 때와 손잡이를 끌 때 같은 값을 주고받을 수 있다. GitHub에서 복사할 수 있는 수식 하나를 찾는 것보다, 이처럼 양방향 연결을 완성하는 일이 실제 편집기에서는 더 중요했다.
// The middle handle marks the input that becomes 50% after inverse gamma.
export function gammaPosition(black: number, white: number, gamma: number) {
return black + (white - black) * Math.pow(0.5, clamp(gamma, 0.1, 10));
}
export function gammaAtPosition(black: number, white: number, position: number) {
const t = clamp(
(position - black) / Math.max(1, white - black),
Math.pow(0.5, 10),
Math.pow(0.5, 0.1),
);
return Math.round(clamp(Math.log(t) / Math.log(0.5), 0.1, 10) * 100) / 100;
}히스토그램에도 투명도 규칙을 넣었다
히스토그램은 단순한 막대 장식이 아니다. 투명한 픽셀을 검은 픽셀처럼 세면 이미지의 분포를 잘못 보여준다. tone-analysis.ts에서는 알파를 0~1의 가중치로 쓰고, 완전히 투명한 픽셀은 건너뛴다. 밝기 분포와 R·G·B 채널 분포도 따로 누적한다.
이 부분은 AI와 작성한 분석 코드다. 레벨 계산 자체와 화면의 분포 그래프가 같은 문서를 바라보도록 연결하면서, “작동하는 함수”를 “사용할 수 있는 도구”로 바꿨다.
const weight = data[i + 3] / 255;
if (!weight) continue;
bins[Math.round(0.2126 * data[i] + 0.7152 * data[i + 1] + 0.0722 * data[i + 2])] += weight;
channelBins.r[data[i]] += weight;
channelBins.g[data[i + 1]] += weight;
channelBins.b[data[i + 2]] += weight;
count += weight;화면과 비교 기록
사진 예시는 RGB 입력 12 / 1.08 / 245, 출력 0 / 255다. 아래는 실제 포토샷 화면과 보정 전후다. 인물 원본은 AI 생성 예시 이미지다.
과거 Photoshop 비교 차트는 다른 설정인 입력 18 / 1.12 / 235, 출력 0 / 255를 사용했다. 그 조건의 평균 채널 차이는 약 0.229, 최대 1이었다. 두 예시를 섞지 않고, 손잡이와 계산의 연결은 별도 테스트로 확인했다.
세 파일을 조합해 하나의 기능으로
무료 포토샵의 레벨은 GIMP 문서로 기능을 이해하고, 기존 포토샷 코드에 계산·조작·분석을 연결한 결과다. GitHub에 공개한 파일을 보면 어느 부분이 수학이고 어느 부분이 UI인지 분리해서 읽을 수 있다.
AI가 새로 작성한 코드의 가치는 줄 수보다 연결의 정확성에 있었다. 다음 편은 glfx.js의 곡선 구현에서 LUT 아이디어를 확인하고, 포토샷의 보간 코드를 따로 작성한 이야기다.
