- // ==UserScript==
- // @name Easy Compare
- // @description Compare images
- // @version 0.9.5
- // @author Secant (TYT@NexusHD)
- // @license GPL-3.0-or-later
- // @supportURL zzwu@zju.edu.cn
- // @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://greasyfork.org/scripts/401377-pixelmatch/code/pixelmatch.js
- // @resource PixelMatchCore https://greasyfork.org/scripts/401377-pixelmatch/code/pixelmatch.js
- // @namespace https://greasyfork.org/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);