screengraft 0.54.6 → 0.54.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/ui/index.html +33 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.7",
|
|
4
4
|
"description": "Put a UI screenshot or screen recording onto a photographed device screen with the perspective exactly right \u2014 a homography you confirm by hand, not a generative guess.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mockup",
|
package/ui/index.html
CHANGED
|
@@ -643,11 +643,11 @@
|
|
|
643
643
|
/* Dash lengths are set inline in photo px scaled by the zoom (SVG dashes
|
|
644
644
|
are viewBox units; non-scaling-stroke does not cover them), so they read
|
|
645
645
|
as 6/5 on screen at any zoom -- the fit overlay's GUIDE_DASH. */
|
|
646
|
-
#dofGizmo .h{fill
|
|
646
|
+
#dofGizmo .h{fill:none;stroke:#f5623d;stroke-width:2;vector-effect:non-scaling-stroke;pointer-events:auto;cursor:move}
|
|
647
|
+
#dofGizmo .hcase{fill:none;stroke:rgba(0,0,0,.35);stroke-width:4;vector-effect:non-scaling-stroke;pointer-events:none}
|
|
647
648
|
#dofGizmo .h.pip{cursor:url("data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22' viewBox='0 0 22 22'%3E%3Cpath d='M17.5 11a6.5 6.5 0 1 1-2.2-4.9' fill='none' stroke='rgba(0,0,0,.85)' stroke-width='4.2' stroke-linecap='round'/%3E%3Cpath d='M17.5 11a6.5 6.5 0 1 1-2.2-4.9' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round'/%3E%3Cpath d='M15.6 2.6l0.4 4.2-4.2 0.3' fill='none' stroke='rgba(0,0,0,.85)' stroke-width='4.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Cpath d='M15.6 2.6l0.4 4.2-4.2 0.3' fill='none' stroke='%23fff' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") 11 11, crosshair}
|
|
648
649
|
#dofGizmo .h.thumb{cursor:ew-resize}
|
|
649
|
-
#dofGizmo .h.
|
|
650
|
-
#dofGizmo .h.pip{fill:#f5623d}
|
|
650
|
+
#dofGizmo .h.pip{fill:#f5623d;stroke:#fff;stroke-width:1.2}
|
|
651
651
|
#dofGizmo .h.thumb{fill:#fff;stroke:#f5623d;stroke-width:2}
|
|
652
652
|
#dofGizmo .h:active{cursor:grabbing}
|
|
653
653
|
#dofGizmo text{font:600 11px system-ui,-apple-system,sans-serif;fill:#fff;paint-order:stroke;stroke:rgba(0,0,0,.75);stroke-width:3px;stroke-linejoin:round;pointer-events:none}
|
|
@@ -1508,6 +1508,17 @@ function cased(c, path, coreW, coreColor, dash){
|
|
|
1508
1508
|
/* A FILLED handle, still cased. cased() strokes twice; a disc has to fill the
|
|
1509
1509
|
core and put the dark casing around its rim, or the handle loses the
|
|
1510
1510
|
readability guarantee that makes markers survive an arbitrary photograph. */
|
|
1511
|
+
/* A HOLLOW handle: 8px circle, 2px stroke, nothing inside, so the pixel
|
|
1512
|
+
under the handle stays visible; the casing sits outside the ring so the
|
|
1513
|
+
ring itself reads on any photograph (13 Sep 2026, on request). */
|
|
1514
|
+
function casedRing(c, x, y, r, coreColor){
|
|
1515
|
+
c.save();
|
|
1516
|
+
c.strokeStyle = CASE_A; c.lineWidth = 2 + CASE_PAD;
|
|
1517
|
+
c.beginPath(); c.arc(x, y, r, 0, Math.PI*2); c.stroke();
|
|
1518
|
+
c.strokeStyle = coreColor; c.lineWidth = 2;
|
|
1519
|
+
c.beginPath(); c.arc(x, y, r, 0, Math.PI*2); c.stroke();
|
|
1520
|
+
c.restore();
|
|
1521
|
+
}
|
|
1511
1522
|
function casedDisc(c, x, y, r, coreColor){
|
|
1512
1523
|
c.save();
|
|
1513
1524
|
c.strokeStyle = CASE_A; c.lineWidth = 2.0;
|
|
@@ -2299,7 +2310,12 @@ function draw(){
|
|
|
2299
2310
|
// hollow; the pivot pips went 2.5 -> 3.5 and are filled too, staying visibly
|
|
2300
2311
|
// smaller than a corner so the two never read as the same control. The gap the
|
|
2301
2312
|
// edge leaves for each corner grows with it, or the disc sits on the line.
|
|
2302
|
-
|
|
2313
|
+
// Corner handle: an 8px ring (r to the stroke centre = 4) with a 2px stroke
|
|
2314
|
+
// and the casing outside it, so its visible outer edge is at r + 1 + CASE_PAD/2;
|
|
2315
|
+
// the edge breaks 1px short of that, which is the gap that reads as
|
|
2316
|
+
// "handle, then line" rather than a bead on a wire.
|
|
2317
|
+
const HANDLE_R = 4, PIVOT_R = 3.5;
|
|
2318
|
+
const HANDLE_GAP = HANDLE_R + 1 + CASE_PAD / 2 + 1;
|
|
2303
2319
|
|
|
2304
2320
|
// Guide: the active edge extended to infinity, so you can see the line it
|
|
2305
2321
|
// is really on. Thinner and dashed — it is a reference, not a handle.
|
|
@@ -2345,7 +2361,7 @@ function draw(){
|
|
|
2345
2361
|
ctx.font='600 11px system-ui'; ctx.textBaseline='alphabetic';
|
|
2346
2362
|
P.forEach(([x,y],i) => {
|
|
2347
2363
|
const on = active && active.kind==='corner' && active.i===i;
|
|
2348
|
-
|
|
2364
|
+
casedRing(ctx, x, y, on ? HANDLE_R + 1 : HANDLE_R, CORE_QUAD);
|
|
2349
2365
|
casedText(ctx, LBL[i], x+13, y-11, 'rgba(255,255,255,.95)');
|
|
2350
2366
|
});
|
|
2351
2367
|
}
|
|
@@ -2676,6 +2692,7 @@ $('#dofMeasure').onclick = measureDof;
|
|
|
2676
2692
|
the SVG's viewBox is the photo so it stays aligned at every zoom for free. */
|
|
2677
2693
|
const gz = $('#dofGizmo');
|
|
2678
2694
|
const SIGMA_FULL_FRAC = 0.02; // mirrors dof.DOF_MAX_FRAC -- keep in step
|
|
2695
|
+
const HANDLE_BREAK = 4 + 1 + 2 + 1; // ring radius + half its stroke + casing + the 1px gap, screen px
|
|
2679
2696
|
let dofEnd = parseFloat(recall('dofEnd','1')); if (!(dofEnd > 0)) dofEnd = 1;
|
|
2680
2697
|
// Where along the lines the handles sit (0..1 across the screen's extent).
|
|
2681
2698
|
// Page-only: it changes nothing in the render. Turning re-derives it so the
|
|
@@ -2727,7 +2744,8 @@ function paintGizmo(){
|
|
|
2727
2744
|
const c0 = two ? P(dofEnd2, -0.06) : null, c1 = two ? P(dofEnd2, 1.06) : null, cc = two ? P(dofEnd2, dofK) : null;
|
|
2728
2745
|
const th = P(dofEnd, 0.08 + 0.84 * dofStr); // the strength thumb slides along the dashed line
|
|
2729
2746
|
const sigma = dofStr * SIGMA_FULL_FRAC * g.side;
|
|
2730
|
-
const r =
|
|
2747
|
+
const r = 4 * k, rp = 3.5 * k, fs11 = 11 * k, off = 14 * k;
|
|
2748
|
+
const ring = (p, h, cls='') => `<circle cx="${p[0]}" cy="${p[1]}" r="${r}" class="hcase"/><circle cx="${p[0]}" cy="${p[1]}" r="${r}" class="h ${cls}" data-h="${h}"/>`;
|
|
2731
2749
|
const L = (x, y, cls, extra='') => `<line x1="${x[0]}" y1="${x[1]}" x2="${y[0]}" y2="${y[1]}" class="${cls}" ${extra}/>`;
|
|
2732
2750
|
const T = (p, txt, dy) => `<text x="${p[0]}" y="${p[1] + dy}" text-anchor="middle" font-size="${fs11}">${txt}</text>`;
|
|
2733
2751
|
// GUIDE_DASH in screen px, expressed in photo px; a line past the screen gets a sparser dash
|
|
@@ -2735,15 +2753,18 @@ function paintGizmo(){
|
|
|
2735
2753
|
const away = unit(sub(bc, ac)); // label offsets: away from the other line
|
|
2736
2754
|
const beyond = dofEnd > 1.0001;
|
|
2737
2755
|
const beyond2 = two && dofEnd2 < -0.0001;
|
|
2756
|
+
// Each line breaks around its ring handle: the ring is hollow, so a line
|
|
2757
|
+
// running through it would show inside. Break = ring outer edge + 1px.
|
|
2758
|
+
const brk = (HANDLE_BREAK) * k;
|
|
2759
|
+
const split = (p0, p1, h) => { const v = unit(sub(p1, p0)); return [[p0, sub(h, mul(v, brk))], [add(h, mul(v, brk)), p1]]; };
|
|
2760
|
+
const LL = (p0, p1, h, cls, extra='') => split(p0, p1, h).map(([x, y]) => L(x, y, cls, extra)).join('');
|
|
2738
2761
|
gz.innerHTML =
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
(two ?
|
|
2762
|
+
LL(a0, a1, ac, 'case', 'stroke-width="3"') + LL(a0, a1, ac, 'focus') +
|
|
2763
|
+
LL(b0, b1, bc, 'case', `stroke-width="2.9" ${dash(beyond)}`) + LL(b0, b1, bc, 'far' + (beyond ? ' out' : ''), dash(beyond)) +
|
|
2764
|
+
(two ? LL(c0, c1, cc, 'case', `stroke-width="2.9" ${dash(beyond2)}`) + LL(c0, c1, cc, 'far' + (beyond2 ? ' out' : ''), dash(beyond2)) : '') +
|
|
2742
2765
|
`<circle cx="${a0[0]}" cy="${a0[1]}" r="${rp}" class="h pip" data-h="rotA"/>` +
|
|
2743
2766
|
`<circle cx="${a1[0]}" cy="${a1[1]}" r="${rp}" class="h pip" data-h="rotB"/>` +
|
|
2744
|
-
|
|
2745
|
-
`<circle cx="${bc[0]}" cy="${bc[1]}" r="${r}" class="h hollow" data-h="end"/>` +
|
|
2746
|
-
(two ? `<circle cx="${cc[0]}" cy="${cc[1]}" r="${r}" class="h hollow" data-h="end2"/>` : '') +
|
|
2767
|
+
ring(ac, 'start') + ring(bc, 'end') + (two ? ring(cc, 'end2') : '') +
|
|
2747
2768
|
`<rect x="${th[0]-r}" y="${th[1]-r}" width="${2*r}" height="${2*r}" transform="rotate(45 ${th[0]} ${th[1]})" class="h thumb" data-h="str"/>` +
|
|
2748
2769
|
T([ac[0] - away[0]*off*(two ? 0 : 1), ac[1] - away[1]*off*(two ? 0 : 1) + (two ? -off : 0)], 'sharp', 4*k) +
|
|
2749
2770
|
T([bc[0] + away[0]*off, bc[1] + away[1]*off], `σ ${sigma.toFixed(0)}px from here${beyond ? ' · past the screen' : ''}`, 4*k) +
|