Skip to content

Instantly share code, notes, and snippets.

@alexrintt
Last active March 5, 2023 16:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexrintt/29bfa505da47d1a981c8ab8b44d0ebbf to your computer and use it in GitHub Desktop.
Save alexrintt/29bfa505da47d1a981c8ab8b44d0ebbf to your computer and use it in GitHub Desktop.
Tampermonkey script to download any HTML canvas element as png.
// ==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