| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | (function () { |
| | const cnetAllAccordions = new Set(); |
| | onUiUpdate(() => { |
| | const ImgChangeType = { |
| | NO_CHANGE: 0, |
| | REMOVE: 1, |
| | ADD: 2, |
| | SRC_CHANGE: 3, |
| | }; |
| |
|
| | function imgChangeObserved(mutationsList) { |
| | |
| | for (let mutation of mutationsList) { |
| | |
| | if (mutation.type === 'childList') { |
| | |
| | if (mutation.addedNodes.length > 0) { |
| | for (const node of mutation.addedNodes) { |
| | if (node.tagName === 'IMG') { |
| | return ImgChangeType.ADD; |
| | } |
| | } |
| | } |
| |
|
| | |
| | if (mutation.removedNodes.length > 0) { |
| | for (const node of mutation.removedNodes) { |
| | if (node.tagName === 'IMG') { |
| | return ImgChangeType.REMOVE; |
| | } |
| | } |
| | } |
| | } |
| | |
| | else if (mutation.type === 'attributes') { |
| | if (mutation.target.tagName === 'IMG' && mutation.attributeName === 'src') { |
| | return ImgChangeType.SRC_CHANGE; |
| | } |
| | } |
| | } |
| | return ImgChangeType.NO_CHANGE; |
| | } |
| |
|
| | function childIndex(element) { |
| | |
| | let children = Array.from(element.parentNode.childNodes); |
| |
|
| | |
| | children = children.filter(child => child.nodeType === Node.ELEMENT_NODE); |
| |
|
| | return children.indexOf(element); |
| | } |
| |
|
| | function imageInputDisabledAlert() { |
| | alert('Inpaint control type must use a1111 input in img2img mode.'); |
| | } |
| |
|
| | class ControlNetUnitTab { |
| | constructor(tab, accordion) { |
| | this.tab = tab; |
| | this.tabOpen = false; |
| | this.accordion = accordion; |
| | this.isImg2Img = tab.querySelector('.cnet-mask-upload').id.includes('img2img'); |
| |
|
| | this.enabledAccordionCheckbox = tab.querySelector('.input-accordion-checkbox'); |
| | this.enabledCheckbox = tab.querySelector('.cnet-unit-enabled input'); |
| | this.inputImage = tab.querySelector('.cnet-input-image-group .cnet-image input[type="file"]'); |
| | this.inputImageContainer = tab.querySelector('.cnet-input-image-group .cnet-image'); |
| | this.generatedImageGroup = tab.querySelector('.cnet-generated-image-group'); |
| | this.maskImageGroup = tab.querySelector('.cnet-mask-image-group'); |
| | this.inputImageGroup = tab.querySelector('.cnet-input-image-group'); |
| | this.controlTypeRadios = tab.querySelectorAll('.controlnet_control_type_filter_group input[type="radio"]'); |
| | this.resizeModeRadios = tab.querySelectorAll('.controlnet_resize_mode_radio input[type="radio"]'); |
| | this.runPreprocessorButton = tab.querySelector('.cnet-run-preprocessor'); |
| |
|
| | this.tabs = tab.parentNode; |
| | this.tabIndex = childIndex(tab); |
| |
|
| | |
| | |
| | |
| | this.enabledAccordionCheckbox.click(); |
| | this.enabledAccordionCheckbox.click(); |
| |
|
| | this.sync_enabled_checkbox(); |
| | this.attachEnabledButtonListener(); |
| | this.attachControlTypeRadioListener(); |
| | this.attachImageUploadListener(); |
| | this.attachImageStateChangeObserver(); |
| | this.attachA1111SendInfoObserver(); |
| | this.attachPresetDropdownObserver(); |
| | this.attachAccordionStateObserver(); |
| | } |
| |
|
| | |
| | |
| | |
| | sync_enabled_checkbox() { |
| | this.enabledCheckbox.addEventListener("change", () => { |
| | if (this.enabledAccordionCheckbox.checked != this.enabledCheckbox.checked) { |
| | this.enabledAccordionCheckbox.click(); |
| | } |
| | }); |
| | this.enabledAccordionCheckbox.addEventListener("change", () => { |
| | if (this.enabledCheckbox.checked != this.enabledAccordionCheckbox.checked) { |
| | this.enabledCheckbox.click(); |
| | } |
| | }); |
| | } |
| | |
| | |
| | |
| | getUnitHeaderTextElement() { |
| | return this.tab.querySelector( |
| | `:nth-child(${this.tabIndex + 1}) span.svelte-s1r2yt` |
| | ); |
| | } |
| |
|
| | getActiveControlType() { |
| | for (let radio of this.controlTypeRadios) { |
| | if (radio.checked) { |
| | return radio.value; |
| | } |
| | } |
| | return undefined; |
| | } |
| |
|
| | updateActiveState() { |
| | const unitHeader = this.getUnitHeaderTextElement(); |
| | if (!unitHeader) return; |
| |
|
| | if (this.enabledCheckbox.checked) { |
| | unitHeader.classList.add('cnet-unit-active'); |
| | } else { |
| | unitHeader.classList.remove('cnet-unit-active'); |
| | } |
| | } |
| |
|
| | updateActiveUnitCount() { |
| | function getActiveUnitCount(checkboxes) { |
| | let activeUnitCount = 0; |
| | for (const checkbox of checkboxes) { |
| | if (checkbox.checked) |
| | activeUnitCount++; |
| | } |
| | return activeUnitCount; |
| | } |
| |
|
| | const checkboxes = this.accordion.querySelectorAll('.cnet-unit-enabled input'); |
| | const span = this.accordion.querySelector('.label-wrap span'); |
| |
|
| | |
| | if (span.childNodes.length !== 1) { |
| | span.removeChild(span.lastChild); |
| | } |
| | |
| | const activeUnitCount = getActiveUnitCount(checkboxes); |
| | if (activeUnitCount > 0) { |
| | const div = document.createElement('div'); |
| | div.classList.add('cnet-badge'); |
| | div.classList.add('primary'); |
| | div.innerHTML = `${activeUnitCount} unit${activeUnitCount > 1 ? 's' : ''}`; |
| | span.appendChild(div); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | updateActiveControlType() { |
| | const unitHeader = this.getUnitHeaderTextElement(); |
| | if (!unitHeader) return; |
| |
|
| | |
| | const controlTypeSuffix = unitHeader.querySelector('.control-type-suffix'); |
| | if (controlTypeSuffix) controlTypeSuffix.remove(); |
| |
|
| | |
| | const controlType = this.getActiveControlType(); |
| | if (controlType === 'All') return; |
| |
|
| | const span = document.createElement('span'); |
| | span.innerHTML = `[${controlType}]`; |
| | span.classList.add('control-type-suffix'); |
| | unitHeader.appendChild(span); |
| | } |
| | getInputImageSrc() { |
| | const img = this.inputImageGroup.querySelector('.cnet-image img'); |
| | return img ? img.src : null; |
| | } |
| | getPreprocessorPreviewImageSrc() { |
| | const img = this.generatedImageGroup.querySelector('.cnet-image img'); |
| | return img ? img.src : null; |
| | } |
| | getMaskImageSrc() { |
| | function isEmptyCanvas(canvas) { |
| | if (!canvas) return true; |
| | const ctx = canvas.getContext('2d'); |
| | |
| | const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); |
| | const data = imageData.data; |
| | |
| | let isPureBlack = true; |
| | for (let i = 0; i < data.length; i += 4) { |
| | if (data[i] !== 0 || data[i + 1] !== 0 || data[i + 2] !== 0) { |
| | isPureBlack = false; |
| | break; |
| | } |
| | } |
| | return isPureBlack; |
| | } |
| | const maskImg = this.maskImageGroup.querySelector('.cnet-mask-image img'); |
| | |
| | const handDrawnMaskCanvas = this.maskImageGroup.querySelector('.cnet-mask-image canvas[key="mask"]'); |
| | |
| | const inputImageHandDrawnMaskCanvas = this.inputImageGroup.querySelector('.cnet-image canvas[key="mask"]'); |
| | if (!isEmptyCanvas(handDrawnMaskCanvas)) { |
| | return handDrawnMaskCanvas.toDataURL(); |
| | } else if (maskImg) { |
| | return maskImg.src; |
| | } else if (!isEmptyCanvas(inputImageHandDrawnMaskCanvas)) { |
| | return inputImageHandDrawnMaskCanvas.toDataURL(); |
| | } else { |
| | return null; |
| | } |
| | } |
| | setThumbnail(imgSrc, maskSrc) { |
| | if (!imgSrc) return; |
| | const unitHeader = this.getUnitHeaderTextElement(); |
| | if (!unitHeader) return; |
| | const img = document.createElement('img'); |
| | img.src = imgSrc; |
| | img.classList.add('cnet-thumbnail'); |
| | unitHeader.appendChild(img); |
| |
|
| | if (maskSrc) { |
| | const mask = document.createElement('img'); |
| | mask.src = maskSrc; |
| | mask.classList.add('cnet-thumbnail'); |
| | unitHeader.appendChild(mask); |
| | } |
| | } |
| | removeThumbnail() { |
| | const unitHeader = this.getUnitHeaderTextElement(); |
| | if (!unitHeader) return; |
| | const imgs = unitHeader.querySelectorAll('.cnet-thumbnail'); |
| | for (const img of imgs) { |
| | img.remove(); |
| | } |
| | } |
| | |
| | |
| | |
| | |
| | updateInputImageThumbnail() { |
| | if (!opts.controlnet_input_thumbnail) return; |
| | if (this.tabOpen) { |
| | this.removeThumbnail(); |
| | } else { |
| | this.setThumbnail(this.getInputImageSrc(), this.getMaskImageSrc()); |
| | } |
| | } |
| |
|
| | attachEnabledButtonListener() { |
| | this.enabledCheckbox.addEventListener('change', () => { |
| | this.updateActiveState(); |
| | this.updateActiveUnitCount(); |
| | }); |
| | } |
| |
|
| | attachControlTypeRadioListener() { |
| | for (const radio of this.controlTypeRadios) { |
| | radio.addEventListener('change', () => { |
| | this.updateActiveControlType(); |
| | }); |
| | } |
| | } |
| |
|
| | attachImageUploadListener() { |
| | |
| | this.inputImage.addEventListener('change', (event) => { |
| | if (!event.target.files) return; |
| | if (!this.enabledCheckbox.checked) |
| | this.enabledCheckbox.click(); |
| | }); |
| |
|
| | |
| | this.tab.querySelector('.cnet-upload-pose input').addEventListener('change', (event) => { |
| | if (!event.target.files) return; |
| | if (!this.enabledCheckbox.checked) |
| | this.enabledCheckbox.click(); |
| | }); |
| | } |
| |
|
| | attachImageStateChangeObserver() { |
| | new MutationObserver((mutationsList) => { |
| | const changeObserved = imgChangeObserved(mutationsList); |
| |
|
| | if (changeObserved === ImgChangeType.ADD) { |
| | |
| | this.runPreprocessorButton.removeAttribute("disabled"); |
| | this.runPreprocessorButton.title = 'Run preprocessor'; |
| | } |
| |
|
| | if (changeObserved === ImgChangeType.REMOVE) { |
| | |
| | this.runPreprocessorButton.setAttribute("disabled", true); |
| | this.runPreprocessorButton.title = "No ControlNet input image available"; |
| | } |
| | }).observe(this.inputImageContainer, { |
| | childList: true, |
| | subtree: true, |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | attachA1111SendInfoObserver() { |
| | const pasteButtons = gradioApp().querySelectorAll('#paste'); |
| | const pngButtons = gradioApp().querySelectorAll( |
| | this.isImg2Img ? |
| | '#img2img_tab, #inpaint_tab' : |
| | '#txt2img_tab' |
| | ); |
| |
|
| | for (const button of [...pasteButtons, ...pngButtons]) { |
| | button.addEventListener('click', () => { |
| | |
| | |
| | |
| | |
| | |
| | setTimeout(() => { |
| | this.updateActiveState(); |
| | this.updateActiveUnitCount(); |
| | }, 2000); |
| | }); |
| | } |
| | } |
| |
|
| | attachPresetDropdownObserver() { |
| | const presetDropDown = this.tab.querySelector('.cnet-preset-dropdown'); |
| |
|
| | new MutationObserver((mutationsList) => { |
| | for (const mutation of mutationsList) { |
| | if (mutation.removedNodes.length > 0) { |
| | setTimeout(() => { |
| | this.updateActiveState(); |
| | this.updateActiveUnitCount(); |
| | this.updateActiveControlType(); |
| | }, 1000); |
| | return; |
| | } |
| | } |
| | }).observe(presetDropDown, { |
| | childList: true, |
| | subtree: true, |
| | }); |
| | } |
| | |
| | |
| | |
| | attachAccordionStateObserver() { |
| | new MutationObserver((mutationsList) => { |
| | for(const mutation of mutationsList) { |
| | if (mutation.type === 'attributes' && mutation.attributeName === 'class') { |
| | const newState = mutation.target.classList.contains('open'); |
| | if (this.tabOpen != newState) { |
| | this.tabOpen = newState; |
| | if (newState) { |
| | this.onAccordionOpen(); |
| | } else { |
| | this.onAccordionClose(); |
| | } |
| | } |
| | } |
| | } |
| | }).observe(this.tab.querySelector('.label-wrap'), { attributes: true, attributeFilter: ['class'] }); |
| | } |
| |
|
| | onAccordionOpen() { |
| | this.updateInputImageThumbnail(); |
| | } |
| |
|
| | onAccordionClose() { |
| | this.updateInputImageThumbnail(); |
| | } |
| | } |
| |
|
| | gradioApp().querySelectorAll('#controlnet').forEach(accordion => { |
| | if (cnetAllAccordions.has(accordion)) return; |
| | const tabs = [...accordion.querySelectorAll('.input-accordion')] |
| | .map(tab => new ControlNetUnitTab(tab, accordion)); |
| |
|
| | |
| | |
| | const labelWrap = accordion.querySelector('.label-wrap'); |
| | const observerAccordionOpen = new MutationObserver(function (mutations) { |
| | for (const mutation of mutations) { |
| | if (mutation.target.classList.contains('open') && |
| | tabs.every(tab => !tab.enabledCheckbox.checked && |
| | !tab.tab.querySelector('.label-wrap').classList.contains('open')) |
| | ) { |
| | tabs[0].tab.querySelector('.label-wrap').click(); |
| | } |
| | } |
| | }); |
| | observerAccordionOpen.observe(labelWrap, { attributes: true, attributeFilter: ['class'] }); |
| |
|
| | cnetAllAccordions.add(accordion); |
| | }); |
| | }); |
| | })(); |