mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2025-08-13 17:57:20 +02:00
Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
324226f87d | |||
3120b593c6 | |||
d98e4772ac | |||
cf87c34bef | |||
656acafed3 | |||
5bc0d1f762 | |||
881fdc58ec | |||
569431dc72 | |||
07e30ae4ad | |||
c74be07c33 | |||
887d871d26 | |||
4dd1a46efa | |||
eb301a67d4 | |||
d9bddffc42 | |||
a43bd2fd3b | |||
aac9acf068 | |||
65bb01892f | |||
5b35c47360 | |||
4bf78521ce | |||
2a5b3040e2 | |||
2c4cd21c8f | |||
8ced5b7199 | |||
41d8847592 | |||
eb96bfe8a4 | |||
3037cceab3 | |||
324ffdefba | |||
9a81d17d33 | |||
0ba9f0549e | |||
f83af28e42 | |||
a2856b2b77 | |||
924fee394a | |||
e349fb1a23 | |||
4f799a2bf0 | |||
5398765fd7 | |||
48edce72a9 | |||
267c7b85ea | |||
e23f66a697 | |||
9a0031c47b | |||
0d8e73b206 | |||
9486c03a89 | |||
c09512bf12 | |||
05c2de9450 | |||
6ae5cb28cf | |||
cf6c1add1d | |||
d0184a1598 | |||
79d6ab9915 | |||
047390873c | |||
4b36ca75cb | |||
f7c52b700e | |||
c81d98ad0f | |||
046c00d844 | |||
b14653cb9e | |||
c72b287c82 | |||
a10aa92634 | |||
8a2c09c6de | |||
401fc30617 | |||
6ca7247c02 | |||
1d5309decb | |||
ab0218050c | |||
6dcf7539bb | |||
51d52d3a07 | |||
3045f5211f | |||
9ce076eb0d | |||
2080d6e27b | |||
41ecc822df | |||
ce2a42ca13 |
14
CHANGES.md
14
CHANGES.md
@ -22,6 +22,20 @@
|
||||
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 - 24 Jun 2023 - (beta-only) Fix broken inpainting in low VRAM usage mode.
|
||||
* 2.5.41 - 24 Jun 2023 - (beta-only) Fix a recent regression where the LoRA would not get applied when changing SD models.
|
||||
* 2.5.41 - 23 Jun 2023 - Fix a regression where latent upscaler stopped working on PCs without a graphics card.
|
||||
* 2.5.41 - 20 Jun 2023 - Automatically fix black images if fp32 attention precision is required in diffusers.
|
||||
* 2.5.41 - 19 Jun 2023 - Another fix for multi-gpu rendering (in all VRAM usage modes).
|
||||
* 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.
|
||||
|
@ -5,10 +5,10 @@ If you haven't downloaded Stable Diffusion UI yet, please download from https://
|
||||
After downloading, to install please follow these instructions:
|
||||
|
||||
For Windows:
|
||||
- Please double-click the "Start Stable Diffusion UI.cmd" file inside the "stable-diffusion-ui" folder.
|
||||
- Please double-click the "Easy-Diffusion-Windows.exe" file and follow the instructions.
|
||||
|
||||
For Linux:
|
||||
- Please open a terminal, and go to the "stable-diffusion-ui" directory. Then run ./start.sh
|
||||
- Please open a terminal, unzip the Easy-Diffusion-Linux.zip file and go to the "easy-diffusion" directory. Then run ./start.sh
|
||||
|
||||
That file will automatically install everything. After that it will start the Stable Diffusion interface in a web browser.
|
||||
|
||||
@ -21,4 +21,4 @@ If you have any problems, please:
|
||||
3. Or, file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues
|
||||
|
||||
Thanks
|
||||
cmdr2 (and contributors to the project)
|
||||
cmdr2 (and contributors to the project)
|
||||
|
@ -23,6 +23,7 @@ Click the download button for your operating system:
|
||||
- Minimum 8 GB of system RAM.
|
||||
- Atleast 25 GB of space on the hard disk.
|
||||
|
||||
|
||||
The installer will take care of whatever is needed. If you face any problems, you can join the friendly [Discord community](https://discord.com/invite/u9yhsFmEkB) and ask for assistance.
|
||||
|
||||
## On Windows:
|
||||
|
@ -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.101",
|
||||
"sdkit": "1.0.112",
|
||||
"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
|
||||
|
||||
|
@ -4,7 +4,7 @@ import sys
|
||||
|
||||
# The config file is in the same directory as this script
|
||||
config_directory = os.path.dirname(__file__)
|
||||
config_yaml = os.path.join(config_directory, "config.yaml")
|
||||
# config_yaml = os.path.join(config_directory, "config.yaml")
|
||||
config_json = os.path.join(config_directory, "config.json")
|
||||
|
||||
parser = argparse.ArgumentParser(description='Get values from config file')
|
||||
@ -16,15 +16,16 @@ parser.add_argument('key', metavar='key', nargs='+',
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
if os.path.isfile(config_yaml):
|
||||
import yaml
|
||||
with open(config_yaml, 'r') as configfile:
|
||||
try:
|
||||
config = yaml.safe_load(configfile)
|
||||
except Exception as e:
|
||||
print(e, file=sys.stderr)
|
||||
config = {}
|
||||
elif os.path.isfile(config_json):
|
||||
# if os.path.isfile(config_yaml):
|
||||
# import yaml
|
||||
# with open(config_yaml, 'r') as configfile:
|
||||
# try:
|
||||
# config = yaml.safe_load(configfile)
|
||||
# except Exception as e:
|
||||
# print(e, file=sys.stderr)
|
||||
# config = {}
|
||||
# el
|
||||
if os.path.isfile(config_json):
|
||||
import json
|
||||
with open(config_json, 'r') as configfile:
|
||||
try:
|
||||
|
@ -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" (
|
||||
|
@ -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
|
||||
|
@ -100,7 +100,28 @@ def init():
|
||||
def getConfig(default_val=APP_CONFIG_DEFAULTS):
|
||||
try:
|
||||
config_json_path = os.path.join(CONFIG_DIR, "config.json")
|
||||
if not os.path.exists(config_json_path):
|
||||
|
||||
# compatibility with upcoming yaml changes, switching from beta to main
|
||||
config_yaml_path = os.path.join(CONFIG_DIR, "..", "config.yaml")
|
||||
|
||||
# migrate the old config yaml location
|
||||
config_legacy_yaml = os.path.join(CONFIG_DIR, "config.yaml")
|
||||
if os.path.isfile(config_legacy_yaml):
|
||||
shutil.move(config_legacy_yaml, config_yaml_path)
|
||||
|
||||
if os.path.exists(config_yaml_path):
|
||||
try:
|
||||
import yaml
|
||||
|
||||
with open(config_yaml_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
|
||||
setConfig(config) # save to config.json
|
||||
os.remove(config_yaml_path) # delete the yaml file
|
||||
except:
|
||||
log.warn(traceback.format_exc())
|
||||
config = default_val
|
||||
elif not os.path.exists(config_json_path):
|
||||
config = default_val
|
||||
else:
|
||||
with open(config_json_path, "r", encoding="utf-8") as f:
|
||||
|
@ -60,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,
|
||||
@ -72,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
|
||||
@ -85,7 +90,7 @@ 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()
|
||||
@ -108,7 +113,7 @@ def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
||||
return os.path.abspath(model_name + model_extension)
|
||||
|
||||
# Can't find requested model, check the default paths.
|
||||
if model_type == "stable-diffusion":
|
||||
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):
|
||||
@ -118,7 +123,8 @@ def resolve_model_to_use(model_name: str = None, model_type: str = None):
|
||||
)
|
||||
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):
|
||||
@ -142,10 +148,12 @@ def reload_models_if_necessary(context: Context, task_data: TaskData):
|
||||
if context.model_paths.get(model_type) != path
|
||||
}
|
||||
|
||||
if task_data.codeformer_upscale_faces and "realesrgan" not in models_to_reload.keys():
|
||||
models_to_reload["realesrgan"] = resolve_model_to_use(
|
||||
DEFAULT_MODELS["realesrgan"][0]["file_name"], "realesrgan"
|
||||
)
|
||||
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"]
|
||||
|
@ -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,
|
||||
@ -157,36 +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 "codeformer" in task_data.use_face_correction.lower():
|
||||
filters_to_apply.append("codeformer")
|
||||
images = apply_filters(context, "nsfw_checker", images)
|
||||
|
||||
filter_params["upscale_faces"] = task_data.codeformer_upscale_faces
|
||||
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():
|
||||
filters_to_apply.append("gfpgan")
|
||||
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))
|
||||
|
||||
|
@ -52,6 +52,7 @@ class TaskData(BaseModel):
|
||||
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,11 +266,12 @@
|
||||
<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">
|
||||
<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>
|
||||
<div id="codeformer_settings" class="displayNone sub-settings">
|
||||
<input id="codeformer_upscale_faces" name="codeformer_upscale_faces" type="checkbox"><label for="codeformer_upscale_faces">Upscale Faces <small>(improves the resolution of faces)</small></label>
|
||||
</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>
|
||||
@ -281,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 sub-settings">
|
||||
<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>
|
||||
@ -361,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">
|
||||
@ -539,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 {
|
||||
|
@ -1309,6 +1309,29 @@ body.wait-pause {
|
||||
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;
|
||||
|
@ -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"), ["codeformer", "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")
|
||||
@ -1266,6 +1268,7 @@ function getCurrentUserRequest() {
|
||||
|
||||
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) {
|
||||
@ -1588,8 +1591,10 @@ 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)
|
||||
@ -1610,9 +1615,11 @@ function onUpscaleModelChange() {
|
||||
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)
|
||||
@ -1627,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
|
||||
@ -1725,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
|
||||
@ -1979,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
|
||||
})
|
||||
}
|
||||
@ -633,7 +664,7 @@ saveSettingsBtn.addEventListener("click", function() {
|
||||
update_branch: updateBranch,
|
||||
}
|
||||
|
||||
Array.from(parametersTable.children).forEach((parameterRow) => {
|
||||
document.querySelectorAll('#system-settings [data-setting-id]').forEach((parameterRow) => {
|
||||
if (parameterRow.dataset.saveInAppConfig === "true") {
|
||||
const parameterElement =
|
||||
document.getElementById(parameterRow.dataset.settingId) ||
|
||||
@ -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,10 +85,11 @@ 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)
|
||||
@ -94,7 +97,8 @@ class ModelDropdown {
|
||||
let modelKeys = Array.isArray(this.modelKey) ? this.modelKey : [this.modelKey]
|
||||
for (let i = 0; i < modelKeys.length; i++) {
|
||||
let key = modelKeys[i]
|
||||
this.inputModels.push(...modelsOptions[key])
|
||||
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||
this.inputModels.push(...k)
|
||||
}
|
||||
this.populateModels()
|
||||
}
|
||||
@ -102,12 +106,12 @@ class ModelDropdown {
|
||||
"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]
|
||||
this.inputModels.push(...modelsOptions[key])
|
||||
let k = Array.isArray(modelsOptions[key]) ? modelsOptions[key] : [modelsOptions[key]]
|
||||
this.inputModels.push(...k)
|
||||
}
|
||||
this.populateModels()
|
||||
}, this)
|
||||
@ -565,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))
|
||||
|
||||
})()
|
Reference in New Issue
Block a user