You know this frame, because everybody has a folder of them. A sky that has gone to paper white behind the subject, or a window blazing out of an interior, or the sun somewhere it shouldn't be. You open it, you reach for the Highlights slider, and you pull it down waiting for something to come back.

In Photo Developer the slider worked. It just spent itself on the wrong half. The blown part came back exactly as clipped as it went in, and the whole of the move landed on the transition around it — the part that still had detail and colour — which flattened and cooled toward the colour of the sky. All of the effect, none of it where you were aiming. Andy Hutchinson put it better than I had in the review: "I lost the transition and kept the blown bit, which is unfortunately the wrong way round."

Version 1.6 fixes the transition. Pull Highlights now and the tones it darkens keep their colour instead of drifting toward grey, and the pull stays roughly where you aimed it instead of leaking down into the midtones. The blown part is a separate problem with a different cause, one stage further upstream, and it is the next release's work.

That's the change. The rest of this is what was actually wrong, which took a while to find, and the part at the end I'd rather not write down.


Two problems, not one

The first useful thing was an A/B I should have run months earlier. Open the same file in a peer editor built on the same platform frameworks, pull its highlights the same distance, and it holds detail and colour where mine went to white fog. Same file, same machine, same decode. So whatever was wrong was mine.

That split one symptom into two problems. The grey, cooled-off gradient — which is what was on screen for most of the review's running time — was happening to pixels that were never clipped at all. My tone curve ran per channel, and compressing the top of a per-channel curve pulls R, G and B toward each other. That is desaturation by construction, and in a warm scene desaturation reads as a cool shift. The blown cores were something else entirely: real data destroyed at the decode boundary, before my pipeline ever sees the file.

So most of what the review showed was never the clamp. One of the two is fixed in this release.

The colour half

The fix is small enough to describe in a sentence. Apply the curve to the largest of the three channels and scale the other two by the same factor, f(N)/N, instead of running the curve on each channel independently. Ratios survive by construction, so a colour can get darker without getting greyer.

Measured on the delivered render, chroma error at Highlights −100 went from somewhere between −16% and −30%, depending on the frame, to ±0.2%. That half was done.

The zone half wouldn't move at all

The same measurement, next column over, said the other complaint was untouched. Not improved a little. Untouched. Every pixel in the display 128–173 band still moved, by a mean of ten code values, and on a frame pushed +2 EV up to 42% of pixels below 128 moved as well.

Which is worth saying plainly: the Highlights slider was reaching well past the highlights. Into the lights, and on a pushed frame into the midtones — on the control the review called the one you reach for on half the pictures you take.

I assumed this was point placement. Nudge the control points, confine the pull. It wasn't point placement.

There was no knot in the highlights

Photo Developer's tone curves were built on a five-point curve filter, and that filter applies its curve in a different encoding from the one the app works in. Nothing is broken about that. It is documented behaviour on both sides. But it means every curve I had ever tuned was wrapped in an extra round trip that nobody put there, and the numbers in my source were not the numbers being delivered to the picture.

There was no knot in the highlights to aim with, and there never had been. That is why no amount of fiddling with the points was ever going to confine the pull, and why the calibration note in my own source explaining that this app's −100 lands near another developer's −200 — which I had written up as a considered decision about strength — was really just compensating for a distortion I couldn't see. The long version of that discovery, and what else it turned out to explain, is a separate post.

Leaving the spline

So negative Highlights left the five-point curve. It is now an analytic shoulder: identity below a knee, a smooth compression above it, white landing at 0.82 at −100. Positive Highlights kept its curve, because it was validated by eye and it works; the asymmetry across zero is a decision rather than an oversight.

The knee came off a sweep rather than off taste. At 0.60 the pull leaks into 4–20% of the 128–173 band, which is the original complaint coming back. At 0.75 the leak nearly vanishes, but the whole compression packs into the top quarter of the range and costs measurable top-band retention. It sits at 0.68, where the leak is under 2.6% of that band at less than one code value of movement.

Then the harder question, which came from an adversarial review round and was right: a shaped curve like that can only relocate the flattened band. It can never remove it. Whether that's good enough depends entirely on whether you can put the flat part where the picture isn't — and on a frame pushed +2 EV, you can't, because the picture is in the band being flattened.

The alternative is to protect the detail: separate the image into a smooth base and the fine structure on top of it, apply the shoulder to the base only, and put the structure back afterwards. That is more code, more render time and more ways to be wrong, so it had to earn itself against the cheap version before it shipped. It did: measured on the delivered render at matched pull strength, roughly twice as much local contrast survives the pull on every frame I tested, and the gap widens the harder you pull.

The fix also costs something, and the cost is the sort of thing that is easy not to mention. A smooth base varies across a large blown region, so blown cores come back with a faint modulation — two to three code values, where the plain shoulder gives a dead-flat plateau. Gradation appearing where the sensor recorded a plateau is synthesis: it comes from the neighbourhood, not from data. It is too small for me to see, and it is still invention, so it's written down rather than quietly enjoyed.

Did any of it matter

Roughly three in four of the frames I've developed in Photo Developer carry a negative Highlights value. That's why it was worth doing, and it's the number I'd want to see in somebody else's write-up before reading their algorithm.

The same data cuts the other way. The positive direction of that control, which took more design attention than any other single element in this work, appears on about one frame in a hundred. Effort and use weren't correlated. I don't have a lesson from that yet.

The awkward part

Here is the thing I have been avoiding saying for the whole of this post. I liked it.

I expose to the right — protect the shadows, let the top of the histogram fend for itself — so I blow highlights routinely and on purpose. That's the price of keeping the rest of the tonal range where I want it, and it has never especially bothered me. On the same three in four frames, Highlights was something I reached for sparingly and deliberately: sometimes in the Light panel, sometimes in Curves, sometimes in Tone EQ, often two of them, occasionally all three. Soften the blown area, put a careful tint into the highlights in colour grading, sometimes a little Bloom from the Effects panel. What came out the other end was a look I was happy with. Filmic, papery at the top.

Which means the operator I've spent this post taking apart was, in my own hands, making photographs I liked. And the reason isn't mysterious: per-channel desaturation near the top of the range is roughly what film does, because the dye layers saturate independently and highlights go pastel. I had been getting that for free without knowing where it came from.

What changed my mind wasn't that the look was wrong. It was that I couldn't ask for it. It arrived with every pull whether the frame wanted it or not, it came bundled with a midtone leak I hadn't asked for either, and on the frames where I wanted the tone down and the colour left alone there was no way to say so. Now the control acts where its label says it acts, and if I want the papery white top I can still build it deliberately — grading, Bloom, a curve placed on purpose. Same look, different provenance. That is most of the difference between a tool and a habit, and I only noticed which one I had when the tool started working.

There is a broader version of that, which is that a defect you happen to like is the hardest kind to find, because nothing in you is looking for it. That's the subject of the other post.

The other half

Under the review, a developer who has built one of these engines themselves — and lost a year of their life to it, in their words — asked whether I'd write the highlight work up. This is the half I can write, because it's the half that has shipped.

The complaint had two halves too. I lost the transition, and I kept the blown bit. Version 1.6 fixes the lost transition. The kept blown bit is the next release, and it is not a mystery any more: the data survives the exposure, the decode is what throws it away, and decoding with the range left in reaches it. That part is measured. It was also the part I'd have expected to be hard.

What I didn't expect is that the rest of it isn't really an algorithm question. It's how much of a blown highlight should come back, and who decides. Recovery is not strictly better: shoot straight into the sun and an area reading as blown is sometimes the photograph you meant to take, and no measurement can tell you which frame you're holding. Bring none of it back and you are at the top of this post. There is no constant that is right for both, which makes this a control rather than a calibration — and working out what that control should be is a different kind of work from getting the arithmetic right.

Where I most want it is the frames where the blown area is the subject rather than the light: a bright exterior seen from inside a shaded room, a white wall in full sun beyond a colonnade. On a frame like that the blank patch isn't a stylistic decision I made by exposing to the right. It's a building I couldn't see. That's the one I'd like the option on.

The defect was in my own notes in April, filed as an extreme-exposure curiosity and dismissed as marginal. It sat there for four months. What moved it up the list was using the app on my own photographs — and then somebody else using it on theirs, and saying so out loud.