Skip to content

Commit 4084faf

Browse files
committed
composite gain maps without the source light where it isn't used
1 parent 8c0df98 commit 4084faf

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Performance
66

77
- Drawing on an image with a gain map (an HDR photo) is much faster: `drawBox()` and `drawCircle()` only recompute the part of the map they cover, instead of the whole map for every call. 400 scanlines over a 1820x1820 Ultra HDR photo take 0.3 s instead of 5.8 s, with the same result.
8+
- `composite()` onto or from an image with a gain map is up to twice as fast: 1820x1820 with a blend mode like `'add'` takes 0.22 s instead of 0.48 s, with the same result. Gain maps stay in sync with the pixels as before.
89

910
## 3.20.0
1011

‎src/image.ts‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1895,24 +1895,46 @@ export class Image {
18951895
const sourceOS = sourceMap?.offsetSDR ?? [0, 0, 0], sourceOH = sourceMap?.offsetHDR ?? [0, 0, 0];
18961896
const oS = map.offsetSDR, oH = map.offsetHDR;
18971897
const src = source.bitmap, Gs = [0, 0, 0];
1898+
let lastGain = NaN, lastPower = 1;
18981899
gain.redraw(this, map, left, top, right, bottom, (px, py, S, boost, out) => {
18991900
const s = 4 * ((py - y) * source.width + px - x), b = 4 * ((py - top) * beforeWidth + px - left);
19001901
const as = src[s + 3] / 255 * opacity, ab = before[b + 3] / 255;
19011902
const ao = as + ab * (1 - as);
1902-
if (sample) sample(px - x, py - y, Gs);
1903+
// (blend modes over an opaque backdrop keep the backdrop's boost on the blended color: no source light needed)
1904+
if (blend && ab === 1) {
1905+
for (let c = 0; c < 3; c++) out[c] = (S[c] + oS[c]) * boost[c] - oH[c];
1906+
return;
1907+
}
1908+
if (ao <= 0) {
1909+
out[0] = out[1] = out[2] = 0;
1910+
return;
1911+
}
1912+
let sampled = false;
19031913
for (let c = 0; c < 3; c++) {
19041914
const Sd = gain.LINEAR[before[b + c]], Ss = gain.LINEAR[src[s + c]];
19051915
const Hd = (Sd + oS[c]) * boost[c] - oH[c];
1906-
const Hs = sample ? (Ss + sourceOS[c]) * 2 ** Gs[c] - sourceOH[c] : Ss;
1907-
if (blend && ab === 1) {
1908-
// (blend modes keep the backdrop's boost on the blended color)
1909-
out[c] = (S[c] + oS[c]) * boost[c] - oH[c];
1910-
continue;
1911-
}
19121916
// the source's share of the result as the pixels got it (composited in sRGB, not linear light): the
19131917
// HDR light is mixed alike, so it looks like the SDR result, boosted
1914-
const share = Math.abs(Ss - Sd) > 1e-4 ? Math.min(1, Math.max(0, (S[c] - Sd) / (Ss - Sd))) : ao > 0 ? as / ao : 0;
1915-
out[c] = ao > 0 ? share * Hs + (1 - share) * Hd : 0;
1918+
const share = Math.abs(Ss - Sd) > 1e-4 ? Math.min(1, Math.max(0, (S[c] - Sd) / (Ss - Sd))) : as / ao;
1919+
if (share === 0) {
1920+
// (nothing of the source is in the result: its light isn't needed)
1921+
out[c] = Hd;
1922+
continue;
1923+
}
1924+
let Hs = Ss;
1925+
if (sample) {
1926+
if (!sampled) {
1927+
sample(px - x, py - y, Gs);
1928+
sampled = true;
1929+
}
1930+
// (the same gain as the one before has the same power: single channel maps have one in all three)
1931+
if (Gs[c] !== lastGain) {
1932+
lastGain = Gs[c];
1933+
lastPower = 2 ** lastGain;
1934+
}
1935+
Hs = (Ss + sourceOS[c]) * lastPower - sourceOH[c];
1936+
}
1937+
out[c] = share * Hs + (1 - share) * Hd;
19161938
}
19171939
}, map.channels === 1 && (!sourceMap || sourceMap.channels === 1), Math.max(0, ...(sourceMap?.gainMapMax ?? [0])));
19181940
return this;

0 commit comments

Comments
 (0)