Easy Compare

Compare images

目前為 2020-03-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name               Easy Compare
// @description        Compare images
// @version            0.1
// @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
// @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              unsafewindow
// ==/UserScript==

// TODO List
// - image diff
// - more sites support
// - guess images
// - redirect url chopper

(function ($, Mousetrap) {
  'use strict';
  // 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 replaceLib = [
    [/\.thumb\.jpe?g$/, ''], // nexusphp
    [/\.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
  ];
  // Guess original image selectors on a view page
  const guessSelectorLib = [
    'img#img',
    '.image-container>img'
  ];
  // virtual DOM for selection without fetching images
  function $$(htmlString) {
    return $(htmlString, document.implementation.createHTMLDocument('virtual'));
  }
  //<svg height="20" width="20"><text x="0" y="15" fill="white">sample text</text></svg>
  function guessOriginalImage(url, src) {
    return new Promise((resolve) => {
      GM_xmlhttpRequest({
        url: url,
        method: 'GET',
        timeout: 6000,
        onload: (x) => {
          if (x.status === 200) {
            try {
              const $e = $$(x.responseText);
              resolve($e.find(guessSelectorLib.join(','))[0].src);
            }
            catch (e) {
              console.warn(e);
              resolve(src);
            }
          }
          else {
            console.warn(x);
            resolve(src);
          }
        },
        ontimeout: (e) => {
          console.warn(e);
          resolve(src);
        }
      });
    });
  }
  // Function to make an <img/> element
  function makeImage(src, outlineColor = 'red') {
    return $(`<img src="${src}"/>`).css({
      'display': 'none',
      'top': '50%',
      'left': '50%',
      'position': 'fixed',
      'transform': 'translate(-50%, -50%)',
      'opacity': '1',
      'outline': '3px solid ' + outlineColor,
      '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 compare button is clicked and toggled on
  function enterCompare($overlay, $images) {
    if (Mousetrap) {
      Mousetrap.pause();
    }
    $overlay.show()[0].state = true;
    let colors = ['red', 'blue'];
    let step = 1;
    $images.on('mouseenter.compare', (e) => {
      const target = e.currentTarget;
      clearTimeout(timeout);
      $overlay.find('img:visible').hide();
      if (!target.originalImage) {
        const targetSrc = target.src;
        let realSrc = targetSrc;
        for (let pairs of replaceLib) {
          realSrc = realSrc.replace(pairs[0], pairs[1]);
          if (realSrc !== targetSrc) {
            break;
          }
        }
        /*
        if (realSrc === targetSrc) {
          const $parent = $(target.parentElement);
        }
        */
        const $originalImage = makeImage(realSrc, colors[0]);
        $originalImage[0].targetImage = target;
        $overlay.append($originalImage);
        target.originalImage = $originalImage[0];
      }
      else {
        $(target.originalImage).css({
          'outline-color': colors[0]
        });
      }
      $(target.originalImage).show();
      colors.push(colors.shift());
    }).on('mouseleave.compare', (e) => {
      const target = e.currentTarget;
      timeout = setTimeout(() => {
        $overlay.find('img:visible').hide();
      }, 200);
    });
    $(document).on('scroll.compare', (e) => {
      const temp = $overlay.find('img:visible')[0];
      if (temp) {
        const $prev = $(temp.targetImage);
        if (!$prev.is(':hover')) {
          $prev.trigger('mouseleave');
          $images.find('img:hover').trigger('mousenter');
        }
      }
    }).on('keydown.compare', (e) => {
      e.preventDefault();
      e.stopImmediatePropagation();
      switch (e.key) {
        case 'Escape':
          exitCompare($overlay, $images);
          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 = 1;
          break;
        case 'E':
        case 'e':
          {
            const index = $images.index($overlay.find('img:visible')[0].targetImage);
            $($images[index]).trigger('mouseleave');
            const nextElem = $images[index + step] || $images[index];
            $(nextElem).trigger('mouseenter');
          }
          break;
        case 'W':
        case 'w':
          {
            const index = $images.index($overlay.find('img:visible')[0].targetImage);
            $($images[index]).trigger('mouseleave');
            const nextElem = $images[index - step] || $images[index];
            $(nextElem).trigger('mouseenter');
          }
          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();
    }
    $overlay.find('img').hide();
    $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': 2147483647 - 1,
    'background-color': 'rgba(0, 0, 0, 0.75)',
    'pointer-events': 'none',
    'display': 'none'
  });
  // 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'));
          break;
        case true:
          exitCompare($overlay, $('img'));
          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);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址