mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-25 01:34:38 +01:00
commit
8ced5b7199
@ -22,6 +22,15 @@
|
||||
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.5.41 - 13 Jun 2023 - Fix multi-gpu bug with "low" VRAM usage mode while generating images.
|
||||
* 2.5.41 - 12 Jun 2023 - Fix multi-gpu bug with CodeFormer.
|
||||
* 2.5.41 - 6 Jun 2023 - Allow changing the strength of CodeFormer, and slightly improved styling of the CodeFormer options.
|
||||
* 2.5.41 - 5 Jun 2023 - Allow sharing an Easy Diffusion instance via https://try.cloudflare.com/ . You can find this option at the bottom of the Settings tab. Thanks @JeLuf.
|
||||
* 2.5.41 - 5 Jun 2023 - Show an option to download for tiled images. Shows a button on the generated image. Creates larger images by tiling them with the image generated by Easy Diffusion. Thanks @JeLuf.
|
||||
* 2.5.41 - 5 Jun 2023 - (beta-only) Allow LoRA strengths between -2 and 2. Thanks @ogmaresca.
|
||||
* 2.5.40 - 5 Jun 2023 - Reduce the VRAM usage of Latent Upscaling when using "balanced" VRAM usage mode.
|
||||
* 2.5.40 - 5 Jun 2023 - Fix the "realesrgan" key error when using CodeFormer with more than 1 image in a batch.
|
||||
* 2.5.40 - 3 Jun 2023 - Added CodeFormer as another option for fixing faces and eyes. CodeFormer tends to perform better than GFPGAN for many images. Thanks @patriceac for the implementation, and for contacting the CodeFormer team (who were supportive of it being integrated into Easy Diffusion).
|
||||
* 2.5.39 - 25 May 2023 - (beta-only) Seamless Tiling - make seamlessly tiled images, e.g. rock and grass textures. Thanks @JeLuf.
|
||||
* 2.5.38 - 24 May 2023 - Better reporting of errors, and show an explanation if the user cannot disable the "Use CPU" setting.
|
||||
* 2.5.38 - 23 May 2023 - Add Latent Upscaler as another option for upscaling images. Thanks @JeLuf for the implementation of the Latent Upscaler model.
|
||||
|
@ -1,101 +0,0 @@
|
||||
# this script runs inside the legacy "stable-diffusion" folder
|
||||
|
||||
from sdkit.models import download_model, get_model_info_from_db
|
||||
from sdkit.utils import hash_file_quick
|
||||
|
||||
import os
|
||||
import shutil
|
||||
from glob import glob
|
||||
import traceback
|
||||
|
||||
models_base_dir = os.path.abspath(os.path.join("..", "models"))
|
||||
|
||||
models_to_check = {
|
||||
"stable-diffusion": [
|
||||
{"file_name": "sd-v1-4.ckpt", "model_id": "1.4"},
|
||||
],
|
||||
"gfpgan": [
|
||||
{"file_name": "GFPGANv1.4.pth", "model_id": "1.4"},
|
||||
],
|
||||
"realesrgan": [
|
||||
{"file_name": "RealESRGAN_x4plus.pth", "model_id": "x4plus"},
|
||||
{"file_name": "RealESRGAN_x4plus_anime_6B.pth", "model_id": "x4plus_anime_6"},
|
||||
],
|
||||
"vae": [
|
||||
{"file_name": "vae-ft-mse-840000-ema-pruned.ckpt", "model_id": "vae-ft-mse-840000-ema-pruned"},
|
||||
],
|
||||
}
|
||||
MODEL_EXTENSIONS = { # copied from easydiffusion/model_manager.py
|
||||
"stable-diffusion": [".ckpt", ".safetensors"],
|
||||
"vae": [".vae.pt", ".ckpt", ".safetensors"],
|
||||
"hypernetwork": [".pt", ".safetensors"],
|
||||
"gfpgan": [".pth"],
|
||||
"realesrgan": [".pth"],
|
||||
"lora": [".ckpt", ".safetensors"],
|
||||
}
|
||||
|
||||
|
||||
def download_if_necessary(model_type: str, file_name: str, model_id: str):
|
||||
model_path = os.path.join(models_base_dir, model_type, file_name)
|
||||
expected_hash = get_model_info_from_db(model_type=model_type, model_id=model_id)["quick_hash"]
|
||||
|
||||
other_models_exist = any_model_exists(model_type)
|
||||
known_model_exists = os.path.exists(model_path)
|
||||
known_model_is_corrupt = known_model_exists and hash_file_quick(model_path) != expected_hash
|
||||
|
||||
if known_model_is_corrupt or (not other_models_exist and not known_model_exists):
|
||||
print("> download", model_type, model_id)
|
||||
download_model(model_type, model_id, download_base_dir=models_base_dir)
|
||||
|
||||
|
||||
def init():
|
||||
migrate_legacy_model_location()
|
||||
|
||||
for model_type, models in models_to_check.items():
|
||||
for model in models:
|
||||
try:
|
||||
download_if_necessary(model_type, model["file_name"], model["model_id"])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
fail(model_type)
|
||||
|
||||
print(model_type, "model(s) found.")
|
||||
|
||||
|
||||
### utilities
|
||||
def any_model_exists(model_type: str) -> bool:
|
||||
extensions = MODEL_EXTENSIONS.get(model_type, [])
|
||||
for ext in extensions:
|
||||
if any(glob(f"{models_base_dir}/{model_type}/**/*{ext}", recursive=True)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def migrate_legacy_model_location():
|
||||
'Move the models inside the legacy "stable-diffusion" folder, to their respective folders'
|
||||
|
||||
for model_type, models in models_to_check.items():
|
||||
for model in models:
|
||||
file_name = model["file_name"]
|
||||
if os.path.exists(file_name):
|
||||
dest_dir = os.path.join(models_base_dir, model_type)
|
||||
os.makedirs(dest_dir, exist_ok=True)
|
||||
shutil.move(file_name, os.path.join(dest_dir, file_name))
|
||||
|
||||
|
||||
def fail(model_name):
|
||||
print(
|
||||
f"""Error downloading the {model_name} model. Sorry about that, please try to:
|
||||
1. Run this installer again.
|
||||
2. If that doesn't fix it, please try to download the file manually. The address to download from, and the destination to save to are printed above this message.
|
||||
3. If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB
|
||||
4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues
|
||||
Thanks!"""
|
||||
)
|
||||
exit(1)
|
||||
|
||||
|
||||
### start
|
||||
|
||||
init()
|
@ -18,13 +18,15 @@ 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.98",
|
||||
"sdkit": "1.0.106",
|
||||
"stable-diffusion-sdkit": "2.1.4",
|
||||
"rich": "12.6.0",
|
||||
"uvicorn": "0.19.0",
|
||||
"fastapi": "0.85.1",
|
||||
"pycloudflared": "0.2.0",
|
||||
# "xformers": "0.0.16",
|
||||
}
|
||||
modules_to_log = ["torch", "torchvision", "sdkit", "stable-diffusion-sdkit"]
|
||||
|
||||
|
||||
def version(module_name: str) -> str:
|
||||
@ -89,7 +91,8 @@ def init():
|
||||
traceback.print_exc()
|
||||
fail(module_name)
|
||||
|
||||
print(f"{module_name}: {version(module_name)}")
|
||||
if module_name in modules_to_log:
|
||||
print(f"{module_name}: {version(module_name)}")
|
||||
|
||||
|
||||
### utilities
|
||||
|
@ -39,6 +39,8 @@ if [ "$0" == "bash" ]; then
|
||||
export PYTHONPATH="$(pwd)/stable-diffusion/env/lib/python3.8/site-packages"
|
||||
fi
|
||||
|
||||
export PYTHONNOUSERSITE=y
|
||||
|
||||
which python
|
||||
python --version
|
||||
|
||||
|
@ -67,7 +67,6 @@ if "%update_branch%"=="" (
|
||||
@xcopy sd-ui-files\ui ui /s /i /Y /q
|
||||
@copy sd-ui-files\scripts\on_sd_start.bat scripts\ /Y
|
||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
||||
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||
@copy "sd-ui-files\scripts\Start Stable Diffusion UI.cmd" . /Y
|
||||
@copy "sd-ui-files\scripts\Developer Console.cmd" . /Y
|
||||
|
@ -50,7 +50,6 @@ cp -Rf sd-ui-files/ui .
|
||||
cp sd-ui-files/scripts/on_sd_start.sh scripts/
|
||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||
cp sd-ui-files/scripts/check_models.py scripts/
|
||||
cp sd-ui-files/scripts/get_config.py scripts/
|
||||
cp sd-ui-files/scripts/start.sh .
|
||||
cp sd-ui-files/scripts/developer_console.sh .
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
@copy sd-ui-files\scripts\on_env_start.bat scripts\ /Y
|
||||
@copy sd-ui-files\scripts\check_modules.py scripts\ /Y
|
||||
@copy sd-ui-files\scripts\check_models.py scripts\ /Y
|
||||
@copy sd-ui-files\scripts\get_config.py scripts\ /Y
|
||||
|
||||
if exist "%cd%\profile" (
|
||||
@ -79,13 +78,6 @@ call WHERE uvicorn > .tmp
|
||||
@echo conda_sd_ui_deps_installed >> ..\scripts\install_status.txt
|
||||
)
|
||||
|
||||
@rem Download the required models
|
||||
call python ..\scripts\check_models.py
|
||||
if "%ERRORLEVEL%" NEQ "0" (
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
|
||||
@>nul findstr /m "sd_install_complete" ..\scripts\install_status.txt
|
||||
@if "%ERRORLEVEL%" NEQ "0" (
|
||||
@echo sd_weights_downloaded >> ..\scripts\install_status.txt
|
||||
|
@ -4,7 +4,6 @@ cp sd-ui-files/scripts/functions.sh scripts/
|
||||
cp sd-ui-files/scripts/on_env_start.sh scripts/
|
||||
cp sd-ui-files/scripts/bootstrap.sh scripts/
|
||||
cp sd-ui-files/scripts/check_modules.py scripts/
|
||||
cp sd-ui-files/scripts/check_models.py scripts/
|
||||
cp sd-ui-files/scripts/get_config.py scripts/
|
||||
|
||||
source ./scripts/functions.sh
|
||||
@ -51,12 +50,6 @@ if ! command -v uvicorn &> /dev/null; then
|
||||
fail "UI packages not found!"
|
||||
fi
|
||||
|
||||
# Download the required models
|
||||
if ! python ../scripts/check_models.py; then
|
||||
read -p "Press any key to continue"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ `grep -c sd_install_complete ../scripts/install_status.txt` -gt "0" ]; then
|
||||
echo sd_weights_downloaded >> ../scripts/install_status.txt
|
||||
echo sd_install_complete >> ../scripts/install_status.txt
|
||||
|
@ -90,8 +90,8 @@ def init():
|
||||
os.makedirs(USER_SERVER_PLUGINS_DIR, exist_ok=True)
|
||||
|
||||
# https://pytorch.org/docs/stable/storage.html
|
||||
warnings.filterwarnings('ignore', category=UserWarning, message='TypedStorage is deprecated')
|
||||
|
||||
warnings.filterwarnings("ignore", category=UserWarning, message="TypedStorage is deprecated")
|
||||
|
||||
load_server_plugins()
|
||||
|
||||
update_render_threads()
|
||||
@ -221,12 +221,41 @@ def open_browser():
|
||||
|
||||
webbrowser.open(f"http://localhost:{port}")
|
||||
|
||||
Console().print(Panel(
|
||||
"\n" +
|
||||
"[white]Easy Diffusion is ready to serve requests.\n\n" +
|
||||
"A new browser tab should have been opened by now.\n" +
|
||||
f"If not, please open your web browser and navigate to [bold yellow underline]http://localhost:{port}/\n",
|
||||
title="Easy Diffusion is ready", style="bold yellow on blue"))
|
||||
Console().print(
|
||||
Panel(
|
||||
"\n"
|
||||
+ "[white]Easy Diffusion is ready to serve requests.\n\n"
|
||||
+ "A new browser tab should have been opened by now.\n"
|
||||
+ f"If not, please open your web browser and navigate to [bold yellow underline]http://localhost:{port}/\n",
|
||||
title="Easy Diffusion is ready",
|
||||
style="bold yellow on blue",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def fail_and_die(fail_type: str, data: str):
|
||||
suggestions = [
|
||||
"Run this installer again.",
|
||||
"If those steps don't help, please copy *all* the error messages in this window, and ask the community at https://discord.com/invite/u9yhsFmEkB",
|
||||
"If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues",
|
||||
]
|
||||
|
||||
if fail_type == "model_download":
|
||||
fail_label = f"Error downloading the {data} model"
|
||||
suggestions.insert(
|
||||
1,
|
||||
"If that doesn't fix it, please try to download the file manually. The address to download from, and the destination to save to are printed above this message.",
|
||||
)
|
||||
else:
|
||||
fail_label = "Error while installing Easy Diffusion"
|
||||
|
||||
msg = [f"{fail_label}. Sorry about that, please try to:"]
|
||||
for i, suggestion in enumerate(suggestions):
|
||||
msg.append(f"{i+1}. {suggestion}")
|
||||
msg.append("Thanks!")
|
||||
|
||||
print("\n".join(msg))
|
||||
exit(1)
|
||||
|
||||
|
||||
def get_image_modifiers():
|
||||
|
@ -1,10 +1,14 @@
|
||||
import os
|
||||
import shutil
|
||||
from glob import glob
|
||||
import traceback
|
||||
|
||||
from easydiffusion import app
|
||||
from easydiffusion.types import TaskData
|
||||
from easydiffusion.utils import log
|
||||
from sdkit import Context
|
||||
from sdkit.models import load_model, scan_model, unload_model
|
||||
from sdkit.models import load_model, scan_model, unload_model, download_model, get_model_info_from_db
|
||||
from sdkit.utils import hash_file_quick
|
||||
|
||||
KNOWN_MODEL_TYPES = [
|
||||
"stable-diffusion",
|
||||
@ -13,6 +17,7 @@ KNOWN_MODEL_TYPES = [
|
||||
"gfpgan",
|
||||
"realesrgan",
|
||||
"lora",
|
||||
"codeformer",
|
||||
]
|
||||
MODEL_EXTENSIONS = {
|
||||
"stable-diffusion": [".ckpt", ".safetensors"],
|
||||
@ -21,14 +26,22 @@ MODEL_EXTENSIONS = {
|
||||
"gfpgan": [".pth"],
|
||||
"realesrgan": [".pth"],
|
||||
"lora": [".ckpt", ".safetensors"],
|
||||
"codeformer": [".pth"],
|
||||
}
|
||||
DEFAULT_MODELS = {
|
||||
"stable-diffusion": [ # needed to support the legacy installations
|
||||
"custom-model", # only one custom model file was supported initially, creatively named 'custom-model'
|
||||
"sd-v1-4", # Default fallback.
|
||||
"stable-diffusion": [
|
||||
{"file_name": "sd-v1-4.ckpt", "model_id": "1.4"},
|
||||
],
|
||||
"gfpgan": [
|
||||
{"file_name": "GFPGANv1.4.pth", "model_id": "1.4"},
|
||||
],
|
||||
"realesrgan": [
|
||||
{"file_name": "RealESRGAN_x4plus.pth", "model_id": "x4plus"},
|
||||
{"file_name": "RealESRGAN_x4plus_anime_6B.pth", "model_id": "x4plus_anime_6"},
|
||||
],
|
||||
"vae": [
|
||||
{"file_name": "vae-ft-mse-840000-ema-pruned.ckpt", "model_id": "vae-ft-mse-840000-ema-pruned"},
|
||||
],
|
||||
"gfpgan": ["GFPGANv1.3"],
|
||||
"realesrgan": ["RealESRGAN_x4plus"],
|
||||
}
|
||||
MODELS_TO_LOAD_ON_START = ["stable-diffusion", "vae", "hypernetwork", "lora"]
|
||||
|
||||
@ -37,6 +50,8 @@ known_models = {}
|
||||
|
||||
def init():
|
||||
make_model_folders()
|
||||
migrate_legacy_model_location() # if necessary
|
||||
download_default_models_if_necessary()
|
||||
getModels() # run this once, to cache the picklescan results
|
||||
|
||||
|
||||
@ -45,7 +60,7 @@ def load_default_models(context: Context):
|
||||
|
||||
# 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)
|
||||
context.model_paths[model_type] = resolve_model_to_use(model_type=model_type, fail_if_not_found=False)
|
||||
try:
|
||||
load_model(
|
||||
context,
|
||||
@ -57,7 +72,12 @@ def load_default_models(context: Context):
|
||||
del context.model_load_errors[model_type]
|
||||
except Exception as e:
|
||||
log.error(f"[red]Error while loading {model_type} model: {context.model_paths[model_type]}[/red]")
|
||||
log.exception(e)
|
||||
if "DefaultCPUAllocator: not enough memory" in str(e):
|
||||
log.error(
|
||||
f"[red]Your PC is low on system RAM. Please add some virtual memory (or swap space) by following the instructions at this link: https://www.ibm.com/docs/en/opw/8.2.0?topic=tuning-optional-increasing-paging-file-size-windows-computers[/red]"
|
||||
)
|
||||
else:
|
||||
log.exception(e)
|
||||
del context.model_paths[model_type]
|
||||
|
||||
context.model_load_errors[model_type] = str(e) # storing the entire Exception can lead to memory leaks
|
||||
@ -70,12 +90,12 @@ def unload_all(context: Context):
|
||||
del context.model_load_errors[model_type]
|
||||
|
||||
|
||||
def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
||||
def resolve_model_to_use(model_name: str = None, model_type: str = None, fail_if_not_found: bool = True):
|
||||
model_extensions = MODEL_EXTENSIONS.get(model_type, [])
|
||||
default_models = DEFAULT_MODELS.get(model_type, [])
|
||||
config = app.getConfig()
|
||||
|
||||
model_dirs = [os.path.join(app.MODELS_DIR, model_type), app.SD_DIR]
|
||||
model_dir = os.path.join(app.MODELS_DIR, model_type)
|
||||
if not model_name: # When None try user configured model.
|
||||
# config = getConfig()
|
||||
if "model" in config and model_type in config["model"]:
|
||||
@ -83,45 +103,42 @@ def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
||||
|
||||
if model_name:
|
||||
# Check models directory
|
||||
models_dir_path = os.path.join(app.MODELS_DIR, model_type, model_name)
|
||||
model_path = os.path.join(model_dir, model_name)
|
||||
if os.path.exists(model_path):
|
||||
return model_path
|
||||
for model_extension in model_extensions:
|
||||
if os.path.exists(models_dir_path + model_extension):
|
||||
return models_dir_path + model_extension
|
||||
if os.path.exists(model_path + model_extension):
|
||||
return model_path + model_extension
|
||||
if os.path.exists(model_name + model_extension):
|
||||
return os.path.abspath(model_name + model_extension)
|
||||
|
||||
# Default locations
|
||||
if model_name in default_models:
|
||||
default_model_path = os.path.join(app.SD_DIR, model_name)
|
||||
for model_extension in model_extensions:
|
||||
if os.path.exists(default_model_path + model_extension):
|
||||
return default_model_path + model_extension
|
||||
|
||||
# Can't find requested model, check the default paths.
|
||||
for default_model in default_models:
|
||||
for model_dir in model_dirs:
|
||||
default_model_path = os.path.join(model_dir, default_model)
|
||||
for model_extension in model_extensions:
|
||||
if os.path.exists(default_model_path + model_extension):
|
||||
if model_name is not None:
|
||||
log.warn(
|
||||
f"Could not find the configured custom model {model_name}{model_extension}. Using the default one: {default_model_path}{model_extension}"
|
||||
)
|
||||
return default_model_path + model_extension
|
||||
if model_type == "stable-diffusion" and not fail_if_not_found:
|
||||
for default_model in default_models:
|
||||
default_model_path = os.path.join(model_dir, default_model["file_name"])
|
||||
if os.path.exists(default_model_path):
|
||||
if model_name is not None:
|
||||
log.warn(
|
||||
f"Could not find the configured custom model {model_name}. Using the default one: {default_model_path}"
|
||||
)
|
||||
return default_model_path
|
||||
|
||||
return None
|
||||
if model_name and fail_if_not_found:
|
||||
raise Exception(f"Could not find the desired model {model_name}! Is it present in the {model_dir} folder?")
|
||||
|
||||
|
||||
def reload_models_if_necessary(context: Context, task_data: TaskData):
|
||||
use_upscale_lower = task_data.use_upscale.lower() if task_data.use_upscale else ""
|
||||
face_fix_lower = task_data.use_face_correction.lower() if task_data.use_face_correction else ""
|
||||
upscale_lower = task_data.use_upscale.lower() if task_data.use_upscale else ""
|
||||
|
||||
model_paths_in_req = {
|
||||
"stable-diffusion": task_data.use_stable_diffusion_model,
|
||||
"vae": task_data.use_vae_model,
|
||||
"hypernetwork": task_data.use_hypernetwork_model,
|
||||
"gfpgan": task_data.use_face_correction,
|
||||
"realesrgan": task_data.use_upscale if "realesrgan" in use_upscale_lower else None,
|
||||
"latent_upscaler": True if task_data.use_upscale == "latent_upscaler" else None,
|
||||
"codeformer": task_data.use_face_correction if "codeformer" in face_fix_lower else None,
|
||||
"gfpgan": task_data.use_face_correction if "gfpgan" in face_fix_lower else None,
|
||||
"realesrgan": task_data.use_upscale if "realesrgan" in upscale_lower else None,
|
||||
"latent_upscaler": True if "latent_upscaler" in upscale_lower else None,
|
||||
"nsfw_checker": True if task_data.block_nsfw else None,
|
||||
"lora": task_data.use_lora_model,
|
||||
}
|
||||
@ -131,6 +148,13 @@ def reload_models_if_necessary(context: Context, task_data: TaskData):
|
||||
if context.model_paths.get(model_type) != path
|
||||
}
|
||||
|
||||
if task_data.codeformer_upscale_faces:
|
||||
if "realesrgan" not in models_to_reload and "realesrgan" not in context.models:
|
||||
default_realesrgan = DEFAULT_MODELS["realesrgan"][0]["file_name"]
|
||||
models_to_reload["realesrgan"] = resolve_model_to_use(default_realesrgan, "realesrgan")
|
||||
elif "realesrgan" in models_to_reload and models_to_reload["realesrgan"] is None:
|
||||
del models_to_reload["realesrgan"] # don't unload realesrgan
|
||||
|
||||
if set_vram_optimizations(context) or set_clip_skip(context, task_data): # reload SD
|
||||
models_to_reload["stable-diffusion"] = model_paths_in_req["stable-diffusion"]
|
||||
|
||||
@ -157,7 +181,13 @@ def resolve_model_paths(task_data: TaskData):
|
||||
task_data.use_lora_model = resolve_model_to_use(task_data.use_lora_model, model_type="lora")
|
||||
|
||||
if task_data.use_face_correction:
|
||||
task_data.use_face_correction = resolve_model_to_use(task_data.use_face_correction, "gfpgan")
|
||||
if "gfpgan" in task_data.use_face_correction.lower():
|
||||
model_type = "gfpgan"
|
||||
elif "codeformer" in task_data.use_face_correction.lower():
|
||||
model_type = "codeformer"
|
||||
download_if_necessary("codeformer", "codeformer.pth", "codeformer-0.1.0")
|
||||
|
||||
task_data.use_face_correction = resolve_model_to_use(task_data.use_face_correction, model_type)
|
||||
if task_data.use_upscale and "realesrgan" in task_data.use_upscale.lower():
|
||||
task_data.use_upscale = resolve_model_to_use(task_data.use_upscale, "realesrgan")
|
||||
|
||||
@ -167,7 +197,31 @@ def fail_if_models_did_not_load(context: Context):
|
||||
if model_type in context.model_load_errors:
|
||||
e = context.model_load_errors[model_type]
|
||||
raise Exception(f"Could not load the {model_type} model! Reason: " + e)
|
||||
# concat 'e', don't use in format string (injection attack)
|
||||
|
||||
|
||||
def download_default_models_if_necessary():
|
||||
for model_type, models in DEFAULT_MODELS.items():
|
||||
for model in models:
|
||||
try:
|
||||
download_if_necessary(model_type, model["file_name"], model["model_id"])
|
||||
except:
|
||||
traceback.print_exc()
|
||||
app.fail_and_die(fail_type="model_download", data=model_type)
|
||||
|
||||
print(model_type, "model(s) found.")
|
||||
|
||||
|
||||
def download_if_necessary(model_type: str, file_name: str, model_id: str):
|
||||
model_path = os.path.join(app.MODELS_DIR, model_type, file_name)
|
||||
expected_hash = get_model_info_from_db(model_type=model_type, model_id=model_id)["quick_hash"]
|
||||
|
||||
other_models_exist = any_model_exists(model_type)
|
||||
known_model_exists = os.path.exists(model_path)
|
||||
known_model_is_corrupt = known_model_exists and hash_file_quick(model_path) != expected_hash
|
||||
|
||||
if known_model_is_corrupt or (not other_models_exist and not known_model_exists):
|
||||
print("> download", model_type, model_id)
|
||||
download_model(model_type, model_id, download_base_dir=app.MODELS_DIR)
|
||||
|
||||
|
||||
def set_vram_optimizations(context: Context):
|
||||
@ -181,6 +235,26 @@ def set_vram_optimizations(context: Context):
|
||||
return False
|
||||
|
||||
|
||||
def migrate_legacy_model_location():
|
||||
'Move the models inside the legacy "stable-diffusion" folder, to their respective folders'
|
||||
|
||||
for model_type, models in DEFAULT_MODELS.items():
|
||||
for model in models:
|
||||
file_name = model["file_name"]
|
||||
legacy_path = os.path.join(app.SD_DIR, file_name)
|
||||
if os.path.exists(legacy_path):
|
||||
shutil.move(legacy_path, os.path.join(app.MODELS_DIR, model_type, file_name))
|
||||
|
||||
|
||||
def any_model_exists(model_type: str) -> bool:
|
||||
extensions = MODEL_EXTENSIONS.get(model_type, [])
|
||||
for ext in extensions:
|
||||
if any(glob(f"{app.MODELS_DIR}/{model_type}/**/*{ext}", recursive=True)):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def set_clip_skip(context: Context, task_data: TaskData):
|
||||
clip_skip = task_data.clip_skip
|
||||
|
||||
@ -238,17 +312,12 @@ def is_malicious_model(file_path):
|
||||
|
||||
def getModels():
|
||||
models = {
|
||||
"active": {
|
||||
"stable-diffusion": "sd-v1-4",
|
||||
"vae": "",
|
||||
"hypernetwork": "",
|
||||
"lora": "",
|
||||
},
|
||||
"options": {
|
||||
"stable-diffusion": ["sd-v1-4"],
|
||||
"vae": [],
|
||||
"hypernetwork": [],
|
||||
"lora": [],
|
||||
"codeformer": ["codeformer"],
|
||||
},
|
||||
}
|
||||
|
||||
@ -309,9 +378,4 @@ def getModels():
|
||||
if models_scanned > 0:
|
||||
log.info(f"[green]Scanned {models_scanned} models. Nothing infected[/]")
|
||||
|
||||
# legacy
|
||||
custom_weight_path = os.path.join(app.SD_DIR, "custom-model.ckpt")
|
||||
if os.path.exists(custom_weight_path):
|
||||
models["options"]["stable-diffusion"].append("custom-model")
|
||||
|
||||
return models
|
||||
|
@ -7,10 +7,12 @@ from easydiffusion import device_manager
|
||||
from easydiffusion.types import GenerateImageRequest
|
||||
from easydiffusion.types import Image as ResponseImage
|
||||
from easydiffusion.types import Response, TaskData, UserInitiatedStop
|
||||
from easydiffusion.model_manager import DEFAULT_MODELS, resolve_model_to_use
|
||||
from easydiffusion.utils import get_printable_request, log, save_images_to_disk
|
||||
from sdkit import Context
|
||||
from sdkit.filter import apply_filters
|
||||
from sdkit.generate import generate_images
|
||||
from sdkit.models import load_model
|
||||
from sdkit.utils import (
|
||||
diffusers_latent_samples_to_images,
|
||||
gc,
|
||||
@ -34,6 +36,7 @@ def init(device):
|
||||
context.temp_images = {}
|
||||
context.partial_x_samples = None
|
||||
context.model_load_errors = {}
|
||||
context.enable_codeformer = True
|
||||
|
||||
from easydiffusion import app
|
||||
|
||||
@ -156,32 +159,51 @@ def filter_images(req: GenerateImageRequest, task_data: TaskData, images: list,
|
||||
if user_stopped:
|
||||
return images
|
||||
|
||||
filters_to_apply = []
|
||||
filter_params = {}
|
||||
if task_data.block_nsfw:
|
||||
filters_to_apply.append("nsfw_checker")
|
||||
if task_data.use_face_correction and "gfpgan" in task_data.use_face_correction.lower():
|
||||
filters_to_apply.append("gfpgan")
|
||||
images = apply_filters(context, "nsfw_checker", images)
|
||||
|
||||
if task_data.use_face_correction and "codeformer" in task_data.use_face_correction.lower():
|
||||
default_realesrgan = DEFAULT_MODELS["realesrgan"][0]["file_name"]
|
||||
prev_realesrgan_path = None
|
||||
if task_data.codeformer_upscale_faces and default_realesrgan not in context.model_paths["realesrgan"]:
|
||||
prev_realesrgan_path = context.model_paths["realesrgan"]
|
||||
context.model_paths["realesrgan"] = resolve_model_to_use(default_realesrgan, "realesrgan")
|
||||
load_model(context, "realesrgan")
|
||||
|
||||
try:
|
||||
images = apply_filters(
|
||||
context,
|
||||
"codeformer",
|
||||
images,
|
||||
upscale_faces=task_data.codeformer_upscale_faces,
|
||||
codeformer_fidelity=task_data.codeformer_fidelity,
|
||||
)
|
||||
finally:
|
||||
if prev_realesrgan_path:
|
||||
context.model_paths["realesrgan"] = prev_realesrgan_path
|
||||
load_model(context, "realesrgan")
|
||||
elif task_data.use_face_correction and "gfpgan" in task_data.use_face_correction.lower():
|
||||
images = apply_filters(context, "gfpgan", images)
|
||||
|
||||
if task_data.use_upscale:
|
||||
if "realesrgan" in task_data.use_upscale.lower():
|
||||
filters_to_apply.append("realesrgan")
|
||||
images = apply_filters(context, "realesrgan", images, scale=task_data.upscale_amount)
|
||||
elif task_data.use_upscale == "latent_upscaler":
|
||||
filters_to_apply.append("latent_upscaler")
|
||||
images = apply_filters(
|
||||
context,
|
||||
"latent_upscaler",
|
||||
images,
|
||||
scale=task_data.upscale_amount,
|
||||
latent_upscaler_options={
|
||||
"prompt": req.prompt,
|
||||
"negative_prompt": req.negative_prompt,
|
||||
"seed": req.seed,
|
||||
"num_inference_steps": task_data.latent_upscaler_steps,
|
||||
"guidance_scale": 0,
|
||||
},
|
||||
)
|
||||
|
||||
filter_params["latent_upscaler_options"] = {
|
||||
"prompt": req.prompt,
|
||||
"negative_prompt": req.negative_prompt,
|
||||
"seed": req.seed,
|
||||
"num_inference_steps": task_data.latent_upscaler_steps,
|
||||
"guidance_scale": 0,
|
||||
}
|
||||
|
||||
filter_params["scale"] = task_data.upscale_amount
|
||||
|
||||
if len(filters_to_apply) == 0:
|
||||
return images
|
||||
|
||||
return apply_filters(context, filters_to_apply, images, **filter_params)
|
||||
return images
|
||||
|
||||
|
||||
def construct_response(images: list, seeds: list, task_data: TaskData, base_seed: int):
|
||||
|
@ -15,6 +15,7 @@ from fastapi import FastAPI, HTTPException
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel, Extra
|
||||
from starlette.responses import FileResponse, JSONResponse, StreamingResponse
|
||||
from pycloudflared import try_cloudflare
|
||||
|
||||
log.info(f"started in {app.SD_DIR}")
|
||||
log.info(f"started at {datetime.datetime.now():%x %X}")
|
||||
@ -113,6 +114,14 @@ def init():
|
||||
def get_image(task_id: int, img_id: int):
|
||||
return get_image_internal(task_id, img_id)
|
||||
|
||||
@server_api.post("/tunnel/cloudflare/start")
|
||||
def start_cloudflare_tunnel(req: dict):
|
||||
return start_cloudflare_tunnel_internal(req)
|
||||
|
||||
@server_api.post("/tunnel/cloudflare/stop")
|
||||
def stop_cloudflare_tunnel(req: dict):
|
||||
return stop_cloudflare_tunnel_internal(req)
|
||||
|
||||
@server_api.get("/")
|
||||
def read_root():
|
||||
return FileResponse(os.path.join(app.SD_UI_DIR, "index.html"), headers=NOCACHE_HEADERS)
|
||||
@ -211,6 +220,8 @@ def ping_internal(session_id: str = None):
|
||||
session = task_manager.get_cached_session(session_id, update_ttl=True)
|
||||
response["tasks"] = {id(t): t.status for t in session.tasks}
|
||||
response["devices"] = task_manager.get_devices()
|
||||
if cloudflare.address != None:
|
||||
response["cloudflare"] = cloudflare.address
|
||||
return JSONResponse(response, headers=NOCACHE_HEADERS)
|
||||
|
||||
|
||||
@ -322,3 +333,47 @@ def get_image_internal(task_id: int, img_id: int):
|
||||
return StreamingResponse(img_data, media_type="image/jpeg")
|
||||
except KeyError as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
#---- Cloudflare Tunnel ----
|
||||
class CloudflareTunnel:
|
||||
def __init__(self):
|
||||
config = app.getConfig()
|
||||
self.urls = None
|
||||
self.port = config.get("net", {}).get("listen_port")
|
||||
|
||||
def start(self):
|
||||
if self.port:
|
||||
self.urls = try_cloudflare(self.port)
|
||||
|
||||
def stop(self):
|
||||
if self.urls:
|
||||
try_cloudflare.terminate(self.port)
|
||||
self.urls = None
|
||||
|
||||
@property
|
||||
def address(self):
|
||||
if self.urls:
|
||||
return self.urls.tunnel
|
||||
else:
|
||||
return None
|
||||
|
||||
cloudflare = CloudflareTunnel()
|
||||
|
||||
def start_cloudflare_tunnel_internal(req: dict):
|
||||
try:
|
||||
cloudflare.start()
|
||||
log.info(f"- Started cloudflare tunnel. Using address: {cloudflare.address}")
|
||||
return JSONResponse({"address":cloudflare.address})
|
||||
except Exception as e:
|
||||
log.error(str(e))
|
||||
log.error(traceback.format_exc())
|
||||
return HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
def stop_cloudflare_tunnel_internal(req: dict):
|
||||
try:
|
||||
cloudflare.stop()
|
||||
except Exception as e:
|
||||
log.error(str(e))
|
||||
log.error(traceback.format_exc())
|
||||
return HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
@ -23,7 +23,7 @@ class GenerateImageRequest(BaseModel):
|
||||
sampler_name: str = None # "ddim", "plms", "heun", "euler", "euler_a", "dpm2", "dpm2_a", "lms"
|
||||
hypernetwork_strength: float = 0
|
||||
lora_alpha: float = 0
|
||||
tiling: str = "none" # "none", "x", "y", "xy"
|
||||
tiling: str = "none" # "none", "x", "y", "xy"
|
||||
|
||||
|
||||
class TaskData(BaseModel):
|
||||
@ -51,6 +51,8 @@ class TaskData(BaseModel):
|
||||
stream_image_progress: bool = False
|
||||
stream_image_progress_interval: int = 5
|
||||
clip_skip: bool = False
|
||||
codeformer_upscale_faces: bool = False
|
||||
codeformer_fidelity: float = 0.5
|
||||
|
||||
|
||||
class MergeRequest(BaseModel):
|
||||
|
@ -30,7 +30,7 @@
|
||||
<h1>
|
||||
<img id="logo_img" src="/media/images/icon-512x512.png" >
|
||||
Easy Diffusion
|
||||
<small>v2.5.39 <span id="updateBranchLabel"></span></small>
|
||||
<small>v2.5.41 <span id="updateBranchLabel"></span></small>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="server-status">
|
||||
@ -227,7 +227,10 @@
|
||||
</td></tr>
|
||||
<tr id="lora_alpha_container" class="pl-5">
|
||||
<td><label for="lora_alpha_slider">LoRA Strength:</label></td>
|
||||
<td> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="50" type="range" min="0" max="100"> <input id="lora_alpha" name="lora_alpha" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"><br/></td>
|
||||
<td>
|
||||
<small>-2</small> <input id="lora_alpha_slider" name="lora_alpha_slider" class="editor-slider" value="50" type="range" min="-200" max="200"> <small>2</small>
|
||||
<input id="lora_alpha" name="lora_alpha" size="4" pattern="^-?[0-9]*\.?[0-9]*$" onkeypress="preventNonNumericalInput(event)"><br/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pl-5"><td><label for="hypernetwork_model">Hypernetwork:</label></td><td>
|
||||
<input id="hypernetwork_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" />
|
||||
@ -263,7 +266,13 @@
|
||||
<div><ul>
|
||||
<li><b class="settings-subheader">Render Settings</b></li>
|
||||
<li class="pl-5"><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview <small>(uses more VRAM, slower images)</small></label></li>
|
||||
<li class="pl-5"><input id="use_face_correction" name="use_face_correction" type="checkbox"> <label for="use_face_correction">Fix incorrect faces and eyes</label> <div style="display:inline-block;"><input id="gfpgan_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /></div></li>
|
||||
<li class="pl-5" id="use_face_correction_container">
|
||||
<input id="use_face_correction" name="use_face_correction" type="checkbox"> <label for="use_face_correction">Fix incorrect faces and eyes</label> <div style="display:inline-block;"><input id="gfpgan_model" type="text" spellcheck="false" autocomplete="off" class="model-filter" data-path="" /></div>
|
||||
<table id="codeformer_settings" class="displayNone sub-settings">
|
||||
<tr class="pl-5"><td><label for="codeformer_fidelity_slider">Strength:</label></td><td><input id="codeformer_fidelity_slider" name="codeformer_fidelity_slider" class="editor-slider" value="5" type="range" min="0" max="10"> <input id="codeformer_fidelity" name="codeformer_fidelity" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||
<tr class="pl-5"><td><label for="codeformer_upscale_faces">Upscale Faces:</label></td><td><input id="codeformer_upscale_faces" name="codeformer_upscale_faces" type="checkbox" checked> <label><small>(improves the resolution of faces)</small></label></td></tr>
|
||||
</table>
|
||||
</li>
|
||||
<li class="pl-5">
|
||||
<input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Scale up by</label>
|
||||
<select id="upscale_amount" name="upscale_amount">
|
||||
@ -276,9 +285,9 @@
|
||||
<option value="RealESRGAN_x4plus_anime_6B">RealESRGAN_x4plus_anime_6B</option>
|
||||
<option value="latent_upscaler">Latent Upscaler 2x</option>
|
||||
</select>
|
||||
<div id="latent_upscaler_settings" class="displayNone">
|
||||
<label for="latent_upscaler_steps_slider">Upscaling Steps:</label></td><td> <input id="latent_upscaler_steps_slider" name="latent_upscaler_steps_slider" class="editor-slider" value="10" type="range" min="1" max="50"> <input id="latent_upscaler_steps" name="latent_upscaler_steps" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)">
|
||||
</div>
|
||||
<table id="latent_upscaler_settings" class="displayNone sub-settings">
|
||||
<tr class="pl-5"><td><label for="latent_upscaler_steps_slider">Upscaling Steps:</label></td><td><input id="latent_upscaler_steps_slider" name="latent_upscaler_steps_slider" class="editor-slider" value="10" type="range" min="1" max="50"> <input id="latent_upscaler_steps" name="latent_upscaler_steps" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)"></td></tr>
|
||||
</table>
|
||||
</li>
|
||||
<li class="pl-5"><input id="show_only_filtered_image" name="show_only_filtered_image" type="checkbox" checked> <label for="show_only_filtered_image">Show only the corrected/upscaled image</label></li>
|
||||
</ul></div>
|
||||
@ -356,10 +365,16 @@
|
||||
<div id="tab-content-settings" class="tab-content">
|
||||
<div id="system-settings" class="tab-content-inner">
|
||||
<h1>System Settings</h1>
|
||||
<div class="parameters-table"></div>
|
||||
<div class="parameters-table" id="system-settings-table"></div>
|
||||
<br/>
|
||||
<button id="save-system-settings-btn" class="primaryButton">Save</button>
|
||||
<br/><br/>
|
||||
<div id="share-easy-diffusion">
|
||||
<h3><i class="fa fa-user-group"></i> Share Easy Diffusion</h3>
|
||||
<div class="parameters-table" id="system-settings-network-table">
|
||||
</div>
|
||||
</div>
|
||||
<br/><br/>
|
||||
<div>
|
||||
<h3><i class="fa fa-microchip icon"></i> System Info</h3>
|
||||
<div id="system-info">
|
||||
@ -534,7 +549,8 @@ async function init() {
|
||||
SD.init({
|
||||
events: {
|
||||
statusChange: setServerStatus,
|
||||
idle: onIdle
|
||||
idle: onIdle,
|
||||
ping: tunnelUpdate
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -69,13 +69,15 @@
|
||||
}
|
||||
|
||||
.parameters-table > div:first-child {
|
||||
border-radius: 12px 12px 0px 0px;
|
||||
border-top-left-radius: 12px;
|
||||
border-top-right-radius: 12px;
|
||||
}
|
||||
|
||||
.parameters-table > div:last-child {
|
||||
border-radius: 0px 0px 12px 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
|
||||
.parameters-table .fa-fire {
|
||||
color: #F7630C;
|
||||
}
|
||||
}
|
||||
|
@ -96,7 +96,7 @@
|
||||
|
||||
.editor-controls-center {
|
||||
/* background: var(--background-color2); */
|
||||
flex: 1;
|
||||
flex: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
@ -105,6 +105,8 @@
|
||||
.editor-controls-center > div {
|
||||
position: relative;
|
||||
background: black;
|
||||
margin: 20pt;
|
||||
margin-top: 40pt;
|
||||
}
|
||||
|
||||
.editor-controls-center canvas {
|
||||
@ -164,8 +166,10 @@
|
||||
margin: var(--popup-margin);
|
||||
padding: var(--popup-padding);
|
||||
min-height: calc(99h - (2 * var(--popup-margin)));
|
||||
max-width: none;
|
||||
max-width: fit-content;
|
||||
min-width: fit-content;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.image-editor-popup h1 {
|
||||
|
@ -1303,12 +1303,35 @@ body.wait-pause {
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
#latent_upscaler_settings {
|
||||
.sub-settings {
|
||||
padding-top: 3pt;
|
||||
padding-bottom: 3pt;
|
||||
padding-left: 5pt;
|
||||
}
|
||||
|
||||
#cloudflare-address {
|
||||
background-color: var(--background-color3);
|
||||
padding: 6px;
|
||||
border-radius: var(--input-border-radius);
|
||||
border: var(--input-border-size) solid var(--input-border-color);
|
||||
margin-top: 0.2em;
|
||||
margin-bottom: 0.2em;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#copy-cloudflare-address {
|
||||
padding: 4px 8px;
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.expandedSettingRow {
|
||||
background: var(--background-color1);
|
||||
width: 95%;
|
||||
border-radius: 4pt;
|
||||
margin-top: 5pt;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
|
||||
/* TOAST NOTIFICATIONS */
|
||||
.toast-notification {
|
||||
position: fixed;
|
||||
@ -1322,7 +1345,7 @@ body.wait-pause {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
z-index: 9999;
|
||||
animation: slideInRight 0.5s ease forwards;
|
||||
transition: bottom 0.5s ease; // Add a transition to smoothly reposition the toasts
|
||||
transition: bottom 0.5s ease; /* Add a transition to smoothly reposition the toasts */
|
||||
}
|
||||
|
||||
.toast-notification-error {
|
||||
|
@ -186,6 +186,7 @@
|
||||
const EVENT_TASK_START = "taskStart"
|
||||
const EVENT_TASK_END = "taskEnd"
|
||||
const EVENT_TASK_ERROR = "task_error"
|
||||
const EVENT_PING = "ping"
|
||||
const EVENT_UNEXPECTED_RESPONSE = "unexpectedResponse"
|
||||
const EVENTS_TYPES = [
|
||||
EVENT_IDLE,
|
||||
@ -196,6 +197,7 @@
|
||||
EVENT_TASK_START,
|
||||
EVENT_TASK_END,
|
||||
EVENT_TASK_ERROR,
|
||||
EVENT_PING,
|
||||
|
||||
EVENT_UNEXPECTED_RESPONSE,
|
||||
]
|
||||
@ -240,6 +242,7 @@
|
||||
setServerStatus("error", "offline")
|
||||
return false
|
||||
}
|
||||
|
||||
// Set status
|
||||
switch (serverState.status) {
|
||||
case ServerStates.init:
|
||||
@ -261,6 +264,7 @@
|
||||
break
|
||||
}
|
||||
serverState.time = Date.now()
|
||||
await eventSource.fireEvent(EVENT_PING, serverState)
|
||||
return true
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
|
@ -87,13 +87,15 @@ let promptStrengthField = document.querySelector("#prompt_strength")
|
||||
let samplerField = document.querySelector("#sampler_name")
|
||||
let samplerSelectionContainer = document.querySelector("#samplerSelection")
|
||||
let useFaceCorrectionField = document.querySelector("#use_face_correction")
|
||||
let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), "gfpgan")
|
||||
let gfpganModelField = new ModelDropdown(document.querySelector("#gfpgan_model"), ["gfpgan", "codeformer"], "", false)
|
||||
let useUpscalingField = document.querySelector("#use_upscale")
|
||||
let upscaleModelField = document.querySelector("#upscale_model")
|
||||
let upscaleAmountField = document.querySelector("#upscale_amount")
|
||||
let latentUpscalerSettings = document.querySelector("#latent_upscaler_settings")
|
||||
let latentUpscalerStepsSlider = document.querySelector("#latent_upscaler_steps_slider")
|
||||
let latentUpscalerStepsField = document.querySelector("#latent_upscaler_steps")
|
||||
let codeformerFidelitySlider = document.querySelector("#codeformer_fidelity_slider")
|
||||
let codeformerFidelityField = document.querySelector("#codeformer_fidelity")
|
||||
let stableDiffusionModelField = new ModelDropdown(document.querySelector("#stable_diffusion_model"), "stable-diffusion")
|
||||
let clipSkipField = document.querySelector("#clip_skip")
|
||||
let tilingField = document.querySelector("#tiling")
|
||||
@ -270,7 +272,9 @@ function shiftOrConfirm(e, prompt, fn) {
|
||||
confirm(
|
||||
'<small>Tip: To skip this dialog, use shift-click or disable the "Confirm dangerous actions" setting in the Settings tab.</small>',
|
||||
prompt,
|
||||
() => { fn(e) }
|
||||
() => {
|
||||
fn(e)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1261,6 +1265,11 @@ function getCurrentUserRequest() {
|
||||
}
|
||||
if (useFaceCorrectionField.checked) {
|
||||
newTask.reqBody.use_face_correction = gfpganModelField.value
|
||||
|
||||
if (gfpganModelField.value.includes("codeformer")) {
|
||||
newTask.reqBody.codeformer_upscale_faces = document.querySelector("#codeformer_upscale_faces").checked
|
||||
newTask.reqBody.codeformer_fidelity = 1 - parseFloat(codeformerFidelityField.value)
|
||||
}
|
||||
}
|
||||
if (useUpscalingField.checked) {
|
||||
newTask.reqBody.use_upscale = upscaleModelField.value
|
||||
@ -1574,24 +1583,43 @@ metadataOutputFormatField.disabled = !saveToDiskField.checked
|
||||
gfpganModelField.disabled = !useFaceCorrectionField.checked
|
||||
useFaceCorrectionField.addEventListener("change", function(e) {
|
||||
gfpganModelField.disabled = !this.checked
|
||||
|
||||
onFixFaceModelChange()
|
||||
})
|
||||
|
||||
function onFixFaceModelChange() {
|
||||
let codeformerSettings = document.querySelector("#codeformer_settings")
|
||||
if (gfpganModelField.value === "codeformer" && !gfpganModelField.disabled) {
|
||||
codeformerSettings.classList.remove("displayNone")
|
||||
codeformerSettings.classList.add("expandedSettingRow")
|
||||
} else {
|
||||
codeformerSettings.classList.add("displayNone")
|
||||
codeformerSettings.classList.remove("expandedSettingRow")
|
||||
}
|
||||
}
|
||||
gfpganModelField.addEventListener("change", onFixFaceModelChange)
|
||||
onFixFaceModelChange()
|
||||
|
||||
upscaleModelField.disabled = !useUpscalingField.checked
|
||||
upscaleAmountField.disabled = !useUpscalingField.checked
|
||||
useUpscalingField.addEventListener("change", function(e) {
|
||||
upscaleModelField.disabled = !this.checked
|
||||
upscaleAmountField.disabled = !this.checked
|
||||
|
||||
onUpscaleModelChange()
|
||||
})
|
||||
|
||||
function onUpscaleModelChange() {
|
||||
let upscale4x = document.querySelector("#upscale_amount_4x")
|
||||
if (upscaleModelField.value === "latent_upscaler") {
|
||||
if (upscaleModelField.value === "latent_upscaler" && !upscaleModelField.disabled) {
|
||||
upscale4x.disabled = true
|
||||
upscaleAmountField.value = "2"
|
||||
latentUpscalerSettings.classList.remove("displayNone")
|
||||
latentUpscalerSettings.classList.add("expandedSettingRow")
|
||||
} else {
|
||||
upscale4x.disabled = false
|
||||
latentUpscalerSettings.classList.add("displayNone")
|
||||
latentUpscalerSettings.classList.remove("expandedSettingRow")
|
||||
}
|
||||
}
|
||||
upscaleModelField.addEventListener("change", onUpscaleModelChange)
|
||||
@ -1606,6 +1634,27 @@ document.onkeydown = function(e) {
|
||||
}
|
||||
}
|
||||
|
||||
/********************* CodeFormer Fidelity **************************/
|
||||
function updateCodeformerFidelity() {
|
||||
codeformerFidelityField.value = codeformerFidelitySlider.value / 10
|
||||
codeformerFidelityField.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
function updateCodeformerFidelitySlider() {
|
||||
if (codeformerFidelityField.value < 0) {
|
||||
codeformerFidelityField.value = 0
|
||||
} else if (codeformerFidelityField.value > 1) {
|
||||
codeformerFidelityField.value = 1
|
||||
}
|
||||
|
||||
codeformerFidelitySlider.value = codeformerFidelityField.value * 10
|
||||
codeformerFidelitySlider.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
codeformerFidelitySlider.addEventListener("input", updateCodeformerFidelity)
|
||||
codeformerFidelityField.addEventListener("input", updateCodeformerFidelitySlider)
|
||||
updateCodeformerFidelity()
|
||||
|
||||
/********************* Latent Upscaler Steps **************************/
|
||||
function updateLatentUpscalerSteps() {
|
||||
latentUpscalerStepsField.value = latentUpscalerStepsSlider.value
|
||||
@ -1704,10 +1753,10 @@ function updateLoraAlpha() {
|
||||
}
|
||||
|
||||
function updateLoraAlphaSlider() {
|
||||
if (loraAlphaField.value < 0) {
|
||||
loraAlphaField.value = 0
|
||||
} else if (loraAlphaField.value > 1) {
|
||||
loraAlphaField.value = 1
|
||||
if (loraAlphaField.value < -2) {
|
||||
loraAlphaField.value = -2
|
||||
} else if (loraAlphaField.value > 2) {
|
||||
loraAlphaField.value = 2
|
||||
}
|
||||
|
||||
loraAlphaSlider.value = loraAlphaField.value * 100
|
||||
@ -1958,6 +2007,38 @@ resumeBtn.addEventListener("click", function() {
|
||||
document.body.classList.remove("wait-pause")
|
||||
})
|
||||
|
||||
function tunnelUpdate(event) {
|
||||
if ("cloudflare" in event) {
|
||||
document.getElementById("cloudflare-off").classList.add("displayNone")
|
||||
document.getElementById("cloudflare-on").classList.remove("displayNone")
|
||||
cloudflareAddressField.innerHTML = event.cloudflare
|
||||
document.getElementById("toggle-cloudflare-tunnel").innerHTML = "Stop"
|
||||
} else {
|
||||
document.getElementById("cloudflare-on").classList.add("displayNone")
|
||||
document.getElementById("cloudflare-off").classList.remove("displayNone")
|
||||
document.getElementById("toggle-cloudflare-tunnel").innerHTML = "Start"
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("toggle-cloudflare-tunnel").addEventListener("click", async function() {
|
||||
let command = "stop"
|
||||
if (document.getElementById("toggle-cloudflare-tunnel").innerHTML == "Start") {
|
||||
command = "start"
|
||||
}
|
||||
showToast(`Cloudflare tunnel ${command} initiated. Please wait.`)
|
||||
|
||||
let res = await fetch("/tunnel/cloudflare/" + command, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({}),
|
||||
})
|
||||
res = await res.json()
|
||||
|
||||
console.log(`Cloudflare tunnel ${command} result:`, res)
|
||||
})
|
||||
|
||||
/* Pause function */
|
||||
document.querySelectorAll(".tab").forEach(linkTabContents)
|
||||
|
||||
|
@ -11,6 +11,12 @@ var ParameterType = {
|
||||
custom: "custom",
|
||||
}
|
||||
|
||||
/**
|
||||
* Element shortcuts
|
||||
*/
|
||||
let parametersTable = document.querySelector("#system-settings-table")
|
||||
let networkParametersTable = document.querySelector("#system-settings-network-table")
|
||||
|
||||
/**
|
||||
* JSDoc style
|
||||
* @typedef {object} Parameter
|
||||
@ -186,6 +192,7 @@ var PARAMETERS = [
|
||||
icon: "fa-network-wired",
|
||||
default: true,
|
||||
saveInAppConfig: true,
|
||||
table: networkParametersTable,
|
||||
},
|
||||
{
|
||||
id: "listen_port",
|
||||
@ -198,6 +205,7 @@ var PARAMETERS = [
|
||||
return `<input id="${parameter.id}" name="${parameter.id}" size="6" value="9000" onkeypress="preventNonNumericalInput(event)">`
|
||||
},
|
||||
saveInAppConfig: true,
|
||||
table: networkParametersTable,
|
||||
},
|
||||
{
|
||||
id: "use_beta_channel",
|
||||
@ -218,6 +226,21 @@ var PARAMETERS = [
|
||||
default: false,
|
||||
saveInAppConfig: true,
|
||||
},
|
||||
{
|
||||
id: "cloudflare",
|
||||
type: ParameterType.custom,
|
||||
label: "Cloudflare tunnel",
|
||||
note: `<span id="cloudflare-off">Create a VPN tunnel to share your Easy Diffusion instance with your friends. This will
|
||||
generate a web server address on the public Internet for your Easy Diffusion instance. </span>
|
||||
<div id="cloudflare-on" class="displayNone"><div>This Easy Diffusion server is available on the Internet using the
|
||||
address:</div><div><div id="cloudflare-address"></div><button id="copy-cloudflare-address">Copy</button></div></div>
|
||||
<b>Anyone knowing this address can access your server.</b> The address of your server will change each time
|
||||
you share a session.<br>
|
||||
Uses <a href="https://try.cloudflare.com/" target="_blank">Cloudflare services</a>.`,
|
||||
icon: ["fa-brands", "fa-cloudflare"],
|
||||
render: () => '<button id="toggle-cloudflare-tunnel" class="primaryButton">Start</button>',
|
||||
table: networkParametersTable,
|
||||
}
|
||||
]
|
||||
|
||||
function getParameterSettingsEntry(id) {
|
||||
@ -266,7 +289,6 @@ function getParameterElement(parameter) {
|
||||
}
|
||||
}
|
||||
|
||||
let parametersTable = document.querySelector("#system-settings .parameters-table")
|
||||
/**
|
||||
* fill in the system settings popup table
|
||||
* @param {Array<Parameter> | undefined} parameters
|
||||
@ -293,7 +315,10 @@ function initParameters(parameters) {
|
||||
noteElements.push(noteElement)
|
||||
}
|
||||
|
||||
const icon = parameter.icon ? [createElement("i", undefined, ["fa", parameter.icon])] : []
|
||||
if (typeof(parameter.icon) == "string") {
|
||||
parameter.icon = [parameter.icon]
|
||||
}
|
||||
const icon = parameter.icon ? [createElement("i", undefined, ["fa", ...parameter.icon])] : []
|
||||
|
||||
const label = typeof parameter.label === "function" ? parameter.label(parameter) : parameter.label
|
||||
const labelElement = createElement("label", { for: parameter.id })
|
||||
@ -313,7 +338,13 @@ function initParameters(parameters) {
|
||||
elementWrapper,
|
||||
]
|
||||
)
|
||||
parametersTable.appendChild(newrow)
|
||||
|
||||
let p = parametersTable
|
||||
if (parameter.table) {
|
||||
p = parameter.table
|
||||
}
|
||||
p.appendChild(newrow)
|
||||
|
||||
parameter.settingsEntry = newrow
|
||||
})
|
||||
}
|
||||
@ -667,8 +698,25 @@ saveSettingsBtn.addEventListener("click", function() {
|
||||
})
|
||||
|
||||
const savePromise = changeAppConfig(updateAppConfigRequest)
|
||||
showToast("Settings saved")
|
||||
saveSettingsBtn.classList.add("active")
|
||||
Promise.all([savePromise, asyncDelay(300)]).then(() => saveSettingsBtn.classList.remove("active"))
|
||||
})
|
||||
|
||||
listenToNetworkField.addEventListener("change", debounce( ()=>{
|
||||
saveSettingsBtn.click()
|
||||
}, 1000))
|
||||
|
||||
listenPortField.addEventListener("change", debounce( ()=>{
|
||||
saveSettingsBtn.click()
|
||||
}, 1000))
|
||||
|
||||
let copyCloudflareAddressBtn = document.querySelector("#copy-cloudflare-address")
|
||||
let cloudflareAddressField = document.getElementById("cloudflare-address")
|
||||
|
||||
copyCloudflareAddressBtn.addEventListener("click", (e) => {
|
||||
navigator.clipboard.writeText(cloudflareAddressField.innerHTML)
|
||||
showToast("Copied server address to clipboard")
|
||||
})
|
||||
|
||||
document.addEventListener("system_info_update", (e) => setDeviceInfo(e.detail))
|
||||
|
@ -38,6 +38,8 @@ class ModelDropdown {
|
||||
noneEntry //= ''
|
||||
modelFilterInitialized //= undefined
|
||||
|
||||
sorted //= true
|
||||
|
||||
/* MIMIC A REGULAR INPUT FIELD */
|
||||
get parentElement() {
|
||||
return this.modelFilter.parentElement
|
||||
@ -83,21 +85,34 @@ class ModelDropdown {
|
||||
|
||||
/* SEARCHABLE INPUT */
|
||||
|
||||
constructor(input, modelKey, noneEntry = "") {
|
||||
constructor(input, modelKey, noneEntry = "", sorted = true) {
|
||||
this.modelFilter = input
|
||||
this.noneEntry = noneEntry
|
||||
this.modelKey = modelKey
|
||||
this.sorted = sorted
|
||||
|
||||
if (modelsOptions !== undefined) {
|
||||
// reuse models from cache (only useful for plugins, which are loaded after models)
|
||||
this.inputModels = modelsOptions[this.modelKey]
|
||||
this.inputModels = []
|
||||
let modelKeys = Array.isArray(this.modelKey) ? this.modelKey : [this.modelKey]
|
||||
for (let i = 0; i < modelKeys.length; i++) {
|
||||
let key = modelKeys[i]
|
||||
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||
this.inputModels.push(...k)
|
||||
}
|
||||
this.populateModels()
|
||||
}
|
||||
document.addEventListener(
|
||||
"refreshModels",
|
||||
this.bind(function(e) {
|
||||
// reload the models
|
||||
this.inputModels = modelsOptions[this.modelKey]
|
||||
this.inputModels = []
|
||||
let modelKeys = Array.isArray(this.modelKey) ? this.modelKey : [this.modelKey]
|
||||
for (let i = 0; i < modelKeys.length; i++) {
|
||||
let key = modelKeys[i]
|
||||
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||
this.inputModels.push(...k)
|
||||
}
|
||||
this.populateModels()
|
||||
}, this)
|
||||
)
|
||||
@ -554,11 +569,15 @@ class ModelDropdown {
|
||||
})
|
||||
|
||||
const childFolderNames = Array.from(foldersMap.keys())
|
||||
this.sortStringArray(childFolderNames)
|
||||
if (this.sorted) {
|
||||
this.sortStringArray(childFolderNames)
|
||||
}
|
||||
const folderElements = childFolderNames.map((name) => foldersMap.get(name))
|
||||
|
||||
const modelNames = Array.from(modelsMap.keys())
|
||||
this.sortStringArray(modelNames)
|
||||
if (this.sorted) {
|
||||
this.sortStringArray(modelNames)
|
||||
}
|
||||
const modelElements = modelNames.map((name) => modelsMap.get(name))
|
||||
|
||||
if (modelElements.length && folderName) {
|
||||
|
@ -402,12 +402,12 @@ function debounce(func, wait, immediate) {
|
||||
|
||||
function preventNonNumericalInput(e) {
|
||||
e = e || window.event
|
||||
let charCode = typeof e.which == "undefined" ? e.keyCode : e.which
|
||||
let charStr = String.fromCharCode(charCode)
|
||||
let re = e.target.getAttribute("pattern") || "^[0-9]+$"
|
||||
re = new RegExp(re)
|
||||
const charCode = typeof e.which == "undefined" ? e.keyCode : e.which
|
||||
const charStr = String.fromCharCode(charCode)
|
||||
const newInputValue = `${e.target.value}${charStr}`
|
||||
const re = new RegExp(e.target.getAttribute("pattern") || "^[0-9]+$")
|
||||
|
||||
if (!charStr.match(re)) {
|
||||
if (!re.test(charStr) && !re.test(newInputValue)) {
|
||||
e.preventDefault()
|
||||
}
|
||||
}
|
||||
|
326
ui/plugins/ui/tiled-image-download.plugin.js
Normal file
326
ui/plugins/ui/tiled-image-download.plugin.js
Normal file
@ -0,0 +1,326 @@
|
||||
;(function(){
|
||||
"use strict";
|
||||
const PAPERSIZE = [
|
||||
{id: "a3p", width: 297, height: 420, unit: "mm"},
|
||||
{id: "a3l", width: 420, height: 297, unit: "mm"},
|
||||
{id: "a4p", width: 210, height: 297, unit: "mm"},
|
||||
{id: "a4l", width: 297, height: 210, unit: "mm"},
|
||||
{id: "ll", width: 279, height: 216, unit: "mm"},
|
||||
{id: "lp", width: 216, height: 279, unit: "mm"},
|
||||
{id: "hd", width: 1920, height: 1080, unit: "pixels"},
|
||||
{id: "4k", width: 3840, height: 2160, unit: "pixels"},
|
||||
]
|
||||
|
||||
// ---- Register plugin
|
||||
PLUGINS['IMAGE_INFO_BUTTONS'].push({
|
||||
html: '<i class="fa-solid fa-table-cells-large"></i> Download tiled image',
|
||||
on_click: onDownloadTiledImage,
|
||||
filter: (req, img) => req.tiling != "none",
|
||||
})
|
||||
|
||||
var thisImage
|
||||
|
||||
function onDownloadTiledImage(req, img) {
|
||||
document.getElementById("download-tiled-image-dialog").showModal()
|
||||
thisImage = new Image()
|
||||
thisImage.src = img.src
|
||||
thisImage.dataset["prompt"] = img.dataset["prompt"]
|
||||
}
|
||||
|
||||
// ---- Add HTML
|
||||
document.getElementById('container').lastElementChild.insertAdjacentHTML("afterend",
|
||||
`<dialog id="download-tiled-image-dialog">
|
||||
<h1>Download tiled image</h1>
|
||||
<div class="download-tiled-image dtim-container">
|
||||
<div class="download-tiled-image-top">
|
||||
<div class="tab-container">
|
||||
<span id="tab-image-tiles" class="tab active">
|
||||
<span>Number of tiles</small></span>
|
||||
</span>
|
||||
<span id="tab-image-size" class="tab">
|
||||
<span>Image dimensions</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div id="tab-content-image-tiles" class="tab-content active">
|
||||
<div class="tab-content-inner">
|
||||
<label for="dtim1-width">Width:</label> <input id="dtim1-width" min="1" max="99" type="number" value="2">
|
||||
<label for="dtim1-height">Height:</label> <input id="dtim1-height" min="1" max="99" type="number" value="2">
|
||||
</div>
|
||||
</div>
|
||||
<div id="tab-content-image-size" class="tab-content">
|
||||
<div class="tab-content-inner">
|
||||
<div class="method-2-options">
|
||||
<label for="dtim2-width">Width:</label> <input id="dtim2-width" size="3" value="1920">
|
||||
<label for="dtim2-height">Height:</label> <input id="dtim2-height" size="3" value="1080">
|
||||
<select id="dtim2-unit">
|
||||
<option>pixels</option>
|
||||
<option>mm</option>
|
||||
<option>inches</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="method-2-dpi">
|
||||
<label for="dtim2-dpi">DPI:</label> <input id="dtim2-dpi" size="3" value="72">
|
||||
</div>
|
||||
<div class="method-2-paper">
|
||||
<i>Some standard sizes:</i><br>
|
||||
<button id="dtim2-a3p">A3 portrait</button><button id="dtim2-a3l">A3 landscape</button><br>
|
||||
<button id="dtim2-a4p">A4 portrait</button><button id="dtim2-a4l">A4 landscape</button><br>
|
||||
<button id="dtim2-lp">Letter portrait</button><button id="dtim2-ll">Letter landscape</button><br>
|
||||
<button id="dtim2-hd">Full HD</button><button id="dtim2-4k">4K</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="download-tiled-image-placement">
|
||||
<div class="tab-container">
|
||||
<span id="tab-image-placement" class="tab active">
|
||||
<span>Tile placement</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div id="tab-content-image-placement" class="tab-content active">
|
||||
<div class="tab-content-inner">
|
||||
<img id="dtim-1tl" class="active" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlUlEQVQ4y7VUMU7DQBCckpYCJEpS0ByhcecuUZQUtvIHGku0vICSDtHkA9eltCylOEBKFInCRworXToK3kDJ7jpn2SYmgGESeWyPRuudvTugHTyC72momKDGMMJDLIhmgK+nWmuPXxtlxkhjExszRKqU6uRuTW7TYTwh6HTpR25+JLcngBJ5jL5wIecqu9nFbid3t27N7vhrtypqV2SfP4zc5pfu/Msb3P6U4fru1eXpVg7tcmnDZ1gb0s1ceAEcSPI3uM2B9xLf7Z3YLlfJ/WCppF1QbbqxeW0brlztjXzprBhJrW8nu4HWGlt/xz1qcrervfmT2ma3WxpTjfK5ZUioNg+VsUL+tiXuI8YJLrd8KHyENyaqPWC8QGiwwlJ4LtyvNtb9vFKrqZXXeebkrEiN3ZUNXHJnO3aJkxt2aH2gDRNTLdyzJvee1CZXUTSJrhA55itlfszUdqDrxCQmGIEu9KfFFCRJYnpIgyB4JJlPWM6cY6MjN+UW5MjdM7FKavF/pFbfRD9zv8rjBa6FT5EJn0HoA8lOiD4+8B3mAAAAAElFTkSuQmCC" />
|
||||
<img id="dtim-1tr" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABoUlEQVQ4y61UsU7CUBQ9o6uGmJgwNSQOLS7dOndrGfwEDWnC4tQNB8duxoUfYGN86fZgIWy+iVUnwwYf4Oi9lxa0tFSpB9IDXM6775773gUaYWjbtoO+IrI1VsIKnjt2CYCllJqir7Wt7SlWWmn+m+t53sQbU5hAamtJrxRr/mppUnuZOgszOlgJK7gCUS93YVbzKqx2q9U2q71Mbf1Qbxc/qqadu7y509W7nX8Pt/K6JwwKO+HCGLRNKPy4oA9mkYUnwGeSJM9IBDknOJN+PNV2rEy9XyXLvaGcktuY0FBux9AP5rVYd96SofCsWFje0NJwUd2rUse/UTfLPTspd83iFFZZYY4xbKoRsKlmaypjjoaICA+4ZYrO8SJ8mfEV0PF9P0Tb94U3wj7eheaHJ3VZLKxbcs4P6uartz/nMYlKbFnzYtWe5ze0wtSjDd1Ph7iReheucS0aRYM78pwoiiDPUc6Dpg19S9N0ipYOgiClw5TqgN6I6aGD7S2RkcsbppnKbLPnPHt7VZOpxvN/cq1cHf9BbeFeqIsL4Wt8CN/gC1XPfwv6U6jJAAAAAElFTkSuQmCC" /><br>
|
||||
<img id="dtim-1bl" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjElEQVQ4y7WUsU7DMBCG/5GVoUiMMLC4ZcmWrVXVDq7yDiyRuvIEjGyIpS/graMVKYMBqVUkhoYOUTc2Bp6BEd+5qdI2SasGLpE/KZc/d76LD/i6JrvFPfMKGfMGDOCTGUMwA/SYA8yL7iEM8w2yzH1AHderh9D1alGuXmmtBaVGchFgQe8J69bOHZnIyCHsYu8AUkZRZLpYSClf0dAm4zCchGOEOWkNW7ggAO0+2QcY/SUS5ozZ+6uqNVHHR9Q8q1Bnm9+B1B17HdGxbDe1zn7sA1V7DskucbfmObOFb0LThrZTsjlGzHcw0iXcU6woQxFv9i3rah5UdSxvSa/+EMn6hp6iroy9+s/YL2mSjlxRE1fUkX2wZNqiPjrDTwmfDnbsjNeH0q9Y9ZRN2diJjeliJ+ksj+2v3W5j3d2N+R5b4T/fcjuvqppMvqfsdbqaxF7VCc3Vho9eUd0pZi6q1cpThemwdYB9NVVK2cy1NsLYmaqNNmZgZ6sQa7VPmWuavQG9ZkkjV+u42QH8BWe+iD71TSARAAAAAElFTkSuQmCC" />
|
||||
<img id="dtim-1br" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUCAgKIhobRz89vMJ7s6uo9PDx4d3ewra0bHR1dXV19NrLa2Nj+/f29u7uWlJQuLi7ws27qAAAACXBIWXMAAAsTAAALEwEAmpwYAAABkklEQVQ4y7VULU/DUBQ9EgtZSEimliWIt2Lmnq5rJ/gJkKXJDGquCGQdwewPzE2+PPeGWeaomgVF6tgPmOTe2zWkox8LC6dLT/ZuTs9957UXPQbuhTxcCF/jU/gGYBpgLH/7yIQNYuHXctniS9hhWlU+Wh03qVX1wwt1+eGKyoZXl8iYFbdmlFIj4N1au0THBUFgLTLrAvphSjcXUPk0RLNocodbpiiC3GcFT4C+7/shur4vvBX28SG0+o/UTHNqrZnXe3uVrW3oKtScuVeUvZYTK3lvyq21pBYRHihzxjlehC/3fHXqgQ5SArapAI9C63w1XR3GUuw75neuN6otV++78SOyza9Dv/lAj1Yf5T061XsQrjnUdSpMoYZ5qLSQvgG7JEmekQgOOWk9sV2l6kxqT4V3Nw1zb7IMyVsvBIcb6+w3lpfn1V+Jw1Awr5tMOq/XqzVdf1Mr9tZ7701JzZ+ia1ab352z6mc6cGO52hiapWPnlFM0U51ximbqUGu90KSOabTyyCWi5UyYO5/n6pPwDYr8fwvXgN7jAAAAAElFTkSuQmCC" /> <br>
|
||||
<img id="dtim-1center" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUBAQGPjY3Rz89DQ0O0sbHz8vJZJoFwbm6+vLxBHV10MqY6ODifnZ1XV1cdHR3c2trZIbFLAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCElEQVQ4y7VVPWvjQBCVnZXt5JRT1LsQwwnS7YHrgLg6hTBsva4MSeUurX5CqqtTuXCVn6CAf0DgimvvJ4iFredmd2VZcqTYIWSap2V4+/HezMgDSAdgIw0cFhVeMQAPIAuUWfF44jAdOix8k8YsmCNXyEuGynzkQ4u68BE8LbKJsJGFDkvfYe6Lufe5ePi7unywUePY4Suhl4iMCaGlFOWQQGiRhw5Tc3b9MIiZe1Deehhkg3nXu5MirGW5NmnmkNjX9EFs1VKN7djgNg+bovae3a15cpg+ZIfquGP97J2h0viJ5cT6SoZaX+lspYyoX2doFBXjlyiaRtHV+XL5++5u+TiITExXQRR5XGeBQIkoyouNjadQSqm1Tn2pmg8bbe5NelFb0nQMKL25XxO7tsSoxmu/q80XbdXK/eZrs/2iR/OR4a53m1eO8ayPfWDJqHU2VuwywNbVnt6czflbttucVGOkmUbdUI2WSG0g0ZvepuNbiu22IM1NPAZ2uV2x7csnDf3FywFdhQPmttYUT6kkubv5D4+bWlOoFNWa5BRJHiJNDzTDQzUd21fqoaHdPaaO1/kp/d1mwwkdunfsIx2K7Q6lapmDIrVihqAsG41qiZ0tbq7ZFhw6jKsWtHPtz+z128zGT4c3z2cVXs5ujjn2773k9472j5vtf/pkYp2TKe/7E00A/gO7G7pwJRGqtAAAAABJRU5ErkJggg==" />
|
||||
<img id="dtim-4center" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA3BAMAAACiFTSCAAAAMFBMVEUBAQGbmppqLZZ8fHzRz8+LPMZ0MqXs6upTUVE+G1q5ublubm6iRedQInH9/Pze3NzAiv2WAAAACXBIWXMAAAsTAAALEwEAmpwYAAABuUlEQVQ4y6VVvU7CUBSmCEVQao0vQDrcWWcWhiYkDsICc3OS3pmhSbfiI7C6MfgAJo4sPoCDcSdhMGFDBhI24zn3tBjh2DTy3eEj9+P0/Pa05BtMrZQbzG7KJaZFIyLqrS82hqfOnszXnUquLFv3dvLOeiP57uT69teFfHcP5CKJ+QdyN0YsF5Ul8WpUI06SjmM4Ks0Mrk+Zn86Y70+YS8fha0VY2K8GL1XmwDHXnxj54y1GXgbwwNOBrT2lPNWiyFHIEitrhYdkDaB1ay/vsjY6yiiCJJMVykq0pkt+OPoQH44gGX8IskqtFYjWgODQQAwt8y1aAygPdN8GCkHt5LSoaTH75ylnRT0ON5cEtz4nvL81Dc8nlrm+wmEax9t4YQ/0MNRhn1iHYcuJt+PxOMoib5p8uGpySzAfUxbQKqcsSov9VmlRqSPyOHBR6Q9Sx9haKci3lvsNPGtyv8Hogc3c+nkFl3iwasNBOBz0q8yBk8RJvMRXsE3HrT8YTDK2Zs942kc29I6npbZi5ilZjZg/DpaHL++WqNDiypfzt+LfO3VTaO39c6dmsp9nPZJ9Z18i19r8+hJ9A3EAErhB3eXkAAAAAElFTkSuQmCC" /> <br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dtim-ok">
|
||||
<button class="primaryButton" id="dti-ok">Download</button>
|
||||
</div>
|
||||
<div class="dtim-newtab">
|
||||
<button class="primaryButton" id="dti-newtab">Open in new tab</button>
|
||||
</div>
|
||||
<div class="dtim-cancel">
|
||||
<button class="primaryButton" id="dti-cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</dialog>`)
|
||||
|
||||
let downloadTiledImageDialog = document.getElementById("download-tiled-image-dialog")
|
||||
let dtim1_width = document.getElementById("dtim1-width")
|
||||
let dtim1_height = document.getElementById("dtim1-height")
|
||||
let dtim2_width = document.getElementById("dtim2-width")
|
||||
let dtim2_height = document.getElementById("dtim2-height")
|
||||
let dtim2_unit = document.getElementById("dtim2-unit")
|
||||
let dtim2_dpi = document.getElementById("dtim2-dpi")
|
||||
let tabTiledTilesOptions = document.getElementById("tab-image-tiles")
|
||||
let tabTiledSizeOptions = document.getElementById("tab-image-size")
|
||||
|
||||
linkTabContents(tabTiledTilesOptions)
|
||||
linkTabContents(tabTiledSizeOptions)
|
||||
|
||||
prettifyInputs(downloadTiledImageDialog)
|
||||
|
||||
// ---- Predefined image dimensions
|
||||
PAPERSIZE.forEach( function(p) {
|
||||
document.getElementById("dtim2-" + p.id).addEventListener("click", (e) => {
|
||||
dtim2_unit.value = p.unit
|
||||
dtim2_width.value = p.width
|
||||
dtim2_height.value = p.height
|
||||
})
|
||||
})
|
||||
|
||||
// ---- Close popup
|
||||
document.getElementById("dti-cancel").addEventListener("click", (e) => downloadTiledImageDialog.close())
|
||||
downloadTiledImageDialog.addEventListener('click', function (event) {
|
||||
var rect = downloadTiledImageDialog.getBoundingClientRect();
|
||||
var isInDialog=(rect.top <= event.clientY && event.clientY <= rect.top + rect.height
|
||||
&& rect.left <= event.clientX && event.clientX <= rect.left + rect.width);
|
||||
if (!isInDialog) {
|
||||
downloadTiledImageDialog.close();
|
||||
}
|
||||
});
|
||||
|
||||
// ---- Stylesheet
|
||||
const styleSheet = document.createElement("style")
|
||||
styleSheet.textContent = `
|
||||
dialog {
|
||||
background: var(--background-color2);
|
||||
color: var(--text-color);
|
||||
border-radius: 7px;
|
||||
border: 1px solid var(--background-color3);
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
|
||||
button[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.method-2-dpi {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.method-2-paper button {
|
||||
width: 10em;
|
||||
padding: 4px;
|
||||
margin: 4px;
|
||||
}
|
||||
|
||||
.download-tiled-image .tab-content {
|
||||
background: var(--background-color1);
|
||||
border-radius: 3pt;
|
||||
}
|
||||
|
||||
.dtim-container { display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-rows: auto auto;
|
||||
gap: 1em 0px;
|
||||
grid-auto-flow: row;
|
||||
grid-template-areas:
|
||||
"dtim-tab dtim-tab dtim-plc"
|
||||
"dtim-ok dtim-newtab dtim-cancel";
|
||||
}
|
||||
|
||||
.download-tiled-image-top {
|
||||
justify-self: center;
|
||||
grid-area: dtim-tab;
|
||||
}
|
||||
|
||||
.download-tiled-image-placement {
|
||||
justify-self: center;
|
||||
grid-area: dtim-plc;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.dtim-ok {
|
||||
justify-self: center;
|
||||
align-self: start;
|
||||
grid-area: dtim-ok;
|
||||
}
|
||||
|
||||
.dtim-newtab {
|
||||
justify-self: center;
|
||||
align-self: start;
|
||||
grid-area: dtim-newtab;
|
||||
}
|
||||
|
||||
.dtim-cancel {
|
||||
justify-self: center;
|
||||
align-self: start;
|
||||
grid-area: dtim-cancel;
|
||||
}
|
||||
|
||||
#tab-content-image-placement img {
|
||||
margin: 4px;
|
||||
opacity: 0.3;
|
||||
border: solid 2px var(--background-color1);
|
||||
}
|
||||
|
||||
#tab-content-image-placement img:hover {
|
||||
margin: 4px;
|
||||
opacity: 1;
|
||||
border: solid 2px var(--accent-color);
|
||||
filter: brightness(2);
|
||||
}
|
||||
|
||||
#tab-content-image-placement img.active {
|
||||
margin: 4px;
|
||||
opacity: 1;
|
||||
border: solid 2px var(--background-color1);
|
||||
}
|
||||
|
||||
`
|
||||
document.head.appendChild(styleSheet)
|
||||
|
||||
// ---- Placement widget
|
||||
|
||||
function updatePlacementWidget(event) {
|
||||
document.querySelector("#tab-content-image-placement img.active").classList.remove("active")
|
||||
event.target.classList.add("active")
|
||||
}
|
||||
|
||||
document.querySelectorAll("#tab-content-image-placement img").forEach(
|
||||
(i) => i.addEventListener("click", updatePlacementWidget)
|
||||
)
|
||||
|
||||
function getPlacement() {
|
||||
return document.querySelector("#tab-content-image-placement img.active").id.substr(5)
|
||||
}
|
||||
|
||||
// ---- Make the image
|
||||
function downloadTiledImage(image, width, height, offsetX=0, offsetY=0, new_tab=false) {
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
const w = image.width
|
||||
const h = image.height
|
||||
|
||||
for (var x = offsetX; x < width; x += w) {
|
||||
for (var y = offsetY; y < height; y += h) {
|
||||
context.drawImage(image, x, y, w, h)
|
||||
}
|
||||
}
|
||||
if (new_tab) {
|
||||
var newTab = window.open("")
|
||||
newTab.document.write(`<html><head><title>${width}×${height}, "${image.dataset["prompt"]}"</title></head><body><img src="${canvas.toDataURL()}"></body></html>`)
|
||||
} else {
|
||||
const link = document.createElement('a')
|
||||
link.href = canvas.toDataURL()
|
||||
link.download = image.dataset["prompt"].replace(/[^a-zA-Z0-9]+/g, "-").substr(0,22)+crypto.randomUUID()+".png"
|
||||
link.click()
|
||||
}
|
||||
}
|
||||
|
||||
function onDownloadTiledImageClick(e, newtab=false) {
|
||||
var width, height, offsetX, offsetY
|
||||
|
||||
if (isTabActive(tabTiledTilesOptions)) {
|
||||
width = thisImage.width * dtim1_width.value
|
||||
height = thisImage.height * dtim1_height.value
|
||||
} else {
|
||||
if ( dtim2_unit.value == "pixels" ) {
|
||||
width = dtim2_width.value
|
||||
height= dtim2_height.value
|
||||
} else if ( dtim2_unit.value == "mm" ) {
|
||||
width = Math.floor( dtim2_width.value * dtim2_dpi.value / 25.4 )
|
||||
height = Math.floor( dtim2_height.value * dtim2_dpi.value / 25.4 )
|
||||
} else { // inch
|
||||
width = Math.floor( dtim2_width.value * dtim2_dpi.value )
|
||||
height = Math.floor( dtim2_height.value * dtim2_dpi.value )
|
||||
}
|
||||
}
|
||||
|
||||
var placement = getPlacement()
|
||||
if (placement == "1tl") {
|
||||
offsetX = 0
|
||||
offsetY = 0
|
||||
} else if (placement == "1tr") {
|
||||
offsetX = width - thisImage.width * Math.ceil( width / thisImage.width )
|
||||
offsetY = 0
|
||||
} else if (placement == "1bl") {
|
||||
offsetX = 0
|
||||
offsetY = height - thisImage.height * Math.ceil( height / thisImage.height )
|
||||
} else if (placement == "1br") {
|
||||
offsetX = width - thisImage.width * Math.ceil( width / thisImage.width )
|
||||
offsetY = height - thisImage.height * Math.ceil( height / thisImage.height )
|
||||
} else if (placement == "4center") {
|
||||
offsetX = width/2 - thisImage.width * Math.ceil( width/2 / thisImage.width )
|
||||
offsetY = height/2 - thisImage.height * Math.ceil( height/2 / thisImage.height )
|
||||
} else if (placement == "1center") {
|
||||
offsetX = width/2 - thisImage.width/2 - thisImage.width * Math.ceil( (width/2 - thisImage.width/2) / thisImage.width )
|
||||
offsetY = height/2 - thisImage.height/2 - thisImage.height * Math.ceil( (height/2 - thisImage.height/2) / thisImage.height )
|
||||
}
|
||||
downloadTiledImage(thisImage, width, height, offsetX, offsetY, newtab)
|
||||
downloadTiledImageDialog.close()
|
||||
}
|
||||
|
||||
document.getElementById("dti-ok").addEventListener("click", onDownloadTiledImageClick)
|
||||
document.getElementById("dti-newtab").addEventListener("click", (e) => onDownloadTiledImageClick(e,true))
|
||||
|
||||
})()
|
Loading…
Reference in New Issue
Block a user