Загрузка данных
// ==UserScript==
// @name линейка
// @namespace http://tampermonkey.net/
// @version 3.0.0
// @description линейка делать ровные полоска
// @author Assistant & Vetanskiy
// @match https://drawaria.online/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
'use strict';
window.transparencyBrushEnabled = true;
window.transparencyBrushOpacity = 0.5;
// Создаем закадровый буферный холст для рисования 1 слоя без самонаслоения
const strokeCanvas = document.createElement('canvas');
const strokeCtx = strokeCanvas.getContext('2d');
let isDrawingLayer = false;
function syncCanvasSize() {
const mainCanvas = document.getElementById('canvas');
if (mainCanvas) {
if (strokeCanvas.width !== mainCanvas.width || strokeCanvas.height !== mainCanvas.height) {
strokeCanvas.width = mainCanvas.width;
strokeCanvas.height = mainCanvas.height;
}
}
}
// Преобразование цвета
window.applyColorAlpha = function (colorStr, alpha) {
if (!colorStr) return `rgba(0,0,0,${alpha})`;
colorStr = String(colorStr).trim();
if (colorStr.startsWith('rgba(')) {
return colorStr.replace(
/rgba\(\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^,]+)\s*,\s*[^)]+\)/,
`rgba($1,$2,$3,${alpha})`
);
}
if (colorStr.startsWith('rgb(')) {
return colorStr.replace(
/rgb\(\s*([^,]+)\s*,\s*([^,]+)\s*,\s*([^)]+)\)/,
`rgba($1,$2,$3,${alpha})`
);
}
if (colorStr.startsWith('#')) {
let hex = colorStr.slice(1);
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
else if (hex.length === 8) hex = hex.slice(0, 6);
if (hex.length === 6) {
const r = parseInt(hex.slice(0, 2), 16) || 0;
const g = parseInt(hex.slice(2, 4), 16) || 0;
const b = parseInt(hex.slice(2, 4), 16) || 0;
return `rgba(${r},${g},${b},${alpha})`;
}
}
return `rgba(0,0,0,${alpha})`;
};
// 1. Отправка через WebSocket с нужной прозрачностью для всех участников
const originalSend = WebSocket.prototype.send;
WebSocket.prototype.send = function (data) {
if (typeof data === 'string' && window.transparencyBrushEnabled) {
if (data.startsWith('42[') && data.includes('"drawcmd"')) {
try {
const parsed = JSON.parse(data.slice(2));
if (Array.isArray(parsed) && parsed[0] === 'drawcmd' && Array.isArray(parsed[2])) {
const payload = parsed[2];
if (payload.length >= 7 && typeof payload[6] === 'string') {
// Применяем прозрачность к отправляемому цвету
payload[6] = window.applyColorAlpha(payload[6], window.transparencyBrushOpacity);
data = '42' + JSON.stringify(parsed);
}
}
} catch (e) {}
}
}
return originalSend.call(this, data);
};
// 2. Отслеживание начала и конца штриха для слияния слоя
document.addEventListener('pointerdown', e => {
if (e.target && (e.target.id === 'canvas' || e.target.classList.contains('canvas-drawer'))) {
syncCanvasSize();
isDrawingLayer = true;
strokeCtx.clearRect(0, 0, strokeCanvas.width, strokeCanvas.height);
}
}, true);
const endStroke = () => {
if (isDrawingLayer) {
isDrawingLayer = false;
const mainCanvas = document.getElementById('canvas');
if (mainCanvas && window.transparencyBrushEnabled) {
const mainCtx = mainCanvas.getContext('2d');
mainCtx.save();
mainCtx.globalAlpha = window.transparencyBrushOpacity;
mainCtx.drawImage(strokeCanvas, 0, 0);
mainCtx.restore();
strokeCtx.clearRect(0, 0, strokeCanvas.width, strokeCanvas.height);
}
}
};
document.addEventListener('pointerup', endStroke, true);
document.addEventListener('pointercancel', endStroke, true);
// 3. Перехват контекста рисования: во время движения пишем в 100% альфе на временный буфер,
// чтобы штрих не темнел сам на себе при пересечении линий в одном клике.
const originalStroke = CanvasRenderingContext2D.prototype.stroke;
CanvasRenderingContext2D.prototype.stroke = function () {
if (window.transparencyBrushEnabled && this.canvas && this.canvas.id === 'canvas' && isDrawingLayer) {
// Рисуем на вспомогательном буфере
strokeCtx.lineWidth = this.lineWidth;
strokeCtx.lineCap = this.lineCap;
strokeCtx.lineJoin = this.lineJoin;
strokeCtx.strokeStyle = this.strokeStyle;
strokeCtx.stroke(this.currentPath || arguments[0]);
}
return originalStroke.apply(this, arguments);
};
// 4. Панель управления UI
function injectNativeUI() {
if (document.getElementById('tm-transparency-control')) return;
const drawcontrols = document.getElementById('drawcontrols');
if (!drawcontrols) return;
const widthToggle = drawcontrols.querySelector('.drawcontrols-widthtoggle');
if (!widthToggle) return;
const control = document.createElement('div');
control.id = 'tm-transparency-control';
control.className = 'drawcontrols-button drawcontrols-sep';
control.style.cssText = `
width: 175px;
padding: 0 8px;
position: relative;
display: flex;
align-items: center;
gap: 6px;
background: rgba(0,0,0,0.05);
border-radius: 4px;
`;
control.title = 'Gartic Layered Transparency';
control.innerHTML = `
<span id="tm-tb-val" style="font-size: 11px; font-weight: bold; min-width: 32px; color: #333;">50%</span>
<input type="range" id="tm-tb-opacity-range"
min="5" max="100" step="5" value="50"
style="flex: 1; height: 5px; cursor: pointer; accent-color: #007bff;"
title="Прозрачность">
<div id="tm-tb-toggle-btn" style="
width: 28px;
height: 18px;
font-size: 10px;
line-height: 18px;
text-align: center;
border-radius: 3px;
background: #28a745;
color: #fff;
cursor: pointer;
user-select: none;
font-weight: bold;
box-shadow: 0 1px 2px rgba(0,0,0,0.2);
" title="Включить / Выключить">ON</div>
`;
widthToggle.parentNode.insertBefore(control, widthToggle.nextSibling);
const toggleBtn = document.getElementById('tm-tb-toggle-btn');
const range = document.getElementById('tm-tb-opacity-range');
const valText = document.getElementById('tm-tb-val');
function updateVisual() {
const pct = Math.round(window.transparencyBrushOpacity * 100);
range.value = pct;
valText.textContent = `${pct}%`;
if (window.transparencyBrushEnabled) {
toggleBtn.textContent = 'ON';
toggleBtn.style.background = '#28a745';
} else {
toggleBtn.textContent = 'OFF';
toggleBtn.style.background = '#6c757d';
}
}
toggleBtn.addEventListener('click', (e) => {
e.stopPropagation();
window.transparencyBrushEnabled = !window.transparencyBrushEnabled;
updateVisual();
});
range.addEventListener('input', (e) => {
window.transparencyBrushOpacity = (parseInt(e.target.value, 10) || 50) / 100;
updateVisual();
});
range.addEventListener('mousedown', e => e.stopPropagation());
range.addEventListener('pointerdown', e => e.stopPropagation());
updateVisual();
}
const observer = new MutationObserver(() => {
if (document.getElementById('drawcontrols') && !document.getElementById('tm-transparency-control')) {
injectNativeUI();
}
});
observer.observe(document.documentElement, { childList: true, subtree: true });
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(injectNativeUI, 800));
} else {
setTimeout(injectNativeUI, 800);
}
})();