Skip to content

Commit 7dfded7

Browse files
committed
Écrêtage : pourcentages en direct et histogramme non faussé
- Le mode écrêtage peint une copie de l'aperçu : l'histogramme et les statistiques ne sont plus calculés sur l'image colorée rouge/bleu - Le bouton affiche en direct le % de zones brûlées (▲) et bouchées (▼) - Message explicatif à l'activation (y compris quand il n'y a rien à signaler) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EsNwAFsCdaUHa6jesYNTTf
1 parent a000a5d commit 7dfded7

4 files changed

Lines changed: 48 additions & 9 deletions

File tree

‎css/style.css‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,8 @@ table.meta td:last-child { font-family: ui-monospace, SFMono-Regular, Menlo, mon
224224
content: ""; position: absolute; left: 9px; top: 50%; width: 10px; height: 10px; margin-top: -6px;
225225
border: 2px solid var(--bg-3); border-top-color: var(--accent); border-radius: 50%; animation: spin .8s linear infinite;
226226
}
227+
228+
/* Pourcentages d'écrêtage dans le bouton */
229+
#clipBtn .clip-hi, #clipBtn .clip-lo { font-variant-numeric: tabular-nums; font-size: .78rem; padding: 1px 5px; border-radius: 6px; background: rgba(0,0,0,.35); }
230+
#clipBtn .clip-hi { color: #ffb3c0; }
231+
#clipBtn .clip-lo { color: #b3cfff; }

‎index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ <h1>Développez vos RAW dans le navigateur</h1>
6262
<canvas id="canvas" hidden></canvas>
6363
<div class="viewer-tools" id="viewerTools" hidden>
6464
<button class="chip" id="beforeBtn" title="Maintenir pour voir l'original">Avant / Après</button>
65-
<button class="chip" id="clipBtn" aria-pressed="false" title="Afficher l'écrêtage (rouge : blancs, bleu : noirs)">Écrêtage</button>
65+
<button class="chip" id="clipBtn" aria-pressed="false" title="Colore les zones sans détail : rouge = hautes lumières brûlées, bleu = ombres bouchées">Écrêtage</button>
6666
</div>
6767
<div class="info-line" id="infoLine" hidden></div>
6868
<div class="full-status" id="fullStatus" hidden></div>

‎js/app.js‎

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,43 @@ function setupProcCanvas() {
268268
function renderProcessed() {
269269
const { data, w, h } = state.preview;
270270
const pr = makeProcessor(state.params, state.base);
271-
processRGBA(pr, data, proc.img.data, w * h, state.clipWarn);
272-
proc.ctx.putImageData(proc.img, 0, 0);
271+
processRGBA(pr, data, proc.img.data, w * h);
272+
// proc.img reste « propre » (histogramme, statistiques) ; l'alerte d'écrêtage
273+
// est peinte sur une copie
274+
proc.ctx.putImageData(state.clipWarn ? clipOverlay(proc.img) : proc.img, 0, 0);
275+
updateClipButton();
276+
}
277+
278+
function updateClipButton() {
279+
const btn = $('#clipBtn'), c = state.clipCounts;
280+
if (!state.clipWarn || !c) { btn.textContent = 'Écrêtage'; return; }
281+
const pct = (v) => (v * 100).toFixed(v < 0.1 && v > 0 ? 2 : 1).replace('.', ',') + '%';
282+
btn.innerHTML = `Écrêtage <span class="clip-hi">▲${pct(c.hi)}</span> <span class="clip-lo">▼${pct(c.lo)}</span>`;
283+
}
284+
285+
// Rouge : au moins un canal saturé (≥ 254) ; bleu : noir pur (tous canaux ≤ 1)
286+
function clipOverlay(img) {
287+
if (!proc.clipImg || proc.clipImg.width !== img.width) proc.clipImg = new ImageData(img.width, img.height);
288+
const s = img.data, d = proc.clipImg.data;
289+
let hi = 0, lo = 0;
290+
for (let i = 0; i < s.length; i += 4) {
291+
const r = s[i], g = s[i + 1], b = s[i + 2];
292+
if (r >= 254 || g >= 254 || b >= 254) { d[i] = 255; d[i + 1] = 0; d[i + 2] = 60; hi++; }
293+
else if (r <= 1 && g <= 1 && b <= 1) { d[i] = 0; d[i + 1] = 90; d[i + 2] = 255; lo++; }
294+
else { d[i] = r; d[i + 1] = g; d[i + 2] = b; }
295+
d[i + 3] = 255;
296+
}
297+
const n = s.length / 4;
298+
state.clipCounts = { hi: hi / n, lo: lo / n };
299+
return proc.clipImg;
300+
}
301+
302+
function clipSummary() {
303+
const c = state.clipCounts;
304+
if (!c) return '';
305+
const pct = (v) => (v * 100).toFixed(v < 0.01 ? 2 : 1).replace('.', ',') + ' %';
306+
if (c.hi < 0.0005 && c.lo < 0.0005) return 'Écrêtage : aucune zone brûlée ni bouchée ✓';
307+
return `Écrêtage — rouge : ${pct(c.hi)} brûlé · bleu : ${pct(c.lo)} bouché`;
273308
}
274309

275310
function beforeCanvas() {
@@ -804,6 +839,10 @@ beforeBtn.addEventListener('contextmenu', (e) => e.preventDefault());
804839
$('#clipBtn').addEventListener('click', () => {
805840
state.clipWarn = !state.clipWarn;
806841
$('#clipBtn').setAttribute('aria-pressed', String(state.clipWarn));
842+
if (state.clipWarn) {
843+
renderProcessed(); drawView();
844+
toast(clipSummary() + '. Réglez Hautes lumières / Blancs / Noirs pour les réduire.', false, 5000);
845+
}
807846
scheduleRender(true);
808847
});
809848
window.addEventListener('keydown', (e) => {

‎js/pipeline.js‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,11 @@ const ENC_LUT = (() => {
162162
})();
163163

164164
// Traite un tampon linéaire RVB (Float32 0..1) vers RGBA 8 bits.
165-
export function processRGBA(proc, src, dst, n, clipWarn = false) {
165+
export function processRGBA(proc, src, dst, n) {
166166
const px = proc.pixel;
167167
for (let i = 0, s = 0, d = 0; i < n; i++, s += 3, d += 4) {
168168
px(src[s], src[s + 1], src[s + 2], dst, d);
169169
dst[d + 3] = 255;
170-
if (clipWarn) {
171-
const r = dst[d], g = dst[d + 1], b = dst[d + 2];
172-
if (r >= 254 || g >= 254 || b >= 254) { dst[d] = 255; dst[d + 1] = 0; dst[d + 2] = 60; }
173-
else if (r <= 1 && g <= 1 && b <= 1) { dst[d] = 0; dst[d + 1] = 90; dst[d + 2] = 255; }
174-
}
175170
}
176171
}
177172

0 commit comments

Comments
 (0)