diff --git a/CHANGES.md b/CHANGES.md index 9e72b44f..471a0c80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,14 @@ Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed. ### Detailed changelog +* 3.0.1 - 17 Aug 2023 - Fix broken embeddings with SDXL. +* 3.0.1 - 16 Aug 2023 - Fix broken LoRA with SDXL. +* 3.0.1 - 15 Aug 2023 - Fix broken seamless tiling. +* 3.0.1 - 15 Aug 2023 - Fix textual inversion embeddings not working in `low` VRAM usage mode. +* 3.0.1 - 15 Aug 2023 - Fix for custom VAEs not working in `low` VRAM usage mode. +* 3.0.1 - 14 Aug 2023 - Slider to change the image dimensions proportionally (in Image Settings). Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Show an error to the user if an embedding isn't compatible with the model, instead of failing silently without informing the user. Thanks @JeLuf. +* 3.0.1 - 14 Aug 2023 - Disable watermarking for SDXL img2img. Thanks @AvidGameFan. * 3.0.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2 engine can be used by disabling the "Use v3 engine" option in the Settings tab. * 2.5.48 - 1 Aug 2023 - (beta-only) Full support for ControlNets. You can select a control image to guide the AI. You can pick a filter to pre-process the image, and one of the known (or custom) controlnet models. Supports `OpenPose`, `Canny`, `Straight Lines`, `Depth`, `Line Art`, `Scribble`, `Soft Edge`, `Shuffle` and `Segment`. * 2.5.47 - 30 Jul 2023 - An option to use `Strict Mask Border` while inpainting, to avoid touching areas outside the mask. But this might show a slight outline of the mask, which you will have to touch up separately. diff --git a/scripts/check_modules.py b/scripts/check_modules.py index aecf7576..df752a02 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -18,7 +18,7 @@ os_name = platform.system() modules_to_check = { "torch": ("1.11.0", "1.13.1", "2.0.0"), "torchvision": ("0.12.0", "0.14.1", "0.15.1"), - "sdkit": "1.0.167", + "sdkit": "1.0.174", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", diff --git a/scripts/on_env_start.bat b/scripts/on_env_start.bat index 43f7e2b7..d0e915e2 100644 --- a/scripts/on_env_start.bat +++ b/scripts/on_env_start.bat @@ -46,6 +46,8 @@ if "%update_branch%"=="" ( @cd sd-ui-files + @call git add -A . + @call git stash @call git reset --hard @call git -c advice.detachedHead=false checkout "%update_branch%" @call git pull diff --git a/scripts/on_env_start.sh b/scripts/on_env_start.sh index 02428ce5..6fd61c26 100755 --- a/scripts/on_env_start.sh +++ b/scripts/on_env_start.sh @@ -29,6 +29,8 @@ if [ -f "scripts/install_status.txt" ] && [ `grep -c sd_ui_git_cloned scripts/in cd sd-ui-files + git add -A . + git stash git reset --hard git -c advice.detachedHead=false checkout "$update_branch" git pull diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py index 93bbbcd6..7fa52968 100644 --- a/ui/easydiffusion/bucket_manager.py +++ b/ui/easydiffusion/bucket_manager.py @@ -100,15 +100,16 @@ def init(): raise HTTPException(status_code=404, detail="Image not found") @server_api.get("/all_images") - def get_all_images(prompt: str = "", model: str = "", page: int = 0, images_per_page: int = 50, db: Session = Depends(get_db)): + def get_all_images(prompt: str = "", model: str = "", page: int = 0, images_per_page: int = 50, workspace : str = "default", db: Session = Depends(get_db)): from easydiffusion.easydb.mappings import GalleryImage - images = db.query(GalleryImage).order_by(GalleryImage.time_created.desc()) + images = db.query(GalleryImage).filter(GalleryImage.workspace == workspace).order_by(GalleryImage.time_created.desc()) if prompt != "": images = images.filter(GalleryImage.prompt.like("%"+prompt+"%")) if model != "": images = images.filter(GalleryImage.use_stable_diffusion_model.like("%"+model+"%")) images = images.offset(page*images_per_page).limit(images_per_page) return images.all() + def get_filename_from_url(url): path = urlparse(url).path diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index 845e9126..841b0cd2 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -65,9 +65,6 @@ def load_default_models(context: Context): runtime.set_vram_optimizations(context) - config = app.getConfig() - context.embeddings_path = os.path.join(app.MODELS_DIR, "embeddings") - # init default model paths for model_type in MODELS_TO_LOAD_ON_START: context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False) diff --git a/ui/easydiffusion/package_manager.py b/ui/easydiffusion/package_manager.py index de64b66c..72479379 100644 --- a/ui/easydiffusion/package_manager.py +++ b/ui/easydiffusion/package_manager.py @@ -12,9 +12,9 @@ from easydiffusion import app manifest = { "tensorrt": { "install": [ - "nvidia-cudnn --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt-libs --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt --pre --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", + "nvidia-cudnn --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt-libs --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", + "tensorrt --pre --extra-index-url=https://pypi.nvidia.com --trusted-host pypi.nvidia.com", ], "uninstall": ["tensorrt"], # TODO also uninstall tensorrt-libs and nvidia-cudnn, but do it upon restarting (avoid 'file in use' error) diff --git a/ui/easydiffusion/types.py b/ui/easydiffusion/types.py index fe936ca2..f7110f6b 100644 --- a/ui/easydiffusion/types.py +++ b/ui/easydiffusion/types.py @@ -73,6 +73,7 @@ class TaskData(BaseModel): use_hypernetwork_model: Union[str, List[str]] = None use_lora_model: Union[str, List[str]] = None use_controlnet_model: Union[str, List[str]] = None + use_embeddings_model: Union[str, List[str]] = None filters: List[str] = [] filter_params: Dict[str, Dict[str, Any]] = {} control_filter_to_apply: Union[str, List[str]] = None @@ -85,6 +86,7 @@ class TaskData(BaseModel): clip_skip: bool = False codeformer_upscale_faces: bool = False codeformer_fidelity: float = 0.5 + use_gallery: str = None class MergeRequest(BaseModel): @@ -200,6 +202,7 @@ def convert_legacy_render_req_to_new(old_req: dict): model_paths["hypernetwork"] = old_req.get("use_hypernetwork_model") model_paths["lora"] = old_req.get("use_lora_model") model_paths["controlnet"] = old_req.get("use_controlnet_model") + model_paths["embeddings"] = old_req.get("use_embeddings_model") model_paths["gfpgan"] = old_req.get("use_face_correction", "") model_paths["gfpgan"] = model_paths["gfpgan"] if "gfpgan" in model_paths["gfpgan"].lower() else None diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 7f668280..84a6c800 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -160,28 +160,30 @@ def save_images_to_disk( from easydiffusion.easydb.mappings import GalleryImage from easydiffusion.easydb.database import SessionLocal - session = SessionLocal() - session.add(GalleryImage( - path = path_i, - seed = metadata_entries[i]["seed"], - use_stable_diffusion_model = metadata_entries[i]["use_stable_diffusion_model"], - clip_skip = metadata_entries[i]["clip_skip"], - use_vae_model = metadata_entries[i]["use_vae_model"], - sampler_name = metadata_entries[i]["sampler_name"], - width = metadata_entries[i]["width"], - height = metadata_entries[i]["height"], - num_inference_steps = metadata_entries[i]["num_inference_steps"], - guidance_scale = metadata_entries[i]["guidance_scale"], - lora = createLoraString(metadata_entries, i), - use_hypernetwork_model = metadata_entries[i]["use_hypernetwork_model"], - tiling = metadata_entries[i]["tiling"], - use_face_correction = metadata_entries[i]["use_face_correction"], - use_upscale = metadata_entries[i]["use_upscale"], - prompt = metadata_entries[i]["prompt"], - negative_prompt = metadata_entries[i]["negative_prompt"] - )) - session.commit() - session.close() + if task_data.use_gallery != None: + session = SessionLocal() + session.add(GalleryImage( + path = path_i, + seed = metadata_entries[i]["seed"], + use_stable_diffusion_model = metadata_entries[i]["use_stable_diffusion_model"], + clip_skip = metadata_entries[i]["clip_skip"], + use_vae_model = metadata_entries[i]["use_vae_model"], + sampler_name = metadata_entries[i]["sampler_name"], + width = metadata_entries[i]["width"], + height = metadata_entries[i]["height"], + num_inference_steps = metadata_entries[i]["num_inference_steps"], + guidance_scale = metadata_entries[i]["guidance_scale"], + lora = createLoraString(metadata_entries, i), + use_hypernetwork_model = metadata_entries[i]["use_hypernetwork_model"], + tiling = metadata_entries[i]["tiling"], + use_face_correction = metadata_entries[i]["use_face_correction"], + use_upscale = metadata_entries[i]["use_upscale"], + prompt = metadata_entries[i]["prompt"], + negative_prompt = metadata_entries[i]["negative_prompt"], + workspace = task_data.use_gallery + )) + session.commit() + session.close() if task_data.metadata_output_format: for metadata_output_format in task_data.metadata_output_format.split(","): diff --git a/ui/index.html b/ui/index.html index 1819366e..b7492109 100644 --- a/ui/index.html +++ b/ui/index.html @@ -35,7 +35,7 @@

Easy Diffusion - v3.0.0 + v3.0.1

@@ -307,12 +307,22 @@ ×
+ Resize:
+
+
    
- Enlarge:
-
  
+
+
+ Recently used:
+
+
+
+
+ Common sizes:
+
+
+
- Recently used:
-
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index e7f1f6e1..f22911f3 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -34,6 +34,7 @@ code { width: 32px; height: 32px; transform: translateY(4px); + cursor: pointer; } #prompt { width: 100%; @@ -1031,6 +1032,9 @@ input::file-selector-button { .input-toggle > input:checked + label { background: var(--accent-color); } +.input-toggle > input:disabled + label { + background: var(--background-color1); +} .input-toggle > input:checked + label:before { right: calc(var(--input-border-size) + var(--input-switch-padding)); opacity: 1; @@ -1418,6 +1422,10 @@ div.task-fs-initimage { display: none; position: absolute; } +div.task-fs-initimage img { + max-height: 70vH; + max-width: 70vW; +} div.task-initimg:hover div.task-fs-initimage { display: block; position: absolute; @@ -1433,9 +1441,13 @@ div.top-right { right: 8px; } +button.useForControlnetBtn { + margin-top: 6px; +} + #small_image_warning { - font-size: smaller; - color: var(--status-orange); + font-size: smaller; + color: var(--status-orange); } button#save-system-settings-btn { @@ -1460,6 +1472,9 @@ button#save-system-settings-btn { cursor: pointer;; } +.validation-failed { + border: solid 2px red; +} /* SCROLLBARS */ :root { --scrollbar-width: 14px; @@ -1833,6 +1848,10 @@ div#recent-resolutions-popup small { opacity: 0.7; } +div#common-resolution-list button { + background: var(--background-color1); +} + td#image-size-options small { margin-right: 0px !important; } @@ -1849,6 +1868,27 @@ div#enlarge-buttons { text-align: center; } +.two-column { display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + gap: 0px 0.5em; + grid-auto-flow: row; + grid-template-areas: + "left-column right-column"; +} + +.left-column { + justify-self: center; + align-self: center; + grid-area: left-column; +} + +.right-column { + justify-self: center; + align-self: center; + grid-area: right-column; +} + .clickable { cursor: pointer; } @@ -1890,15 +1930,6 @@ div#enlarge-buttons { /* Gallery CSS */ -/* -.gallery { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - font-family: sans-serif; -} -*/ button:disabled { background-color: var(--secondary-button-background); } diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 56364a3d..2631d4c4 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -14,6 +14,7 @@ const SETTINGS_IDS_LIST = [ "num_outputs_parallel", "stable_diffusion_model", "clip_skip", + "use_gallery", "vae_model", "hypernetwork_model", "sampler_name", diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 2a394de4..7004357c 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -83,9 +83,9 @@ let customHeightField = document.querySelector("#custom-height") let recentResolutionsButton = document.querySelector("#recent-resolutions-button") let recentResolutionsPopup = document.querySelector("#recent-resolutions-popup") let recentResolutionList = document.querySelector("#recent-resolution-list") -let enlarge15Button = document.querySelector("#enlarge15") -let enlarge2Button = document.querySelector("#enlarge2") -let enlarge3Button = document.querySelector("#enlarge3") +let commonResolutionList = document.querySelector("#common-resolution-list") +let resizeSlider = document.querySelector("#resize-slider") +let enlargeButtons = document.querySelector("#enlarge-buttons") let swapWidthHeightButton = document.querySelector("#swap-width-height") let smallImageWarning = document.querySelector("#small_image_warning") let initImageSelector = document.querySelector("#init_image") @@ -152,7 +152,6 @@ let galleryPromptSearchField = document.querySelector("#gallery-prompt-search") let galleryModelSearchField = document.querySelector("#gallery-model-search") let galleryImageContainer = document.querySelector("#imagecontainer") - let positiveEmbeddingText = document.querySelector("#positive-embedding-text") let negativeEmbeddingText = document.querySelector("#negative-embedding-text") let embeddingsCollapsiblesBtn = document.querySelector("#embeddings-action-collapsibles-btn") @@ -199,6 +198,7 @@ let undoButton = document.querySelector("#undo") let undoBuffer = [] const UNDO_LIMIT = 20 const MAX_IMG_UNDO_ENTRIES = 5 +var GALLERY_NAME="default" let IMAGE_STEP_SIZE = 64 @@ -534,6 +534,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { const imageRedoBuffer = [] let buttons = [ { text: "Use as Input", on_click: onUseAsInputClick }, + { text: "Use for Controlnet", on_click: onUseForControlnetClick }, [ { html: ' Download Image', @@ -645,6 +646,10 @@ function onUseAsInputClick(req, img) { maskSetting.checked = false } +function onUseForControlnetClick(req, img) { + controlImagePreview.src = img.src +} + function getDownloadFilename(img, suffix) { const imageSeed = img.getAttribute("data-seed") const imagePrompt = img.getAttribute("data-prompt") @@ -955,12 +960,25 @@ function makeImage() { } if (!randomSeedField.checked && seedField.value == "") { alert('The "Seed" field must not be empty.') + seedField.classList.add("validation-failed") return } + seedField.classList.remove("validation-failed") + if (numInferenceStepsField.value == "") { alert('The "Inference Steps" field must not be empty.') + numInferenceStepsField.classList.add("validation-failed") return } + numInferenceStepsField.classList.remove("validation-failed") + + if (controlnetModelField.value === "" && IMAGE_REGEX.test(controlImagePreview.src)) { + alert("Please choose a ControlNet model, to use the ControlNet image.") + document.getElementById("controlnet_model").classList.add("validation-failed") + return + } + document.getElementById("controlnet_model").classList.remove("validation-failed") + if (numOutputsTotalField.value == "" || numOutputsTotalField.value == 0) { numOutputsTotalField.value = 1 } @@ -979,6 +997,7 @@ function makeImage() { reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody), }) ) + newTaskRequests.forEach(setEmbeddings) newTaskRequests.forEach(createTask) updateInitialText() @@ -1172,15 +1191,18 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { Suggestions:
Try to use a different model or a different LORA.` - } else if (msg.includes("Tensor on device cuda:0 is not on the expected device meta")) { + } else if (msg.includes("'ModuleList' object has no attribute '1'")) { msg += `

- Reason: Due to some software issues, embeddings currently don't work with the "Low" memory profile. + Reason: SDXL models need a yaml config file.

Suggestions:
- 1. Set the memory profile to "Balanced"
- 2. Remove the embeddings from the prompt and the negative prompt
- 3. Check whether the plugins you're using change the memory profile automatically.` +
    +
  1. Download the config file
  2. +
  3. Save it in the same directory as the SDXL model file
  4. +
  5. Rename the config file so that it matches the filename of the model, with the extension of the model file replaced by yaml. + For example, if the model file is called FantasySDXL_v2.safetensors, the config file must be called FantasySDXL_v2.yaml. +
` } } else { msg = `Unexpected Read Error:
StepUpdate: ${JSON.stringify(stepUpdate, undefined, 4)}
` @@ -1334,6 +1356,27 @@ async function onTaskStart(task) { /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry) { + taskEntry.querySelectorAll(".task-initimg").forEach( thumb => { + let thumbimg = thumb.querySelector("img") + let img = createElement("img", {src: thumbimg.src}) + thumb.querySelector(".task-fs-initimage").appendChild(img) + let div = createElement("div", undefined, ["top-right"]) + div.innerHTML = ` + +
+ ` + div.querySelector(".useAsInputBtn").addEventListener("click", e => { + e.preventDefault() + onUseAsInputClick(null, img) + }) + div.querySelector(".useForControlnetBtn").addEventListener("click", e => { + e.preventDefault() + controlImagePreview.src = img.src + }) + thumb.querySelector(".task-fs-initimage").appendChild(div) + }) + return + var $tooltip = $(taskEntry.querySelector(".task-fs-initimage")) var img = document.createElement("img") img.src = taskEntry.querySelector("div.task-initimg > img").src @@ -1398,6 +1441,11 @@ function createTask(task) { let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 taskConfig += `
` } + if (task.reqBody.control_image !== undefined) { + let h = 80 + let w = ((task.reqBody.width * h) / task.reqBody.height) >> 0 + taskConfig += `
` + } taskConfig += `
${createTaskConfig(task)}
` @@ -1448,7 +1496,7 @@ function createTask(task) { startY = e.target.closest(".imageTaskContainer").offsetTop }) - if (task.reqBody.init_image !== undefined) { + if (task.reqBody.init_image !== undefined || task.reqBody.control_image !== undefined) { createInitImageHover(taskEntry) } @@ -1542,6 +1590,7 @@ function getCurrentUserRequest() { output_quality: parseInt(outputQualityField.value), output_lossless: outputLosslessField.checked, metadata_output_format: metadataOutputFormatField.value, + use_gallery: useGalleryField.checked?GALLERY_NAME:null, original_prompt: promptField.value, active_tags: activeTags.map((x) => x.name), inactive_tags: activeTags.filter((tag) => tag.inactive === true).map((x) => x.name), @@ -1627,6 +1676,42 @@ function getCurrentUserRequest() { return newTask } +function setEmbeddings(task) { + let prompt = task.reqBody.prompt.toLowerCase() + let negativePrompt = task.reqBody.negative_prompt.toLowerCase() + let overallPrompt = (prompt + " " + negativePrompt).split(" ") + + let embeddingsTree = modelsOptions["embeddings"] + let embeddings = [] + function extract(entries, basePath = "") { + entries.forEach((e) => { + if (Array.isArray(e)) { + let path = basePath === "" ? basePath + e[0] : basePath + "/" + e[0] + extract(e[1], path) + } else { + let path = basePath === "" ? basePath + e : basePath + "/" + e + embeddings.push([e.toLowerCase().replace(" ", "_"), path]) + } + }) + } + extract(embeddingsTree) + + let embeddingPaths = [] + + embeddings.forEach((e) => { + let token = e[0] + let path = e[1] + + if (overallPrompt.includes(token)) { + embeddingPaths.push(path) + } + }) + + if (embeddingPaths.length > 0) { + task.reqBody.use_embeddings_model = embeddingPaths + } +} + function getModelInfo(models) { let modelInfo = models.map((e) => [e[0].value, e[1].value]) modelInfo = modelInfo.filter((e) => e[0].trim() !== "") @@ -2008,6 +2093,7 @@ function onDimensionChange() { diskPathField.disabled = !saveToDiskField.checked metadataOutputFormatField.disabled = !saveToDiskField.checked +useGalleryField.disabled = !saveToDiskField.checked gfpganModelField.disabled = !useFaceCorrectionField.checked useFaceCorrectionField.addEventListener("change", function(e) { @@ -2663,7 +2749,7 @@ function updateEmbeddingsList(filter = "") { let button if (iconlist.length==0) { button = document.createElement("button") - button.innerText="m" + button.innerText = m } else { let img = '/media/images/noimg.png' if (token in embIcon) { @@ -2976,38 +3062,27 @@ let recentResolutionsValues = [] ;(function() { ///// Init resolutions dropdown - function makeResolutionButtons() { - recentResolutionList.innerHTML = "" - recentResolutionsValues.forEach((el) => { - let button = document.createElement("button") - button.classList.add("tertiaryButton") - button.style.width = "8em" - button.innerHTML = `${el.w}×${el.h}` + + function makeResolutionButtons(listElement, resolutionList) { + listElement.innerHTML = "" + resolutionList.forEach((el) => { + let button = createElement("button", { style: "width: 8em;" }, "tertiaryButton", `${el.w}×${el.h}`) button.addEventListener("click", () => { customWidthField.value = el.w customHeightField.value = el.h hidePopup() }) - recentResolutionList.appendChild(button) - recentResolutionList.appendChild(document.createElement("br")) + listElement.appendChild(button) + listElement.appendChild(document.createElement("br")) }) - localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } - enlarge15Button.addEventListener("click", () => { - enlargeImageSize(1.5) - hidePopup() - }) - - enlarge2Button.addEventListener("click", () => { - enlargeImageSize(2) - hidePopup() - }) - - enlarge3Button.addEventListener("click", () => { - enlargeImageSize(3) - hidePopup() - }) + enlargeButtons.querySelectorAll("button").forEach((button) => + button.addEventListener("click", (e) => { + enlargeImageSize(parseFloat(button.dataset["factor"])) + hidePopup() + }) + ) customWidthField.addEventListener("change", () => { let w = customWidthField.value @@ -3034,25 +3109,29 @@ let recentResolutionsValues = [] recentResolutionsValues = recentResolutionsValues.slice(0, 8) localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) - makeResolutionButtons() + makeResolutionButtons(recentResolutionList, recentResolutionsValues) }) + const defaultResolutionsValues = [ + { w: 512, h: 512 }, + { w: 448, h: 640 }, + { w: 512, h: 768 }, + { w: 768, h: 512 }, + { w: 1024, h: 768 }, + { w: 768, h: 1024 }, + { w: 1024, h: 1024 }, + { w: 1920, h: 1080 }, + ] let _jsonstring = localStorage.recentResolutionsValues if (_jsonstring == undefined) { - recentResolutionsValues = [ - { w: 512, h: 512 }, - { w: 640, h: 448 }, - { w: 448, h: 640 }, - { w: 512, h: 768 }, - { w: 768, h: 512 }, - { w: 1024, h: 768 }, - { w: 768, h: 1024 }, - ] + recentResolutionsValues = defaultResolutionsValues localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } else { recentResolutionsValues = JSON.parse(localStorage.recentResolutionsValues) } - makeResolutionButtons() + + makeResolutionButtons(recentResolutionList, recentResolutionsValues) + makeResolutionButtons(commonResolutionList, defaultResolutionsValues) recentResolutionsValues.forEach((val) => { addImageSizeOption(val.w) @@ -3069,6 +3148,9 @@ let recentResolutionsValues = [] customWidthField.value = widthField.value customHeightField.value = heightField.value recentResolutionsPopup.classList.remove("displayNone") + resizeSlider.value = 1 + resizeSlider.dataset["w"] = widthField.value + resizeSlider.dataset["h"] = heightField.value document.addEventListener("click", processClick) } @@ -3087,6 +3169,20 @@ let recentResolutionsValues = [] } }) + resizeSlider.addEventListener("input", (e) => { + let w = parseInt(resizeSlider.dataset["w"]) + let h = parseInt(resizeSlider.dataset["h"]) + let factor = parseFloat(resizeSlider.value) + let step = customWidthField.step + + customWidthField.value = roundToMultiple(w * factor * factor, step) + customHeightField.value = roundToMultiple(h * factor * factor, step) + }) + + resizeSlider.addEventListener("change", (e) => { + hidePopup() + }) + swapWidthHeightButton.addEventListener("click", (event) => { let temp = widthField.value widthField.value = heightField.value @@ -3273,7 +3369,12 @@ function layoutGallery() { galleryModelSearchField.addEventListener("keyup", debounce(e => refreshGallery(true), 500)) galleryPromptSearchField.addEventListener("keyup", debounce(e => refreshGallery(true), 500)) - +galleryPageField.addEventListener("keyup", e => { + if (e.code === "Enter") { + e.preventDefault() + refreshGallery(false) + } +}) function refreshGallery(newsearch = false) { if (newsearch) { @@ -3281,6 +3382,7 @@ function refreshGallery(newsearch = false) { } galleryImageContainer.innerHTML = "" let params = new URLSearchParams({ + workspace: GALLERY_NAME, prompt: galleryPromptSearchField.value, model: galleryModelSearchField.value, page: galleryPageField.value diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 7fdf8632..ce91c045 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -97,6 +97,13 @@ var PARAMETERS = [ }, ], }, + { + id: "use_gallery", + type: ParameterType.checkbox, + label: "Save images to the gallery", + note: "Stores metadata of all images into a database so that they show up on the gallery tab.", + default: false, + }, { id: "block_nsfw", type: ParameterType.checkbox, @@ -421,6 +428,7 @@ let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_star let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions") let testDiffusers = document.querySelector("#test_diffusers") let profileNameField = document.querySelector("#profileName") +let useGalleryField = document.querySelector("#use_gallery") let saveSettingsBtn = document.querySelector("#save-system-settings-btn") @@ -560,6 +568,7 @@ function applySettingsFromConfig(config) { saveToDiskField.addEventListener("change", function(e) { diskPathField.disabled = !this.checked metadataOutputFormatField.disabled = !this.checked + useGalleryField.disabled = !this.checked }) function getCurrentRenderDeviceSelection() { diff --git a/ui/plugins/ui/image-editor-improvements.plugin.js b/ui/plugins/ui/image-editor-improvements.plugin.js index fbc7ad80..672ed77b 100644 --- a/ui/plugins/ui/image-editor-improvements.plugin.js +++ b/ui/plugins/ui/image-editor-improvements.plugin.js @@ -191,19 +191,32 @@ function createDropAreas(container) { // Create two drop areas - const dropAreaI2I = document.createElement("div") - dropAreaI2I.setAttribute("id", "drop-area-I2I") - dropAreaI2I.setAttribute("class", "drop-area") - dropAreaI2I.innerHTML = "Use as Image2Image source" + const dropAreaI2I = createElement("div", {id: "drop-area-I2I"}, ["drop-area"], "Use as Image2Image source") container.appendChild(dropAreaI2I) - const dropAreaMD = document.createElement("div") - dropAreaMD.setAttribute("id", "drop-area-MD") - dropAreaMD.setAttribute("class", "drop-area") - dropAreaMD.innerHTML = "Extract embedded metadata" + const dropAreaMD = createElement("div", {id: "drop-area-MD"}, ["drop-area"], "Extract embedded metadata") container.appendChild(dropAreaMD) + const dropAreaCN = createElement("div", {id: "drop-area-CN"}, ["drop-area"], "Use as Controlnet image") + container.appendChild(dropAreaCN) + // Add event listeners to drop areas + dropAreaCN.addEventListener("dragenter", function(event) { + event.preventDefault() + dropAreaCN.style.backgroundColor = 'darkGreen' + }) + dropAreaCN.addEventListener("dragleave", function(event) { + event.preventDefault() + dropAreaCN.style.backgroundColor = '' + }) + dropAreaCN.addEventListener("drop", function(event) { + event.stopPropagation() + event.preventDefault() + hideDropAreas() + + getImageFromDropEvent(event, e => controlImagePreview.src=e) + }) + dropAreaI2I.addEventListener("dragenter", function(event) { event.preventDefault() dropAreaI2I.style.backgroundColor = 'darkGreen' @@ -212,16 +225,12 @@ event.preventDefault() dropAreaI2I.style.backgroundColor = '' }) - - dropAreaI2I.addEventListener("drop", function(event) { - event.stopPropagation(); - event.preventDefault(); - hideDropAreas() - + + function getImageFromDropEvent(event, callback) { // Find the first image file, uri, or moz-url in the items list - let imageItem = null; + let imageItem = null for (let i = 0; i < event.dataTransfer.items.length; i++) { - let item = event.dataTransfer.items[i]; + let item = event.dataTransfer.items[i] if (item.kind === 'file' && item.type.startsWith('image/')) { imageItem = item; break; @@ -258,18 +267,22 @@ // Create a FileReader object to read the dropped file as a data URL let reader = new FileReader(); reader.onload = function(e) { - // Set the src attribute of the img element to the data URL - imageObj.src = e.target.result; + callback(e.target.result) }; reader.readAsDataURL(file); } else { // If the item is a URL, retrieve it and use it to load the image - imageItem.getAsString(function(url) { - // Set the src attribute of the img element to the URL - imageObj.src = url; - }); + imageItem.getAsString(callback) } - } + } + } + + dropAreaI2I.addEventListener("drop", function(event) { + event.stopPropagation() + event.preventDefault() + hideDropAreas() + + getImageFromDropEvent(event, e => imageObj.src=e) }) dropAreaMD.addEventListener("dragenter", function(event) {