Guest User

Danbooru Strip 0.1.1

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