├── .gitignore ├── img ├── install.png ├── tampermonkey.png ├── normal_overlay.png ├── advanced_overlay.png ├── script-uebersicht.png ├── tapermonkey-marker.png ├── advanced_overlay_big.png └── advanced_overlay_sm.png ├── src └── scripts │ ├── placeDE-overlay.user.js │ └── advanced_overlay.user.js └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /img/install.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/install.png -------------------------------------------------------------------------------- /img/tampermonkey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/tampermonkey.png -------------------------------------------------------------------------------- /img/normal_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/normal_overlay.png -------------------------------------------------------------------------------- /img/advanced_overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/advanced_overlay.png -------------------------------------------------------------------------------- /img/script-uebersicht.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/script-uebersicht.png -------------------------------------------------------------------------------- /img/tapermonkey-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/tapermonkey-marker.png -------------------------------------------------------------------------------- /img/advanced_overlay_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/advanced_overlay_big.png -------------------------------------------------------------------------------- /img/advanced_overlay_sm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PlaceDE-Official/place-overlay/HEAD/img/advanced_overlay_sm.png -------------------------------------------------------------------------------- /src/scripts/placeDE-overlay.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name r/placeDE Template 3 | // @namespace http://tampermonkey.net/ 4 | // @version 8.7 5 | // @description try to take over the canvas! 6 | // @author placeDE Devs 7 | // @match https://garlic-bread.reddit.com/embed* 8 | // @icon https://www.google.com/s2/favicons?sz=64&domain=reddit.com 9 | // @updateURL https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/placeDE-overlay.user.js 10 | // @downloadURL https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/placeDE-overlay.user.js 11 | // @run-at document-start 12 | // ==/UserScript== 13 | 14 | const updateEvery = 30 * 1000; 15 | const src = "https://place.army/overlay_target.png"; 16 | const style = 17 | "position: absolute;left: 0;top: 0;image-rendering: pixelated;width: 1000px;height: 1000px;"; 18 | 19 | let overlayImage = null; 20 | if (window.top !== window.self) { 21 | window.addEventListener( 22 | "load", 23 | () => { 24 | const canvasContainer = document 25 | .querySelector("garlic-bread-embed") 26 | .shadowRoot.querySelector("garlic-bread-canvas") 27 | .shadowRoot.querySelector(".container"); 28 | const canvas = canvasContainer.querySelector("canvas"); 29 | 30 | overlayImage = document.createElement("img"); 31 | overlayImage.style = style; 32 | 33 | const updateImage = () => (overlayImage.src = src + "?" + Date.now()); 34 | 35 | updateImage(); 36 | setInterval(updateImage, updateEvery); 37 | canvasContainer.appendChild(overlayImage); 38 | 39 | const canvasObserver = new MutationObserver((mutations) => { 40 | mutations.forEach((mutation) => { 41 | if (mutation.type === "attributes") { 42 | overlayImage.style.width = 43 | mutation.target.getAttribute("width") + "px"; 44 | overlayImage.style.height = 45 | mutation.target.getAttribute("height") + "px"; 46 | } 47 | }); 48 | }); 49 | 50 | canvasObserver.observe(canvas, { 51 | attributes: true, 52 | }); 53 | }, 54 | false 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Overlay 2 | ### Was ist das Overlay? 3 | Das Overlay zeigt dir an, wo falsche Pixel sind, damit du richtige setzen kannst. Es setzt keine Pixel für dich. 4 | 5 | Direktlinks: 6 | [Installation](#overlay-installieren) 7 | [Updates](#overlay-updaten) 8 | [Wie funktioniert das Overlay?](#wie-funktioniert-das-overlay) 9 | [FAQ](#faq) 10 | ---- 11 | ## Overlay installieren 12 | 13 | 1. https://www.tampermonkey.net/ öffnen 14 | 15 | ![Website von Tampermonkey](imt/../img/tampermonkey.png) 16 | 17 | 2. Unter "Download" mit "Gehe zum Store" das Plugin installieren -> Weiterleitung zu den App-Stores dort installieren 18 | 19 | ![Download-Fenster](img/tapermonkey-marker.png) 20 | 21 | 3. Anschließend auf einen der folgenden Links klicken, um das jeweilige Overlay zu installieren, Tampermonkey sollte sich automatisch öffnen (wenn du dich nicht entscheiden kannst, findest du die Unterschiede [hier](#wie-funktioniert-das-overlay)): 22 | - [Normales Overlay](https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/placeDE-overlay.user.js) 23 | - [Erweitertes Overlay](https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/advanced_overlay.user.js) 24 | 25 | 4. Nun drückt ihr in Tampermonkey nur noch auf "Updaten" oder "Neu installieren". 26 | Das Ganze sieht dann in Tampermonkey (abhängig von der gewählten Variante) ungefähr so aus: 27 | 28 | ![Addonseite von Tampermonkey](img/script-uebersicht.png) 29 | 30 | ------ 31 | 32 | ## Overlay updaten 33 | Um das Overlay auf eine neue Version zu aktualisieren (nicht die Daten, sondern das Skript) klickt ihr einfach oben bei 3. auf den entsprechenden Link. 34 | 35 | -------- 36 | 37 | ## Wie funktioniert das Overlay? 38 | ### Normale Variante: 39 | Nach dem Installieren siehst du auf der r/place Leinwand kleinere "Pixel" innerhalb der tatsächlichen Pixel. Diese kleineren Pixel geben dir an, welche Farbe der Pixel haben sollte: 40 | 41 | ![Vorschau des normalen Overlays](img/normal_overlay.png) 42 | 43 | ### Erweiterte Variante: 44 | 45 | Der Kamera Button (1) erstellt ein Screenshot vom gesamten Canvas. 46 | 47 | Mit dem Knopf unten rechts (2) kann man zwischen mehreren Modi wechseln: 48 | 1. - Kleine Pixel (wie im normalen Overlay); 49 | - Dies ist der Standardmodus, welcher bei jedem Neuladen der Seite aktiv ist 50 | - ![Vorschau des erweiterten Overlays](img/advanced_overlay_sm.png) 51 | 52 | 2. - Volle Pixel, d.h. man sieht wie das Bild aussehen sollte und falsche Pixel werden vollständig überdeckt 53 | - Dieser Modus eignet sich sehr gut, wenn man das ganze Bild ohne Fehler sehen will 54 | - ![Vorschau des erweiterten Overlays](img/advanced_overlay_big.png) 55 | 56 | Welcher Modus aktiv ist, erkennt man am Icon und anhand vom Tooltip des Buttons (2). 57 | 58 | Außerdem gibt es unter dem Button einen Schieberegler (3). 59 | Dieser regelt, wie "durchscheinend" das Overlay sein soll. 60 | Auf der ganz unteren Position ist das Overlay komplett durchsichtig. 61 | 62 | ![Vorschau des erweiterten Overlays](img/advanced_overlay.png) 63 | 64 | ------------ 65 | 66 | ## FAQ 67 | 68 | ### Wieso ist die Flagge nicht im Overlay? 69 | Die Flagge ist relativ simpel und klar abgegrenzt. 70 | Somit kann jeder - auch Leute ohne Overlay - die Flagge reparieren. 71 | Wir nutzen die verfügbaren Pixel der Leute, die das Overlay installiert haben, lieber um die komplizierteren Artworks zu schützen, die man nicht so einfach ohne irgendwelche Anweisungen wie z.B. das Overlay reparieren kann. 72 | 73 | ### Wieso funktioniert das Overlay nicht? 74 | Falls das Overlay nicht funktioniert stelle bitte folgende Sachen sicher: 75 | - Lade die Seite einmal neu, eventuell wurde das Skript einfach nur nicht geladen. 76 | - Zoome einmal in das Canvas auf Höhe des r/placeDE-Bereichs herein, da es sein kann, dass das Overlay erst bei etwas Zoom deutlich sichtbar wird. 77 | - In den ersten Versionen kam es bei manchen Browsern in Kombination mit einem eingeschalteten Darkmode zu Komplikationen. Bitte update das Skript für das Overlay oder deaktiviere den Darkmode. 78 | - Falls du noch einen anderen Browser hast, probiere bitte einmal diesen Browser. 79 | - Falls alles nicht hilft, frage bitte im tech-support nach, vielleicht hatte dort jemand das gleiche Problem. 80 | 81 | ### Wieso aktualisiert sich mein Overlay nicht? 82 | Eventuell hast du noch die alte Overlay-Version, probiere einmal das Skript für das Overlay neu zu installieren. 83 | Danach sollte sich das Overlay automatisch alle 30 Sekunden updaten. 84 | Falls das nicht klappt, muss die Seite leider manuell neu geladen werden, um ein Update zu erhalten. 85 | Danach sollte es sich wieder selbst aktualisieren (eventuell sorgen aber auch andere Erweiterungen dafür, dass nur durch Neuladen der Seite neue Pixelarts angezeigt werden können). 86 | 87 | ### Wie kann ich das Overlay auf dem Handy nutzen? 88 | Ne, leider ist das nicht möglich und wird auch in Zukunft nicht möglich sein. 89 | 90 | ### Wieso ist der Pixel im Overlay falsch? 91 | Das liegt daran, dass die Vorlage einen Fehler enthält. 92 | Um das zu beheben, muss sich ein Designer an die Vorlage setzen und sie reparieren. 93 | 94 | ### Setzt das Overlay automatisch Pixel? 95 | Nein, das ist nicht der Zweck des Overlays. 96 | Das Overlay ist nur als Hilfe zum Pixeln gedacht. 97 | Bei Interesse findest du [hier](https://place.army/) weitere Informationen. 98 | -------------------------------------------------------------------------------- /src/scripts/advanced_overlay.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name r/place 2023 Canada Overlay with German tiles 3 | // @namespace http://tampermonkey.net/ 4 | // @version 0.13 5 | // @description Script that adds a button to toggle an hardcoded image shown in the 2023's r/place canvas 6 | // @author max-was-here and placeDE Devs 7 | // @match https://garlic-bread.reddit.com/embed* 8 | // @icon https://www.redditstatic.com/desktop2x/img/favicon/favicon-32x32.png 9 | // @updateURL https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/advanced_overlay.user.js 10 | // @downloadURL https://github.com/PlaceDE-Official/place-overlay/raw/main/src/scripts/advanced_overlay.user.js 11 | // @grant none 12 | // @run-at document-start 13 | // ==/UserScript== 14 | 15 | const AO_STYLE = ` 16 | .ao-hidden { 17 | display: none !important; 18 | } 19 | .ao-wrapper { 20 | position: absolute; 21 | bottom: 25px; 22 | right: 25px; 23 | display: flex; 24 | flex-direction: column; 25 | gap: 12px; 26 | align-items: flex-end; 27 | } 28 | .ao-button { 29 | display: flex; 30 | justify-content: center; 31 | align-items: center; 32 | height: 44px; 33 | width: 44px; 34 | background-color: #fff; 35 | color: #000; 36 | border: var(--pixel-border); 37 | box-shadow: var(--pixel-box-shadow); 38 | font-family: var(--garlic-bread-font-pixel); 39 | cursor: pointer; 40 | } 41 | .ao-button:hover { 42 | background: linear-gradient(rgba(0, 0, 0, 0.2) 0px, rgba(0, 0, 0, 0.2) 0px), rgb(255, 255, 255); 43 | } 44 | .ao-opacity-wrapper { 45 | position: relative; 46 | display: flex; 47 | align-items: center; 48 | justify-content: center; 49 | flex-direction: column; 50 | width: 44px; 51 | height: 132px; 52 | background-color: #fff; 53 | color: #000; 54 | border: var(--pixel-border); 55 | box-shadow: var(--pixel-box-shadow); 56 | font-family: var(--garlic-bread-font-pixel); 57 | white-space: nowrap; 58 | box-sizing: border-box; 59 | } 60 | .ao-opacity-wrapper::before, 61 | .ao-opacity-wrapper::after { 62 | display: block; 63 | position: absolute; 64 | z-index: 99; 65 | width: 100%; 66 | text-align: center; 67 | font-size: 1.5rem; 68 | line-height: 1; 69 | padding: 4px 0; 70 | pointer-events: none; 71 | mix-blend-mode: darken; 72 | } 73 | .ao-opacity-wrapper::before { 74 | content: "+"; 75 | top: -6px; 76 | } 77 | .ao-opacity-wrapper::after { 78 | content: "−"; 79 | bottom: -2px; 80 | } 81 | .ao-opacity-slider { 82 | -webkit-appearance: none; 83 | appearance: none; 84 | height: 0; 85 | width: 124px; 86 | transform: rotate(270deg); 87 | outline: none; 88 | opacity: 1; 89 | cursor: row-resize; 90 | } 91 | .ao-opacity-slider::-webkit-slider-thumb { 92 | -webkit-appearance: none; 93 | appearance: none; 94 | width: 24px; 95 | height: 36px; 96 | background: rgb(0, 163, 104); 97 | cursor: pointer; 98 | border-radius: 0; 99 | } 100 | .ao-opacity-slider::-moz-range-thumb { 101 | width: 24px; 102 | height: 36px; 103 | background: rgb(0, 163, 104); 104 | cursor: pointer; 105 | border-radius: 0; 106 | } 107 | `; 108 | 109 | let width = "2500px"; 110 | let height = "2000px"; 111 | let toggleSmallPixelButton; 112 | let toggleFullPixelButton; 113 | 114 | if (window.top !== window.self) { 115 | addEventListener('load', () => { 116 | // ============================================== 117 | const STORAGE_KEY = 'place-germany-2023-ostate'; 118 | const OVERLAYS = [ 119 | ["https://place.army/overlay_target.png", "kleine Pixel"], 120 | ["https://place.army/default_target.png", "große Pixel"] 121 | ]; 122 | const getConfig = (text) => { 123 | return text + "?" + Date.now() 124 | } 125 | 126 | let oState = { 127 | opacity: 100, 128 | overlayIdx: 0 129 | }; 130 | 131 | const oStateStorage = localStorage.getItem(STORAGE_KEY); 132 | if (oStateStorage !== null) { 133 | try { 134 | oState = Object.assign({}, oState, JSON.parse(oStateStorage)); 135 | } catch { } 136 | } 137 | 138 | 139 | 140 | const img = document.createElement('img'); 141 | img.style.pointerEvents = 'none'; 142 | img.style.position = 'absolute'; 143 | img.style.imageRendering = 'pixelated'; 144 | img.style.opacity = oState.opacity; 145 | img.style.top = '0px'; 146 | img.style.left = '0px'; 147 | img.style.zIndex = '100'; 148 | img.onload = () => { 149 | console.log('[PLACEDE] img loaded'); 150 | img.style.opacity = oState.opacity / 100; 151 | }; 152 | 153 | const updateImage = () => { 154 | img.src = getConfig(OVERLAYS[oState.overlayIdx][0]) 155 | console.log("[PLACEDE] updated overlay image") 156 | }; 157 | 158 | updateImage(); 159 | 160 | setInterval(updateImage, 30000); 161 | 162 | const mainContainer = document 163 | .querySelector('garlic-bread-embed') 164 | .shadowRoot.querySelector('.layout'); 165 | const positionContainer = mainContainer 166 | .querySelector('garlic-bread-canvas') 167 | .shadowRoot.querySelector('.container'); 168 | positionContainer.appendChild(img); 169 | 170 | // ============================================== 171 | // Canvas size observer 172 | 173 | const canvas = positionContainer.querySelector("canvas"); 174 | const canvasObserver = new MutationObserver((mutations) => { 175 | mutations.forEach((mutation) => { 176 | if (mutation.type === "attributes") { 177 | img.style.width = mutation.target.getAttribute("width") + "px"; 178 | img.style.height = mutation.target.getAttribute("height") + "px"; 179 | } 180 | }); 181 | }); 182 | 183 | canvasObserver.observe(canvas, { 184 | attributes: true 185 | }); 186 | 187 | // Add style to shadow root 188 | const styleContainer = document.createElement('style'); 189 | styleContainer.innerHTML = AO_STYLE; 190 | mainContainer.appendChild(styleContainer); 191 | 192 | // ============================================== 193 | // Add buttons to toggle overlay 194 | 195 | const buttonsWrapper = document.createElement('div'); 196 | buttonsWrapper.classList.add('ao-wrapper'); 197 | 198 | mainContainer.appendChild(buttonsWrapper); 199 | 200 | const saveState = () => { 201 | localStorage.setItem(STORAGE_KEY, JSON.stringify(oState)); 202 | } 203 | 204 | const changeOpacity = (e) => { 205 | oState.opacity = e.target.value 206 | img.style.opacity = oState.opacity / 100; 207 | saveState(); 208 | }; 209 | 210 | const updateSwitchButtonState = () => { 211 | if (oState.overlayIdx === 0) { 212 | toggleFullPixelButton.classList.add('ao-hidden'); 213 | toggleSmallPixelButton.classList.remove('ao-hidden'); 214 | } else { 215 | toggleSmallPixelButton.classList.add('ao-hidden'); 216 | toggleFullPixelButton.classList.remove('ao-hidden'); 217 | } 218 | } 219 | 220 | const switchOverlay = () => { 221 | oState.overlayIdx = (oState.overlayIdx + 1) % OVERLAYS.length 222 | updateImage(); 223 | img.style.opacity = oState.opacity / 100; 224 | updateSwitchButtonState(); 225 | saveState(); 226 | }; 227 | 228 | const exportScreenshot = () => { 229 | const canvas = mainContainer 230 | .querySelector('garlic-bread-canvas') 231 | .shadowRoot.querySelector('canvas'); 232 | if (!canvas) { 233 | return; 234 | } 235 | const imgUrl = canvas 236 | .toDataURL('image/png'); 237 | 238 | const downloadEl = document 239 | .createElement('a'); 240 | downloadEl.href = imgUrl; 241 | downloadEl.download = `place-${Date.now()}.png`; 242 | downloadEl.click(); 243 | downloadEl.remove(); 244 | } 245 | 246 | const addButton = (content, title, onClick) => { 247 | const button = document.createElement('button'); 248 | button.classList.add('ao-button'); 249 | button.onclick = onClick; 250 | button.innerHTML = content; 251 | button.title = title; 252 | buttonsWrapper.appendChild(button); 253 | 254 | return button; 255 | }; 256 | 257 | const addSlider = (text, min, max, val, onChange) => { 258 | const opacityWrapper = document.createElement('div'); 259 | opacityWrapper.classList.add('ao-opacity-wrapper'); 260 | opacityWrapper.title = text; 261 | 262 | const opacitySlider = document.createElement('input'); 263 | opacitySlider.classList.add('ao-opacity-slider'); 264 | opacitySlider.type = "range"; 265 | opacitySlider.min = min; 266 | opacitySlider.max = max; 267 | opacitySlider.value = val; 268 | opacitySlider.oninput = onChange; 269 | opacitySlider.title = text; 270 | 271 | opacityWrapper.appendChild(opacitySlider); 272 | buttonsWrapper.appendChild(opacityWrapper); 273 | }; 274 | 275 | // All icons are from https://www.svgrepo.com/ (MIT License) 276 | addButton( 277 | ` 278 | 279 | 280 | 281 | 282 | `, 283 | 'Screenshot', 284 | exportScreenshot 285 | ); 286 | 287 | toggleSmallPixelButton = addButton( 288 | ` 289 | 290 | 291 | 292 | `, 293 | `Switch Overlay\n(kleine Pixel)`, 294 | switchOverlay 295 | ); 296 | toggleFullPixelButton = addButton( 297 | ` 298 | 299 | 300 | 301 | 302 | 303 | 304 | `, 305 | `Switch Overlay\n(große Pixel)`, 306 | switchOverlay 307 | ); 308 | updateSwitchButtonState(); 309 | 310 | addSlider( 311 | 'Opacity', 312 | 0, 100, oState.opacity, 313 | changeOpacity 314 | ); 315 | }); 316 | } 317 | --------------------------------------------------------------------------------