Guest User

Danbooru Strip

a guest
Nov 13th, 2024
500
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 14.67 KB | Software | 0 0
  1. // ==UserScript==
  2. // @name         Danbooru Strip
  3. // @description  Strip Danbooru images with your mouse
  4. // @version      0.1.0
  5. // @namespace    https://github.com/andre-atgit/danbooru-strip/
  6. // @match        *://danbooru.donmai.us/posts/*
  7. // @license      MIT
  8. // @run-at       document-idle
  9. // ==/UserScript==
  10.  
  11. /* jshint esversion: 8 */
  12.  
  13. (function() {
  14.     'use strict';
  15.  
  16.     const strip = { isLoaded: false };
  17.     appendCss();
  18.     appendStripTags();
  19.  
  20.     async function init() {
  21.         if (strip.isLoaded) {
  22.             await fetchData();
  23.             loadImgs();
  24.             return;
  25.         }
  26.  
  27.         strip.isLoaded = true;
  28.         strip.lineWidth = 100;
  29.         strip.isDrawing = false;
  30.  
  31.         strip.undoHistory = [];
  32.         strip.redoHistory = [];
  33.  
  34.         strip.prevX = null;
  35.         strip.prevY = null;
  36.         strip.currentX = null;
  37.         strip.currentY = null;
  38.  
  39.         appendCanvas();
  40.         appendOptions();
  41.         await fetchData();
  42.         loadImgs();
  43.         addEvents();
  44.         addHotkeys();
  45.     }
  46.  
  47.     function appendCss() {
  48.         const style = document.createElement('style');
  49.         document.head.appendChild(style);
  50.         style.innerHTML = `
  51.         .strip-preview-tag {
  52.             border-radius: 0px 0px 5px 5px;
  53.             color: white;
  54.             text-align: center;
  55.         }
  56.  
  57.         .post-status-has-children .strip-preview-tag {
  58.             background-color: var(--preview-has-children-color);
  59.         }
  60.  
  61.         .post-status-has-parent .strip-preview-tag {
  62.             background-color: var(--preview-has-parent-color);
  63.         }
  64.  
  65.         #strip-canvas-container {
  66.             position: relative;
  67.             width: fit-content;
  68.         }
  69.  
  70.         #strip-full-view-container {
  71.             display: none;
  72.             position: fixed;
  73.             z-index: 1;
  74.             left: 0;
  75.             top: 0;
  76.             width: 100%;
  77.             height: 100%;
  78.             overflow: auto;
  79.             background-color: #0E0E0E;
  80.         }
  81.  
  82.         #strip-canvas-container .fit {
  83.             max-width: 100%;
  84.             height: auto !important
  85.         }
  86.  
  87.         #strip-full-view-container .fit {
  88.             max-height: 100%;
  89.             max-width: 100%;
  90.             position: fixed !important;
  91.             top: 50%;
  92.             left: 50%;
  93.             transform: translate(-50%, -50%);
  94.         }
  95.  
  96.         #strip-bottom-layer {
  97.             position: absolute;
  98.         }
  99.  
  100.         #strip-top-layer {
  101.             position: absolute;
  102.             z-index: 1;
  103.         }
  104.  
  105.         #strip-cursor-layer {
  106.             position: relative;
  107.             z-index: 2;
  108.         }`;
  109.     }
  110.  
  111.     function appendStripTags() {
  112.         const previewElements = document.getElementsByClassName('post-preview-container');
  113.         if (!previewElements || previewElements.length < 2) return;
  114.  
  115.         for (const previewElement of previewElements) {
  116.             const previewLinkElem = previewElement.getElementsByClassName('post-preview-link')[0];
  117.             const apiLink = previewLinkElem.href.split('?')[0] + '.json';
  118.  
  119.             const p = document.createElement('p');
  120.             p.classList.add('strip-preview-tag', 'cursor-pointer');
  121.             previewElement.after(p);
  122.  
  123.             if (previewElement.parentElement.classList.contains('current-post')) {
  124.                 p.innerHTML = 'Current';
  125.                 strip.topLayerApiLink = apiLink;
  126.             } else {
  127.                 p.innerHTML = '<a>Strip!</a>';
  128.                 p.onclick = (evt) => {
  129.                     const currentlySelected = document.getElementById('strip-selected');
  130.                     if (currentlySelected) currentlySelected.parentElement.innerHTML = '<a>Strip!</a>';
  131.                     evt.currentTarget.innerHTML = '<span id="strip-selected">Selected</span>';
  132.  
  133.                     strip.bottomLayerApiLink = apiLink;
  134.                     init();
  135.                 };
  136.             }
  137.         }
  138.     }
  139.  
  140.     function appendCanvas() {
  141.         const content = document.createElement('div');
  142.         content.innerHTML = `
  143.         <div id="strip-canvas-container">
  144.             <canvas id="strip-bottom-layer" class="fit"></canvas>
  145.             <canvas id="strip-top-layer" class="fit"></canvas>
  146.             <canvas id="strip-cursor-layer" oncontextmenu="return false" class="fit"> </canvas>
  147.         </div>
  148.         <div id="strip-full-view-container"></div>`;
  149.  
  150.         const containers = content.children;
  151.         strip.canvasContainer = containers[0];
  152.         strip.fullViewContainer = containers[1];
  153.         strip.fullViewContainer.onmousedown = (evt) => (evt.target === strip.fullViewContainer) && toggleFullView();
  154.  
  155.         const canvases = content.getElementsByTagName('canvas');
  156.         strip.bottomLayer = canvases[0];
  157.         strip.topLayer = canvases[1];
  158.         strip.cursorLayer = canvases[2];
  159.  
  160.         strip.bottomCtx = strip.bottomLayer.getContext('2d');
  161.         strip.topCtx = strip.topLayer.getContext('2d');
  162.         strip.cursorCtx = strip.cursorLayer.getContext('2d');
  163.  
  164.         const resizeNotice = document.getElementById('image-resize-notice');
  165.         if (resizeNotice) resizeNotice.style.display = 'none';
  166.  
  167.         const image = document.getElementById('image');
  168.         const imageSection = image.closest('section');
  169.         for (const child of imageSection.children) {
  170.             child.style.display = 'none';
  171.         }
  172.  
  173.         imageSection.appendChild(strip.canvasContainer);
  174.         imageSection.appendChild(strip.fullViewContainer);
  175.     }
  176.  
  177.     function appendOptions() {
  178.         const stripOptions = document.createElement('section');
  179.         stripOptions.innerHTML = `
  180.         <h2>Strip</h2>
  181.         <ul>
  182.             <li><a class="cursor-pointer">Toggle full view</a></li>
  183.             <li><a class="cursor-pointer">Undo</a></li>
  184.             <li><a class="cursor-pointer">Redo</a></li>
  185.             <li><a class="cursor-pointer">Download strip</a></li>
  186.             <li><a class="cursor-pointer">Brush width</a></li>
  187.             <li><input type="range" min="1" max="400" value="100"></li>
  188.         </ul>`;
  189.  
  190.         const options = stripOptions.getElementsByTagName('a');
  191.         options[0].onclick = () => toggleFullView();
  192.         options[1].onclick = () => undo();
  193.         options[2].onclick = () => redo();
  194.         options[3].onclick = () => download();
  195.  
  196.         strip.lineWidthInput = stripOptions.getElementsByTagName('input')[0];
  197.         strip.lineWidthInput.onchange = (evt) => setLineWidth(Number(evt.target.value));
  198.  
  199.         const sidebar = document.getElementById('sidebar');
  200.         sidebar.appendChild(stripOptions);
  201.     }
  202.  
  203.     async function fetchData() {
  204.         const topLayerRequest = fetch(strip.topLayerApiLink).then((res) => res.json());
  205.         const bottomLayerRequest = fetch(strip.bottomLayerApiLink).then((res) => res.json());
  206.  
  207.         const [topLayerData, bottomLayerData] = await Promise.all([topLayerRequest, bottomLayerRequest]);
  208.         const image = document.getElementById('image');
  209.  
  210.         const topVariant = topLayerData.media_asset.variants.find((variant) => variant.width === image.naturalWidth);
  211.         const bottomVariant = bottomLayerData.media_asset.variants.find((variant) => variant.width === image.naturalWidth);
  212.  
  213.         strip.topLayerUrl = topVariant ? topVariant.url : topLayerData.file_url;
  214.         strip.bottomLayerUrl = bottomVariant ? bottomVariant.url : bottomLayerData.file_url;
  215.     }
  216.  
  217.     function loadImgs() {
  218.         const topImg = new Image();
  219.         const bottomImg = new Image();
  220.  
  221.         topImg.crossOrigin = 'anonymous';
  222.         topImg.src = strip.topLayerUrl;
  223.  
  224.         bottomImg.crossOrigin = 'anonymous';
  225.         bottomImg.src = strip.bottomLayerUrl;
  226.  
  227.         topImg.onload = () => {
  228.             strip.topImage = topImg;
  229.             if (strip.bottomImage) drawImgs();
  230.         }
  231.  
  232.         bottomImg.onload = () => {
  233.             strip.bottomImage = bottomImg;
  234.             if (strip.topImage) drawImgs();
  235.         }
  236.     }
  237.  
  238.     function drawImgs() {
  239.         strip.cursorLayer.width = strip.bottomLayer.width = strip.topLayer.width = strip.topImage.width;
  240.         strip.cursorLayer.height = strip.bottomLayer.height = strip.topLayer.height = strip.topImage.height;
  241.  
  242.         strip.topCtx.drawImage(strip.undoHistory.at(-1) || strip.topImage, 0, 0);
  243.         strip.bottomCtx.drawImage(strip.bottomImage, 0, 0);
  244.     }
  245.  
  246.     function addEvents() {
  247.         strip.cursorLayer.addEventListener('pointerenter', (evt) => {
  248.             strip.prevX = strip.currentX = evt.offsetX;
  249.             strip.prevY = strip.currentY = evt.offsetY;
  250.             if (evt.pressure) strip.isDrawing = true;
  251.         });
  252.  
  253.         strip.cursorLayer.addEventListener('pointermove', (evt) => {
  254.             strip.prevX = strip.currentX;
  255.             strip.prevY = strip.currentY;
  256.             strip.currentX = evt.offsetX;
  257.             strip.currentY = evt.offsetY;
  258.             drawCursor(strip.currentX, strip.currentY);
  259.             if (evt.pressure) drawLine(strip.prevX, strip.prevY, strip.currentX, strip.currentY);
  260.         });
  261.  
  262.         strip.cursorLayer.addEventListener('pointerleave', (evt) => {
  263.             strip.currentX = null;
  264.             strip.currentY = null;
  265.             clearCursor();
  266.             if (strip.isDrawing) {
  267.                 strip.isDrawing = false;
  268.                 addStrokeToHistory();
  269.             }
  270.         });
  271.  
  272.         strip.cursorLayer.addEventListener('pointerdown', (evt) => {
  273.             strip.isDrawing = true;
  274.             drawArc(evt.offsetX, evt.offsetY);
  275.         });
  276.  
  277.         strip.cursorLayer.addEventListener('pointerup', (evt) => {
  278.             strip.isDrawing = false;
  279.             addStrokeToHistory();
  280.         });
  281.  
  282.         strip.cursorLayer.addEventListener('touchmove', (evt) => {
  283.             if (evt.changedTouches.length === 1) evt.preventDefault();
  284.         });
  285.     }
  286.  
  287.     function addHotkeys() {
  288.         document.addEventListener('keydown', (evt) => {
  289.             if (document.activeElement.value !== undefined) return;
  290.             switch (evt.key) {
  291.                 case '+':
  292.                     setLineWidth(strip.lineWidth + 1);
  293.                     break;
  294.                 case '-':
  295.                     setLineWidth(Math.max(strip.lineWidth - 1, 1));
  296.                     break;
  297.                 case "'":
  298.                     if (evt.ctrlKey) {
  299.                         evt.preventDefault();
  300.                         toggleFullView();
  301.                     }
  302.                     break;
  303.                 case 'z':
  304.                     if (evt.ctrlKey && strip.undoHistory.length) {
  305.                         evt.preventDefault();
  306.                         undo();
  307.                     }
  308.                     break;
  309.                 case 'y':
  310.                     if (evt.ctrlKey && strip.redoHistory.length) {
  311.                         evt.preventDefault();
  312.                         redo();
  313.                     }
  314.                     break;
  315.             }
  316.         });
  317.     }
  318.  
  319.     function drawArc(x, y) {
  320.         const scale = getScale();
  321.         strip.topCtx.globalCompositeOperation = 'destination-out';
  322.         strip.topCtx.beginPath();
  323.         strip.topCtx.arc(x / scale, y / scale, strip.lineWidth / 2, 0, Math.PI * 2);
  324.         strip.topCtx.fill();
  325.     }
  326.  
  327.     function drawLine(x1, y1, x2, y2) {
  328.         const scale = getScale();
  329.         strip.topCtx.globalCompositeOperation = 'destination-out';
  330.         strip.topCtx.beginPath();
  331.         strip.topCtx.lineWidth = strip.lineWidth;
  332.         strip.topCtx.lineJoin = 'round';
  333.         strip.topCtx.moveTo(x1 / scale, y1 / scale);
  334.         strip.topCtx.lineTo(x2 / scale, y2 / scale);
  335.         strip.topCtx.closePath();
  336.         strip.topCtx.stroke();
  337.     }
  338.  
  339.     function drawCursor(x, y) {
  340.         const scale = getScale();
  341.         strip.cursorCtx.clearRect(0, 0, strip.cursorLayer.width, strip.cursorLayer.height);
  342.         strip.cursorCtx.beginPath();
  343.         strip.cursorCtx.arc(x / scale, y / scale, strip.lineWidth / 2, 0, Math.PI * 2);
  344.         strip.cursorCtx.lineWidth = 1;
  345.         strip.cursorCtx.strokeStyle = 'black';
  346.         strip.cursorCtx.fillStyle = 'transparent';
  347.         strip.cursorCtx.stroke();
  348.     }
  349.  
  350.     function clearCursor() {
  351.         strip.cursorCtx.clearRect(0, 0, strip.cursorLayer.width, strip.cursorLayer.height);
  352.     }
  353.  
  354.     function getScale() {
  355.         return strip.cursorLayer.getBoundingClientRect().width / strip.cursorLayer.width;
  356.     }
  357.  
  358.     function setLineWidth(width) {
  359.         strip.lineWidth = width;
  360.         strip.lineWidthInput.value = strip.lineWidth;
  361.         if (strip.currentX !== null) drawCursor(strip.currentX, strip.currentY);
  362.     }
  363.  
  364.     function addStrokeToHistory() {
  365.         const historyEntry = document.createElement('canvas');
  366.         historyEntry.width = strip.topLayer.width;
  367.         historyEntry.height  = strip.topLayer.height;
  368.  
  369.         const context = historyEntry.getContext('2d');
  370.         context.drawImage(strip.bottomLayer, 0, 0);
  371.         context.drawImage(strip.topLayer, 0, 0);
  372.  
  373.         strip.redoHistory = [];
  374.         strip.undoHistory.push(historyEntry);
  375.     }
  376.  
  377.     function undo() {
  378.         if (!strip.topImage || !strip.undoHistory.length) return;
  379.         strip.redoHistory.push(strip.undoHistory.pop());
  380.  
  381.         strip.topCtx.globalCompositeOperation = 'source-over';
  382.         strip.topCtx.drawImage(strip.undoHistory.at(-1) || strip.topImage, 0, 0);
  383.     }
  384.  
  385.     function redo() {
  386.         if (!strip.topImage || !strip.redoHistory.length) return;
  387.         strip.undoHistory.push(strip.redoHistory.pop());
  388.  
  389.         strip.topCtx.globalCompositeOperation = 'source-over';
  390.         strip.topCtx.drawImage(strip.undoHistory.at(-1), 0, 0);
  391.     }
  392.  
  393.     function download() {
  394.         const downloadCanvas = document.createElement('canvas');
  395.         downloadCanvas.width = strip.topLayer.width;
  396.         downloadCanvas.height = strip.topLayer.height;
  397.  
  398.         const context = downloadCanvas.getContext('2d');
  399.         context.drawImage(strip.bottomLayer, 0, 0);
  400.         context.drawImage(strip.topLayer, 0, 0);
  401.  
  402.         const link = document.createElement('a');
  403.         link.href = downloadCanvas.toDataURL('image/png');
  404.         link.download = 'strip.png';
  405.         link.click();
  406.     }
  407.  
  408.     function toggleFullView() {
  409.         if (strip.fullViewContainer.style.display !== 'block') {
  410.             strip.fullViewContainer.style.display = 'block';
  411.             while (strip.canvasContainer.firstChild) {
  412.                 strip.fullViewContainer.appendChild(strip.canvasContainer.firstChild);
  413.             }
  414.         } else {
  415.             strip.fullViewContainer.style.display = 'none';
  416.             while (strip.fullViewContainer.firstChild) {
  417.                 strip.canvasContainer.appendChild(strip.fullViewContainer.firstChild);
  418.             }
  419.         }
  420.     }
  421. })();
  422.  
Tags: danbooru
Add Comment
Please, Sign In to add comment