screengraft 0.64.0 → 0.64.1
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 +22 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "screengraft",
|
|
3
|
-
"version": "0.64.
|
|
3
|
+
"version": "0.64.1",
|
|
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
|
@@ -1747,7 +1747,10 @@ const GUIDE_DASH = [6, 5];
|
|
|
1747
1747
|
function cased(c, path, coreW, coreColor, dash){
|
|
1748
1748
|
c.save();
|
|
1749
1749
|
if (dash) c.setLineDash(dash);
|
|
1750
|
-
|
|
1750
|
+
// Butt caps on a dashed stroke: a round cap grows every dash by the line
|
|
1751
|
+
// width at both ends, so the 6/5 guide read as ~9/2 under its 2.9px casing
|
|
1752
|
+
// while the gizmo (SVG, butt caps) showed a true 6/5 (14 Sep 2026).
|
|
1753
|
+
c.lineCap = dash ? 'butt' : 'round'; c.lineJoin = 'round';
|
|
1751
1754
|
c.strokeStyle = CASE_A; c.lineWidth = coreW + CASE_PAD; path(); c.stroke();
|
|
1752
1755
|
c.strokeStyle = coreColor; c.lineWidth = coreW; path(); c.stroke();
|
|
1753
1756
|
c.restore();
|
|
@@ -2148,6 +2151,7 @@ $('#figmaBtn').onclick = async () => {
|
|
|
2148
2151
|
with the two UNCHANGED adjacent lines. Corner handles stay for edges that
|
|
2149
2152
|
run off-frame or sit behind an occluder. */
|
|
2150
2153
|
const cv = $('#c'), ctx = cv.getContext('2d');
|
|
2154
|
+
let cvW = 0, cvH = 0, cvDpr = 1; // the canvas's CSS size and the device-pixel factor of its store
|
|
2151
2155
|
const strip = $('#strip'), sctx = strip.getContext('2d');
|
|
2152
2156
|
const stripF = $('#stripF'), fctx = stripF.getContext('2d');
|
|
2153
2157
|
const img = new Image();
|
|
@@ -2223,8 +2227,17 @@ function computeFit(){
|
|
|
2223
2227
|
function setCanvasZoom(z, focus){
|
|
2224
2228
|
const prev = scale;
|
|
2225
2229
|
scale = Math.max(0.05, Math.min(4, z));
|
|
2226
|
-
|
|
2227
|
-
|
|
2230
|
+
// The backing store is sized in DEVICE pixels and the element in CSS pixels,
|
|
2231
|
+
// so on a Retina display the photo and the overlay are drawn at 2x and the
|
|
2232
|
+
// overlay's 1px lines are as sharp as the SVG gizmo's in the result pane.
|
|
2233
|
+
// Until 14 Sep 2026 the store was CSS-sized and the browser upsampled it --
|
|
2234
|
+
// every line in the fit pane was softer than the same line one pane over.
|
|
2235
|
+
// draw() undoes the factor with setTransform, so everything below still
|
|
2236
|
+
// thinks in CSS px: cvW/cvH are the CSS size, cv.width/height the store.
|
|
2237
|
+
cvW = Math.round(img.naturalWidth * scale); cvH = Math.round(img.naturalHeight * scale);
|
|
2238
|
+
cvDpr = Math.max(1, window.devicePixelRatio || 1);
|
|
2239
|
+
cv.width = Math.round(cvW * cvDpr); cv.height = Math.round(cvH * cvDpr);
|
|
2240
|
+
cv.style.width = cvW + 'px'; cv.style.height = cvH + 'px';
|
|
2228
2241
|
$('#czSt').textContent = Math.round(scale*100) + '%';
|
|
2229
2242
|
draw();
|
|
2230
2243
|
const sc = $('#scroller');
|
|
@@ -2264,8 +2277,8 @@ function zoomAt(z, clientX, clientY){
|
|
|
2264
2277
|
const before = pos({clientX, clientY});
|
|
2265
2278
|
setCanvasZoom(z);
|
|
2266
2279
|
const sc = $('#scroller'), r = cv.getBoundingClientRect();
|
|
2267
|
-
sc.scrollLeft += r.left + before[0] * scale * (r.width /
|
|
2268
|
-
sc.scrollTop += r.top + before[1] * scale * (r.height /
|
|
2280
|
+
sc.scrollLeft += r.left + before[0] * scale * (r.width / cvW) - clientX;
|
|
2281
|
+
sc.scrollTop += r.top + before[1] * scale * (r.height / cvH) - clientY;
|
|
2269
2282
|
}
|
|
2270
2283
|
|
|
2271
2284
|
/* A trackpad pinch arrives as a wheel event with ctrlKey synthesised true, and
|
|
@@ -2661,9 +2674,10 @@ function draw(){
|
|
|
2661
2674
|
// fit, so leaving it on the old corners would make the two panes disagree
|
|
2662
2675
|
// mid-drag — which is the one thing the side-by-side exists to prevent.
|
|
2663
2676
|
if (!$('#liveWrap').hidden) placeLiveVideo();
|
|
2664
|
-
ctx.
|
|
2677
|
+
ctx.setTransform(cvDpr, 0, 0, cvDpr, 0, 0); // CSS px from here on; the store is cvDpr times larger
|
|
2678
|
+
ctx.clearRect(0,0,cvW,cvH);
|
|
2665
2679
|
if (!img.naturalWidth) return;
|
|
2666
|
-
ctx.drawImage(img, 0,0,
|
|
2680
|
+
ctx.drawImage(img, 0,0, cvW, cvH);
|
|
2667
2681
|
if (!st.corners) return;
|
|
2668
2682
|
const P = st.corners.map(([x,y]) => [x*scale, y*scale]);
|
|
2669
2683
|
const active = drag || hover || pick;
|
|
@@ -2742,7 +2756,7 @@ function draw(){
|
|
|
2742
2756
|
|
|
2743
2757
|
function pos(e){
|
|
2744
2758
|
const r = cv.getBoundingClientRect();
|
|
2745
|
-
return [(e.clientX-r.left)*
|
|
2759
|
+
return [(e.clientX-r.left)*cvW/r.width/scale, (e.clientY-r.top)*cvH/r.height/scale];
|
|
2746
2760
|
}
|
|
2747
2761
|
function hitTest(p){
|
|
2748
2762
|
if (!st.corners) return null;
|