Acquisizione: zoom +/-, Adatta e pan sui tre riquadri immagine, lato anteprima che segue lo zoom
Barra in sovrimpressione fuori dal palco che scorre, Ctrl+rotella, pan col puntatore. Refs #111 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,21 @@ PAGINA = r"""<!doctype html>
|
||||
.riquadro .titolo { font-size: .88rem; font-weight: 600; }
|
||||
.riquadro .titolo span { font-weight: 400; font-size: .78rem; color: var(--secondario); margin-left: 6px; }
|
||||
.riquadro .palco { margin-top: 4px; }
|
||||
/* #111: visore zoom. .visore (position:relative) NON scorre: la barra e' sua figlia e resta ferma; a scorrere e' il .palco da zoomato */
|
||||
.visore { position: relative; margin-top: 10px; }
|
||||
.riquadro .visore { margin-top: 4px; }
|
||||
.visore > .palco { margin-top: 0; }
|
||||
.barraZoom { position: absolute; top: 8px; right: 8px; z-index: 3; display: flex; align-items: center; gap: 4px;
|
||||
padding: 3px 5px; border-radius: 6px; background: rgba(0, 0, 0, .6); color: #fff; font-size: .8rem; }
|
||||
.barraZoom button { padding: 2px 9px; font-size: .85rem; background: rgba(255, 255, 255, .15); color: #fff; }
|
||||
.barraZoom span { min-width: 3.4em; text-align: right; font-variant-numeric: tabular-nums; }
|
||||
.palco.zoomata + .barraZoom { right: 26px; } /* #111: lascia libera la barra di scorrimento verticale */
|
||||
.visore:has(.cornice[hidden]) .barraZoom { display: none; } /* #111: barra nascosta finche' la cornice e' nascosta */
|
||||
/* #111: stato zoomato: palco ad altezza fissa che scorre, cornice a dimensioni esplicite (impostate da JS); centrata con margin:auto solo se piu' piccola */
|
||||
.palco.zoomata { display: flex; align-items: flex-start; justify-content: flex-start; overflow: auto; cursor: grab; touch-action: none; }
|
||||
.palco.zoomata.panning { cursor: grabbing; }
|
||||
.palco.zoomata .cornice { display: block; flex: none; margin: auto; max-width: none; }
|
||||
.palco.zoomata .cornice img { display: block; width: 100%; height: 100%; max-width: none; max-height: none; min-height: 0; }
|
||||
.sorgente-riga { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
|
||||
.sorgente-riga .campo-sorgente { flex: 1 1 260px; min-width: 0; }
|
||||
.sorgente-riga select { width: 100%; }
|
||||
@@ -164,6 +179,7 @@ PAGINA = r"""<!doctype html>
|
||||
<div class="riquadri" id="riquadri">
|
||||
<div class="riquadro">
|
||||
<div class="titolo">Immagine<span>grezza, senza correzione</span></div>
|
||||
<div class="visore"> <!-- #111 -->
|
||||
<div class="palco">
|
||||
<div id="vuoto">Anteprima ferma. Premere «Avvia anteprima» o «Scatta».</div>
|
||||
<div class="cornice" id="cornice" hidden>
|
||||
@@ -171,9 +187,12 @@ PAGINA = r"""<!doctype html>
|
||||
<div id="mirino" class="mirino" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="barraZoom"><button id="zoomMeno" class="secondario" type="button" title="Riduci">−</button><button id="zoomAdatta" class="secondario" type="button" title="Adatta allo spazio">Adatta</button><button id="zoomPiu" class="secondario" type="button" title="Ingrandisci">+</button><span id="zoomPct">100 %</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="riquadro" id="riquadroCal" hidden>
|
||||
<div class="titolo">Calibrata<span id="nomeCalibrata"></span></div>
|
||||
<div class="visore"> <!-- #111 -->
|
||||
<div class="palco">
|
||||
<div id="vuotoCal">Premere «Avvia anteprima» o «Scatta».</div>
|
||||
<div class="cornice" id="corniceCal" hidden>
|
||||
@@ -181,6 +200,8 @@ PAGINA = r"""<!doctype html>
|
||||
<div id="mirinoCal" class="mirino" hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="barraZoom"><button id="zoomMenoCal" class="secondario" type="button" title="Riduci">−</button><button id="zoomAdattaCal" class="secondario" type="button" title="Adatta allo spazio">Adatta</button><button id="zoomPiuCal" class="secondario" type="button" title="Ingrandisci">+</button><span id="zoomPctCal">100 %</span></div>
|
||||
</div>
|
||||
<!-- #93: scelta della calibrazione da attivare sulla sorgente, con CONFERMA -->
|
||||
<div class="riga scelta-cal">
|
||||
<select id="sceltaCal" aria-label="Calibrazione da attivare" disabled></select>
|
||||
@@ -239,12 +260,15 @@ PAGINA = r"""<!doctype html>
|
||||
<h2>Calibrazione</h2>
|
||||
<div class="stato" style="margin:0 0 10px">Sorgente: <strong id="calSorgente">-</strong> — immagine grezza dalla tab Acquisizione</div>
|
||||
<!-- #94: immagine viva (stesso frame dell'anteprima) e «Blocca immagine» per calcolare su quella -->
|
||||
<div class="visore" style="margin-top:0"> <!-- #111 -->
|
||||
<div class="palco" style="margin-top:0">
|
||||
<div id="vuotoCalTab">Nessuna immagine: «Scatta» o avviare l'anteprima nella tab Acquisizione</div>
|
||||
<div class="cornice" id="corniceCalTab" hidden>
|
||||
<img id="immagineCalTab" alt="Immagine grezza della sorgente">
|
||||
</div>
|
||||
</div>
|
||||
<div class="barraZoom"><button id="zoomMenoCalTab" class="secondario" type="button" title="Riduci">−</button><button id="zoomAdattaCalTab" class="secondario" type="button" title="Adatta allo spazio">Adatta</button><button id="zoomPiuCalTab" class="secondario" type="button" title="Ingrandisci">+</button><span id="zoomPctCalTab">100 %</span></div>
|
||||
</div>
|
||||
<div class="riga" style="margin-top:8px">
|
||||
<button id="scattaCal" class="secondario" type="button">Scatta</button>
|
||||
<label style="margin:0 0 6px"><input id="bloccaImg" type="checkbox"> Blocca immagine</label>
|
||||
@@ -469,6 +493,7 @@ function cambioSorgente(salva) {
|
||||
if (A.bloccata) rilasciaBlocco(); // #94: casella spenta e DELETE sulla sorgente vecchia (prima di svuotare i riquadri)
|
||||
azzeraIndicatori();
|
||||
svuotaRiquadri(); // #92: il frame vecchio non e' della nuova sorgente
|
||||
tornaAdatta(); // #111: nuova sorgente = visori ad Adatta
|
||||
A.calAttiva = null; A.bloccoCal = false; // #92: stato calibrazione della nuova sorgente ancora da leggere
|
||||
annullaRimuovi();
|
||||
$("rimuoviImmagine").hidden = !sorgenteImmagine();
|
||||
@@ -485,6 +510,131 @@ function cambioSorgente(salva) {
|
||||
// #91: ris = {w, h} dell'ultimo frame; #92: calAttiva = la sorgente ha una calibrazione attiva (null = non nota), bloccoCal = un 409 ha fermato le richieste calibrata; urlCal/seqCal = riquadro calibrata, bloccata/sorgenteBloccata/urlBlocco/seqBlocco = #94 immagine bloccata per il calcolo (il riquadro della tab Calibrazione non segue piu' il ciclo), lato = ultimo lato_max
|
||||
var A = { attiva: false, gen: 0, inVolo: false, url: null, urlCal: null, seqCal: 0, bloccata: false, bloccando: 0, sorgenteBloccata: null, urlBlocco: null, seqBlocco: 0, lato: 1280, calAttiva: null, bloccoCal: false, nitMax: 0, ris: null };
|
||||
|
||||
// ---- #111: visori con zoom +/-, Adatta e pan (come il wizard DXF #104), uno per riquadro immagine; ognuno ha il suo zoom
|
||||
// z = pixel CSS per pixel di camera (100 % = pixel della camera), limiti [zFit, ZMAX]; le dimensioni del frame pieno sono A.ris
|
||||
var ZMAX = 8, PASSO = 1.25;
|
||||
var V = {};
|
||||
var sondaAlt = null;
|
||||
// #111: altezza massima del palco = max-height che il CSS da' alle immagini (letta da una sonda nascosta: nessun numero duplicato, segue la media query)
|
||||
function altezzaMax() {
|
||||
if (!sondaAlt) {
|
||||
sondaAlt = document.createElement("div");
|
||||
sondaAlt.className = "cornice";
|
||||
sondaAlt.hidden = true;
|
||||
sondaAlt.appendChild(document.createElement("img"));
|
||||
document.body.appendChild(sondaAlt);
|
||||
}
|
||||
var h = parseFloat(getComputedStyle(sondaAlt.firstChild).maxHeight);
|
||||
return isFinite(h) && h > 0 ? h : 400;
|
||||
}
|
||||
// #111: zoom che fa stare tutto il frame nel palco (0 = non calcolabile: palco nascosto o risoluzione ignota)
|
||||
function zFit(v) {
|
||||
var larg = v.palco.offsetWidth - 2 * v.palco.clientLeft; // larghezza utile senza la barra di scorrimento
|
||||
if (!A.ris || larg <= 0) return 0;
|
||||
return Math.min(larg / A.ris.w, altezzaMax() / A.ris.h);
|
||||
}
|
||||
function zoomata(v) { return v.palco.classList.contains("zoomata"); }
|
||||
// #111: applica lo stato al DOM. Adatta (z == zFit): nessuna regola nuova, layout di prima. Zoomato: palco ad altezza fissa che scorre, cornice W*z x H*z
|
||||
function applica(v) {
|
||||
var zf = zFit(v);
|
||||
if (!zf) return;
|
||||
var z = v.adatta ? zf : Math.max(zf, Math.min(ZMAX, v.z));
|
||||
v.z = z;
|
||||
if (z > zf * 1.0001) {
|
||||
v.palco.classList.add("zoomata");
|
||||
v.palco.style.height = altezzaMax() + "px";
|
||||
v.cornice.style.width = (A.ris.w * z) + "px";
|
||||
v.cornice.style.height = (A.ris.h * z) + "px";
|
||||
} else {
|
||||
v.palco.classList.remove("zoomata", "panning");
|
||||
v.palco.style.height = v.cornice.style.width = v.cornice.style.height = "";
|
||||
}
|
||||
v.pct.textContent = Math.round(z * 100) + " %";
|
||||
}
|
||||
// #111: zoom che tiene fermo il centro visibile (stessa idea del wizard), centro misurato in pixel di camera
|
||||
function zoomBy(v, f) {
|
||||
var zf = zFit(v);
|
||||
if (!zf) return;
|
||||
var vecchio = v.z || zf;
|
||||
var z = Math.max(zf, Math.min(ZMAX, vecchio * f));
|
||||
if (z === vecchio) return;
|
||||
var p = v.palco, pr = p.getBoundingClientRect(), cr = v.cornice.getBoundingClientRect();
|
||||
var cx = A.ris.w / 2, cy = A.ris.h / 2;
|
||||
if (cr.width > 0 && cr.height > 0) {
|
||||
cx = Math.max(0, Math.min(A.ris.w, (pr.left + p.clientLeft + p.clientWidth / 2 - cr.left) / (cr.width / A.ris.w)));
|
||||
cy = Math.max(0, Math.min(A.ris.h, (pr.top + p.clientTop + p.clientHeight / 2 - cr.top) / (cr.height / A.ris.h)));
|
||||
}
|
||||
v.z = z;
|
||||
v.adatta = z <= zf * 1.0001;
|
||||
applica(v);
|
||||
if (zoomata(v)) { p.scrollLeft = cx * z - p.clientWidth / 2; p.scrollTop = cy * z - p.clientHeight / 2; }
|
||||
}
|
||||
function adatta(v) {
|
||||
v.adatta = true;
|
||||
v.z = 0;
|
||||
applica(v);
|
||||
v.palco.scrollLeft = 0;
|
||||
v.palco.scrollTop = 0;
|
||||
}
|
||||
// #111: tutti i visori tornano ad Adatta (cambio sorgente, risoluzione del frame diversa); con A.ris ignoto si toglie anche lo stato zoomato
|
||||
function tornaAdatta() {
|
||||
Object.keys(V).forEach(function (k) {
|
||||
var v = V[k];
|
||||
v.adatta = true;
|
||||
v.z = 0;
|
||||
v.palco.classList.remove("zoomata", "panning");
|
||||
v.palco.style.height = v.cornice.style.width = v.cornice.style.height = "";
|
||||
v.palco.scrollLeft = v.palco.scrollTop = 0;
|
||||
applica(v);
|
||||
});
|
||||
}
|
||||
// #111: lato_max dell'anteprima: 1280 come prima se nessun visore dell'Acquisizione e' zoomato, altrimenti abbastanza pixel per lo zoom piu' alto
|
||||
function latoAnteprima() {
|
||||
var zm = 0;
|
||||
["acq", "cal"].forEach(function (k) {
|
||||
var v = V[k];
|
||||
if (v.palco.offsetWidth > 0 && zoomata(v) && v.z > zm) zm = v.z;
|
||||
});
|
||||
if (!zm || !A.ris) return 1280;
|
||||
return Math.min(4096, Math.max(1280, Math.ceil(Math.max(A.ris.w, A.ris.h) * zm * (window.devicePixelRatio || 1))));
|
||||
}
|
||||
function creaVisore(chiave, suf, idCornice, idImg) {
|
||||
var cornice = $(idCornice);
|
||||
var v = { cornice: cornice, img: $(idImg), palco: cornice.parentNode, pct: $("zoomPct" + suf), z: 0, adatta: true, pan: null };
|
||||
V[chiave] = v;
|
||||
$("zoomMeno" + suf).onclick = function () { zoomBy(v, 1 / PASSO); };
|
||||
$("zoomPiu" + suf).onclick = function () { zoomBy(v, PASSO); };
|
||||
$("zoomAdatta" + suf).onclick = function () { adatta(v); };
|
||||
// #111: Ctrl+rotella = zoom; senza Ctrl la rotella scorre la pagina
|
||||
v.palco.addEventListener("wheel", function (ev) {
|
||||
if (!ev.ctrlKey) return;
|
||||
ev.preventDefault();
|
||||
zoomBy(v, ev.deltaY < 0 ? PASSO : 1 / PASSO);
|
||||
}, { passive: false });
|
||||
// #111: pan col puntatore (solo zoomato): scroll del palco di quanto si muove il puntatore
|
||||
v.palco.addEventListener("pointerdown", function (ev) {
|
||||
if (!zoomata(v) || (ev.pointerType === "mouse" && ev.button)) return;
|
||||
ev.preventDefault(); // senza, Chromium manda pointercancel al primo movimento e il pan si ferma
|
||||
v.pan = { x: ev.clientX, y: ev.clientY, left: v.palco.scrollLeft, top: v.palco.scrollTop };
|
||||
v.palco.classList.add("panning");
|
||||
try { v.palco.setPointerCapture(ev.pointerId); } catch (e) { /* puntatore gia' rilasciato */ }
|
||||
});
|
||||
v.palco.addEventListener("pointermove", function (ev) {
|
||||
if (!v.pan) return;
|
||||
v.palco.scrollLeft = v.pan.left - (ev.clientX - v.pan.x);
|
||||
v.palco.scrollTop = v.pan.top - (ev.clientY - v.pan.y);
|
||||
});
|
||||
var fine = function () { v.pan = null; v.palco.classList.remove("panning"); };
|
||||
v.palco.addEventListener("pointerup", fine);
|
||||
v.palco.addEventListener("pointercancel", fine);
|
||||
// #111: palco ridimensionato (finestra, griglia a due riquadri, tab mostrata): ricalcola zFit e riapplica
|
||||
if (window.ResizeObserver) new ResizeObserver(function () { applica(v); }).observe(v.palco);
|
||||
else window.addEventListener("resize", function () { applica(v); });
|
||||
}
|
||||
creaVisore("acq", "", "cornice", "immagine");
|
||||
creaVisore("cal", "Cal", "corniceCal", "immagineCal");
|
||||
creaVisore("tab", "CalTab", "corniceCalTab", "immagineCalTab");
|
||||
|
||||
function azzeraIndicatori() {
|
||||
A.nitMax = 0;
|
||||
["nitidezza", "saturati", "media", "dimensioni", "nitidezzaMax"].forEach(function (i) { $(i).textContent = "-"; });
|
||||
@@ -594,7 +744,7 @@ function aggiornaIndicatori(h) {
|
||||
$("dimensioni").textContent = fmt(w, 0) + " x " + fmt(a, 0);
|
||||
// #91: risoluzione nota del frame, per segnare le calibrazioni che non combaciano
|
||||
var nw = parseInt(w, 10), na = parseInt(a, 10);
|
||||
if (!A.ris || A.ris.w !== nw || A.ris.h !== na) { A.ris = { w: nw, h: na }; segnaRisoluzioni(); }
|
||||
if (!A.ris || A.ris.w !== nw || A.ris.h !== na) { A.ris = { w: nw, h: na }; segnaRisoluzioni(); tornaAdatta(); } // #111: risoluzione diversa = Adatta
|
||||
}
|
||||
$("raddrizzato").textContent = "frame grezzo"; // #92: l'indicatore viene sempre dal frame grezzo
|
||||
}
|
||||
@@ -639,7 +789,7 @@ async function ciclo(g) {
|
||||
while (A.bloccando) await pausa(20); // #94: durante «Blocca immagine» nessuna nuova anteprima
|
||||
if (!A.attiva || g !== A.gen) return;
|
||||
var t0 = Date.now();
|
||||
var esito = await unaAnteprima(1280);
|
||||
var esito = await unaAnteprima(latoAnteprima()); // #111: 1280 salvo zoom sull'Acquisizione
|
||||
if (g !== A.gen) return;
|
||||
if (esito === "fatale") { fermaAnteprima(); return; }
|
||||
await pausa(esito === "errore" ? 1500 : Math.max(100 - (Date.now() - t0), 0));
|
||||
|
||||
Reference in New Issue
Block a user