您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Compare images
当前为
// ==UserScript== // @name Easy Compare // @description Compare images // @version 0.3 // @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://bundle.run/[email protected] // /require https://cdn.staticfile.org/pako/1.0.10/pako.min.js // /require https://cdn.staticfile.org/upng-js/2.1.0/UPNG.min.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 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 * // ==/UserScript== // # TODO List // ☑ guess original images from hyper link // ☑ redirect url chopper: deferer, anonymouse // ☑ image diff by hold "Shift" and switch to another image: https://bundle.run/[email protected] // ☑ solar curve filter toggled by "s": see bandings clearly // ☑ save current active image by "ctrl + s" // ☑ clear caches by "ctrl + l" // ☐ canvasless (async/sync) // ☐ more sites support // ☐ other filters? // ☐ webgl acceleration (webgl in worker?) // jshint esversion:8 (async function ($, Mousetrap, pixelmatch, UPNG, URL) { 'use strict'; // Solar Curve; const [Rc, Gc, Bc] = [ new Uint8Array([128, 153, 177, 199, 218, 233, 245, 252, 255, 253, 248, 238, 225, 209, 190, 170, 148, 126, 105, 84, 65, 47, 32, 20, 10, 4, 0, 0, 3, 9, 18, 29, 42, 57, 73, 91, 109, 127, 145, 163, 179, 195, 209, 222, 232, 241, 248, 252, 255, 255, 253, 249, 243, 236, 227, 216, 205, 192, 178, 164, 150, 135, 120, 106, 92, 78, 65, 54, 43, 33, 24, 17, 11, 6, 3, 1, 0, 1, 3, 6, 11, 17, 23, 31, 40, 49, 59, 70, 81, 93, 104, 116, 128, 140, 152, 163, 174, 185, 195, 204, 213, 221, 228, 235, 241, 245, 249, 252, 254, 255, 255, 254, 252, 249, 246, 241, 236, 230, 223, 215, 207, 198, 189, 180, 170, 159, 149, 138, 128, 117, 106, 96, 85, 75, 66, 57, 48, 40, 32, 25, 19, 14, 9, 6, 3, 1, 0, 0, 1, 3, 6, 10, 14, 20, 27, 34, 42, 51, 60, 70, 81, 92, 103, 115, 127, 139, 151, 162, 174, 185, 196, 206, 215, 224, 232, 238, 244, 249, 252, 254, 255, 254, 252, 249, 244, 238, 231, 222, 212, 201, 190, 177, 163, 149, 135, 120, 105, 91, 77, 63, 50, 39, 28, 19, 12, 6, 2, 0, 0, 3, 7, 14, 23, 33, 46, 60, 76, 92, 110, 128, 146, 164, 182, 198, 213, 226, 237, 246, 252, 255, 255, 251, 245, 235, 223, 208, 190, 171, 150, 129, 107, 85, 65, 46, 30, 17, 7, 2, 0, 3, 10, 22, 37, 56, 78, 102]), new Uint8Array([19, 35, 55, 77, 102, 128, 153, 177, 199, 218, 233, 245, 252, 255, 253, 248, 238, 225, 209, 190, 170, 148, 126, 105, 84, 65, 47, 32, 20, 10, 4, 0, 0, 3, 9, 18, 29, 42, 57, 73, 91, 109, 127, 145, 163, 179, 195, 209, 222, 232, 241, 248, 252, 255, 255, 253, 249, 243, 236, 227, 216, 205, 192, 178, 164, 150, 135, 120, 106, 92, 78, 65, 54, 43, 33, 24, 17, 11, 6, 3, 1, 0, 1, 3, 6, 11, 17, 23, 31, 40, 49, 59, 70, 81, 93, 104, 116, 128, 140, 152, 163, 174, 185, 195, 204, 213, 221, 228, 235, 241, 245, 249, 252, 254, 255, 255, 254, 252, 249, 246, 241, 236, 230, 223, 215, 207, 198, 189, 180, 170, 159, 149, 138, 128, 117, 106, 96, 85, 75, 66, 57, 48, 40, 32, 25, 19, 14, 9, 6, 3, 1, 0, 0, 1, 3, 6, 10, 14, 20, 27, 34, 42, 51, 60, 70, 81, 92, 103, 115, 127, 139, 151, 162, 174, 185, 196, 206, 215, 224, 232, 238, 244, 249, 252, 254, 255, 254, 252, 249, 244, 238, 231, 222, 212, 201, 190, 177, 163, 149, 135, 120, 105, 91, 77, 63, 50, 39, 28, 19, 12, 6, 2, 0, 0, 3, 7, 14, 23, 33, 46, 60, 76, 92, 110, 128, 146, 164, 182, 198, 213, 226, 237, 246, 252, 255, 255, 251, 245, 235, 223, 208, 190, 171, 150, 129, 107, 85, 65, 46, 30, 17, 7, 2, 0, 3, 10]), new Uint8Array([233, 245, 252, 255, 253, 248, 238, 225, 209, 190, 170, 148, 126, 105, 84, 65, 47, 32, 20, 10, 4, 0, 0, 3, 9, 18, 29, 42, 57, 73, 91, 109, 127, 145, 163, 179, 195, 209, 222, 232, 241, 248, 252, 255, 255, 253, 249, 243, 236, 227, 216, 205, 192, 178, 164, 150, 135, 120, 106, 92, 78, 65, 54, 43, 33, 24, 17, 11, 6, 3, 1, 0, 1, 3, 6, 11, 17, 23, 31, 40, 49, 59, 70, 81, 93, 104, 116, 128, 140, 152, 163, 174, 185, 195, 204, 213, 221, 228, 235, 241, 245, 249, 252, 254, 255, 255, 254, 252, 249, 246, 241, 236, 230, 223, 215, 207, 198, 189, 180, 170, 159, 149, 138, 128, 117, 106, 96, 85, 75, 66, 57, 48, 40, 32, 25, 19, 14, 9, 6, 3, 1, 0, 0, 1, 3, 6, 10, 14, 20, 27, 34, 42, 51, 60, 70, 81, 92, 103, 115, 127, 139, 151, 162, 174, 185, 196, 206, 215, 224, 232, 238, 244, 249, 252, 254, 255, 254, 252, 249, 244, 238, 231, 222, 212, 201, 190, 177, 163, 149, 135, 120, 105, 91, 77, 63, 50, 39, 28, 19, 12, 6, 2, 0, 0, 3, 7, 14, 23, 33, 46, 60, 76, 92, 110, 128, 146, 164, 182, 198, 213, 226, 237, 246, 252, 255, 255, 251, 245, 235, 223, 208, 190, 171, 150, 129, 107, 85, 65, 46, 30, 17, 7, 2, 0, 3, 10, 22, 37, 56, 78, 102, 127, 153, 178, 200, 220]) ]; async function loadBuffer(worker) { return new Promise((resolve) => { rainbowWorker.onmessage = (e) => { resolve(e.data.result); }; rainbowWorker.postMessage({ Rc: Rc.buffer, Gc: Gc.buffer, Bc: Bc.buffer }, [Rc.buffer, Gc.buffer, Bc.buffer]); }); } // Diff, Rainbow Worker Initialization let diffWorker, rainbowWorker; const diffWorkerScript = `const defaultOptions={threshold:.1,includeAA:!1,alpha:.1,aaColor:[255,255,0],diffColor:[255,0,0],diffMask:!1};function pixelmatch(a,b,c,d,e,f){if(!isPixelData(a)||!isPixelData(b)||c&&!isPixelData(c))throw new Error("Image data: Uint8Array, Uint8ClampedArray or Buffer expected.");if(a.length!==b.length||c&&c.length!==a.length)throw new Error("Image sizes do not match.");if(a.length!==4*(d*e))throw new Error("Image data size does not match width/height.");f=Object.assign({},defaultOptions,f);const g=d*e,h=new Uint32Array(a.buffer,a.byteOffset,g),j=new Uint32Array(b.buffer,b.byteOffset,g);let k=!0;for(let l=0;l<g;l++)if(h[l]!==j[l]){k=!1;break}if(k){if(c&&!f.diffMask)for(let b=0;b<g;b++)drawGrayPixel(a,4*b,f.alpha,c);return 0}const l=35215*f.threshold*f.threshold;let m=0;const[n,o,p]=f.aaColor,[q,r,s]=f.diffColor;for(let g=0;g<e;g++)for(let h=0;h<d;h++){const i=4*(g*d+h),j=colorDelta(a,b,i,i);j>l?!f.includeAA&&(antialiased(a,h,g,d,e,b)||antialiased(b,h,g,d,e,a))?c&&!f.diffMask&&drawPixel(c,i,n,o,p):(c&&drawPixel(c,i,q,r,s),m++):c&&!f.diffMask&&drawGrayPixel(a,i,f.alpha,c)}return m}function isPixelData(a){return ArrayBuffer.isView(a)&&1===a.constructor.BYTES_PER_ELEMENT}function antialiased(a,b,c,d,e,f){const g=Math.max(b-1,0),h=Math.max(c-1,0),i=Math.min(b+1,d-1),j=Math.min(c+1,e-1);let k,l,m,n,o=b===g||b===i||c===h||c===j?1:0,p=0,q=0;for(let r=g;r<=i;r++)for(let e=h;e<=j;e++){if(r===b&&e===c)continue;const f=colorDelta(a,a,4*(c*d+b),4*(e*d+r),!0);if(0!==f)f<p?(p=f,k=r,l=e):f>q&&(q=f,m=r,n=e);else if(o++,2<o)return!1}return 0!==p&&0!==q&&(hasManySiblings(a,k,l,d,e)&&hasManySiblings(f,k,l,d,e)||hasManySiblings(a,m,n,d,e)&&hasManySiblings(f,m,n,d,e))}function hasManySiblings(a,b,c,d,e){const f=Math.max(b-1,0),g=Math.max(c-1,0),h=Math.min(b+1,d-1),i=Math.min(c+1,e-1),j=4*(c*d+b);let k=b===f||b===h||c===g||c===i?1:0;for(let l=f;l<=h;l++)for(let e=g;e<=i;e++){if(l===b&&e===c)continue;const f=4*(e*d+l);if(a[j]===a[f]&&a[j+1]===a[f+1]&&a[j+2]===a[f+2]&&a[j+3]===a[f+3]&&k++,2<k)return!0}return!1}function colorDelta(a,b,c,d,e){let f=a[c+0],g=a[c+1],h=a[c+2],j=a[c+3],k=b[d+0],l=b[d+1],m=b[d+2],n=b[d+3];if(j===n&&f===k&&g===l&&h===m)return 0;255>j&&(j/=255,f=blend(f,j),g=blend(g,j),h=blend(h,j)),255>n&&(n/=255,k=blend(k,n),l=blend(l,n),m=blend(m,n));const o=rgb2y(f,g,h)-rgb2y(k,l,m);if(e)return o;const p=rgb2i(f,g,h)-rgb2i(k,l,m),i=rgb2q(f,g,h)-rgb2q(k,l,m);return .5053*o*o+.299*p*p+.1957*i*i}function rgb2y(a,c,d){return .29889531*a+.58662247*c+.11448223*d}function rgb2i(a,c,d){return .59597799*a-.2741761*c-.32180189*d}function rgb2q(a,c,d){return .21147017*a-.52261711*c+.31114694*d}function blend(b,c){return 255+(b-255)*c}function drawPixel(a,c,d,e,f){a[c+0]=d,a[c+1]=e,a[c+2]=f,a[c+3]=255}function drawGrayPixel(a,c,d,e){const f=a[c+0],h=a[c+1],g=a[c+2],b=blend(rgb2y(f,h,g),d*a[c+3]/255);drawPixel(e,c,b,b,b)}self.onmessage=a=>{img1=new Uint8ClampedArray(a.data.img1),img2=new Uint8ClampedArray(a.data.img2),diff=new Uint8ClampedArray(img1),width=a.data.width,height=a.data.height,init=a.data.init,key=a.data.key;try{pixelmatch(img1,img2,diff,width,height,init),self.postMessage({diff:diff.buffer,width:width,height:height,key:key},[diff.buffer])}catch(a){console.warn(a),self.postMessage({diff:null,key:key})}};`; const rainbowWorkerScript = `let Rc,Gc,Bc;self.onmessage=a=>{const b=a.data.key;if(a.data.Rc&&a.data.Gc&&a.data.Bc)Rc=new Uint8ClampedArray(a.data.Rc),Gc=new Uint8ClampedArray(a.data.Gc),Bc=new Uint8ClampedArray(a.data.Bc),self.postMessage({result:!0});else{const c=new Uint8ClampedArray(a.data.img),d=new Uint8ClampedArray(c),e=a.data.width,f=a.data.height;try{for(let a=0;a<f;++a)for(let b,f=0;f<e;++f)b=4*f+4*(a*e),d[b]=Rc[c[b]],d[b+1]=Gc[c[b+1]],d[b+2]=Bc[c[b+2]],d[b+3]=c[b+3];self.postMessage({filter:d.buffer,width:e,height:f,key:b},[d.buffer])}catch(a){console.warn(a),self.postMessage({filter:null,key:b})}}};`; try { const diffWorkerBlob = new Blob([diffWorkerScript], { type: 'application/javascript' }); diffWorker = new Worker(URL.createObjectURL(diffWorkerBlob)); diffWorker.keyPool = {}; URL.revokeObjectURL(diffWorkerBlob); const rainbowWorkerBlob = new Blob([rainbowWorkerScript], { type: 'application/javascript' }); rainbowWorker = new Worker(URL.createObjectURL(rainbowWorkerBlob)); rainbowWorker.keyPool = {}; URL.revokeObjectURL(rainbowWorkerBlob); await loadBuffer(rainbowWorker); } catch (e) { try { const diffWorkerDataURI = `data:application/javascript,${encodeURIComponent(diffWorkerScript)}`; diffWorker = new Worker(diffWorkerDataURI); diffWorker.keyPool = {}; const rainbowWorkerDataURI = `data:application/javascript,${encodeURIComponent(rainbowWorkerScript)}`; rainbowWorker = new Worker(rainbowWorkerDataURI); rainbowWorker.keyPool = {}; await loadBuffer(rainbowWorker); } catch (e) { diffWorker = null; rainbowWorker = null; } } // Title: Mousetrap Pause Plugin // Reference: https://github.com/ccampbell/mousetrap/tree/master/plugins/pause 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; }; } // A global timeout ID holder let timeout; // 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 ]; // Skip redirections const skipRedirLib = [ [/^https?:\/\/anonym\.to\/\?(.*)$/, (_, p1) => decodeURIComponent(p1)], [/^https?:\/\/www\.dereferer\.org\/\?(.*)$/, (_, p1) => decodeURIComponent(p1)] ]; // Probable original image selectors on a view page const guessSelectorLib = [ '#image-viewer-container>img', '.image-container img', 'div.img.big>img', 'img.mainimage', 'img#img' ]; // 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); } }); }); } // Get image uint8 array buffer async function getImageBytesBuffer(src, fn) { const imageArrayBuffer = await new Promise((resolve) => { GM_xmlhttpRequest({ url: src, method: 'GET', responseType: 'arraybuffer', onprogress: (e) => { if (e.total !== -1) { fn(e.loaded / e.total); } else { fn(-e.loaded); } }, onload: (e) => { if (e.status === 200) { resolve(e.response); } else { console.warn(e); resolve(null); } }, onerror: (e) => { console.warn(e); resolve(null); } }); }); if (imageArrayBuffer) { /* const upngObj = UPNG.decode(imageArrayBuffer); console.log(upngObj); if(upngObj.data) { return { raw: upngObj.data, width: upngObj.width, height: upngObj.height }; } else { return null; } */ return new Promise(async (resolve) => { const url = await new Promise((resolve) => { const fr = new FileReader(); fr.onload = e => resolve(fr.result); fr.readAsDataURL(new Blob([new Uint8Array(imageArrayBuffer)], { type: 'image/png' })); }); const img = new Image(); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); img.onload = function () { const [width, height] = [this.width, this.height]; canvas.width = width; canvas.height = height; context.drawImage(this, 0, 0, width, height); resolve({ raw: context.getImageData(0, 0, width, height).data.buffer, width: width, height: height }); }; img.onerror = function () { resolve(null); }; img.src = url; }); } else { return null; } } function solarCurve(raw, filter, width, height) { for (let row = 0; row < height; ++row) { for (let col = 0; col < width; ++col) { let ind = col * 4 + row * width * 4; filter[ind] = Rc[raw[ind]]; filter[ind + 1] = Gc[raw[ind + 1]]; filter[ind + 2] = Bc[raw[ind + 2]]; filter[ind + 3] = raw[ind + 3]; } } } async function rainbowImage(src, onprogress, worker = rainbowWorker) { const img = await getImageBytesBuffer(src, onprogress); if (img) { onprogress(null); if (worker) { const [raw, width, height] = [img.raw, img.width, img.height]; const key = '' + Date.now(); worker.onmessage = (e) => { const returnKey = e.data.key; const resolve = worker.keyPool[returnKey]; if (resolve) { const canvas = document.createElement('canvas'); const [width, height] = [e.data.width, e.data.height]; [canvas.width, canvas.height] = [width, height]; const context = canvas.getContext('2d'); context.putImageData(new ImageData( new Uint8ClampedArray(e.data.filter), width, height ), 0, 0); canvas.toBlob((blob) => { resolve(URL.createObjectURL(blob)); delete worker.keyPool[returnKey]; }, 'image/png', 1); } }; worker.postMessage({ img: raw, width: width, height: height, key: key }, [raw]); return new Promise((res) => { worker.keyPool[key] = res; }); } else { const [raw, width, height] = [new Uint8ClampedArray(img.raw), img.width, img.height]; const canvas = document.createElement('canvas'); [canvas.width, canvas.height] = [width, height]; const context = canvas.getContext('2d'); const rainbow = context.createImageData(width, height); solarCurve(raw, rainbow.data, width, height); context.putImageData(rainbow, 0, 0); return new Promise((resolve) => { canvas.toBlob((blob) => { resolve(URL.createObjectURL(blob)); }, 'image/png', 1); }); } } } // Diff images (async or sync) // input: src1, src2, on progress function, pixelmatch initilization object, web worker // ouput: diffsrc (dataURL) async function diffImage(src1, src2, onprogress, init = { alpha: 0.5, threshold: 0.007 }, worker = diffWorker) { const [img1, img2] = await Promise.all([ getImageBytesBuffer(src1, (p) => onprogress(p, 0)), getImageBytesBuffer(src2, (p) => onprogress(p, 1)) ]); if ( img1 && img2 && img1.width === img2.width && img1.height === img2.height ) { onprogress(null, null); if (worker) {// async diff const [raw1, raw2, width, height] = [img1.raw, img2.raw, img1.width, img1.height]; const key = '' + Date.now(); worker.onmessage = (e) => { const returnKey = e.data.key; const resolve = worker.keyPool[returnKey]; if (resolve) { const canvas = document.createElement('canvas'); const [width, height] = [e.data.width, e.data.height]; [canvas.width, canvas.height] = [width, height]; const context = canvas.getContext('2d'); context.putImageData(new ImageData( new Uint8ClampedArray(e.data.diff), width, height ), 0, 0); canvas.toBlob((blob) => { resolve(URL.createObjectURL(blob)); delete worker.keyPool[returnKey]; }, 'image/png', 1); } }; 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 [raw1, raw2, width, height] = [ new Uint8ClampedArray(img1.raw), new Uint8ClampedArray(img2.raw), img1.width, img1.height ]; const canvas = document.createElement('canvas'); [canvas.width, canvas.height] = [width, height]; const context = canvas.getContext('2d'); const diff = context.createImageData(width, height); pixelmatch(raw1, raw2, diff.data, width, height, init); context.putImageData(diff, 0, 0); return new Promise((resolve) => { canvas.toBlob((blob) => { resolve(URL.createObjectURL(blob)); }, 'image/png', 1); }); } } else { return null; } } // Virtual DOM for selection without fetching images function $$(htmlString) { return $(htmlString, document.implementation.createHTMLDocument('virtual')); } // Convert text to SVG image function text2SVGDataURL(text, width, height = 20) { return `data:image/svg+xml,${ encodeURIComponent( `<svg xmlns='http://www.w3.org/2000/svg' height="${height}" width="${width}"><text x="0" y="15" fill="white">${text}</text></svg>` )}`; } // Function to make an <img/> element function makeImage(src, outlineColor = 'red') { return $(`<img src="${src}"/>`).attr({ 'class': 'easy-compare-image' }).css({ 'display': 'none', 'top': '50%', 'left': '50%', 'position': 'fixed', 'transform': 'translate(-50%, -50%)', 'opacity': '1', 'outline': '3px solid ' + outlineColor, 'outline-offset': '2px', 'vertical-align': 'middle' }); } // 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 = $overlay.find('img:visible').hide()[0]; if ((target || (original && (target = original.targetImage))) && target.easyCompare && target.easyCompare.boxShadow !== undefined) { $(target).css('box-shadow', target.easyCompare.boxShadow); } } const filterImage = { 'rainbow': rainbowImage }; function getOriginalImage(target, $overlay) { if (target.easyCompare && target.easyCompare.originalImage) { return (target.easyCompare.originalImage); } else { const originalImage = makeImage(text2SVGDataURL(`Loading...`, 80))[0]; originalImage.targetImage = target; $overlay.append(originalImage); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare.originalImage = originalImage; target.easyCompare.originalImagePromise = new Promise((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) { originalImage.src = realSrc; resolve(originalImage); } } // Guess original src from hyper link let href, hrefOriginal; if ((hrefOriginal = target.parentElement.href, href = hrefOriginal) && !href.match(/\.png$|\.jpe?g$|\.webp$/)) { for (let pairs of skipRedirLib) { href = href.replace(pairs[0], pairs[1]); if (href !== hrefOriginal) { guessOriginalImage(href).then(src => { originalImage.src = src || realSrc; resolve(originalImage); }); break; } } } else if (href && href.match(/\.png$|\.jpe?g$|\.webp$/)) { originalImage.src = href; resolve(originalImage); } else { originalImage.src = realSrc; resolve(originalImage); } }); return originalImage; } } 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; return target.easyCompare[base.src]; } else { const diffedImage = makeImage(text2SVGDataURL(`Loading ...`, 80))[0]; diffedImage.targetImage = target; diffedImage.baseImage = base; diffedImage.threshold = -1; diffedImage.step = 0.001; $overlay.append(diffedImage); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare[base.src] = diffedImage; if (!base.easyCompare) { base.easyCompare = {}; } base.easyCompare[target.src] = diffedImage; let progress = [0, 0]; // Progress update function const updateProgress = (p, ind) => { if (p !== null && p >= 0 && ind !== null) { progress[ind] = p; diffedImage.src = text2SVGDataURL(`Loading ${((progress[0] + progress[1]) * 50).toFixed(1)}%`, 120); } else if (p < 0) { diffedImage.src = text2SVGDataURL(`Loading...`, 80); } else { diffedImage.src = text2SVGDataURL(`Diffing...`, 80); } }; getOriginalImage(target, $overlay); getOriginalImage(base, $overlay); Promise.all([ target.easyCompare.originalImagePromise, base.easyCompare.originalImagePromise ]).then(([{ src: src1 }, { src: src2 }]) => diffImage(src1, src2, updateProgress, { alpha: 0.5, threshold: 0.007 })).then((diffedSrc) => { if (diffedSrc === null) { diffedImage.src = text2SVGDataURL(`Sizes Not Match`, 120); } else { diffedImage.src = diffedSrc; diffedImage.threshold = 0.007; } }).catch((err) => { console.warn(err); diffedImage.src = text2SVGDataURL(`Sth. Went Wrong`, 120); }); return diffedImage; } } function getFilteredImage(target, ftType, $overlay) { if (target.easyCompare && target.easyCompare[ftType]) { return target.easyCompare[ftType]; } else { const filteredImage = makeImage(text2SVGDataURL(`Loading...`, 80))[0]; filteredImage.targetImage = target; $overlay.append(filteredImage); if (!target.easyCompare) { target.easyCompare = {}; } target.easyCompare[ftType] = filteredImage; // Progress Update Function const updateProgress = (p) => { if (p !== null && p >= 0) { filteredImage.src = text2SVGDataURL(`Loading ${(p * 100).toFixed(1)}%`, 120); } else if (p < 0) { filteredImage.src = text2SVGDataURL(`Loading...`, 80); } else { filteredImage.src = text2SVGDataURL(`Filtering...`, 80); } }; // Wait original image and filter the original image getOriginalImage(target, $overlay); target.easyCompare.originalImagePromise.then(originalImage => { filterImage[ftType](originalImage.src, updateProgress).then(filterdSrc => { filteredImage.src = filterdSrc; }); }); return filteredImage; } } // Function fired when compare button is clicked and toggled on 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'; // 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); }); // Scroll event $(document).on('scroll.compare', (e) => { const temp = $overlay.find('img:visible')[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': try { const index = $images.index($overlay.find('img:visible')[0].targetImage); baseImage = $images[index]; } catch (err) { baseImage = undefined; if (!(err instanceof TypeError)) { console.warn(err); } } break; case 'S': case 's': if (e.ctrlKey) { try { const target = $overlay.find('img:visible')[0]; GM_download({ url: target.src, name: 'easycompare.png', onerror: (e) => { if (e.error === 'Invalid scheme') { const a = document.createElement('a'); a.href = target.src; a.download = 'easycompare.png'; a.click(); } } }); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } } else { ftType = (ftType === 'rainbow' ? 'none' : 'rainbow'); try { const target = $overlay.find('img:visible').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); } } } break; case 'I': case 'i': case 'ArrowUp': try { const target = $overlay.find('img:visible')[0]; let threshold = target.threshold; if (threshold !== undefined && threshold >= 0) { const thresholdPrev = threshold; $message.text(`Threshold: ${thresholdPrev.toFixed(4)}`).css('opacity', '1'); threshold += target.step; if (threshold > 1) { threshold = 1; } target.threshold = -1; diffImage(target.baseImage.easyCompare.originalImage.src, target.targetImage.easyCompare.originalImage.src, (a, b) => { }, { alpha: 0.5, threshold: threshold }) .then((diffSrc) => { let temp; if (diffSrc === null) { target.src = text2SVGDataURL(`Sizes Not Match`, 120); temp = thresholdPrev; setTimeout(() => { target.threshold = thresholdPrev; }, 300); } else { target.src = diffSrc; temp = threshold; setTimeout(() => { target.threshold = threshold; }, 300); } $message.text(`Threshold: ${temp.toFixed(4)}`).css('opacity', '1'); setTimeout(() => $message.css('opacity', '0'), 300); }); } } catch (err) { console.warn(err); } break; case 'K': case 'k': case 'ArrowDown': try { const target = $overlay.find('img:visible')[0]; let threshold = target.threshold; if (threshold !== undefined && threshold >= 0) { const thresholdPrev = threshold; $message.text(`Threshold: ${thresholdPrev.toFixed(4)}`).css('opacity', '1'); threshold -= target.step; if (threshold < 0) { threshold = 0; } target.threshold = -1; diffImage(target.baseImage.easyCompare.originalImage.src, target.targetImage.easyCompare.originalImage.src, (a, b) => { }, { alpha: 0.5, threshold: threshold }) .then((diffSrc) => { let temp; if (diffSrc === null) { target.src = text2SVGDataURL(`Sizes Not Match`, 120); temp = thresholdPrev; setTimeout(() => { target.threshold = thresholdPrev; }, 300); } else { target.src = diffSrc; temp = threshold; setTimeout(() => { target.threshold = threshold; }, 300); } $message.text(`Threshold: ${temp.toFixed(4)}`).css('opacity', '1'); setTimeout(() => $message.css('opacity', '0'), 300); }); } } catch (err) { console.warn(err); } break; case 'J': case 'j': case 'ArrowLeft': try { const target = $overlay.find('img:visible')[0]; switch (target.step) { case 0.0001: target.step = 0.001; break; case 0.001: target.step = 0.01; break; case 0.01: target.step = 0.1; break; case 0.1: target.step = 1.0; break; default: break; } if (target.step) { $message.text(`Step: ${target.step.toFixed(4)}`).css('opacity', '1'); setTimeout(() => $message.css('opacity', '0'), 300); } } catch (err) { console.warn(err); } break; case 'L': case 'l': case 'ArrowRight': if (e.ctrlKey) { try { leaveImage($overlay, $overlay.find('img:visible')[0].targetImage); } catch (err) { if (!(err instanceof TypeError)) { console.warn(err); } } $overlay.find('img').toArray().forEach(e => { const target = e.targetImage; delete target.easyCompare; URL.revokeObjectURL(e.src); e.remove(); }); } else { try { const target = $overlay.find('img:visible')[0]; switch (target.step) { case 1.0: target.step = 0.1; break; case 0.1: target.step = 0.01; break; case 0.01: target.step = 0.001; break; case 0.001: target.step = 0.0001; break; default: break; } if (target.step) { $message.text(`Step: ${target.step.toFixed(4)}`).css('opacity', '1'); setTimeout(() => $message.css('opacity', '0'), 300); } } catch (err) { console.warn(err); } } 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 'E': case 'e': try { const targetImage = $overlay.find('img:visible')[0].targetImage; const index = $images.index(targetImage); leaveImage($overlay, targetImage); const nextElem = $images[index + step] || $images[index]; $(nextElem).trigger('mouseenter', [e.shiftKey]); } catch (err) { console.warn(err); } break; case 'W': case 'w': try { const targetImage = $overlay.find('img:visible')[0].targetImage; const index = $images.index(targetImage); leaveImage($overlay, targetImage); const nextElem = $images[index - step] || $images[index]; $(nextElem).trigger('mouseenter', [e.shiftKey]); } catch (err) { console.warn(err); } 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'); } // An overlay on the whole page const $overlay = $('<div/>').css({ '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' }); // 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' }); $overlay.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': '15px', 'right': '15px', 'z-index': 2147483647, 'paint-order': 'stroke', 'opacity': 0, 'transition': 'all 0.2s', 'cursor': 'auto' }).on('mouseenter', (e) => { $(e.currentTarget).attr({ 'fill': 'gray' }).css({ 'opacity': 0.2 }); timeout = setTimeout(() => activateCompare($(e.currentTarget)), $overlay[0].state ? 0 : 1000); }).on('mouseleave', (e) => { clearTimeout(timeout); $(e.currentTarget).attr({ 'fill': 'gray' }).css({ 'cursor': 'auto', 'opacity': 0 })[0].state = false; }).click((e) => { if (e.currentTarget.state) { switch ($overlay[0].state) { case false: enterCompare($overlay, $('img:visible:not(.easy-compare-image)'), $message); break; case true: exitCompare($overlay, $('img:visible:not(.easy-compare-image)')); 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' }); } }); $overlay[0].state = false; $compareButton[0].state = false; $('body').append($compareButton).append($overlay); })(window.$.noConflict(true), unsafeWindow.Mousetrap, window.pixelmatch, window.UPNG, unsafeWindow.URL.createObjectURL ? unsafeWindow.URL : unsafeWindow.webkitURL);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址