AI-generated study image / before editing
DEVELOPMENT JOURNAL / 14
Photoshot
GITHUB × AI
Building a free Photoshop alternative

Group color into levels

Quantization was already established. With AI, we connected it to existing tables and revision handling so an improved boundary calculation would not silently change old projects.

PHOTOSHOT DEVELOPMENT JOURNAL / 14

AI-generated study photograph. Editor captures and outputs show Photoshot in use.

PhotoshotDEVELOPMENT JOURNAL / 14FOUNDATION

From the source.

Integrate established quantization

Posterize maps continuous channel values to a limited number of output levels. Photoshot reuses the channel-table path shared with Invert and brightness instead of introducing another renderer.

Our work connected the level control, lookup table and saved adjustment revision. The supported range is 2–256, with a default of 6. Adobe documentation serves as a reference for what per-channel levels mean.

Rounding versus input buckets

Revision 1 rounded normalized values against steps−1. Revision 2 takes an eight-bit input i, computes floor(i×steps/256), limits the bucket index and divides by steps−1.

The boundaries differ. With four levels in the newer path, inputs 0–63 become 0; 64–127 become 85; 128–191 become 170; and 192–255 become 255. Stating the boundaries explains why the same control value can produce a different result.

src/adjustments.ts · v0.16.0
L190–200
Photoshot implementation ↗
  invert: { name: '반전', description: 'RGB 색상을 반전합니다.', controls: [] },
  posterize: {
    name: '포스터화',
    description: '각 채널의 명암 단계 수를 줄입니다.',
    controls: [control('steps', '단계', 2, 256, 6)],
  },
  threshold: {
    name: '한계값',
    description: '기준 밝기에 따라 검정과 흰색으로 나눕니다.',
    controls: [control('threshold', '한계값', 0, 255, 128)],
  },
PhotoshotDEVELOPMENT JOURNAL / 14IMPLEMENTATION

Inside the
calculation.

Four levels do not mean four colors

The mapping acts independently on R, G and B. Four levels allow up to 4³=64 RGB combinations, though a particular photograph may use fewer.

Even two levels can retain red, green and blue. Threshold instead combines channels into one brightness value and produces black or white. Blending with the original through opacity can also introduce intermediate values.

Keep the old calculation for old documents

Replacing the formula for every document would change previously saved work. Photoshot reads the adjustment revision and retains the earlier mapping for older projects.

Today the existing upgrade checks passed again, including exact pixel preservation for old sixteen-adjustment projects and serialization. Improving a calculation and preserving existing work are linked responsibilities.

PHOTOSHOT / SOURCE NOTES14

Read the code.

src/adjustments.ts · v0.16.0
L541–547
Photoshot implementation ↗
        if (t === 'invert') x = 1 - x;
        if (t === 'posterize')
          x =
            a.revision === 2
              ? Math.min(v.steps - 1, Math.floor((i * v.steps) / 256)) / (v.steps - 1)
              : Math.round(x * (v.steps - 1)) / (v.steps - 1);
        return clamp(x);
src/adjustments.ts · v0.16.0
L550–554
Photoshot implementation ↗
    return (r, g, b, out) => {
      out[0] = tables[0][r];
      out[1] = tables[1][g];
      out[2] = tables[2][b];
    };
src/adjustments.ts · v0.16.0
L253–265
Photoshot implementation ↗
export function isIdentityAdjustment(a: Adjustment): boolean {
  if (a.type === 'lookup') return !a.cube;
  if (a.type === 'curves') return channels.every((c) => a.curves![c].every((p) => p.x === p.y));
  if (a.type === 'photo') return a.values.density === 0;
  if (['blackwhite', 'gradient', 'invert', 'posterize', 'threshold'].includes(a.type)) return false;
  if (a.bands?.some((b) => b.hue || b.saturation || b.lightness)) return false;
  return definitions[a.type].controls.every(
    (c) =>
      ['preserve', 'relative', 'skin', 'legacy', 'colorizeHue', 'colorizeSaturation'].includes(
        c.key,
      ) || (a.values[c.key] ?? c.initial) === c.initial,
  );
}
PhotoshotDEVELOPMENT JOURNAL / 14IN PRACTICE

In the editor.

Actual Photoshot workspace. Settings and the image are shown together.
Actual Photoshot workspace. Settings and the image are shown together.

The photographic example uses five levels

The stored photograph and project use Levels 5 and opacity 100%. The four-level numerical example is kept separate from this five-level demonstration. The AI-generated portrait was edited in actual Photoshot.

The known quantization formula became an editing tool through input bounds, revision handling and compositing. Part 15 divides brightness into black and white with Threshold.

PhotoshotDEVELOPMENT JOURNAL / 14BEFORE / AFTER

The visible result.

AI-generated study image / before editing
01 / AI-generated study image / before editing
Photoshot output / edited result
02 / Photoshot output / edited result

AI-generated study photograph. Editor captures and outputs show Photoshot in use.