Drawaria Avatar Size+Hider+Multiplier Customizer

Extend or diminish the size of selfavatarimage and avatar builder canvas, affecting uploaded canvas resolution, and add dynamic avatar tiling.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Drawaria Avatar Size+Hider+Multiplier Customizer
// @namespace    http://tampermonkey.net/
// @version      1.0.1
// @description  Extend or diminish the size of selfavatarimage and avatar builder canvas, affecting uploaded canvas resolution, and add dynamic avatar tiling.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/avatar/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Store original sizes from the very first detection on page load (for "Original Size" button)
    let initialSelfAvatarImageWidth, initialSelfAvatarImageHeight;
    let initialAvatarBuilderCanvasWidth, initialAvatarBuilderCanvasHeight;

    // Global/scoped variables for the Avatar Builder Canvas tiling state
    let tiledBaseAvatarImage = null; // Stores the cropped Image object of the single avatar to be tiled
    let isTiledMode = false;         // Flag to indicate if tiling is currently active

    // Helper to get element's current effective dimensions
    function getElementCurrentDimensions(element, isCanvasAttributeBased) {
        if (isCanvasAttributeBased) {
            // For canvases, read the actual width/height attributes for intrinsic size
            return {
                width: element.width,
                height: element.height
            };
        } else {
            // For images/other elements, read computed CSS style dimensions
            const computedStyle = getComputedStyle(element);
            return {
                width: parseFloat(computedStyle.width),
                height: parseFloat(computedStyle.height)
            };
        }
    }

    // Function to apply dimensions to an element (CSS for images, Attributes + CSS for canvas)
    function applyElementDimensions(element, width, height, isCanvasAttributeBased) {
        if (isCanvasAttributeBased) {
            // For canvases, update the actual width/height attributes (clears content)
            element.width = width;
            element.height = height;
            // Also update CSS style for visual consistency and scaling
            element.style.width = `${width}px`;
            element.style.height = `${height}px`;
        } else {
            // For images and other elements, only update CSS style
            element.style.width = `${width}px`;
            element.style.height = `${height}px`;
        }
    }

    // Function to create and append the draggable menu
    function createDraggableMenu() {
        const menu = document.createElement('div');
        menu.id = 'tampermonkey-menu';
        menu.style.cssText = `
            position: fixed;
            top: 50px;
            right: 50px;
            width: 280px;
            background: #282c34; /* Dark background */
            border: 1px solid #61dafb; /* Light blue border */
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
            z-index: 99999;
            font-family: Arial, sans-serif;
            color: #fff;
            padding: 15px;
            resize: both; /* Allow resizing the menu itself */
            overflow: auto; /* Show scrollbars if content overflows */
            min-width: 200px;
            min-height: 100px;
        `;

        const header = document.createElement('div');
        header.style.cssText = `
            cursor: grab;
            font-weight: bold;
            padding-bottom: 10px;
            border-bottom: 1px solid #61dafb;
            margin-bottom: 15px;
            color: #61dafb;
            user-select: none;
        `;
        header.textContent = 'Drawaria Size Customizer';
        menu.appendChild(header);

        // Make the menu draggable
        let isDragging = false;
        let offsetX, offsetY;

        header.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - menu.getBoundingClientRect().left;
            offsetY = e.clientY - menu.getBoundingClientRect().top;
            menu.style.cursor = 'grabbing';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;

            newX = Math.max(0, Math.min(newX, window.innerWidth - menu.offsetWidth));
            newY = Math.max(0, Math.min(newY, window.innerHeight - menu.offsetHeight));

            menu.style.left = newX + 'px';
            menu.style.top = newY + 'px';
        });

        document.addEventListener('mouseup', () => {
            isDragging = false;
            menu.style.cursor = 'grab';
        });

        document.body.appendChild(menu);
        return menu;
    }

    // Function to create a slider control
    const createSlider = (label, value, min, max, step, onChange) => {
        const container = document.createElement('div');
        container.style.marginBottom = '10px';

        const labelElem = document.createElement('label');
        labelElem.textContent = label + ': ';
        labelElem.style.display = 'block';
        labelElem.style.marginBottom = '5px';
        labelElem.style.color = '#ccc';
        container.appendChild(labelElem);

        const slider = document.createElement('input');
        slider.type = 'range';
        slider.min = min;
        slider.max = max;
        slider.step = step;
        slider.value = value;
        slider.style.width = 'calc(100% - 60px)';
        slider.style.verticalAlign = 'middle';
        slider.style.background = '#444';
        slider.style.appearance = 'none';
        slider.style.height = '8px';
        slider.style.borderRadius = '5px';
        slider.style.outline = 'none';
        slider.style.webkitAppearance = 'none';
        slider.style.cursor = 'pointer';

        if (!document.getElementById('tampermonkey-slider-style')) {
            const styleSheet = document.createElement('style');
            styleSheet.id = 'tampermonkey-slider-style';
            styleSheet.textContent = `
                input[type=range]::-webkit-slider-thumb {
                    -webkit-appearance: none;
                    appearance: none;
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: #61dafb;
                    cursor: pointer;
                    box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
                }
                input[type=range]::-moz-range-thumb {
                    width: 18px;
                    height: 18px;
                    border-radius: 50%;
                    background: #61dafb;
                    cursor: pointer;
                    box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
                }
            `;
            document.head.appendChild(styleSheet);
        }

        container.appendChild(slider);

        const valueSpan = document.createElement('span');
        valueSpan.textContent = ` ${value}px`;
        valueSpan.style.marginLeft = '5px';
        valueSpan.style.verticalAlign = 'middle';
        valueSpan.style.color = '#fff';
        container.appendChild(valueSpan);

        slider.oninput = (e) => {
            valueSpan.textContent = ` ${e.target.value}px`;
            onChange(parseInt(e.target.value));
        };

        slider.update = (newValue) => {
            slider.value = newValue;
            valueSpan.textContent = ` ${newValue}px`;
        };
        return container;
    };

    // Helper function to find the bounding box of non-transparent pixels
    function getBoundingBox(imageData) {
        const data = imageData.data;
        const width = imageData.width;
        const height = imageData.height;

        let minX = width;
        let minY = height;
        let maxX = -1;
        let maxY = -1;

        let foundPixel = false;

        for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x++) {
                const i = (y * width + x) * 4;
                const alpha = data[i + 3];
                if (alpha > 0) { // If not fully transparent
                    minX = Math.min(minX, x);
                    minY = Math.min(minY, y);
                    maxX = Math.max(maxX, x);
                    maxY = Math.max(maxY, y);
                    foundPixel = true;
                }
            }
        }

        if (!foundPixel) {
            return null; // Canvas is empty or fully transparent
        }

        return {
            x: minX,
            y: minY,
            width: maxX - minX + 1,
            height: maxY - minY + 1
        };
    }

    // Function to render the tiled avatar
    function renderTiledAvatar(canvas, ctx, avatarImage) {
        if (!avatarImage) {
            console.warn("No avatar image to tile.");
            return;
        }

        ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the main canvas before tiling

        const tileSourceWidth = avatarImage.width;
        const tileSourceHeight = avatarImage.height;

        if (tileSourceWidth === 0 || tileSourceHeight === 0) {
            console.warn("Captured avatar has zero dimensions. Cannot tile.");
            return;
        }

        // Calculate the number of columns and rows needed to perfectly fill the canvas
        // and the size of each tile to stretch to fit
        const numCols = Math.ceil(canvas.width / tileSourceWidth);
        const numRows = Math.ceil(canvas.height / tileSourceHeight);

        // Adjust the drawing dimensions of each tile to perfectly fill the current canvas size
        const drawWidth = canvas.width / numCols;
        const drawHeight = canvas.height / numRows;

        for (let r = 0; r < numRows; r++) {
            for (let c = 0; c < numCols; c++) {
                ctx.drawImage(avatarImage, c * drawWidth, r * drawHeight, drawWidth, drawHeight);
            }
        }
    }


    // Function to add a section for an element's controls
    function addControlSection(menu, title, targetSelector, isCanvasAttributeBased = false, localStorageKey) {
        const section = document.createElement('div');
        section.style.marginBottom = '20px';
        section.style.borderTop = '1px dashed #444';
        section.style.paddingTop = '15px';

        const sectionTitle = document.createElement('h3');
        sectionTitle.style.cssText = `
            font-size: 1.1em;
            margin-bottom: 10px;
            color: #61dafb;
        `;
        sectionTitle.textContent = title;
        section.appendChild(sectionTitle);

        const targetElement = document.querySelector(targetSelector);
        if (!targetElement) {
            section.textContent = `Element "${targetSelector}" not found.`;
            section.style.color = 'red';
            menu.appendChild(section);
            return;
        }

        // Capture initial dimensions on page load for "Original Size" button
        const initialDimensions = getElementCurrentDimensions(targetElement, isCanvasAttributeBased);
        if (targetSelector === '#selfavatarimage') {
            initialSelfAvatarImageWidth = initialDimensions.width;
            initialSelfAvatarImageHeight = initialDimensions.height;
        } else if (targetSelector === 'canvas.main') {
            initialAvatarBuilderCanvasWidth = initialDimensions.width;
            initialAvatarBuilderCanvasHeight = initialDimensions.height;
        }

        let currentWidth = initialDimensions.width;
        let currentHeight = initialDimensions.height;
        let originalAspectRatio = (initialDimensions.width / initialDimensions.height);
        if (isNaN(originalAspectRatio) || !isFinite(originalAspectRatio) || originalAspectRatio === 0) {
            originalAspectRatio = 1; // Fallback to 1:1 if original dimensions are invalid or zero
        }

        // --- Sliders ---
        const widthSliderContainer = createSlider(
            'Width',
            currentWidth,
            0,
            1000,
            1,
            (val) => {
                const newWidth = val;
                const newHeight = Math.round(newWidth / originalAspectRatio);
                applyElementDimensions(targetElement, newWidth, newHeight, isCanvasAttributeBased);
                widthSliderContainer.querySelector('input').update(newWidth);
                heightSliderContainer.querySelector('input').update(newHeight);

                // Re-tile if in tiled mode
                if (isTiledMode && targetSelector === 'canvas.main') {
                    renderTiledAvatar(targetElement, targetElement.getContext('2d'), tiledBaseAvatarImage);
                }
            }
        );
        section.appendChild(widthSliderContainer);

        const heightSliderContainer = createSlider(
            'Height',
            currentHeight,
            0,
            1000,
            1,
            (val) => {
                const newHeight = val;
                const newWidth = Math.round(newHeight * originalAspectRatio);
                applyElementDimensions(targetElement, newWidth, newHeight, isCanvasAttributeBased);
                heightSliderContainer.querySelector('input').update(newHeight);
                widthSliderContainer.querySelector('input').update(newWidth);

                // Re-tile if in tiled mode
                if (isTiledMode && targetSelector === 'canvas.main') {
                    renderTiledAvatar(targetElement, targetElement.getContext('2d'), tiledBaseAvatarImage);
                }
            }
        );
        section.appendChild(heightSliderContainer);

        // --- Custom Size Buttons ---
        const buttonsContainer = document.createElement('div');
        buttonsContainer.style.marginTop = '10px';
        buttonsContainer.style.display = 'flex';
        buttonsContainer.style.flexWrap = 'wrap';
        buttonsContainer.style.gap = '8px';
        section.appendChild(buttonsContainer);

        const createButton = (text, onClick) => {
            const button = document.createElement('button');
            button.textContent = text;
            button.style.cssText = `
                background: #61dafb;
                color: #282c34;
                border: none;
                padding: 8px 12px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 0.9em;
                flex: 1;
                min-width: 60px;
                white-space: nowrap;
                transition: background 0.2s ease;
            `;
            button.onmouseover = () => button.style.background = '#21a1f1';
            button.onmouseout = () => button.style.background = '#61dafb';
            button.onclick = onClick;
            buttonsContainer.appendChild(button);
        };

        createButton('Hide (0px)', () => {
            // Exit tiled mode
            isTiledMode = false;
            tiledBaseAvatarImage = null;

            applyElementDimensions(targetElement, 0, 0, isCanvasAttributeBased);
            widthSliderContainer.querySelector('input').update(0);
            heightSliderContainer.querySelector('input').update(0);
        });

        createButton('Set W=200px', () => {
            // Exit tiled mode
            isTiledMode = false;
            tiledBaseAvatarImage = null;

            const newWidth = 200;
            const newHeight = Math.round(newWidth / originalAspectRatio);
            applyElementDimensions(targetElement, newWidth, newHeight, isCanvasAttributeBased);
            widthSliderContainer.querySelector('input').update(newWidth);
            heightSliderContainer.querySelector('input').update(newHeight);
        });

        createButton('Original Size', () => {
            // Exit tiled mode
            isTiledMode = false;
            tiledBaseAvatarImage = null;

            const initialW = (targetSelector === '#selfavatarimage') ? initialSelfAvatarImageWidth : initialAvatarBuilderCanvasWidth;
            const initialH = (targetSelector === '#selfavatarimage') ? initialSelfAvatarImageHeight : initialAvatarBuilderCanvasHeight;

            if (isNaN(initialW) || isNaN(initialH) || initialW === 0 || initialH === 0) {
                console.warn("Original size not captured or is zero for", targetSelector, ". Cannot restore accurately.");
                alert("Original size not captured or is zero. Cannot restore accurately. Please refresh the page.");
                return;
            }
            applyElementDimensions(targetElement, initialW, initialH, isCanvasAttributeBased);
            widthSliderContainer.querySelector('input').update(initialW);
            heightSliderContainer.querySelector('input').update(initialH);
        });

        // --- Tile Avatar Button (only for Avatar Builder Canvas) ---
        if (isCanvasAttributeBased && targetSelector === 'canvas.main') {
            const tileButton = document.createElement('button');
            tileButton.textContent = 'Tile Avatar';
            tileButton.style.cssText = `
                background: #ffc107; /* Amber/Yellow */
                color: #282c34;
                border: none;
                padding: 8px 12px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 0.9em;
                margin-top: 10px;
                width: 100%;
                transition: background 0.2s ease;
            `;
            tileButton.onmouseover = () => tileButton.style.background = '#e0a800';
            tileButton.onmouseout = () => tileButton.style.background = '#ffc107';
            tileButton.onclick = () => {
                const canvas = targetElement;
                const ctx = canvas.getContext('2d');

                // 1. Capture the actual drawn avatar by finding its bounding box
                const currentImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                const boundingBox = getBoundingBox(currentImageData);

                if (!boundingBox) {
                    alert("Canvas is empty or only transparent pixels. Draw your avatar first!");
                    return;
                }

                // Create a temporary canvas to hold the cropped avatar
                const croppedCanvas = document.createElement('canvas');
                croppedCanvas.width = boundingBox.width;
                croppedCanvas.height = boundingBox.height;
                const croppedCtx = croppedCanvas.getContext('2d');
                // Put only the content within the bounding box onto the cropped canvas
                croppedCtx.putImageData(ctx.getImageData(boundingBox.x, boundingBox.y, boundingBox.width, boundingBox.height), 0, 0);

                const avatarImage = new Image();
                avatarImage.src = croppedCanvas.toDataURL(); // This is our clean single tile source

                avatarImage.onload = () => {
                    tiledBaseAvatarImage = avatarImage; // Store the image for later re-tiling
                    isTiledMode = true; // Activate tiled mode

                    renderTiledAvatar(canvas, ctx, tiledBaseAvatarImage);
                    alert("Avatar tiled across the canvas!");
                };
                 avatarImage.onerror = () => {
                    alert("Failed to load avatar image for tiling. Is the canvas content corrupted or empty?");
                };
            };
            section.appendChild(tileButton);
        }


        // --- Default Load Values Section ---
        const defaultLoadSection = document.createElement('div');
        defaultLoadSection.style.marginTop = '20px';
        defaultLoadSection.style.borderTop = '1px solid #444';
        defaultLoadSection.style.paddingTop = '15px';
        defaultLoadSection.style.color = '#ccc';

        const defaultTitle = document.createElement('h4');
        defaultTitle.style.cssText = `
            font-size: 1em;
            margin-bottom: 10px;
            color: #ddd;
        `;
        defaultTitle.textContent = 'Default Load Size:';
        defaultLoadSection.appendChild(defaultTitle);

        const defaultWidthInput = document.createElement('input');
        defaultWidthInput.type = 'number';
        defaultWidthInput.placeholder = 'Enter default width (px)';
        defaultWidthInput.style.cssText = `
            width: calc(100% - 10px);
            padding: 8px;
            margin-bottom: 10px;
            background: #333;
            border: 1px solid #555;
            border-radius: 4px;
            color: #fff;
        `;
        defaultLoadSection.appendChild(defaultWidthInput);

        const defaultButtonsContainer = document.createElement('div');
        defaultButtonsContainer.style.display = 'flex';
        defaultButtonsContainer.style.gap = '8px';
        defaultLoadSection.appendChild(defaultButtonsContainer);

        const saveButton = document.createElement('button');
        saveButton.textContent = 'Save as Default';
        saveButton.style.cssText = `
            background: #28a745; /* Green */
            color: #fff;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9em;
            flex: 1;
            transition: background 0.2s ease;
        `;
        saveButton.onmouseover = () => saveButton.style.background = '#218838';
        saveButton.onmouseout = () => saveButton.style.background = '#28a745';
        saveButton.onclick = () => {
            const val = parseInt(defaultWidthInput.value);
            if (!isNaN(val) && val >= 0) {
                const defaultHeight = Math.round(val / originalAspectRatio);
                localStorage.setItem(localStorageKey, JSON.stringify({ width: val, height: defaultHeight }));
                alert(`Default size saved for ${title}: ${val}x${defaultHeight}px`);
            } else {
                alert('Please enter a valid positive number for default width.');
            }
        };
        defaultButtonsContainer.appendChild(saveButton);

        const resetButton = document.createElement('button');
        resetButton.textContent = 'Reset Default';
        resetButton.style.cssText = `
            background: #dc3545; /* Red */
            color: #fff;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.9em;
            flex: 1;
            transition: background 0.2s ease;
        `;
        resetButton.onmouseover = () => resetButton.style.background = '#c82333';
        resetButton.onmouseout = () => resetButton.style.background = '#dc3545';
        resetButton.onclick = () => {
            localStorage.removeItem(localStorageKey);
            alert(`Default size reset for ${title}. Refresh page to apply original application size.`);

            // Exit tiled mode when default is reset for canvas
            if (targetSelector === 'canvas.main') {
                isTiledMode = false;
                tiledBaseAvatarImage = null;
            }

            const initialW = (targetSelector === '#selfavatarimage') ? initialSelfAvatarImageWidth : initialAvatarBuilderCanvasWidth;
            const initialH = (targetSelector === '#selfavatarimage') ? initialSelfAvatarImageHeight : initialAvatarBuilderCanvasHeight;
            applyElementDimensions(targetElement, initialW, initialH, isCanvasAttributeBased);
            widthSliderContainer.querySelector('input').update(initialW);
            heightSliderContainer.querySelector('input').update(initialH);
            defaultWidthInput.value = '';
        };
        defaultButtonsContainer.appendChild(resetButton);

        section.appendChild(defaultLoadSection);
        menu.appendChild(section);

        const savedDefaults = localStorage.getItem(localStorageKey);
        if (savedDefaults) {
            try {
                const { width, height } = JSON.parse(savedDefaults);
                if (!isNaN(width) && !isNaN(height) && width >= 0 && height >= 0) {
                    applyElementDimensions(targetElement, width, height, isCanvasAttributeBased);
                    widthSliderContainer.querySelector('input').update(width);
                    heightSliderContainer.querySelector('input').update(height);
                    defaultWidthInput.value = width;
                    console.log(`Applied saved default for ${title}: ${width}x${height}px`);
                }
            } catch (e) {
                console.error("Error parsing saved defaults for", title, e);
                localStorage.removeItem(localStorageKey);
            }
        }
    }

    // Initialize the script
    const menu = createDraggableMenu();

    const currentUrl = window.location.href;

    if (currentUrl.includes('drawaria.online') && !currentUrl.includes('drawaria.online/avatar/builder/')) {
        const checkSelfAvatarImage = setInterval(() => {
            const selfAvatarImage = document.getElementById('selfavatarimage');
            if (selfAvatarImage) {
                clearInterval(checkSelfAvatarImage);
                addControlSection(menu, 'Self Avatar Image', '#selfavatarimage', false, 'drawariaCustomizer.selfAvatarImageDefaults');
            }
        }, 500);
    }

    if (currentUrl.includes('drawaria.online/avatar/builder/')) {
        const checkAvatarBuilderCanvas = setInterval(() => {
            const avatarBuilderCanvas = document.querySelector('canvas.main');
            if (avatarBuilderCanvas) {
                clearInterval(checkAvatarBuilderCanvas);
                addControlSection(menu, 'Avatar Builder Canvas', 'canvas.main', true, 'drawariaCustomizer.avatarBuilderCanvasDefaults');
            }
        }, 500);
    }

})();