Trigger image extract popup by drawing a circle gesture
// ==UserScript==
// @name Image Extract (Circle Gesture)
// @namespace CircleGestureImageExtract
// @version 1.0
// @description Trigger image extract popup by drawing a circle gesture
// @match *://*/*
// ==/UserScript==
(function() {
'use strict';
function createUI(images) {
if (document.querySelector('#image-extract-panel')) return;
const panel = document.createElement('div');
panel.id = 'image-extract-panel';
panel.style.position = 'fixed';
panel.style.top = '10px';
panel.style.left = '10px';
panel.style.background = '#222';
panel.style.color = '#eee';
panel.style.border = '1px solid #555';
panel.style.padding = '10px';
panel.style.zIndex = 999999;
panel.style.maxHeight = '400px';
panel.style.overflowY = 'auto';
panel.style.width = '300px';
const closeBtn = document.createElement('span');
closeBtn.textContent = '×';
closeBtn.style.cursor = 'pointer';
closeBtn.style.float = 'right';
closeBtn.style.fontSize = '18px';
closeBtn.style.fontWeight = 'bold';
closeBtn.onclick = () => panel.remove();
panel.appendChild(closeBtn);
const title = document.createElement('div');
title.textContent = `Found ${images.length} images`;
title.style.fontWeight = 'bold';
panel.appendChild(title);
const list = document.createElement('div');
images.forEach(src => {
const thumb = document.createElement('img');
thumb.src = src;
thumb.style.maxWidth = '100%';
thumb.style.margin = '5px 0';
list.appendChild(thumb);
});
panel.appendChild(list);
document.body.appendChild(panel);
}
function extractImages() {
const imgs = Array.from(document.querySelectorAll('img'))
.map(img => img.src)
.filter(src => src && src.startsWith('http'));
const bgImgs = Array.from(document.querySelectorAll('*'))
.map(el => getComputedStyle(el).backgroundImage)
.filter(bg => bg && bg.startsWith('url'))
.map(bg => bg.replace(/^url\(["']?/, '').replace(/["']?\)$/, ''));
const allImgs = [...imgs, ...bgImgs];
if (allImgs.length > 0) {
createUI(allImgs);
} else {
alert("No images found!");
}
}
// Gesture detection
let gesturePoints = [];
let canvas, ctx;
function initCanvas() {
if (canvas) return;
canvas = document.createElement('canvas');
canvas.style.position = 'fixed';
canvas.style.top = 0;
canvas.style.left = 0;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.zIndex = 999998;
canvas.style.pointerEvents = 'none';
document.body.appendChild(canvas);
ctx = canvas.getContext('2d');
}
function drawPath(points) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'rgba(0,255,0,0.5)';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (let i = 1; i < points.length; i++) {
ctx.lineTo(points[i][0], points[i][1]);
}
ctx.stroke();
}
document.addEventListener('touchstart', e => {
initCanvas();
gesturePoints = [];
const touch = e.touches[0];
gesturePoints.push([touch.clientX, touch.clientY]);
});
document.addEventListener('touchmove', e => {
const touch = e.touches[0];
gesturePoints.push([touch.clientX, touch.clientY]);
drawPath(gesturePoints);
});
document.addEventListener('touchend', () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (isCircleGesture(gesturePoints)) {
extractImages();
}
});
function isCircleGesture(points) {
if (points.length < 6) return false;
const xs = points.map(p => p[0]);
const ys = points.map(p => p[1]);
const w = Math.max(...xs) - Math.min(...xs);
const h = Math.max(...ys) - Math.min(...ys);
// bounding box roughly square
const aspect = w / h;
if (aspect < 0.5 || aspect > 2.0) return false;
// path length check
let pathLen = 0;
for (let i = 1; i < points.length; i++) {
const dx = points[i][0] - points[i-1][0];
const dy = points[i][1] - points[i-1][1];
pathLen += Math.sqrt(dx*dx + dy*dy);
}
if (pathLen < Math.max(w,h) * 1.5) return false;
// average radius consistency
const cx = (Math.max(...xs) + Math.min(...xs)) / 2;
const cy = (Math.max(...ys) + Math.min(...ys)) / 2;
const radii = points.map(p => Math.sqrt((p[0]-cx)**2 + (p[1]-cy)**2));
const avgR = radii.reduce((a,b)=>a+b,0)/radii.length;
const variance = radii.reduce((a,b)=>a+(b-avgR)**2,0)/radii.length;
if (Math.sqrt(variance) > avgR * 0.4) return false;
// start-end proximity
const dx = points[0][0] - points[points.length-1][0];
const dy = points[0][1] - points[points.length-1][1];
const dist = Math.sqrt(dx*dx + dy*dy);
if (dist > avgR * 0.6) return false;
return true;
}
})();