Unverified Commit b4b7e6e5 authored by AUTOMATIC1111's avatar AUTOMATIC1111 Committed by GitHub
Browse files

Merge pull request #11005 from daswer123/dev

Fixed bugs in the zoom builtin extensions and made the zoom function global
parents 7f28e8c4 0432e378
Loading
Loading
Loading
Loading
+133 −44
Original line number Original line Diff line number Diff line
// Main

// Helper functions
// Helper functions
// Get active tab
// Get active tab
function getActiveTab(elements, all = false) {
function getActiveTab(elements, all = false) {
@@ -14,6 +12,17 @@ function getActiveTab(elements, all = false) {
    }
    }
}
}


// Get tab ID
function getTabId(elements, elementIDs) {
    const activeTab = getActiveTab(elements);
    const tabIdLookup = {
        "Sketch": elementIDs.sketch,
        "Inpaint sketch": elementIDs.inpaintSketch,
        "Inpaint": elementIDs.inpaint
    };
    return tabIdLookup[activeTab.innerText];
}

// Wait until opts loaded
// Wait until opts loaded
async function waitForOpts() {
async function waitForOpts() {
    return new Promise(resolve => {
    return new Promise(resolve => {
@@ -63,6 +72,48 @@ function createHotkeyConfig(defaultHotkeysConfig, hotkeysConfigOpts) {
    return result;
    return result;
}
}


/**
 * The restoreImgRedMask function displays a red mask around an image to indicate the aspect ratio.
 * If the image display property is set to 'none', the mask breaks. To fix this, the function
 * temporarily sets the display property to 'block' and then hides the mask again after 300 milliseconds
 * to avoid breaking the canvas. Additionally, the function adjusts the mask to work correctly on
 * very long images.
 */

function restoreImgRedMask(elements, elementIDs) {
    const mainTabId = getTabId(elements, elementIDs);

    if (!mainTabId) return;

    const mainTab = gradioApp().querySelector(mainTabId);
    const img = mainTab.querySelector("img");
    const imageARPreview = gradioApp().querySelector("#imageARPreview");

    if (!img || !imageARPreview) return;

    imageARPreview.style.transform = "";
    if (parseFloat(mainTab.style.width) > 865) {
        const transformString = mainTab.style.transform;
        const scaleMatch = transformString.match(/scale\(([-+]?[0-9]*\.?[0-9]+)\)/);
        let zoom = 1; // default zoom

        if (scaleMatch && scaleMatch[1]) {
            zoom = Number(scaleMatch[1]);
        }

        imageARPreview.style.transformOrigin = "0 0";
        imageARPreview.style.transform = `scale(${zoom})`;
    }

    if (img.style.display !== "none") return;

    img.style.display = "block";

    setTimeout(() => {
        img.style.display = "none";
    }, 400);
}

// Main
// Main
onUiLoaded(async() => {
onUiLoaded(async() => {
    const hotkeysConfigOpts = await waitForOpts();
    const hotkeysConfigOpts = await waitForOpts();
@@ -84,17 +135,19 @@ onUiLoaded(async() => {


    let isMoving = false;
    let isMoving = false;
    let mouseX, mouseY;
    let mouseX, mouseY;
    let activeElement;


    const elementIDs = {
    const elementIDs = {
        sketch: "#img2img_sketch",
        sketch: "#img2img_sketch",
        inpaint: "#img2maskimg",
        inpaint: "#img2maskimg",
        inpaintSketch: "#inpaint_sketch",
        inpaintSketch: "#inpaint_sketch",
        img2imgTabs: "#mode_img2img .tab-nav"
        img2imgTabs: "#mode_img2img .tab-nav",
        rangeGroup: "#img2img_column_size"
    };
    };


    async function getElements() {
    async function getElements() {
        const elements = await Promise.all(
        const elements = await Promise.all(
            Object.values(elementIDs).map(id => document.querySelector(id))
            Object.values(elementIDs).map(id => gradioApp().querySelector(id))
        );
        );
        return Object.fromEntries(
        return Object.fromEntries(
            Object.keys(elementIDs).map((key, index) => [key, elements[index]])
            Object.keys(elementIDs).map((key, index) => [key, elements[index]])
@@ -102,10 +155,36 @@ onUiLoaded(async() => {
    }
    }


    const elements = await getElements();
    const elements = await getElements();
    const elemData = {};

    // Apply functionality to the range inputs. Restore redmask and correct for long images.
    const rangeInputs = elements.rangeGroup ? elements.rangeGroup.querySelectorAll("input") :
        [
            gradioApp().querySelector("#img2img_width input[type='range']"),
            gradioApp().querySelector("#img2img_height input[type='range']")
        ];

    rangeInputs.forEach(input => {
        if (input) {
            input.addEventListener("input", () => restoreImgRedMask(elements, elementIDs));
        }
    });

    function applyZoomAndPan(elemId) {
        const targetElement = gradioApp().querySelector(elemId);

        if (!targetElement) {
            console.log("Element not found");
            return;
        }


    function applyZoomAndPan(targetElement, elemId) {
        targetElement.style.transformOrigin = "0 0";
        targetElement.style.transformOrigin = "0 0";
        let [zoomLevel, panX, panY] = [1, 0, 0];

        elemData[elemId] = {
            zoom: 1,
            panX: 0,
            panY: 0
        };
        let fullScreenMode = false;
        let fullScreenMode = false;


        // Create tooltip
        // Create tooltip
@@ -124,7 +203,7 @@ onUiLoaded(async() => {
            const tooltipContent = document.createElement("div");
            const tooltipContent = document.createElement("div");
            tooltipContent.className = "tooltip-content";
            tooltipContent.className = "tooltip-content";


            // Add info about hotkets
            // Add info about hotkeys
            const zoomKey = hotkeysConfig.canvas_swap_controls ? "Ctrl" : "Shift";
            const zoomKey = hotkeysConfig.canvas_swap_controls ? "Ctrl" : "Shift";
            const adjustKey = hotkeysConfig.canvas_swap_controls ? "Shift" : "Ctrl";
            const adjustKey = hotkeysConfig.canvas_swap_controls ? "Shift" : "Ctrl";


@@ -132,21 +211,15 @@ onUiLoaded(async() => {
                {key: `${zoomKey} + wheel`, action: "Zoom canvas"},
                {key: `${zoomKey} + wheel`, action: "Zoom canvas"},
                {key: `${adjustKey} + wheel`, action: "Adjust brush size"},
                {key: `${adjustKey} + wheel`, action: "Adjust brush size"},
                {
                {
                    key: hotkeysConfig.canvas_hotkey_reset.charAt(
                    key: hotkeysConfig.canvas_hotkey_reset.charAt(hotkeysConfig.canvas_hotkey_reset.length - 1),
                        hotkeysConfig.canvas_hotkey_reset.length - 1
                    ),
                    action: "Reset zoom"
                    action: "Reset zoom"
                },
                },
                {
                {
                    key: hotkeysConfig.canvas_hotkey_fullscreen.charAt(
                    key: hotkeysConfig.canvas_hotkey_fullscreen.charAt(hotkeysConfig.canvas_hotkey_fullscreen.length - 1),
                        hotkeysConfig.canvas_hotkey_fullscreen.length - 1
                    ),
                    action: "Fullscreen mode"
                    action: "Fullscreen mode"
                },
                },
                {
                {
                    key: hotkeysConfig.canvas_hotkey_move.charAt(
                    key: hotkeysConfig.canvas_hotkey_move.charAt(hotkeysConfig.canvas_hotkey_move.length - 1),
                        hotkeysConfig.canvas_hotkey_move.length - 1
                    ),
                    action: "Move canvas"
                    action: "Move canvas"
                }
                }
            ];
            ];
@@ -186,12 +259,14 @@ onUiLoaded(async() => {


        // Reset the zoom level and pan position of the target element to their initial values
        // Reset the zoom level and pan position of the target element to their initial values
        function resetZoom() {
        function resetZoom() {
            zoomLevel = 1;
            elemData[elemId] = {
            panX = 0;
                zoomLevel: 1,
            panY = 0;
                panX: 0,
                panY: 0
            };


            fixCanvas();
            fixCanvas();
            targetElement.style.transform = `scale(${zoomLevel}) translate(${panX}px, ${panY}px)`;
            targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`;


            const canvas = gradioApp().querySelector(
            const canvas = gradioApp().querySelector(
                `${elemId} canvas[key="interface"]`
                `${elemId} canvas[key="interface"]`
@@ -269,11 +344,14 @@ onUiLoaded(async() => {
        // Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables
        // Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables
        function updateZoom(newZoomLevel, mouseX, mouseY) {
        function updateZoom(newZoomLevel, mouseX, mouseY) {
            newZoomLevel = Math.max(0.5, Math.min(newZoomLevel, 15));
            newZoomLevel = Math.max(0.5, Math.min(newZoomLevel, 15));
            panX += mouseX - (mouseX * newZoomLevel) / zoomLevel;

            panY += mouseY - (mouseY * newZoomLevel) / zoomLevel;
            elemData[elemId].panX +=
                mouseX - (mouseX * newZoomLevel) / elemData[elemId].zoomLevel;
            elemData[elemId].panY +=
                mouseY - (mouseY * newZoomLevel) / elemData[elemId].zoomLevel;


            targetElement.style.transformOrigin = "0 0";
            targetElement.style.transformOrigin = "0 0";
            targetElement.style.transform = `translate(${panX}px, ${panY}px) scale(${newZoomLevel})`;
            targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${newZoomLevel})`;


            toggleOverlap("on");
            toggleOverlap("on");
            return newZoomLevel;
            return newZoomLevel;
@@ -289,9 +367,9 @@ onUiLoaded(async() => {


                let zoomPosX, zoomPosY;
                let zoomPosX, zoomPosY;
                let delta = 0.2;
                let delta = 0.2;
                if (zoomLevel > 7) {
                if (elemData[elemId].zoomLevel > 7) {
                    delta = 0.9;
                    delta = 0.9;
                } else if (zoomLevel > 2) {
                } else if (elemData[elemId].zoomLevel > 2) {
                    delta = 0.6;
                    delta = 0.6;
                }
                }


@@ -299,8 +377,9 @@ onUiLoaded(async() => {
                zoomPosY = e.clientY;
                zoomPosY = e.clientY;


                fullScreenMode = false;
                fullScreenMode = false;
                zoomLevel = updateZoom(
                elemData[elemId].zoomLevel = updateZoom(
                    zoomLevel + (operation === "+" ? delta : -delta),
                    elemData[elemId].zoomLevel +
                        (operation === "+" ? delta : -delta),
                    zoomPosX - targetElement.getBoundingClientRect().left,
                    zoomPosX - targetElement.getBoundingClientRect().left,
                    zoomPosY - targetElement.getBoundingClientRect().top
                    zoomPosY - targetElement.getBoundingClientRect().top
                );
                );
@@ -351,9 +430,9 @@ onUiLoaded(async() => {
            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;


            // Update global variables
            // Update global variables
            zoomLevel = scale;
            elemData[elemId].zoomLevel = scale;
            panX = offsetX;
            elemData[elemId].panX = offsetX;
            panY = offsetY;
            elemData[elemId].panY = offsetY;


            fullScreenMode = false;
            fullScreenMode = false;
            toggleOverlap("off");
            toggleOverlap("off");
@@ -427,9 +506,9 @@ onUiLoaded(async() => {
            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
            targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;


            // Update global variables
            // Update global variables
            zoomLevel = scale;
            elemData[elemId].zoomLevel = scale;
            panX = offsetX;
            elemData[elemId].panX = offsetX;
            panY = offsetY;
            elemData[elemId].panY = offsetY;


            fullScreenMode = true;
            fullScreenMode = true;
            toggleOverlap("on");
            toggleOverlap("on");
@@ -465,6 +544,8 @@ onUiLoaded(async() => {
            if (!isKeyDownHandlerAttached) {
            if (!isKeyDownHandlerAttached) {
                document.addEventListener("keydown", handleKeyDown);
                document.addEventListener("keydown", handleKeyDown);
                isKeyDownHandlerAttached = true;
                isKeyDownHandlerAttached = true;

                activeElement = elemId;
            }
            }
        }
        }


@@ -472,6 +553,8 @@ onUiLoaded(async() => {
            if (isKeyDownHandlerAttached) {
            if (isKeyDownHandlerAttached) {
                document.removeEventListener("keydown", handleKeyDown);
                document.removeEventListener("keydown", handleKeyDown);
                isKeyDownHandlerAttached = false;
                isKeyDownHandlerAttached = false;

                activeElement = null;
            }
            }
        }
        }


@@ -528,21 +611,24 @@ onUiLoaded(async() => {


        // Detect zoom level and update the pan speed.
        // Detect zoom level and update the pan speed.
        function updatePanPosition(movementX, movementY) {
        function updatePanPosition(movementX, movementY) {
            let panSpeed = 1.5;
            let panSpeed = 2;


            if (zoomLevel > 8) {
            if (elemData[elemId].zoomLevel > 8) {
                panSpeed = 2.5;
                panSpeed = 3.5;
            }
            }


            panX = panX + movementX * panSpeed;
            elemData[elemId].panX += movementX * panSpeed;
            panY = panY + movementY * panSpeed;
            elemData[elemId].panY += movementY * panSpeed;


            targetElement.style.transform = `translate(${panX}px, ${panY}px) scale(${zoomLevel})`;
            // Delayed redraw of an element
            requestAnimationFrame(() => {
                targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${elemData[elemId].zoomLevel})`;
                toggleOverlap("on");
                toggleOverlap("on");
            });
        }
        }


        function handleMoveByKey(e) {
        function handleMoveByKey(e) {
            if (isMoving) {
            if (isMoving && elemId === activeElement) {
                updatePanPosition(e.movementX, e.movementY);
                updatePanPosition(e.movementX, e.movementY);
                targetElement.style.pointerEvents = "none";
                targetElement.style.pointerEvents = "none";
            } else {
            } else {
@@ -558,7 +644,10 @@ onUiLoaded(async() => {
        gradioApp().addEventListener("mousemove", handleMoveByKey);
        gradioApp().addEventListener("mousemove", handleMoveByKey);
    }
    }


    applyZoomAndPan(elements.sketch, elementIDs.sketch);
    applyZoomAndPan(elementIDs.sketch);
    applyZoomAndPan(elements.inpaint, elementIDs.inpaint);
    applyZoomAndPan(elementIDs.inpaint);
    applyZoomAndPan(elements.inpaintSketch, elementIDs.inpaintSketch);
    applyZoomAndPan(elementIDs.inpaintSketch);

    // Make the function global so that other extensions can take advantage of this solution
    window.applyZoomAndPan = applyZoomAndPan;
});
});