Last active
March 5, 2023 16:03
-
-
Save alexcmgit/29bfa505da47d1a981c8ab8b44d0ebbf to your computer and use it in GitHub Desktop.
Tampermonkey script to download any HTML canvas element as png.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Canvas Export | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description try to take over the world! | |
// @author @alexrintt | |
// @match https://*/* | |
// @icon data:image/svg+xml,%3Csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M11 4h2v12h2v2h-2v2h-2v-2H9v-2h2V4zM7 14v2h2v-2H7zm0 0v-2H5v2h2zm10 0v2h-2v-2h2zm0 0v-2h2v2h-2z' fill='%23808080'/%3E%3C/svg%3E | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
let borders = {} | |
let enable = false | |
let id = 0 | |
function getId() { | |
return (id++) % 1e7 | |
} | |
function normalizeBorders() { | |
borders.forEach() | |
for(const e of Object.keys(borders)) { | |
e.style.border = borders[e] | |
} | |
borders = {} | |
} | |
window.addEventListener("keydown", function(e) { | |
enable = e.ctrlKey && e.shiftKey; | |
}) | |
window.addEventListener("keyup", function(e) { | |
enable = e.ctrlKey && e.shiftKey; | |
}) | |
window.addEventListener("mouseover", function(e) { | |
if(!enable) return | |
const originalBorder = e.target.style.border | |
e.target.style.border = "1px solid red" | |
function removeBorder(_) { | |
e.target.style.border = originalBorder | |
e.target.removeEventListener(this) | |
} | |
e.target.addEventListener("mouseout", removeBorder) | |
e.target.addEventListener("mouseleave", removeBorder) | |
e.target.addEventListener("mouseout", removeBorder) | |
e.target.addEventListener("mouseout", removeBorder) | |
}) | |
window.addEventListener("click", function(e) { | |
if(!enable) return | |
if(e.target.hasAttribute("canvasexportlinkelement")) { | |
return | |
} | |
e.preventDefault(); | |
e.stopImmediatePropagation() | |
e.stopPropagation(); | |
function downloadURI(uri, name) { | |
const link = document.createElement("a"); | |
link.setAttribute("canvasexportlinkelement", true) | |
link.download = name; | |
link.href = uri; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
console.log("downloading") | |
} | |
const scaleFactor = 1 | |
const canvas = e.target; | |
if (canvas.nodeName.toLowerCase() !== "canvas") { | |
console.log("This element is not a canvas:") | |
console.log(e.target) | |
window.alert("This is not a canvas element: " + e.target + ", see the console for details."); | |
return | |
} | |
const shouldTrimCanvas = window.prompt("Should we trim the canvas? (Remove transparent padding)", "yes")?.toLowerCase()?.startsWith("y") | |
if(shouldTrimCanvas === null || shouldTrimCanvas === undefined) return | |
const resizedCanvas = document.createElement("canvas"); | |
resizedCanvas.height = canvas.height * scaleFactor; | |
resizedCanvas.width = canvas.width * scaleFactor; | |
const resizedContext = resizedCanvas.getContext("2d"); | |
resizedContext.scale(scaleFactor, scaleFactor); | |
resizedContext.drawImage(canvas, 0, 0, canvas.width * scaleFactor, canvas.height * scaleFactor); | |
if(shouldTrimCanvas) trimCanvas(resizedContext) | |
downloadURI(resizedCanvas.toDataURL("image/png"), "canvas.png") | |
}, {capture: true}) | |
// ctx is the 2d context of the canvas to be trimmed | |
// This function will return false if the canvas contains no or no non transparent pixels. | |
// Returns true if the canvas contains non transparent pixels | |
function trimCanvas(ctx) { // removes transparent edges | |
var x, y, w, h, top, left, right, bottom, data, idx1, idx2, found, imgData; | |
w = ctx.canvas.width; | |
h = ctx.canvas.height; | |
if (!w && !h) { return false } | |
imgData = ctx.getImageData(0, 0, w, h); | |
data = new Uint32Array(imgData.data.buffer); | |
idx1 = 0; | |
idx2 = w * h - 1; | |
found = false; | |
// search from top and bottom to find first rows containing a non transparent pixel. | |
for (y = 0; y < h && !found; y += 1) { | |
for (x = 0; x < w; x += 1) { | |
if (data[idx1++] && !top) { | |
top = y + 1; | |
if (bottom) { // top and bottom found then stop the search | |
found = true; | |
break; | |
} | |
} | |
if (data[idx2--] && !bottom) { | |
bottom = h - y - 1; | |
if (top) { // top and bottom found then stop the search | |
found = true; | |
break; | |
} | |
} | |
} | |
if (y > h - y && !top && !bottom) { return false } // image is completely blank so do nothing | |
} | |
top -= 1; // correct top | |
found = false; | |
// search from left and right to find first column containing a non transparent pixel. | |
for (x = 0; x < w && !found; x += 1) { | |
idx1 = top * w + x; | |
idx2 = top * w + (w - x - 1); | |
for (y = top; y <= bottom; y += 1) { | |
if (data[idx1] && !left) { | |
left = x + 1; | |
if (right) { // if left and right found then stop the search | |
found = true; | |
break; | |
} | |
} | |
if (data[idx2] && !right) { | |
right = w - x - 1; | |
if (left) { // if left and right found then stop the search | |
found = true; | |
break; | |
} | |
} | |
idx1 += w; | |
idx2 += w; | |
} | |
} | |
left -= 1; // correct left | |
if(w === right - left + 1 && h === bottom - top + 1) { return true } // no need to crop if no change in size | |
w = right - left + 1; | |
h = bottom - top + 1; | |
ctx.canvas.width = w; | |
ctx.canvas.height = h; | |
ctx.putImageData(imgData, -left, -top); | |
return true; | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment