Exposure

Where middle grey lands in S-Log3, and what follows

Every exposure decision in S-Log3 hangs off one number: the code value 18% reflectance lands on. How much room sits above your subject, what the conversion does to it, whether a target from another camera still applies — all of it is arithmetic from there.

This page computes that number, and the seven other formats' equivalents, from public/js/color-transforms.js, the transform library this site ships and runs in both browser and Node. Nothing below is quoted or remembered; rerun instructions are at the bottom.

41.06%, and it is exact

CT.encode('Sony S-Log3', 0.18) returns 0.4105571848 — 41.06% of the code range.

S-Log3's log branch is (420 + log10((x + 0.01) / 0.19) × 261.5) / 1023. At x = 0.18 the inner term is 0.19/0.19 = 1, its log is zero, and the expression collapses to 420/1023 — the two compare equal in floating point, not merely close. The constant 420 in Sony's formula is the middle-grey anchor, and nothing moves it: there is no exposure, gain or ISO term anywhere in the function. 18% reflectance goes to 41.06% by construction.

This site's homepage publishes 41% IRE as the S-Log3 target. The computation lands 0.06 of a point above that.

What sits above and below

AnchorS-Log3 codeStops from grey
Linear zero9.29%
Curve branch junction16.74%−4.00 exactly
Middle grey41.06%0
Ceiling, code 100%100%+7.74

CT.decode('Sony S-Log3', 1.0) is linear 38.42 — 213.45× middle grey, which is log2(38.42 / 0.18) = 7.74 stops of headroom in the file above your subject.

The junction is worth knowing because it is exact. The log branch hands over to a straight line at linear 0.01125, and 0.18 ÷ 0.01125 = 16.000 — exactly four stops under grey. Below it the curve is a line, not a log, so each further stop halves what is left of the 7.45 code points between 9.29% and 16.74%.

The shoulder takes 2.26 of those stops back

The 7.74 stops are what the file holds; what survives conversion to Rec.709 is smaller, and one constant fixes it. The shared shoulder is extended Reinhard with WHITE = 8.0, and shoulder(8) returns exactly 1.0 — linear 8.0 is where the display runs out. That is log2(8.0 / 0.18) = 5.47 stops over middle grey, the same for every format here, because it is a threshold in the linear domain, before any curve. In S-Log3 code values, linear 8.0 sits at 82.59%.

Stops over greyS-Log3 codeRec.709 out (8-bit)
041.06%95
+148.45%129
+256.00%165
+471.27%225
+578.95%246
+5.4782.59%255
+7.74100%255

Read the right-hand column as diminishing returns: the half-stop from +1.0 to +1.5 buys 18 levels of output, the half-stop from +5.0 to +5.5 buys 9. The last 2.26 stops S-Log3 records — 82.59% up to 100% — are grading room through this chain, not picture, and useful only if you decide later the shot should sit lower.

The gamut matrix keeps grey grey. That is all it does.

S-Gamut3.Cine → Rec.709 in this library is a 3×3, and its three rows each sum to 1.000000000000 — deviation from 1.0 is exactly zero, not merely small.

Here is what that buys. Feeding the matrix [v, v, v] computes each output row as v × (row sum), so a row sum of 1.0 means a neutral goes in and the identical neutral comes out — checked at linear 0.02, 0.18, 0.9 and 4.0, output equals input to twelve decimal places.

It does not mean the matrix leaves colour alone. Take a subject that should be neutral but was recorded 10% blue — [0.18, 0.18, 0.198]. Out comes [0.178438, 0.175901, 0.205187], and the blue-to-green ratio went from 1.100 in to 1.1665 out. The matrix did not remove the cast and did not preserve it; it re-mapped it. Only the neutral axis is fixed.

How big that job is: S-Gamut3.Cine's own blue primary comes out of the matrix as (−0.0868, −0.2277, 1.3993) — two negative channels, which the shipped chain clamps to zero because Rec.709 has no coordinate for them. Sweep that primary from linear 8.0 down to 0.06 — seven stops — and both stay clamped at zero the whole way: the output runs 0/0/255 at the top to 0/0/63 at the bottom, saturation 1.000 at every step. Exposure changes how bright it is; it puts nothing back into the other two channels.

Two of the eight carry a row-sum error bigger than floating-point noise. Apple Log's rows sum to 1.0001, 1.0 and 0.9999; ARRI LogC3's first row is short by 1e-6. Run 18% grey through the full Apple Log chain and the output is 0.373160 / 0.373142 / 0.373124 — the outer channels 1.8e-5 either side of the middle one, exactly where the algebra puts them. ARRI's smaller error does the same at 1.8e-7. Both round to 95/95/95 in 8-bit — a transcription artefact, not a picture problem. The other six land on 0.373142 in all three channels.

Seven other formats, and why your target does not travel

Every one of these curves encodes 18% grey. None of them encodes it in the same place.

Format18% greyCode points per stopFile ceiling above greyCode at linear 8.0Stops above display white
Apple Log48.83%8.236.0695.00%0.58
Panasonic V-Log42.33%7.028.0081.64%2.53
Sony S-Log341.06%7.407.7482.59%2.26
DJI D-Log M39.88%7.407.8781.53%2.39
ARRI LogC339.10%7.178.2679.30%2.78
Blackmagic Film Gen538.36%5.9010.2771.08%4.80
RED Log3G1033.33%6.3410.0069.46%4.53
Canon C-Log333.10%8.746.5088.72%1.03

The grey codes span 15.73 points, from 33.10% to 48.83%. That spread is the reason a target is per-format and not per-shooter.

Put a subject on 41.06% while the camera is writing something else, and the file is off by a computable amount:

Actually recordingError at 41.06%
RED Log3G10+1.21 stops
Canon C-Log3+0.92 stops
Blackmagic Film Gen5+0.46 stops
ARRI LogC3+0.28 stops
DJI D-Log M+0.16 stops
Panasonic V-Log−0.18 stops
Apple Log−0.98 stops

Across the full eight-by-eight grid of targets against formats, the worst pairing is metering for Apple Log while recording RED Log3G10: 2.40 stops.

The points-per-stop column is not a constant either. One stop at grey is 5.90 code points in Blackmagic Film Gen5 and 8.74 in Canon C-Log3, so the same 3-point wobble on a waveform is 0.51 of a stop in the first and 0.34 in the second — half again as much error for an identical reading.

What actually follows for exposure

Only these follow from the numbers above. Nothing here is a menu setting, an ISO or a zebra value — the transform library does not carry those, and this page will not invent them.

Place middle grey at 41.06% of the code range in S-Log3. It is a property of the format, not of a body, a lens or a venue: the function carries no term for any of those, so it is the same on every camera writing this curve.

Read distance from 41.06% in stops, not in points. The first stop above grey is 7.40 points wide and the spacing widens higher up, so the divisor that fits the whole run to the ceiling is 7.6. Subtract 41, divide by 7.6. Swept at 0.05-stop intervals from grey to code 100%, that shortcut is never more than 0.027 of a stop out. Below grey it drifts as the curve flattens — it is a highlight tool only.

Treat 82.59% as the practical ceiling, not 100%. Everything above it is one colour after conversion. If a stage wash or a beam sits there, it is committed.

Do not carry a target between formats. The error is stated above, per format, and it ranges from 0.16 to 1.21 stops against the S-Log3 number alone.

Do not expect the gamut matrix to fix a cast. It fixes neutrals and nothing else. White balance at the camera or as a correction; the transform will not do it.

Where the lattice falls against a stop

Reading the declared lattice off all 60 published LUT pages gives 16³ (5), 17³ (29), 32³ (16), 33³ (8) and 65³ (2). A 17³ lattice has nodes every 6.25 code points, and one stop at S-Log3 grey is 7.40 — so one node interval is 0.845 of a stop, and the 5.47-stop run from grey to display white is crossed in 6.65 interpolated steps. At 65³ the interval is 0.211 of a stop and the same run gets 26.6. middleware/lutfile.js bounds uploads at MIN_3D_SIZE = 8 and MAX_3D_SIZE = 65. On a smooth haze wash, that interval is what bands.

How these numbers were produced

Every curve, stop and Rec.709 figure comes from public/js/color-transforms.js, run in Node. Nothing was measured from footage, nothing quoted from a manufacturer.

const CT = require('./public/js/color-transforms.js');
CT.encode('Sony S-Log3', 0.18);        // 0.4105571848 === 420/1023
CT.decode('Sony S-Log3', 1.0);         // 38.4209 linear -> 7.7378 stops over 0.18
CT.shoulder(CT.WHITE);                 // exactly 1.0, WHITE = 8
CT.encode('Sony S-Log3', CT.WHITE);    // 0.8259
CT.matrix('Sony S-Log3').map(r => r.reduce((a,b) => a + b));   // [1, 1, 1]
CT.applyMatrix(CT.matrix('Sony S-Log3'), [0.18, 0.18, 0.198]);
CT.toRec709('Sony S-Log3', [0.4106, 0.4106, 0.4106]);

The lattice counts are not from that file: they are the LUT_3D_SIZE each of the 60 published LUT pages reports, tallied. middleware/lutfile.js parses that header and holds the 8 and 65 bounds.

The comparison tables loop CT.CAMERAS through the same calls. Cross-format error is Math.log2(CT.decode(other, CT.encode('Sony S-Log3', 0.18)) / 0.18); the 8-bit column is Math.round(CT.toRec709(...)[0] * 255). Round-trip error on decode(encode(0.18)) is at most 8.3e-17 across all eight curves, so the anchors are algebra, not a fit.

Sources

  • public/js/color-transforms.js — the shipped transform library. Every curve value, every stop count, the WHITE = 8.0 shoulder, all eight gamut matrices, the Rec.709 column.
  • middleware/lutfile.js — the upload validator, source of the 8 and 65 lattice bounds.
  • The site's own colour-pipeline notes — the published 18% grey code values for all eight formats, which the computation reproduces, and the count of 60 published LUTs.
  • The 60 published LUT pages — each states its own lattice size; that is the 16/17/32/33/65 tally.
  • The homepage FAQ — the 41% IRE middle-grey target.
All guides in the Academy