Building a Free Photoshop Alternative, Part 2: From Konva’s Code to Brightness and Contrast
Brightness and contrast began with a GitHub reference: Konva’s filter implementation. We read it, wrote Photoshot’s own calculation with AI, and then discovered that a working slider did not guarantee the intended output. This entry separates the reference, our implementation and the measurement-driven correction.
Sources checked · Revised October 16, 2026
Read the article ↓

First, we opened Konva’s Contrast.ts
The September 18, 2026 development record shows an actual lookup of konvajs/konva’s src/filters/Contrast.ts. Its pixel loop normalizes RGB and changes contrast around a midpoint. It provided a concrete implementation to inspect.
The evidence shows source consultation and a separately written implementation, not installation or verbatim copying of Konva’s filter into Photoshot.
Our first Photoshot calculation
The earlier formula remains in v0.16.0 for compatibility. It applies brightness first, then adjusts contrast around 0.5. Although Konva helped illustrate midpoint-based processing, these brightness and contrast expressions belong to Photoshot’s implementation.
Reading a reference did not provide a complete adjustment layer. Stored values, masks, opacity and rendering still needed integration.
if (t === 'brightness' && a.revision !== 2) {
x = v.brightness < 0 ? x * (1 + v.brightness / 100) : x + ((1 - x) * v.brightness) / 100;
const contrast = v.contrast < 0 ? 1 + v.contrast / 100 : 1 / (1 - v.contrast / 101);
x = (x - 0.5) * contrast + 0.5;
}Comparison changed the implementation
On September 25 we compared a test chart with Photoshop 27.3.1. At brightness +30 and contrast +20, the intermediate implementation in that comparison had a mean channel-value difference of about 11.505 and a maximum of 25. This was not a direct measurement of the original September 18 code.
The revised normal mode reads measured response data through sampleResponse. The two lines below apply brightness and then contrast. Reference reading had become an application-specific correction based on observed output.
x = sampleResponse(toneResponse.brightness, v.brightness, -150, x);
x = sampleResponse(toneResponse.contrast, v.contrast, -50, x);Compile once, reuse a 256-entry table
Each channel has 256 possible byte inputs. Instead of repeating the full calculation for every pixel, compileAdjustment builds a lookup table and reuses it. The excerpt below is Photoshot’s table-construction code.
Connecting brightness to this shared path also gave levels, curves and exposure a common home. The integration work was as important as the individual formula.
const tables = ['r', 'g', 'b'].map((channel) =>
Float64Array.from({ length: 256 }, (_, i) => {
let x = i / 255;The photo example and the numerical test
The portrait example uses brightness 18 and contrast 12, not the +30/+20 chart settings. The source portrait is AI-generated; the screenshot and adjusted output come from the actual editor.
For the recorded +30/+20 chart, the revision reduced mean difference to about 0.241 and maximum difference to 1. Reversing the order to contrast then brightness produced about 1.150 and 4. Operation order was part of the implementation, too.
From a GitHub reference to verified application code
Konva’s Contrast.ts was the external implementation we consulted. Photoshot’s adjustments.ts and response-data wiring were written and revised with AI. The development work combined finding useful public implementations with integrating and testing our own code.
These numbers describe a specific historical chart and settings, not universal equivalence with Photoshop. Next comes levels and its histogram, connected to the same adjustment path.
