<!DOCTYPE html> <html> <head> <style> form { display: grid; grid-template-columns: 30% 50%; } svg { background: repeating-conic-gradient(#dddddd 0% 25%, #999999 0% 50%) 50% / 20px 20px; } </style> </head> <body> <h1>Antifa Sticker Generator</h1> <p> A purely client-side sticker generator. <a href="https://git.kabelsalat.ch/s3lph/antifa-sticker-generator">Source Code.</a> </p> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" width="500" height="500" id="svg"> <style> text { fill: white; font-family: sans-serif; font-weight: bold; font-size: 40px; text-align: center; text-anchor: middle; } </style> <defs> <filter id="black"> <feColorMatrix in="SourceGraphic" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0" /> </filter> <filter id="red"> <feColorMatrix in="SourceGraphic" type="matrix" values="1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0" /> </filter> <path id="upper" stroke="red" fill="none" d="M 249.999 460 A 210 210 0 1 1 250.001 460" /> <path id="lower" stroke="red" fill="none" d="M 249.999 10 A 240 240 0 1 0 250.001 10" /> </defs> <g id="svgroot" transform="scale(1, 1)"> <circle id="bleed" cx="250" cy="250" r="249" stroke="#ff00ff" fill="none" stroke-width="0" /> <circle cx="250" cy="250" r="225" stroke="black" fill="white" stroke-width="50" /> <g id="icon"> <image transform="translate(0 0) translate(-9 -2) scale(1, 1)" id="iconred" href="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTEycHgiCiAgICAgaGVpZ2h0PSI1MTJweCIgdmlld0JveD0iMCAwIDUxMiA1MTIiPgogIDxwYXRoIGZpbGw9IiMwMDAwMDAiIGQ9Ik0xMjUuNjY3LDEyMi45MTdjMCwwLDI4LDMzLjA4Myw4OS4yNSwzNC4zMzNjNjEuMjUxLDEuMjUsOTUuOTE3LTM3LjkxNywxMzAuOTE3LTMzLjMzM3M2Myw0My4zMzMsNzcuNzUsNTAuMTY3TDMzMyw0MjUuNzUKICAgICAgICBjMCwwLTEuODMzLDEuMzM0LTExLDQuNjY3cy0xMS4zMzMsNC4zMzMtMTEuMzMzLDQuMzMzbDQwLjI1LTEyMi45MTdjMCwwLTM4Ljc1LTMyLjY4Ny03My43NS0zNQogICAgICAgIGMtMzUuMDAxLTIuMzEzLTUwLjkxNywyMy43NS0xMTAuMDgzLDEzLjc1QzEwNy45MTcsMjgwLjU4Myw2OS41LDI0My41LDY5LjUsMjQzLjVzLTEuNDE1LTIxLjA2NywxNC41LTU5LjgzMwogICAgICAgIEM5OS45MTQsMTQ0LjkwMiwxMjUuNjY3LDEyMi45MTcsMTI1LjY2NywxMjIuOTE3eiIvPgo8L3N2Zz4K" width="512" height="512" filter="url(#red)" /> <image transform="translate(0 0) translate(-9 -2) scale(1, 1)" id="iconblack" href="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTEycHgiCiAgICAgaGVpZ2h0PSI1MTJweCIgdmlld0JveD0iMCAwIDUxMiA1MTIiPgogIDxwYXRoIGZpbGw9IiMwMDAwMDAiIGQ9Ik02OS45MTcsMjUxLjI1YzAsMCw0MS4wODQsMzguNDE2LDEwMi40MTcsNDYuMDgzYzYxLjMzMiw3LjY2Nyw2Ny4wMzktMTQuNDkxLDEwMC45MTYtMTQuMjUKICAgICAgICBjMjEuNTQ4LDAuMTUzLDI5LjMzNCw5LjU4NCwyOS4zMzQsOS41ODRsLTUzLjUwNiwxNDkuNjdjMCwwLTQuMTgtMC4wNDQtMTAuMDkyLTAuNjkyYy01LjkxMS0wLjY0OC05LjU0MS0xLjI5NC05LjU0MS0xLjI5NAogICAgICAgIEwyNTcsMzU3LjVjMCwwLTEzLjU4NC04LjcwNy0yNy4yNS0xMC41Yy0xMy42NjgtMS43OTMtMjEuMDQyLDMtNDYuNSwyLjVjLTI0LjA3My0wLjQ3My03MS4yNS0xMi43NS05My4zMzMtNDUuNzUKICAgICAgICBTNjkuOTE3LDI1MS4yNSw2OS45MTcsMjUxLjI1eiIvPgo8L3N2Zz4K" width="512" height="512" filter="url(#black)" /> </g> <text><textPath id="uppertext" startOffset="50%" href="#upper">ANTIFASCHISTISCHE</textPath></text> <text><textPath id="lowertext" startOffset="50%" href="#lower">AKTION</textPath></text> </g> </svg> <form> <label for="input-text-upper">Upper text</label> <input type="text" id="input-text-upper" name="upper" value="ANTIFASCHISTISCHE" /> <label for="input-text-lower">Lower text</label> <input type="text" id="input-text-lower" name="lower" value="AKTION" /> <label for="input-range-shift-x">X Position</label> <input type="range" id="input-range-position-x" name="position-x" value="-9" min="-500" max="500" /> <label for="input-range-shift-y">Y Position</label> <input type="range" id="input-range-position-y" name="position-y" value="-2" min="-500" max="500" /> <label for="input-range-shift-x">X Shift</label> <input type="range" id="input-range-shift-x" name="distance-x" value="0" min="-250" max="250" /> <label for="input-range-shift-y">Y Shift</label> <input type="range" id="input-range-shift-y" name="distance-y" value="0" min="-250" max="250" /> <label for="input-range-scale-black">Black Scale</label> <input type="range" id="input-range-scale-black" name="scale-black" value="0" min="-3" max="3" step="0.01" /> <label for="input-range-scale-red">Red Scale</label> <input type="range" id="input-range-scale-red" name="scale-red" value="0" min="-3" max="3" step="0.01" /> <label for="input-file-icon">Black Icon (black+white+alpha only)</label> <input type="file" id="input-file-icon-black" name="icon-black" accept="image/*" /> <label for="input-file-icon">Red Icon (black+white+alpha only)</label> <input type="file" id="input-file-icon-red" name="icon-red" accept="image/*" /> <label for="input-range-bleed">Bleed (black in download)</label> <input type="range" id="input-range-bleed" name="bleed" value="0" min="0" max="50" /> <div> <input type="checkbox" id="input-check-swap-red-black" name="swap-red-black" /> <label for="input-check-swap-red-black">Red on top of black</label> <input type="checkbox" id="input-check-lock-scale" name="lock-scale" /> <label for="input-check-lock-scale">Same scale for red and black</label> </div> <div> <input type="button" id="input-button-download" value="Download SVG" /> <input type="button" id="input-button-download-png" value="Download PNG" /> </div> <label for="input-file-import-svg">Import downloaded SVG</label> <input type="file" id="input-file-import-svg" name="import-svg" accept="image/svg+xml" /> </form> <a id="download" download="antifa.svg" filename="antifa.svg"></a> <a id="download-png" download="antifa.png" filename="antifa.png"></a> <script> let doc = document.getElementById('svg'); let inputTextUpper = document.getElementById("input-text-upper"); let inputTextLower = document.getElementById("input-text-lower"); let inputRangePositionX = document.getElementById("input-range-position-x"); let inputRangePositionY = document.getElementById("input-range-position-y"); let inputRangeShiftX = document.getElementById("input-range-shift-x"); let inputRangeShiftY = document.getElementById("input-range-shift-y"); let inputRangeScaleBlack = document.getElementById("input-range-scale-black"); let inputRangeScaleRed = document.getElementById("input-range-scale-red"); let inputFileIconBlack = document.getElementById("input-file-icon-black"); let inputFileIconRed = document.getElementById("input-file-icon-red"); let inputCheckSwap = document.getElementById("input-check-swap-red-black"); let inputCheckLockScale = document.getElementById("input-check-lock-scale"); let inputRangeBleed = document.getElementById("input-range-bleed"); let inputButtonDownload = document.getElementById("input-button-download"); let inputButtonDownloadPng = document.getElementById("input-button-download-png"); let inputFileImportSvg = document.getElementById("input-file-import-svg"); inputTextUpper.oninput = (ev) => { doc.getElementById("uppertext").textContent = inputTextUpper.value; }; inputTextLower.oninput = (ev) => { doc.getElementById("lowertext").textContent = inputTextLower.value; }; inputRangePositionX.oninput = (ev) => { doc.getElementById("iconblack").transform.baseVal[1].matrix.e = inputRangePositionX.value; doc.getElementById("iconred").transform.baseVal[1].matrix.e = inputRangePositionX.value; }; inputRangePositionY.oninput = (ev) => { doc.getElementById("iconblack").transform.baseVal[1].matrix.f = inputRangePositionY.value; doc.getElementById("iconred").transform.baseVal[1].matrix.f = inputRangePositionY.value; }; inputRangeShiftX.oninput = (ev) => { doc.getElementById("iconblack").transform.baseVal[0].matrix.e = inputRangeShiftX.value; doc.getElementById("iconred").transform.baseVal[0].matrix.e = -inputRangeShiftX.value; }; inputRangeShiftY.oninput = (ev) => { doc.getElementById("iconblack").transform.baseVal[0].matrix.f = inputRangeShiftY.value; doc.getElementById("iconred").transform.baseVal[0].matrix.f = -inputRangeShiftY.value; }; inputRangeScaleBlack.oninput = (ev) => { let iconblack = doc.getElementById("iconblack"); iconblack.transform.baseVal[2].matrix.a = Math.pow(10, inputRangeScaleBlack.value); iconblack.transform.baseVal[2].matrix.d = Math.pow(10, inputRangeScaleBlack.value); if (inputCheckLockScale.checked) { let iconred = doc.getElementById("iconred"); iconred.transform.baseVal[2].matrix.a = Math.pow(10, inputRangeScaleBlack.value); iconred.transform.baseVal[2].matrix.d = Math.pow(10, inputRangeScaleBlack.value); } }; inputRangeScaleRed.oninput = (ev) => { let iconred = doc.getElementById("iconred"); iconred.transform.baseVal[2].matrix.a = Math.pow(10, inputRangeScaleRed.value); iconred.transform.baseVal[2].matrix.d = Math.pow(10, inputRangeScaleRed.value); }; inputFileIconBlack.onchange = (ev) => { let file = ev.target.files[0]; let reader = new FileReader(); let iconblack = doc.getElementById("iconblack"); reader.onload = (re) => { let img = new Image(); img.onload = (i) => { iconblack.href.baseVal = re.target.result; iconblack.width.baseVal.value = i.path[0].width; iconblack.height.baseVal.value = i.path[0].height; }; img.src = re.target.result; }; reader.readAsDataURL(file); }; inputFileIconRed.onchange = (ev) => { let file = ev.target.files[0]; let reader = new FileReader(); let iconred = doc.getElementById("iconred"); reader.onload = (re) => { let img = new Image(); img.onload = (i) => { iconred.href.baseVal = re.target.result; iconred.width.baseVal.value = i.path[0].width; iconred.height.baseVal.value = i.path[0].height; }; img.src = re.target.result; }; reader.readAsDataURL(file); }; inputCheckSwap.onchange = (ev) => { if (ev.target.checked) { doc.getElementById("icon").insertBefore(doc.getElementById("iconblack"), doc.getElementById("iconred")); } else { doc.getElementById("icon").insertBefore(doc.getElementById("iconred"), doc.getElementById("iconblack")); } }; inputCheckLockScale.onchange = (ev) => { inputRangeScaleRed.disabled = ev.target.checked; let iconred = doc.getElementById("iconred"); if (ev.target.checked) { iconred.transform.baseVal[2].matrix.a = Math.pow(10, inputRangeScaleBlack.value); iconred.transform.baseVal[2].matrix.d = Math.pow(10, inputRangeScaleBlack.value); } else { iconred.transform.baseVal[2].matrix.a = Math.pow(10, inputRangeScaleRed.value); iconred.transform.baseVal[2].matrix.d = Math.pow(10, inputRangeScaleRed.value); } } inputRangeBleed.oninput = (ev) => { let bleed = doc.getElementById("bleed"); let bleedVal = parseInt(inputRangeBleed.value); if (bleedVal == 0) { bleed.style.strokeWidth = 0; } else { bleed.style.strokeWidth = bleedVal * 2 + 1; } doc.width.baseVal.value = 500 + bleedVal * 2; doc.height.baseVal.value = 500 + bleedVal * 2; doc.viewBox.baseVal.x = -bleedVal; doc.viewBox.baseVal.y = -bleedVal; doc.viewBox.baseVal.width = 500 + bleedVal * 2; doc.viewBox.baseVal.height = 500 + bleedVal * 2; }; inputButtonDownload.onclick = (ev) => { doc.getElementById("bleed").style.stroke = 'black'; let serialized = new XMLSerializer().serializeToString(doc); doc.getElementById("bleed").style.stroke = '#ff00ff'; let a = document.getElementById('download'); a.setAttribute('href', 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(serialized)); a.click(); }; inputButtonDownloadPng.onclick = (ev) => { let svgSize = doc.width.baseVal.value; let svgOffset = doc.viewBox.baseVal.x; let imgSize = parseInt(prompt("Enter image size (e.g. 2048)", "2048")); let svgRoot = doc.getElementById("svgroot"); if (Number.isNaN(imgSize)) { return; } // Resize SVG to output size, then export as XML, and resize back to default size doc.getElementById("bleed").style.stroke = 'black'; doc.width.baseVal.value = imgSize; doc.height.baseVal.value = imgSize; doc.viewBox.baseVal.width = imgSize; doc.viewBox.baseVal.height = imgSize; doc.viewBox.baseVal.x = svgOffset * imgSize/svgSize; doc.viewBox.baseVal.y = svgOffset * imgSize/svgSize; svgRoot.transform.baseVal[0].matrix.a = imgSize/svgSize; svgRoot.transform.baseVal[0].matrix.d = imgSize/svgSize; let serialized = new XMLSerializer().serializeToString(doc); doc.getElementById("bleed").style.stroke = '#ff00ff'; svgRoot.transform.baseVal[0].matrix.a = 1; svgRoot.transform.baseVal[0].matrix.d = 1; doc.viewBox.baseVal.width = svgSize; doc.viewBox.baseVal.height = svgSize; doc.viewBox.baseVal.x = svgOffset; doc.viewBox.baseVal.y = svgOffset; doc.width.baseVal.value = svgSize; doc.height.baseVal.value = svgSize; let canvas = document.createElement('canvas'); canvas.width = imgSize; canvas.height = imgSize; let ctx = canvas.getContext("2d"); let image = new Image(); image.onload = () => { ctx.drawImage(image, 0, 0); let a = document.getElementById('download-png'); a.setAttribute('href', canvas.toDataURL()); a.click(); }; image.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(serialized); }; inputFileImportSvg.onchange = (ev) => { let file = ev.target.files[0]; let reader = new FileReader(); reader.onload = (re) => { let parser = new DOMParser(); let imp = parser.parseFromString(re.target.result, 'image/svg+xml'); let iconblack = doc.getElementById('iconblack'); let iconred = doc.getElementById('iconred'); // texts inputTextUpper.value = imp.getElementById('uppertext').textContent; inputTextLower.value = imp.getElementById('lowertext').textContent; doc.getElementById('uppertext').textContent = inputTextUpper.value; doc.getElementById('lowertext').textContent = inputTextLower.value; // img href iconblack.href.baseVal = imp.getElementById('iconblack').href.baseVal; iconred.href.baseVal = imp.getElementById('iconred').href.baseVal; // shift inputRangeShiftX.value = imp.getElementById('iconblack').transform.baseVal[0].matrix.e; inputRangeShiftY.value = imp.getElementById('iconblack').transform.baseVal[0].matrix.f; iconblack.transform.baseVal[0].matrix.e = inputRangeShiftX.value; iconred.transform.baseVal[0].matrix.e = -inputRangeShiftX.value; iconblack.transform.baseVal[0].matrix.f = inputRangeShiftY.value; iconred.transform.baseVal[0].matrix.f = -inputRangeShiftY.value; // position inputRangePositionX.value = imp.getElementById('iconblack').transform.baseVal[1].matrix.e; inputRangePositionY.value = imp.getElementById('iconblack').transform.baseVal[1].matrix.f; iconblack.transform.baseVal[1].matrix.e = inputRangePositionX.value; iconred.transform.baseVal[1].matrix.e = inputRangePositionX.value; iconblack.transform.baseVal[1].matrix.f = inputRangePositionY.value; iconred.transform.baseVal[1].matrix.f = inputRangePositionY.value; // scale let scaleBlack = imp.getElementById('iconblack').transform.baseVal[2].matrix.a; let scaleRed = imp.getElementById('iconred').transform.baseVal[2].matrix.a; inputRangeScaleBlack.value = Math.log10(scaleBlack); inputRangeScaleRed.value = Math.log10(scaleRed); iconblack.transform.baseVal[2].matrix.a = scaleBlack; iconblack.transform.baseVal[2].matrix.d = scaleBlack; iconred.transform.baseVal[2].matrix.a = scaleRed; iconred.transform.baseVal[2].matrix.d = scaleRed; // swap if (imp.getElementById('icon').children[0].id == 'iconblack') { inputCheckSwap.checked = true; doc.getElementById('icon').insertBefore(doc.getElementById('iconblack'), doc.getElementById('iconred')); } else { inputCheckSwap.checked = false; doc.getElementById('icon').insertBefore(doc.getElementById('iconred'), doc.getElementById('iconblack')); } // bleed inputRangeBleed.value = Math.max(imp.getElementById('bleed').style.strokeWidth - 1, 0); let bleed = doc.getElementById("bleed"); let bleedVal = parseInt(inputRangeBleed.value); if (bleedVal == 0) { bleed.style.strokeWidth = 0; } else { bleed.style.strokeWidth = bleedVal * 2 + 1; } doc.width.baseVal.value = 500 + bleedVal * 2; doc.height.baseVal.value = 500 + bleedVal * 2; doc.viewBox.baseVal.x = -bleedVal; doc.viewBox.baseVal.y = -bleedVal; doc.viewBox.baseVal.width = 500 + bleedVal * 2; doc.viewBox.baseVal.height = 500 + bleedVal * 2; }; reader.readAsText(file); }; </script> </body> </html>