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:
+
+
×0.5 ×1.2 ×1.5 ×2 ×3
-
Enlarge:
-
×1.5 ×2 ×3
+
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.`
+
+ Download the config file
+ Save it in the same directory as the SDXL model file
+ 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 = `
+ Use as Input
+
+ Use for Controlnet `
+ 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) {