← Photoshot · 사용법과 개발기↗편집기 열기
Photoshot / Development03

무료 포토샵 개발기 3편: GitHub 코드에 레벨·히스토그램 연결하기

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

출처 대조 · 원고 갱신 2026.10.16

원고 읽기 ↓
보정 전 · AI로 제작한 예시 인물 이미지
보정 전 · AI로 제작한 예시 인물 이미지
실제 포토샷 편집 화면 · 2026.10.13
실제 포토샷 편집 화면 · 2026.10.13
BUILD LOG / 01

GIMP는 동작 참고, 구현은 포토샷 코드

입력 검은점·흰점과 출력 범위, 감마, 개별 채널이 어떻게 나뉘는지는 GIMP Levels 문서에서 확인했다. 이 자료는 기능의 의미를 정리하는 참고 문서다. GIMP의 C 코드를 가져와 포토샷에 이식했다고 확인되는 기록은 없다.

대신 기존 adjustments.ts에 레벨 계산을 넣고, levels-controls.ts에 손잡이 좌표를, tone-analysis.ts에 히스토그램 집계를 나눠 작성했다. 이번 편에서 GitHub로 따라갈 코드는 이 세 포토샷 파일이다.

BUILD LOG / 02

기존 LUT 경로에 레벨 식을 넣기

코드를 읽는 순서는 입력 범위 정규화 → 감마 → 출력 범위다. 검은점과 흰점 사이의 입력을 0~1로 바꾸고, 감마의 역수로 거듭제곱한 뒤 출력 검은점·흰점 사이로 다시 옮긴다. for문의 rgb는 공통 채널, channel은 개별 R·G·B다.

이 계산은 앞 편에서 만든 256칸 조회표 생성 안에 들어간다. AI에게 레벨만 따로 그리게 한 것이 아니라, 기존 레이어의 마스크·불투명도·저장 흐름을 이용하도록 연결했다.

코드 발췌 · src/adjustments.ts · v0.16.0 · L521–528 ↗
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;
BUILD LOG / 03

가운데 손잡이도 계산과 맞춰야 했다

감마 손잡이를 화면의 정확한 중앙에만 두면 계산과 UI가 어긋난다. 가운데 손잡이는 감마를 적용한 뒤 50%가 되는 입력 위치를 나타내야 한다. 그래서 gammaPosition은 0.5를 감마만큼 거듭제곱하고, gammaAtPosition은 로그로 반대 변환을 한다.

두 함수를 함께 두면 숫자를 입력할 때와 손잡이를 끌 때 같은 값을 주고받을 수 있다. GitHub에서 복사할 수 있는 수식 하나를 찾는 것보다, 이처럼 양방향 연결을 완성하는 일이 실제 편집기에서는 더 중요했다.

코드 발췌 · src/levels-controls.ts · v0.16.0 · L2–13 ↗
// 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;
}
BUILD LOG / 04

히스토그램에도 투명도 규칙을 넣었다

히스토그램은 단순한 막대 장식이 아니다. 투명한 픽셀을 검은 픽셀처럼 세면 이미지의 분포를 잘못 보여준다. tone-analysis.ts에서는 알파를 0~1의 가중치로 쓰고, 완전히 투명한 픽셀은 건너뛴다. 밝기 분포와 R·G·B 채널 분포도 따로 누적한다.

이 부분은 AI와 작성한 분석 코드다. 레벨 계산 자체와 화면의 분포 그래프가 같은 문서를 바라보도록 연결하면서, “작동하는 함수”를 “사용할 수 있는 도구”로 바꿨다.

코드 발췌 · src/tone-analysis.ts · v0.16.0 · L22–28 ↗
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;
BUILD LOG / 05

화면과 비교 기록

사진 예시는 RGB 입력 12 / 1.08 / 245, 출력 0 / 255다. 아래는 실제 포토샷 화면과 보정 전후다. 인물 원본은 AI 생성 예시 이미지다.

과거 Photoshop 비교 차트는 다른 설정인 입력 18 / 1.12 / 235, 출력 0 / 255를 사용했다. 그 조건의 평균 채널 차이는 약 0.229, 최대 1이었다. 두 예시를 섞지 않고, 손잡이와 계산의 연결은 별도 테스트로 확인했다.

보정 전 · AI 생성 예시 인물 이미지
보정 전 · AI 생성 예시 인물 이미지
보정 후 · 실제 포토샷 출력
보정 후 · 실제 포토샷 출력
BUILD LOG / 06

세 파일을 조합해 하나의 기능으로

무료 포토샵의 레벨은 GIMP 문서로 기능을 이해하고, 기존 포토샷 코드에 계산·조작·분석을 연결한 결과다. GitHub에 공개한 파일을 보면 어느 부분이 수학이고 어느 부분이 UI인지 분리해서 읽을 수 있다.

AI가 새로 작성한 코드의 가치는 줄 수보다 연결의 정확성에 있었다. 다음 편은 glfx.js의 곡선 구현에서 LUT 아이디어를 확인하고, 포토샷의 보간 코드를 따로 작성한 이야기다.

구현과 참고 자료

모든 사용법·개발기 보기 ↗↗직접 편집해 보기