Adds a direct download button to both main and thumbnail images on Freepik
// ==UserScript==
// @name Freepik Image Direct Download
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Adds a direct download button to both main and thumbnail images on Freepik
// @author CHJ85
// @match *://*.freepik.com/*
// @grant GM_download
// @grant GM_xmlhttpRequest
// @connect cdnpk.net
// @connect *
// ==/UserScript==
(function() {
'use strict';
/**
* Deep Download Strategy:
* Draws the image onto an off-screen canvas to extract pixels as a local URI,
* bypassing cross-origin restrictions.
*/
const triggerDeepDownload = (imgUrl) => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Create a new image object to ensure CORS headers are applied before drawing
const tempImg = new Image();
tempImg.crossOrigin = "anonymous";
tempImg.onload = () => {
// Set canvas size to the actual image resolution
canvas.width = tempImg.naturalWidth || tempImg.width;
canvas.height = tempImg.naturalHeight || tempImg.height;
ctx.drawImage(tempImg, 0, 0);
try {
const dataUrl = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = dataUrl;
link.download = `freepik-gen-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (e) {
console.error("DataURL generation failed:", e);
window.open(imgUrl, '_blank');
}
};
tempImg.onerror = () => {
console.error("Failed to load image for download:", imgUrl);
window.open(imgUrl, '_blank');
};
tempImg.src = imgUrl;
} catch (err) {
console.error("Canvas download initialization failed:", err);
window.open(imgUrl, '_blank');
}
};
/**
* Injects a download button into a target container.
* @param {HTMLElement} container - The wrapper for the image.
* @param {string} position - 'left' or 'right' for bottom alignment.
*/
const injectButton = (container, position = 'right') => {
if (container.querySelector('.custom-download-btn')) return;
const img = container.querySelector('img');
if (!img || !img.src) return;
const btn = document.createElement('button');
btn.innerText = '↓'; // Compact icon for thumbnails, text for main
if (container.id === 'image-comparer-container') {
btn.innerText = 'Download Image';
}
btn.className = 'custom-download-btn';
// Base styling
Object.assign(btn.style, {
position: 'absolute',
bottom: '10px',
[position]: '10px',
zIndex: '99999',
padding: container.id === 'image-comparer-container' ? '12px 20px' : '6px 10px',
backgroundColor: '#00cc66',
color: 'white',
border: 'none',
borderRadius: '6px',
cursor: 'pointer',
fontWeight: 'bold',
fontSize: '14px',
boxShadow: '0 2px 8px rgba(0,0,0,0.3)',
transition: 'all 0.2s ease',
lineHeight: '1'
});
btn.onmouseover = () => {
btn.style.backgroundColor = '#00b359';
btn.style.transform = 'scale(1.05)';
};
btn.onmouseout = () => {
btn.style.backgroundColor = '#00cc66';
btn.style.transform = 'scale(1)';
};
btn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const latestImg = container.querySelector('img');
if (latestImg) {
// Ensure we get the high-res version by removing the preview/thumbnail params
// We use a robust URL cleanup to ensure it matches the full render URL
let highResUrl = latestImg.src
.replace(/[&?]preview=1/, '')
.replace(/[&?]size=\d+/, '');
triggerDeepDownload(highResUrl);
}
};
// Ensure container can hold the absolute button
if (getComputedStyle(container).position === 'static') {
container.style.position = 'relative';
}
container.appendChild(btn);
};
const findAndInject = () => {
// 1. Main large preview
const mainContainer = document.getElementById('image-comparer-container');
if (mainContainer) {
injectButton(mainContainer, 'right');
}
// 2. Thumbnails
const thumbnails = document.querySelectorAll('div.size-full > img[alt="text-to-image"]');
thumbnails.forEach(img => {
const parent = img.parentElement;
if (parent) {
injectButton(parent, 'left');
}
});
};
// Observer to handle dynamic content loading
const observer = new MutationObserver(findAndInject);
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial run
findAndInject();
})();