您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Compare images
// ==UserScript== // @name Easy Compare // @description Compare images // @version 0.9.5 // @author Secant (TYT@NexusHD) // @license GPL-3.0-or-later // @supportURL [email protected] // @contributionURL https://i.loli.net/2020/02/28/JPGgHc3UMwXedhv.jpg // @contributionAmount 10 // @include * // @require https://cdn.staticfile.org/jquery/3.4.1/jquery.min.js // @require https://gf.qytechs.cn/scripts/401377-pixelmatch/code/pixelmatch.js // @resource PixelMatchCore https://gf.qytechs.cn/scripts/401377-pixelmatch/code/pixelmatch.js // @namespace https://gf.qytechs.cn/users/152136 // @icon data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23008000'%3E%3Cpath id='ld' d='M20 6H10c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h10v4h4V2h-4v4zm0 30H10l10-12v12zM38 6H28v4h10v26L28 24v18h10c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4z'/%3E%3C/svg%3E // @grant GM_xmlhttpRequest // @grant GM_download // @grant GM_getValue // @grant GM_setValue // @grant GM_getResourceText // @grant unsafewindow // @connect hdbits.org // @connect awesome-hd.me // @connect ptpimg.me // @connect imgbox.com // @connect malzo.com // @connect imagebam.com // @connect pixhost.to // @connect loli.net // @connect funkyimg.com // @connect ilikeshots.club // @connect z4a.net // @connect picgd.com // @connect tu.totheglory.im // @connect tpimg.ccache.org // @connect pterclub.com // @connect catbox.moe // @connect sm.ms // @connect broadcasthe.net // @connect * // ==/UserScript== // jshint esversion:8, -W054 (async function ($, Mousetrap, pixelmatch, URL) { 'use strict'; /*--- Preparation ---*/ // Mousetrap Pause Plugin if (Mousetrap) { let target = Mousetrap.prototype || Mousetrap; const _originalStopCallback = target.stopCallback; target.stopCallback = function (e, element, combo) { var self = this; if (self.paused) { return true; } return _originalStopCallback.call(self, e, element, combo); }; target.pause = function () { var self = this; self.paused = true; }; target.unpause = function () { var self = this; self.paused = false; }; try { Mousetrap.init(); } catch (_) { } } /*--- Global Contexts ---*/ // A global timeout ID holder let timeout; // A global scale factor let scale = 10; // Regex replacement array that converts thumbs to originals const t2oLib = [ [/\.thumb\.jpe?g$/, ''], // nexusphp [/\.md\.png$/, '.png'], // m-team [/\.th\.png$/, '.png'], // pterclub [/_thumb\.png$/, '.png'], // totheglory [/img\.awesome\-hd\.me\/t(\/\d+)?\//, 'img.awesome-hd.me/images/'], // awesome-hd [/thumbs((?:\d+)?\.imgbox\.com\/.+_)t\.png$/, 'images$1o.png'], // imgbox [/t((?:\d+)?\.pixhost\.to\/)thumbs\//, 'img$1images/'], // pixhost [/t(\.hdbits\.org\/.+)\.jpg$/, 'i$1.png'], // hdbits [/^.*?imagecache\.php\?url=(https?)%3A%2F%2Fthumbs(\d+)?\.imgbox\.com%2F(\w+)%2F(\w+)%2F(\w+)_t\.png/, '$1://images$2.imgbox.com/$3/$4/$5_o.png'] ]; // Skip redirections const skipRedirLib = [ [/^https?:\/\/anonym\.to\/\?(.*)$/, (_, p1) => decodeURIComponent(p1)], [/^https?:\/\/www\.dereferer\.org\/\?(.*)$/, (_, p1) => decodeURIComponent(p1)], [/^(?:https?:\/\/pterclub\.com)?\/link\.php\?sign=.+?&target=(.*)$/, (_, p1) => decodeURIComponent(p1.replace(/\+/g, ' ')).replace(/ /g, '%20')], [/^.*?imagecache\.php\?url=(.*)$/, (_, p1) => decodeURIComponent(p1.replace(/\+/g, ' ')).replace(/ /g, '%20')] ]; // Probable original image selectors on a view page const guessSelectorLib = [ '#image-viewer-container>img', '.image-container img', 'div.img.big>img', 'img.mainimage', 'img.main-image', 'img#img' ]; // Filter function mapping const filterImage = { 'solar': img => rgbImage(img, solarWorker || rgbSolarCurve), 's2lar': img => rgbImage(img, s2larWorker || rgbS2larCurve) }; /*--- Workers Initialization ---*/ // Solar Curve function solarCurve(x, t = 5, k = 5.5) { const m = (k * Math.PI - 128 / t); const A = -1 / 4194304 * m; const B = 3 / 32768 * m; const C = 1 / t; return Math.round( 127.9999 * Math.sin( A * x ** 3 + B * x ** 2 + C * x - Math.PI / 2 ) + 127.5 ) || 0; } let rgbSolarCurve = GM_getValue('solarCurve'); let rgbS2larCurve = GM_getValue('s2larCurve'); if (!rgbSolarCurve) { rgbSolarCurve = [ Array.from({ length: 256 }, (_, x) => solarCurve(x)), Array.from({ length: 256 }, (_, x) => solarCurve(x - 5)), Array.from({ length: 256 }, (_, x) => solarCurve(x + 5)) ]; GM_setValue('solarCurve', JSON.stringify(rgbSolarCurve)); rgbS2larCurve = [ Array.from({ length: 256 }, (_, x) => rgbSolarCurve[0][[rgbSolarCurve[0][x]]]), Array.from({ length: 256 }, (_, x) => rgbSolarCurve[1][[rgbSolarCurve[1][x]]]), Array.from({ length: 256 }, (_, x) => rgbSolarCurve[2][[rgbSolarCurve[2][x]]]) ]; GM_setValue('s2larCurve', JSON.stringify(rgbS2larCurve)); } else { rgbSolarCurve = JSON.parse(rgbSolarCurve); rgbS2larCurve = JSON.parse(rgbS2larCurve); } rgbSolarCurve = rgbSolarCurve.map(e => new Uint8Array(e)); rgbS2larCurve = rgbS2larCurve.map(e => new Uint8Array(e)); async function loadBuffer(worker, [R, G, B]) { return new Promise((resolve) => { worker.onmessage = (e) => { resolve(e.data.result); }; worker.postMessage({ R: R.buffer, G: G.buffer, B: B.buffer }, [R.buffer, G.buffer, B.buffer]); }); } // Diff, Solar, S2lar Worker Initialization function diffWork(f) { f.apply(self); const u = Uint8ClampedArray; self.onmessage = ({ data: { key, img1, img2, width, height, init } }) => { img1 = new u(img1); img2 = new u(img2); const diff = new u(img1); try { self.pixelmatch(img1, img2, diff, width, height, init); self.postMessage({ diff: diff.buffer, width: width, height: height, key: key }, [diff.buffer]); } catch (err) { console.warn(err); self.postMessage({ diff: null, key: key }); } }; } function rgbWork(f) { const u = Uint8ClampedArray; self.onmessage = ({ data: { key, R, G, B, img, width, height } }) => { if (R && G && B) { self.RGB = [new u(R), new u(G), new u(B)]; self.postMessage({ result: true }); } else { img = new u(img); const filter = new u(img); try { f.apply(self, [img, filter, width, height, self.RGB]); self.postMessage({ filter: filter.buffer, width: width, height: height, key: key }, [filter.buffer]); } catch (err) { console.warn(err); self.postMessage({ filter: null, key: key }); } } }; } function stringifyWork(workFun, arg) { return `(${workFun.toString()})(${arg})`; } let diffWorker, solarWorker, s2larWorker; let loadBufferPromise; try { const diffWorkerBlob = new Blob([ stringifyWork(diffWork, new Function( GM_getResourceText('PixelMatchCore') )) ], { type: 'application/javascript' }); diffWorker = new Worker(URL.createObjectURL(diffWorkerBlob)); diffWorker.keyPool = {}; URL.revokeObjectURL(diffWorkerBlob); const rgbWorkerBlob = new Blob([stringifyWork(rgbWork, rgbRemap)], { type: 'application/javascript' }); const rgbWorkerURL = URL.createObjectURL(rgbWorkerBlob); solarWorker = new Worker(rgbWorkerURL); solarWorker.keyPool = {}; const transSo = loadBuffer(solarWorker, rgbSolarCurve); s2larWorker = new Worker(rgbWorkerURL); s2larWorker.keyPool = {}; const transS2 = loadBuffer(s2larWorker, rgbS2larCurve); URL.revokeObjectURL(rgbWorkerURL); loadBufferPromise = Promise.all([transSo, transS2]); } catch (e) { try { const diffWorkerDataURI = `data:application/javascript,${ encodeURIComponent( stringifyWork(diffWork, new Function( GM_getResourceText('PixelMatchCore') )) )}`; diffWorker = new Worker(diffWorkerDataURI); diffWorker.keyPool = {}; const rgbWorkerDataURI = `data:application/javascript,${ encodeURIComponent( stringifyWork(rgbWork, rgbRemap) )}`; solarWorker = new Worker(rgbWorkerDataURI); solarWorker.keyPool = {}; const transSo = loadBuffer(solarWorker, rgbSolarCurve); s2larWorker = new Worker(rgbWorkerDataURI); s2larWorker.keyPool = {}; const transS2 = loadBuffer(s2larWorker, rgbS2larCurve); loadBufferPromise = Promise.all([transSo, transS2]); } catch (e) { diffWorker = null; solarWorker = null; } } /*--- Helper Functions ---*/ // Virtual DOM for selection without fetching images function $$(htmlString) { return $(htmlString, document.implementation.createHTMLDocument('virtual')); } // Function to make an <canvas/> element function makeCanvas(outlineColor = 'red') { const $figure = $('<figure/>').css({ 'width': 'fit-content', 'position': 'fixed', 'top': '50%', 'left': '50%', 'margin': '0', 'vertical-align': 'middle' }); const $canvas = $(`<canvas/>`).css({ 'display': 'none', 'transform': 'translate(-50%, -50%)', 'opacity': '1', 'outline': '3px solid ' + outlineColor, 'outline-offset': '2px', }); $figure.append($canvas); return $canvas[0]; } // Draw text on canvas function drawText(canvas, text, font = '16px sans serif', fillStyle = 'rgba(255,255,255,255)') { const context = canvas.getContext('2d'); context.font = font; canvas.width = context.measureText(text).width; canvas.height = 20; context.font = font; context.fillStyle = fillStyle; context.fillText(text, 0, 15); } // Draw image on canvas function drawImage(canvas, imageData) { canvas.width = imageData.width; canvas.height = imageData.height; canvas.getContext('2d').putImageData(imageData, 0, 0); } // Guess original image src from view page function guessOriginalImage(url) { return new Promise((resolve) => { GM_xmlhttpRequest({ url: url, method: 'GET', timeout: 6000, onload: (x) => { if (x.status === 200) { try { const $e = $$(x.responseText); const src = $e.find(guessSelectorLib.join(','))[0].src; let realSrc = src; for (let pairs of t2oLib) { realSrc = realSrc.replace(pairs[0], pairs[1]); if (realSrc !== src) { break; } } resolve(realSrc); } catch (e) { console.warn(e); resolve(null); } } else { console.warn(x); resolve(null); } }, ontimeout: (e) => { console.warn(e); resolve(null); } }); }); } // RGB channel remap function (lowlevel) function rgbRemap(raw, filter, width, height, rgb) { const [R, G, B] = rgb; for (let row = 0; row < height; ++row) { for (let col = 0; col < width; ++col) { let ind = col * 4 + row * width * 4; filter[ind] = R[raw[ind]]; filter[ind + 1] = G[raw[ind + 1]]; filter[ind + 2] = B[raw[ind + 2]]; filter[ind + 3] = raw[ind + 3]; } } } // Get ImageData from src with an optional update hook // Cross origin is supported async function GM_getImageData(src, fn) { return new Promise((resolve) => { GM_xmlhttpRequest({ url: src, method: 'GET', // Blob or Arraybuffer responseType will slow down the page noticeably, // so we text type with x-user-defined charset to get raw binaries overrideMimeType: 'text/plain; charset=x-user-defined', // Progress update hook onprogress: (e) => { if (typeof (fn) == 'function') { if (e.total !== -1) { fn(e.loaded / e.total); } else { fn(-e.loaded); } } }, onload: (e) => { if (e.status === 200) { // Get binary from text const imageResponseText = e.responseText; const l = imageResponseText.length; const bytes = new Uint8Array(l); for (let i = 0; i < l; i++) { bytes[i] = imageResponseText.charCodeAt(i) & 0xff; } // Decode png binary and resolve the image data arraybuffer, // createImageBitmap is a multi-thread operation, // and won't complain about CSP img-src errors when using Image object const type = (e.responseHeaders.match(/content\-type: *(.+)$/m) || ['', 'image/png'])[1]; let ext; switch (type) { case 'image/apng': ext = '.apng'; break; case 'image/bmp': ext = '.bmp'; break; case 'image/gif': ext = '.gif'; break; case 'image/x-icon': ext = '.ico'; break; case 'image/jpeg': ext = '.jpg'; break; case 'image/png': ext = '.png'; break; case 'image/svg+xml': ext = '.svg'; break; case 'image/tiff': ext = '.tiff'; break; case 'image/webp': ext = '.webp'; break; default: if (type.slice(0, 5) === 'image') { let temp = type.match(/\/(.*)/); if (temp) { ext = '.' + temp; } else { ext = ''; } } else { ext = (src.match(/\.[^\.]+$/) || [''])[0]; } break; } createImageBitmap(new Blob([bytes], { type: type })) .then((e) => { const [width, height] = [e.width, e.height]; const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const context = canvas.getContext('2d'); context.drawImage(e, 0, 0); e.close(); resolve({ imageData: new ImageData( context.getImageData(0, 0, width, height).data, width, height ), extension: ext }); }); } else { console.warn(e); resolve(null); } }, onerror: (e) => { console.warn(e); resolve(null); } }); }); } /*--- Diff and Filter Core Function ---*/ // Diff images async function diffImage(img1, img2, init = { alpha: 0.5, threshold: 0.007 }, worker = diffWorker) { if ( img1 && img2 && img1.width === img2.width && img1.height === img2.height ) { if (worker) {// async diff const [ raw1, raw2, width, height ] = [ img1.data.buffer, img2.data.buffer, img1.width, img1.height ]; const key = '' + Date.now(); worker.onmessage = (e) => { const returnKey = e.data.key; const resolve = worker.keyPool[returnKey]; if (resolve) { resolve( new ImageData( new Uint8ClampedArray(e.data.diff), e.data.width, e.data.height ) ); } }; worker.postMessage({ img1: raw1, img2: raw2, width: width, height: height, init: init, key: key }, [raw1, raw2]); return new Promise((res) => { worker.keyPool[key] = res; }); } else {// sync diff const [data1, data2, width, height] = [ img1.data, img2.data, img1.width, img1.height ]; const res = new Uint8ClampedArray(data1); pixelmatch(data1, data2, res, width, height, init); return ( new ImageData( res, width, height ) ); } } else { return null; } } // RGB channel remap filter image async function rgbImage(img, argument) { if (img) { if (argument instanceof Worker) { const worker = argument; const [raw, width, height] = [img.data.buffer, img.width, img.height]; const key = '' + Date.now(); worker.onmessage = (e) => { const returnKey = e.data.key; const resolve = worker.keyPool[returnKey]; if (resolve) { resolve( new ImageData( new Uint8ClampedArray(e.data.filter), e.data.width, e.data.height ) ); } }; await loadBufferPromise; worker.postMessage({ img: raw, width: width, height: height, key: key }, [raw]); return new Promise((res) => { worker.keyPool[key] = res; }); } else { const rgb = argument; const [data, width, height] = [img.data, img.width, img.height]; const res = new Uint8ClampedArray(data); rgbRemap(data, res, width, height, rgb); return ( new ImageData( res, width, height ) ); } } else { return null; } } function reRenderImage(image, scale) { if (scale > 10) { image.style['image-rendering'] = 'pixelated'; } else { image.style['image-rendering'] = 'auto'; } } /*--- Get Images: Original, Diffed or Filtered ---*/ // Get original image function function getOriginalImage(target, $overlay) { if (target.easyCompare && target.easyCompare.originalImage) { const originalImage = target.easyCompare.originalImage; if (originalImage.ready) { originalImage.style.width = `${scale * 10}%`; reRenderImage(originalImage, scale); } return originalImage; } else { const originalCanvas = makeCanvas(); const updateProgress = (p) => { if (p !== null && p >= 0) { drawText(originalCanvas, `Loading ${(p * 100).toFixed(1)}%`); } else if (p < 0) { drawText(originalCanvas, `Loading...`); } }; const resolveOriginal = (src, onprogress, resolve) => { GM_getImageData(src, onprogress).then(({ imageData: originalImageData, extension }) => { resolve(originalImageData); originalCanvas.src = src; originalCanvas.ext = extension; drawImage(originalCanvas, originalImageData); originalCanvas.style.width = `${scale * 10}%`; reRenderImage(originalCanvas, scale); originalCanvas.ready = true; }); }; drawText(originalCanvas, `Loading...`); originalCanvas.ready = false; originalCanvas.targetImage = target; $overlay.append(originalCanvas.parentElement); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare.originalImage = originalCanvas; target.easyCompare.originalImagePromise = onprogress => new Promise(async (resolve) => { let realSrc = target.src; // Parse original src from thumb src for (let pairs of t2oLib) { realSrc = realSrc.replace(pairs[0], pairs[1]); if (realSrc !== target.src) { resolveOriginal(realSrc, onprogress, resolve); return; } } // Guess original src from hyper link let href, hrefOriginal; if ((hrefOriginal = target.parentElement.href, href = hrefOriginal)) { for (let pairs of skipRedirLib) { href = href.replace(pairs[0], pairs[1]); if (href !== hrefOriginal) { break; } } if (href.match(/\.png$|\.jpe?g$|\.webp|\.gif|\.bmp|\.svg$/)) { resolveOriginal(href, onprogress, resolve); return; } else { guessOriginalImage(href).then(src => { resolveOriginal(src || realSrc, onprogress, resolve); return; }); } } else { resolveOriginal(realSrc, onprogress, resolve); return; } }); target.easyCompare.originalImagePromise(updateProgress); return originalCanvas; } } // Get diffed image function function getDiffedImage(target, base, $overlay) { if (target.src === base.src) { return getOriginalImage(target); } if (target.easyCompare && target.easyCompare[base.src]) { target.easyCompare[base.src].targetImage = target; target.easyCompare[base.src].baseImage = base; const diffedCanvas = target.easyCompare[base.src]; if (diffedCanvas.ready) { diffedCanvas.style.width = `${scale * 10}%`; reRenderImage(diffedCanvas, scale); } return diffedCanvas; } else { const diffedCanvas = makeCanvas(); drawText(diffedCanvas, 'Loading...'); diffedCanvas.ready = false; diffedCanvas.targetImage = target; diffedCanvas.baseImage = base; diffedCanvas.threshold = -1; diffedCanvas.step = 0.001; $overlay.append(diffedCanvas.parentElement); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare[base.src] = diffedCanvas; if (!base.easyCompare) { base.easyCompare = {}; } base.easyCompare[target.src] = diffedCanvas; let progress = [0, 0]; // Progress update function const updateProgress = (p, ind) => { if (p !== null && p >= 0 && ind !== null) { progress[ind] = p; drawText(diffedCanvas, `Loading ${((progress[0] + progress[1]) * 50).toFixed(1)}%`); } else if (p < 0) { drawText(diffedCanvas, 'Loading...'); } else { drawText(diffedCanvas, 'Diffing...'); } }; getOriginalImage(target, $overlay); getOriginalImage(base, $overlay); Promise.all([ target.easyCompare.originalImagePromise((p) => updateProgress(p, 0)), base.easyCompare.originalImagePromise((p) => updateProgress(p, 1)) ]).then(imageData => { updateProgress(null, null); return diffImage(...imageData, { alpha: 0.5, threshold: 0.007 }); }).then((diffedImageData) => { if (diffedImageData === null) { drawText(diffedCanvas, 'Sizes Not Match'); } else { drawImage(diffedCanvas, diffedImageData); diffedCanvas.ext = '.png'; diffedCanvas.threshold = 0.007; diffedCanvas.style.width = `${scale * 10}%`; reRenderImage(diffedCanvas, scale); diffedCanvas.ready = true; } }).catch((err) => { console.warn(err); drawText(diffedCanvas, 'Something Went Wrong'); }); return diffedCanvas; } } // Get filtered image function function getFilteredImage(target, ftType, $overlay) { if (target.easyCompare && target.easyCompare[ftType]) { const filteredCanvas = target.easyCompare[ftType]; if (filteredCanvas.ready) { filteredCanvas.style.width = `${scale * 10}%`; reRenderImage(filteredCanvas, scale); } return filteredCanvas; } else { const filteredCanvas = makeCanvas(); drawText(filteredCanvas, 'Loading...'); filteredCanvas.ready = false; filteredCanvas.targetImage = target; $overlay.append(filteredCanvas.parentElement); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare[ftType] = filteredCanvas; // Progress Update Function const updateProgress = (p) => { if (p !== null && p >= 0) { drawText(filteredCanvas, `Loading ${(p * 100).toFixed(1)}%`); } else if (p < 0) { drawText(filteredCanvas, 'Loading...'); } else { drawText(filteredCanvas, 'Filtering...'); } }; // Wait original image and filter the original image getOriginalImage(target, $overlay); target.easyCompare .originalImagePromise(updateProgress).then((imageData) => { updateProgress(null); return filterImage[ftType](imageData); }).then(filterdImageData => { drawImage(filteredCanvas, filterdImageData); filteredCanvas.ext = '.png'; filteredCanvas.style.width = `${scale * 10}%`; reRenderImage(filteredCanvas, scale); filteredCanvas.ready = true; }); return filteredCanvas; } } /*--- UI Response Functions ---*/ // Function to acquire active image function getActive($overlay) { return $overlay.find('canvas:visible'); } // Function fired when compare button is activated function activateCompare($target) { $target.attr({ 'fill': '#008000' }).css({ 'cursor': 'pointer', 'opacity': '1' })[0].state = true; } // Function fired when leaving image function leaveImage($overlay, target = undefined) { const original = getActive($overlay).hide()[0]; if (((original && (target = original.targetImage)) || target) && target.easyCompare && target.easyCompare.boxShadow !== undefined) { $(target).css('box-shadow', target.easyCompare.boxShadow); } } // Function fired when compare button is clicked and toggled on // (Main UI Logic) function enterCompare($overlay, $images, $message) { if (Mousetrap) { Mousetrap.pause(); } $overlay.show()[0].state = true; let colors = ['red', 'blue']; let step = 1, baseImage; let ftType = 'none'; let fadingTime = 300; // Mouse enter event $images.on('mouseenter.compare', (e, triggeredShiftKey) => { const target = e.currentTarget; clearTimeout(timeout); leaveImage($overlay); if (!target.easyCompare) { target.easyCompare = {}; target.easyCompare.boxShadow = target.style['box-shadow']; } $(target).css({ 'box-shadow': '0px 0px 8px ' + colors[0] }); let displayedImage; if ((e.shiftKey || triggeredShiftKey) && baseImage) { displayedImage = $(getDiffedImage(target, baseImage, $overlay)) .css('outline-color', colors[0]) .show(); } else { switch (ftType) { case 'none': displayedImage = $(getOriginalImage(target, $overlay)) .css('outline-color', colors[0]) .show(); break; default: displayedImage = $(getFilteredImage(target, ftType, $overlay)) .css('outline-color', colors[0]) .show(); break; } } colors.push(colors.shift()); //Mouse leave event }).on('mouseleave.compare', (e) => { const target = e.currentTarget; timeout = setTimeout(() => { leaveImage($overlay, target); }, 200); }); // KeyBoard functions function setBaseImage() { try { baseImage = getActive($overlay)[0].targetImage; } catch (err) { baseImage = undefined; if (!(err instanceof TypeError)) { console.warn(err); } } } function downloadImage(name = 'easycompare') { try { const target = getActive($overlay)[0]; const url = target.src || target.toDataURL('image/png').replace(/^data:image\/[^;]/, 'data:application/octet-stream'); const ext = target.ext || ''; GM_download({ url: url, name: name + ext }); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function toggleFilter(filter) { ftType = (ftType === filter ? 'none' : filter); try { const target = getActive($overlay).hide()[0]; let $displayImage; if (ftType === 'none') { $displayImage = $(getOriginalImage(target.targetImage, $overlay)); } else { $displayImage = $(getFilteredImage(target.targetImage, ftType, $overlay)); } $displayImage .css('outline-color', target.style['outline-color']) .show(); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function adjustView(up) { try { if (up && scale < 10) { scale = scale + 1; } else if (up && scale < 30) { scale = scale + 2; } else if (!up && scale > 10) { scale = scale - 2; } else if (!up && scale > 1) { scale = scale - 1; } const target = getActive($overlay)[0]; if (target.ready) { target.style.width = `${scale * 10}%`; reRenderImage(target, scale); } $message.text(`Zoom: ${parseInt(scale * 10)}%`).css('opacity', '1'); setTimeout(() => { $message.css('opacity', '0'); }, fadingTime); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function setView(scl) { try { if (scale !== scl) { scale = scl; const target = getActive($overlay)[0]; if (target.ready) { target.style.width = `${scale * 10}%`; reRenderImage(target, scale); } $message.text(`Zoom: ${parseInt(scale * 10)}%`).css('opacity', '1'); setTimeout(() => { $message.css('opacity', '0'); }, fadingTime); } } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function adjustThreshold(up) { try { const target = getActive($overlay)[0]; let threshold = target.threshold; if (threshold !== undefined && threshold >= 0) { const thresholdPrev = threshold; $message.text(`Threshold: ${thresholdPrev.toFixed(4)}`).css('opacity', '1'); if (up) { threshold += target.step; if (threshold > 1) { threshold = 1; } } else { threshold -= target.step; if (threshold < 0) { threshold = 0; } } target.threshold = -1; const [ baseCanvas, targetCanvas ] = [ target.baseImage.easyCompare.originalImage, target.targetImage.easyCompare.originalImage ]; diffImage( baseCanvas.getContext('2d').getImageData(0, 0, baseCanvas.width, baseCanvas.height), targetCanvas.getContext('2d').getImageData(0, 0, targetCanvas.width, targetCanvas.height), { alpha: 0.5, threshold: threshold } ).then((imageData) => { target.getContext('2d').putImageData(imageData, 0, 0); $message.text(`Threshold: ${threshold.toFixed(4)}`).css('opacity', '1'); setTimeout(() => { target.threshold = threshold; $message.css('opacity', '0'); }, fadingTime); }); } } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function adjustStep(left) { try { const target = getActive($overlay)[0]; let step = target.step; if (step) { if (left && step <= 0.1) { target.step = step * 10; } else if (left) { target.step = 1.0; } else if (!left && step >= 0.001) { target.step = step / 10; } else { target.step = 0.0001; } $message.text(`Step: ${target.step.toFixed(4)}`).css('opacity', '1'); setTimeout(() => $message.css('opacity', '0'), fadingTime); } } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } function clearCache() { try { leaveImage($overlay, getActive($overlay)[0].targetImage); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } $overlay.find('canvas').toArray().forEach(e => { const target = e.targetImage; delete target.easyCompare; e.parentElement.remove(); }); } function switchImage(left, shiftKey) { try { const targetImage = getActive($overlay)[0].targetImage; const index = $images.index(targetImage); leaveImage($overlay, targetImage); const nextElem = $images[left ? index - step : index + step] || $images[index]; $(nextElem).trigger('mouseenter', [shiftKey]); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } // Scroll and Keyboard event $(document).on('scroll.compare', (e) => { const temp = getActive($overlay)[0]; if (temp) { const $prev = $(temp.targetImage); if (!$prev.is(':hover')) { leaveImage($overlay, $prev[0]); $images.find('img:hover').trigger('mousenter'); } }// Hot-Keys }).on('keydown.compare', (e) => { e.preventDefault(); e.stopImmediatePropagation(); switch (e.key) { case 'Escape': exitCompare($overlay, $images); break; case 'Shift': setBaseImage(); break; case '+': case '=': if (e.ctrlKey) { adjustView(true); } break; case '-': case '_': if (e.ctrlKey) { adjustView(false); } break; case 'O': case 'o': if (e.ctrlKey) { setView(10); } break; case 'P': case 'p': if (e.ctrlKey) { setView(30); } break; case 'S': case 's': if (e.ctrlKey) { downloadImage(); } else { toggleFilter('solar'); } break; case 'A': case 'a': toggleFilter('s2lar'); break; case 'I': case 'i': if (e.ctrlKey) { setView(1); } else { adjustThreshold(true); } break; case 'ArrowUp': adjustThreshold(true); break; case 'K': case 'k': case 'ArrowDown': adjustThreshold(false); break; case 'J': case 'j': case 'ArrowLeft': adjustStep(true); break; case 'L': case 'l': case 'ArrowRight': if (e.ctrlKey) { clearCache(); } else { adjustStep(false); } break; case 'Q': case 'q': $overlay.css('opacity', 0.5); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': step = parseInt(e.key); break; case '0': step = 10; break; case 'W': case 'w': switchImage(true, e.shiftKey); break; case 'E': case 'e': switchImage(false, e.shiftKey); break; } return false; }).on('keyup.compare', (e) => { e.preventDefault(); e.stopImmediatePropagation(); switch (e.key) { case 'Q': case 'q': $overlay.css('opacity', ''); break; } return false; }); } // Function fired when compare button is clicked and toggled off // or quit via keyboard 'esc' function exitCompare($overlay, $images) { if (Mousetrap) { Mousetrap.unpause(); } leaveImage($overlay); $overlay.hide()[0].state = false; $images .off('mouseenter.compare') .off('mouseleave.compare'); $(document) .off('scroll.compare') .off('keydown.compare'); } /*--- Building Blocks ---*/ // A message on the whole page const $message = $('<div>').css({ 'top': '50%', 'left': '50%', 'z-index': 2147483647, 'position': 'fixed', 'transform': 'translate(-50%, -50%)', 'opacity': '0', 'vertical-align': 'middle', 'pointer-events': 'none', 'transition': 'all 0.1s', 'font-size': '500%', 'color': 'yellow', 'font-weight': 'bold' }); // An overlay on the whole page const $overlay = $('<div/>').css({ 'id': 'easy-compare-overlay', 'position': 'fixed', 'top': 0, 'right': 0, 'bottom': 0, 'left': 0, 'z-index': 2147483646, 'background-color': 'rgba(0, 0, 0, 0.75)', 'pointer-events': 'none', 'display': 'none' }).append($message); // The compare button const $compareButton = $(`<svg xmlns="http://www.w3.org/2000/svg"> <path id="ld" d="M20 6H10c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h10v4h4V2h-4v4zm0 30H10l10-12v12zM38 6H28v4h10v26L28 24v18h10c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4z"/> </svg>`).attr({ 'width': '30', 'height': '30', 'viewBox': '0 0 48 48', 'stroke': 'white', 'stroke-width': '5px', 'fill': 'gray' }).css({ 'position': 'fixed', 'top': '0px', 'right': '0px', 'padding': '15px', 'box-sizing': 'content-box', 'z-index': 2147483647, 'paint-order': 'stroke', 'opacity': 0, 'transition': 'all 0.2s', 'cursor': 'auto' }).on('mouseenter', (e) => { const $target = $(e.currentTarget); if ($target[0].manualFlag) { $target.attr({ 'fill': 'gray' }).css({ 'opacity': 0.2, 'pointer-events': 'none' }); $target[0].manualFlag = false; const clientWidth = document.documentElement.clientWidth; $(document).on('mousemove.compare', ({ clientX, clientY }) => { if (clientX < clientWidth - 61 || clientY > 61) { $target[0].insideFlag = 0; clearTimeout(timeout); $target.attr({ 'fill': 'gray' }).css({ 'cursor': 'auto', 'opacity': 0, 'pointer-events': 'auto' })[0].state = false; $(document).off('mousemove.compare'); $target[0].manualFlag = true; } else if (clientX >= clientWidth - 45 && clientX <= clientWidth - 15 && clientY >= 15 && clientY <= 45) { if (!$target[0].insideFlag) { $target[0].insideFlag = 1; timeout = setTimeout(() => { activateCompare($target); $target.css({ 'pointer-events': 'auto' }); }, $overlay[0].state ? 0 : 1000); } } else if (clientX < clientWidth - 45 || clientX > clientWidth - 15 || clientY < 15 || clientY > 45) { $target[0].insideFlag = 0; clearTimeout(timeout); $target.attr({ 'fill': 'gray' }).css({ 'cursor': 'auto', 'opacity': 0.2, 'pointer-events': 'none' })[0].state = false; } }); } }).click((e) => { if (e.currentTarget.state) { switch ($overlay[0].state) { case false: enterCompare($overlay, $(':not("#easy-compare-overlay") img:visible'), $message); break; case true: exitCompare($overlay, $(':not("#easy-compare-overlay") img:visible')); break; } } else { let x = e.clientX; let y = e.clientY; const lowerElement = document .elementsFromPoint(x, y) .find(e => !['svg', 'path'].includes(e.tagName)); lowerElement.click(); } }).mousedown((e) => { if (e.currentTarget.state) { $(e.currentTarget).attr({ 'fill': '#006000' }); } }).mouseup((e) => { if (e.currentTarget.state) { $(e.currentTarget).attr({ 'fill': '#008000' }); } }); $compareButton[0].manualFlag = true; $compareButton[0].insideFlag = false; /*--- Insert to Document ---*/ $overlay[0].state = false; $compareButton[0].state = false; $('body').append($compareButton).append($overlay); })(window.$.noConflict(true), unsafeWindow.Mousetrap, window.pixelmatch, unsafeWindow.URL.createObjectURL ? unsafeWindow.URL : unsafeWindow.webkitURL);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址