From cc7452374d33c28c5a74736477f70617cc6cbcec Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 17:43:49 +0530 Subject: [PATCH 001/120] Remove hypernetworks from the UI options in diffusers. Sorry --- ui/index.html | 2 +- ui/media/js/parameters.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 7cedc0e5..856150c6 100644 --- a/ui/index.html +++ b/ui/index.html @@ -324,7 +324,7 @@ - + diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 098eb908..9db01807 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -462,6 +462,7 @@ async function getAppConfig() { document.querySelector("#lora_model_container").style.display = "none" document.querySelector("#tiling_container").style.display = "none" document.querySelector("#controlnet_model_container").style.display = "none" + document.querySelector("#hypernetwork_model_container").style.display = "" document.querySelector("#hypernetwork_strength_container").style.display = "" document.querySelectorAll("#sampler_name option.diffusers-only").forEach((option) => { @@ -474,6 +475,7 @@ async function getAppConfig() { document.querySelector("#lora_model_container").style.display = "" document.querySelector("#tiling_container").style.display = "" document.querySelector("#controlnet_model_container").style.display = "" + document.querySelector("#hypernetwork_model_container").style.display = "none" document.querySelector("#hypernetwork_strength_container").style.display = "none" document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach((option) => { From 21297d98f24a05b5624dc728d88e60f4fc1e37ef Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 18:38:25 +0530 Subject: [PATCH 002/120] Option to disable LoRA tag parsing --- ui/media/js/auto-save.js | 1 + ui/media/js/parameters.js | 9 +++++++++ ui/plugins/ui/lora-prompt-parser.plugin.js | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index 670fee0d..f70b19c7 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -54,6 +54,7 @@ const SETTINGS_IDS_LIST = [ "zip_toggle", "tree_toggle", "json_toggle", + "extract_lora_from_prompt", ] const IGNORE_BY_DEFAULT = ["prompt"] diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 9db01807..671451eb 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -121,6 +121,15 @@ var PARAMETERS = [ icon: "fa-arrow-down-short-wide", default: false, }, + { + id: "extract_lora_from_prompt", + type: ParameterType.checkbox, + label: "Extract LoRA tags from the prompt", + note: + "Automatically extract lora tags like <lora:name:0.4> from the prompt, and apply the correct LoRA (if present)", + icon: "fa-code", + default: true, + }, { id: "ui_open_browser_on_start", type: ParameterType.checkbox, diff --git a/ui/plugins/ui/lora-prompt-parser.plugin.js b/ui/plugins/ui/lora-prompt-parser.plugin.js index 5030b74c..201d49af 100644 --- a/ui/plugins/ui/lora-prompt-parser.plugin.js +++ b/ui/plugins/ui/lora-prompt-parser.plugin.js @@ -8,6 +8,11 @@ "use strict" promptField.addEventListener('input', function(e) { + let loraExtractSetting = document.getElementById("extract_lora_from_prompt") + if (!loraExtractSetting.checked) { + return + } + const { LoRA, prompt } = extractLoraTags(e.target.value); //console.log('e.target: ' + JSON.stringify(LoRA)); From d47816e7b94e9450653b49384a68cac41a631327 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 18:47:58 +0530 Subject: [PATCH 003/120] sdkit 1.0.166 - check for lora to SD model compatibility only for text-based loras --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 04d1309b..89291293 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.165", + "sdkit": "1.0.166", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From fcb59c68d4ebf14ca71d3667e5d91bd178fbd96a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 19:42:56 +0530 Subject: [PATCH 004/120] sdkit 1.0.167 - fix reversing loras --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 89291293..a5edf5f5 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.166", + "sdkit": "1.0.167", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 2d2a75f23ca6ec9ec7f4cbc7c91be2732f4a8696 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 20:01:27 +0530 Subject: [PATCH 005/120] v2.6.0 (beta) - switch beta to use diffusers by default --- ui/easydiffusion/app.py | 3 ++- ui/easydiffusion/runtime.py | 2 +- ui/easydiffusion/server.py | 2 +- ui/easydiffusion/utils/save_utils.py | 2 +- ui/index.html | 2 +- ui/media/js/main.js | 4 ---- ui/media/js/parameters.js | 11 +++++++---- 7 files changed, 13 insertions(+), 13 deletions(-) diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index e181f9b8..aa1c5ba7 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -60,6 +60,7 @@ APP_CONFIG_DEFAULTS = { "ui": { "open_browser_on_start": True, }, + "test_diffusers": True, } IMAGE_EXTENSIONS = [ @@ -115,7 +116,7 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS): def set_config_on_startup(config: dict): if getConfig.__test_diffusers_on_startup is None: - getConfig.__test_diffusers_on_startup = config.get("test_diffusers", False) + getConfig.__test_diffusers_on_startup = config.get("test_diffusers", True) config["config_on_startup"] = {"test_diffusers": getConfig.__test_diffusers_on_startup} if os.path.isfile(config_yaml_path): diff --git a/ui/easydiffusion/runtime.py b/ui/easydiffusion/runtime.py index 4098ee8e..8d2f0186 100644 --- a/ui/easydiffusion/runtime.py +++ b/ui/easydiffusion/runtime.py @@ -31,7 +31,7 @@ def init(device): app_config = app.getConfig() context.test_diffusers = ( - app_config.get("test_diffusers", False) and app_config.get("update_branch", "main") != "main" + app_config.get("test_diffusers", True) and app_config.get("update_branch", "main") != "main" ) log.info("Device usage during initialization:") diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index a8f848fd..1ecbbbd3 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -63,7 +63,7 @@ class SetAppConfigRequest(BaseModel, extra=Extra.allow): ui_open_browser_on_start: bool = None listen_to_network: bool = None listen_port: int = None - test_diffusers: bool = False + test_diffusers: bool = True def init(): diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 89dae991..090aef0f 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -219,7 +219,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output task_data_metadata.update(output_format.dict()) app_config = app.getConfig() - using_diffusers = app_config.get("test_diffusers", False) + using_diffusers = app_config.get("test_diffusers", True) # Save the metadata in the order defined in TASK_TEXT_MAPPING metadata = {} diff --git a/ui/index.html b/ui/index.html index 856150c6..da074f90 100644 --- a/ui/index.html +++ b/ui/index.html @@ -32,7 +32,7 @@

Easy Diffusion - v2.5.48 + v2.6.0

diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 802cfb55..6ed8d506 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2648,10 +2648,6 @@ embeddingsCollapsiblesBtn.addEventListener("click", (e) => { } }) -if (testDiffusers.checked) { - document.getElementById("embeddings-container").classList.remove("displayNone") -} - /* Pause function */ document.querySelectorAll(".tab").forEach(linkTabContents) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 671451eb..bda2f99b 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -229,11 +229,11 @@ var PARAMETERS = [ { id: "test_diffusers", type: ParameterType.checkbox, - label: "Test Diffusers", + label: "Use the new v2.6 engine (diffusers)", note: - "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.", + "Use our new v2.6 engine, with additional features like LoRA, ControlNet, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.", icon: "fa-bolt", - default: false, + default: true, saveInAppConfig: true, }, { @@ -454,7 +454,10 @@ async function getAppConfig() { listenPortField.value = config.net.listen_port } - const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main" + let testDiffusersEnabled = config.update_branch !== "main" + if (config.test_diffusers === false) { + testDiffusersEnabled = false + } testDiffusers.checked = testDiffusersEnabled if (config.config_on_startup) { From abdf0b67195e1cfc1579d1c3781c4badae6e9681 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 20:02:52 +0530 Subject: [PATCH 006/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 141c0773..477067b1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 +* 2.6.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2.5 engine can be used by disabling the "Use v2.6 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. * 2.5.47 - 29 Jul 2023 - (beta-only) Fix long prompts with SDXL. From df518f822c3d44305531cbd3273cee9ea6d5f1f2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 20:11:27 +0530 Subject: [PATCH 007/120] SDXL --- ui/media/js/parameters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index bda2f99b..2678bb91 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -231,7 +231,7 @@ var PARAMETERS = [ type: ParameterType.checkbox, label: "Use the new v2.6 engine (diffusers)", note: - "Use our new v2.6 engine, with additional features like LoRA, ControlNet, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.", + "Use our new v2.6 engine, with additional features like LoRA, ControlNet, SDXL, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.", icon: "fa-bolt", default: true, saveInAppConfig: true, From 3216a68d63aa97d6404f7b82a8b83bdcea65928f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 3 Aug 2023 21:01:21 +0530 Subject: [PATCH 008/120] Fix regression with new installations not being able to start ED --- ui/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/main.py b/ui/main.py index f5998622..8647b9ff 100644 --- a/ui/main.py +++ b/ui/main.py @@ -1,11 +1,12 @@ from easydiffusion import model_manager, app, server from easydiffusion.server import server_api # required for uvicorn +app.init() + server.init() # Init the app model_manager.init() -app.init() app.init_render_threads() # start the browser ui From 7d71c353b29cef2a7c02334bce6bd319ac11ddf0 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 4 Aug 2023 11:19:35 +0530 Subject: [PATCH 009/120] changelog --- CHANGES.md | 2 +- ui/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 477067b1..9e72b44f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,7 @@ 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 -* 2.6.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2.5 engine can be used by disabling the "Use v2.6 engine" option in the Settings tab. +* 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. * 2.5.47 - 29 Jul 2023 - (beta-only) Fix long prompts with SDXL. diff --git a/ui/index.html b/ui/index.html index da074f90..d5ee2ae6 100644 --- a/ui/index.html +++ b/ui/index.html @@ -32,7 +32,7 @@

Easy Diffusion - v2.6.0 + v3.0.0

From b6e512e65f10e04ffbaeaa150bfeecfd89b98398 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 4 Aug 2023 11:21:31 +0530 Subject: [PATCH 010/120] v3 engine name --- ui/media/js/parameters.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 2678bb91..c66aedc1 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -229,9 +229,9 @@ var PARAMETERS = [ { id: "test_diffusers", type: ParameterType.checkbox, - label: "Use the new v2.6 engine (diffusers)", + label: "Use the new v3 engine (diffusers)", note: - "Use our new v2.6 engine, with additional features like LoRA, ControlNet, SDXL, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.", + "Use our new v3 engine, with additional features like LoRA, ControlNet, SDXL, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.", icon: "fa-bolt", default: true, saveInAppConfig: true, From 20b06db35985a77107a02ee76d30c5c4db226c9e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 4 Aug 2023 19:46:37 +0530 Subject: [PATCH 011/120] Include onnx and polygraphy for TRT, and allow skipping the wheels for TRT --- ui/easydiffusion/package_manager.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/easydiffusion/package_manager.py b/ui/easydiffusion/package_manager.py index c246c54d..6a0521b2 100644 --- a/ui/easydiffusion/package_manager.py +++ b/ui/easydiffusion/package_manager.py @@ -25,7 +25,7 @@ installing = [] # remove this once TRT releases on pypi if platform.system() == "Windows": trt_dir = os.path.join(app.ROOT_DIR, "tensorrt") - if os.path.exists(trt_dir): + if os.path.exists(trt_dir) and os.path.isdir(trt_dir) and len(os.listdir(trt_dir)) > 0: files = os.listdir(trt_dir) packages = manifest["tensorrt"]["install"] @@ -61,6 +61,10 @@ def install(module_name): raise RuntimeError(f"Can't install unknown package: {module_name}!") commands = manifest[module_name]["install"] + if module_name == "tensorrt": + commands += [ + "protobuf==3.20.3 polygraphy==0.47.1 onnx==1.14.0 --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com" + ] commands = [f"python -m pip install --upgrade {cmd}" for cmd in commands] installing.append(module_name) From 50dea4cb52a593f46710006596b075825e9c2537 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 4 Aug 2023 19:48:24 +0530 Subject: [PATCH 012/120] Use --pre for trt installs --- ui/easydiffusion/package_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/easydiffusion/package_manager.py b/ui/easydiffusion/package_manager.py index 6a0521b2..de64b66c 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 --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt-libs --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", - "tensorrt --extra-index-url=https://pypi.ngc.nvidia.com --trusted-host pypi.ngc.nvidia.com", + "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", ], "uninstall": ["tensorrt"], # TODO also uninstall tensorrt-libs and nvidia-cudnn, but do it upon restarting (avoid 'file in use' error) From 12fa08d7a7e5546354df1a609cc28b6077575e39 Mon Sep 17 00:00:00 2001 From: ManInDark <61268856+ManInDark@users.noreply.github.com> Date: Tue, 8 Aug 2023 21:45:18 +0200 Subject: [PATCH 013/120] Changed cursor on logo hover to indicate that it can be clicked --- patch.patch | Bin 0 -> 618 bytes ui/media/css/main.css | 1 + 2 files changed, 1 insertion(+) create mode 100644 patch.patch diff --git a/patch.patch b/patch.patch new file mode 100644 index 0000000000000000000000000000000000000000..a452181bff804685e1f8ef1e12eedb642652c74c GIT binary patch literal 618 zcmbV~-D<)>5QWcmq3;kVLM2A63I6rQ*XU(T)C5cnYf1(2)os5?f`wiPmLb{Mvu9?` z?Cke7Rjw`ODZqB@YW!Fued3Q)Xd~kolIVyT7K4-;c8I6-L;PnwHSsCAPgI+*&ZuHv ztOa?8{1eR;X{-=DhV_O&))Xv~WiY+L!&)uUZ|py+S6WdqKvSdvT0Anf{tUl8O?04- z4f}JW)5B}+i>_1dH`5X%r0u!Mw+-<+zuUiEOFpn2ND~(LJX%xjyuY tTC3FWYWX)s2~&1tuy~8E@NBXc=Wfa_ox&H+9gKq2UdjFb+IjW5EI TB-m5 literal 0 HcmV?d00001 diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 2e7d7da9..9c84037a 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%; From 84c5a759d46d1eef14072587aa1af6ce4a5bfc26 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 14 Aug 2023 14:56:33 +0200 Subject: [PATCH 014/120] Resize slider for the advanced image popup (#1490) --- ui/index.html | 18 ++++++++--- ui/media/css/main.css | 27 +++++++++++++++- ui/media/js/main.js | 72 ++++++++++++++++++++++++------------------- 3 files changed, 80 insertions(+), 37 deletions(-) diff --git a/ui/index.html b/ui/index.html index d5ee2ae6..6811774e 100644 --- a/ui/index.html +++ b/ui/index.html @@ -301,12 +301,22 @@ ×
+ Resize:
+
+
    
- Enlarge:
-
  
+
+
+ Recently used:
+
+
+
+
+ Common sizes:
+
+
+
- Recently used:
-
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 9c84037a..745cf53c 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1779,6 +1779,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; } @@ -1795,6 +1799,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; } @@ -1832,4 +1857,4 @@ div#enlarge-buttons { /* hack for fixing Image Modifier Improvements plugin */ #imageTagPopupContainer { position: absolute; -} \ No newline at end of file +} diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 6ed8d506..9e6252f0 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") @@ -2792,38 +2792,25 @@ 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) + enlargeButtons.querySelectorAll("button").forEach( button => button.addEventListener("click", e => { + enlargeImageSize(parseFloat(button.dataset["factor"])) hidePopup() - }) - - enlarge2Button.addEventListener("click", () => { - enlargeImageSize(2) - hidePopup() - }) - - enlarge3Button.addEventListener("click", () => { - enlargeImageSize(3) - hidePopup() - }) + })) customWidthField.addEventListener("change", () => { let w = customWidthField.value @@ -2850,25 +2837,29 @@ let recentResolutionsValues = [] recentResolutionsValues = recentResolutionsValues.slice(0, 8) localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) - makeResolutionButtons() + makeResolutionButtons(recentResolutionList, recentResolutionsValues) }) - let _jsonstring = localStorage.recentResolutionsValues - if (_jsonstring == undefined) { - recentResolutionsValues = [ + const defaultResolutionsValues = [ { 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 }, + { w: 1024, h: 1024 }, + { w: 1920, h: 1080 }, ] + let _jsonstring = localStorage.recentResolutionsValues + if (_jsonstring == undefined) { + 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) @@ -2885,6 +2876,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) } @@ -2903,6 +2897,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 From dc5748624f9d6a19ed4ddaa318d7a6eb7ef66d91 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 18:52:20 +0530 Subject: [PATCH 015/120] sdkit 1.0.168 - fail loudly if an embedding fails to load, disable watermarks for sdxl img2img --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index a5edf5f5..1dceacb6 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.168", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From b5490f7712a218568fde4ed61522f1c8abb65681 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 18:52:38 +0530 Subject: [PATCH 016/120] changelog --- CHANGES.md | 3 +++ ui/index.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9e72b44f..7eae411b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,9 @@ 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 - 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/ui/index.html b/ui/index.html index d5ee2ae6..2c93162f 100644 --- a/ui/index.html +++ b/ui/index.html @@ -32,7 +32,7 @@

Easy Diffusion - v3.0.0 + v3.0.1

From 30ca98b597e2ad13a3b7d77018c378e01ad5d9f8 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 14 Aug 2023 19:02:56 +0530 Subject: [PATCH 017/120] sdkit 1.0.169 - don't clip-skip with SDXL, isn't supported yet --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 1dceacb6..ce26cdf6 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.168", + "sdkit": "1.0.169", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 0adb7831e7aabc97eee87ce9338d5e0bcb8ac771 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 12:53:16 +0530 Subject: [PATCH 018/120] Use the correct nvidia wheels path --- ui/easydiffusion/package_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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) From 6a216be5cbeb6af1e38a29139d9717400219ecdf Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 13:31:08 +0530 Subject: [PATCH 019/120] Force-clean the local git repo, even if it has some unmerged changes --- scripts/on_env_start.bat | 2 ++ scripts/on_env_start.sh | 2 ++ 2 files changed, 4 insertions(+) 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 From e98bd70871de82b6ba443a5a2efbc97ec339a31e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:00:47 +0530 Subject: [PATCH 020/120] sdkit 1.0.170 - fix VAE in low vram mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index ce26cdf6..12a26622 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.169", + "sdkit": "1.0.170", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 253d0dbd5e3681f855de3644fe60daa743202715 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:02:10 +0530 Subject: [PATCH 021/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7eae411b..472b533b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 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. From 6777459e628b8d480df83afe030c0479f5fdbba5 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:49:51 +0530 Subject: [PATCH 022/120] sdkit 1.0.171 - fix embeddings in low vram usage mode --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 12a26622..f01009f7 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.170", + "sdkit": "1.0.171", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 4975f8167efc0d0637708231c7cd80e892ae4646 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:50:21 +0530 Subject: [PATCH 023/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 472b533b..80039bfb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 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. From 34de4fe8fed115df79417eef2574b7f6c4922300 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:57:51 +0530 Subject: [PATCH 024/120] Remove warning about embeddings in low vram mode, works now --- ui/media/js/main.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 9e6252f0..00aed0cc 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2559,17 +2559,7 @@ function updateEmbeddingsList(filter = "") { } } - // Remove after fixing https://github.com/huggingface/diffusers/issues/3922 - let warning = "" - if (vramUsageLevelField.value == "low") { - warning = ` -
- Warning: Your GPU memory profile is set to "Low". Embeddings currently only work in "Balanced" mode! -
` - } - // END of remove block - - embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, "", filter) + embeddingsList.innerHTML = html(modelsOptions.embeddings, "", filter) embeddingsList.querySelectorAll("button").forEach((b) => { b.addEventListener("click", onButtonClick) }) From 01c1c77564a7e9711cfea45254994c3642759882 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 16:58:12 +0530 Subject: [PATCH 025/120] formatting --- ui/media/js/main.js | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 00aed0cc..74119841 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2786,7 +2786,7 @@ let recentResolutionsValues = [] function makeResolutionButtons(listElement, resolutionList) { listElement.innerHTML = "" resolutionList.forEach((el) => { - let button = createElement("button", {style: "width: 8em;"}, "tertiaryButton", `${el.w}×${el.h}`) + let button = createElement("button", { style: "width: 8em;" }, "tertiaryButton", `${el.w}×${el.h}`) button.addEventListener("click", () => { customWidthField.value = el.w customHeightField.value = el.h @@ -2797,10 +2797,12 @@ let recentResolutionsValues = [] }) } - enlargeButtons.querySelectorAll("button").forEach( button => button.addEventListener("click", e => { - enlargeImageSize(parseFloat(button.dataset["factor"])) - hidePopup() - })) + enlargeButtons.querySelectorAll("button").forEach((button) => + button.addEventListener("click", (e) => { + enlargeImageSize(parseFloat(button.dataset["factor"])) + hidePopup() + }) + ) customWidthField.addEventListener("change", () => { let w = customWidthField.value @@ -2831,18 +2833,18 @@ let 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 }, - ] + { 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 = defaultResolutionsValues; + recentResolutionsValues = defaultResolutionsValues localStorage.recentResolutionsValues = JSON.stringify(recentResolutionsValues) } else { recentResolutionsValues = JSON.parse(localStorage.recentResolutionsValues) @@ -2887,17 +2889,17 @@ let recentResolutionsValues = [] } }) - resizeSlider.addEventListener("input", e => { + 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) + customWidthField.value = roundToMultiple(w * factor * factor, step) + customHeightField.value = roundToMultiple(h * factor * factor, step) }) - resizeSlider.addEventListener("change", e => { + resizeSlider.addEventListener("change", (e) => { hidePopup() }) From a2e7bfb30e27e12beeb7a8512e58e43ade29178f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 19:05:46 +0530 Subject: [PATCH 026/120] sdkit 1.0.172 - fix broken tiling after diffusers 0.19.2 upgrade --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index f01009f7..cea2f336 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.171", + "sdkit": "1.0.172", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 074c566826e9eb60c6796073049abbc2bae9c9b7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 15 Aug 2023 19:06:48 +0530 Subject: [PATCH 027/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 80039bfb..8fdbde7f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 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. From a1854d37343373d83ab25297ab6076a7eeb762c5 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Wed, 16 Aug 2023 06:53:12 +0200 Subject: [PATCH 028/120] 'Use for Controlnet', Drag'n'Drop (#1501) * Drop area for Controlnet * 'Use for Controlnet', DND --- ui/media/js/main.js | 5 ++ .../ui/image-editor-improvements.plugin.js | 59 +++++++++++-------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 74119841..ea2af7e3 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -517,6 +517,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', @@ -627,6 +628,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") 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) { From 7c012df1d5c126fcf08e2c79b6821eab9fd316b7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 16 Aug 2023 15:59:07 +0530 Subject: [PATCH 029/120] sdkit 1.0.173 - SDXL LoRA support --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index cea2f336..dce05de0 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.172", + "sdkit": "1.0.173", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From b4cc21ea89f0e7a75e9e5777228f6752733b5a65 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 16 Aug 2023 16:02:35 +0530 Subject: [PATCH 030/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 8fdbde7f..98cb0e74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 15 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. From ac1c65fba11c3d79e64f42509c51372a76364285 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 10:54:47 +0530 Subject: [PATCH 031/120] Move the extraction logic for embeddings-from-prompt, from sdkit to ED's UI --- ui/easydiffusion/model_manager.py | 3 --- ui/easydiffusion/types.py | 2 ++ ui/media/js/main.js | 37 +++++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) 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/types.py b/ui/easydiffusion/types.py index fe936ca2..eeffcb72 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 @@ -200,6 +201,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/media/js/main.js b/ui/media/js/main.js index ea2af7e3..c29850c2 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -845,6 +845,7 @@ function makeImage() { reqBody: Object.assign({ prompt: prompt }, taskTemplate.reqBody), }) ) + newTaskRequests.forEach(setEmbeddings) newTaskRequests.forEach(createTask) updateInitialText() @@ -1493,6 +1494,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() !== "") From edd10bcfe73ff4b8efcc3a41df7d82086c9aa093 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 10:55:26 +0530 Subject: [PATCH 032/120] sdkit 1.0.174 - embedding support for SDXL models, refactor embeddings to use the standard context.models API in sdkit --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index dce05de0..10f1420c 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.173", + "sdkit": "1.0.174", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 097dc99e7734c36cd62f5470186fccebf8507e86 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Thu, 17 Aug 2023 11:01:13 +0530 Subject: [PATCH 033/120] changelog --- CHANGES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 98cb0e74..471a0c80 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,8 @@ 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 - 15 Aug 2023 - Fix broken LoRA with SDXL. +* 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. From 2baad73bb98d0f2abb1811000504bec15f225c5a Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:38:18 +0200 Subject: [PATCH 034/120] Error messages for SDXL yaml files (#1504) * Error messages for SDXL embeddings and SDXL yaml files * Embeddings are supported now with SDXL --- ui/media/js/main.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index c29850c2..fad67ab4 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1039,15 +1039,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)}
` From 23a0a48b814bc4c52df602e6e6999d3bca802ade Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:48:00 +0200 Subject: [PATCH 035/120] Warn when no controlnet model is chosen (#1503) * Warn when no controlnet model is chosen * Update main.js --- ui/media/css/main.css | 3 +++ ui/media/js/main.js | 13 +++++++++++++ 2 files changed, 16 insertions(+) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 745cf53c..48f30f5b 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1461,6 +1461,9 @@ button#save-system-settings-btn { cursor: pointer;; } +.validation-failed { + border: solid 2px red; +} /* SCROLLBARS */ :root { --scrollbar-width: 14px; diff --git a/ui/media/js/main.js b/ui/media/js/main.js index fad67ab4..162ea3be 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -821,12 +821,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 } From 285792f69229b7edc1ff59c402b32ef3fe233292 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 07:48:47 +0200 Subject: [PATCH 036/120] Controlnet thumb in taskConfig (#1502) --- ui/media/css/main.css | 12 ++++++++++-- ui/media/js/main.js | 28 +++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 48f30f5b..bac1122d 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1419,6 +1419,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; @@ -1434,9 +1438,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 { diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 162ea3be..69224dbc 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1217,6 +1217,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 @@ -1281,6 +1302,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)}
` @@ -1331,7 +1357,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) } From 7270b5fe0c5ed5aba3c72ae8dbd880cfbbe207f3 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Thu, 17 Aug 2023 08:03:05 +0200 Subject: [PATCH 037/120] Thumbnails for Embeddings (#1483) * sqlalchemy * sqlalchemy * sqlalchemy bucket v1 * Bucket API * Move easydb files to its own folders * show images * add croppr * croppr ui * profile, thumbnail croppr size limit * fill list * add upload * Use modifiers card, resize thumbs * Remove debugging code * remove unused variable --- 3rd-PARTY-LICENSES | 28 + scripts/check_modules.py | 2 + ui/easydiffusion/app.py | 1 + ui/easydiffusion/bucket_manager.py | 102 +++ ui/easydiffusion/easydb/crud.py | 24 + ui/easydiffusion/easydb/database.py | 14 + ui/easydiffusion/easydb/models.py | 25 + ui/easydiffusion/easydb/schemas.py | 36 + ui/index.html | 39 + ui/main.py | 3 +- ui/media/css/croppr.css | 58 ++ ui/media/css/main.css | 55 ++ ui/media/images/noimg.png | Bin 0 -> 1338 bytes ui/media/js/auto-save.js | 2 + ui/media/js/croppr.js | 1189 +++++++++++++++++++++++++++ ui/media/js/main.js | 224 ++++- ui/media/js/parameters.js | 11 + ui/media/js/utils.js | 42 + 18 files changed, 1834 insertions(+), 21 deletions(-) create mode 100644 ui/easydiffusion/bucket_manager.py create mode 100644 ui/easydiffusion/easydb/crud.py create mode 100644 ui/easydiffusion/easydb/database.py create mode 100644 ui/easydiffusion/easydb/models.py create mode 100644 ui/easydiffusion/easydb/schemas.py create mode 100644 ui/media/css/croppr.css create mode 100644 ui/media/images/noimg.png create mode 100755 ui/media/js/croppr.js diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES index bd29393a..78bfe3bb 100644 --- a/3rd-PARTY-LICENSES +++ b/3rd-PARTY-LICENSES @@ -712,3 +712,31 @@ FileSaver.js is licensed under the MIT license: SOFTWARE. [1]: http://eligrey.com + +croppr.js +========= +https://github.com/jamesssooi/Croppr.js + +croppr.js is licensed under the MIT license: + + MIT License + + Copyright (c) 2017 James Ooi + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 10f1420c..df752a02 100644 --- a/scripts/check_modules.py +++ b/scripts/check_modules.py @@ -25,6 +25,8 @@ modules_to_check = { "fastapi": "0.85.1", "pycloudflared": "0.2.0", "ruamel.yaml": "0.17.21", + "sqlalchemy": "2.0.19", + "python-multipart": "0.0.6", # "xformers": "0.0.16", } modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"] diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py index aa1c5ba7..e2c190f8 100644 --- a/ui/easydiffusion/app.py +++ b/ui/easydiffusion/app.py @@ -38,6 +38,7 @@ SD_UI_DIR = os.getenv("SD_UI_PATH", None) CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "..", "scripts")) MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "models")) +BUCKET_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "bucket")) USER_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, "..", "plugins")) CORE_PLUGINS_DIR = os.path.abspath(os.path.join(SD_UI_DIR, "plugins")) diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py new file mode 100644 index 00000000..60a4ed6c --- /dev/null +++ b/ui/easydiffusion/bucket_manager.py @@ -0,0 +1,102 @@ +from typing import List + +from fastapi import Depends, FastAPI, HTTPException, Response, File +from sqlalchemy.orm import Session + +from easydiffusion.easydb import crud, models, schemas +from easydiffusion.easydb.database import SessionLocal, engine + +from requests.compat import urlparse + +import base64, json + +MIME_TYPES = { + "jpg": "image/jpeg", + "jpeg": "image/jpeg", + "gif": "image/gif", + "png": "image/png", + "webp": "image/webp", + "js": "text/javascript", + "htm": "text/html", + "html": "text/html", + "css": "text/css", + "json": "application/json", + "mjs": "application/json", + "yaml": "application/yaml", + "svg": "image/svg+xml", + "txt": "text/plain", +} + +def init(): + from easydiffusion.server import server_api + + models.BucketBase.metadata.create_all(bind=engine) + + + # Dependency + def get_db(): + db = SessionLocal() + try: + yield db + finally: + db.close() + + @server_api.get("/bucket/{obj_path:path}") + def bucket_get_object(obj_path: str, db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + + if filename==None: + bucket = crud.get_bucket_by_path(db, path=path) + if bucket == None: + raise HTTPException(status_code=404, detail="Bucket not found") + bucketfiles = db.query(models.BucketFile).with_entities(models.BucketFile.filename).filter(models.BucketFile.bucket_id == bucket.id).all() + bucketfiles = [ x.filename for x in bucketfiles ] + return bucketfiles + + else: + bucket_id = crud.get_bucket_by_path(db, path).id + bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id == bucket_id, models.BucketFile.filename == filename).first() + + suffix = get_suffix_from_filename(filename) + + return Response(content=bucketfile.data, media_type=MIME_TYPES.get(suffix, "application/octet-stream")) + + @server_api.post("/bucket/{obj_path:path}") + def bucket_post_object(obj_path: str, file: bytes = File(), db: Session = Depends(get_db)): + filename = get_filename_from_url(obj_path) + path = get_path_from_url(obj_path) + bucket = crud.get_bucket_by_path(db, path) + + if bucket == None: + bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path)) + bucket_id = bucket.id + + bucketfile = schemas.BucketFileCreate(filename=filename, data=file) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + + @server_api.post("/buckets/{bucket_id}/items/", response_model=schemas.BucketFile) + def create_bucketfile_in_bucket( + bucket_id: int, bucketfile: schemas.BucketFileCreate, db: Session = Depends(get_db) + ): + bucketfile.data = base64.decodestring(bucketfile.data) + result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id) + result.data = base64.encodestring(result.data) + return result + + +def get_filename_from_url(url): + path = urlparse(url).path + name = path[path.rfind('/')+1:] + return name or None + +def get_path_from_url(url): + path = urlparse(url).path + path = path[0:path.rfind('/')] + return path or None + +def get_suffix_from_filename(filename): + return filename[filename.rfind('.')+1:] diff --git a/ui/easydiffusion/easydb/crud.py b/ui/easydiffusion/easydb/crud.py new file mode 100644 index 00000000..65bea255 --- /dev/null +++ b/ui/easydiffusion/easydb/crud.py @@ -0,0 +1,24 @@ +from sqlalchemy.orm import Session + +from easydiffusion.easydb import models, schemas + + +def get_bucket_by_path(db: Session, path: str): + return db.query(models.Bucket).filter(models.Bucket.path == path).first() + + +def create_bucket(db: Session, bucket: schemas.BucketCreate): + db_bucket = models.Bucket(path=bucket.path) + db.add(db_bucket) + db.commit() + db.refresh(db_bucket) + return db_bucket + + +def create_bucketfile(db: Session, bucketfile: schemas.BucketFileCreate, bucket_id: int): + db_bucketfile = models.BucketFile(**bucketfile.dict(), bucket_id=bucket_id) + db.merge(db_bucketfile) + db.commit() + db_bucketfile = db.query(models.BucketFile).filter(models.BucketFile.bucket_id==bucket_id, models.BucketFile.filename==bucketfile.filename).first() + return db_bucketfile + diff --git a/ui/easydiffusion/easydb/database.py b/ui/easydiffusion/easydb/database.py new file mode 100644 index 00000000..6cb43ecb --- /dev/null +++ b/ui/easydiffusion/easydb/database.py @@ -0,0 +1,14 @@ +import os +from easydiffusion import app + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker + +os.makedirs(app.BUCKET_DIR, exist_ok=True) +SQLALCHEMY_DATABASE_URL = "sqlite:///"+os.path.join(app.BUCKET_DIR, "bucket.db") + +engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}) +SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +BucketBase = declarative_base() diff --git a/ui/easydiffusion/easydb/models.py b/ui/easydiffusion/easydb/models.py new file mode 100644 index 00000000..04834951 --- /dev/null +++ b/ui/easydiffusion/easydb/models.py @@ -0,0 +1,25 @@ +from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, BLOB +from sqlalchemy.orm import relationship + +from easydiffusion.easydb.database import BucketBase + + +class Bucket(BucketBase): + __tablename__ = "bucket" + + id = Column(Integer, primary_key=True, index=True) + path = Column(String, unique=True, index=True) + + bucketfiles = relationship("BucketFile", back_populates="bucket") + + +class BucketFile(BucketBase): + __tablename__ = "bucketfile" + + filename = Column(String, index=True, primary_key=True) + bucket_id = Column(Integer, ForeignKey("bucket.id"), primary_key=True) + + data = Column(BLOB, index=False) + + bucket = relationship("Bucket", back_populates="bucketfiles") + diff --git a/ui/easydiffusion/easydb/schemas.py b/ui/easydiffusion/easydb/schemas.py new file mode 100644 index 00000000..68bc04e2 --- /dev/null +++ b/ui/easydiffusion/easydb/schemas.py @@ -0,0 +1,36 @@ +from typing import List, Union + +from pydantic import BaseModel + + +class BucketFileBase(BaseModel): + filename: str + data: bytes + + +class BucketFileCreate(BucketFileBase): + pass + + +class BucketFile(BucketFileBase): + bucket_id: int + + class Config: + orm_mode = True + + +class BucketBase(BaseModel): + path: str + + +class BucketCreate(BucketBase): + pass + + +class Bucket(BucketBase): + id: int + bucketfiles: List[BucketFile] = [] + + class Config: + orm_mode = True + diff --git a/ui/index.html b/ui/index.html index d3eafd5c..3d366498 100644 --- a/ui/index.html +++ b/ui/index.html @@ -18,12 +18,14 @@ + +
@@ -689,6 +691,15 @@ + +  
@@ -696,6 +707,34 @@
+ +
+
+

Use as thumbnail

+ Use a pictures as thumbnail for embeddings, LORAs, etc. +
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+ + +
+
+
+
+ ` @@ -1473,6 +1473,30 @@ function createTask(task) {
` + if (task.reqBody.init_image !== undefined || task.reqBody.control_image !== undefined) { + createInitImageHover(taskEntry) + } + + if (task.reqBody.control_image !== undefined && task.reqBody.control_filter_to_apply !== undefined) { + let controlImagePreview = taskEntry.querySelector(".controlnet-img-preview > img") + let req = { + image: task.reqBody.control_image, + filter: task.reqBody.control_filter_to_apply, + model_paths: {}, + filter_params: {}, + } + req["model_paths"][task.reqBody.control_filter_to_apply] = task.reqBody.control_filter_to_apply + SD.filter(req).then( + (result) => { + console.log(result) + controlImagePreview.src = result.output[0] + let controlImageLargePreview = taskEntry.querySelector(".controlnet-img-preview .task-fs-initimage img") + controlImageLargePreview.src = controlImagePreview.src + }, + (error) => console.log("filter error", error) + ) + } + createCollapsibles(taskEntry) let draghandle = taskEntry.querySelector(".drag-handle") @@ -1503,10 +1527,6 @@ function createTask(task) { startY = e.target.closest(".imageTaskContainer").offsetTop }) - if (task.reqBody.init_image !== undefined || task.reqBody.control_image !== undefined) { - createInitImageHover(taskEntry) - } - task["taskStatusLabel"] = taskEntry.querySelector(".taskStatusLabel") task["outputContainer"] = taskEntry.querySelector(".img-preview") task["outputMsg"] = taskEntry.querySelector(".outputMsg") From 846bb2134e62a32db6901cbc712b3048b64285a9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 16:14:30 +0530 Subject: [PATCH 056/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 42e5d02c..f15713d4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 18 Aug 2023 - Show controlnet filter preview in the task entry. * 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA. * 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. * 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder. From e80db71d1c58539ce80eaad2ae579705d23329b2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 16:24:17 +0530 Subject: [PATCH 057/120] Allow downloading the controlnet preview image --- ui/media/css/main.css | 2 +- ui/media/js/main.js | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 82186dd2..ea338653 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1439,7 +1439,7 @@ div.top-right { right: 8px; } -button.useForControlnetBtn { +.task-fs-initimage .top-right button { margin-top: 6px; } diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 281f4b49..3195969c 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1362,7 +1362,7 @@ async function onTaskStart(task) { } /* Hover effect for the init image in the task list */ -function createInitImageHover(taskEntry) { +function createInitImageHover(taskEntry, task) { taskEntry.querySelectorAll(".task-initimg").forEach((thumb) => { let thumbimg = thumb.querySelector("img") let img = createElement("img", { src: thumbimg.src }) @@ -1371,7 +1371,9 @@ function createInitImageHover(taskEntry) { div.innerHTML = `
- ` + +
+ ` div.querySelector(".useAsInputBtn").addEventListener("click", (e) => { e.preventDefault() onUseAsInputClick(null, img) @@ -1380,6 +1382,13 @@ function createInitImageHover(taskEntry) { e.preventDefault() controlImagePreview.src = img.src }) + div.querySelector(".downloadPreviewImg").addEventListener("click", (e) => { + e.preventDefault() + + const name = "image." + task.reqBody["output_format"] + const blob = dataURItoBlob(img.src) + saveAs(blob, name) + }) thumb.querySelector(".task-fs-initimage").appendChild(div) }) return @@ -1474,7 +1483,7 @@ function createTask(task) { ` if (task.reqBody.init_image !== undefined || task.reqBody.control_image !== undefined) { - createInitImageHover(taskEntry) + createInitImageHover(taskEntry, task) } if (task.reqBody.control_image !== undefined && task.reqBody.control_filter_to_apply !== undefined) { From 1190bedafdeff98cb568e9c9bc27a08763a1b64d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 17:03:41 +0530 Subject: [PATCH 058/120] Don't include empty lora values in the metadata --- ui/media/js/dnd.js | 8 ++++ ui/media/js/multi-model-selector.js | 67 +++++++++++------------------ 2 files changed, 34 insertions(+), 41 deletions(-) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 7baa27b3..7af94905 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -292,6 +292,10 @@ const TASK_MAPPING = { use_lora_model: { name: "LoRA model", setUI: (use_lora_model) => { + if (!use_lora_model) { + return + } + let modelPaths = [] use_lora_model.forEach((m) => { if (m.includes("models\\lora\\")) { @@ -329,6 +333,10 @@ const TASK_MAPPING = { lora_alpha: { name: "LoRA Strength", setUI: (lora_alpha) => { + if (!lora_alpha) { + return + } + loraModelField.modelWeights = lora_alpha }, readUI: () => { diff --git a/ui/media/js/multi-model-selector.js b/ui/media/js/multi-model-selector.js index 5b719b88..6dc4e795 100644 --- a/ui/media/js/multi-model-selector.js +++ b/ui/media/js/multi-model-selector.js @@ -29,15 +29,7 @@ class MultiModelSelector { return this.root.parentNode } get value() { - let modelNames = [] - let modelWeights = [] - - this.modelElements.forEach((e) => { - modelNames.push(e.name.value) - modelWeights.push(e.weight.value) - }) - - return { modelNames: modelNames, modelWeights: modelWeights } + return { modelNames: this.modelNames, modelWeights: this.modelWeights } } set value(modelData) { if (typeof modelData !== "object") { @@ -53,31 +45,13 @@ class MultiModelSelector { throw new Error("Need to pass an equal number of modelNames and modelWeights!") } - // expand or shrink entries - let currElements = this.modelElements - if (currElements.length < newModelNames.length) { - for (let i = currElements.length; i < newModelNames.length; i++) { - this.addModelEntry() - } - } else { - for (let i = newModelNames.length; i < currElements.length; i++) { - this.removeModelEntry() - } - } - - // assign to the corresponding elements - currElements = this.modelElements - for (let i = 0; i < newModelNames.length; i++) { - let curr = currElements[i] - - // update weight first, name second. - // for some unholy reason this order matters for dispatch chains - // the root of all this unholiness is because searchable-models automatically dispatches an update event - // as soon as the value is updated via JS, which is against the DOM pattern of not dispatching an event automatically - // unless the caller explicitly dispatches the event. - curr.weight.value = newModelWeights[i] - curr.name.value = newModelNames[i] - } + // update weight first, name second. + // for some unholy reason this order matters for dispatch chains + // the root of all this unholiness is because searchable-models automatically dispatches an update event + // as soon as the value is updated via JS, which is against the DOM pattern of not dispatching an event automatically + // unless the caller explicitly dispatches the event. + this.modelWeights = newModelWeights + this.modelNames = newModelNames } get disabled() { return false @@ -85,12 +59,19 @@ class MultiModelSelector { set disabled(state) { // do nothing } - get modelElements() { + getModelElements(ignoreEmpty = false) { let entries = this.root.querySelectorAll(".model_entry") entries = [...entries] let elements = entries.map((e) => { - return { name: e.querySelector(".model_name").field, weight: e.querySelector(".model_weight") } + let modelName = e.querySelector(".model_name").field + let modelWeight = e.querySelector(".model_weight") + if (ignoreEmpty && modelName.value.trim() === "") { + return null + } + + return { name: modelName, weight: modelWeight } }) + elements = elements.filter((e) => e !== null) return elements } addEventListener(type, listener, options) { @@ -213,18 +194,18 @@ class MultiModelSelector { } get length() { - return this.modelContainer.childElementCount + return this.getModelElements().length } get modelNames() { - return this.modelElements.map((e) => e.name.value) + return this.getModelElements(true).map((e) => e.name.value) } set modelNames(newModelNames) { this.resizeEntryList(newModelNames.length) // assign to the corresponding elements - let currElements = this.modelElements + let currElements = this.getModelElements() for (let i = 0; i < newModelNames.length; i++) { let curr = currElements[i] @@ -233,14 +214,14 @@ class MultiModelSelector { } get modelWeights() { - return this.modelElements.map((e) => e.weight.value) + return this.getModelElements(true).map((e) => e.weight.value) } set modelWeights(newModelWeights) { this.resizeEntryList(newModelWeights.length) // assign to the corresponding elements - let currElements = this.modelElements + let currElements = this.getModelElements() for (let i = 0; i < newModelWeights.length; i++) { let curr = currElements[i] @@ -249,6 +230,10 @@ class MultiModelSelector { } resizeEntryList(newLength) { + if (newLength === 0) { + newLength = 1 + } + let currLength = this.length if (currLength < newLength) { for (let i = currLength; i < newLength; i++) { From 894f34678e63e6abc47bca57a84b6841420c9184 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 17:08:19 +0530 Subject: [PATCH 059/120] Some more fixes for multi-lora use-these-settings --- ui/media/js/dnd.js | 8 -------- ui/media/js/multi-model-selector.js | 8 ++++++++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 7af94905..7baa27b3 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -292,10 +292,6 @@ const TASK_MAPPING = { use_lora_model: { name: "LoRA model", setUI: (use_lora_model) => { - if (!use_lora_model) { - return - } - let modelPaths = [] use_lora_model.forEach((m) => { if (m.includes("models\\lora\\")) { @@ -333,10 +329,6 @@ const TASK_MAPPING = { lora_alpha: { name: "LoRA Strength", setUI: (lora_alpha) => { - if (!lora_alpha) { - return - } - loraModelField.modelWeights = lora_alpha }, readUI: () => { diff --git a/ui/media/js/multi-model-selector.js b/ui/media/js/multi-model-selector.js index 6dc4e795..472ed81a 100644 --- a/ui/media/js/multi-model-selector.js +++ b/ui/media/js/multi-model-selector.js @@ -204,6 +204,10 @@ class MultiModelSelector { set modelNames(newModelNames) { this.resizeEntryList(newModelNames.length) + if (newModelNames.length === 0) { + this.getModelElements()[0].name.value = "" + } + // assign to the corresponding elements let currElements = this.getModelElements() for (let i = 0; i < newModelNames.length; i++) { @@ -220,6 +224,10 @@ class MultiModelSelector { set modelWeights(newModelWeights) { this.resizeEntryList(newModelWeights.length) + if (newModelWeights.length === 0) { + this.getModelElements()[0].weight.value = this.defaultWeight + } + // assign to the corresponding elements let currElements = this.getModelElements() for (let i = 0; i < newModelWeights.length; i++) { From 767d8fc35dc49944e6a9cc473ef14c328606917e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 17:18:01 +0530 Subject: [PATCH 060/120] Use these Settings work for multi-lora now --- ui/media/js/dnd.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 7baa27b3..437651c9 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -293,6 +293,7 @@ const TASK_MAPPING = { name: "LoRA model", setUI: (use_lora_model) => { let modelPaths = [] + use_lora_model = Array.isArray(use_lora_model) ? use_lora_model : [use_lora_model] use_lora_model.forEach((m) => { if (m.includes("models\\lora\\")) { m = m.split("models\\lora\\")[1] @@ -329,6 +330,7 @@ const TASK_MAPPING = { lora_alpha: { name: "LoRA Strength", setUI: (lora_alpha) => { + lora_alpha = Array.isArray(lora_alpha) ? lora_alpha : [lora_alpha] loraModelField.modelWeights = lora_alpha }, readUI: () => { @@ -454,11 +456,8 @@ function restoreTaskToUI(task, fieldsToSkip) { } if (!("use_lora_model" in task.reqBody)) { - loraModels.forEach((e) => { - e[0].value = "" - e[1].value = 0 - e[0].dispatchEvent(new Event("change")) - }) + loraModelField.modelNames = [] + loraModelField.modelWeights = [] } // restore the original prompt if provided (e.g. use settings), fallback to prompt as needed (e.g. copy/paste or d&d) From 1d54943d712af097d3b77e1e420a99402b163acb Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 17:53:16 +0530 Subject: [PATCH 061/120] Support drag-and-drop and use-these-settings for controlnet --- ui/media/js/dnd.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 437651c9..21f66f2e 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -289,6 +289,22 @@ const TASK_MAPPING = { readUI: () => vaeModelField.value, parse: (val) => val, }, + use_controlnet_model: { + name: "ControlNet model", + setUI: (use_controlnet_model) => { + controlnetModelField.value = getModelPath(use_controlnet_model, [".pth", ".safetensors"]) + }, + readUI: () => controlnetModelField.value, + parse: (val) => val, + }, + control_filter_to_apply: { + name: "ControlNet Filter", + setUI: (control_filter_to_apply) => { + controlImageFilterField.value = control_filter_to_apply + }, + readUI: () => controlImageFilterField.value, + parse: (val) => val, + }, use_lora_model: { name: "LoRA model", setUI: (use_lora_model) => { @@ -552,6 +568,8 @@ const TASK_TEXT_MAPPING = { hypernetwork_strength: "Hypernetwork Strength", use_lora_model: "LoRA model", lora_alpha: "LoRA Strength", + use_controlnet_model: "ControlNet model", + control_filter_to_apply: "ControlNet Filter", } function parseTaskFromText(str) { const taskReqBody = {} From 8999f9450fc60f63972432b72878cb16d3b89360 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 17:59:00 +0530 Subject: [PATCH 062/120] Show control image when 'Use these Settings' is used --- ui/media/js/dnd.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index 21f66f2e..a9ccb34f 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -516,6 +516,15 @@ function restoreTaskToUI(task, fieldsToSkip) { ) initImagePreview.src = task.reqBody.init_image } + + // hide/show controlnet picture as needed + if (IMAGE_REGEX.test(controlImagePreview.src) && task.reqBody.control_image == undefined) { + // hide source image + controlImageClearBtn.dispatchEvent(new Event("click")) + } else if (task.reqBody.control_image !== undefined) { + // listen for inpainter loading event, which happens AFTER the main image loads (which reloads the inpai + controlImagePreview.src = task.reqBody.control_image + } } function readUI() { const reqBody = {} From 4cd8ae45e35ca262440d304644d062468eb5df66 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 18:01:15 +0530 Subject: [PATCH 063/120] changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f15713d4..fe7a791e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,7 +23,7 @@ Our focus continues to remain on an easy installation experience, and an easy us ### Detailed changelog * 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. -* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA. +* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. * 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. * 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder. * 3.0.1 - 17 Aug 2023 - Fix broken embeddings with SDXL. From 5efabfaea6ed9e76042e6bc5018a3bfaf37b95cb Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 18:10:35 +0530 Subject: [PATCH 064/120] Don't include hypernetwork info in 'copy settings' if using diffusers --- ui/media/js/dnd.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ui/media/js/dnd.js b/ui/media/js/dnd.js index a9ccb34f..5cdb9539 100644 --- a/ui/media/js/dnd.js +++ b/ui/media/js/dnd.js @@ -529,6 +529,10 @@ function restoreTaskToUI(task, fieldsToSkip) { function readUI() { const reqBody = {} for (const key in TASK_MAPPING) { + if (testDiffusers.checked && (key === "use_hypernetwork_model" || key === "hypernetwork_strength")) { + continue + } + reqBody[key] = TASK_MAPPING[key].readUI() } return { From 7f878f365b3975659304c4d9eb791bdeb8df118a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 18:23:21 +0530 Subject: [PATCH 065/120] Don't add hypernetwork info in the metadata if using diffusers (v3 engine) --- ui/easydiffusion/utils/save_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 090aef0f..4660b1ed 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -265,7 +265,10 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output if task_data.use_controlnet_model is None and "control_filter_to_apply" in metadata: del metadata["control_filter_to_apply"] - if not using_diffusers: + if using_diffusers: + for key in (x for x in ["use_hypernetwork_model", "hypernetwork_strength"] if x in metadata): + del metadata[key] + else: for key in ( x for x in ["use_lora_model", "lora_alpha", "clip_skip", "tiling", "latent_upscaler_steps", "use_controlnet_model", "control_filter_to_apply"] if x in metadata ): From ab4d34e509622695419587bdfc5e94222b08f23c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 18:43:23 +0530 Subject: [PATCH 066/120] sdkit 1.0.176 - resize control images to the task dimensions, to avoid memory errors with high-res control images --- scripts/check_modules.py | 2 +- ui/easydiffusion/tasks/render_images.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index b3a28a76..3edaebff 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.175", + "sdkit": "1.0.176", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", diff --git a/ui/easydiffusion/tasks/render_images.py b/ui/easydiffusion/tasks/render_images.py index bdf6e3ac..593b409d 100644 --- a/ui/easydiffusion/tasks/render_images.py +++ b/ui/easydiffusion/tasks/render_images.py @@ -15,6 +15,8 @@ from sdkit.utils import ( img_to_base64_str, img_to_buffer, latent_samples_to_images, + resize_img, + get_image, log, ) @@ -226,6 +228,8 @@ def generate_images_internal( req.width, req.height = map(lambda x: x - x % 8, (req.width, req.height)) # clamp to 8 if req.control_image and task_data.control_filter_to_apply: + req.control_image = get_image(req.control_image) + req.control_image = resize_img(req.control_image.convert("RGB"), req.width, req.height, clamp_to_8=True) req.control_image = filter_images(context, req.control_image, task_data.control_filter_to_apply)[0] if context.test_diffusers: From 3759d7794546d7f3941948946b641728b6c59fb7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 18:44:16 +0530 Subject: [PATCH 067/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index fe7a791e..6b773e0b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. * 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. * 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. * 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. From 1b6ec418a17e40158aaaa3a28aee88284a0b9449 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 19:01:16 +0530 Subject: [PATCH 068/120] sdkit 1.0.177 - rotate images if EXIF rotation present --- scripts/check_modules.py | 2 +- ui/easydiffusion/tasks/filter_images.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 3edaebff..15c8c2cd 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.176", + "sdkit": "1.0.177", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", diff --git a/ui/easydiffusion/tasks/filter_images.py b/ui/easydiffusion/tasks/filter_images.py index c4e674d7..1e653e3e 100644 --- a/ui/easydiffusion/tasks/filter_images.py +++ b/ui/easydiffusion/tasks/filter_images.py @@ -3,7 +3,7 @@ import pprint from sdkit.filter import apply_filters from sdkit.models import load_model -from sdkit.utils import img_to_base64_str, log +from sdkit.utils import img_to_base64_str, get_image, log from easydiffusion import model_manager, runtime from easydiffusion.types import FilterImageRequest, FilterImageResponse, ModelsData, OutputFormatData @@ -42,7 +42,12 @@ class FilterTask(Task): print_task_info(self.request, self.models_data, self.output_format) - images = filter_images(context, self.request.image, self.request.filter, self.request.filter_params) + if isinstance(self.request.image, list): + images = [get_image(img) for img in self.request.image] + else: + images = get_image(self.request.image) + + images = filter_images(context, images, self.request.filter, self.request.filter_params) output_format = self.output_format images = [ From 8957250db8fcaa32871053774be7018004b5544d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 19:02:14 +0530 Subject: [PATCH 069/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6b773e0b..7aed1ffa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. * 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. * 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. * 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. From ca8a96f956a800be3f683ccb5ecf3eaa99a3e18c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 18 Aug 2023 19:09:14 +0530 Subject: [PATCH 070/120] Don't show or save hypernetwork info if using v3 (diffusers) --- ui/media/js/auto-save.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js index eff23a99..1aec8a55 100644 --- a/ui/media/js/auto-save.js +++ b/ui/media/js/auto-save.js @@ -15,14 +15,12 @@ const SETTINGS_IDS_LIST = [ "stable_diffusion_model", "clip_skip", "vae_model", - "hypernetwork_model", "sampler_name", "width", "height", "num_inference_steps", "guidance_scale", "prompt_strength", - "hypernetwork_strength", "tiling", "output_format", "output_quality", @@ -62,6 +60,11 @@ const SETTINGS_IDS_LIST = [ const IGNORE_BY_DEFAULT = ["prompt"] +if (!testDiffusers.checked) { + SETTINGS_IDS_LIST.push("hypernetwork_model") + SETTINGS_IDS_LIST.push("hypernetwork_strength") +} + const SETTINGS_SECTIONS = [ // gets the "keys" property filled in with an ordered list of settings in this section via initSettings { id: "editor-inputs", name: "Prompt" }, From bdd7d2599f1377b3e2efb2de099f287a7da73d0e Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sat, 19 Aug 2023 06:17:59 +0200 Subject: [PATCH 071/120] API to get SHA256 of a model file (#1510) To be used from javascript to collect metadata from civitai https://civitai.com/api/v1/model-versions/by-hash/0A35347528 --- ui/easydiffusion/server.py | 27 +++++++++++++++++++++++++++ ui/easydiffusion/utils/__init__.py | 12 ++++++++++++ 2 files changed, 39 insertions(+) diff --git a/ui/easydiffusion/server.py b/ui/easydiffusion/server.py index 1ecbbbd3..e3f80f42 100644 --- a/ui/easydiffusion/server.py +++ b/ui/easydiffusion/server.py @@ -139,6 +139,10 @@ def init(): def modify_package(package_name: str, req: dict): return modify_package_internal(package_name, req) + @server_api.get("/sha256/{obj_path:path}") + def get_sha256(obj_path: str): + return get_sha256_internal(obj_path) + @server_api.get("/") def read_root(): return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS) @@ -451,3 +455,26 @@ def modify_package_internal(package_name: str, req: dict): log.error(str(e)) log.error(traceback.format_exc()) return HTTPException(status_code=500, detail=str(e)) + +def get_sha256_internal(obj_path): + import hashlib + from easydiffusion.utils import sha256sum + + path = obj_path.split("/") + type = path.pop(0) + + try: + model_path = model_manager.resolve_model_to_use("/".join(path), type) + except Exception as e: + log.error(str(e)) + log.error(traceback.format_exc()) + + return HTTPException(status_code=404) + try: + digest = sha256sum(model_path) + return {"digest": digest} + except Exception as e: + log.error(str(e)) + log.error(traceback.format_exc()) + return HTTPException(status_code=500, detail=str(e)) + diff --git a/ui/easydiffusion/utils/__init__.py b/ui/easydiffusion/utils/__init__.py index b9c5e21a..f6758809 100644 --- a/ui/easydiffusion/utils/__init__.py +++ b/ui/easydiffusion/utils/__init__.py @@ -6,3 +6,15 @@ from .save_utils import ( save_images_to_disk, get_printable_request, ) + +def sha256sum(filename): + sha256 = hashlib.sha256() + with open(filename, "rb") as f: + while True: + data = f.read(8192) # Read in chunks of 8192 bytes + if not data: + break + sha256.update(data) + + return sha256.hexdigest() + From 77aa7a0148ca58d861297ad829fe208319afe133 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sat, 19 Aug 2023 06:20:40 +0200 Subject: [PATCH 072/120] Improvements/Fixes for embeddings UI (#1509) - Don't show "Use as thumbnail" if no embeddings were used in the prompt - Fix layout issue on small screens - use req.use_embeddings_model instead of parsing the prompt - Add button to upload a thumb" ../../index.html ../css/main.css main.js --- ui/index.html | 2 ++ ui/media/css/main.css | 6 ++++++ ui/media/js/main.js | 36 ++++++++++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/ui/index.html b/ui/index.html index 4f62b5b4..1d49a4c4 100644 --- a/ui/index.html +++ b/ui/index.html @@ -680,6 +680,8 @@
+ +
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index ea338653..25602e8c 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1706,6 +1706,12 @@ body.wait-pause { overflow-y: scroll; } +@media screen and (max-width: 1400px) { + #embeddings-list { + width: 80vW; + } +} + #embeddings-list button { margin: 2px; color: var(--button-color); diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 3195969c..a06f2016 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -144,6 +144,8 @@ let embeddingsSearchBox = document.querySelector("#embeddings-search-box") let embeddingsList = document.querySelector("#embeddings-list") let embeddingsModeField = document.querySelector("#embeddings-mode") let embeddingsCardSizeSelector = document.querySelector("#embedding-card-size-selector") +let addEmbeddingsThumb = document.querySelector("#add-embeddings-thumb") +let addEmbeddingsThumbInput = document.querySelector("#add-embeddings-thumb-input") let positiveEmbeddingText = document.querySelector("#positive-embedding-text") let negativeEmbeddingText = document.querySelector("#negative-embedding-text") @@ -547,7 +549,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { { text: "Upscale", on_click: onUpscaleClick }, { text: "Fix Faces", on_click: onFixFacesClick }, ], - { text: "Use as Thumbnail", on_click: onUseAsThumbnailClick }, + { text: "Use as Thumbnail", on_click: onUseAsThumbnailClick, filter: (req, img) => "use_embeddings_model" in req }, ] // include the plugins @@ -758,9 +760,7 @@ function onUseAsThumbnailClick(req, img) { onUseAsThumbnailClick.croppr.setImage(img.src) } - let embeddings = getAllModelNames("embeddings").filter( - (e) => req.prompt.includes(e) || req.negative_prompt.includes(e) - ) + let embeddings = req.use_embeddings_model.map(e => e.split("/").pop()) let LORA = [] if ("use_lora_model" in req) { @@ -2741,6 +2741,34 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun /* Embeddings */ +addEmbeddingsThumb.addEventListener("click", e => addEmbeddingsThumbInput.click()) +addEmbeddingsThumbInput.addEventListener("change", loadThumbnailImageFromFile) + +function loadThumbnailImageFromFile() { + if (addEmbeddingsThumbInput.files.length === 0) { + return + } + + let reader = new FileReader() + let file = addEmbeddingsThumbInput.files[0] + + reader.addEventListener("load", function(event) { + let img = document.createElement("img") + img.src = reader.result + onUseAsThumbnailClick( + { + use_embeddings_model: getAllModelNames("embeddings").sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) + }, + img + ) + }) + + if (file) { + reader.readAsDataURL(file) + } +} + + function updateEmbeddingsList(filter = "") { function html(model, iconlist = [], prefix = "", filter = "") { filter = filter.toLowerCase() From 7b8e1bc9196088c1eeb388660846ba11aea2d03a Mon Sep 17 00:00:00 2001 From: JeLuF Date: Sat, 19 Aug 2023 06:21:34 +0200 Subject: [PATCH 073/120] type=number for number of images fields (#1507) --- ui/index.html | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 1d49a4c4..c31472f7 100644 --- a/ui/index.html +++ b/ui/index.html @@ -143,7 +143,12 @@
Image Settings - + + + Image Settings -
+ + +
From 47d5cb9e33cfc58243c5fddf1ff934cd775fb8a2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Sat, 19 Aug 2023 13:15:32 +0530 Subject: [PATCH 074/120] Refactor some of the task-related functions into task-manager.js, with callbacks for some UI-related code. This isn't exhaustive, just another step towards breaking up main.js --- ui/index.html | 1 + ui/media/js/main.js | 532 ++++++------------------------------ ui/media/js/task-manager.js | 376 +++++++++++++++++++++++++ ui/media/js/utils.js | 33 +++ 4 files changed, 490 insertions(+), 452 deletions(-) create mode 100644 ui/media/js/task-manager.js diff --git a/ui/index.html b/ui/index.html index 4f62b5b4..fe848321 100644 --- a/ui/index.html +++ b/ui/index.html @@ -788,6 +788,7 @@ + diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 3195969c..306a8d25 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -3,7 +3,6 @@ const MAX_INIT_IMAGE_DIMENSION = 768 const MIN_GPUS_TO_SHOW_SELECTION = 2 const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") -const htmlTaskMap = new WeakMap() const spinnerPacmanHtml = '
' @@ -151,8 +150,6 @@ let embeddingsCollapsiblesBtn = document.querySelector("#embeddings-action-colla let makeImageBtn = document.querySelector("#makeImage") let stopImageBtn = document.querySelector("#stopImage") -let pauseBtn = document.querySelector("#pause") -let resumeBtn = document.querySelector("#resume") let renderButtons = document.querySelector("#render-buttons") let imagesContainer = document.querySelector("#current-images") @@ -182,8 +179,6 @@ let useAsThumbCancelBtn = document.querySelector("#use-as-thumb-cancel") let maskSetting = document.querySelector("#enable_mask") -const processOrder = document.querySelector("#process_order_toggle") - let imagePreview = document.querySelector("#preview") let imagePreviewContent = document.querySelector("#preview-content") @@ -325,41 +320,6 @@ function shiftOrConfirm(e, prompt, fn, allowSkip = true) { } } -function logMsg(msg, level, outputMsg) { - if (outputMsg.hasChildNodes()) { - outputMsg.appendChild(document.createElement("br")) - } - if (level === "error") { - outputMsg.innerHTML += 'Error: ' + msg + "" - } else if (level === "warn") { - outputMsg.innerHTML += 'Warning: ' + msg + "" - } else { - outputMsg.innerText += msg - } - console.log(level, msg) -} - -function logError(msg, res, outputMsg) { - logMsg(msg, "error", outputMsg) - - console.log("request error", res) - console.trace() - setStatus("request", "error", "error") -} - -function playSound() { - const audio = new Audio("/media/ding.mp3") - audio.volume = 0.2 - var promise = audio.play() - if (promise !== undefined) { - promise - .then((_) => {}) - .catch((error) => { - console.warn("browser blocked autoplay") - }) - } -} - function undoableRemove(element, doubleUndo = false) { let data = { element: element, @@ -940,22 +900,6 @@ function onContinueDrawingClick(req, img) { }) } -function getUncompletedTaskEntries() { - const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) - .filter((taskLabel) => taskLabel.style.display !== "none") - .map(function(taskLabel) { - let imageTaskContainer = taskLabel.parentNode - while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { - imageTaskContainer = imageTaskContainer.parentNode - } - return imageTaskContainer - }) - if (!processOrder.checked) { - taskEntries.reverse() - } - return taskEntries -} - function makeImage() { if (typeof performance == "object" && performance.mark) { performance.mark("click-makeImage") @@ -1010,357 +954,6 @@ function makeImage() { updateInitialText() } -async function onIdle() { - const serverCapacity = SD.serverCapacity - if (pauseClient === true) { - await resumeClient() - } - - for (const taskEntry of getUncompletedTaskEntries()) { - if (SD.activeTasks.size >= serverCapacity) { - break - } - const task = htmlTaskMap.get(taskEntry) - if (!task) { - const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") - taskStatusLabel.style.display = "none" - continue - } - await onTaskStart(task) - } -} - -function getTaskUpdater(task, reqBody, outputContainer) { - const outputMsg = task["outputMsg"] - const progressBar = task["progressBar"] - const progressBarInner = progressBar.querySelector("div") - - const batchCount = task.batchCount - let lastStatus = undefined - return async function(event) { - if (this.status !== lastStatus) { - lastStatus = this.status - switch (this.status) { - case SD.TaskStatus.pending: - task["taskStatusLabel"].innerText = "Pending" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - break - case SD.TaskStatus.waiting: - task["taskStatusLabel"].innerText = "Waiting" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - task["taskStatusLabel"].classList.remove("activeTaskLabel") - break - case SD.TaskStatus.processing: - case SD.TaskStatus.completed: - task["taskStatusLabel"].innerText = "Processing" - task["taskStatusLabel"].classList.add("activeTaskLabel") - task["taskStatusLabel"].classList.remove("waitingTaskLabel") - break - case SD.TaskStatus.stopped: - break - case SD.TaskStatus.failed: - if (!SD.isServerAvailable()) { - logError( - "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", - event, - outputMsg - ) - } else if (typeof event?.response === "object") { - let msg = "Stable Diffusion had an error reading the response:
"
-                        if (this.exception) {
-                            msg += `Error: ${this.exception.message}
` - } - try { - // 'Response': body stream already read - msg += "Read: " + (await event.response.text()) - } catch (e) { - msg += "Unexpected end of stream. " - } - const bufferString = event.reader.bufferedString - if (bufferString) { - msg += "Buffered data: " + bufferString - } - msg += "
" - logError(msg, event, outputMsg) - } - break - } - } - if ("update" in event) { - const stepUpdate = event.update - if (!("step" in stepUpdate)) { - return - } - // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) - const overallStepCount = - task.instances.reduce( - (sum, instance) => - sum + - (instance.isPending - ? Math.max(0, instance.step || stepUpdate.step) / - (instance.total_steps || stepUpdate.total_steps) - : 1), - 0 // Initial value - ) * stepUpdate.total_steps // Scale to current number of steps. - const totalSteps = task.instances.reduce( - (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), - stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) - ) - const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0) - - const timeTaken = stepUpdate.step_time // sec - const stepsRemaining = Math.max(0, totalSteps - overallStepCount) - const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) - outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` - outputMsg.style.display = "block" - progressBarInner.style.width = `${percent}%` - - if (stepUpdate.output) { - showImages(reqBody, stepUpdate, outputContainer, true) - } - } - } -} - -function abortTask(task) { - if (!task.isProcessing) { - return false - } - task.isProcessing = false - task.progressBar.classList.remove("active") - task["taskStatusLabel"].style.display = "none" - task["stopTask"].innerHTML = ' Remove' - if (!task.instances?.some((r) => r.isPending)) { - return - } - task.instances.forEach((instance) => { - try { - instance.abort() - } catch (e) { - console.error(e) - } - }) -} - -function onTaskErrorHandler(task, reqBody, instance, reason) { - if (!task.isProcessing) { - return - } - console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) - abortTask(task) - const outputMsg = task["outputMsg"] - logError( - "Stable Diffusion had an error. Please check the logs in the command-line window.

" + - reason + - "
" +
-            reason.stack +
-            "
", - task, - outputMsg - ) - setStatus("request", "error", "error") -} - -function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { - if (typeof stepUpdate === "object") { - if (stepUpdate.status === "succeeded") { - showImages(reqBody, stepUpdate, outputContainer, false) - } else { - task.isProcessing = false - const outputMsg = task["outputMsg"] - let msg = "" - if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { - msg = stepUpdate.detail - if (msg.toLowerCase().includes("out of memory")) { - msg += `

- Suggestions: -
- 1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.
- 2. Try picking a lower level in the 'GPU Memory Usage' setting (in the 'Settings' tab).
- 3. Try generating a smaller image.
` - } else if (msg.includes("DefaultCPUAllocator: not enough memory")) { - msg += `

- Reason: Your computer is running out of system RAM! -

- Suggestions: -
- 1. Try closing unnecessary programs and browser tabs.
- 2. If that doesn't help, please increase your computer's virtual memory by following these steps for - Windows or - Linux.
- 3. Try restarting your computer.
` - } else if ( - msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape") - ) { - msg += `

- Reason: You tried to use a LORA that was trained for a different Stable Diffusion model version! -

- Suggestions: -
- Try to use a different model or a different LORA.` - } else if (msg.includes("'ModuleList' object has no attribute '1'")) { - msg += `

- Reason: SDXL models need a yaml config file. -

- Suggestions: -
-
    -
  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)}
` - } - logError(msg, stepUpdate, outputMsg) - } - } - if (task.isProcessing && task.batchesDone < task.batchCount) { - task["taskStatusLabel"].innerText = "Pending" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - task["taskStatusLabel"].classList.remove("activeTaskLabel") - return - } - if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { - return - } - - task.isProcessing = false - task["stopTask"].innerHTML = ' Remove' - task["taskStatusLabel"].style.display = "none" - - let time = millisecondsToStr(Date.now() - task.startTime) - - if (task.batchesDone == task.batchCount) { - if (!task.outputMsg.innerText.toLowerCase().includes("error")) { - task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` - } - task.progressBar.style.height = "0px" - task.progressBar.style.border = "0px solid var(--background-color3)" - task.progressBar.classList.remove("active") - setStatus("request", "done", "success") - } else { - task.outputMsg.innerText += `. Task ended after ${time}` - } - - if (randomSeedField.checked) { - seedField.value = task.seed - } - - if (SD.activeTasks.size > 0) { - return - } - const uncompletedTasks = getUncompletedTaskEntries() - if (uncompletedTasks && uncompletedTasks.length > 0) { - return - } - - if (pauseClient) { - resumeBtn.click() - } - renderButtons.style.display = "none" - renameMakeImageButton() - - if (isSoundEnabled()) { - playSound() - } -} - -async function onTaskStart(task) { - if (!task.isProcessing || task.batchesDone >= task.batchCount) { - return - } - - if (typeof task.startTime !== "number") { - task.startTime = Date.now() - } - if (!("instances" in task)) { - task["instances"] = [] - } - - task["stopTask"].innerHTML = ' Stop' - task["taskStatusLabel"].innerText = "Starting" - task["taskStatusLabel"].classList.add("waitingTaskLabel") - - let newTaskReqBody = task.reqBody - if (task.batchCount > 1) { - // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. - newTaskReqBody = Object.assign({}, task.reqBody) - if (task.batchesDone == task.batchCount - 1) { - // Last batch of the task - // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images - // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. - newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) - } - } - - const startSeed = task.seed || newTaskReqBody.seed - const genSeeds = Boolean( - typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) - ) - if (genSeeds) { - newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs - } - - // Update the seed *before* starting the processing so it's retained if user stops the task - if (randomSeedField.checked) { - seedField.value = task.seed - } - - const outputContainer = document.createElement("div") - outputContainer.className = "img-batch" - task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) - - const eventInfo = { reqBody: newTaskReqBody } - const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { - if (typeof hook !== "function") { - console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) - return Promise.reject(new Error("hook is not a function.")) - } - try { - return Promise.resolve(hook.call(task, eventInfo)) - } catch (err) { - console.error(err) - return Promise.reject(err) - } - }) - await Promise.allSettled(callbacksPromises) - let instance = eventInfo.instance - if (!instance) { - const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) - if (factory) { - instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) - } - if (!instance) { - console.error( - `${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody - ?.output_format || newTaskReqBody.output_format}. Instance is ${instance || - "undefined"}. Using default renderer.` - ) - instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) - } - } - - task["instances"].push(instance) - task.batchesDone++ - - instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( - (renderResult) => { - onTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult) - }, - (reason) => { - onTaskErrorHandler(task, newTaskReqBody, instance, reason) - } - ) - - setStatus("request", "fetching..") - renderButtons.style.display = "flex" - renameMakeImageButton() - updateInitialText() -} - /* Hover effect for the init image in the task list */ function createInitImageHover(taskEntry, task) { taskEntry.querySelectorAll(".task-initimg").forEach((thumb) => { @@ -1924,20 +1517,6 @@ function createFileName(prompt, seed, steps, guidance, outputFormat) { return fileName } -async function stopAllTasks() { - getUncompletedTaskEntries().forEach((taskEntry) => { - const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") - if (taskStatusLabel) { - taskStatusLabel.style.display = "none" - } - const task = htmlTaskMap.get(taskEntry) - if (!task) { - return - } - abortTask(task) - }) -} - function updateInitialText() { if (document.querySelector(".imageTaskContainer") === null) { if (undoBuffer.length > 0) { @@ -2579,22 +2158,6 @@ function isTabActive(tab) { return tab.classList.contains("active") } -let pauseClient = false - -function resumeClient() { - if (pauseClient) { - document.body.classList.remove("wait-pause") - document.body.classList.add("pause") - } - return new Promise((resolve) => { - let playbuttonclick = function() { - resumeBtn.removeEventListener("click", playbuttonclick) - resolve("resolved") - } - resumeBtn.addEventListener("click", playbuttonclick) - }) -} - function splashScreen(force = false) { const splashVersion = splashScreenPopup.dataset["version"] const lastSplash = localStorage.getItem("lastSplashScreenVersion") || 0 @@ -2612,21 +2175,6 @@ document.getElementById("logo_img").addEventListener("click", (e) => { promptField.addEventListener("input", debounce(renameMakeImageButton, 1000)) -pauseBtn.addEventListener("click", function() { - pauseClient = true - pauseBtn.style.display = "none" - resumeBtn.style.display = "inline" - document.body.classList.add("wait-pause") -}) - -resumeBtn.addEventListener("click", function() { - pauseClient = false - resumeBtn.style.display = "none" - pauseBtn.style.display = "inline" - document.body.classList.remove("pause") - document.body.classList.remove("wait-pause") -}) - function onPing(event) { tunnelUpdate(event) packagesUpdate(event) @@ -3128,3 +2676,83 @@ let recentResolutionsValues = [] heightField.value = temp }) })() + +TASK_CALLBACKS["before_task_start"].push(function(task) { + // Update the seed *before* starting the processing so it's retained if user stops the task + if (randomSeedField.checked) { + seedField.value = task.seed + } +}) + +TASK_CALLBACKS["after_task_start"].push(function(task) { + // setStatus("request", "fetching..") // no-op implementation + renderButtons.style.display = "flex" + renameMakeImageButton() + updateInitialText() +}) + +TASK_CALLBACKS["on_task_step"].push(function(task, reqBody, stepUpdate, outputContainer) { + showImages(reqBody, stepUpdate, outputContainer, true) +}) + +TASK_CALLBACKS["on_render_task_success"].push(function(task, reqBody, stepUpdate, outputContainer) { + showImages(reqBody, stepUpdate, outputContainer, false) +}) + +TASK_CALLBACKS["on_render_task_fail"].push(function(task, reqBody, stepUpdate, outputContainer) { + const outputMsg = task["outputMsg"] + let msg = "" + if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { + msg = stepUpdate.detail + if (msg.toLowerCase().includes("out of memory")) { + msg += `

+ Suggestions: +
+ 1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.
+ 2. Try picking a lower level in the 'GPU Memory Usage' setting (in the 'Settings' tab).
+ 3. Try generating a smaller image.
` + } else if (msg.includes("DefaultCPUAllocator: not enough memory")) { + msg += `

+ Reason: Your computer is running out of system RAM! +

+ Suggestions: +
+ 1. Try closing unnecessary programs and browser tabs.
+ 2. If that doesn't help, please increase your computer's virtual memory by following these steps for + Windows or + Linux.
+ 3. Try restarting your computer.
` + } else if (msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape")) { + msg += `

+ Reason: You tried to use a LORA that was trained for a different Stable Diffusion model version! +

+ Suggestions: +
+ Try to use a different model or a different LORA.` + } else if (msg.includes("'ModuleList' object has no attribute '1'")) { + msg += `

+ Reason: SDXL models need a yaml config file. +

+ Suggestions: +
+
    +
  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)}
` + } + logError(msg, stepUpdate, outputMsg) +}) + +TASK_CALLBACKS["on_all_tasks_complete"].push(function() { + renderButtons.style.display = "none" + renameMakeImageButton() + + if (isSoundEnabled()) { + playSound() + } +}) diff --git a/ui/media/js/task-manager.js b/ui/media/js/task-manager.js new file mode 100644 index 00000000..1f100cb2 --- /dev/null +++ b/ui/media/js/task-manager.js @@ -0,0 +1,376 @@ +const htmlTaskMap = new WeakMap() + +const pauseBtn = document.querySelector("#pause") +const resumeBtn = document.querySelector("#resume") +const processOrder = document.querySelector("#process_order_toggle") + +let TASK_CALLBACKS = { + before_task_start: [], + after_task_start: [], + on_task_step: [], + on_render_task_success: [], + on_render_task_fail: [], + on_all_tasks_complete: [], +} + +let pauseClient = false + +async function onIdle() { + const serverCapacity = SD.serverCapacity + if (pauseClient === true) { + await resumeClient() + } + + for (const taskEntry of getUncompletedTaskEntries()) { + if (SD.activeTasks.size >= serverCapacity) { + break + } + const task = htmlTaskMap.get(taskEntry) + if (!task) { + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + taskStatusLabel.style.display = "none" + continue + } + await onTaskStart(task) + } +} + +function getUncompletedTaskEntries() { + const taskEntries = Array.from(document.querySelectorAll("#preview .imageTaskContainer .taskStatusLabel")) + .filter((taskLabel) => taskLabel.style.display !== "none") + .map(function(taskLabel) { + let imageTaskContainer = taskLabel.parentNode + while (!imageTaskContainer.classList.contains("imageTaskContainer") && imageTaskContainer.parentNode) { + imageTaskContainer = imageTaskContainer.parentNode + } + return imageTaskContainer + }) + if (!processOrder.checked) { + taskEntries.reverse() + } + return taskEntries +} + +async function onTaskStart(task) { + if (!task.isProcessing || task.batchesDone >= task.batchCount) { + return + } + + if (typeof task.startTime !== "number") { + task.startTime = Date.now() + } + if (!("instances" in task)) { + task["instances"] = [] + } + + task["stopTask"].innerHTML = ' Stop' + task["taskStatusLabel"].innerText = "Starting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + + let newTaskReqBody = task.reqBody + if (task.batchCount > 1) { + // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. + newTaskReqBody = Object.assign({}, task.reqBody) + if (task.batchesDone == task.batchCount - 1) { + // Last batch of the task + // If the number of parallel jobs is no factor of the total number of images, the last batch must create less than "parallel jobs count" images + // E.g. with numOutputsTotal = 6 and num_outputs = 5, the last batch shall only generate 1 image. + newTaskReqBody.num_outputs = task.numOutputsTotal - task.reqBody.num_outputs * (task.batchCount - 1) + } + } + + const startSeed = task.seed || newTaskReqBody.seed + const genSeeds = Boolean( + typeof newTaskReqBody.seed !== "number" || (newTaskReqBody.seed === task.seed && task.numOutputsTotal > 1) + ) + if (genSeeds) { + newTaskReqBody.seed = parseInt(startSeed) + task.batchesDone * task.reqBody.num_outputs + } + + const outputContainer = document.createElement("div") + outputContainer.className = "img-batch" + task.outputContainer.insertBefore(outputContainer, task.outputContainer.firstChild) + + const eventInfo = { reqBody: newTaskReqBody } + const callbacksPromises = PLUGINS["TASK_CREATE"].map((hook) => { + if (typeof hook !== "function") { + console.error("The provided TASK_CREATE hook is not a function. Hook: %o", hook) + return Promise.reject(new Error("hook is not a function.")) + } + try { + return Promise.resolve(hook.call(task, eventInfo)) + } catch (err) { + console.error(err) + return Promise.reject(err) + } + }) + await Promise.allSettled(callbacksPromises) + let instance = eventInfo.instance + if (!instance) { + const factory = PLUGINS.OUTPUTS_FORMATS.get(eventInfo.reqBody?.output_format || newTaskReqBody.output_format) + if (factory) { + instance = await Promise.resolve(factory(eventInfo.reqBody || newTaskReqBody)) + } + if (!instance) { + console.error( + `${factory ? "Factory " + String(factory) : "No factory defined"} for output format ${eventInfo.reqBody + ?.output_format || newTaskReqBody.output_format}. Instance is ${instance || + "undefined"}. Using default renderer.` + ) + instance = new SD.RenderTask(eventInfo.reqBody || newTaskReqBody) + } + } + + task["instances"].push(instance) + task.batchesDone++ + + TASK_CALLBACKS["before_task_start"].forEach((callback) => callback(task)) + + instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( + (renderResult) => { + onRenderTaskCompleted(task, newTaskReqBody, instance, outputContainer, renderResult) + }, + (reason) => { + onTaskErrorHandler(task, newTaskReqBody, instance, reason) + } + ) + + TASK_CALLBACKS["after_task_start"].forEach((callback) => callback(task)) +} + +function getTaskUpdater(task, reqBody, outputContainer) { + const outputMsg = task["outputMsg"] + const progressBar = task["progressBar"] + const progressBarInner = progressBar.querySelector("div") + + const batchCount = task.batchCount + let lastStatus = undefined + return async function(event) { + if (this.status !== lastStatus) { + lastStatus = this.status + switch (this.status) { + case SD.TaskStatus.pending: + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + break + case SD.TaskStatus.waiting: + task["taskStatusLabel"].innerText = "Waiting" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") + break + case SD.TaskStatus.processing: + case SD.TaskStatus.completed: + task["taskStatusLabel"].innerText = "Processing" + task["taskStatusLabel"].classList.add("activeTaskLabel") + task["taskStatusLabel"].classList.remove("waitingTaskLabel") + break + case SD.TaskStatus.stopped: + break + case SD.TaskStatus.failed: + if (!SD.isServerAvailable()) { + logError( + "Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", + event, + outputMsg + ) + } else if (typeof event?.response === "object") { + let msg = "Stable Diffusion had an error reading the response:
"
+                        if (this.exception) {
+                            msg += `Error: ${this.exception.message}
` + } + try { + // 'Response': body stream already read + msg += "Read: " + (await event.response.text()) + } catch (e) { + msg += "Unexpected end of stream. " + } + const bufferString = event.reader.bufferedString + if (bufferString) { + msg += "Buffered data: " + bufferString + } + msg += "
" + logError(msg, event, outputMsg) + } + break + } + } + if ("update" in event) { + const stepUpdate = event.update + if (!("step" in stepUpdate)) { + return + } + // task.instances can be a mix of different tasks with uneven number of steps (Render Vs Filter Tasks) + const instancesWithProgressUpdates = task.instances.filter((instance) => instance.step !== undefined) + const overallStepCount = + instancesWithProgressUpdates.reduce( + (sum, instance) => + sum + + (instance.isPending + ? Math.max(0, instance.step || stepUpdate.step) / + (instance.total_steps || stepUpdate.total_steps) + : 1), + 0 // Initial value + ) * stepUpdate.total_steps // Scale to current number of steps. + const totalSteps = instancesWithProgressUpdates.reduce( + (sum, instance) => sum + (instance.total_steps || stepUpdate.total_steps), + stepUpdate.total_steps * (batchCount - task.batchesDone) // Initial value at (unstarted task count * Nbr of steps) + ) + const percent = Math.min(100, 100 * (overallStepCount / totalSteps)).toFixed(0) + + const timeTaken = stepUpdate.step_time // sec + const stepsRemaining = Math.max(0, totalSteps - overallStepCount) + const timeRemaining = timeTaken < 0 ? "" : millisecondsToStr(stepsRemaining * timeTaken * 1000) + outputMsg.innerHTML = `Batch ${task.batchesDone} of ${batchCount}. Generating image(s): ${percent}%. Time remaining (approx): ${timeRemaining}` + outputMsg.style.display = "block" + progressBarInner.style.width = `${percent}%` + + if (stepUpdate.output) { + TASK_CALLBACKS["on_task_step"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } + } + } +} + +function onRenderTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { + if (typeof stepUpdate === "object") { + if (stepUpdate.status === "succeeded") { + TASK_CALLBACKS["on_render_task_success"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } else { + task.isProcessing = false + TASK_CALLBACKS["on_render_task_fail"].forEach((callback) => + callback(task, reqBody, stepUpdate, outputContainer) + ) + } + } + if (task.isProcessing && task.batchesDone < task.batchCount) { + task["taskStatusLabel"].innerText = "Pending" + task["taskStatusLabel"].classList.add("waitingTaskLabel") + task["taskStatusLabel"].classList.remove("activeTaskLabel") + return + } + if ("instances" in task && task.instances.some((ins) => ins != instance && ins.isPending)) { + return + } + + task.isProcessing = false + task["stopTask"].innerHTML = ' Remove' + task["taskStatusLabel"].style.display = "none" + + let time = millisecondsToStr(Date.now() - task.startTime) + + if (task.batchesDone == task.batchCount) { + if (!task.outputMsg.innerText.toLowerCase().includes("error")) { + task.outputMsg.innerText = `Processed ${task.numOutputsTotal} images in ${time}` + } + task.progressBar.style.height = "0px" + task.progressBar.style.border = "0px solid var(--background-color3)" + task.progressBar.classList.remove("active") + // setStatus("request", "done", "success") + } else { + task.outputMsg.innerText += `. Task ended after ${time}` + } + + // if (randomSeedField.checked) { // we already update this before the task starts + // seedField.value = task.seed + // } + + if (SD.activeTasks.size > 0) { + return + } + const uncompletedTasks = getUncompletedTaskEntries() + if (uncompletedTasks && uncompletedTasks.length > 0) { + return + } + + if (pauseClient) { + resumeBtn.click() + } + + TASK_CALLBACKS["on_all_tasks_complete"].forEach((callback) => callback()) +} + +function resumeClient() { + if (pauseClient) { + document.body.classList.remove("wait-pause") + document.body.classList.add("pause") + } + return new Promise((resolve) => { + let playbuttonclick = function() { + resumeBtn.removeEventListener("click", playbuttonclick) + resolve("resolved") + } + resumeBtn.addEventListener("click", playbuttonclick) + }) +} + +function abortTask(task) { + if (!task.isProcessing) { + return false + } + task.isProcessing = false + task.progressBar.classList.remove("active") + task["taskStatusLabel"].style.display = "none" + task["stopTask"].innerHTML = ' Remove' + if (!task.instances?.some((r) => r.isPending)) { + return + } + task.instances.forEach((instance) => { + try { + instance.abort() + } catch (e) { + console.error(e) + } + }) +} + +async function stopAllTasks() { + getUncompletedTaskEntries().forEach((taskEntry) => { + const taskStatusLabel = taskEntry.querySelector(".taskStatusLabel") + if (taskStatusLabel) { + taskStatusLabel.style.display = "none" + } + const task = htmlTaskMap.get(taskEntry) + if (!task) { + return + } + abortTask(task) + }) +} + +function onTaskErrorHandler(task, reqBody, instance, reason) { + if (!task.isProcessing) { + return + } + console.log("Render request %o, Instance: %o, Error: %s", reqBody, instance, reason) + abortTask(task) + const outputMsg = task["outputMsg"] + logError( + "Stable Diffusion had an error. Please check the logs in the command-line window.

" + + reason + + "
" +
+            reason.stack +
+            "
", + task, + outputMsg + ) + // setStatus("request", "error", "error") +} + +pauseBtn.addEventListener("click", function() { + pauseClient = true + pauseBtn.style.display = "none" + resumeBtn.style.display = "inline" + document.body.classList.add("wait-pause") +}) + +resumeBtn.addEventListener("click", function() { + pauseClient = false + resumeBtn.style.display = "none" + pauseBtn.style.display = "inline" + document.body.classList.remove("pause") + document.body.classList.remove("wait-pause") +}) diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js index 8fef9450..e3014b91 100644 --- a/ui/media/js/utils.js +++ b/ui/media/js/utils.js @@ -1198,4 +1198,37 @@ function makeDialogDraggable(element) { })() ) } +function logMsg(msg, level, outputMsg) { + if (outputMsg.hasChildNodes()) { + outputMsg.appendChild(document.createElement("br")) + } + if (level === "error") { + outputMsg.innerHTML += 'Error: ' + msg + "" + } else if (level === "warn") { + outputMsg.innerHTML += 'Warning: ' + msg + "" + } else { + outputMsg.innerText += msg + } + console.log(level, msg) +} +function logError(msg, res, outputMsg) { + logMsg(msg, "error", outputMsg) + + console.log("request error", res) + console.trace() + // setStatus("request", "error", "error") +} + +function playSound() { + const audio = new Audio("/media/ding.mp3") + audio.volume = 0.2 + var promise = audio.play() + if (promise !== undefined) { + promise + .then((_) => {}) + .catch((error) => { + console.warn("browser blocked autoplay") + }) + } +} From 19fdba7d73c1140a2405c81fc6a14804df97f1a0 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 11:04:13 +0530 Subject: [PATCH 075/120] Send the controlnet filter preview request only when the task is about to start --- ui/media/js/main.js | 28 +++++++++++++--------------- ui/media/js/task-manager.js | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index b7ec4dd1..f7a93f46 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -509,7 +509,11 @@ function showImages(reqBody, res, outputContainer, livePreview) { { text: "Upscale", on_click: onUpscaleClick }, { text: "Fix Faces", on_click: onFixFacesClick }, ], - { text: "Use as Thumbnail", on_click: onUseAsThumbnailClick, filter: (req, img) => "use_embeddings_model" in req }, + { + text: "Use as Thumbnail", + on_click: onUseAsThumbnailClick, + filter: (req, img) => "use_embeddings_model" in req, + }, ] // include the plugins @@ -720,7 +724,7 @@ function onUseAsThumbnailClick(req, img) { onUseAsThumbnailClick.croppr.setImage(img.src) } - let embeddings = req.use_embeddings_model.map(e => e.split("/").pop()) + let embeddings = req.use_embeddings_model.map((e) => e.split("/").pop()) let LORA = [] if ("use_lora_model" in req) { @@ -1080,7 +1084,6 @@ function createTask(task) { } if (task.reqBody.control_image !== undefined && task.reqBody.control_filter_to_apply !== undefined) { - let controlImagePreview = taskEntry.querySelector(".controlnet-img-preview > img") let req = { image: task.reqBody.control_image, filter: task.reqBody.control_filter_to_apply, @@ -1088,15 +1091,8 @@ function createTask(task) { filter_params: {}, } req["model_paths"][task.reqBody.control_filter_to_apply] = task.reqBody.control_filter_to_apply - SD.filter(req).then( - (result) => { - console.log(result) - controlImagePreview.src = result.output[0] - let controlImageLargePreview = taskEntry.querySelector(".controlnet-img-preview .task-fs-initimage img") - controlImageLargePreview.src = controlImagePreview.src - }, - (error) => console.log("filter error", error) - ) + + task["previewTaskReq"] = req } createCollapsibles(taskEntry) @@ -1129,6 +1125,7 @@ function createTask(task) { startY = e.target.closest(".imageTaskContainer").offsetTop }) + task["taskConfig"] = taskEntry.querySelector(".taskConfig") task["taskStatusLabel"] = taskEntry.querySelector(".taskStatusLabel") task["outputContainer"] = taskEntry.querySelector(".img-preview") task["outputMsg"] = taskEntry.querySelector(".outputMsg") @@ -2289,7 +2286,7 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun /* Embeddings */ -addEmbeddingsThumb.addEventListener("click", e => addEmbeddingsThumbInput.click()) +addEmbeddingsThumb.addEventListener("click", (e) => addEmbeddingsThumbInput.click()) addEmbeddingsThumbInput.addEventListener("change", loadThumbnailImageFromFile) function loadThumbnailImageFromFile() { @@ -2305,7 +2302,9 @@ function loadThumbnailImageFromFile() { img.src = reader.result onUseAsThumbnailClick( { - use_embeddings_model: getAllModelNames("embeddings").sort((a, b) => a.localeCompare(b, undefined, { sensitivity: 'base' })) + use_embeddings_model: getAllModelNames("embeddings").sort((a, b) => + a.localeCompare(b, undefined, { sensitivity: "base" }) + ), }, img ) @@ -2316,7 +2315,6 @@ function loadThumbnailImageFromFile() { } } - function updateEmbeddingsList(filter = "") { function html(model, iconlist = [], prefix = "", filter = "") { filter = filter.toLowerCase() diff --git a/ui/media/js/task-manager.js b/ui/media/js/task-manager.js index 1f100cb2..a36bf90b 100644 --- a/ui/media/js/task-manager.js +++ b/ui/media/js/task-manager.js @@ -67,6 +67,23 @@ async function onTaskStart(task) { task["taskStatusLabel"].innerText = "Starting" task["taskStatusLabel"].classList.add("waitingTaskLabel") + if (task.previewTaskReq !== undefined) { + let controlImagePreview = task.taskConfig.querySelector(".controlnet-img-preview > img") + try { + let result = await SD.filter(task.previewTaskReq) + + controlImagePreview.src = result.output[0] + let controlImageLargePreview = task.taskConfig.querySelector( + ".controlnet-img-preview .task-fs-initimage img" + ) + controlImageLargePreview.src = controlImagePreview.src + } catch (error) { + console.log("filter error", error) + } + + delete task.previewTaskReq + } + let newTaskReqBody = task.reqBody if (task.batchCount > 1) { // Each output render batch needs it's own task reqBody instance to avoid altering the other runs after they are completed. From be83336cf7f7d71f49d4bb4e3971defe53456524 Mon Sep 17 00:00:00 2001 From: JeLuF Date: Mon, 21 Aug 2023 09:58:26 +0200 Subject: [PATCH 076/120] WebP metadata support (#1511) * WebP metadata support - Replace piexif.js by Exif-Reader - Merge plugin html/css to index.html/main.css * Add webp to tooltip message --- 3rd-PARTY-LICENSES | 380 +++ ui/index.html | 15 +- ui/media/css/image-editor.css | 25 +- ui/media/css/main.css | 14 + ui/media/js/exif-reader.js | 2 + .../ui/image-editor-improvements.plugin.js | 2577 +---------------- 6 files changed, 459 insertions(+), 2554 deletions(-) create mode 100644 ui/media/js/exif-reader.js diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES index 78bfe3bb..05ba4541 100644 --- a/3rd-PARTY-LICENSES +++ b/3rd-PARTY-LICENSES @@ -740,3 +740,383 @@ croppr.js is licensed under the MIT license: LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +ExifReader +========== +https://github.com/mattiasw/ExifReader + +ExifReader is licensed under the Mozilla Public License: + + Mozilla Public License Version 2.0 + ================================== + + 1. Definitions + -------------- + + 1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + + 1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" + means Covered Software of a particular Contributor. + + 1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + + 1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + + 1.6. "Executable Form" + means any form of the work other than Source Code Form. + + 1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + + 1.8. "License" + means this document. + + 1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + + 1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + + 1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + + 1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + + 1.13. "Source Code Form" + means the form of the work preferred for making modifications. + + 1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + + 2. License Grants and Conditions + -------------------------------- + + 2.1. Grants + + Each Contributor hereby grants You a world-wide, royalty-free, + non-exclusive license: + + (a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + + (b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + + 2.2. Effective Date + + The licenses granted in Section 2.1 with respect to any Contribution + become effective for each Contribution on the date the Contributor first + distributes such Contribution. + + 2.3. Limitations on Grant Scope + + The licenses granted in this Section 2 are the only rights granted under + this License. No additional rights or licenses will be implied from the + distribution or licensing of Covered Software under this License. + Notwithstanding Section 2.1(b) above, no patent license is granted by a + Contributor: + + (a) for any code that a Contributor has removed from Covered Software; + or + + (b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + + (c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + + This License does not grant any rights in the trademarks, service marks, + or logos of any Contributor (except as may be necessary to comply with + the notice requirements in Section 3.4). + + 2.4. Subsequent Licenses + + No Contributor makes additional grants as a result of Your choice to + distribute the Covered Software under a subsequent version of this + License (see Section 10.2) or under the terms of a Secondary License (if + permitted under the terms of Section 3.3). + + 2.5. Representation + + Each Contributor represents that the Contributor believes its + Contributions are its original creation(s) or it has sufficient rights + to grant the rights to its Contributions conveyed by this License. + + 2.6. Fair Use + + This License is not intended to limit any rights You have under + applicable copyright doctrines of fair use, fair dealing, or other + equivalents. + + 2.7. Conditions + + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted + in Section 2.1. + + 3. Responsibilities + ------------------- + + 3.1. Distribution of Source Form + + All distribution of Covered Software in Source Code Form, including any + Modifications that You create or to which You contribute, must be under + the terms of this License. You must inform recipients that the Source + Code Form of the Covered Software is governed by the terms of this + License, and how they can obtain a copy of this License. You may not + attempt to alter or restrict the recipients' rights in the Source Code + Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + + (a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + + (b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + + You may create and distribute a Larger Work under terms of Your choice, + provided that You also comply with the requirements of this License for + the Covered Software. If the Larger Work is a combination of Covered + Software with a work governed by one or more Secondary Licenses, and the + Covered Software is not Incompatible With Secondary Licenses, this + License permits You to additionally distribute such Covered Software + under the terms of such Secondary License(s), so that the recipient of + the Larger Work may, at their option, further distribute the Covered + Software under the terms of either this License or such Secondary + License(s). + + 3.4. Notices + + You may not remove or alter the substance of any license notices + (including copyright notices, patent notices, disclaimers of warranty, + or limitations of liability) contained within the Source Code Form of + the Covered Software, except that You may alter any license notices to + the extent required to remedy known factual inaccuracies. + + 3.5. Application of Additional Terms + + You may choose to offer, and to charge a fee for, warranty, support, + indemnity or liability obligations to one or more recipients of Covered + Software. However, You may do so only on Your own behalf, and not on + behalf of any Contributor. You must make it absolutely clear that any + such warranty, support, indemnity, or liability obligation is offered by + You alone, and You hereby agree to indemnify every Contributor for any + liability incurred by such Contributor as a result of warranty, support, + indemnity or liability terms You offer. You may include additional + disclaimers of warranty and limitations of liability specific to any + jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + --------------------------------------------------- + + If it is impossible for You to comply with any of the terms of this + License with respect to some or all of the Covered Software due to + statute, judicial order, or regulation then You must: (a) comply with + the terms of this License to the maximum extent possible; and (b) + describe the limitations and the code they affect. Such description must + be placed in a text file included with all distributions of the Covered + Software under this License. Except to the extent prohibited by statute + or regulation, such description must be sufficiently detailed for a + recipient of ordinary skill to be able to understand it. + + 5. Termination + -------------- + + 5.1. The rights granted under this License will terminate automatically + if You fail to comply with any of its terms. However, if You become + compliant, then the rights granted under this License from a particular + Contributor are reinstated (a) provisionally, unless and until such + Contributor explicitly and finally terminates Your grants, and (b) on an + ongoing basis, if such Contributor fails to notify You of the + non-compliance by some reasonable means prior to 60 days after You have + come back into compliance. Moreover, Your grants from a particular + Contributor are reinstated on an ongoing basis if such Contributor + notifies You of the non-compliance by some reasonable means, this is the + first time You have received notice of non-compliance with this License + from such Contributor, and You become compliant prior to 30 days after + Your receipt of the notice. + + 5.2. If You initiate litigation against any entity by asserting a patent + infringement claim (excluding declaratory judgment actions, + counter-claims, and cross-claims) alleging that a Contributor Version + directly or indirectly infringes any patent, then the rights granted to + You by any and all Contributors for the Covered Software under Section + 2.1 of this License shall terminate. + + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all + end user license agreements (excluding distributors and resellers) which + have been validly granted by You or Your distributors under this License + prior to termination shall survive termination. + + ************************************************************************ + * * + * 6. Disclaimer of Warranty * + * ------------------------- * + * * + * Covered Software is provided under this License on an "as is" * + * basis, without warranty of any kind, either expressed, implied, or * + * statutory, including, without limitation, warranties that the * + * Covered Software is free of defects, merchantable, fit for a * + * particular purpose or non-infringing. The entire risk as to the * + * quality and performance of the Covered Software is with You. * + * Should any Covered Software prove defective in any respect, You * + * (not any Contributor) assume the cost of any necessary servicing, * + * repair, or correction. This disclaimer of warranty constitutes an * + * essential part of this License. No use of any Covered Software is * + * authorized under this License except under this disclaimer. * + * * + ************************************************************************ + + ************************************************************************ + * * + * 7. Limitation of Liability * + * -------------------------- * + * * + * Under no circumstances and under no legal theory, whether tort * + * (including negligence), contract, or otherwise, shall any * + * Contributor, or anyone who distributes Covered Software as * + * permitted above, be liable to You for any direct, indirect, * + * special, incidental, or consequential damages of any character * + * including, without limitation, damages for lost profits, loss of * + * goodwill, work stoppage, computer failure or malfunction, or any * + * and all other commercial damages or losses, even if such party * + * shall have been informed of the possibility of such damages. This * + * limitation of liability shall not apply to liability for death or * + * personal injury resulting from such party's negligence to the * + * extent applicable law prohibits such limitation. Some * + * jurisdictions do not allow the exclusion or limitation of * + * incidental or consequential damages, so this exclusion and * + * limitation may not apply to You. * + * * + ************************************************************************ + + 8. Litigation + ------------- + + Any litigation relating to this License may be brought only in the + courts of a jurisdiction where the defendant maintains its principal + place of business and such litigation shall be governed by laws of that + jurisdiction, without reference to its conflict-of-law provisions. + Nothing in this Section shall prevent a party's ability to bring + cross-claims or counter-claims. + + 9. Miscellaneous + ---------------- + + This License represents the complete agreement concerning the subject + matter hereof. If any provision of this License is held to be + unenforceable, such provision shall be reformed only to the extent + necessary to make it enforceable. Any law or regulation which provides + that the language of a contract shall be construed against the drafter + shall not be used to construe this License against a Contributor. + + 10. Versions of the License + --------------------------- + + 10.1. New Versions + + Mozilla Foundation is the license steward. Except as provided in Section + 10.3, no one other than the license steward has the right to modify or + publish new versions of this License. Each version will be given a + distinguishing version number. + + 10.2. Effect of New Versions + + You may distribute the Covered Software under the terms of the version + of the License under which You originally received the Covered Software, + or under the terms of any subsequent version published by the license + steward. + + 10.3. Modified Versions + + If you create software not governed by this License, and you want to + create a new license for such software, you may create and use a + modified version of this License if you rename the license and remove + any references to the name of the license steward (except to note that + such modified license differs from this License). + + 10.4. Distributing Source Code Form that is Incompatible With Secondary + Licenses + + If You choose to distribute Source Code Form that is Incompatible With + Secondary Licenses under the terms of this version of the License, the + notice described in Exhibit B of this License must be attached. + + Exhibit A - Source Code Form License Notice + ------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to look + for such a notice. + + You may add additional accurate notices of copyright ownership. + + Exhibit B - "Incompatible With Secondary Licenses" Notice + --------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/ui/index.html b/ui/index.html index 32fdfbf0..05a83c57 100644 --- a/ui/index.html +++ b/ui/index.html @@ -26,6 +26,7 @@ +
@@ -61,7 +62,14 @@
- or + + or +
@@ -83,6 +91,11 @@
+ + Add img2img source image using the Browse button, via drag & drop from external file or browser image (incl. + rendered image) or by pasting an image from the clipboard using Ctrl+V.

+ You may also reload the metadata embedded in a PNG, WEBP or JPEG image (enable embedding from the Settings). +
diff --git a/ui/media/css/image-editor.css b/ui/media/css/image-editor.css index ba046383..46f49ebe 100644 --- a/ui/media/css/image-editor.css +++ b/ui/media/css/image-editor.css @@ -229,4 +229,27 @@ } .inpainter .load_mask { display: flex; -} \ No newline at end of file +} + +.editor-canvas-overlay { + cursor: none; +} + +.image-brush-preview { + position: fixed; + background: black; + opacity: 0.3; + borderRadius: 50%; + cursor: none; + pointer-events: none; + transform: translate(-50%, -50%); +} + +.editor-options-container > * > *:not(.active):not(.button) { + border: 1px dotted slategray; +} + +.image_editor_opacity .editor-options-container > * > *:not(.active):not(.button) { + border: 1px dotted slategray; +} + diff --git a/ui/media/css/main.css b/ui/media/css/main.css index 25602e8c..e9021ead 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1927,6 +1927,20 @@ div#enlarge-buttons { width: 77%; } +.drop-area { + width: 45%; + height: 50px; + border: 2px dashed #ccc; + text-align: center; + line-height: 50px; + font-size: small; + color: #ccc; + border-radius: 10px; + display: none; + margin: 12px 10px; +} + + /* hack for fixing Image Modifier Improvements plugin */ #imageTagPopupContainer { position: absolute; diff --git a/ui/media/js/exif-reader.js b/ui/media/js/exif-reader.js new file mode 100644 index 00000000..6fdc7045 --- /dev/null +++ b/ui/media/js/exif-reader.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.ExifReader=t():e.ExifReader=t()}("undefined"!=typeof self?self:this,(function(){return function(){"use strict";var e={d:function(t,n){for(var r in n)e.o(n,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:1,get:n[r]})},o:function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r:function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:1})}},t={};function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function r(e,t,n){for(var r=[],i=0;i=8){var t=l(e.slice(0,8));if("ASCII\0\0\0"===t)return l(e.slice(8));if("JIS\0\0\0\0\0"===t)return"[JIS encoded text]";if("UNICODE\0"===t)return"[Unicode encoded text]";if("\0\0\0\0\0\0\0\0"===t)return"[Undefined encoding]"}return"Undefined"}function p(e){return e[0][0]/e[0][1]+e[1][0]/e[1][1]/60+e[2][0]/e[2][1]/3600}var m=18761,g=m,v=function(e,t){if(e.getUint16(t)===m)return m;if(19789===e.getUint16(t))return 19789;throw Error("Illegal byte order value. Faulty image.")},h=65505;function y(e,t){return 65472===e.getUint16(t)}function S(e,t){return 65474===e.getUint16(t)}function b(e,t){return 65506===e.getUint16(t)&&"ICC_PROFILE\0"===r(e,t+4,12)}function C(e,t){return 65506===e.getUint16(t)&&"MPF\0"===r(e,t+4,4)}function A(e,t){return 65504===e.getUint16(t)&&"JFIF"===r(e,t+4,4)&&0===e.getUint8(t+4+4)}function I(e,t){return e.getUint16(t)===h&&"Exif"===r(e,t+4,4)&&0===e.getUint8(t+4+4)}function P(e,t){return e.getUint16(t)===h&&function(e,t){return"http://ns.adobe.com/xap/1.0/\0"===r(e,t+4,29)}(e,t)}function U(e,t){return e.getUint16(t)===h&&function(e,t){return"http://ns.adobe.com/xmp/extension/\0"===r(e,t+4,35)}(e,t)}function w(e,t){return{dataOffset:e+33,length:t-31}}function T(e,t){return{dataOffset:e+79,length:t-77}}function D(e,t){return 65517===e.getUint16(t)&&"Photoshop 3.0"===r(e,t+4,13)&&0===e.getUint8(t+4+13)}function x(e,t){var n=e.getUint16(t);return n>=65504&&n<=65519||65534===n||65472===n||65474===n||65476===n||65499===n||65501===n||65498===n}function M(e,t){return 65535===e.getUint16(t)}var O="‰PNG\r\n\n",F="iTXt",R="pHYs",L="tIME";function k(e,t){return"IHDR"===r(e,t+4,4)}function E(e,t){return"iTXt"===r(e,t+4,4)&&"XML:com.adobe.xmp\0"===r(e,t+8,18)}function N(e,t){var n=r(e,t+4,4);return"tEXt"===n||"iTXt"===n}function G(e,t){return"eXIf"===r(e,t+4,4)}function B(e,t){var n=[R,L],i=r(e,t+4,4);return n.includes(i)}function j(e,t){t+=28;for(var n=0;n<2&&t=4&&function(e){var t=e.getUint16(0)===g;return 42===e.getUint16(2,t)}(e)}(e))return{hasAppMarkers:1,tiffHeaderOffset:0};if(function(e){return!!e&&e.byteLength>=2&&65496===e.getUint16(0)}(e))return function(e){for(var t,n,r,i,o,a,u,c,f,s=2;s+4+5<=e.byteLength;){if(y(e,s))n=s+2;else if(S(e,s))r=s+2;else if(A(e,s))t=e.getUint16(s+2),i=s+2;else if(I(e,s))t=e.getUint16(s+2),o=s+10;else if(P(e,s))u||(u=[]),t=e.getUint16(s+2),u.push(w(s,t));else if(U(e,s))u||(u=[]),t=e.getUint16(s+2),u.push(T(s,t));else if(D(e,s))t=e.getUint16(s+2),a=s+18;else if(b(e,s)){var l=s+18,d=(t=e.getUint16(s+2))-16,p=e.getUint8(s+16),m=e.getUint8(s+17);c||(c=[]),c.push({offset:l,length:d,chunkNumber:p,chunksTotal:m})}else if(C(e,s))t=e.getUint16(s+2),f=s+8;else{if(!x(e,s)){if(M(e,s)){s++;continue}break}t=e.getUint16(s+2)}s+=2+t}return{hasAppMarkers:s>2,fileDataOffset:n||r,jfifDataOffset:i,tiffHeaderOffset:o,iptcDataOffset:a,xmpChunks:u,iccChunks:c,mpfDataOffset:f}}(e);if(function(e){return!!e&&r(e,0,8)===O}(e))return function(e){for(var t={hasAppMarkers:0},n=8;n+4+4<=e.byteLength;){if(k(e,n))t.hasAppMarkers=1,t.pngHeaderOffset=n+8;else if(E(e,n)){var i=j(e,n);void 0!==i&&(t.hasAppMarkers=1,t.xmpChunks=[{dataOffset:i,length:e.getUint32(n+0)-(i-(n+8))}])}else if(N(e,n)){t.hasAppMarkers=1;var o=r(e,n+4,4);t.pngTextChunks||(t.pngTextChunks=[]),t.pngTextChunks.push({length:e.getUint32(n+0),type:o,offset:n+8})}else G(e,n)?(t.hasAppMarkers=1,t.tiffHeaderOffset=n+8):B(e,n)&&(t.hasAppMarkers=1,t.pngChunkOffsets||(t.pngChunkOffsets=[]),t.pngChunkOffsets.push(n+0));n+=e.getUint32(n+0)+4+4+4}return t}(e);if(function(e){if(!e)return 0;var t=r(e,8,4);return"ftyp"===r(e,4,4)&&-1!==["heic","heix","hevc","hevx","heim","heis","hevm","hevs","mif1"].indexOf(t)}(e))return function(e){var t=function(e){for(var t=0;t+4+4<=e.byteLength;){var n=z(e,t);if(n>=8&&"meta"===r(e,t+4,4))return{offset:t,length:n};t+=n}return{offset:void 0,length:0}}(e),n=t.offset,i=t.length;if(void 0===n)return{hasAppMarkers:0};var o=Math.min(n+i,e.byteLength),a=function(e,t,n){for(var i={ilocOffset:void 0,exifItemOffset:void 0,colrOffset:void 0};t+4<=n&&(!i.ilocOffset||!i.exifItemOffset||!i.colrOffset);){var o=r(e,t,4);"iloc"===o?i.ilocOffset=t:"Exif"===o?i.exifItemOffset=t+-4:"colr"===o&&(i.colrOffset=t+-4),t++}return i}(e,n,o),u=a.exifItemOffset,c=a.ilocOffset,f=a.colrOffset,s=function(e,t,n,r){if(n&&t&&!(t+2>r)){var i=e.getUint16(t);for(n+=12;n+16<=r;){if(e.getUint16(n)===i){var o=e.getUint32(n+8);if(o+4<=e.byteLength)return o+(e.getUint32(o)+4)}n+=16}}}(e,u,c,o),l=function(e,t,n){if(t&&!(t+12>n)){var i=r(e,t+8,4);if("prof"===i||"rICC"===i)return[{offset:t+12,length:z(e,t)-12,chunkNumber:1,chunksTotal:1}]}}(e,f,o);return{hasAppMarkers:void 0!==s||void 0!==l,tiffHeaderOffset:s,iccChunks:l}}(e);if(function(e){return!!e&&"RIFF"===r(e,0,4)&&"WEBP"===r(e,8,4)}(e))return function(e){for(var t,n,i,o=12,a=0;o+8=e[1]?"".concat(Math.round(e[0]/e[1])):0!==e[0]?"1/".concat(Math.round(e[1]/e[0])):"0/".concat(e[1])},FNumber:function(e){return"f/".concat(e[0]/e[1])},FocalLength:function(e){return e[0]/e[1]+" mm"},FocalPlaneResolutionUnit:function(e){return 2===e?"inches":3===e?"centimeters":"Unknown"},LightSource:function(e){return 1===e?"Daylight":2===e?"Fluorescent":3===e?"Tungsten (incandescent light)":4===e?"Flash":9===e?"Fine weather":10===e?"Cloudy weather":11===e?"Shade":12===e?"Daylight fluorescent (D 5700 – 7100K)":13===e?"Day white fluorescent (N 4600 – 5400K)":14===e?"Cool white fluorescent (W 3900 – 4500K)":15===e?"White fluorescent (WW 3200 – 3700K)":17===e?"Standard light A":18===e?"Standard light B":19===e?"Standard light C":20===e?"D55":21===e?"D65":22===e?"D75":23===e?"D50":24===e?"ISO studio tungsten":255===e?"Other light source":"Unknown"},MeteringMode:function(e){return 1===e?"Average":2===e?"CenterWeightedAverage":3===e?"Spot":4===e?"MultiSpot":5===e?"Pattern":6===e?"Partial":255===e?"Other":"Unknown"},ResolutionUnit:function(e){return 2===e?"inches":3===e?"centimeters":"Unknown"},Saturation:function(e){return 0===e?"Normal":1===e?"Low saturation":2===e?"High saturation":"Unknown"},SceneCaptureType:function(e){return 0===e?"Standard":1===e?"Landscape":2===e?"Portrait":3===e?"Night scene":"Unknown"},Sharpness:function(e){return 0===e?"Normal":1===e?"Soft":2===e?"Hard":"Unknown"},ShutterSpeedValue:function(e){var t=Math.pow(2,e[0]/e[1]);return t<=1?"".concat(Math.round(1/t)):"1/".concat(Math.round(t))},WhiteBalance:function(e){return 0===e?"Auto white balance":1===e?"Manual white balance":"Unknown"},XResolution:function(e){return""+Math.round(e[0]/e[1])},YResolution:function(e){return""+Math.round(e[0]/e[1])}};function J(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n>31&1,e>>30&1,e>>29&1],n=[];return t[0]&&n.push("Dependent Parent Image"),t[1]&&n.push("Dependent Child Image"),t[2]&&n.push("Representative Image"),{value:t,description:n.join(", ")||"None"}}function fe(e){var t=e>>24&7;return{value:t,description:0===t?"JPEG":"Unknown"}}function se(e){var t=16777215&e;return{value:t,description:{196608:"Baseline MP Primary Image",65537:"Large Thumbnail (VGA equivalent)",65538:"Large Thumbnail (Full HD equivalent)",131073:"Multi-Frame Image (Panorama)",131074:"Multi-Frame Image (Disparity)",131075:"Multi-Frame Image (Multi-Angle)",0:"Undefined"}[t]||"Unknown"}}function le(e){return 0===e}function de(e,t,n,r,i,o){var a=X.getTypeSize("SHORT"),u={},c=function(e,t,n){return t+X.getTypeSize("SHORT")<=e.byteLength?X.getShortAt(e,t,n):0}(e,r,i);r+=a;for(var f=0;fe.byteLength);f++){var s=pe(e,t,n,r,i,o);void 0!==s&&(u[s.name]={id:s.id,value:s.value,description:s.description}),r+=12}if(r"}l===X.tagTypes.ASCII&&(a=function(e){try{return e.map((function(e){return decodeURIComponent(escape(e))}))}catch(t){return e}}(a=function(e){for(var t=[],n=0,r=0;r5&&void 0!==arguments[5]?arguments[5]:0,a=[];o&&(r*=X.typeSizes[n],n=X.tagTypes.BYTE);for(var u=0;un)){var r=X.getByteAt(e,t+7);return{value:r,description:""+r}}}(e,t,n);return{"Bits Per Sample":he(e,t,n),"Image Height":ye(e,t,n),"Image Width":Se(e,t,n),"Color Components":r,Subsampling:r&&be(e,t,r.value,n)}};function he(e,t,n){if(!(3>n)){var r=X.getByteAt(e,t+2);return{value:r,description:""+r}}}function ye(e,t,n){if(!(5>n)){var r=X.getShortAt(e,t+3);return{value:r,description:"".concat(r,"px")}}}function Se(e,t,n){if(!(7>n)){var r=X.getShortAt(e,t+5);return{value:r,description:"".concat(r,"px")}}}function be(e,t,n,r){if(!(8+3*n>r)){for(var i=[],o=0;o1?Ce(i)+Ae(i):""}}}function Ce(e){var t={1:"Y",2:"Cb",3:"Cr",4:"I",5:"Q"};return e.map((function(e){return t[e[0]]})).join("")}function Ae(e){var t={17:"4:4:4 (1 1)",18:"4:4:0 (1 2)",20:"4:4:1 (1 4)",33:"4:2:2 (2 1)",34:"4:2:0 (2 2)",36:"4:2:1 (2 4)",65:"4:1:1 (4 1)",66:"4:1:0 (4 2)"};return 0===e.length||void 0===e[0][1]||void 0===t[e[0][1]]?"":t[e[0][1]]}var Ie=function(e,t){var n=function(e,t){return X.getShortAt(e,t)}(e,t),r=function(e,t,n){if(!(15>n)){var r=X.getByteAt(e,t+14);return{value:r,description:"".concat(r,"px")}}}(e,t,n),i=function(e,t,n){if(!(16>n)){var r=X.getByteAt(e,t+15);return{value:r,description:"".concat(r,"px")}}}(e,t,n),o={"JFIF Version":Pe(e,t,n),"Resolution Unit":Ue(e,t,n),XResolution:Te(e,t,n),YResolution:De(e,t,n),"JFIF Thumbnail Width":r,"JFIF Thumbnail Height":i};if(void 0!==r&&void 0!==i){var a=function(e,t,n,r){if(!(0===n||16+n>r))return{value:e.buffer.slice(t+16,t+16+n),description:"<24-bit RGB pixel data>"}}(e,t,3*r.value*i.value,n);a&&(o["JFIF Thumbnail"]=a)}for(var u in o)void 0===o[u]&&delete o[u];return o};function Pe(e,t,n){if(!(9>n)){var r=X.getByteAt(e,t+7),i=X.getByteAt(e,t+7+1);return{value:256*r+i,description:r+"."+i}}}function Ue(e,t,n){if(!(10>n)){var r=X.getByteAt(e,t+9);return{value:r,description:we(r)}}}function we(e){return 0===e?"None":1===e?"inches":2===e?"cm":"Unknown"}function Te(e,t,n){if(!(12>n)){var r=X.getShortAt(e,t+10);return{value:r,description:""+r}}}function De(e,t,n){if(!(14>n)){var r=X.getShortAt(e,t+12);return{value:r,description:""+r}}}var xe={iptc:{256:{name:"Model Version",description:function(e){return""+((e[0]<<8)+e[1])}},261:{name:"Destination",repeatable:1},276:{name:"File Format",description:function(e){return""+((e[0]<<8)+e[1])}},278:{name:"File Format Version",description:function(e){return""+((e[0]<<8)+e[1])}},286:"Service Identifier",296:"Envelope Number",306:"Product ID",316:"Envelope Priority",326:{name:"Date Sent",description:Me},336:{name:"Time Sent",description:Oe},346:{name:"Coded Character Set",description:Fe,encoding_name:Fe},356:"UNO",376:{name:"ARM Identifier",description:function(e){return""+((e[0]<<8)+e[1])}},378:{name:"ARM Version",description:function(e){return""+((e[0]<<8)+e[1])}},512:{name:"Record Version",description:function(e){return""+((e[0]<<8)+e[1])}},515:"Object Type Reference",516:"Object Attribute Reference",517:"Object Name",519:"Edit Status",520:{name:"Editorial Update",description:function(e){return"01"===l(e)?"Additional Language":"Unknown"}},522:"Urgency",524:{name:"Subject Reference",repeatable:1,description:function(e){var t=l(e).split(":");return t[2]+(t[3]?"/"+t[3]:"")+(t[4]?"/"+t[4]:"")}},527:"Category",532:{name:"Supplemental Category",repeatable:1},534:"Fixture Identifier",537:{name:"Keywords",repeatable:1},538:{name:"Content Location Code",repeatable:1},539:{name:"Content Location Name",repeatable:1},542:"Release Date",547:"Release Time",549:"Expiration Date",550:"Expiration Time",552:"Special Instructions",554:{name:"Action Advised",description:function(e){var t=l(e);return"01"===t?"Object Kill":"02"===t?"Object Replace":"03"===t?"Object Append":"04"===t?"Object Reference":"Unknown"}},557:{name:"Reference Service",repeatable:1},559:{name:"Reference Date",repeatable:1},562:{name:"Reference Number",repeatable:1},567:{name:"Date Created",description:Me},572:{name:"Time Created",description:Oe},574:{name:"Digital Creation Date",description:Me},575:{name:"Digital Creation Time",description:Oe},577:"Originating Program",582:"Program Version",587:{name:"Object Cycle",description:function(e){var t=l(e);return"a"===t?"morning":"p"===t?"evening":"b"===t?"both":"Unknown"}},592:{name:"By-line",repeatable:1},597:{name:"By-line Title",repeatable:1},602:"City",604:"Sub-location",607:"Province/State",612:"Country/Primary Location Code",613:"Country/Primary Location Name",615:"Original Transmission Reference",617:"Headline",622:"Credit",627:"Source",628:"Copyright Notice",630:{name:"Contact",repeatable:1},632:"Caption/Abstract",634:{name:"Writer/Editor",repeatable:1},637:{name:"Rasterized Caption",description:function(e){return e}},642:"Image Type",643:{name:"Image Orientation",description:function(e){var t=l(e);return"P"===t?"Portrait":"L"===t?"Landscape":"S"===t?"Square":"Unknown"}},647:"Language Identifier",662:{name:"Audio Type",description:function(e){var t=l(e),n=t.charAt(0),r=t.charAt(1),i="";return"1"===n?i+="Mono":"2"===n&&(i+="Stereo"),"A"===r?i+=", actuality":"C"===r?i+=", question and answer session":"M"===r?i+=", music, transmitted by itself":"Q"===r?i+=", response to a question":"R"===r?i+=", raw sound":"S"===r?i+=", scener":"V"===r?i+=", voicer":"W"===r&&(i+=", wrap"),""!==i?i:t}},663:{name:"Audio Sampling Rate",description:function(e){return parseInt(l(e),10)+" Hz"}},664:{name:"Audio Sampling Resolution",description:function(e){var t=parseInt(l(e),10);return t+(1===t?" bit":" bits")}},665:{name:"Audio Duration",description:function(e){var t=l(e);return t.length>=6?t.substr(0,2)+":"+t.substr(2,2)+":"+t.substr(4,2):t}},666:"Audio Outcue",698:"Short Document ID",699:"Unique Document ID",700:"Owner ID",712:{name:function(e){return 2===e.length?"ObjectData Preview File Format":"Record 2 destination"},description:function(e){if(2===e.length){var t=(e[0]<<8)+e[1];return 0===t?"No ObjectData":1===t?"IPTC-NAA Digital Newsphoto Parameter Record":2===t?"IPTC7901 Recommended Message Format":3===t?"Tagged Image File Format (Adobe/Aldus Image data)":4===t?"Illustrator (Adobe Graphics data)":5===t?"AppleSingle (Apple Computer Inc)":6===t?"NAA 89-3 (ANPA 1312)":7===t?"MacBinary II":8===t?"IPTC Unstructured Character Oriented File Format (UCOFF)":9===t?"United Press International ANPA 1312 variant":10===t?"United Press International Down-Load Message":11===t?"JPEG File Interchange (JFIF)":12===t?"Photo-CD Image-Pac (Eastman Kodak)":13===t?"Microsoft Bit Mapped Graphics File [*.BMP]":14===t?"Digital Audio File [*.WAV] (Microsoft & Creative Labs)":15===t?"Audio plus Moving Video [*.AVI] (Microsoft)":16===t?"PC DOS/Windows Executable Files [*.COM][*.EXE]":17===t?"Compressed Binary File [*.ZIP] (PKWare Inc)":18===t?"Audio Interchange File Format AIFF (Apple Computer Inc)":19===t?"RIFF Wave (Microsoft Corporation)":20===t?"Freehand (Macromedia/Aldus)":21===t?'Hypertext Markup Language "HTML" (The Internet Society)':22===t?"MPEG 2 Audio Layer 2 (Musicom), ISO/IEC":23===t?"MPEG 2 Audio Layer 3, ISO/IEC":24===t?"Portable Document File (*.PDF) Adobe":25===t?"News Industry Text Format (NITF)":26===t?"Tape Archive (*.TAR)":27===t?"Tidningarnas Telegrambyrå NITF version (TTNITF DTD)":28===t?"Ritzaus Bureau NITF version (RBNITF DTD)":29===t?"Corel Draw [*.CDR]":"Unknown format ".concat(t)}return l(e)}},713:{name:"ObjectData Preview File Format Version",description:function(e,t){var n={"00":{"00":"1"},"01":{"01":"1","02":"2","03":"3","04":"4"},"02":{"04":"4"},"03":{"01":"5.0","02":"6.0"},"04":{"01":"1.40"},"05":{"01":"2"},"06":{"01":"1"},11:{"01":"1.02"},20:{"01":"3.1","02":"4.0","03":"5.0","04":"5.5"},21:{"02":"2.0"}},r=l(e);if(t["ObjectData Preview File Format"]){var i=l(t["ObjectData Preview File Format"].value);if(n[i]&&n[i][r])return n[i][r]}return r}},714:"ObjectData Preview Data",1802:{name:"Size Mode",description:function(e){return e[0].toString()}},1812:{name:"Max Subfile Size",description:function(e){for(var t=0,n=0;n=8?t.substr(0,4)+"-"+t.substr(4,2)+"-"+t.substr(6,2):t}function Oe(e){var t=l(e),n=t;return t.length>=6&&(n=t.substr(0,2)+":"+t.substr(2,2)+":"+t.substr(4,2),11===t.length&&(n+=t.substr(6,1)+t.substr(7,2)+":"+t.substr(9,2))),n}function Fe(e){var t=l(e);return"%G"===t?"UTF-8":"%5"===t?"Windows-1252":"%/G"===t?"UTF-8 Level 1":"%/H"===t?"UTF-8 Level 2":"%/I"===t?"UTF-8 Level 3":"/A"===t?"ISO-8859-1":"/B"===t?"ISO-8859-2":"/C"===t?"ISO-8859-3":"/D"===t?"ISO-8859-4":"/@"===t?"ISO-8859-5":"/G"===t?"ISO-8859-6":"/F"===t?"ISO-8859-7":"/H"===t?"ISO-8859-8":"Unknown"}var Re=function(e,t){var n=function(){if("undefined"!=typeof TextDecoder)return TextDecoder}();if("undefined"!=typeof n&&void 0!==e)try{return new n(e).decode(Uint8Array.from(t))}catch(e){}return function(e){try{return decodeURIComponent(escape(e))}catch(t){return e}}(t.map((function(e){return String.fromCharCode(e)})).join(""))},Le=function(e,t,n){try{if(Array.isArray(e))return Ge(new DataView(Uint8Array.from(e).buffer),{size:e.length},0,n);var r=function(e,t){for(;t+12<=e.byteLength;){var n=ke(e,t);if(Ee(n))return{naaBlock:n,dataOffset:t+12};t+=12+n.size+Ne(n)}throw Error("No IPTC NAA resource block.")}(e,t);return Ge(e,r.naaBlock,r.dataOffset,n)}catch(e){return{}}};function ke(e,t){if(943868237!==e.getUint32(t,0))throw Error("Not an IPTC resource block.");return{type:e.getUint16(t+4),size:e.getUint16(t+10)}}function Ee(e){return 1028===e.type}function Ne(e){return e.size%2!=0?1:0}function Ge(e,t,n,r){for(var i={},o=void 0,a=n+t.size;ne.length)&&(t=e.length);for(var n=0,r=Array(t);ne.length)&&(t=e.length);for(var n=0,r=Array(t);n1&&n.push(Je(e,t.slice(1))),n}(e,t),2,function(e){if(Array.isArray(e))return e}(r)||function(e,t){var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,i,o=[],a=1,u=0;try{for(n=n.call(e);!(a=(r=n.next()).done)&&(o.push(r.value),2!==o.length);a=1);}catch(e){u=1,i=e}finally{try{a||null==n.return||n.return()}finally{if(u)throw i}}return o}}(r)||function(e,t){if(e){if("string"==typeof e)return qe(e,2);var n=Object.prototype.toString.call(e).slice(8,-1);return"Object"===n&&e.constructor&&(n=e.constructor.name),"Map"===n||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?qe(e,2):void 0}}(r)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()),o=i[0],a=i[1],u=$e(n,o);if(a){var c=$e(n,a);u||c||(delete n._raw,$e(n,Je(e,t)))}return n};function Je(e,t){for(var n=t.reduce((function(e,t){return e+t.length}),0),r=new Uint8Array(n),i=0,o=0;o).+$/,"$1"),"application/xml");if("parsererror"===i.documentElement.nodeName)throw Error(i.documentElement.textContent);return{doc:i,raw:n}}(t),i=n.doc,o=n.raw;return e._raw=(e._raw||"")+o,a(e,ot(Ze(Qe(i),1))),1}catch(e){return 0}}function Qe(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:0,n=et(e);return tt(n)?t?{}:nt(n[0]):rt(n)}function et(e){for(var t=[],n=0;n1&&void 0!==arguments[1]?arguments[1]:void 0;if(Array.isArray(e)){var n=lt(e);return t&&"function"==typeof We[t]?We[t](e,n):n}if("object"===_e(e))return dt(e);try{return t&&"function"==typeof We[t]?We[t](e):decodeURIComponent(escape(e))}catch(t){return e}}function lt(e){return e.map((function(e){return void 0!==e.value?st(e.value):st(e)})).join(", ")}function dt(e){var t=[];for(var n in e)t.push("".concat(pt(n),": ").concat(st(e[n].value)));return t.join("; ")}function pt(e){return"CiAdrCity"===e?"CreatorCity":"CiAdrCtry"===e?"CreatorCountry":"CiAdrExtadr"===e?"CreatorAddress":"CiAdrPcode"===e?"CreatorPostalCode":"CiAdrRegion"===e?"CreatorRegion":"CiEmailWork"===e?"CreatorWorkEmail":"CiTelWork"===e?"CreatorWorkPhone":"CiUrlWork"===e?"CreatorWorkUrl":e}function mt(e){var t={};for(var n in e)ct(n)||(t[ft(n)]=gt(e[n],n));return t}function gt(e,t){return vt(e)?ht(e,t):St(e)?bt(e,t):Ct(e)?At(e,t):function(e){return void 0!==It(e.value)}(e)?function(e,t){var n=It(e.value).value["rdf:li"],r=yt(e),i=[];return void 0===n?n=[]:Array.isArray(n)||(n=[n]),n.forEach((function(e){i.push(function(e){return vt(e)?ht(e):St(e)?bt(e).value:Ct(e)?At(e).value:Pt(e)}(e))})),{value:i,attributes:r,description:st(i,t)}}(e,t):Pt(e,t)}function vt(e){return"Resource"===e.attributes["rdf:parseType"]&&void 0!==e.value["rdf:value"]||void 0!==e.value["rdf:Description"]&&void 0!==e.value["rdf:Description"].value["rdf:value"]}function ht(e,t){var n=yt(e);void 0!==e.value["rdf:Description"]&&(e=e.value["rdf:Description"]),a(n,yt(e),function(e){var t={};for(var n in e.value)"rdf:value"===n||ct(n)||(t[ft(n)]=e.value[n].value);return t}(e));var r=function(e){return Ut(e.value["rdf:value"])||e.value["rdf:value"].value}(e);return{value:r,attributes:n,description:st(r,t)}}function yt(e){var t={};for(var n in e.attributes)"rdf:parseType"===n||"rdf:resource"===n||ct(n)||(t[ft(n)]=e.attributes[n]);return t}function St(e){return"Resource"===e.attributes["rdf:parseType"]||void 0!==e.value["rdf:Description"]&&void 0===e.value["rdf:Description"].value["rdf:value"]}function bt(e,t){var n={value:{},attributes:{}};return void 0!==e.value["rdf:Description"]&&(a(n.value,at(e.value["rdf:Description"].attributes)),a(n.attributes,yt(e)),e=e.value["rdf:Description"]),a(n.value,mt(e.value)),n.description=st(n.value,t),n}function Ct(e){return 0===Object.keys(e.value).length&&void 0===e.attributes["xml:lang"]&&void 0===e.attributes["rdf:resource"]}function At(e,t){var n=at(e.attributes);return{value:n,attributes:{},description:st(n,t)}}function It(e){return e["rdf:Bag"]||e["rdf:Seq"]||e["rdf:Alt"]}function Pt(e,t){var n=Ut(e)||ot(e.value);return{value:n,attributes:yt(e),description:st(n,t)}}function Ut(e){return e.attributes&&e.attributes["rdf:resource"]}var wt={desc:{name:"ICC Description"},cprt:{name:"ICC Copyright"},dmdd:{name:"ICC Device Model Description"},vued:{name:"ICC Viewing Conditions Description"},dmnd:{name:"ICC Device Manufacturer for Display"},tech:{name:"Technology"}},Tt={4:{name:"Preferred CMM type",value:function(e,t){return r(e,t,4)},description:function(e){return null!==e?Dt(e):""}},8:{name:"Profile Version",value:function(e,t){return e.getUint8(t).toString(10)+"."+(e.getUint8(t+1)>>4).toString(10)+"."+(e.getUint8(t+1)%16).toString(10)}},12:{name:"Profile/Device class",value:function(e,t){return r(e,t,4)},description:function(e){switch(e.toLowerCase()){case"scnr":return"Input Device profile";case"mntr":return"Display Device profile";case"prtr":return"Output Device profile";case"link":return"DeviceLink profile";case"abst":return"Abstract profile";case"spac":return"ColorSpace profile";case"nmcl":return"NamedColor profile";case"cenc":return"ColorEncodingSpace profile";case"mid ":return"MultiplexIdentification profile";case"mlnk":return"MultiplexLink profile";case"mvis":return"MultiplexVisualization profile";default:return e}}},16:{name:"Color Space",value:function(e,t){return r(e,t,4)}},20:{name:"Connection Space",value:function(e,t){return r(e,t,4)}},24:{name:"ICC Profile Date",value:function(e,t){return function(e,t){var n=e.getUint16(t),r=e.getUint16(t+2)-1,i=e.getUint16(t+4),o=e.getUint16(t+6),a=e.getUint16(t+8),u=e.getUint16(t+10);return new Date(Date.UTC(n,r,i,o,a,u))}(e,t).toISOString()}},36:{name:"ICC Signature",value:function(e,t){return n=e.buffer.slice(t,t+4),String.fromCharCode.apply(null,new Uint8Array(n));var n}},40:{name:"Primary Platform",value:function(e,t){return r(e,t,4)},description:function(e){return Dt(e)}},48:{name:"Device Manufacturer",value:function(e,t){return r(e,t,4)},description:function(e){return Dt(e)}},52:{name:"Device Model Number",value:function(e,t){return r(e,t,4)}},64:{name:"Rendering Intent",value:function(e,t){return e.getUint32(t)},description:function(e){switch(e){case 0:return"Perceptual";case 1:return"Relative Colorimetric";case 2:return"Saturation";case 3:return"Absolute Colorimetric";default:return e}}},80:{name:"Profile Creator",value:function(e,t){return r(e,t,4)}}};function Dt(e){switch(e.toLowerCase()){case"appl":return"Apple";case"adbe":return"Adobe";case"msft":return"Microsoft";case"sunw":return"Sun Microsystems";case"sgi":return"Silicon Graphics";case"tgnt":return"Taligent";default:return e}}var xt=function(e,t){try{for(var n=t.reduce((function(e,t){return e+t.length}),0),o=new Uint8Array(n),a=0,u=function(e){return Array.isArray(e)?new DataView(Uint8Array.from(e).buffer).buffer:e.buffer}(e),c=function(e){var n=t.find((function(t){return t.chunkNumber===e}));if(!n)throw Error("ICC chunk ".concat(e," not found"));var r=u.slice(n.offset,n.offset+n.length),i=new Uint8Array(r);o.set(i,a),a+=i.length},f=1;f<=t.length;f++)c(f);return function(e){var t=e.buffer,n=e.getUint32();if(e.byteLength!==n)throw Error("ICC profile length not matching");if(e.length<84)throw Error("ICC profile too short");for(var o={},a=Object.keys(Tt),u=0;ut.length)return o;var y=r(e,v,4);if(y===Ot){var S=e.getUint32(v+8);if(S>h)return o;Nt(o,g,Et(t.slice(v+12,v+S+11)))}else if(y===Ft){for(var b=e.getUint32(v+8),C=e.getUint32(v+12),A=v+16,I=[],P=0;Pe.byteLength)){var n=X.getLongAt(e,t);return{value:n,description:"".concat(n,"px")}}}function jt(e,t){if(!(t+4+4>e.byteLength)){var n=X.getLongAt(e,t+4);return{value:n,description:"".concat(n,"px")}}}function zt(e,t){if(!(t+8+1>e.byteLength)){var n=X.getByteAt(e,t+8);return{value:n,description:"".concat(n)}}}function Vt(e,t){if(!(t+9+1>e.byteLength)){var n=X.getByteAt(e,t+9);return{value:n,description:{0:"Grayscale",2:"RGB",3:"Palette",4:"Grayscale with Alpha",6:"RGB with Alpha"}[n]||"Unknown"}}}function Wt(e,t){if(!(t+10+1>e.byteLength)){var n=X.getByteAt(e,t+10);return{value:n,description:0===n?"Deflate/Inflate":"Unknown"}}}function Ht(e,t){if(!(t+11+1>e.byteLength)){var n=X.getByteAt(e,t+11);return{value:n,description:0===n?"Adaptive":"Unknown"}}}function Xt(e,t){if(!(t+12+1>e.byteLength)){var n=X.getByteAt(e,t+12);return{value:n,description:{0:"Noninterlaced",1:"Adam7 Interlace"}[n]||"Unknown"}}}var Yt=function(e,t){return t.reduce((function(t,n){var r=n.offset,i=n.length,a=n.type,u=function(e,t,n,r){for(var i=[],a=[],u=[],c=[],f=_t,s=0;s1&&void 0!==arguments[1]?arguments[1]:{}).length,n={method:"GET"};return Number.isInteger(t)&&t>=0&&(n.headers={range:"bytes=0-".concat(t-1)}),fetch(e,n).then((function(e){return e.arrayBuffer()}))}(e,t):function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).length;return new Promise((function(n,r){var i={};Number.isInteger(t)&&t>=0&&(i.headers={range:"bytes=0-".concat(t-1)}),mn(e)(e,i,(function(e){if(e.statusCode>=200&&e.statusCode<=299){var t=[];e.on("data",(function(e){return t.push(Buffer.from(e))})),e.on("error",(function(e){return r(e)})),e.on("end",(function(){return n(Buffer.concat(t))}))}else r("Could not fetch file: ".concat(e.statusCode," ").concat(e.statusMessage)),e.resume()})).on("error",(function(e){return r(e)}))}))}(e,t):function(e){return/^data:[^;,]*(;base64)?,/.test(e)}(e)?Promise.resolve(function(e){var t=e.substring(e.indexOf(",")+1);if(-1!==e.indexOf(";base64")){if("undefined"!=typeof atob)return Uint8Array.from(atob(t),(function(e){return e.charCodeAt(0)})).buffer;if("undefined"==typeof Buffer)return;return void 0!==n(Buffer.from)?Buffer.from(t,"base64"):new Buffer(t,"base64")}var r=decodeURIComponent(t);return"undefined"!=typeof Buffer?void 0!==n(Buffer.from)?Buffer.from(r):new Buffer(r):Uint8Array.from(r,(function(e){return e.charCodeAt(0)})).buffer}(e)):function(e){var t=(arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}).length;return new Promise((function(n,r){var i=gn();i.open(e,(function(o,a){o?r(o):i.stat(e,(function(o,u){if(o)r(o);else{var c=Math.min(u.size,void 0!==t?t:u.size),f=Buffer.alloc(c),s={buffer:f,length:c};i.read(a,s,(function(t){t?r(t):i.close(a,(function(t){t&&console.warn("Could not close file ".concat(e,":"),t),n(f)}))}))}}))}))}))}(e,t)}(e,t).then((function(e){return vn(e,t)})):function(e){return"undefined"!=typeof window&&"undefined"!=typeof File&&e instanceof File}(e)?(r=e,new Promise((function(e,t){var n=new FileReader;n.onload=function(t){return e(t.target.result)},n.onerror=function(){return t(n.error)},n.readAsArrayBuffer(r)}))).then((function(e){return vn(e,t)})):vn(e,t);var r}function mn(e){return/^https:\/\//.test(e)?require("https").get:require("http").get}function gn(){try{return require("fs")}catch(e){return}}function vn(e,t){return function(e){try{return Buffer.isBuffer(e)}catch(e){return 0}}(e)&&(e=new Uint8Array(e).buffer),hn(function(e){try{return new DataView(e)}catch(t){return new s(e)}}(e),t)}function hn(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{expanded:0,includeUnknown:0},n=t.expanded,r=void 0===n?0:n,i=t.includeUnknown,u=void 0===i?0:i,c=0,f={},s=V(e),l=s.fileDataOffset,d=s.jfifDataOffset,p=s.tiffHeaderOffset,m=s.iptcDataOffset,g=s.xmpChunks,v=s.iccChunks,h=s.mpfDataOffset,y=s.pngHeaderOffset,S=s.pngTextChunks,b=s.pngChunkOffsets;if(yn(l)){c=1;var C=ve(e,l);r?f.file=C:f=a({},f,C)}if(Sn(d)){c=1;var A=Ie(e,d);r?f.jfif=A:f=a({},f,A)}if(bn(p)){c=1;var I=ie(e,p,u);if(I.Thumbnail&&(f.Thumbnail=I.Thumbnail,delete I.Thumbnail),r?(f.exif=I,Cn(f)):f=a({},f,I),I["IPTC-NAA"]&&!An(m)){var P=Le(I["IPTC-NAA"].value,0,u);r?f.iptc=P:f=a({},f,P)}if(I.ApplicationNotes&&!In(g)){var U=Ke(o(I.ApplicationNotes.value));r?f.xmp=U:(delete U._raw,f=a({},f,U))}if(I.ICC_Profile&&!Pn(v)){var w=xt(I.ICC_Profile.value,[{offset:0,length:I.ICC_Profile.value.length,chunkNumber:1,chunksTotal:1}]);r?f.icc=w:f=a({},f,w)}}if(An(m)){c=1;var T=Le(e,m,u);r?f.iptc=T:f=a({},f,T)}if(In(g)){c=1;var D=Ke(e,g);r?f.xmp=D:(delete D._raw,f=a({},f,D))}if(Pn(v)){c=1;var x=xt(e,v);r?f.icc=x:f=a({},f,x)}if(Un(h)){c=1;var M=oe(e,h,u);r?f.mpf=M:f=a({},f,M)}if(wn(y)){c=1;var O=Gt(e,y);r?(f.png=f.png?a({},f.png,O):O,f.pngFile=O):f=a({},f,O)}if(Tn(S)){c=1;var F=Yt(e,S);r?(f.png=f.png?a({},f.png,F):F,f.pngText=F):f=a({},f,F)}if(Dn(b)){c=1;var R=Zt(e,b);r?f.png=f.png?a({},f.png,R):R:f=a({},f,R)}var L=cn(e,f.Thumbnail,p);if(L?(c=1,f.Thumbnail=L):delete f.Thumbnail,!c)throw new sn.MetadataMissingError;return f}function yn(e){return void 0!==e}function Sn(e){return void 0!==e}function bn(e){return void 0!==e}function Cn(e){if(e.exif){if(e.exif.GPSLatitude&&e.exif.GPSLatitudeRef)try{e.gps=e.gps||{},e.gps.Latitude=p(e.exif.GPSLatitude.value),"S"===e.exif.GPSLatitudeRef.value.join("")&&(e.gps.Latitude=-e.gps.Latitude)}catch(e){}if(e.exif.GPSLongitude&&e.exif.GPSLongitudeRef)try{e.gps=e.gps||{},e.gps.Longitude=p(e.exif.GPSLongitude.value),"W"===e.exif.GPSLongitudeRef.value.join("")&&(e.gps.Longitude=-e.gps.Longitude)}catch(e){}if(e.exif.GPSAltitude&&e.exif.GPSAltitudeRef)try{e.gps=e.gps||{},e.gps.Altitude=e.exif.GPSAltitude.value[0]/e.exif.GPSAltitude.value[1],1===e.exif.GPSAltitudeRef.value&&(e.gps.Altitude=-e.gps.Altitude)}catch(e){}}}function An(e){return void 0!==e}function In(e){return Array.isArray(e)&&e.length>0}function Pn(e){return Array.isArray(e)&&e.length>0}function Un(e){return void 0!==e}function wn(e){return void 0!==e}function Tn(e){return void 0!==e}function Dn(e){return void 0!==e}return t}()})); +//# sourceMappingURL=exif-reader.js.map \ No newline at end of file diff --git a/ui/plugins/ui/image-editor-improvements.plugin.js b/ui/plugins/ui/image-editor-improvements.plugin.js index 672ed77b..fffa556b 100644 --- a/ui/plugins/ui/image-editor-improvements.plugin.js +++ b/ui/plugins/ui/image-editor-improvements.plugin.js @@ -14,32 +14,6 @@ (function() { "use strict" - var styleSheet = document.createElement("style"); - styleSheet.textContent = ` - .editor-canvas-overlay { - cursor: none; - } - - .image-brush-preview { - position: fixed; - background: black; - opacity: 0.3; - borderRadius: 50%; - cursor: none; - pointer-events: none; - transform: translate(-50%, -50%); - } - - .editor-options-container > * > *:not(.active):not(.button) { - border: 1px dotted slategray; - } - - .image_editor_opacity .editor-options-container > * > *:not(.active):not(.button) { - border: 1px dotted slategray; - } - `; - document.head.appendChild(styleSheet); - let imageBrushPreview let imageCanvas let canvasType @@ -169,25 +143,8 @@ /* ADD SUPPORT FOR DRAG-AND-DROPPING SOURCE IMAGE (from file or straight from UI) */ - document.querySelector('#editor-inputs-init-image label').insertAdjacentHTML('beforeEnd', ` Add img2img source image using the Browse button, via drag & drop from external file or browser image (incl. rendered image) or by pasting an image from the clipboard using Ctrl+V.

You may also reload the metadata embedded in a PNG or JPEG image (enable embedding from the Settings).
`) /* DROP AREAS */ - var styleSheet = document.createElement("style"); - styleSheet.textContent = ` - .drop-area { - width: 45%; - height: 50px; - border: 2px dashed #ccc; - text-align: center; - line-height: 50px; - font-size: small; - color: #ccc; - border-radius: 10px; - display: none; - margin: 12px 10px; - } - `; - document.head.appendChild(styleSheet); function createDropAreas(container) { // Create two drop areas @@ -304,18 +261,13 @@ } else if (event?.dataTransfer?.files) { // Use DataTransfer interface items = Array.from(event.dataTransfer.files) } - if (items[0].type === "image/png" || items[0].type === "image/jpeg") { - // check if image has embedded metadata, load task if it does - if (items[0].type === "image/png") { - readPNGMetadata(items[0]) - } else if (items[0].type === "image/jpeg") { - readJPEGMetadata(items[0]); - } else { - console.log("File must be a PNG or JPEG image."); - } - //dropAreaMD.innerHTML = "Image added to drop area MD" + // check if image has embedded metadata, load task if it does + if (items[0].type === "image/png") { + readPNGMetadata(items[0]) + } else if (items[0].type === "image/jpeg" || items[0].type === "image/webp") { + readJPEGMetadata(items[0]); } else { - //dropAreaMD.innerHTML = "Wrong file type. Please select a PNG or JPEG image." + console.log("File must be a PNG, WEBP or JPEG image."); } event.preventDefault() }) @@ -400,7 +352,6 @@ } /* PNG METADATA EXTRACTION */ - document.querySelector('#editor-inputs-prompt b').insertAdjacentHTML('afterEnd', ` You can type your prompts in the below textbox or load them from a file. You can also reload tasks from metadata embedded in PNG and JPEG images (enable embedding from the Settings).`) function readPNGMetadata(image) { const fileReader = new FileReader() @@ -469,29 +420,30 @@ }); }; - /* JPEG METADATA EXTRACTION */ + /* JPEG or WEBP METADATA EXTRACTION */ function readJPEGMetadata(image) { const fileReader = new FileReader() fileReader.onload = function (e) { - const exifObj = piexif.load(e.target.result) - - const exifData = exifObj['Exif'][piexif.ExifIFD.UserComment] - if (exifData !== undefined) { - try { - const isUnicode = (exifData.toLowerCase().startsWith('unicode')) - let keys = JSON.parse(isUnicode ? decodeUnicode(exifData.slice(8)) : exifData.slice(8)) - let reqBody = {} - for (let key in keys) { - reqBody[key] = keys[key] + ExifReader.load(e.target.result).then(tags => { + const exifData = String.fromCharCode(...tags['UserComment'].value) + if (exifData !== undefined) { + try { + const isUnicode = (exifData.toLowerCase().startsWith('unicode')) + let keys = JSON.parse(isUnicode ? decodeUnicode(exifData.slice(8)) : exifData.slice(8)) + let reqBody = {} + for (let key in keys) { + reqBody[key] = keys[key] + } + let task = { numOutputsTotal: reqBody["num_outputs"], seed: reqBody["seed"] } + task['reqBody'] = reqBody + clearAllImageTagCards() + restoreTaskToUI(task, TASK_REQ_NO_EXPORT) + } catch (e) { + console.error('No valid JSON in EXIF data') } - let task = { numOutputsTotal: reqBody["num_outputs"], seed: reqBody["seed"] } - task['reqBody'] = reqBody - clearAllImageTagCards() - restoreTaskToUI(task, TASK_REQ_NO_EXPORT) - } catch (e) { - console.error('No valid JSON in EXIF data') } - } + }) + } fileReader.readAsDataURL(image); } @@ -507,2483 +459,4 @@ return decodedString } - - /* piexifjs - - The MIT License (MIT) - - Copyright (c) 2014, 2015 hMatoba(https://github.com/hMatoba) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ - - let that = {}; - that.version = "1.0.4"; - - that.remove = function (jpeg) { - var b64 = false; - if (jpeg.slice(0, 2) == "\xff\xd8") { - } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg.slice(0, 22) == "data:image/jpg;base64,") { - jpeg = atob(jpeg.split(",")[1]); - b64 = true; - } else { - throw new Error("Given data is not jpeg."); - } - - var segments = splitIntoSegments(jpeg); - var newSegments = segments.filter(function(seg){ - return !(seg.slice(0, 2) == "\xff\xe1" && - seg.slice(4, 10) == "Exif\x00\x00"); - }); - - var new_data = newSegments.join(""); - if (b64) { - new_data = "data:image/jpeg;base64," + btoa(new_data); - } - - return new_data; - }; - - - that.insert = function (exif, jpeg) { - var b64 = false; - if (exif.slice(0, 6) != "\x45\x78\x69\x66\x00\x00") { - throw new Error("Given data is not exif."); - } - if (jpeg.slice(0, 2) == "\xff\xd8") { - } else if (jpeg.slice(0, 23) == "data:image/jpeg;base64," || jpeg.slice(0, 22) == "data:image/jpg;base64,") { - jpeg = atob(jpeg.split(",")[1]); - b64 = true; - } else { - throw new Error("Given data is not jpeg."); - } - - var exifStr = "\xff\xe1" + pack(">H", [exif.length + 2]) + exif; - var segments = splitIntoSegments(jpeg); - var new_data = mergeSegments(segments, exifStr); - if (b64) { - new_data = "data:image/jpeg;base64," + btoa(new_data); - } - - return new_data; - }; - - - that.load = function (data) { - var input_data; - if (typeof (data) == "string") { - if (data.slice(0, 2) == "\xff\xd8") { - input_data = data; - } else if (data.slice(0, 23) == "data:image/jpeg;base64," || data.slice(0, 22) == "data:image/jpg;base64,") { - input_data = atob(data.split(",")[1]); - } else if (data.slice(0, 4) == "Exif") { - input_data = data.slice(6); - } else { - throw new Error("'load' gots invalid file data."); - } - } else { - throw new Error("'load' gots invalid type argument."); - } - - var exifDict = {}; - var exif_dict = { - "0th": {}, - "Exif": {}, - "GPS": {}, - "Interop": {}, - "1st": {}, - "thumbnail": null - }; - var exifReader = new ExifReader(input_data); - if (exifReader.tiftag === null) { - return exif_dict; - } - - if (exifReader.tiftag.slice(0, 2) == "\x49\x49") { - exifReader.endian_mark = "<"; - } else { - exifReader.endian_mark = ">"; - } - - var pointer = unpack(exifReader.endian_mark + "L", - exifReader.tiftag.slice(4, 8))[0]; - exif_dict["0th"] = exifReader.get_ifd(pointer, "0th"); - - var first_ifd_pointer = exif_dict["0th"]["first_ifd_pointer"]; - delete exif_dict["0th"]["first_ifd_pointer"]; - - if (34665 in exif_dict["0th"]) { - pointer = exif_dict["0th"][34665]; - exif_dict["Exif"] = exifReader.get_ifd(pointer, "Exif"); - } - if (34853 in exif_dict["0th"]) { - pointer = exif_dict["0th"][34853]; - exif_dict["GPS"] = exifReader.get_ifd(pointer, "GPS"); - } - if (40965 in exif_dict["Exif"]) { - pointer = exif_dict["Exif"][40965]; - exif_dict["Interop"] = exifReader.get_ifd(pointer, "Interop"); - } - if (first_ifd_pointer != "\x00\x00\x00\x00") { - pointer = unpack(exifReader.endian_mark + "L", - first_ifd_pointer)[0]; - exif_dict["1st"] = exifReader.get_ifd(pointer, "1st"); - if ((513 in exif_dict["1st"]) && (514 in exif_dict["1st"])) { - var end = exif_dict["1st"][513] + exif_dict["1st"][514]; - var thumb = exifReader.tiftag.slice(exif_dict["1st"][513], end); - exif_dict["thumbnail"] = thumb; - } - } - - return exif_dict; - }; - - - that.dump = function (exif_dict_original) { - var TIFF_HEADER_LENGTH = 8; - - var exif_dict = copy(exif_dict_original); - var header = "Exif\x00\x00\x4d\x4d\x00\x2a\x00\x00\x00\x08"; - var exif_is = false; - var gps_is = false; - var interop_is = false; - var first_is = false; - - var zeroth_ifd, - exif_ifd, - interop_ifd, - gps_ifd, - first_ifd; - - if ("0th" in exif_dict) { - zeroth_ifd = exif_dict["0th"]; - } else { - zeroth_ifd = {}; - } - - if ((("Exif" in exif_dict) && (Object.keys(exif_dict["Exif"]).length)) || - (("Interop" in exif_dict) && (Object.keys(exif_dict["Interop"]).length))) { - zeroth_ifd[34665] = 1; - exif_is = true; - exif_ifd = exif_dict["Exif"]; - if (("Interop" in exif_dict) && Object.keys(exif_dict["Interop"]).length) { - exif_ifd[40965] = 1; - interop_is = true; - interop_ifd = exif_dict["Interop"]; - } else if (Object.keys(exif_ifd).indexOf(that.ExifIFD.InteroperabilityTag.toString()) > -1) { - delete exif_ifd[40965]; - } - } else if (Object.keys(zeroth_ifd).indexOf(that.ImageIFD.ExifTag.toString()) > -1) { - delete zeroth_ifd[34665]; - } - - if (("GPS" in exif_dict) && (Object.keys(exif_dict["GPS"]).length)) { - zeroth_ifd[that.ImageIFD.GPSTag] = 1; - gps_is = true; - gps_ifd = exif_dict["GPS"]; - } else if (Object.keys(zeroth_ifd).indexOf(that.ImageIFD.GPSTag.toString()) > -1) { - delete zeroth_ifd[that.ImageIFD.GPSTag]; - } - - if (("1st" in exif_dict) && - ("thumbnail" in exif_dict) && - (exif_dict["thumbnail"] != null)) { - first_is = true; - exif_dict["1st"][513] = 1; - exif_dict["1st"][514] = 1; - first_ifd = exif_dict["1st"]; - } - - var zeroth_set = _dict_to_bytes(zeroth_ifd, "0th", 0); - var zeroth_length = (zeroth_set[0].length + exif_is * 12 + gps_is * 12 + 4 + - zeroth_set[1].length); - - var exif_set, - exif_bytes = "", - exif_length = 0, - gps_set, - gps_bytes = "", - gps_length = 0, - interop_set, - interop_bytes = "", - interop_length = 0, - first_set, - first_bytes = "", - thumbnail; - if (exif_is) { - exif_set = _dict_to_bytes(exif_ifd, "Exif", zeroth_length); - exif_length = exif_set[0].length + interop_is * 12 + exif_set[1].length; - } - if (gps_is) { - gps_set = _dict_to_bytes(gps_ifd, "GPS", zeroth_length + exif_length); - gps_bytes = gps_set.join(""); - gps_length = gps_bytes.length; - } - if (interop_is) { - var offset = zeroth_length + exif_length + gps_length; - interop_set = _dict_to_bytes(interop_ifd, "Interop", offset); - interop_bytes = interop_set.join(""); - interop_length = interop_bytes.length; - } - if (first_is) { - var offset = zeroth_length + exif_length + gps_length + interop_length; - first_set = _dict_to_bytes(first_ifd, "1st", offset); - thumbnail = _get_thumbnail(exif_dict["thumbnail"]); - if (thumbnail.length > 64000) { - throw new Error("Given thumbnail is too large. max 64kB"); - } - } - - var exif_pointer = "", - gps_pointer = "", - interop_pointer = "", - first_ifd_pointer = "\x00\x00\x00\x00"; - if (exif_is) { - var pointer_value = TIFF_HEADER_LENGTH + zeroth_length; - var pointer_str = pack(">L", [pointer_value]); - var key = 34665; - var key_str = pack(">H", [key]); - var type_str = pack(">H", [TYPES["Long"]]); - var length_str = pack(">L", [1]); - exif_pointer = key_str + type_str + length_str + pointer_str; - } - if (gps_is) { - var pointer_value = TIFF_HEADER_LENGTH + zeroth_length + exif_length; - var pointer_str = pack(">L", [pointer_value]); - var key = 34853; - var key_str = pack(">H", [key]); - var type_str = pack(">H", [TYPES["Long"]]); - var length_str = pack(">L", [1]); - gps_pointer = key_str + type_str + length_str + pointer_str; - } - if (interop_is) { - var pointer_value = (TIFF_HEADER_LENGTH + - zeroth_length + exif_length + gps_length); - var pointer_str = pack(">L", [pointer_value]); - var key = 40965; - var key_str = pack(">H", [key]); - var type_str = pack(">H", [TYPES["Long"]]); - var length_str = pack(">L", [1]); - interop_pointer = key_str + type_str + length_str + pointer_str; - } - if (first_is) { - var pointer_value = (TIFF_HEADER_LENGTH + zeroth_length + - exif_length + gps_length + interop_length); - first_ifd_pointer = pack(">L", [pointer_value]); - var thumbnail_pointer = (pointer_value + first_set[0].length + 24 + - 4 + first_set[1].length); - var thumbnail_p_bytes = ("\x02\x01\x00\x04\x00\x00\x00\x01" + - pack(">L", [thumbnail_pointer])); - var thumbnail_length_bytes = ("\x02\x02\x00\x04\x00\x00\x00\x01" + - pack(">L", [thumbnail.length])); - first_bytes = (first_set[0] + thumbnail_p_bytes + - thumbnail_length_bytes + "\x00\x00\x00\x00" + - first_set[1] + thumbnail); - } - - var zeroth_bytes = (zeroth_set[0] + exif_pointer + gps_pointer + - first_ifd_pointer + zeroth_set[1]); - if (exif_is) { - exif_bytes = exif_set[0] + interop_pointer + exif_set[1]; - } - - return (header + zeroth_bytes + exif_bytes + gps_bytes + - interop_bytes + first_bytes); - }; - - - function copy(obj) { - return JSON.parse(JSON.stringify(obj)); - } - - - function _get_thumbnail(jpeg) { - var segments = splitIntoSegments(jpeg); - while (("\xff\xe0" <= segments[1].slice(0, 2)) && (segments[1].slice(0, 2) <= "\xff\xef")) { - segments = [segments[0]].concat(segments.slice(2)); - } - return segments.join(""); - } - - - function _pack_byte(array) { - return pack(">" + nStr("B", array.length), array); - } - - - function _pack_short(array) { - return pack(">" + nStr("H", array.length), array); - } - - - function _pack_long(array) { - return pack(">" + nStr("L", array.length), array); - } - - - function _value_to_bytes(raw_value, value_type, offset) { - var four_bytes_over = ""; - var value_str = ""; - var length, - new_value, - num, - den; - - if (value_type == "Byte") { - length = raw_value.length; - if (length <= 4) { - value_str = (_pack_byte(raw_value) + - nStr("\x00", 4 - length)); - } else { - value_str = pack(">L", [offset]); - four_bytes_over = _pack_byte(raw_value); - } - } else if (value_type == "Short") { - length = raw_value.length; - if (length <= 2) { - value_str = (_pack_short(raw_value) + - nStr("\x00\x00", 2 - length)); - } else { - value_str = pack(">L", [offset]); - four_bytes_over = _pack_short(raw_value); - } - } else if (value_type == "Long") { - length = raw_value.length; - if (length <= 1) { - value_str = _pack_long(raw_value); - } else { - value_str = pack(">L", [offset]); - four_bytes_over = _pack_long(raw_value); - } - } else if (value_type == "Ascii") { - new_value = raw_value + "\x00"; - length = new_value.length; - if (length > 4) { - value_str = pack(">L", [offset]); - four_bytes_over = new_value; - } else { - value_str = new_value + nStr("\x00", 4 - length); - } - } else if (value_type == "Rational") { - if (typeof (raw_value[0]) == "number") { - length = 1; - num = raw_value[0]; - den = raw_value[1]; - new_value = pack(">L", [num]) + pack(">L", [den]); - } else { - length = raw_value.length; - new_value = ""; - for (var n = 0; n < length; n++) { - num = raw_value[n][0]; - den = raw_value[n][1]; - new_value += (pack(">L", [num]) + - pack(">L", [den])); - } - } - value_str = pack(">L", [offset]); - four_bytes_over = new_value; - } else if (value_type == "SRational") { - if (typeof (raw_value[0]) == "number") { - length = 1; - num = raw_value[0]; - den = raw_value[1]; - new_value = pack(">l", [num]) + pack(">l", [den]); - } else { - length = raw_value.length; - new_value = ""; - for (var n = 0; n < length; n++) { - num = raw_value[n][0]; - den = raw_value[n][1]; - new_value += (pack(">l", [num]) + - pack(">l", [den])); - } - } - value_str = pack(">L", [offset]); - four_bytes_over = new_value; - } else if (value_type == "Undefined") { - length = raw_value.length; - if (length > 4) { - value_str = pack(">L", [offset]); - four_bytes_over = raw_value; - } else { - value_str = raw_value + nStr("\x00", 4 - length); - } - } - - var length_str = pack(">L", [length]); - - return [length_str, value_str, four_bytes_over]; - } - - function _dict_to_bytes(ifd_dict, ifd, ifd_offset) { - var TIFF_HEADER_LENGTH = 8; - var tag_count = Object.keys(ifd_dict).length; - var entry_header = pack(">H", [tag_count]); - var entries_length; - if (["0th", "1st"].indexOf(ifd) > -1) { - entries_length = 2 + tag_count * 12 + 4; - } else { - entries_length = 2 + tag_count * 12; - } - var entries = ""; - var values = ""; - var key; - - for (var key in ifd_dict) { - if (typeof (key) == "string") { - key = parseInt(key); - } - if ((ifd == "0th") && ([34665, 34853].indexOf(key) > -1)) { - continue; - } else if ((ifd == "Exif") && (key == 40965)) { - continue; - } else if ((ifd == "1st") && ([513, 514].indexOf(key) > -1)) { - continue; - } - - var raw_value = ifd_dict[key]; - var key_str = pack(">H", [key]); - var value_type = TAGS[ifd][key]["type"]; - var type_str = pack(">H", [TYPES[value_type]]); - - if (typeof (raw_value) == "number") { - raw_value = [raw_value]; - } - var offset = TIFF_HEADER_LENGTH + entries_length + ifd_offset + values.length; - var b = _value_to_bytes(raw_value, value_type, offset); - var length_str = b[0]; - var value_str = b[1]; - var four_bytes_over = b[2]; - - entries += key_str + type_str + length_str + value_str; - values += four_bytes_over; - } - - return [entry_header + entries, values]; - } - - - - function ExifReader(data) { - var segments, - app1; - if (data.slice(0, 2) == "\xff\xd8") { // JPEG - segments = splitIntoSegments(data); - app1 = getExifSeg(segments); - if (app1) { - this.tiftag = app1.slice(10); - } else { - this.tiftag = null; - } - } else if (["\x49\x49", "\x4d\x4d"].indexOf(data.slice(0, 2)) > -1) { // TIFF - this.tiftag = data; - } else if (data.slice(0, 4) == "Exif") { // Exif - this.tiftag = data.slice(6); - } else { - throw new Error("Given file is neither JPEG nor TIFF."); - } - } - - ExifReader.prototype = { - get_ifd: function (pointer, ifd_name) { - var ifd_dict = {}; - var tag_count = unpack(this.endian_mark + "H", - this.tiftag.slice(pointer, pointer + 2))[0]; - var offset = pointer + 2; - var t; - if (["0th", "1st"].indexOf(ifd_name) > -1) { - t = "Image"; - } else { - t = ifd_name; - } - - for (var x = 0; x < tag_count; x++) { - pointer = offset + 12 * x; - var tag = unpack(this.endian_mark + "H", - this.tiftag.slice(pointer, pointer + 2))[0]; - var value_type = unpack(this.endian_mark + "H", - this.tiftag.slice(pointer + 2, pointer + 4))[0]; - var value_num = unpack(this.endian_mark + "L", - this.tiftag.slice(pointer + 4, pointer + 8))[0]; - var value = this.tiftag.slice(pointer + 8, pointer + 12); - - var v_set = [value_type, value_num, value]; - if (tag in TAGS[t]) { - ifd_dict[tag] = this.convert_value(v_set); - } - } - - if (ifd_name == "0th") { - pointer = offset + 12 * tag_count; - ifd_dict["first_ifd_pointer"] = this.tiftag.slice(pointer, pointer + 4); - } - - return ifd_dict; - }, - - convert_value: function (val) { - var data = null; - var t = val[0]; - var length = val[1]; - var value = val[2]; - var pointer; - - if (t == 1) { // BYTE - if (length > 4) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = unpack(this.endian_mark + nStr("B", length), - this.tiftag.slice(pointer, pointer + length)); - } else { - data = unpack(this.endian_mark + nStr("B", length), value.slice(0, length)); - } - } else if (t == 2) { // ASCII - if (length > 4) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = this.tiftag.slice(pointer, pointer + length - 1); - } else { - data = value.slice(0, length - 1); - } - } else if (t == 3) { // SHORT - if (length > 2) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = unpack(this.endian_mark + nStr("H", length), - this.tiftag.slice(pointer, pointer + length * 2)); - } else { - data = unpack(this.endian_mark + nStr("H", length), - value.slice(0, length * 2)); - } - } else if (t == 4) { // LONG - if (length > 1) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = unpack(this.endian_mark + nStr("L", length), - this.tiftag.slice(pointer, pointer + length * 4)); - } else { - data = unpack(this.endian_mark + nStr("L", length), - value); - } - } else if (t == 5) { // RATIONAL - pointer = unpack(this.endian_mark + "L", value)[0]; - if (length > 1) { - data = []; - for (var x = 0; x < length; x++) { - data.push([unpack(this.endian_mark + "L", - this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], - unpack(this.endian_mark + "L", - this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] - ]); - } - } else { - data = [unpack(this.endian_mark + "L", - this.tiftag.slice(pointer, pointer + 4))[0], - unpack(this.endian_mark + "L", - this.tiftag.slice(pointer + 4, pointer + 8))[0] - ]; - } - } else if (t == 7) { // UNDEFINED BYTES - if (length > 4) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = this.tiftag.slice(pointer, pointer + length); - } else { - data = value.slice(0, length); - } - } else if (t == 9) { // SLONG - if (length > 1) { - pointer = unpack(this.endian_mark + "L", value)[0]; - data = unpack(this.endian_mark + nStr("l", length), - this.tiftag.slice(pointer, pointer + length * 4)); - } else { - data = unpack(this.endian_mark + nStr("l", length), - value); - } - } else if (t == 10) { // SRATIONAL - pointer = unpack(this.endian_mark + "L", value)[0]; - if (length > 1) { - data = []; - for (var x = 0; x < length; x++) { - data.push([unpack(this.endian_mark + "l", - this.tiftag.slice(pointer + x * 8, pointer + 4 + x * 8))[0], - unpack(this.endian_mark + "l", - this.tiftag.slice(pointer + 4 + x * 8, pointer + 8 + x * 8))[0] - ]); - } - } else { - data = [unpack(this.endian_mark + "l", - this.tiftag.slice(pointer, pointer + 4))[0], - unpack(this.endian_mark + "l", - this.tiftag.slice(pointer + 4, pointer + 8))[0] - ]; - } - } else { - throw new Error("Exif might be wrong. Got incorrect value " + - "type to decode. type:" + t); - } - - if ((data instanceof Array) && (data.length == 1)) { - return data[0]; - } else { - return data; - } - }, - }; - - - if (typeof window !== "undefined" && typeof window.btoa === "function") { - var btoa = window.btoa; - } - if (typeof btoa === "undefined") { - var btoa = function (input) { var output = ""; - var chr1, chr2, chr3, enc1, enc2, enc3, enc4; - var i = 0; - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - while (i < input.length) { - - chr1 = input.charCodeAt(i++); - chr2 = input.charCodeAt(i++); - chr3 = input.charCodeAt(i++); - - enc1 = chr1 >> 2; - enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); - enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); - enc4 = chr3 & 63; - - if (isNaN(chr2)) { - enc3 = enc4 = 64; - } else if (isNaN(chr3)) { - enc4 = 64; - } - - output = output + - keyStr.charAt(enc1) + keyStr.charAt(enc2) + - keyStr.charAt(enc3) + keyStr.charAt(enc4); - - } - - return output; - }; - } - - - if (typeof window !== "undefined" && typeof window.atob === "function") { - var atob = window.atob; - } - if (typeof atob === "undefined") { - var atob = function (input) { - var output = ""; - var chr1, chr2, chr3; - var enc1, enc2, enc3, enc4; - var i = 0; - var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; - - input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); - - while (i < input.length) { - - enc1 = keyStr.indexOf(input.charAt(i++)); - enc2 = keyStr.indexOf(input.charAt(i++)); - enc3 = keyStr.indexOf(input.charAt(i++)); - enc4 = keyStr.indexOf(input.charAt(i++)); - - chr1 = (enc1 << 2) | (enc2 >> 4); - chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); - chr3 = ((enc3 & 3) << 6) | enc4; - - output = output + String.fromCharCode(chr1); - - if (enc3 != 64) { - output = output + String.fromCharCode(chr2); - } - if (enc4 != 64) { - output = output + String.fromCharCode(chr3); - } - - } - - return output; - }; - } - - - function getImageSize(imageArray) { - var segments = slice2Segments(imageArray); - var seg, - width, - height, - SOF = [192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207]; - - for (var x = 0; x < segments.length; x++) { - seg = segments[x]; - if (SOF.indexOf(seg[1]) >= 0) { - height = seg[5] * 256 + seg[6]; - width = seg[7] * 256 + seg[8]; - break; - } - } - return [width, height]; - } - - - function pack(mark, array) { - if (!(array instanceof Array)) { - throw new Error("'pack' error. Got invalid type argument."); - } - if ((mark.length - 1) != array.length) { - throw new Error("'pack' error. " + (mark.length - 1) + " marks, " + array.length + " elements."); - } - - var littleEndian; - if (mark[0] == "<") { - littleEndian = true; - } else if (mark[0] == ">") { - littleEndian = false; - } else { - throw new Error(""); - } - var packed = ""; - var p = 1; - var val = null; - var c = null; - var valStr = null; - - while (c = mark[p]) { - if (c.toLowerCase() == "b") { - val = array[p - 1]; - if ((c == "b") && (val < 0)) { - val += 0x100; - } - if ((val > 0xff) || (val < 0)) { - throw new Error("'pack' error."); - } else { - valStr = String.fromCharCode(val); - } - } else if (c == "H") { - val = array[p - 1]; - if ((val > 0xffff) || (val < 0)) { - throw new Error("'pack' error."); - } else { - valStr = String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + - String.fromCharCode(val % 0x100); - if (littleEndian) { - valStr = valStr.split("").reverse().join(""); - } - } - } else if (c.toLowerCase() == "l") { - val = array[p - 1]; - if ((c == "l") && (val < 0)) { - val += 0x100000000; - } - if ((val > 0xffffffff) || (val < 0)) { - throw new Error("'pack' error."); - } else { - valStr = String.fromCharCode(Math.floor(val / 0x1000000)) + - String.fromCharCode(Math.floor((val % 0x1000000) / 0x10000)) + - String.fromCharCode(Math.floor((val % 0x10000) / 0x100)) + - String.fromCharCode(val % 0x100); - if (littleEndian) { - valStr = valStr.split("").reverse().join(""); - } - } - } else { - throw new Error("'pack' error."); - } - - packed += valStr; - p += 1; - } - - return packed; - } - - function unpack(mark, str) { - if (typeof (str) != "string") { - throw new Error("'unpack' error. Got invalid type argument."); - } - var l = 0; - for (var markPointer = 1; markPointer < mark.length; markPointer++) { - if (mark[markPointer].toLowerCase() == "b") { - l += 1; - } else if (mark[markPointer].toLowerCase() == "h") { - l += 2; - } else if (mark[markPointer].toLowerCase() == "l") { - l += 4; - } else { - throw new Error("'unpack' error. Got invalid mark."); - } - } - - if (l != str.length) { - throw new Error("'unpack' error. Mismatch between symbol and string length. " + l + ":" + str.length); - } - - var littleEndian; - if (mark[0] == "<") { - littleEndian = true; - } else if (mark[0] == ">") { - littleEndian = false; - } else { - throw new Error("'unpack' error."); - } - var unpacked = []; - var strPointer = 0; - var p = 1; - var val = null; - var c = null; - var length = null; - var sliced = ""; - - while (c = mark[p]) { - if (c.toLowerCase() == "b") { - length = 1; - sliced = str.slice(strPointer, strPointer + length); - val = sliced.charCodeAt(0); - if ((c == "b") && (val >= 0x80)) { - val -= 0x100; - } - } else if (c == "H") { - length = 2; - sliced = str.slice(strPointer, strPointer + length); - if (littleEndian) { - sliced = sliced.split("").reverse().join(""); - } - val = sliced.charCodeAt(0) * 0x100 + - sliced.charCodeAt(1); - } else if (c.toLowerCase() == "l") { - length = 4; - sliced = str.slice(strPointer, strPointer + length); - if (littleEndian) { - sliced = sliced.split("").reverse().join(""); - } - val = sliced.charCodeAt(0) * 0x1000000 + - sliced.charCodeAt(1) * 0x10000 + - sliced.charCodeAt(2) * 0x100 + - sliced.charCodeAt(3); - if ((c == "l") && (val >= 0x80000000)) { - val -= 0x100000000; - } - } else { - throw new Error("'unpack' error. " + c); - } - - unpacked.push(val); - strPointer += length; - p += 1; - } - - return unpacked; - } - - function nStr(ch, num) { - var str = ""; - for (var i = 0; i < num; i++) { - str += ch; - } - return str; - } - - function splitIntoSegments(data) { - if (data.slice(0, 2) != "\xff\xd8") { - throw new Error("Given data isn't JPEG."); - } - - var head = 2; - var segments = ["\xff\xd8"]; - while (true) { - if (data.slice(head, head + 2) == "\xff\xda") { - segments.push(data.slice(head)); - break; - } else { - var length = unpack(">H", data.slice(head + 2, head + 4))[0]; - var endPoint = head + length + 2; - segments.push(data.slice(head, endPoint)); - head = endPoint; - } - - if (head >= data.length) { - throw new Error("Wrong JPEG data."); - } - } - return segments; - } - - - function getExifSeg(segments) { - var seg; - for (var i = 0; i < segments.length; i++) { - seg = segments[i]; - if (seg.slice(0, 2) == "\xff\xe1" && - seg.slice(4, 10) == "Exif\x00\x00") { - return seg; - } - } - return null; - } - - - function mergeSegments(segments, exif) { - var hasExifSegment = false; - var additionalAPP1ExifSegments = []; - - segments.forEach(function(segment, i) { - // Replace first occurence of APP1:Exif segment - if (segment.slice(0, 2) == "\xff\xe1" && - segment.slice(4, 10) == "Exif\x00\x00" - ) { - if (!hasExifSegment) { - segments[i] = exif; - hasExifSegment = true; - } else { - additionalAPP1ExifSegments.unshift(i); - } - } - }); - - // Remove additional occurences of APP1:Exif segment - additionalAPP1ExifSegments.forEach(function(segmentIndex) { - segments.splice(segmentIndex, 1); - }); - - if (!hasExifSegment && exif) { - segments = [segments[0], exif].concat(segments.slice(1)); - } - - return segments.join(""); - } - - - function toHex(str) { - var hexStr = ""; - for (var i = 0; i < str.length; i++) { - var h = str.charCodeAt(i); - var hex = ((h < 10) ? "0" : "") + h.toString(16); - hexStr += hex + " "; - } - return hexStr; - } - - - var TYPES = { - "Byte": 1, - "Ascii": 2, - "Short": 3, - "Long": 4, - "Rational": 5, - "Undefined": 7, - "SLong": 9, - "SRational": 10 - }; - - - var TAGS = { - 'Image': { - 11: { - 'name': 'ProcessingSoftware', - 'type': 'Ascii' - }, - 254: { - 'name': 'NewSubfileType', - 'type': 'Long' - }, - 255: { - 'name': 'SubfileType', - 'type': 'Short' - }, - 256: { - 'name': 'ImageWidth', - 'type': 'Long' - }, - 257: { - 'name': 'ImageLength', - 'type': 'Long' - }, - 258: { - 'name': 'BitsPerSample', - 'type': 'Short' - }, - 259: { - 'name': 'Compression', - 'type': 'Short' - }, - 262: { - 'name': 'PhotometricInterpretation', - 'type': 'Short' - }, - 263: { - 'name': 'Threshholding', - 'type': 'Short' - }, - 264: { - 'name': 'CellWidth', - 'type': 'Short' - }, - 265: { - 'name': 'CellLength', - 'type': 'Short' - }, - 266: { - 'name': 'FillOrder', - 'type': 'Short' - }, - 269: { - 'name': 'DocumentName', - 'type': 'Ascii' - }, - 270: { - 'name': 'ImageDescription', - 'type': 'Ascii' - }, - 271: { - 'name': 'Make', - 'type': 'Ascii' - }, - 272: { - 'name': 'Model', - 'type': 'Ascii' - }, - 273: { - 'name': 'StripOffsets', - 'type': 'Long' - }, - 274: { - 'name': 'Orientation', - 'type': 'Short' - }, - 277: { - 'name': 'SamplesPerPixel', - 'type': 'Short' - }, - 278: { - 'name': 'RowsPerStrip', - 'type': 'Long' - }, - 279: { - 'name': 'StripByteCounts', - 'type': 'Long' - }, - 282: { - 'name': 'XResolution', - 'type': 'Rational' - }, - 283: { - 'name': 'YResolution', - 'type': 'Rational' - }, - 284: { - 'name': 'PlanarConfiguration', - 'type': 'Short' - }, - 290: { - 'name': 'GrayResponseUnit', - 'type': 'Short' - }, - 291: { - 'name': 'GrayResponseCurve', - 'type': 'Short' - }, - 292: { - 'name': 'T4Options', - 'type': 'Long' - }, - 293: { - 'name': 'T6Options', - 'type': 'Long' - }, - 296: { - 'name': 'ResolutionUnit', - 'type': 'Short' - }, - 301: { - 'name': 'TransferFunction', - 'type': 'Short' - }, - 305: { - 'name': 'Software', - 'type': 'Ascii' - }, - 306: { - 'name': 'DateTime', - 'type': 'Ascii' - }, - 315: { - 'name': 'Artist', - 'type': 'Ascii' - }, - 316: { - 'name': 'HostComputer', - 'type': 'Ascii' - }, - 317: { - 'name': 'Predictor', - 'type': 'Short' - }, - 318: { - 'name': 'WhitePoint', - 'type': 'Rational' - }, - 319: { - 'name': 'PrimaryChromaticities', - 'type': 'Rational' - }, - 320: { - 'name': 'ColorMap', - 'type': 'Short' - }, - 321: { - 'name': 'HalftoneHints', - 'type': 'Short' - }, - 322: { - 'name': 'TileWidth', - 'type': 'Short' - }, - 323: { - 'name': 'TileLength', - 'type': 'Short' - }, - 324: { - 'name': 'TileOffsets', - 'type': 'Short' - }, - 325: { - 'name': 'TileByteCounts', - 'type': 'Short' - }, - 330: { - 'name': 'SubIFDs', - 'type': 'Long' - }, - 332: { - 'name': 'InkSet', - 'type': 'Short' - }, - 333: { - 'name': 'InkNames', - 'type': 'Ascii' - }, - 334: { - 'name': 'NumberOfInks', - 'type': 'Short' - }, - 336: { - 'name': 'DotRange', - 'type': 'Byte' - }, - 337: { - 'name': 'TargetPrinter', - 'type': 'Ascii' - }, - 338: { - 'name': 'ExtraSamples', - 'type': 'Short' - }, - 339: { - 'name': 'SampleFormat', - 'type': 'Short' - }, - 340: { - 'name': 'SMinSampleValue', - 'type': 'Short' - }, - 341: { - 'name': 'SMaxSampleValue', - 'type': 'Short' - }, - 342: { - 'name': 'TransferRange', - 'type': 'Short' - }, - 343: { - 'name': 'ClipPath', - 'type': 'Byte' - }, - 344: { - 'name': 'XClipPathUnits', - 'type': 'Long' - }, - 345: { - 'name': 'YClipPathUnits', - 'type': 'Long' - }, - 346: { - 'name': 'Indexed', - 'type': 'Short' - }, - 347: { - 'name': 'JPEGTables', - 'type': 'Undefined' - }, - 351: { - 'name': 'OPIProxy', - 'type': 'Short' - }, - 512: { - 'name': 'JPEGProc', - 'type': 'Long' - }, - 513: { - 'name': 'JPEGInterchangeFormat', - 'type': 'Long' - }, - 514: { - 'name': 'JPEGInterchangeFormatLength', - 'type': 'Long' - }, - 515: { - 'name': 'JPEGRestartInterval', - 'type': 'Short' - }, - 517: { - 'name': 'JPEGLosslessPredictors', - 'type': 'Short' - }, - 518: { - 'name': 'JPEGPointTransforms', - 'type': 'Short' - }, - 519: { - 'name': 'JPEGQTables', - 'type': 'Long' - }, - 520: { - 'name': 'JPEGDCTables', - 'type': 'Long' - }, - 521: { - 'name': 'JPEGACTables', - 'type': 'Long' - }, - 529: { - 'name': 'YCbCrCoefficients', - 'type': 'Rational' - }, - 530: { - 'name': 'YCbCrSubSampling', - 'type': 'Short' - }, - 531: { - 'name': 'YCbCrPositioning', - 'type': 'Short' - }, - 532: { - 'name': 'ReferenceBlackWhite', - 'type': 'Rational' - }, - 700: { - 'name': 'XMLPacket', - 'type': 'Byte' - }, - 18246: { - 'name': 'Rating', - 'type': 'Short' - }, - 18249: { - 'name': 'RatingPercent', - 'type': 'Short' - }, - 32781: { - 'name': 'ImageID', - 'type': 'Ascii' - }, - 33421: { - 'name': 'CFARepeatPatternDim', - 'type': 'Short' - }, - 33422: { - 'name': 'CFAPattern', - 'type': 'Byte' - }, - 33423: { - 'name': 'BatteryLevel', - 'type': 'Rational' - }, - 33432: { - 'name': 'Copyright', - 'type': 'Ascii' - }, - 33434: { - 'name': 'ExposureTime', - 'type': 'Rational' - }, - 34377: { - 'name': 'ImageResources', - 'type': 'Byte' - }, - 34665: { - 'name': 'ExifTag', - 'type': 'Long' - }, - 34675: { - 'name': 'InterColorProfile', - 'type': 'Undefined' - }, - 34853: { - 'name': 'GPSTag', - 'type': 'Long' - }, - 34857: { - 'name': 'Interlace', - 'type': 'Short' - }, - 34858: { - 'name': 'TimeZoneOffset', - 'type': 'Long' - }, - 34859: { - 'name': 'SelfTimerMode', - 'type': 'Short' - }, - 37387: { - 'name': 'FlashEnergy', - 'type': 'Rational' - }, - 37388: { - 'name': 'SpatialFrequencyResponse', - 'type': 'Undefined' - }, - 37389: { - 'name': 'Noise', - 'type': 'Undefined' - }, - 37390: { - 'name': 'FocalPlaneXResolution', - 'type': 'Rational' - }, - 37391: { - 'name': 'FocalPlaneYResolution', - 'type': 'Rational' - }, - 37392: { - 'name': 'FocalPlaneResolutionUnit', - 'type': 'Short' - }, - 37393: { - 'name': 'ImageNumber', - 'type': 'Long' - }, - 37394: { - 'name': 'SecurityClassification', - 'type': 'Ascii' - }, - 37395: { - 'name': 'ImageHistory', - 'type': 'Ascii' - }, - 37397: { - 'name': 'ExposureIndex', - 'type': 'Rational' - }, - 37398: { - 'name': 'TIFFEPStandardID', - 'type': 'Byte' - }, - 37399: { - 'name': 'SensingMethod', - 'type': 'Short' - }, - 40091: { - 'name': 'XPTitle', - 'type': 'Byte' - }, - 40092: { - 'name': 'XPComment', - 'type': 'Byte' - }, - 40093: { - 'name': 'XPAuthor', - 'type': 'Byte' - }, - 40094: { - 'name': 'XPKeywords', - 'type': 'Byte' - }, - 40095: { - 'name': 'XPSubject', - 'type': 'Byte' - }, - 50341: { - 'name': 'PrintImageMatching', - 'type': 'Undefined' - }, - 50706: { - 'name': 'DNGVersion', - 'type': 'Byte' - }, - 50707: { - 'name': 'DNGBackwardVersion', - 'type': 'Byte' - }, - 50708: { - 'name': 'UniqueCameraModel', - 'type': 'Ascii' - }, - 50709: { - 'name': 'LocalizedCameraModel', - 'type': 'Byte' - }, - 50710: { - 'name': 'CFAPlaneColor', - 'type': 'Byte' - }, - 50711: { - 'name': 'CFALayout', - 'type': 'Short' - }, - 50712: { - 'name': 'LinearizationTable', - 'type': 'Short' - }, - 50713: { - 'name': 'BlackLevelRepeatDim', - 'type': 'Short' - }, - 50714: { - 'name': 'BlackLevel', - 'type': 'Rational' - }, - 50715: { - 'name': 'BlackLevelDeltaH', - 'type': 'SRational' - }, - 50716: { - 'name': 'BlackLevelDeltaV', - 'type': 'SRational' - }, - 50717: { - 'name': 'WhiteLevel', - 'type': 'Short' - }, - 50718: { - 'name': 'DefaultScale', - 'type': 'Rational' - }, - 50719: { - 'name': 'DefaultCropOrigin', - 'type': 'Short' - }, - 50720: { - 'name': 'DefaultCropSize', - 'type': 'Short' - }, - 50721: { - 'name': 'ColorMatrix1', - 'type': 'SRational' - }, - 50722: { - 'name': 'ColorMatrix2', - 'type': 'SRational' - }, - 50723: { - 'name': 'CameraCalibration1', - 'type': 'SRational' - }, - 50724: { - 'name': 'CameraCalibration2', - 'type': 'SRational' - }, - 50725: { - 'name': 'ReductionMatrix1', - 'type': 'SRational' - }, - 50726: { - 'name': 'ReductionMatrix2', - 'type': 'SRational' - }, - 50727: { - 'name': 'AnalogBalance', - 'type': 'Rational' - }, - 50728: { - 'name': 'AsShotNeutral', - 'type': 'Short' - }, - 50729: { - 'name': 'AsShotWhiteXY', - 'type': 'Rational' - }, - 50730: { - 'name': 'BaselineExposure', - 'type': 'SRational' - }, - 50731: { - 'name': 'BaselineNoise', - 'type': 'Rational' - }, - 50732: { - 'name': 'BaselineSharpness', - 'type': 'Rational' - }, - 50733: { - 'name': 'BayerGreenSplit', - 'type': 'Long' - }, - 50734: { - 'name': 'LinearResponseLimit', - 'type': 'Rational' - }, - 50735: { - 'name': 'CameraSerialNumber', - 'type': 'Ascii' - }, - 50736: { - 'name': 'LensInfo', - 'type': 'Rational' - }, - 50737: { - 'name': 'ChromaBlurRadius', - 'type': 'Rational' - }, - 50738: { - 'name': 'AntiAliasStrength', - 'type': 'Rational' - }, - 50739: { - 'name': 'ShadowScale', - 'type': 'SRational' - }, - 50740: { - 'name': 'DNGPrivateData', - 'type': 'Byte' - }, - 50741: { - 'name': 'MakerNoteSafety', - 'type': 'Short' - }, - 50778: { - 'name': 'CalibrationIlluminant1', - 'type': 'Short' - }, - 50779: { - 'name': 'CalibrationIlluminant2', - 'type': 'Short' - }, - 50780: { - 'name': 'BestQualityScale', - 'type': 'Rational' - }, - 50781: { - 'name': 'RawDataUniqueID', - 'type': 'Byte' - }, - 50827: { - 'name': 'OriginalRawFileName', - 'type': 'Byte' - }, - 50828: { - 'name': 'OriginalRawFileData', - 'type': 'Undefined' - }, - 50829: { - 'name': 'ActiveArea', - 'type': 'Short' - }, - 50830: { - 'name': 'MaskedAreas', - 'type': 'Short' - }, - 50831: { - 'name': 'AsShotICCProfile', - 'type': 'Undefined' - }, - 50832: { - 'name': 'AsShotPreProfileMatrix', - 'type': 'SRational' - }, - 50833: { - 'name': 'CurrentICCProfile', - 'type': 'Undefined' - }, - 50834: { - 'name': 'CurrentPreProfileMatrix', - 'type': 'SRational' - }, - 50879: { - 'name': 'ColorimetricReference', - 'type': 'Short' - }, - 50931: { - 'name': 'CameraCalibrationSignature', - 'type': 'Byte' - }, - 50932: { - 'name': 'ProfileCalibrationSignature', - 'type': 'Byte' - }, - 50934: { - 'name': 'AsShotProfileName', - 'type': 'Byte' - }, - 50935: { - 'name': 'NoiseReductionApplied', - 'type': 'Rational' - }, - 50936: { - 'name': 'ProfileName', - 'type': 'Byte' - }, - 50937: { - 'name': 'ProfileHueSatMapDims', - 'type': 'Long' - }, - 50938: { - 'name': 'ProfileHueSatMapData1', - 'type': 'Float' - }, - 50939: { - 'name': 'ProfileHueSatMapData2', - 'type': 'Float' - }, - 50940: { - 'name': 'ProfileToneCurve', - 'type': 'Float' - }, - 50941: { - 'name': 'ProfileEmbedPolicy', - 'type': 'Long' - }, - 50942: { - 'name': 'ProfileCopyright', - 'type': 'Byte' - }, - 50964: { - 'name': 'ForwardMatrix1', - 'type': 'SRational' - }, - 50965: { - 'name': 'ForwardMatrix2', - 'type': 'SRational' - }, - 50966: { - 'name': 'PreviewApplicationName', - 'type': 'Byte' - }, - 50967: { - 'name': 'PreviewApplicationVersion', - 'type': 'Byte' - }, - 50968: { - 'name': 'PreviewSettingsName', - 'type': 'Byte' - }, - 50969: { - 'name': 'PreviewSettingsDigest', - 'type': 'Byte' - }, - 50970: { - 'name': 'PreviewColorSpace', - 'type': 'Long' - }, - 50971: { - 'name': 'PreviewDateTime', - 'type': 'Ascii' - }, - 50972: { - 'name': 'RawImageDigest', - 'type': 'Undefined' - }, - 50973: { - 'name': 'OriginalRawFileDigest', - 'type': 'Undefined' - }, - 50974: { - 'name': 'SubTileBlockSize', - 'type': 'Long' - }, - 50975: { - 'name': 'RowInterleaveFactor', - 'type': 'Long' - }, - 50981: { - 'name': 'ProfileLookTableDims', - 'type': 'Long' - }, - 50982: { - 'name': 'ProfileLookTableData', - 'type': 'Float' - }, - 51008: { - 'name': 'OpcodeList1', - 'type': 'Undefined' - }, - 51009: { - 'name': 'OpcodeList2', - 'type': 'Undefined' - }, - 51022: { - 'name': 'OpcodeList3', - 'type': 'Undefined' - } - }, - 'Exif': { - 33434: { - 'name': 'ExposureTime', - 'type': 'Rational' - }, - 33437: { - 'name': 'FNumber', - 'type': 'Rational' - }, - 34850: { - 'name': 'ExposureProgram', - 'type': 'Short' - }, - 34852: { - 'name': 'SpectralSensitivity', - 'type': 'Ascii' - }, - 34855: { - 'name': 'ISOSpeedRatings', - 'type': 'Short' - }, - 34856: { - 'name': 'OECF', - 'type': 'Undefined' - }, - 34864: { - 'name': 'SensitivityType', - 'type': 'Short' - }, - 34865: { - 'name': 'StandardOutputSensitivity', - 'type': 'Long' - }, - 34866: { - 'name': 'RecommendedExposureIndex', - 'type': 'Long' - }, - 34867: { - 'name': 'ISOSpeed', - 'type': 'Long' - }, - 34868: { - 'name': 'ISOSpeedLatitudeyyy', - 'type': 'Long' - }, - 34869: { - 'name': 'ISOSpeedLatitudezzz', - 'type': 'Long' - }, - 36864: { - 'name': 'ExifVersion', - 'type': 'Undefined' - }, - 36867: { - 'name': 'DateTimeOriginal', - 'type': 'Ascii' - }, - 36868: { - 'name': 'DateTimeDigitized', - 'type': 'Ascii' - }, - 37121: { - 'name': 'ComponentsConfiguration', - 'type': 'Undefined' - }, - 37122: { - 'name': 'CompressedBitsPerPixel', - 'type': 'Rational' - }, - 37377: { - 'name': 'ShutterSpeedValue', - 'type': 'SRational' - }, - 37378: { - 'name': 'ApertureValue', - 'type': 'Rational' - }, - 37379: { - 'name': 'BrightnessValue', - 'type': 'SRational' - }, - 37380: { - 'name': 'ExposureBiasValue', - 'type': 'SRational' - }, - 37381: { - 'name': 'MaxApertureValue', - 'type': 'Rational' - }, - 37382: { - 'name': 'SubjectDistance', - 'type': 'Rational' - }, - 37383: { - 'name': 'MeteringMode', - 'type': 'Short' - }, - 37384: { - 'name': 'LightSource', - 'type': 'Short' - }, - 37385: { - 'name': 'Flash', - 'type': 'Short' - }, - 37386: { - 'name': 'FocalLength', - 'type': 'Rational' - }, - 37396: { - 'name': 'SubjectArea', - 'type': 'Short' - }, - 37500: { - 'name': 'MakerNote', - 'type': 'Undefined' - }, - 37510: { - 'name': 'UserComment', - 'type': 'Ascii' - }, - 37520: { - 'name': 'SubSecTime', - 'type': 'Ascii' - }, - 37521: { - 'name': 'SubSecTimeOriginal', - 'type': 'Ascii' - }, - 37522: { - 'name': 'SubSecTimeDigitized', - 'type': 'Ascii' - }, - 40960: { - 'name': 'FlashpixVersion', - 'type': 'Undefined' - }, - 40961: { - 'name': 'ColorSpace', - 'type': 'Short' - }, - 40962: { - 'name': 'PixelXDimension', - 'type': 'Long' - }, - 40963: { - 'name': 'PixelYDimension', - 'type': 'Long' - }, - 40964: { - 'name': 'RelatedSoundFile', - 'type': 'Ascii' - }, - 40965: { - 'name': 'InteroperabilityTag', - 'type': 'Long' - }, - 41483: { - 'name': 'FlashEnergy', - 'type': 'Rational' - }, - 41484: { - 'name': 'SpatialFrequencyResponse', - 'type': 'Undefined' - }, - 41486: { - 'name': 'FocalPlaneXResolution', - 'type': 'Rational' - }, - 41487: { - 'name': 'FocalPlaneYResolution', - 'type': 'Rational' - }, - 41488: { - 'name': 'FocalPlaneResolutionUnit', - 'type': 'Short' - }, - 41492: { - 'name': 'SubjectLocation', - 'type': 'Short' - }, - 41493: { - 'name': 'ExposureIndex', - 'type': 'Rational' - }, - 41495: { - 'name': 'SensingMethod', - 'type': 'Short' - }, - 41728: { - 'name': 'FileSource', - 'type': 'Undefined' - }, - 41729: { - 'name': 'SceneType', - 'type': 'Undefined' - }, - 41730: { - 'name': 'CFAPattern', - 'type': 'Undefined' - }, - 41985: { - 'name': 'CustomRendered', - 'type': 'Short' - }, - 41986: { - 'name': 'ExposureMode', - 'type': 'Short' - }, - 41987: { - 'name': 'WhiteBalance', - 'type': 'Short' - }, - 41988: { - 'name': 'DigitalZoomRatio', - 'type': 'Rational' - }, - 41989: { - 'name': 'FocalLengthIn35mmFilm', - 'type': 'Short' - }, - 41990: { - 'name': 'SceneCaptureType', - 'type': 'Short' - }, - 41991: { - 'name': 'GainControl', - 'type': 'Short' - }, - 41992: { - 'name': 'Contrast', - 'type': 'Short' - }, - 41993: { - 'name': 'Saturation', - 'type': 'Short' - }, - 41994: { - 'name': 'Sharpness', - 'type': 'Short' - }, - 41995: { - 'name': 'DeviceSettingDescription', - 'type': 'Undefined' - }, - 41996: { - 'name': 'SubjectDistanceRange', - 'type': 'Short' - }, - 42016: { - 'name': 'ImageUniqueID', - 'type': 'Ascii' - }, - 42032: { - 'name': 'CameraOwnerName', - 'type': 'Ascii' - }, - 42033: { - 'name': 'BodySerialNumber', - 'type': 'Ascii' - }, - 42034: { - 'name': 'LensSpecification', - 'type': 'Rational' - }, - 42035: { - 'name': 'LensMake', - 'type': 'Ascii' - }, - 42036: { - 'name': 'LensModel', - 'type': 'Ascii' - }, - 42037: { - 'name': 'LensSerialNumber', - 'type': 'Ascii' - }, - 42240: { - 'name': 'Gamma', - 'type': 'Rational' - } - }, - 'GPS': { - 0: { - 'name': 'GPSVersionID', - 'type': 'Byte' - }, - 1: { - 'name': 'GPSLatitudeRef', - 'type': 'Ascii' - }, - 2: { - 'name': 'GPSLatitude', - 'type': 'Rational' - }, - 3: { - 'name': 'GPSLongitudeRef', - 'type': 'Ascii' - }, - 4: { - 'name': 'GPSLongitude', - 'type': 'Rational' - }, - 5: { - 'name': 'GPSAltitudeRef', - 'type': 'Byte' - }, - 6: { - 'name': 'GPSAltitude', - 'type': 'Rational' - }, - 7: { - 'name': 'GPSTimeStamp', - 'type': 'Rational' - }, - 8: { - 'name': 'GPSSatellites', - 'type': 'Ascii' - }, - 9: { - 'name': 'GPSStatus', - 'type': 'Ascii' - }, - 10: { - 'name': 'GPSMeasureMode', - 'type': 'Ascii' - }, - 11: { - 'name': 'GPSDOP', - 'type': 'Rational' - }, - 12: { - 'name': 'GPSSpeedRef', - 'type': 'Ascii' - }, - 13: { - 'name': 'GPSSpeed', - 'type': 'Rational' - }, - 14: { - 'name': 'GPSTrackRef', - 'type': 'Ascii' - }, - 15: { - 'name': 'GPSTrack', - 'type': 'Rational' - }, - 16: { - 'name': 'GPSImgDirectionRef', - 'type': 'Ascii' - }, - 17: { - 'name': 'GPSImgDirection', - 'type': 'Rational' - }, - 18: { - 'name': 'GPSMapDatum', - 'type': 'Ascii' - }, - 19: { - 'name': 'GPSDestLatitudeRef', - 'type': 'Ascii' - }, - 20: { - 'name': 'GPSDestLatitude', - 'type': 'Rational' - }, - 21: { - 'name': 'GPSDestLongitudeRef', - 'type': 'Ascii' - }, - 22: { - 'name': 'GPSDestLongitude', - 'type': 'Rational' - }, - 23: { - 'name': 'GPSDestBearingRef', - 'type': 'Ascii' - }, - 24: { - 'name': 'GPSDestBearing', - 'type': 'Rational' - }, - 25: { - 'name': 'GPSDestDistanceRef', - 'type': 'Ascii' - }, - 26: { - 'name': 'GPSDestDistance', - 'type': 'Rational' - }, - 27: { - 'name': 'GPSProcessingMethod', - 'type': 'Undefined' - }, - 28: { - 'name': 'GPSAreaInformation', - 'type': 'Undefined' - }, - 29: { - 'name': 'GPSDateStamp', - 'type': 'Ascii' - }, - 30: { - 'name': 'GPSDifferential', - 'type': 'Short' - }, - 31: { - 'name': 'GPSHPositioningError', - 'type': 'Rational' - } - }, - 'Interop': { - 1: { - 'name': 'InteroperabilityIndex', - 'type': 'Ascii' - } - }, - }; - TAGS["0th"] = TAGS["Image"]; - TAGS["1st"] = TAGS["Image"]; - that.TAGS = TAGS; - - - that.ImageIFD = { - ProcessingSoftware:11, - NewSubfileType:254, - SubfileType:255, - ImageWidth:256, - ImageLength:257, - BitsPerSample:258, - Compression:259, - PhotometricInterpretation:262, - Threshholding:263, - CellWidth:264, - CellLength:265, - FillOrder:266, - DocumentName:269, - ImageDescription:270, - Make:271, - Model:272, - StripOffsets:273, - Orientation:274, - SamplesPerPixel:277, - RowsPerStrip:278, - StripByteCounts:279, - XResolution:282, - YResolution:283, - PlanarConfiguration:284, - GrayResponseUnit:290, - GrayResponseCurve:291, - T4Options:292, - T6Options:293, - ResolutionUnit:296, - TransferFunction:301, - Software:305, - DateTime:306, - Artist:315, - HostComputer:316, - Predictor:317, - WhitePoint:318, - PrimaryChromaticities:319, - ColorMap:320, - HalftoneHints:321, - TileWidth:322, - TileLength:323, - TileOffsets:324, - TileByteCounts:325, - SubIFDs:330, - InkSet:332, - InkNames:333, - NumberOfInks:334, - DotRange:336, - TargetPrinter:337, - ExtraSamples:338, - SampleFormat:339, - SMinSampleValue:340, - SMaxSampleValue:341, - TransferRange:342, - ClipPath:343, - XClipPathUnits:344, - YClipPathUnits:345, - Indexed:346, - JPEGTables:347, - OPIProxy:351, - JPEGProc:512, - JPEGInterchangeFormat:513, - JPEGInterchangeFormatLength:514, - JPEGRestartInterval:515, - JPEGLosslessPredictors:517, - JPEGPointTransforms:518, - JPEGQTables:519, - JPEGDCTables:520, - JPEGACTables:521, - YCbCrCoefficients:529, - YCbCrSubSampling:530, - YCbCrPositioning:531, - ReferenceBlackWhite:532, - XMLPacket:700, - Rating:18246, - RatingPercent:18249, - ImageID:32781, - CFARepeatPatternDim:33421, - CFAPattern:33422, - BatteryLevel:33423, - Copyright:33432, - ExposureTime:33434, - ImageResources:34377, - ExifTag:34665, - InterColorProfile:34675, - GPSTag:34853, - Interlace:34857, - TimeZoneOffset:34858, - SelfTimerMode:34859, - FlashEnergy:37387, - SpatialFrequencyResponse:37388, - Noise:37389, - FocalPlaneXResolution:37390, - FocalPlaneYResolution:37391, - FocalPlaneResolutionUnit:37392, - ImageNumber:37393, - SecurityClassification:37394, - ImageHistory:37395, - ExposureIndex:37397, - TIFFEPStandardID:37398, - SensingMethod:37399, - XPTitle:40091, - XPComment:40092, - XPAuthor:40093, - XPKeywords:40094, - XPSubject:40095, - PrintImageMatching:50341, - DNGVersion:50706, - DNGBackwardVersion:50707, - UniqueCameraModel:50708, - LocalizedCameraModel:50709, - CFAPlaneColor:50710, - CFALayout:50711, - LinearizationTable:50712, - BlackLevelRepeatDim:50713, - BlackLevel:50714, - BlackLevelDeltaH:50715, - BlackLevelDeltaV:50716, - WhiteLevel:50717, - DefaultScale:50718, - DefaultCropOrigin:50719, - DefaultCropSize:50720, - ColorMatrix1:50721, - ColorMatrix2:50722, - CameraCalibration1:50723, - CameraCalibration2:50724, - ReductionMatrix1:50725, - ReductionMatrix2:50726, - AnalogBalance:50727, - AsShotNeutral:50728, - AsShotWhiteXY:50729, - BaselineExposure:50730, - BaselineNoise:50731, - BaselineSharpness:50732, - BayerGreenSplit:50733, - LinearResponseLimit:50734, - CameraSerialNumber:50735, - LensInfo:50736, - ChromaBlurRadius:50737, - AntiAliasStrength:50738, - ShadowScale:50739, - DNGPrivateData:50740, - MakerNoteSafety:50741, - CalibrationIlluminant1:50778, - CalibrationIlluminant2:50779, - BestQualityScale:50780, - RawDataUniqueID:50781, - OriginalRawFileName:50827, - OriginalRawFileData:50828, - ActiveArea:50829, - MaskedAreas:50830, - AsShotICCProfile:50831, - AsShotPreProfileMatrix:50832, - CurrentICCProfile:50833, - CurrentPreProfileMatrix:50834, - ColorimetricReference:50879, - CameraCalibrationSignature:50931, - ProfileCalibrationSignature:50932, - AsShotProfileName:50934, - NoiseReductionApplied:50935, - ProfileName:50936, - ProfileHueSatMapDims:50937, - ProfileHueSatMapData1:50938, - ProfileHueSatMapData2:50939, - ProfileToneCurve:50940, - ProfileEmbedPolicy:50941, - ProfileCopyright:50942, - ForwardMatrix1:50964, - ForwardMatrix2:50965, - PreviewApplicationName:50966, - PreviewApplicationVersion:50967, - PreviewSettingsName:50968, - PreviewSettingsDigest:50969, - PreviewColorSpace:50970, - PreviewDateTime:50971, - RawImageDigest:50972, - OriginalRawFileDigest:50973, - SubTileBlockSize:50974, - RowInterleaveFactor:50975, - ProfileLookTableDims:50981, - ProfileLookTableData:50982, - OpcodeList1:51008, - OpcodeList2:51009, - OpcodeList3:51022, - NoiseProfile:51041, - }; - - - that.ExifIFD = { - ExposureTime:33434, - FNumber:33437, - ExposureProgram:34850, - SpectralSensitivity:34852, - ISOSpeedRatings:34855, - OECF:34856, - SensitivityType:34864, - StandardOutputSensitivity:34865, - RecommendedExposureIndex:34866, - ISOSpeed:34867, - ISOSpeedLatitudeyyy:34868, - ISOSpeedLatitudezzz:34869, - ExifVersion:36864, - DateTimeOriginal:36867, - DateTimeDigitized:36868, - ComponentsConfiguration:37121, - CompressedBitsPerPixel:37122, - ShutterSpeedValue:37377, - ApertureValue:37378, - BrightnessValue:37379, - ExposureBiasValue:37380, - MaxApertureValue:37381, - SubjectDistance:37382, - MeteringMode:37383, - LightSource:37384, - Flash:37385, - FocalLength:37386, - SubjectArea:37396, - MakerNote:37500, - UserComment:37510, - SubSecTime:37520, - SubSecTimeOriginal:37521, - SubSecTimeDigitized:37522, - FlashpixVersion:40960, - ColorSpace:40961, - PixelXDimension:40962, - PixelYDimension:40963, - RelatedSoundFile:40964, - InteroperabilityTag:40965, - FlashEnergy:41483, - SpatialFrequencyResponse:41484, - FocalPlaneXResolution:41486, - FocalPlaneYResolution:41487, - FocalPlaneResolutionUnit:41488, - SubjectLocation:41492, - ExposureIndex:41493, - SensingMethod:41495, - FileSource:41728, - SceneType:41729, - CFAPattern:41730, - CustomRendered:41985, - ExposureMode:41986, - WhiteBalance:41987, - DigitalZoomRatio:41988, - FocalLengthIn35mmFilm:41989, - SceneCaptureType:41990, - GainControl:41991, - Contrast:41992, - Saturation:41993, - Sharpness:41994, - DeviceSettingDescription:41995, - SubjectDistanceRange:41996, - ImageUniqueID:42016, - CameraOwnerName:42032, - BodySerialNumber:42033, - LensSpecification:42034, - LensMake:42035, - LensModel:42036, - LensSerialNumber:42037, - Gamma:42240, - }; - - - that.GPSIFD = { - GPSVersionID:0, - GPSLatitudeRef:1, - GPSLatitude:2, - GPSLongitudeRef:3, - GPSLongitude:4, - GPSAltitudeRef:5, - GPSAltitude:6, - GPSTimeStamp:7, - GPSSatellites:8, - GPSStatus:9, - GPSMeasureMode:10, - GPSDOP:11, - GPSSpeedRef:12, - GPSSpeed:13, - GPSTrackRef:14, - GPSTrack:15, - GPSImgDirectionRef:16, - GPSImgDirection:17, - GPSMapDatum:18, - GPSDestLatitudeRef:19, - GPSDestLatitude:20, - GPSDestLongitudeRef:21, - GPSDestLongitude:22, - GPSDestBearingRef:23, - GPSDestBearing:24, - GPSDestDistanceRef:25, - GPSDestDistance:26, - GPSProcessingMethod:27, - GPSAreaInformation:28, - GPSDateStamp:29, - GPSDifferential:30, - GPSHPositioningError:31, - }; - - - that.InteropIFD = { - InteroperabilityIndex:1, - }; - - that.GPSHelper = { - degToDmsRational:function (degFloat) { - var degAbs = Math.abs(degFloat); - var minFloat = degAbs % 1 * 60; - var secFloat = minFloat % 1 * 60; - var deg = Math.floor(degAbs); - var min = Math.floor(minFloat); - var sec = Math.round(secFloat * 100); - - return [[deg, 1], [min, 1], [sec, 100]]; - }, - - dmsRationalToDeg:function (dmsArray, ref) { - var sign = (ref === 'S' || ref === 'W') ? -1.0 : 1.0; - var deg = dmsArray[0][0] / dmsArray[0][1] + - dmsArray[1][0] / dmsArray[1][1] / 60.0 + - dmsArray[2][0] / dmsArray[2][1] / 3600.0; - - return deg * sign; - } - }; - - - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = that; - } - exports.piexif = that; - } else { - window.piexif = that; - } })() From b93a206a4841b69408096c21596d41fa4603688f Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 14:04:40 +0530 Subject: [PATCH 077/120] sdkit 1.0.178 - prevent image size errors when blending mask with strict mask border --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 15c8c2cd..b424d652 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.177", + "sdkit": "1.0.178", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 41d4ad2096409946f85932b6d88f7a4cea379cb2 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 14:29:54 +0530 Subject: [PATCH 078/120] sdkit 1.0.179 - tile controlnet --- scripts/check_modules.py | 2 +- ui/easydiffusion/model_manager.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index b424d652..2db17aa6 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.178", + "sdkit": "1.0.179", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index 841b0cd2..d18ebba0 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -312,6 +312,7 @@ def getModels(scan_for_malicious: bool = True): {"control_v11p_sd15_mlsd": "Straight Lines"}, {"control_v11p_sd15_seg": "Segment"}, {"control_v11e_sd15_shuffle": "Shuffle"}, + {"control_v11f1e_sd15_tile.pth": "Tile"}, ], }, } From d2078d4dde76263f991ff44f3e36e43d37851bd7 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 19:17:30 +0530 Subject: [PATCH 079/120] sdkit 1.0.180 - auto-download the tile controlnet if necessary, by fixing the id in the models db --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 2db17aa6..2754443e 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.179", + "sdkit": "1.0.180", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From f2b441d9fc66577b7430b414ac1dfc3d5d1da28a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 19:20:23 +0530 Subject: [PATCH 080/120] typo --- ui/easydiffusion/model_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/easydiffusion/model_manager.py b/ui/easydiffusion/model_manager.py index d18ebba0..7848291a 100644 --- a/ui/easydiffusion/model_manager.py +++ b/ui/easydiffusion/model_manager.py @@ -312,7 +312,7 @@ def getModels(scan_for_malicious: bool = True): {"control_v11p_sd15_mlsd": "Straight Lines"}, {"control_v11p_sd15_seg": "Segment"}, {"control_v11e_sd15_shuffle": "Shuffle"}, - {"control_v11f1e_sd15_tile.pth": "Tile"}, + {"control_v11f1e_sd15_tile": "Tile"}, ], }, } From 18049d529a8fa305adf207d6387195d4e93757af Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Mon, 21 Aug 2023 19:45:26 +0530 Subject: [PATCH 081/120] Fix the lora prompt parser --- ui/plugins/ui/lora-prompt-parser.plugin.js | 26 ++++------------------ 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/ui/plugins/ui/lora-prompt-parser.plugin.js b/ui/plugins/ui/lora-prompt-parser.plugin.js index 201d49af..c1cbcac7 100644 --- a/ui/plugins/ui/lora-prompt-parser.plugin.js +++ b/ui/plugins/ui/lora-prompt-parser.plugin.js @@ -25,29 +25,11 @@ } if (LoRA !== null && LoRA.length > 0 && testDiffusers?.checked) { - for (let i = 0; i < LoRA.length; i++) { - //if (loraModelField.value !== LoRA[0].lora_model) { - // Set the new LoRA value - //console.log("Loading info"); - //console.log(LoRA[0].lora_model_0); - //console.log(JSON.stringify(LoRa)); - - let lora = `lora_model_${i}`; - let alpha = `lora_alpha_${i}`; - let loramodel = document.getElementById(lora); - let alphavalue = document.getElementById(alpha); - loramodel.setAttribute("data-path", LoRA[i].lora_model_0); - loramodel.value = LoRA[i].lora_model_0; - alphavalue.value = LoRA[i].lora_alpha_0; - if (i != LoRA.length - 1) - createLoraEntry(); - } - //loraAlphaSlider.value = loraAlphaField.value * 100; - //TBD.value = LoRA[0].blockweights; // block weights not supported by ED at this time - //} + let modelNames = LoRA.map(e => e.lora_model_0) + let modelWeights = LoRA.map(e => e.lora_alpha_0) + loraModelField.value = {modelNames: modelNames, modelWeights: modelWeights} + showToast("Prompt successfully processed", LoRA[0].lora_model_0); - //console.log('LoRa: ' + LoRA[0].lora_model_0); - //showToast("Prompt successfully processed", lora_model_0.value); } From 669d40a9d235c6117a9cc0207fff4c0a6c607865 Mon Sep 17 00:00:00 2001 From: rbertus2000 <91765399+rbertus2000@users.noreply.github.com> Date: Tue, 22 Aug 2023 05:38:59 +0200 Subject: [PATCH 082/120] fix embedding parser and use standard embedding varuable for metadata (#1516) --- ui/easydiffusion/utils/save_utils.py | 25 ++----------------------- ui/media/js/main.js | 2 +- 2 files changed, 3 insertions(+), 24 deletions(-) diff --git a/ui/easydiffusion/utils/save_utils.py b/ui/easydiffusion/utils/save_utils.py index 4660b1ed..465e2b8e 100644 --- a/ui/easydiffusion/utils/save_utils.py +++ b/ui/easydiffusion/utils/save_utils.py @@ -34,7 +34,7 @@ TASK_TEXT_MAPPING = { "lora_alpha": "LoRA Strength", "use_hypernetwork_model": "Hypernetwork model", "hypernetwork_strength": "Hypernetwork Strength", - "use_embedding_models": "Embedding models", + "use_embeddings_model": "Embedding models", "tiling": "Seamless Tiling", "use_face_correction": "Use Face Correction", "use_upscale": "Use Upscaling", @@ -228,28 +228,7 @@ def get_printable_request(req: GenerateImageRequest, task_data: TaskData, output metadata[key] = req_metadata[key] elif key in task_data_metadata: metadata[key] = task_data_metadata[key] - elif key == "use_embedding_models" and using_diffusers: - embeddings_extensions = {".pt", ".bin", ".safetensors"} - - def scan_directory(directory_path: str): - used_embeddings = [] - for entry in os.scandir(directory_path): - if entry.is_file(): - entry_extension = os.path.splitext(entry.name)[1] - if entry_extension not in embeddings_extensions: - continue - - embedding_name_regex = regex.compile( - r"(^|[\s,])" + regex.escape(os.path.splitext(entry.name)[0]) + r"([+-]*$|[\s,]|[+-]+[\s,])" - ) - if embedding_name_regex.search(req.prompt) or embedding_name_regex.search(req.negative_prompt): - used_embeddings.append(entry.path) - elif entry.is_dir(): - used_embeddings.extend(scan_directory(entry.path)) - return used_embeddings - - used_embeddings = scan_directory(os.path.join(app.MODELS_DIR, "embeddings")) - metadata["use_embedding_models"] = used_embeddings if len(used_embeddings) > 0 else None + # Clean up the metadata if req.init_image is None and "prompt_strength" in metadata: diff --git a/ui/media/js/main.js b/ui/media/js/main.js index f7a93f46..a1fecbc7 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -1306,7 +1306,7 @@ function getCurrentUserRequest() { function setEmbeddings(task) { let prompt = task.reqBody.prompt.toLowerCase() let negativePrompt = task.reqBody.negative_prompt.toLowerCase() - let overallPrompt = (prompt + " " + negativePrompt).replaceAll(",", "").split(" ") + let overallPrompt = (prompt + " " + negativePrompt) let embeddingsTree = modelsOptions["embeddings"] let embeddings = [] From 4807744aa7d57ecd54ae271b63fcbc572a153911 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 11:42:38 +0530 Subject: [PATCH 083/120] sdkit 1.0.181 - fix typo in watermark skip --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 2754443e..14c28c4d 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.180", + "sdkit": "1.0.181", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From a8c0abfd5d60072fa9a40eafbdebb515817fbd91 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 12:02:34 +0530 Subject: [PATCH 084/120] Use document event handling for task events --- ui/media/js/main.js | 22 +++++++++------- ui/media/js/task-manager.js | 52 ++++++++++++++++++++++++------------- 2 files changed, 47 insertions(+), 27 deletions(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index f7a93f46..27dad2cb 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2703,29 +2703,33 @@ let recentResolutionsValues = [] }) })() -TASK_CALLBACKS["before_task_start"].push(function(task) { +document.addEventListener("before_task_start", (e) => { + let task = e.detail.task + // Update the seed *before* starting the processing so it's retained if user stops the task if (randomSeedField.checked) { seedField.value = task.seed } }) -TASK_CALLBACKS["after_task_start"].push(function(task) { - // setStatus("request", "fetching..") // no-op implementation +document.addEventListener("after_task_start", (e) => { renderButtons.style.display = "flex" renameMakeImageButton() updateInitialText() }) -TASK_CALLBACKS["on_task_step"].push(function(task, reqBody, stepUpdate, outputContainer) { - showImages(reqBody, stepUpdate, outputContainer, true) +document.addEventListener("on_task_step", (e) => { + showImages(e.detail.reqBody, e.detail.stepUpdate, e.detail.outputContainer, true) }) -TASK_CALLBACKS["on_render_task_success"].push(function(task, reqBody, stepUpdate, outputContainer) { - showImages(reqBody, stepUpdate, outputContainer, false) +document.addEventListener("on_render_task_success", (e) => { + showImages(e.detail.reqBody, e.detail.stepUpdate, e.detail.outputContainer, false) }) -TASK_CALLBACKS["on_render_task_fail"].push(function(task, reqBody, stepUpdate, outputContainer) { +document.addEventListener("on_render_task_fail", (e) => { + let task = e.detail.task + let stepUpdate = e.detail.stepUpdate + const outputMsg = task["outputMsg"] let msg = "" if ("detail" in stepUpdate && typeof stepUpdate.detail === "string" && stepUpdate.detail.length > 0) { @@ -2774,7 +2778,7 @@ TASK_CALLBACKS["on_render_task_fail"].push(function(task, reqBody, stepUpdate, o logError(msg, stepUpdate, outputMsg) }) -TASK_CALLBACKS["on_all_tasks_complete"].push(function() { +document.addEventListener("on_all_tasks_complete", (e) => { renderButtons.style.display = "none" renameMakeImageButton() diff --git a/ui/media/js/task-manager.js b/ui/media/js/task-manager.js index a36bf90b..71fd7803 100644 --- a/ui/media/js/task-manager.js +++ b/ui/media/js/task-manager.js @@ -4,15 +4,6 @@ const pauseBtn = document.querySelector("#pause") const resumeBtn = document.querySelector("#resume") const processOrder = document.querySelector("#process_order_toggle") -let TASK_CALLBACKS = { - before_task_start: [], - after_task_start: [], - on_task_step: [], - on_render_task_success: [], - on_render_task_fail: [], - on_all_tasks_complete: [], -} - let pauseClient = false async function onIdle() { @@ -141,7 +132,7 @@ async function onTaskStart(task) { task["instances"].push(instance) task.batchesDone++ - TASK_CALLBACKS["before_task_start"].forEach((callback) => callback(task)) + document.dispatchEvent(new CustomEvent("before_task_start", { detail: { task: task } })) instance.enqueue(getTaskUpdater(task, newTaskReqBody, outputContainer)).then( (renderResult) => { @@ -152,7 +143,7 @@ async function onTaskStart(task) { } ) - TASK_CALLBACKS["after_task_start"].forEach((callback) => callback(task)) + document.dispatchEvent(new CustomEvent("after_task_start", { detail: { task: task } })) } function getTaskUpdater(task, reqBody, outputContainer) { @@ -242,8 +233,15 @@ function getTaskUpdater(task, reqBody, outputContainer) { progressBarInner.style.width = `${percent}%` if (stepUpdate.output) { - TASK_CALLBACKS["on_task_step"].forEach((callback) => - callback(task, reqBody, stepUpdate, outputContainer) + document.dispatchEvent( + new CustomEvent("on_task_step", { + detail: { + task: task, + reqBody: reqBody, + stepUpdate: stepUpdate, + outputContainer: outputContainer, + }, + }) ) } } @@ -253,13 +251,27 @@ function getTaskUpdater(task, reqBody, outputContainer) { function onRenderTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { if (typeof stepUpdate === "object") { if (stepUpdate.status === "succeeded") { - TASK_CALLBACKS["on_render_task_success"].forEach((callback) => - callback(task, reqBody, stepUpdate, outputContainer) + document.dispatchEvent( + new CustomEvent("on_render_task_success", { + detail: { + task: task, + reqBody: reqBody, + stepUpdate: stepUpdate, + outputContainer: outputContainer, + }, + }) ) } else { task.isProcessing = false - TASK_CALLBACKS["on_render_task_fail"].forEach((callback) => - callback(task, reqBody, stepUpdate, outputContainer) + document.dispatchEvent( + new CustomEvent("on_render_task_fail", { + detail: { + task: task, + reqBody: reqBody, + stepUpdate: stepUpdate, + outputContainer: outputContainer, + }, + }) ) } } @@ -307,7 +319,11 @@ function onRenderTaskCompleted(task, reqBody, instance, outputContainer, stepUpd resumeBtn.click() } - TASK_CALLBACKS["on_all_tasks_complete"].forEach((callback) => callback()) + document.dispatchEvent( + new CustomEvent("on_all_tasks_complete", { + detail: {}, + }) + ) } function resumeClient() { From 0dc01cb97435d634b02140f97365d0e59373de59 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 15:14:53 +0530 Subject: [PATCH 085/120] sdkit 1.0.182 - improve detection of SD 2.0 and 2.1 models, auto-detect v-parameterization and improve load time by speeding up the black-image test --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 14c28c4d..e2e12b70 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.181", + "sdkit": "1.0.182", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 8f9feb3ed929c634b4f7df8b1c07e5009e833936 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 15:16:46 +0530 Subject: [PATCH 086/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7aed1ffa..a60c18fc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. * 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. * 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. * 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. From 061380742cf979931fa2e1d08ce557a086f84054 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 15:17:24 +0530 Subject: [PATCH 087/120] version --- CHANGES.md | 2 +- ui/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a60c18fc..f29b6d23 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,7 +22,7 @@ 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 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. +* 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. * 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. * 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. * 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. diff --git a/ui/index.html b/ui/index.html index 05a83c57..eb4c62da 100644 --- a/ui/index.html +++ b/ui/index.html @@ -35,7 +35,7 @@

Easy Diffusion - v3.0.1 + v3.0.2

From 1fd011b1bef9b21be15c3b63cd9359ef63cbb3a0 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 15:41:21 +0530 Subject: [PATCH 088/120] Don't fail if the prompt strength is too low --- ui/easydiffusion/tasks/render_images.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui/easydiffusion/tasks/render_images.py b/ui/easydiffusion/tasks/render_images.py index 593b409d..f0d25d37 100644 --- a/ui/easydiffusion/tasks/render_images.py +++ b/ui/easydiffusion/tasks/render_images.py @@ -232,6 +232,9 @@ def generate_images_internal( req.control_image = resize_img(req.control_image.convert("RGB"), req.width, req.height, clamp_to_8=True) req.control_image = filter_images(context, req.control_image, task_data.control_filter_to_apply)[0] + if req.init_image is not None and int(req.num_inference_steps * req.prompt_strength) == 0: + req.prompt_strength = 1 / req.num_inference_steps if req.num_inference_steps > 0 else 1 + if context.test_diffusers: pipe = context.models["stable-diffusion"]["default"] if hasattr(pipe.unet, "_allocate_trt_buffers_backup"): From bde811341409c53de9d3406b5bc9e8b5f7058038 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 16:10:02 +0530 Subject: [PATCH 089/120] sdkit 1.0.183 - reduce VRAM usage of controlnet in low vram mode, and allow accelerating controlnets with xformers --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index e2e12b70..bff501f5 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.182", + "sdkit": "1.0.183", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From e607035c6504d1bbb439ecb552680fb24768482c Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 16:11:20 +0530 Subject: [PATCH 090/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index f29b6d23..824bcd28 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. * 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. * 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. * 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. From 1cd9c7fdac25ed66a95187b64dcb2881ba0be800 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 16:59:40 +0530 Subject: [PATCH 091/120] Show the negative embeddings button only if the negative prompt panel is open --- ui/media/js/main.js | 11 +++++++++++ ui/media/js/parameters.js | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/ui/media/js/main.js b/ui/media/js/main.js index fb20c5dc..1ce88d4d 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -2517,6 +2517,17 @@ window.addEventListener("beforeunload", function(e) { } }) +document.addEventListener("collapsibleClick", function(e) { + let header = e.detail + if (header === document.querySelector("#negative_prompt_handle")) { + if (header.classList.contains("active")) { + negativeEmbeddingsButton.classList.remove("displayNone") + } else { + negativeEmbeddingsButton.classList.add("displayNone") + } + } +}) + createCollapsibles() prettifyInputs(document) diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js index 7fdf8632..2209f286 100644 --- a/ui/media/js/parameters.js +++ b/ui/media/js/parameters.js @@ -487,6 +487,7 @@ async function getAppConfig() { document.querySelector("#controlnet_model_container").style.display = "none" document.querySelector("#hypernetwork_model_container").style.display = "" document.querySelector("#hypernetwork_strength_container").style.display = "" + document.querySelector("#negative-embeddings-button").style.display = "none" document.querySelectorAll("#sampler_name option.diffusers-only").forEach((option) => { option.style.display = "none" @@ -506,7 +507,6 @@ async function getAppConfig() { }) document.querySelector("#clip_skip_config").classList.remove("displayNone") document.querySelector("#embeddings-button").classList.remove("displayNone") - document.querySelector("#negative-embeddings-button").classList.remove("displayNone") IMAGE_STEP_SIZE = 8 customWidthField.step = IMAGE_STEP_SIZE customHeightField.step = IMAGE_STEP_SIZE From c92470ff7eea13ae56e0fbddfe0dcef738fd2c7e Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 17:45:00 +0530 Subject: [PATCH 092/120] sdkit 1.0.184 - fix broken SD2 inpainting model --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index bff501f5..38f9d179 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.183", + "sdkit": "1.0.184", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From ad9d9e0b048b068f597db5f22ad5a202df9e2ef3 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 18:57:41 +0530 Subject: [PATCH 093/120] sdkit 1.0.185 - full support for custom inpainting models --- scripts/check_modules.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/check_modules.py b/scripts/check_modules.py index 38f9d179..f622a6f1 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.184", + "sdkit": "1.0.185", "stable-diffusion-sdkit": "2.1.4", "rich": "12.6.0", "uvicorn": "0.19.0", From 5a49818a106ae7b6719740edb864c6bf0cee6374 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Tue, 22 Aug 2023 19:06:38 +0530 Subject: [PATCH 094/120] changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 824bcd28..85661068 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -22,6 +22,7 @@ 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.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file. * 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. * 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. * 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. From b8f533d0ea0121775acb394868cde035440787e6 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 11:06:53 +0530 Subject: [PATCH 095/120] v3 changelog summary --- CHANGES.md | 54 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 85661068..cf487d37 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,41 @@ # What's new? +## v3.0 +### Major Changes +- **ControlNet** - Full support for ControlNet, with native integration of the common ControlNet models. Just select a control image, then choose the ControlNet filter/model and run. No additional configuration or download necessary. Supports custom ControlNets as well. +- **SDXL** - Full support for SDXL. No configuration necessary, just put the SDXL model in the `models/stable-diffusion` folder. +- **Multiple LoRAs** - Use multiple LoRAs, including SDXL and SD2-compatible LoRAs. Put them in the `models/lora` folder. +- **Embeddings** - Use textual inversion embeddings easily, by putting them in the `models/embeddings` folder and using their names in the prompt (or by clicking the `+ Embeddings` button to select embeddings visually). +- **Seamless Tiling** - Generate repeating textures that can be useful for games and other art projects. Works best in 512x512 resolution. +- **Inpainting Models** - Full support for inpainting models, including custom inpainting models. No configuration (or yaml files) necessary. +- **Faster than v2.5** - Nearly 40% faster than Easy Diffusion v2.5, and can be even faster if you enable xFormers. +- **Even less VRAM usage** - Less than 2 GB for 512x512 images on 'low' VRAM usage setting (SD 1.5). Can generate large images with SDXL. +- **WebP images** - Supports saving images in the lossless webp format. +- **Undo/Redo in the UI** - Remove tasks or images from the queue easily, and undo the action if you removed anything accidentally. +- **Three new samplers, and latent upscaler** - Added `DEIS`, `DDPM` and `DPM++ 2m SDE` as additional samplers. Thanks @ogmaresca and @rbertus2000. +- **Significantly faster 'Upscale' and 'Fix Faces' buttons on the images** +- **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. + +### Detailed changelog +* 3.0.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file. +* 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. +* 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. +* 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. +* 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. +* 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. +* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. +* 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. +* 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder. +* 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. + ## v2.5 ### Major Changes - **Nearly twice as fast** - significantly faster speed of image generation. Code contributions are welcome to make our project even faster: https://github.com/easydiffusion/sdkit/#is-it-fast @@ -22,24 +58,6 @@ 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.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file. -* 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. -* 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. -* 3.0.1 - 18 Aug 2023 - Rotate an image if EXIF rotation is present. For e.g. this is common in images taken with a smartphone. -* 3.0.1 - 18 Aug 2023 - Resize control images to the task dimensions, to avoid memory errors with high-res control images. -* 3.0.1 - 18 Aug 2023 - Show controlnet filter preview in the task entry. -* 3.0.1 - 18 Aug 2023 - Fix drag-and-drop and 'Use these Settings' for LoRA and ControlNet. -* 3.0.1 - 18 Aug 2023 - Auto-save LoRA models and strengths. -* 3.0.1 - 17 Aug 2023 - Automatically use the correct yaml config file for custom SDXL models, even if a yaml file isn't present in the folder. -* 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. * 2.5.47 - 29 Jul 2023 - (beta-only) Fix long prompts with SDXL. From f387b9f464a1d9b9a35edfe129ee5381ef92ad3a Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 11:07:38 +0530 Subject: [PATCH 096/120] changelog --- CHANGES.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cf487d37..63e81f85 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,13 +5,13 @@ - **ControlNet** - Full support for ControlNet, with native integration of the common ControlNet models. Just select a control image, then choose the ControlNet filter/model and run. No additional configuration or download necessary. Supports custom ControlNets as well. - **SDXL** - Full support for SDXL. No configuration necessary, just put the SDXL model in the `models/stable-diffusion` folder. - **Multiple LoRAs** - Use multiple LoRAs, including SDXL and SD2-compatible LoRAs. Put them in the `models/lora` folder. -- **Embeddings** - Use textual inversion embeddings easily, by putting them in the `models/embeddings` folder and using their names in the prompt (or by clicking the `+ Embeddings` button to select embeddings visually). -- **Seamless Tiling** - Generate repeating textures that can be useful for games and other art projects. Works best in 512x512 resolution. +- **Embeddings** - Use textual inversion embeddings easily, by putting them in the `models/embeddings` folder and using their names in the prompt (or by clicking the `+ Embeddings` button to select embeddings visually). Thanks @JeLuf. +- **Seamless Tiling** - Generate repeating textures that can be useful for games and other art projects. Works best in 512x512 resolution. Thanks @JeLuf. - **Inpainting Models** - Full support for inpainting models, including custom inpainting models. No configuration (or yaml files) necessary. - **Faster than v2.5** - Nearly 40% faster than Easy Diffusion v2.5, and can be even faster if you enable xFormers. - **Even less VRAM usage** - Less than 2 GB for 512x512 images on 'low' VRAM usage setting (SD 1.5). Can generate large images with SDXL. - **WebP images** - Supports saving images in the lossless webp format. -- **Undo/Redo in the UI** - Remove tasks or images from the queue easily, and undo the action if you removed anything accidentally. +- **Undo/Redo in the UI** - Remove tasks or images from the queue easily, and undo the action if you removed anything accidentally. Thanks @JeLuf. - **Three new samplers, and latent upscaler** - Added `DEIS`, `DDPM` and `DPM++ 2m SDE` as additional samplers. Thanks @ogmaresca and @rbertus2000. - **Significantly faster 'Upscale' and 'Fix Faces' buttons on the images** - **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. From 04274f5839889dc9ab375210547a3a236b75246d Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 12:11:06 +0530 Subject: [PATCH 097/120] Fix styling on mobile devices --- CHANGES.md | 1 + ui/index.html | 8 ++-- ui/media/css/main.css | 69 +++++++++++++++++++++++++++++ ui/media/js/multi-model-selector.js | 2 +- 4 files changed, 75 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 63e81f85..4f5b7d8f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - **Major rewrite of the code** - We've switched to using diffusers under-the-hood, which allows us to release new features faster, and focus on making the UI and installer even easier to use. ### Detailed changelog +* 3.0.2 - 23 Aug 2023 - Fix styling on mobile devices. * 3.0.2 - 22 Aug 2023 - Full support for inpainting models, including custom models. Support SD 1.x and SD 2.x inpainting models. Does not require you to specify a yaml config file. * 3.0.2 - 22 Aug 2023 - Reduce VRAM consumption of controlnet in 'low' VRAM mode, and allow accelerating controlnets using xformers. * 3.0.2 - 22 Aug 2023 - Improve auto-detection of SD 2.0 and 2.1 models, removing the need for custom yaml files for SD 2.x models. Improve the model load time by speeding-up the black image test. diff --git a/ui/index.html b/ui/index.html index eb4c62da..4cac4ae4 100644 --- a/ui/index.html +++ b/ui/index.html @@ -157,9 +157,9 @@
+ - +
@@ -290,7 +290,7 @@ - + Swap width and height - +
Advanced sizes
diff --git a/ui/media/css/main.css b/ui/media/css/main.css index e9021ead..8fe59882 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1940,8 +1940,77 @@ div#enlarge-buttons { margin: 12px 10px; } +#num_outputs_total { + width: 42pt; +} +#num_outputs_parallel { + width: 42pt; +} +.model_entry .model_weight { + width: 50pt; +} /* hack for fixing Image Modifier Improvements plugin */ #imageTagPopupContainer { position: absolute; } + +@media screen and (max-width: 400px) { + .editor-slider { + width: 40%; + } + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + input[type=number] { + -moz-appearance: textfield; + /* Firefox */ + } + #num_outputs_total { + width: 27pt; + } + #num_outputs_parallel { + width: 27pt; + margin-left: -4pt; + } + .model_entry .model_weight { + width: 30pt; + } + #width { + width: 50pt; + } + #height { + width: 50pt; + } +} +@media screen and (max-width: 460px) { + #widthLabel small span { + display: none; + } + #widthLabel small:after { + content: "(w)"; + } + #heightLabel small span { + display: none; + } + #heightLabel small:after { + content: "(h)"; + } + #prompt-toolbar-right { + text-align: right; + } + #editor-settings label { + font-size: 9pt; + } + #editor-settings .model-filter { + width: 56%; + } + #vae_model { + width: 65% !important; + } + .model_entry .model_name { + width: 60% !important; + } +} \ No newline at end of file diff --git a/ui/media/js/multi-model-selector.js b/ui/media/js/multi-model-selector.js index 472ed81a..0640288f 100644 --- a/ui/media/js/multi-model-selector.js +++ b/ui/media/js/multi-model-selector.js @@ -137,7 +137,7 @@ class MultiModelSelector { modelElement.className = "model_entry" modelElement.innerHTML = ` - + ` this.modelContainer.appendChild(modelElement) From 98f58e8672ea25e3ad0b4c9f0efcaa194b312054 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 12:15:35 +0530 Subject: [PATCH 098/120] Download button styling on mobile --- ui/index.html | 2 +- ui/media/css/main.css | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ui/index.html b/ui/index.html index 4cac4ae4..2fe11cb4 100644 --- a/ui/index.html +++ b/ui/index.html @@ -432,7 +432,7 @@
- +
From c62161770d4b7431ef99d7082ec96ac7a0490a3b Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 13:52:37 +0530 Subject: [PATCH 104/120] v3 readme --- README.md | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 8acafd76..a31edfc1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ -# Easy Diffusion 2.5 +# Easy Diffusion 3.0 ### The easiest way to install and use [Stable Diffusion](https://github.com/CompVis/stable-diffusion) on your computer. Does not require technical knowledge, does not require pre-installed software. 1-click install, powerful features, friendly community. [Installation guide](#installation) | [Troubleshooting guide](https://github.com/easydiffusion/easydiffusion/wiki/Troubleshooting) | [![Discord Server](https://img.shields.io/discord/1014774730907209781?label=Discord)](https://discord.com/invite/u9yhsFmEkB) (for support queries, and development discussions) -![t2i](https://raw.githubusercontent.com/Stability-AI/stablediffusion/main/assets/stable-samples/txt2img/768/merged-0006.png) +--- +![262597678-11089485-2514-4a11-88fb-c3acc81fc9ec](https://github.com/easydiffusion/easydiffusion/assets/844287/050b5e15-e909-45bf-8162-a38234830e38) + # Installation Click the download button for your operating system: @@ -59,17 +61,19 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. - **UI Themes**: Customize the program to your liking. - **Searchable models dropdown**: organize your models into sub-folders, and search through them in the UI. -### Image generation -- **Supports**: "*Text to Image*" and "*Image to Image*". -- **21 Samplers**: `ddim`, `plms`, `heun`, `euler`, `euler_a`, `dpm2`, `dpm2_a`, `lms`, `dpm_solver_stability`, `dpmpp_2s_a`, `dpmpp_2m`, `dpmpp_sde`, `dpm_fast`, `dpm_adaptive`, `ddpm`, `deis`, `unipc_snr`, `unipc_tu`, `unipc_tq`, `unipc_snr_2`, `unipc_tu_2`. -- **In-Painting**: Specify areas of your image to paint into. +### Powerful image generation +- **Supports**: "*Text to Image*", "*Image to Image*" and "*InPainting*" +- **ControlNet**: For advanced control over the image, e.g. by setting the pose or drawing the outline for the AI to fill in. +- **16 Samplers**: `PLMS`, `DDIM`, `DEIS`, `Heun`, `Euler`, `Euler Ancestral`, `DPM2`, `DPM2 Ancestral`, `LMS`, `DPM Solver`, `DPM++ 2s Ancestral`, `DPM++ 2m`, `DPM++ 2m SDE`, `DPM++ SDE`, `DDPM`, `UniPC`. +- **Stable Diffusion XL and 2.1**: Generate higher-quality images using the latest Stable Diffusion XL models. +- **Textual Inversion Embeddings**: For guiding the AI strongly towards a particular concept. - **Simple Drawing Tool**: Draw basic images to guide the AI, without needing an external drawing program. - **Face Correction (GFPGAN)** - **Upscaling (RealESRGAN)** -- **Loopback**: Use the output image as the input image for the next img2img task. +- **Loopback**: Use the output image as the input image for the next image task. - **Negative Prompt**: Specify aspects of the image to *remove*. -- **Attention/Emphasis**: () in the prompt increases the model's attention to enclosed words, and [] decreases it. -- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `red:2.4 dragon:1.2`. +- **Attention/Emphasis**: `+` in the prompt increases the model's attention to enclosed words, and `-` decreases it. E.g. `apple++ falling from a tree`. +- **Weighted Prompts**: Use weights for specific words in your prompt to change their importance, e.g. `(red)2.4 (dragon)1.2`. - **Prompt Matrix**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut riding a horse | illustration | cinematic lighting`. - **Prompt Set**: Quickly create multiple variations of your prompt, e.g. `a photograph of an astronaut on the {moon,earth}` - **1-click Upscale/Face Correction**: Upscale or correct an image after it has been generated. @@ -79,10 +83,11 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. ### Advanced features - **Custom Models**: Use your own `.ckpt` or `.safetensors` file, by placing it inside the `models/stable-diffusion` folder! -- **Stable Diffusion 2.1 support** +- **Stable Diffusion XL and 2.1 support** - **Merge Models** - **Use custom VAE models** -- **Use pre-trained Hypernetworks** +- **Textual Inversion Embeddings** +- **ControlNet** - **Use custom GFPGAN models** - **UI Plugins**: Choose from a growing list of [community-generated UI plugins](https://github.com/easydiffusion/easydiffusion/wiki/UI-Plugins), or write your own plugin to add features to the project! @@ -100,18 +105,8 @@ Just delete the `EasyDiffusion` folder to uninstall all the downloaded packages. ---- -## Easy for new users: -![Screenshot of the initial UI](https://user-images.githubusercontent.com/844287/217043152-29454d15-0387-4228-b70d-9a4b84aeb8ba.png) - - -## Powerful features for advanced users: -![Screenshot of advanced settings](https://user-images.githubusercontent.com/844287/217042588-fc53c975-bacd-4a9c-af88-37408734ade3.png) - - -## Live Preview -Useful for judging (and stopping) an image quickly, without waiting for it to finish rendering. - -![live-512](https://user-images.githubusercontent.com/844287/192097249-729a0a1e-a677-485e-9ccc-16a9e848fabe.gif) +## Easy for new users, powerful features for advanced users: +![image](https://github.com/easydiffusion/easydiffusion/assets/844287/efbbac9f-42ce-4aef-8625-fd23c74a8241) ## Task Queue ![Screenshot of task queue](https://user-images.githubusercontent.com/844287/217043984-0b35f73b-1318-47cb-9eed-a2a91b430490.png) @@ -125,12 +120,6 @@ Please refer to our [guide](https://github.com/easydiffusion/easydiffusion/wiki/ # Bugs reports and code contributions welcome If there are any problems or suggestions, please feel free to ask on the [discord server](https://discord.com/invite/u9yhsFmEkB) or [file an issue](https://github.com/easydiffusion/easydiffusion/issues). -We could really use help on these aspects (click to view tasks that need your help): -* [User Interface](https://github.com/users/cmdr2/projects/1/views/1) -* [Engine](https://github.com/users/cmdr2/projects/3/views/1) -* [Installer](https://github.com/users/cmdr2/projects/4/views/1) -* [Documentation](https://github.com/users/cmdr2/projects/5/views/1) - If you have any code contributions in mind, please feel free to say Hi to us on the [discord server](https://discord.com/invite/u9yhsFmEkB). We use the Discord server for development-related discussions, and for helping users. # Credits From 1e13c4e808beeb2577c63940b96fd641802c32ab Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Wed, 23 Aug 2023 19:50:38 +0530 Subject: [PATCH 105/120] Support banner with kofi, patreon and itch.io links --- ui/index.html | 4 +++- ui/media/css/main.css | 10 ++++++++++ ui/media/js/main.js | 16 +++++++++++++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/ui/index.html b/ui/index.html index 4fd33886..8ce5f820 100644 --- a/ui/index.html +++ b/ui/index.html @@ -461,6 +461,9 @@
+
+ If you found this project useful and want to help keep it alive, please consider buying me a coffee or supporting me on Patreon to help cover the cost of development and maintenance! Or even better, purchasing it at the full price. Thank you for your support! +
@@ -789,7 +792,6 @@