Verbeter grafische kwaliteit van kleurplaten (v0.4.67-beta) #5

Open
bes-r wants to merge 208 commits from bes-r/coloring-quality-v0.4.67 into main AGit
4 changed files with 103 additions and 25 deletions
Showing only changes of commit 652e039a39 - Show all commits

View file

@ -1 +1 @@
0.3.64-beta
0.3.65-beta

View file

@ -123,6 +123,9 @@
/* drawing layer on the board: sits ABOVE widgets like a transparency sheet,
so you can annotate over everything; clicks pass through when pen is off */
#boardInk{position:absolute; inset:0; pointer-events:none; z-index:4000;}
/* live-laag van de markeerstift: dekkend getekend, doorschijnend getoond;
bij het loslaten wordt de haal in één keer op de inktlaag gezet */
#boardMark{position:absolute; inset:0; pointer-events:none; z-index:4000; opacity:.32;}
body.pen #boardInk{pointer-events:auto; cursor:crosshair;}
body.pen.pen-text #boardInk{cursor:text;}
body.pen.pen-hand #boardInk{pointer-events:none;} /* hand tool: interact with everything */
@ -360,6 +363,16 @@
box-shadow:0 2px 6px rgba(0,0,0,.25);
}
.dtool-opts button:hover{background:var(--accent-soft);}
/* lengteknop toont de maat ("10 cm") en is dus breder dan een rond knopje */
.dtool-opts .do-wide{width:auto; min-width:48px; padding:0 9px; border-radius:12px;}
/* live hoek-uitlezing tijdens het draaien van liniaal/geodriehoek */
.dtool-deg{
display:none; position:absolute; left:50%; top:50%; margin:-16px 0 0 -24px;
width:48px; height:32px; line-height:32px; text-align:center; pointer-events:none;
background:rgba(30,50,90,.85); color:#fff; font-size:14px; font-weight:800;
border-radius:9px; z-index:3;
}
.dtool-deg.snapped{background:var(--green);}
.dtool-lens{z-index:4500;} /* the glass sits above the ink sheet */
.dtool-lens canvas{
border:5px solid #5a6c85; box-shadow:0 8px 22px rgba(0,0,0,.3);

View file

@ -352,16 +352,27 @@ const bInk = document.createElement("canvas");
bInk.id = "boardInk";
board.insertBefore(bInk, board.firstChild);
const bCtx = bInk.getContext("2d");
const bDpr = window.devicePixelRatio || 1;
/* aparte laag voor de markeerstift: dáár tekent hij dekkend, de laag zelf is
doorschijnend (CSS), en pas bij het loslaten wordt de hele haal in één keer
met 32% op de inktlaag gezet. Zo stapelen overlappende lijnstukjes binnen
één haal niet meer tot donkere vlekken op elke overgang. */
const bMark = document.createElement("canvas");
bMark.id = "boardMark";
board.insertBefore(bMark, bInk.nextSibling);
const bMarkCtx = bMark.getContext("2d");
let bDpr = window.devicePixelRatio || 1;
function bResize(){
const w = board.clientWidth, h = board.clientHeight;
if(w<2 || h<2) return;
bDpr = window.devicePixelRatio || 1; /* vers: verhuizen naar ander scherm/zoom */
const keep = document.createElement("canvas");
keep.width = bInk.width; keep.height = bInk.height;
if(bInk.width) keep.getContext("2d").drawImage(bInk,0,0);
bInk.width = Math.round(w*bDpr); bInk.height = Math.round(h*bDpr);
if(keep.width) bCtx.drawImage(keep,0,0);
bCtx.lineCap = "round"; bCtx.lineJoin = "round";
bMark.width = bInk.width; bMark.height = bInk.height;
bMarkCtx.lineCap = "round"; bMarkCtx.lineJoin = "round";
}
new ResizeObserver(bResize).observe(board);
bResize();
@ -555,11 +566,25 @@ function makeMovable(t){
const r = t.getBoundingClientRect();
const cx = r.left+r.width/2, cy = r.top+r.height/2;
const start = Math.atan2(e.clientY-cy, e.clientX-cx)*180/Math.PI - ang;
/* klikt vast op veelvouden van 45° (waterpas/haaks in één beweging)
en toont tijdens het draaien de hoek in graden */
const badge = t.querySelector(".dtool-deg") || t.appendChild(Object.assign(document.createElement("div"), { className:"dtool-deg" }));
badge.style.display = "block";
const mv = ev=>{
ang = Math.atan2(ev.clientY-cy, ev.clientX-cx)*180/Math.PI - start;
let a = Math.atan2(ev.clientY-cy, ev.clientX-cx)*180/Math.PI - start;
const near = Math.round(a/45)*45;
if(Math.abs(a-near) < 4) a = near;
ang = a;
t.style.transform = `rotate(${ang}deg)`;
const shown = Math.round(((ang % 360) + 360) % 360);
badge.textContent = shown + "°";
badge.style.transform = `rotate(${-ang}deg)`; /* leesbaar houden */
badge.classList.toggle("snapped", Math.abs(((ang % 45) + 45) % 45) < 0.01);
};
const up = ()=>{
badge.style.display = "none";
document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up);
};
const up = ()=>{ document.removeEventListener("pointermove",mv); document.removeEventListener("pointerup",up); };
document.addEventListener("pointermove",mv); document.addEventListener("pointerup",up);
return;
}
@ -582,17 +607,21 @@ function toggleDTool(kind){
const t = document.createElement("div");
t.className = "dtool dtool-"+kind;
if(kind==="ruler"){
t._len = 600; t._hc = false;
/* 50px = 1 cm; logische lengtes in hele centimeters, schaal begint bij 0
met een kleine kantlijn zoals op een echte liniaal */
const RULER_CMS = [5, 10, 15, 20];
t._cm = 10; t._hc = false;
const drawRuler = ()=>{
const L = t._len, hc = t._hc;
const cmLen = Math.min(t._cm, Math.max(5, Math.floor((board.clientWidth-60)/50)));
const pad = 18, scale = cmLen*50, L = scale + pad*2, hc = t._hc;
const ink = hc ? "#000" : "#5a4a20";
const bg = hc ? "rgba(255,214,0,.95)" : "rgba(255,243,205,.82)";
let ticks = "";
for(let i=5; i<L; i+=5){
for(let i=0; i<=scale; i+=5){
const cm = i%50===0, half = i%25===0;
const len = cm ? 22 : half ? 14 : 8;
ticks += `<line x1="${i}" y1="2" x2="${i}" y2="${2+len}" stroke="${ink}" stroke-width="${cm ? (hc?2.5:1.6) : (hc?1.6:1)}"/>`;
if(cm) ticks += `<text x="${i}" y="${hc?46:42}" font-size="${hc?18:14}" font-weight="bold" fill="${ink}" text-anchor="middle">${i/50}</text>`;
ticks += `<line x1="${pad+i}" y1="2" x2="${pad+i}" y2="${2+len}" stroke="${ink}" stroke-width="${cm ? (hc?2.5:1.6) : (hc?1.6:1)}"/>`;
if(cm) ticks += `<text x="${pad+i}" y="${hc?46:42}" font-size="${hc?18:14}" font-weight="bold" fill="${ink}" text-anchor="middle">${i/50}</text>`;
}
t.style.width = L+"px";
t.querySelector(".dtool-svg").innerHTML = `<svg viewBox="0 0 ${L} 76" width="${L}" height="76">
@ -602,18 +631,23 @@ function toggleDTool(kind){
<rect x="0" y="0" width="${L}" height="76" rx="9" fill="${bg}" stroke="${ink}" stroke-width="${hc?3:1.5}"/>
<rect x="0" y="0" width="${L}" height="76" rx="9" fill="url(#rg)"/>
<line x1="0" y1="2" x2="${L}" y2="2" stroke="${ink}" stroke-width="${hc?3:2}"/>
<text x="${L/2}" y="66" font-size="12" fill="${ink}" text-anchor="middle" opacity=".65">teach · cm</text>
<text x="${L/2}" y="68" font-size="12" fill="${ink}" text-anchor="middle" opacity=".65">${cmLen} cm</text>
${ticks}</svg>`;
const lenBtn = t.querySelector(".do-len");
lenBtn.textContent = cmLen + " cm";
lenBtn.title = T("dtLen");
};
t.innerHTML = `<div class="dtool-svg"></div>
<div class="dtool-opts">
<button class="do-len" title="↔"></button>
<button class="do-hc" title="◐"></button>
<button class="do-len do-wide"></button>
<button class="do-hc"></button>
</div>`;
t.querySelector(".do-len").addEventListener("click", ()=>{
t._len = t._len===600 ? 800 : t._len===800 ? 400 : 600; drawRuler();
t._cm = RULER_CMS[(RULER_CMS.indexOf(t._cm)+1) % RULER_CMS.length]; drawRuler();
});
t.querySelector(".do-hc").addEventListener("click", ()=>{ t._hc = !t._hc; drawRuler(); });
const hcBtn = t.querySelector(".do-hc");
hcBtn.title = T("dtContrast");
hcBtn.addEventListener("click", ()=>{ t._hc = !t._hc; drawRuler(); });
t.style.height = "76px";
drawRuler();
}else if(kind==="geo"){
@ -663,10 +697,14 @@ function toggleDTool(kind){
<button class="do-len"></button>
<button class="do-hc"></button>
</div>`;
t.querySelector(".do-len").addEventListener("click", ()=>{
const geoLen = t.querySelector(".do-len");
geoLen.title = T("dtLen");
geoLen.addEventListener("click", ()=>{
t._sz = t._sz===340 ? 440 : t._sz===440 ? 280 : 340; drawGeo();
});
t.querySelector(".do-hc").addEventListener("click", ()=>{ t._hc = !t._hc; drawGeo(); });
const geoHc = t.querySelector(".do-hc");
geoHc.title = T("dtContrast");
geoHc.addEventListener("click", ()=>{ t._hc = !t._hc; drawGeo(); });
drawGeo();
}else if(kind==="light"){ /* compact traffic light directly on the board */
let on = "green";
@ -794,7 +832,7 @@ function startBoardText(e){
inp.addEventListener("blur", commit);
}
let bDraw = false, bx = 0, by = 0;
let bDraw = false, bx = 0, by = 0, bMidX = null, bMidY = null, bUsedMark = false;
const bPos = ev=>{
const r = bInk.getBoundingClientRect();
/* ratio-based: correct under any display zoom */
@ -808,6 +846,7 @@ bInk.addEventListener("pointerdown", e=>{
bDraw = true;
PEN.nib = PEN.size*0.8;
[bx,by] = bPos(e);
bMidX = bMidY = null;
bStroke(bx+0.01, by+0.01, e.pressure, e.pointerType);
});
bInk.addEventListener("pointermove", e=>{
@ -817,7 +856,20 @@ bInk.addEventListener("pointermove", e=>{
});
});
const bStop = ()=>{
if(bDraw) markBoardChange();
if(bDraw){
/* markeerstift: de haal staat dekkend op de overlay - nu in één keer
met vaste transparantie op de inktlaag zetten (egale kleur, geen
donkere overgangen) en de overlay leegmaken */
if(bUsedMark && bMark.width){
bCtx.globalCompositeOperation = "source-over";
bCtx.globalAlpha = 0.32;
bCtx.drawImage(bMark, 0, 0);
bCtx.globalAlpha = 1;
bMarkCtx.clearRect(0, 0, bMark.width, bMark.height);
}
bUsedMark = false;
markBoardChange();
}
bDraw = false;
bCtx.globalAlpha = 1;
if(DTOOLS.lens) DTOOLS.lens.lensDraw();
@ -825,12 +877,15 @@ const bStop = ()=>{
bInk.addEventListener("pointerup", bStop);
bInk.addEventListener("pointercancel", bStop);
function bStroke(x,y,pressure,ptype){
bCtx.globalCompositeOperation = PEN.tool==="erase" ? "destination-out" : "source-over";
bCtx.globalAlpha = PEN.tool==="mark" ? 0.32 : 1;
bCtx.strokeStyle = PEN.color;
const mark = PEN.tool==="mark";
if(mark) bUsedMark = true;
const ctx = mark ? bMarkCtx : bCtx; /* markeerstift op de eigen laag */
ctx.globalCompositeOperation = PEN.tool==="erase" ? "destination-out" : "source-over";
ctx.globalAlpha = 1;
ctx.strokeStyle = PEN.color;
let w = PEN.size;
if(PEN.tool==="erase") w = PEN.size*6;
else if(PEN.tool==="mark") w = PEN.size*3.5;
else if(mark) w = PEN.size*3.5;
else if(PEN.tool==="vulp"){
let target;
if(ptype==="pen" && pressure > 0){
@ -842,8 +897,16 @@ function bStroke(x,y,pressure,ptype){
PEN.nib += (target - PEN.nib)*0.3;
w = PEN.nib;
}
bCtx.lineWidth = w*bDpr;
bCtx.beginPath(); bCtx.moveTo(bx,by); bCtx.lineTo(x,y); bCtx.stroke();
ctx.lineWidth = w*bDpr;
/* vloeiende haal: teken van middelpunt naar middelpunt met het vorige
punt als stuurpunt (kwadratische curve) - geen hoekige knikken meer
tussen de losse pointer-stapjes, ook bij snelle halen */
const mx = (bx + x) / 2, my = (by + y) / 2;
ctx.beginPath();
ctx.moveTo(bMidX ?? bx, bMidY ?? by);
ctx.quadraticCurveTo(bx, by, mx, my);
ctx.stroke();
bMidX = mx; bMidY = my;
bx = x; by = y;
}
function boardInkURL(){ try{ return bInk.toDataURL("image/png"); }catch(e){ return null; } }

View file

@ -2,7 +2,7 @@
"use strict";
/* version shown until /api/version resolves (or if the fetch fails, e.g. offline).
Kept in sync by hand with the VERSION file at the repo root on every release. */
const VERSION = "0.3.64-beta";
const VERSION = "0.3.65-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
@ -231,6 +231,7 @@ const I18N = {
parentUnlink:"Ontkoppel",
parentUnlinkWarn:"Verwijdert alleen de koppeling; het account van je kind blijft bestaan.",
parentProgress:"📈 Voortgang", parentProgressEmpty:"Nog geen voortgang.",
dtLen:"Lengte", dtContrast:"Hoog contrast",
pupilEmpty:"Je leerkracht heeft nog niets voor je klaargezet.",
pupilWatching:"meekijken",
amUsers:"Gebruikers", amClasses:"Klassen", amSchools:"Scholen", amOverarching:"Systeem",
@ -489,6 +490,7 @@ const I18N = {
parentUnlink:"Unlink",
parentUnlinkWarn:"Removes only the link; your child's account remains.",
parentProgress:"📈 Progress", parentProgressEmpty:"No progress yet.",
dtLen:"Length", dtContrast:"High contrast",
pupilEmpty:"Your teacher hasn't set up anything for you yet.",
pupilWatching:"watching",
amUsers:"Users", amClasses:"Classes", amSchools:"Schools", amOverarching:"System",