clone

a photo toolkit for your phone that never uploads: fuji film looks for raw files, social crops, error level analysis

main
gudpics / perspective.html
perspective.html 9.2K
1<!doctype html>2<html lang="en">3<head>4 <meta charset="utf-8">5 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, interactive-widget=resizes-content">6 <script src="./viewport.js"></script>7 <meta name="theme-color" content="#1a1a1a">8 <meta name="description" content="Straighten leaning buildings and walls. Runs on your device.">9 <meta name="apple-mobile-web-app-capable" content="yes">10 <meta name="mobile-web-app-capable" content="yes">11 <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">12 <meta name="apple-mobile-web-app-title" content="gud.pics">13 <link rel="manifest" href="./manifest.webmanifest">14 <link rel="icon" href="./assets/icon.svg" type="image/svg+xml">15 <link rel="apple-touch-icon" href="./assets/icon-192.png">16 <link rel="stylesheet" href="./style.css">17 <title>Perspective</title>18</head>19<body class="tool-body">20 <div class="tool" id="tool" data-state="empty">21 <header class="nav">22 <a class="nav-btn" href="./" aria-label="Tools"><svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true"><path d="M15 5l-7 7 7 7" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></a>23 <h1 class="nav-title">Perspective</h1>24 <button class="nav-btn" id="save" aria-label="Save" disabled><svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 3v12m-5-5l5 5 5-5M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>25 </header>2627 <section class="stage" id="stage" aria-label="Photo">28 <div class="canvas-wrap"><canvas id="canvas" aria-label="Current photo"></canvas><canvas id="overlay" aria-hidden="true"></canvas></div>29 <button class="empty" id="open-empty" aria-label="Open photo">30 <svg width="72" height="72" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 5h6"/><path d="M19 2v6"/><path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/><circle cx="9" cy="9" r="2"/></svg>31 <svg class="plus" width="28" height="28" viewBox="0 0 28 28" aria-hidden="true"><path d="M14 5v18M5 14h18" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round"/></svg>32 </button>33 <div class="hud">34 <button class="chip" id="line-chip" aria-label="Detected lines"><span id="line-count"></span><svg width="6" height="10" viewBox="0 0 6 10" aria-hidden="true"><path d="M1 1l4 4-4 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></button>35 <button class="compare" id="compare" aria-pressed="false" aria-label="Show original" disabled><svg width="20" height="20" viewBox="0 0 20 20" aria-hidden="true"><path d="M10 2v16M7 5H3v10h4M13 5h4v10h-4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>36 </div>37 <div class="toast" id="status" role="status" aria-live="polite" hidden></div>38 <div class="busy" id="busy"><span class="spinner"></span></div>39 </section>4041 <section class="panel" aria-label="Controls">42 <button class="primary" id="detect" disabled><svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 4L3 20M10 4L9 20M14 4l1 16M19 4l2 16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round"/></svg>Find lines</button>43 <div class="segment" role="group" aria-label="Correction">44 <button data-mode="1" aria-pressed="false" aria-label="Vertical"><svg viewBox="0 0 28 28" aria-hidden="true"><path d="M10 5h8l4 18H6z"/><path d="M8 14h12"/></svg></button>45 <button data-mode="2" aria-pressed="false" aria-label="Horizontal"><svg viewBox="0 0 28 28" aria-hidden="true"><path d="M5 10v8l18 4V6z"/><path d="M14 8v12"/></svg></button>46 <button data-mode="3" aria-pressed="false" aria-label="Both"><svg viewBox="0 0 28 28" aria-hidden="true"><path d="M9 6L5 23l18-4L19 5z"/><path d="M7 15l15-3M14 5.5v15"/></svg></button>47 </div>4849 <div class="slider" id="strength-row"><label for="strength">Strength</label><output id="strength-value" for="strength">100%</output><input id="strength" type="range" min="0" max="100" value="100" disabled></div>50 <div class="slider centered" id="rotation-row"><label for="rotation" aria-label="Rotation"><svg width="20" height="20" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.3-5.7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M20 4v5h-5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></label><output id="rotation-value" for="rotation">0.00°</output><input id="rotation" type="range" min="-10" max="10" value="0" step="0.05" disabled></div>5152 <div class="toolbar">53 <button class="tb" id="open" aria-label="Open"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><circle cx="8.5" cy="10" r="1.8" fill="currentColor"/><path d="M4 17l5-5 4 4 2.5-2.5L20 18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>54 <button class="tb" id="lines" aria-pressed="true" disabled aria-label="Lines"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6 4L4 20M11 4l-1 16M14 4l1 16M18 4l2 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></button>55 <button class="tb" id="guides" aria-pressed="false" disabled aria-label="Grid"><svg viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="3.5" width="17" height="17" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M9.2 3.5v17M14.8 3.5v17M3.5 9.2h17M3.5 14.8h17" stroke="currentColor" stroke-width="1.4"/></svg></button>56 <button class="tb" id="crop" disabled aria-label="Crop"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 2v15h15M2 7h15v15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>57 <button class="tb" id="reset" disabled aria-label="Reset"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12a8 8 0 1 0 2.3-5.7" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/><path d="M4 4v5h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg></button>58 </div>59 </section>60 <input id="file" type="file" accept="image/jpeg,image/png" hidden>61 </div>6263 <dialog id="crop-sheet" class="sheet" aria-label="Crop">64 <div class="sheet-body actions" tabindex="-1" autofocus>65 <div class="grabber"></div>66 <div class="rows" role="radiogroup">67 <button class="row" role="radio" data-crop="largest" aria-checked="true">Largest area<svg class="check" width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M1.5 6.5l4 4L14.5 1.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>68 <button class="row" role="radio" data-crop="original" aria-checked="false">Original ratio<svg class="check" width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M1.5 6.5l4 4L14.5 1.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>69 <button class="row" role="radio" data-crop="none" aria-checked="false">All edges<svg class="check" width="16" height="12" viewBox="0 0 16 12" aria-hidden="true"><path d="M1.5 6.5l4 4L14.5 1.5" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"/></svg></button>70 </div>71 <button class="cancel" data-close>Cancel</button>72 </div>73 </dialog>7475 <dialog id="save-sheet" class="sheet" aria-label="Save">76 <div class="sheet-body actions" tabindex="-1" autofocus>77 <div class="grabber"></div>78 <div class="rows">79 <button class="row" data-format="jpeg">JPG</button>80 <button class="row" data-format="png">PNG</button>81 </div>82 <button class="cancel" data-close>Cancel</button>83 </div>84 </dialog>8586 <dialog id="lines-sheet" class="sheet" aria-labelledby="lines-title">87 <div class="sheet-body" tabindex="-1" autofocus>88 <div class="grabber"></div>89 <div class="sheet-head"><h2 id="lines-title">Lines</h2><button class="sheet-close" data-close>Done</button></div>90 <div class="counts" id="debug-counts"></div>91 <div class="legend"><span><i class="lv"></i>Vertical, used</span><span><i class="lh"></i>Horizontal, used</span><span><i class="lr"></i>Vertical, rejected</span><span><i class="ly"></i>Horizontal, rejected</span><span><i class="lg"></i>Other edges</span></div>92 <label class="switch-row"><span>Show lines</span><input id="show-lines" class="switch" type="checkbox" checked></label>93 <label class="switch-row"><span>Show rejected</span><input id="show-rejected" class="switch" type="checkbox" checked></label>94 </div>95 </dialog>9697 <script type="module" src="./app.mjs"></script>98</body>99</html>