539 lines
19 KiB
JavaScript
539 lines
19 KiB
JavaScript
const inpaintAnything_waitForElement = async (parent, selector, exist) => {
|
|
return new Promise((resolve) => {
|
|
const observer = new MutationObserver(() => {
|
|
if (!!parent.querySelector(selector) != exist) {
|
|
return;
|
|
}
|
|
observer.disconnect();
|
|
resolve(undefined);
|
|
});
|
|
|
|
observer.observe(parent, {
|
|
childList: true,
|
|
subtree: true,
|
|
});
|
|
|
|
if (!!parent.querySelector(selector) == exist) {
|
|
resolve(undefined);
|
|
}
|
|
});
|
|
};
|
|
|
|
const inpaintAnything_waitForStyle = async (parent, selector, style) => {
|
|
return new Promise((resolve) => {
|
|
const observer = new MutationObserver(() => {
|
|
if (!parent.querySelector(selector) || !parent.querySelector(selector).style[style]) {
|
|
return;
|
|
}
|
|
observer.disconnect();
|
|
resolve(undefined);
|
|
});
|
|
|
|
observer.observe(parent, {
|
|
childList: true,
|
|
subtree: true,
|
|
attributes: true,
|
|
attributeFilter: ["style"],
|
|
});
|
|
|
|
if (!!parent.querySelector(selector) && !!parent.querySelector(selector).style[style]) {
|
|
resolve(undefined);
|
|
}
|
|
});
|
|
};
|
|
|
|
const inpaintAnything_timeout = (ms) => {
|
|
return new Promise(function (resolve, reject) {
|
|
setTimeout(() => reject("Timeout"), ms);
|
|
});
|
|
};
|
|
|
|
async function inpaintAnything_sendToInpaint() {
|
|
const waitForElementToBeInDocument = (parent, selector) =>
|
|
Promise.race([inpaintAnything_waitForElement(parent, selector, true), inpaintAnything_timeout(10000)]);
|
|
|
|
const waitForElementToBeRemoved = (parent, selector) =>
|
|
Promise.race([inpaintAnything_waitForElement(parent, selector, false), inpaintAnything_timeout(10000)]);
|
|
|
|
const updateGradioImage = async (element, url, name) => {
|
|
const blob = await (await fetch(url)).blob();
|
|
const file = new File([blob], name, { type: "image/png" });
|
|
const dt = new DataTransfer();
|
|
dt.items.add(file);
|
|
|
|
function getClearButton() {
|
|
let clearButton = null;
|
|
let clearLabel = null;
|
|
|
|
let allButtons = element.querySelectorAll("button");
|
|
if (allButtons.length > 0) {
|
|
for (let button of allButtons) {
|
|
let label = button.getAttribute("aria-label");
|
|
if (label && !label.includes("Edit") && !label.includes("Éditer")) {
|
|
clearButton = button;
|
|
clearLabel = label;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return [clearButton, clearLabel];
|
|
}
|
|
|
|
const [clearButton, clearLabel] = getClearButton();
|
|
|
|
if (clearButton) {
|
|
clearButton?.click();
|
|
await waitForElementToBeRemoved(element, `button[aria-label='${clearLabel}']`);
|
|
}
|
|
|
|
const input = element.querySelector("input[type='file']");
|
|
input.value = "";
|
|
input.files = dt.files;
|
|
input.dispatchEvent(
|
|
new Event("change", {
|
|
bubbles: true,
|
|
composed: true,
|
|
})
|
|
);
|
|
await waitForElementToBeInDocument(element, "button");
|
|
};
|
|
|
|
const inputImg = document.querySelector("#ia_input_image img");
|
|
const maskImg = document.querySelector("#mask_out_image img");
|
|
|
|
if (!inputImg || !maskImg) {
|
|
return;
|
|
}
|
|
|
|
const inputImgDataUrl = inputImg.src;
|
|
const maskImgDataUrl = maskImg.src;
|
|
|
|
window.scrollTo(0, 0);
|
|
switch_to_img2img_tab(4);
|
|
|
|
await waitForElementToBeInDocument(document.querySelector("#img2img_inpaint_upload_tab"), "#img_inpaint_base");
|
|
|
|
await updateGradioImage(document.querySelector("#img_inpaint_base"), inputImgDataUrl, "input.png");
|
|
await updateGradioImage(document.querySelector("#img_inpaint_mask"), maskImgDataUrl, "mask.png");
|
|
}
|
|
|
|
async function inpaintAnything_clearSamMask() {
|
|
const waitForElementToBeInDocument = (parent, selector) =>
|
|
Promise.race([inpaintAnything_waitForElement(parent, selector, true), inpaintAnything_timeout(1000)]);
|
|
|
|
const elemId = "#ia_sam_image";
|
|
|
|
const targetElement = document.querySelector(elemId);
|
|
if (!targetElement) {
|
|
return;
|
|
}
|
|
await waitForElementToBeInDocument(targetElement, "button[aria-label='Clear']");
|
|
|
|
targetElement.style.transform = null;
|
|
targetElement.style.zIndex = null;
|
|
targetElement.style.overflow = "auto";
|
|
|
|
const samMaskClear = targetElement.querySelector("button[aria-label='Clear']");
|
|
if (!samMaskClear) {
|
|
return;
|
|
}
|
|
const removeImageButton = targetElement.querySelector("button[aria-label='Remove Image']");
|
|
if (!removeImageButton) {
|
|
return;
|
|
}
|
|
samMaskClear?.click();
|
|
|
|
if (typeof inpaintAnything_clearSamMask.clickRemoveImage === "undefined") {
|
|
inpaintAnything_clearSamMask.clickRemoveImage = () => {
|
|
targetElement.style.transform = null;
|
|
targetElement.style.zIndex = null;
|
|
};
|
|
} else {
|
|
removeImageButton.removeEventListener("click", inpaintAnything_clearSamMask.clickRemoveImage);
|
|
}
|
|
removeImageButton.addEventListener("click", inpaintAnything_clearSamMask.clickRemoveImage);
|
|
}
|
|
|
|
async function inpaintAnything_clearSelMask() {
|
|
const waitForElementToBeInDocument = (parent, selector) =>
|
|
Promise.race([inpaintAnything_waitForElement(parent, selector, true), inpaintAnything_timeout(1000)]);
|
|
|
|
const elemId = "#ia_sel_mask";
|
|
|
|
const targetElement = document.querySelector(elemId);
|
|
if (!targetElement) {
|
|
return;
|
|
}
|
|
await waitForElementToBeInDocument(targetElement, "button[aria-label='Clear']");
|
|
|
|
targetElement.style.transform = null;
|
|
targetElement.style.zIndex = null;
|
|
targetElement.style.overflow = "auto";
|
|
|
|
const selMaskClear = targetElement.querySelector("button[aria-label='Clear']");
|
|
if (!selMaskClear) {
|
|
return;
|
|
}
|
|
const removeImageButton = targetElement.querySelector("button[aria-label='Remove Image']");
|
|
if (!removeImageButton) {
|
|
return;
|
|
}
|
|
selMaskClear?.click();
|
|
|
|
if (typeof inpaintAnything_clearSelMask.clickRemoveImage === "undefined") {
|
|
inpaintAnything_clearSelMask.clickRemoveImage = () => {
|
|
targetElement.style.transform = null;
|
|
targetElement.style.zIndex = null;
|
|
};
|
|
} else {
|
|
removeImageButton.removeEventListener("click", inpaintAnything_clearSelMask.clickRemoveImage);
|
|
}
|
|
removeImageButton.addEventListener("click", inpaintAnything_clearSelMask.clickRemoveImage);
|
|
}
|
|
|
|
async function inpaintAnything_initSamSelMask() {
|
|
inpaintAnything_clearSamMask();
|
|
inpaintAnything_clearSelMask();
|
|
}
|
|
|
|
async function inpaintAnything_getPrompt(tabName, promptId, negPromptId) {
|
|
const tabTxt2img = document.querySelector(`#tab_${tabName}`);
|
|
if (!tabTxt2img) {
|
|
return;
|
|
}
|
|
|
|
const txt2imgPrompt = tabTxt2img.querySelector(`#${tabName}_prompt textarea`);
|
|
const txt2imgNegPrompt = tabTxt2img.querySelector(`#${tabName}_neg_prompt textarea`);
|
|
if (!txt2imgPrompt || !txt2imgNegPrompt) {
|
|
return;
|
|
}
|
|
|
|
const iaSdPrompt = document.querySelector(`#${promptId} textarea`);
|
|
const iaSdNPrompt = document.querySelector(`#${negPromptId} textarea`);
|
|
if (!iaSdPrompt || !iaSdNPrompt) {
|
|
return;
|
|
}
|
|
|
|
iaSdPrompt.value = txt2imgPrompt.value;
|
|
iaSdNPrompt.value = txt2imgNegPrompt.value;
|
|
|
|
iaSdPrompt.dispatchEvent(new Event("input", { bubbles: true }));
|
|
iaSdNPrompt.dispatchEvent(new Event("input", { bubbles: true }));
|
|
}
|
|
|
|
async function inpaintAnything_getTxt2imgPrompt() {
|
|
inpaintAnything_getPrompt("txt2img", "ia_sd_prompt", "ia_sd_n_prompt");
|
|
}
|
|
|
|
async function inpaintAnything_getImg2imgPrompt() {
|
|
inpaintAnything_getPrompt("img2img", "ia_sd_prompt", "ia_sd_n_prompt");
|
|
}
|
|
|
|
async function inpaintAnything_webuiGetTxt2imgPrompt() {
|
|
inpaintAnything_getPrompt("txt2img", "ia_webui_sd_prompt", "ia_webui_sd_n_prompt");
|
|
}
|
|
|
|
async function inpaintAnything_webuiGetImg2imgPrompt() {
|
|
inpaintAnything_getPrompt("img2img", "ia_webui_sd_prompt", "ia_webui_sd_n_prompt");
|
|
}
|
|
|
|
async function inpaintAnything_cnGetTxt2imgPrompt() {
|
|
inpaintAnything_getPrompt("txt2img", "ia_cn_sd_prompt", "ia_cn_sd_n_prompt");
|
|
}
|
|
|
|
async function inpaintAnything_cnGetImg2imgPrompt() {
|
|
inpaintAnything_getPrompt("img2img", "ia_cn_sd_prompt", "ia_cn_sd_n_prompt");
|
|
}
|
|
|
|
onUiLoaded(async () => {
|
|
const elementIDs = {
|
|
ia_sam_image: "#ia_sam_image",
|
|
ia_sel_mask: "#ia_sel_mask",
|
|
ia_out_image: "#ia_out_image",
|
|
ia_cleaner_out_image: "#ia_cleaner_out_image",
|
|
ia_webui_out_image: "#ia_webui_out_image",
|
|
ia_cn_out_image: "#ia_cn_out_image",
|
|
};
|
|
|
|
function setStyleHeight(elemId, height) {
|
|
const elem = gradioApp().querySelector(elemId);
|
|
if (elem) {
|
|
if (!elem.style.height) {
|
|
elem.style.height = height;
|
|
const observer = new MutationObserver(() => {
|
|
const divPreview = elem.querySelector(".preview");
|
|
if (divPreview) {
|
|
divPreview.classList.remove("fixed-height");
|
|
}
|
|
});
|
|
observer.observe(elem, {
|
|
childList: true,
|
|
attributes: true,
|
|
attributeFilter: ["class"],
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
setStyleHeight(elementIDs.ia_out_image, "520px");
|
|
setStyleHeight(elementIDs.ia_cleaner_out_image, "520px");
|
|
setStyleHeight(elementIDs.ia_webui_out_image, "520px");
|
|
setStyleHeight(elementIDs.ia_cn_out_image, "520px");
|
|
|
|
// Default config
|
|
const defaultHotkeysConfig = {
|
|
canvas_hotkey_reset: "KeyR",
|
|
canvas_hotkey_fullscreen: "KeyS",
|
|
};
|
|
|
|
const elemData = {};
|
|
let activeElement;
|
|
|
|
function applyZoomAndPan(elemId) {
|
|
const targetElement = gradioApp().querySelector(elemId);
|
|
|
|
if (!targetElement) {
|
|
console.log("Element not found");
|
|
return;
|
|
}
|
|
|
|
targetElement.style.transformOrigin = "0 0";
|
|
|
|
elemData[elemId] = {
|
|
zoomLevel: 1,
|
|
panX: 0,
|
|
panY: 0,
|
|
};
|
|
let fullScreenMode = false;
|
|
|
|
// Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements
|
|
function toggleOverlap(forced = "") {
|
|
// const zIndex1 = "0";
|
|
const zIndex1 = null;
|
|
const zIndex2 = "998";
|
|
|
|
targetElement.style.zIndex = targetElement.style.zIndex !== zIndex2 ? zIndex2 : zIndex1;
|
|
|
|
if (forced === "off") {
|
|
targetElement.style.zIndex = zIndex1;
|
|
} else if (forced === "on") {
|
|
targetElement.style.zIndex = zIndex2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This function fits the target element to the screen by calculating
|
|
* the required scale and offsets. It also updates the global variables
|
|
* zoomLevel, panX, and panY to reflect the new state.
|
|
*/
|
|
|
|
function fitToElement() {
|
|
//Reset Zoom
|
|
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
|
|
|
// Get element and screen dimensions
|
|
const elementWidth = targetElement.offsetWidth;
|
|
const elementHeight = targetElement.offsetHeight;
|
|
const parentElement = targetElement.parentElement;
|
|
const screenWidth = parentElement.clientWidth;
|
|
const screenHeight = parentElement.clientHeight;
|
|
|
|
// Get element's coordinates relative to the parent element
|
|
const elementRect = targetElement.getBoundingClientRect();
|
|
const parentRect = parentElement.getBoundingClientRect();
|
|
const elementX = elementRect.x - parentRect.x;
|
|
|
|
// Calculate scale and offsets
|
|
const scaleX = screenWidth / elementWidth;
|
|
const scaleY = screenHeight / elementHeight;
|
|
const scale = Math.min(scaleX, scaleY);
|
|
|
|
const transformOrigin = window.getComputedStyle(targetElement).transformOrigin;
|
|
const [originX, originY] = transformOrigin.split(" ");
|
|
const originXValue = parseFloat(originX);
|
|
const originYValue = parseFloat(originY);
|
|
|
|
const offsetX = (screenWidth - elementWidth * scale) / 2 - originXValue * (1 - scale);
|
|
const offsetY = (screenHeight - elementHeight * scale) / 2.5 - originYValue * (1 - scale);
|
|
|
|
// Apply scale and offsets to the element
|
|
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
|
|
// Update global variables
|
|
elemData[elemId].zoomLevel = scale;
|
|
elemData[elemId].panX = offsetX;
|
|
elemData[elemId].panY = offsetY;
|
|
|
|
fullScreenMode = false;
|
|
toggleOverlap("off");
|
|
}
|
|
|
|
// Reset the zoom level and pan position of the target element to their initial values
|
|
function resetZoom() {
|
|
elemData[elemId] = {
|
|
zoomLevel: 1,
|
|
panX: 0,
|
|
panY: 0,
|
|
};
|
|
|
|
// fixCanvas();
|
|
targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`;
|
|
|
|
// const canvas = gradioApp().querySelector(`${elemId} canvas[key="interface"]`);
|
|
|
|
toggleOverlap("off");
|
|
fullScreenMode = false;
|
|
|
|
// if (
|
|
// canvas &&
|
|
// parseFloat(canvas.style.width) > 865 &&
|
|
// parseFloat(targetElement.style.width) > 865
|
|
// ) {
|
|
// fitToElement();
|
|
// return;
|
|
// }
|
|
|
|
// targetElement.style.width = "";
|
|
// if (canvas) {
|
|
// targetElement.style.height = canvas.style.height;
|
|
// }
|
|
targetElement.style.width = null;
|
|
targetElement.style.height = 480;
|
|
}
|
|
|
|
/**
|
|
* This function fits the target element to the screen by calculating
|
|
* the required scale and offsets. It also updates the global variables
|
|
* zoomLevel, panX, and panY to reflect the new state.
|
|
*/
|
|
|
|
// Fullscreen mode
|
|
function fitToScreen() {
|
|
const canvas = gradioApp().querySelector(`${elemId} canvas[key="interface"]`);
|
|
const img = gradioApp().querySelector(`${elemId} img`);
|
|
|
|
if (!canvas && !img) return;
|
|
|
|
// if (canvas.offsetWidth > 862) {
|
|
// targetElement.style.width = canvas.offsetWidth + "px";
|
|
// }
|
|
|
|
if (fullScreenMode) {
|
|
resetZoom();
|
|
fullScreenMode = false;
|
|
return;
|
|
}
|
|
|
|
//Reset Zoom
|
|
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
|
|
|
// Get scrollbar width to right-align the image
|
|
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
|
|
// Get element and screen dimensions
|
|
const elementWidth = targetElement.offsetWidth;
|
|
const elementHeight = targetElement.offsetHeight;
|
|
const screenWidth = window.innerWidth - scrollbarWidth;
|
|
const screenHeight = window.innerHeight;
|
|
|
|
// Get element's coordinates relative to the page
|
|
const elementRect = targetElement.getBoundingClientRect();
|
|
const elementY = elementRect.y;
|
|
const elementX = elementRect.x;
|
|
|
|
// Calculate scale and offsets
|
|
const scaleX = screenWidth / elementWidth;
|
|
const scaleY = screenHeight / elementHeight;
|
|
const scale = Math.min(scaleX, scaleY);
|
|
|
|
// Get the current transformOrigin
|
|
const computedStyle = window.getComputedStyle(targetElement);
|
|
const transformOrigin = computedStyle.transformOrigin;
|
|
const [originX, originY] = transformOrigin.split(" ");
|
|
const originXValue = parseFloat(originX);
|
|
const originYValue = parseFloat(originY);
|
|
|
|
// Calculate offsets with respect to the transformOrigin
|
|
const offsetX = (screenWidth - elementWidth * scale) / 2 - elementX - originXValue * (1 - scale);
|
|
const offsetY = (screenHeight - elementHeight * scale) / 2 - elementY - originYValue * (1 - scale);
|
|
|
|
// Apply scale and offsets to the element
|
|
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
|
|
|
// Update global variables
|
|
elemData[elemId].zoomLevel = scale;
|
|
elemData[elemId].panX = offsetX;
|
|
elemData[elemId].panY = offsetY;
|
|
|
|
fullScreenMode = true;
|
|
toggleOverlap("on");
|
|
}
|
|
|
|
// Reset zoom when uploading a new image
|
|
const fileInput = gradioApp().querySelector(`${elemId} input[type="file"][accept="image/*"].svelte-116rqfv`);
|
|
if (fileInput) {
|
|
fileInput.addEventListener("click", resetZoom);
|
|
}
|
|
|
|
// Handle keydown events
|
|
function handleKeyDown(event) {
|
|
// Disable key locks to make pasting from the buffer work correctly
|
|
if (
|
|
(event.ctrlKey && event.code === "KeyV") ||
|
|
(event.ctrlKey && event.code === "KeyC") ||
|
|
event.code === "F5"
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// before activating shortcut, ensure user is not actively typing in an input field
|
|
if (event.target.nodeName === "TEXTAREA" || event.target.nodeName === "INPUT") {
|
|
return;
|
|
}
|
|
|
|
const hotkeyActions = {
|
|
[defaultHotkeysConfig.canvas_hotkey_reset]: resetZoom,
|
|
[defaultHotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen,
|
|
};
|
|
|
|
const action = hotkeyActions[event.code];
|
|
if (action) {
|
|
event.preventDefault();
|
|
action(event);
|
|
}
|
|
}
|
|
|
|
// Handle events only inside the targetElement
|
|
let isKeyDownHandlerAttached = false;
|
|
|
|
function handleMouseMove() {
|
|
if (!isKeyDownHandlerAttached) {
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
isKeyDownHandlerAttached = true;
|
|
|
|
activeElement = elemId;
|
|
}
|
|
}
|
|
|
|
function handleMouseLeave() {
|
|
if (isKeyDownHandlerAttached) {
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
isKeyDownHandlerAttached = false;
|
|
|
|
activeElement = null;
|
|
}
|
|
}
|
|
|
|
// Add mouse event handlers
|
|
targetElement.addEventListener("mousemove", handleMouseMove);
|
|
targetElement.addEventListener("mouseleave", handleMouseLeave);
|
|
}
|
|
|
|
applyZoomAndPan(elementIDs.ia_sam_image);
|
|
applyZoomAndPan(elementIDs.ia_sel_mask);
|
|
// applyZoomAndPan(elementIDs.ia_out_image);
|
|
// applyZoomAndPan(elementIDs.ia_cleaner_out_image);
|
|
// applyZoomAndPan(elementIDs.ia_webui_out_image);
|
|
// applyZoomAndPan(elementIDs.ia_cn_out_image);
|
|
});
|