- // ==UserScript==
- // @name Gradient Tool
- // @namespace https://greasyfork.org/users/281093
- // @match https://sketchful.io/
- // @grant none
- // @version 1.0
- // @author Bell
- // @license MIT
- // @copyright 2020, Faux (https://greasyfork.org/users/281093)
- // @description Ctrl + Click and drag to draw a gradient, click a color to set it as the first and right click a color to set it as the second.
- // ==/UserScript==
- /* jshint esversion: 8 */
-
- const canvas = document.querySelector('#canvas');
- const colorsDiv = document.querySelector('#gameToolsColors');
- const lineCanvas = document.createElement('canvas');
- const lineCtx = lineCanvas.getContext('2d');
- const sizeInput = document.querySelector("#gameToolsColorPreview");
- lineCanvas.style.position = 'absolute';
- lineCanvas.style.cursor = 'crosshair';
- lineCanvas.style.width = '100%';
- lineCanvas.style.display = 'none';
- lineCanvas.style.userSelect = 'none';
- lineCanvas.style.zIndex = '2';
- lineCanvas.style.filter = 'opacity(0.7)';
- lineCanvas.oncontextmenu = () => { return false; };
- [lineCanvas.width, lineCanvas.height] = [canvas.width, canvas.height];
- canvas.parentElement.insertBefore(lineCanvas, canvas);
-
- lineCanvas.clear = () => {
- lineCtx.clearRect(0, 0, lineCanvas.width, lineCanvas.height);
- };
-
- canvas.save = () => {
- canvas.dispatchEvent(createMouseEvent('pointerup', { x: 0, y: 0 }, true));
- };
-
- let origin = {};
- let realOrigin = {};
- let previewPos = {};
- let realPos = {};
- let canvasHidden = true;
- let drawingLine = false;
- let activeColor = [0, 0, 0];
- let color1 = [0, 200, 0];
- let color2 = [250, 0, 0];
- let drawingGradient = false;
- let queueKeyUp = false;
-
- document.addEventListener('keydown', (e) => {
- if (!isDrawing()) return;
- if (e.code === 'ControlLeft' && canvasHidden) {
- lineCanvas.style.display = '';
- disableScroll();
- canvasHidden = false;
- }
- });
-
- document.addEventListener('keyup', (e) => {
- if (e.code === 'Digit1' && e.shiftKey) color1 = activeColor;
- else if (e.code === 'Digit2' && e.shiftKey) color2 = activeColor;
- if (e.code === 'ControlLeft' && !canvasHidden) {
- if (drawingGradient) {
- queueKeyUp = true;
- return;
- }
- onKeyUp();
- }
- });
-
- function onKeyUp() {
- lineCanvas.style.display = 'none';
- canvasHidden = true;
- enableScroll();
- resetLineCanvas();
- document.removeEventListener('pointermove', savePos);
- document.removeEventListener('pointerup', pointerUpDraw);
- }
-
- function savePos(e) {
- previewPos = getPos(e);
- realPos = getRealPos(e);
-
- if (canvasHidden || !drawingLine) return;
- lineCanvas.clear();
- drawPreviewLine(previewPos);
- e.preventDefault();
- }
-
- colorsDiv.addEventListener('pointerdown', (e) => {
- if (!e.target.classList.contains('gameToolsColor') || e.button === 2) return;
-
- const colorStr = e.target.style.background;
- const match = colorStr.match(/rgb\((\d+),\s*(\d+),\s*(\d+)/);
- activeColor = match.slice(1, 4).map(str => parseInt(str));
- color1 = activeColor;
- });
-
- colorsDiv.addEventListener('contextmenu', (e) => {
- if (!e.target.classList.contains('gameToolsColor')) return;
- e.preventDefault();
- const colorStr = e.target.style.background;
- const match = colorStr.match(/rgb\((\d+),\s*(\d+),\s*(\d+)/);
- color2 = match.slice(1, 4).map(str => parseInt(str));
- });
-
- lineCanvas.addEventListener('pointerdown', (e) => {
- if (drawingGradient) return;
- origin = getPos(e);
- realOrigin = getRealPos(e);
- drawingLine = true;
- document.addEventListener('pointerup', pointerUpDraw);
- document.addEventListener('pointermove', savePos);
- });
-
- function pointerUpDraw(e) {
- document.removeEventListener('pointermove', savePos);
- document.removeEventListener('pointerup', pointerUpDraw);
- drawGradient(realOrigin.x, realOrigin.y, realPos.x, realPos.y);
- previewPos = getPos(e);
- realPos = getRealPos(e);
- resetLineCanvas();
- }
-
- async function drawGradient(x1, y1, x2, y2) {
- drawingGradient = true;
- const lab1 = rgb2lab(color1);
- const lab2 = rgb2lab(color2);
- const scale = canvas.getBoundingClientRect().width / lineCanvas.width;
- const offset = (parseInt(sizeInput.value) / 2) * scale;
-
- const max = Math.round((y2 - y1) / offset) * 1.6;
-
- for (let s = 0; s <= max; s += 1) {
- const t = mapRange(s, 0, max, 0, 1);
- const y = lerp(y1 + offset, y2 - offset, t);
- const color = lerpColors(lab1, lab2, t);
-
- changeColor(lab2rgb(color));
- drawLine(x1 + offset, y, x2 - offset, y);
- await delay(10);
- }
-
- canvas.save();
- drawingGradient = false;
- if (queueKeyUp) {
- onKeyUp();
- queueKeyUp = false;
- }
- }
-
- function mapRange(value, istart, istop, ostart, ostop) {
- return ostart + (ostop - ostart) * ((value - istart) / (istop - istart));
- }
-
- function delay(time) {
- return new Promise((resolve) => {
- setTimeout(() => resolve(time), time);
- });
- }
-
- function changeColor(rgbArray) {
- const colorElement = document.querySelector('.gameToolsColor');
- const prevColor = colorElement.style.background;
-
- colorElement.style.background = `rgb(${rgbArray[0]}, ${rgbArray[1]}, ${rgbArray[2]})`;
- colorElement.dispatchEvent(new Event('pointerdown'));
- colorElement.style.background = prevColor;
- }
-
- function resetLineCanvas() {
- drawingLine = false;
- lineCanvas.clear();
- }
-
- function getPos(event) {
- const canvasRect = canvas.getBoundingClientRect();
- const canvasScale = canvas.width / canvasRect.width;
- return {
- x: (event.clientX - canvasRect.left) * canvasScale,
- y: (event.clientY - canvasRect.top) * canvasScale
- };
- }
-
- function getRealPos(event) {
- return {
- x: event.clientX,
- y: event.clientY
- };
- }
-
- function drawPreviewLine(pos) {
- const offset = parseInt(sizeInput.value) / 2;
- roundRect(
- lineCtx,
- origin.x,
- origin.y,
- pos.x - origin.x,
- pos.y - origin.y,
- offset,
- true,
- false,
- [
- color1,
- color2
- ]
- );
- }
-
- function roundRect(ctx, x, y, width, height, radius, fill, stroke, gradientColors) {
- if (typeof stroke === 'undefined') {
- stroke = true;
- }
- if (typeof radius === 'undefined') {
- radius = 5;
- }
- if (typeof radius === 'number') {
- radius = {tl: radius, tr: radius, br: radius, bl: radius};
- } else {
- var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
- for (var side in defaultRadius) {
- radius[side] = radius[side] || defaultRadius[side];
- }
- }
- ctx.beginPath();
- ctx.moveTo(x + radius.tl, y);
- ctx.lineTo(x + width - radius.tr, y);
- ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
- ctx.lineTo(x + width, y + height - radius.br);
- ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
- ctx.lineTo(x + radius.bl, y + height);
- ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
- ctx.lineTo(x, y + radius.tl);
- ctx.quadraticCurveTo(x, y, x + radius.tl, y);
- ctx.closePath();
-
- if (gradientColors) {
- const gradient = ctx.createLinearGradient(0, y, 0, height + y);
- const _color1 = `rgb(${gradientColors[0][0]}, ${gradientColors[0][1]}, ${gradientColors[0][2]}`;
- const _color2 = `rgb(${gradientColors[1][0]}, ${gradientColors[1][1]}, ${gradientColors[1][2]}`;
- gradient.addColorStop('0', _color1);
- gradient.addColorStop('1', _color2);
- ctx.strokeStyle = gradient;
- ctx.fillStyle = gradient;
- }
-
- if (fill) {
- ctx.fill();
- }
- if (stroke) {
- ctx.lineWidth = 5;
- ctx.stroke();
- }
- }
-
- function drawLine(x1, y1, x2, y2) {
- const coords = { x: x1, y: y1 };
- const newCoords = { x: x2, y: y2 };
-
- canvas.dispatchEvent(createMouseEvent('pointerdown', coords));
- canvas.dispatchEvent(createMouseEvent('pointermove', newCoords));
- }
-
- function createMouseEvent(name, pos, bubbles = false) {
- return new MouseEvent(name, {
- bubbles: bubbles,
- clientX: pos.x,
- clientY: pos.y,
- button: 0
- });
- }
-
- function lerp(a, b, t) {
- return a + (b - a) * t;
- }
-
- function lerpColors(lab1, lab2, t) {
- return [
- lab1[0] + (lab2[0] - lab1[0]) * t,
- lab1[1] + (lab2[1] - lab1[1]) * t,
- lab1[2] + (lab2[2] - lab1[2]) * t,
- ];
- }
-
- function rgb2lab(rgb) {
- let r = rgb[0] / 255,
- g = rgb[1] / 255,
- b = rgb[2] / 255,
- x, y, z;
-
- r = (r > 0.04045) ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92;
- g = (g > 0.04045) ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92;
- b = (b > 0.04045) ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92;
-
- x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
- y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
- z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
-
- x = (x > 0.008856) ? x ** (1 / 3) : (7.787 * x) + 16 / 116;
- y = (y > 0.008856) ? y ** (1 / 3) : (7.787 * y) + 16 / 116;
- z = (z > 0.008856) ? z ** (1 / 3) : (7.787 * z) + 16 / 116;
-
- return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];
- }
-
- function lab2rgb(lab){
- let y = (lab[0] + 16) / 116,
- x = lab[1] / 500 + y,
- z = y - lab[2] / 200,
- r, g, b;
-
- x = 0.95047 * ((x * x * x > 0.008856) ? x * x * x : (x - 16/116) / 7.787);
- y = 1.00000 * ((y * y * y > 0.008856) ? y * y * y : (y - 16/116) / 7.787);
- z = 1.08883 * ((z * z * z > 0.008856) ? z * z * z : (z - 16/116) / 7.787);
-
- r = x * 3.2406 + y * -1.5372 + z * -0.4986;
- g = x * -0.9689 + y * 1.8758 + z * 0.0415;
- b = x * 0.0557 + y * -0.2040 + z * 1.0570;
-
- r = (r > 0.0031308) ? (1.055 * Math.pow(r, 1/2.4) - 0.055) : 12.92 * r;
- g = (g > 0.0031308) ? (1.055 * Math.pow(g, 1/2.4) - 0.055) : 12.92 * g;
- b = (b > 0.0031308) ? (1.055 * Math.pow(b, 1/2.4) - 0.055) : 12.92 * b;
-
- return [
- Math.max(0, Math.min(1, r)) * 255,
- Math.max(0, Math.min(1, g)) * 255,
- Math.max(0, Math.min(1, b)) * 255
- ];
- }
-
- const keys = { 32: 1, 37: 1, 38: 1, 39: 1, 40: 1 };
-
- function preventDefault(e) {
- e.preventDefault();
- }
-
- function preventDefaultForScrollKeys(e) {
- if (keys[e.keyCode]) {
- preventDefault(e);
- return false;
- }
- }
-
- function isDrawing() {
- return document.querySelector('#gameTools').style.display !== 'none' &&
- document.querySelector('body > div.game').style.display !== 'none' &&
- document.activeElement.tagName !== 'INPUT';
- }
-
- let supportsPassive = false;
- try {
- window.addEventListener('test', null, Object.defineProperty({}, 'passive', {
- get: function() {
- supportsPassive = true;
- return true;
- }
- }));
- }
- catch(e) {
- console.log(e);
- }
-
- const wheelOpt = supportsPassive ? { passive: false } : false;
- const wheelEvent = 'onwheel' in document.createElement('div') ? 'wheel' : 'mousewheel';
-
- function disableScroll() {
- window.addEventListener('DOMMouseScroll', preventDefault, false);
- window.addEventListener(wheelEvent, preventDefault, wheelOpt);
- window.addEventListener('touchmove', preventDefault, wheelOpt);
- window.addEventListener('keydown', preventDefaultForScrollKeys, false);
- }
-
- function enableScroll() {
- window.removeEventListener('DOMMouseScroll', preventDefault, false);
- window.removeEventListener(wheelEvent, preventDefault, wheelOpt);
- window.removeEventListener('touchmove', preventDefault, wheelOpt);
- window.removeEventListener('keydown', preventDefaultForScrollKeys, false);
- }