mirror of
https://github.com/easydiffusion/easydiffusion.git
synced 2024-11-23 00:33:28 +01:00
commit
0a43305455
@ -2,9 +2,10 @@
|
||||
|
||||
## v2.4
|
||||
### Major Changes
|
||||
- **Automatic scanning for malicious model files** - using `picklescan`. Thanks @JeLuf
|
||||
- **Automatic scanning for malicious model files** - using `picklescan`, and support for `safetensor` model format. Thanks @JeLuf
|
||||
- **Support for custom VAE models**. You can place your VAE files in the `models/vae` folder, and refresh the browser page to use them. More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/VAE-Variational-Auto-Encoder
|
||||
- **Experimental support for multiple GPUs!** It should work automatically. Just open one browser tab per GPU, and spread your tasks across your GPUs. For e.g. open our UI in two browser tabs if you have two GPUs. You can customize which GPUs it should use in the "Settings" tab, otherwise let it automatically pick the best GPUs. Thanks @madrang . More info: https://github.com/cmdr2/stable-diffusion-ui/wiki/Run-on-Multiple-GPUs
|
||||
- **Image Editor** - for drawing simple images for guiding the AI. Thanks @mdiller
|
||||
- **Cleaner UI design** - Show settings and help in new tabs, instead of dropdown popups (which were buggy). Thanks @mdiller
|
||||
- **Progress bar.** Thanks @mdiller
|
||||
- **Custom Image Modifiers** - You can now save your custom image modifiers! Your saved modifiers can include special characters like `{}, (), [], |`
|
||||
@ -21,8 +22,13 @@
|
||||
- A `What's New?` tab in the UI
|
||||
- Ask for a confimation before clearing the results pane or stopping a render task. The dialog can be skipped by holding down the shift key while clicking on the button.
|
||||
- Show the network addresses of the server in the systems setting dialog
|
||||
- Support loading models in the safetensor format, for improved safety
|
||||
|
||||
### Detailed changelog
|
||||
* 2.4.18 - 5 Dec 2022 - Make JPEG Output quality user controllable. Thanks @JeLuf
|
||||
* 2.4.18 - 5 Dec 2022 - Support loading models in the safetensor format, for improved safety. Thanks @JeLuf
|
||||
* 2.4.18 - 1 Dec 2022 - Image Editor, for drawing simple images for guiding the AI. Thanks @mdiller
|
||||
* 2.4.18 - 1 Dec 2022 - Disable an image modifier temporarily by right-clicking it. Thanks @patriceac
|
||||
* 2.4.17 - 30 Nov 2022 - Scroll to generated image. Thanks @patriceac
|
||||
* 2.4.17 - 30 Nov 2022 - Show the network addresses of the server in the systems setting dialog. Thanks @JeLuf
|
||||
* 2.4.17 - 30 Nov 2022 - Fix a bug where GFPGAN wouldn't work properly when multiple GPUs tried to run it at the same time. Thanks @madrang
|
||||
|
@ -44,7 +44,7 @@ if NOT DEFINED test_sd2 set test_sd2=N
|
||||
@call git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a
|
||||
)
|
||||
if "%test_sd2%" == "Y" (
|
||||
@call git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71
|
||||
@call git -c advice.detachedHead=false checkout b1a80dfc75388914252ce363f923103185eaf48f
|
||||
)
|
||||
|
||||
@cd ..
|
||||
@ -182,6 +182,16 @@ call WHERE uvicorn > .tmp
|
||||
)
|
||||
)
|
||||
|
||||
@>nul 2>nul call python -c "import safetensors"
|
||||
@if "%ERRORLEVEL%" NEQ "0" (
|
||||
@echo. & echo SafeTensors not found. Installing
|
||||
@call pip install safetensors || (
|
||||
echo "Error installing the safetensors package necessary for Stable Diffusion UI. Sorry about that, please try to:" & echo " 1. Run this installer again." & echo " 2. If that doesn't fix it, please try the common troubleshooting steps at https://github.com/cmdr2/stable-diffusion-ui/wiki/Troubleshooting" & echo " 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" & echo " 4. If that doesn't solve the problem, please file an issue at https://github.com/cmdr2/stable-diffusion-ui/issues" & echo "Thanks!"
|
||||
pause
|
||||
exit /b
|
||||
)
|
||||
)
|
||||
|
||||
@>nul findstr /m "conda_sd_ui_deps_installed" ..\scripts\install_status.txt
|
||||
@if "%ERRORLEVEL%" NEQ "0" (
|
||||
@echo conda_sd_ui_deps_installed >> ..\scripts\install_status.txt
|
||||
|
@ -38,7 +38,7 @@ if [ -e "scripts/install_status.txt" ] && [ `grep -c sd_git_cloned scripts/insta
|
||||
if [ "$test_sd2" == "N" ]; then
|
||||
git -c advice.detachedHead=false checkout 7f32368ed1030a6e710537047bacd908adea183a
|
||||
elif [ "$test_sd2" == "Y" ]; then
|
||||
git -c advice.detachedHead=false checkout 5d647c5459f4cd790672512222bc41903c01bb71
|
||||
git -c advice.detachedHead=false checkout b1a80dfc75388914252ce363f923103185eaf48f
|
||||
fi
|
||||
|
||||
cd ..
|
||||
@ -150,6 +150,13 @@ else
|
||||
pip install picklescan || fail "Picklescan installation failed."
|
||||
fi
|
||||
|
||||
if python -c "import safetensors" --help >/dev/null 2>&1; then
|
||||
echo "SafeTensors is already installed."
|
||||
else
|
||||
echo "SafeTensors not found, installing."
|
||||
pip install safetensors || fail "SafeTensors installation failed."
|
||||
fi
|
||||
|
||||
|
||||
|
||||
mkdir -p "../models/stable-diffusion"
|
||||
|
@ -1,6 +0,0 @@
|
||||
@call conda --version
|
||||
@call git --version
|
||||
|
||||
cd %CONDA_PREFIX%\..\scripts
|
||||
|
||||
on_env_start.bat
|
@ -1,12 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
conda-unpack
|
||||
|
||||
source $CONDA_PREFIX/etc/profile.d/conda.sh
|
||||
|
||||
conda --version
|
||||
git --version
|
||||
|
||||
cd $CONDA_PREFIX/../scripts
|
||||
|
||||
./on_env_start.sh
|
@ -12,12 +12,11 @@
|
||||
<link rel="stylesheet" href="/media/css/auto-save.css">
|
||||
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css">
|
||||
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css">
|
||||
<link rel="stylesheet" href="/media/css/drawingboard.min.css">
|
||||
<link rel="stylesheet" href="/media/css/image-editor.css">
|
||||
<link rel="stylesheet" href="/media/css/jquery-confirm.min.css">
|
||||
<link rel="manifest" href="/media/manifest.webmanifest">
|
||||
<script src="/media/js/jquery-3.6.1.min.js"></script>
|
||||
<script src="/media/js/jquery-confirm.min.js"></script>
|
||||
<script src="/media/js/drawingboard.min.js"></script>
|
||||
<script src="/media/js/marked.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@ -26,7 +25,7 @@
|
||||
<div id="logo">
|
||||
<h1>
|
||||
Stable Diffusion UI
|
||||
<small>v2.4.17 <span id="updateBranchLabel"></span></small>
|
||||
<small>v2.4.18 <span id="updateBranchLabel"></span></small>
|
||||
</h1>
|
||||
</div>
|
||||
<div id="server-status">
|
||||
@ -65,7 +64,7 @@
|
||||
</div>
|
||||
|
||||
<div id="editor-inputs-init-image" class="row">
|
||||
<label for="init_image">Initial Image (img2img) <small>(optional)</small> </label> <input id="init_image" name="init_image" type="file" /><br/>
|
||||
<label for="init_image">Initial Image (img2img) <small>(optional)</small> </label>
|
||||
|
||||
<div id="init_image_preview_container" class="image_preview_container">
|
||||
<div id="init_image_wrapper">
|
||||
@ -73,16 +72,26 @@
|
||||
<span id="init_image_size_box"></span>
|
||||
<button class="init_image_clear image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
<input id="enable_mask" name="enable_mask" type="checkbox">
|
||||
<label for="enable_mask">
|
||||
In-Painting (beta)
|
||||
<a href="https://github.com/cmdr2/stable-diffusion-ui/wiki/Inpainting" target="_blank"><i class="fa-solid fa-circle-question help-btn"><span class="simple-tooltip right">Click to learn more about InPainting</span></i></a>
|
||||
<small>(select the area which the AI will paint into)</small>
|
||||
</label>
|
||||
<div id="inpaintingEditor"></div>
|
||||
<div id="init_image_buttons">
|
||||
<div class="button">
|
||||
<i class="fa-regular fa-folder-open"></i>
|
||||
Browse
|
||||
<input id="init_image" name="init_image" type="file" />
|
||||
</div>
|
||||
<div id="init_image_button_draw" class="button">
|
||||
<i class="fa-solid fa-pencil"></i>
|
||||
Draw
|
||||
</div>
|
||||
<div id="inpaint_button_container">
|
||||
<div id="init_image_button_inpaint" class="button">
|
||||
<i class="fa-solid fa-paintbrush"></i>
|
||||
Inpaint
|
||||
</div>
|
||||
<input id="enable_mask" name="enable_mask" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="editor-inputs-tags-container" class="row">
|
||||
@ -108,7 +117,7 @@
|
||||
<div id="editor-settings-entries" class="collapsible-content">
|
||||
<div><table>
|
||||
<tr><b class="settings-subheader">Image Settings</b></tr>
|
||||
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="30000" onkeypress="preventNonNumericalInput(event)"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
|
||||
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="0" onkeypress="preventNonNumericalInput(event)"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
|
||||
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1" onkeypress="preventNonNumericalInput(event)"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
||||
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
||||
@ -190,11 +199,14 @@
|
||||
<option value="png">png</option>
|
||||
</select>
|
||||
</td></tr>
|
||||
<tr class="pl-5" id="output_quality_row"><td><label for="output_quality">JPEG Quality:</label></td><td>
|
||||
<input id="output_quality_slider" name="output_quality" class="editor-slider" value="75" type="range" min="10" max="95"> <input id="output_quality" name="output_quality" size="4" pattern="^[0-9\.]+$" onkeypress="preventNonNumericalInput(event)">
|
||||
</td></tr>
|
||||
</table></div>
|
||||
|
||||
<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, and slower image creation)</small></label></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 <small>(uses GFPGAN)</small></label></li>
|
||||
<li class="pl-5">
|
||||
<input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Upscale image by 4x with </label>
|
||||
@ -330,6 +342,38 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="image-editor" class="popup image-editor-popup">
|
||||
<div>
|
||||
<i class="close-button fa-solid fa-xmark"></i>
|
||||
<h1>Image Editor</h1>
|
||||
<div class="flex-container">
|
||||
<div class="editor-controls-left"></div>
|
||||
<div class="editor-controls-center">
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="editor-controls-right">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="image-inpainter" class="popup image-editor-popup">
|
||||
<div>
|
||||
<i class="close-button fa-solid fa-xmark"></i>
|
||||
<h1>Inpainter</h1>
|
||||
<div class="flex-container">
|
||||
<div class="editor-controls-left"></div>
|
||||
<div class="editor-controls-center">
|
||||
<div></div>
|
||||
</div>
|
||||
<div class="editor-controls-right">
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer-spacer"></div>
|
||||
<div id="footer">
|
||||
<div class="line-separator"> </div>
|
||||
@ -343,24 +387,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="media/js/utils.js"></script>
|
||||
<script src="media/js/parameters.js"></script>
|
||||
<script src="media/js/plugins.js"></script>
|
||||
<script src="media/js/inpainting-editor.js"></script>
|
||||
<script src="media/js/image-modifiers.js"></script>
|
||||
<script src="media/js/auto-save.js"></script>
|
||||
<script src="media/js/main.js"></script>
|
||||
<script src="media/js/themes.js"></script>
|
||||
<script src="media/js/dnd.js"></script>
|
||||
<script src="media/js/image-editor.js"></script>
|
||||
<script>
|
||||
async function init() {
|
||||
await initSettings()
|
||||
await getModels()
|
||||
await getDiskPath()
|
||||
await getAppConfig()
|
||||
await loadModifiers()
|
||||
await loadUIPlugins()
|
||||
await loadModifiers()
|
||||
await getSystemInfo()
|
||||
|
||||
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
||||
|
5
ui/media/css/drawingboard.min.css
vendored
5
ui/media/css/drawingboard.min.css
vendored
File diff suppressed because one or more lines are too long
211
ui/media/css/image-editor.css
Normal file
211
ui/media/css/image-editor.css
Normal file
@ -0,0 +1,211 @@
|
||||
.editor-controls-left {
|
||||
padding-left: 32px;
|
||||
text-align: left;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.editor-options-container {
|
||||
display: flex;
|
||||
row-gap: 10px;
|
||||
max-width: 210px;
|
||||
}
|
||||
|
||||
.editor-options-container > * {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-options-container > * > * {
|
||||
position: inherit;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background: var(--background-color3);
|
||||
cursor: pointer;
|
||||
transition: opacity 0.25s;
|
||||
}
|
||||
.editor-options-container > * > *:hover {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.editor-options-container > * > *.active {
|
||||
border: 2px solid #3584e4;
|
||||
}
|
||||
|
||||
.image_editor_opacity .editor-options-container > * > *:not(.active) {
|
||||
border: 1px solid var(--background-color3);
|
||||
}
|
||||
|
||||
.image_editor_color .editor-options-container {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.image_editor_color .editor-options-container > * {
|
||||
flex: 20%;
|
||||
}
|
||||
.image_editor_color .editor-options-container > * > * {
|
||||
position: relative;
|
||||
}
|
||||
.image_editor_color .editor-options-container > * > *.active::before {
|
||||
content: "\f00c";
|
||||
display: var(--fa-display,inline-block);
|
||||
font-style: normal;
|
||||
font-variant: normal;
|
||||
line-height: 1;
|
||||
text-rendering: auto;
|
||||
font-family: var(--fa-style-family, "Font Awesome 6 Free");
|
||||
font-weight: var(--fa-style, 900);
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%) scale(125%);
|
||||
color: black;
|
||||
}
|
||||
.image_editor_color .editor-options-container > *:first-child {
|
||||
flex: 100%;
|
||||
}
|
||||
.image_editor_color .editor-options-container > *:first-child > * {
|
||||
width: 100%;
|
||||
}
|
||||
.image_editor_color .editor-options-container > *:first-child > * > input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.image_editor_color .editor-options-container > *:first-child > * > span {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
.image_editor_color .editor-options-container > *:first-child > *.active > span {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.image_editor_tool .editor-options-container {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.image_editor_tool .editor-options-container > * {
|
||||
padding: 2px;
|
||||
flex: 50%;
|
||||
}
|
||||
|
||||
.editor-controls-center {
|
||||
/* background: var(--background-color2); */
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-controls-center > div {
|
||||
position: relative;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.editor-controls-center canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.editor-controls-right {
|
||||
padding: 32px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
|
||||
.editor-controls-right > div:last-child {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 200px;
|
||||
gap: 5px;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.image-editor-button {
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
border-radius: 16px;
|
||||
background: var(--background-color3);
|
||||
}
|
||||
|
||||
#init_image_button_inpaint .input-toggle {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
}
|
||||
|
||||
#init_image_button_inpaint .input-toggle input:not(:checked) ~ label {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
|
||||
.image-editor-popup {
|
||||
--popup-margin: 16px;
|
||||
--popup-padding: 24px;
|
||||
}
|
||||
|
||||
.image-editor-popup > div {
|
||||
margin: var(--popup-margin);
|
||||
padding: var(--popup-padding);
|
||||
min-height: calc(100vh - (2 * var(--popup-margin)));
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.image-editor-popup h1 {
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.image-editor-popup > div {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.image-editor-popup h1 {
|
||||
position: relative;
|
||||
transform: none;
|
||||
left: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.image-editor-popup > div > div {
|
||||
min-height: calc(100vh - (2 * var(--popup-margin)) - (2 * var(--popup-padding)));
|
||||
}
|
||||
|
||||
.inpainter .image_editor_color {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inpainter .editor-canvas-background {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
#init_image_preview_container .button {
|
||||
display: flex;
|
||||
padding: 6px;
|
||||
height: 24px;
|
||||
box-shadow: 2px 2px 1px 1px #00000088;
|
||||
}
|
||||
|
||||
#init_image_preview_container .button:hover {
|
||||
background: var(--background-color4)
|
||||
}
|
||||
|
||||
.image-editor-popup .button {
|
||||
display: flex;
|
||||
}
|
||||
.image-editor-popup h4 {
|
||||
text-align: left;
|
||||
}
|
@ -44,9 +44,6 @@ code {
|
||||
margin-top: 5px;
|
||||
display: block;
|
||||
}
|
||||
.image_preview_container {
|
||||
margin-top: 10pt;
|
||||
}
|
||||
.image_clear_btn {
|
||||
position: absolute;
|
||||
transform: translate(30%, -30%);
|
||||
@ -275,32 +272,6 @@ img {
|
||||
transform: translateY(25%);
|
||||
}
|
||||
|
||||
#inpaintingEditor {
|
||||
width: 300pt;
|
||||
height: 300pt;
|
||||
margin-top: 5pt;
|
||||
}
|
||||
.drawing-board-canvas-wrapper {
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.drawing-board-controls {
|
||||
min-width: 273px;
|
||||
}
|
||||
.drawing-board-control > button {
|
||||
background-color: #eee;
|
||||
border-radius: 3pt;
|
||||
}
|
||||
.drawing-board-control-inner {
|
||||
background-color: #eee;
|
||||
border-radius: 3pt;
|
||||
}
|
||||
#inpaintingEditor canvas {
|
||||
opacity: 0.6;
|
||||
}
|
||||
#enable_mask {
|
||||
margin-top: 8pt;
|
||||
}
|
||||
|
||||
#top-nav {
|
||||
position: relative;
|
||||
background: var(--background-color4);
|
||||
@ -484,8 +455,58 @@ img {
|
||||
#prompt_from_file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#init_image_preview_container {
|
||||
display: flex;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#init_image_preview_container:not(.has-image) #init_image_wrapper,
|
||||
#init_image_preview_container:not(.has-image) #inpaint_button_container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
||||
#init_image_buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#init_image_preview_container.has-image #init_image_buttons {
|
||||
flex-direction: column;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
#init_image_buttons .button {
|
||||
position: relative;
|
||||
height: 32px;
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
#init_image_buttons .button > input {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#inpaint_button_container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
#init_image_wrapper {
|
||||
grid-row: span 3;
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
#init_image_preview {
|
||||
max-width: 150px;
|
||||
max-height: 150px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
@ -493,23 +514,18 @@ img {
|
||||
border-radius: 6px;
|
||||
transition: all 1s ease-in-out;
|
||||
}
|
||||
|
||||
/*
|
||||
#init_image_preview:hover {
|
||||
max-width: 500px;
|
||||
max-height: 1000px;
|
||||
|
||||
transition: all 1s 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
#init_image_wrapper {
|
||||
position: relative;
|
||||
width: fit-content;
|
||||
}
|
||||
} */
|
||||
|
||||
#init_image_size_box {
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
bottom: 3px;
|
||||
bottom: 0px;
|
||||
padding: 3px;
|
||||
background: black;
|
||||
color: white;
|
||||
@ -561,6 +577,10 @@ option {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input[type="file"] * {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
select,
|
||||
textarea {
|
||||
@ -599,12 +619,26 @@ input[type="file"] {
|
||||
}
|
||||
|
||||
button,
|
||||
input::file-selector-button {
|
||||
input::file-selector-button,
|
||||
.button {
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
border-radius: var(--input-border-radius);
|
||||
background: var(--button-color);
|
||||
color: var(--button-text-color);
|
||||
border: var(--button-border);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
button:hover,
|
||||
.button:hover {
|
||||
transition-duration: 0.1s;
|
||||
background: hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) + 6%));
|
||||
}
|
||||
|
||||
input::file-selector-button {
|
||||
@ -768,6 +802,8 @@ input::file-selector-button {
|
||||
|
||||
#promptsFromFileBtn {
|
||||
font-size: 9pt;
|
||||
display: inline;
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.section-button {
|
||||
@ -969,8 +1005,8 @@ input::file-selector-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#tab-content-wrapper {
|
||||
border-top: 8px solid var(--background-color1);
|
||||
#tab-content-wrapper > * {
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.tab-content-inner {
|
||||
@ -1007,10 +1043,6 @@ i.active {
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
button:hover {
|
||||
transition-duration: 0.1s;
|
||||
background: hsl(var(--accent-hue), 100%, calc(var(--accent-lightness) + 6%));
|
||||
}
|
||||
|
||||
button:active {
|
||||
transition-duration: 0.1s;
|
||||
|
@ -19,7 +19,7 @@
|
||||
--input-border-color: var(--background-color4);
|
||||
|
||||
--button-text-color: var(--input-text-color);
|
||||
--button-color: var(--accent-color);
|
||||
--button-color: var(--input-background-color);
|
||||
--button-border: none;
|
||||
|
||||
/* other */
|
||||
@ -42,7 +42,6 @@
|
||||
--background-color4: #cccccc;
|
||||
|
||||
--text-color: black;
|
||||
--button-text-color: white;
|
||||
|
||||
--input-text-color: black;
|
||||
--input-background-color: #f8f9fa;
|
||||
@ -134,6 +133,7 @@
|
||||
--input-border-color: #005E05;
|
||||
}
|
||||
|
||||
|
||||
.theme-gnomie {
|
||||
--background-color1: #242424;
|
||||
--background-color2: #353535;
|
||||
@ -157,5 +157,4 @@
|
||||
border: none;
|
||||
box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
}
|
BIN
ui/media/images/fa-eraser.png
Normal file
BIN
ui/media/images/fa-eraser.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
ui/media/images/fa-eye-dropper.png
Normal file
BIN
ui/media/images/fa-eye-dropper.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
ui/media/images/fa-pencil.png
Normal file
BIN
ui/media/images/fa-pencil.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -21,6 +21,7 @@ const SETTINGS_IDS_LIST = [
|
||||
"guidance_scale",
|
||||
"prompt_strength",
|
||||
"output_format",
|
||||
"output_quality",
|
||||
"negative_prompt",
|
||||
"stream_image_progress",
|
||||
"use_face_correction",
|
||||
|
@ -85,13 +85,14 @@ const TASK_MAPPING = {
|
||||
if (!seed) {
|
||||
randomSeedField.checked = true
|
||||
seedField.disabled = true
|
||||
seedField.value = 0
|
||||
return
|
||||
}
|
||||
randomSeedField.checked = false
|
||||
seedField.disabled = false
|
||||
seedField.value = seed
|
||||
},
|
||||
readUI: () => (randomSeedField.checked ? Math.floor(Math.random() * 10000000) : parseInt(seedField.value)),
|
||||
readUI: () => parseInt(seedField.value), // just return the value the user is seeing in the UI
|
||||
parse: (val) => parseInt(val)
|
||||
},
|
||||
num_inference_steps: { name: 'Steps',
|
||||
@ -127,10 +128,12 @@ const TASK_MAPPING = {
|
||||
},
|
||||
mask: { name: 'Mask',
|
||||
setUI: (mask) => {
|
||||
inpaintingEditor.setImg(mask)
|
||||
setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
||||
imageInpainter.setImg(mask)
|
||||
}, 250)
|
||||
maskSetting.checked = Boolean(mask)
|
||||
},
|
||||
readUI: () => (maskSetting.checked ? inpaintingEditor.getImg() : undefined),
|
||||
readUI: () => (maskSetting.checked ? imageInpainter.getImg() : undefined),
|
||||
parse: (val) => val
|
||||
},
|
||||
|
||||
@ -289,18 +292,11 @@ function restoreTaskToUI(task, fieldsToSkip) {
|
||||
// Show the source picture if present
|
||||
initImagePreview.src = (task.reqBody.init_image == undefined ? '' : task.reqBody.init_image)
|
||||
if (IMAGE_REGEX.test(initImagePreview.src)) {
|
||||
Boolean(task.reqBody.mask) ? inpaintingEditor.setImg(task.reqBody.mask) : inpaintingEditor.resetBackground()
|
||||
initImagePreviewContainer.style.display = 'block'
|
||||
inpaintingEditorContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = 'table-row'
|
||||
//samplerSelectionContainer.style.display = 'none'
|
||||
// maskSetting.checked = false
|
||||
inpaintingEditorContainer.style.display = maskSetting.checked ? 'block' : 'none'
|
||||
} else {
|
||||
initImagePreviewContainer.style.display = 'none'
|
||||
// inpaintingEditorContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = 'none'
|
||||
// maskSetting.style.display = 'none'
|
||||
if (Boolean(task.reqBody.mask)) {
|
||||
setTimeout(() => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
|
||||
imageInpainter.setImg(task.reqBody.mask)
|
||||
}, 250)
|
||||
}
|
||||
}
|
||||
}
|
||||
function readUI() {
|
||||
|
4
ui/media/js/drawingboard.min.js
vendored
4
ui/media/js/drawingboard.min.js
vendored
File diff suppressed because one or more lines are too long
677
ui/media/js/image-editor.js
Normal file
677
ui/media/js/image-editor.js
Normal file
@ -0,0 +1,677 @@
|
||||
var editorControlsLeft = document.getElementById("image-editor-controls-left")
|
||||
|
||||
const IMAGE_EDITOR_MAX_SIZE = 800
|
||||
|
||||
const IMAGE_EDITOR_BUTTONS = [
|
||||
{
|
||||
name: "Cancel",
|
||||
icon: "fa-regular fa-circle-xmark",
|
||||
handler: editor => {
|
||||
editor.hide()
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "Save",
|
||||
icon: "fa-solid fa-floppy-disk",
|
||||
handler: editor => {
|
||||
editor.saveImage()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
const defaultToolBegin = (editor, ctx, x, y, is_overlay = false) => {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x, y)
|
||||
}
|
||||
const defaultToolMove = (editor, ctx, x, y, is_overlay = false) => {
|
||||
ctx.lineTo(x, y)
|
||||
if (is_overlay) {
|
||||
ctx.clearRect(0, 0, editor.width, editor.height)
|
||||
ctx.stroke()
|
||||
}
|
||||
}
|
||||
const defaultToolEnd = (editor, ctx, x, y, is_overlay = false) => {
|
||||
ctx.stroke()
|
||||
if (is_overlay) {
|
||||
ctx.clearRect(0, 0, editor.width, editor.height)
|
||||
}
|
||||
}
|
||||
|
||||
const IMAGE_EDITOR_TOOLS = [
|
||||
{
|
||||
id: "draw",
|
||||
name: "Draw",
|
||||
icon: "fa-solid fa-pencil",
|
||||
cursor: "url(/media/images/fa-pencil.png) 0 24, pointer",
|
||||
begin: defaultToolBegin,
|
||||
move: defaultToolMove,
|
||||
end: defaultToolEnd
|
||||
},
|
||||
{
|
||||
id: "erase",
|
||||
name: "Erase",
|
||||
icon: "fa-solid fa-eraser",
|
||||
cursor: "url(/media/images/fa-eraser.png) 0 18, pointer",
|
||||
begin: defaultToolBegin,
|
||||
move: (editor, ctx, x, y, is_overlay = false) => {
|
||||
ctx.lineTo(x, y)
|
||||
if (is_overlay) {
|
||||
ctx.clearRect(0, 0, editor.width, editor.height)
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
ctx.globalAlpha = 1
|
||||
ctx.filter = "none"
|
||||
ctx.drawImage(editor.canvas_current, 0, 0)
|
||||
editor.setBrush(editor.layers.overlay)
|
||||
ctx.stroke()
|
||||
editor.canvas_current.style.opacity = 0
|
||||
}
|
||||
},
|
||||
end: (editor, ctx, x, y, is_overlay = false) => {
|
||||
ctx.stroke()
|
||||
if (is_overlay) {
|
||||
ctx.clearRect(0, 0, editor.width, editor.height)
|
||||
editor.canvas_current.style.opacity = ""
|
||||
}
|
||||
},
|
||||
setBrush: (editor, layer) => {
|
||||
layer.ctx.globalCompositeOperation = "destination-out"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "colorpicker",
|
||||
name: "Color Picker",
|
||||
icon: "fa-solid fa-eye-dropper",
|
||||
cursor: "url(/media/images/fa-eye-dropper.png) 0 24, pointer",
|
||||
begin: (editor, ctx, x, y, is_overlay = false) => {
|
||||
var img_rgb = editor.layers.background.ctx.getImageData(x, y, 1, 1).data
|
||||
var drawn_rgb = editor.ctx_current.getImageData(x, y, 1, 1).data
|
||||
var drawn_opacity = drawn_rgb[3] / 255
|
||||
editor.custom_color_input.value = rgbToHex({
|
||||
r: (drawn_rgb[0] * drawn_opacity) + (img_rgb[0] * (1 - drawn_opacity)),
|
||||
g: (drawn_rgb[1] * drawn_opacity) + (img_rgb[1] * (1 - drawn_opacity)),
|
||||
b: (drawn_rgb[2] * drawn_opacity) + (img_rgb[2] * (1 - drawn_opacity)),
|
||||
})
|
||||
editor.custom_color_input.dispatchEvent(new Event("change"))
|
||||
},
|
||||
move: (editor, ctx, x, y, is_overlay = false) => {},
|
||||
end: (editor, ctx, x, y, is_overlay = false) => {}
|
||||
}
|
||||
]
|
||||
|
||||
const IMAGE_EDITOR_ACTIONS = [
|
||||
{
|
||||
id: "clear",
|
||||
name: "Clear",
|
||||
icon: "fa-solid fa-xmark",
|
||||
handler: (editor) => {
|
||||
editor.ctx_current.clearRect(0, 0, editor.width, editor.height)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
var IMAGE_EDITOR_SECTIONS = [
|
||||
{
|
||||
name: "tool",
|
||||
title: "Tool",
|
||||
default: "draw",
|
||||
options: Array.from(IMAGE_EDITOR_TOOLS.map(t => t.id)),
|
||||
initElement: (element, option) => {
|
||||
var tool_info = IMAGE_EDITOR_TOOLS.find(t => t.id == option)
|
||||
element.className = "image-editor-button button"
|
||||
var sub_element = document.createElement("div")
|
||||
var icon = document.createElement("i")
|
||||
tool_info.icon.split(" ").forEach(c => icon.classList.add(c))
|
||||
sub_element.appendChild(icon)
|
||||
sub_element.append(tool_info.name)
|
||||
element.appendChild(sub_element)
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "color",
|
||||
title: "Color",
|
||||
default: "#f1c232",
|
||||
options: [
|
||||
"custom",
|
||||
"#ea9999", "#e06666", "#cc0000", "#990000", "#660000",
|
||||
"#f9cb9c", "#f6b26b", "#e69138", "#b45f06", "#783f04",
|
||||
"#ffe599", "#ffd966", "#f1c232", "#bf9000", "#7f6000",
|
||||
"#b6d7a8", "#93c47d", "#6aa84f", "#38761d", "#274e13",
|
||||
"#a4c2f4", "#6d9eeb", "#3c78d8", "#1155cc", "#1c4587",
|
||||
"#b4a7d6", "#8e7cc3", "#674ea7", "#351c75", "#20124d",
|
||||
"#d5a6bd", "#c27ba0", "#a64d79", "#741b47", "#4c1130",
|
||||
"#ffffff", "#c0c0c0", "#838383", "#525252", "#000000",
|
||||
],
|
||||
initElement: (element, option) => {
|
||||
if (option == "custom") {
|
||||
var input = document.createElement("input")
|
||||
input.type = "color"
|
||||
element.appendChild(input)
|
||||
var span = document.createElement("span")
|
||||
span.textContent = "Custom"
|
||||
element.appendChild(span)
|
||||
}
|
||||
else {
|
||||
element.style.background = option
|
||||
}
|
||||
},
|
||||
getCustom: editor => {
|
||||
var input = editor.popup.querySelector(".image_editor_color input")
|
||||
return input.value
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "brush_size",
|
||||
title: "Brush Size",
|
||||
default: 48,
|
||||
options: [ 16, 24, 32, 48, 64 ],
|
||||
initElement: (element, option) => {
|
||||
element.parentElement.style.flex = option
|
||||
element.style.width = option + "px"
|
||||
element.style.height = option + "px"
|
||||
element.style["border-radius"] = (option / 2).toFixed() + "px"
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "opacity",
|
||||
title: "Opacity",
|
||||
default: 0,
|
||||
options: [ 0, 0.2, 0.4, 0.6, 0.8 ],
|
||||
initElement: (element, option) => {
|
||||
element.style.background = `repeating-conic-gradient(rgba(0, 0, 0, ${option}) 0% 25%, rgba(255, 255, 255, ${option}) 0% 50%) 50% / 10px 10px`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: "sharpness",
|
||||
title: "Sharpness",
|
||||
default: 0,
|
||||
options: [ 0, 0.05, 0.1, 0.2, 0.3 ],
|
||||
initElement: (element, option) => {
|
||||
var size = 32
|
||||
var blur_amount = parseInt(option * size)
|
||||
var sub_element = document.createElement("div")
|
||||
sub_element.style.background = `var(--background-color3)`
|
||||
sub_element.style.filter = `blur(${blur_amount}px)`
|
||||
sub_element.style.width = `${size - 4}px`
|
||||
sub_element.style.height = `${size - 4}px`
|
||||
sub_element.style['border-radius'] = `${size}px`
|
||||
element.style.background = "none"
|
||||
element.appendChild(sub_element)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
class EditorHistory {
|
||||
constructor(editor) {
|
||||
this.editor = editor
|
||||
this.events = [] // stack of all events (actions/edits)
|
||||
this.current_edit = null
|
||||
this.rewind_index = 0 // how many events back into the history we've rewound to. (current state is just after event at index 'length - this.rewind_index - 1')
|
||||
}
|
||||
push(event) {
|
||||
// probably add something here eventually to save state every x events
|
||||
if (this.rewind_index != 0) {
|
||||
this.events = this.events.slice(0, 0 - this.rewind_index)
|
||||
this.rewind_index = 0
|
||||
}
|
||||
var snapshot_frequency = 20 // (every x edits, take a snapshot of the current drawing state, for faster rewinding)
|
||||
if (this.events.length > 0 && this.events.length % snapshot_frequency == 0) {
|
||||
event.snapshot = this.editor.layers.drawing.ctx.getImageData(0, 0, this.editor.width, this.editor.height)
|
||||
}
|
||||
this.events.push(event)
|
||||
}
|
||||
pushAction(action) {
|
||||
this.push({
|
||||
type: "action",
|
||||
id: action
|
||||
});
|
||||
}
|
||||
editBegin(x, y) {
|
||||
this.current_edit = {
|
||||
type: "edit",
|
||||
id: this.editor.getOptionValue("tool"),
|
||||
options: Object.assign({}, this.editor.options),
|
||||
points: [ { x: x, y: y } ]
|
||||
}
|
||||
}
|
||||
editMove(x, y) {
|
||||
if (this.current_edit) {
|
||||
this.current_edit.points.push({ x: x, y: y })
|
||||
}
|
||||
}
|
||||
editEnd(x, y) {
|
||||
if (this.current_edit) {
|
||||
this.push(this.current_edit)
|
||||
this.current_edit = null
|
||||
}
|
||||
}
|
||||
clear() {
|
||||
this.events = []
|
||||
}
|
||||
undo() {
|
||||
this.rewindTo(this.rewind_index + 1)
|
||||
}
|
||||
redo() {
|
||||
this.rewindTo(this.rewind_index - 1)
|
||||
}
|
||||
rewindTo(new_rewind_index) {
|
||||
if (new_rewind_index < 0 || new_rewind_index > this.events.length) {
|
||||
return; // do nothing if target index is out of bounds
|
||||
}
|
||||
|
||||
var ctx = this.editor.layers.drawing.ctx
|
||||
ctx.clearRect(0, 0, this.editor.width, this.editor.height)
|
||||
|
||||
var target_index = this.events.length - 1 - new_rewind_index
|
||||
var snapshot_index = target_index
|
||||
while (snapshot_index > -1) {
|
||||
if (this.events[snapshot_index].snapshot) {
|
||||
break
|
||||
}
|
||||
snapshot_index--
|
||||
}
|
||||
|
||||
if (snapshot_index != -1) {
|
||||
ctx.putImageData(this.events[snapshot_index].snapshot, 0, 0);
|
||||
}
|
||||
|
||||
for (var i = (snapshot_index + 1); i <= target_index; i++) {
|
||||
var event = this.events[i]
|
||||
if (event.type == "action") {
|
||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == event.id)
|
||||
action.handler(this.editor)
|
||||
}
|
||||
else if (event.type == "edit") {
|
||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == event.id)
|
||||
this.editor.setBrush(this.editor.layers.drawing, event.options)
|
||||
|
||||
var first_point = event.points[0]
|
||||
tool.begin(this.editor, ctx, first_point.x, first_point.y)
|
||||
for (var point_i = 1; point_i < event.points.length; point_i++) {
|
||||
tool.move(this.editor, ctx, event.points[point_i].x, event.points[point_i].y)
|
||||
}
|
||||
var last_point = event.points[event.points.length - 1]
|
||||
tool.end(this.editor, ctx, last_point.x, last_point.y)
|
||||
}
|
||||
}
|
||||
|
||||
// re-set brush to current settings
|
||||
this.editor.setBrush(this.editor.layers.drawing)
|
||||
|
||||
this.rewind_index = new_rewind_index
|
||||
}
|
||||
}
|
||||
|
||||
class ImageEditor {
|
||||
constructor(popup, inpainter = false) {
|
||||
this.inpainter = inpainter
|
||||
this.popup = popup
|
||||
this.history = new EditorHistory(this)
|
||||
if (inpainter) {
|
||||
this.popup.classList.add("inpainter")
|
||||
}
|
||||
this.drawing = false
|
||||
this.temp_previous_tool = null // used for the ctrl-colorpicker functionality
|
||||
this.container = popup.querySelector(".editor-controls-center > div")
|
||||
this.layers = {}
|
||||
var layer_names = [
|
||||
"background",
|
||||
"drawing",
|
||||
"overlay"
|
||||
]
|
||||
layer_names.forEach(name => {
|
||||
let canvas = document.createElement("canvas")
|
||||
canvas.className = `editor-canvas-${name}`
|
||||
this.container.appendChild(canvas)
|
||||
this.layers[name] = {
|
||||
name: name,
|
||||
canvas: canvas,
|
||||
ctx: canvas.getContext("2d")
|
||||
}
|
||||
})
|
||||
|
||||
// add mouse handlers
|
||||
this.container.addEventListener("mousedown", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("mouseup", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("mousemove", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("mouseout", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("mouseenter", this.mouseHandler.bind(this))
|
||||
|
||||
this.container.addEventListener("touchstart", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("touchmove", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("touchcancel", this.mouseHandler.bind(this))
|
||||
this.container.addEventListener("touchend", this.mouseHandler.bind(this))
|
||||
|
||||
// initialize editor controls
|
||||
this.options = {}
|
||||
this.optionElements = {}
|
||||
IMAGE_EDITOR_SECTIONS.forEach(section => {
|
||||
section.id = `image_editor_${section.name}`
|
||||
var sectionElement = document.createElement("div")
|
||||
sectionElement.className = section.id
|
||||
|
||||
var title = document.createElement("h4")
|
||||
title.innerText = section.title
|
||||
sectionElement.appendChild(title)
|
||||
|
||||
var optionsContainer = document.createElement("div")
|
||||
optionsContainer.classList.add("editor-options-container")
|
||||
|
||||
this.optionElements[section.name] = []
|
||||
section.options.forEach((option, index) => {
|
||||
var optionHolder = document.createElement("div")
|
||||
var optionElement = document.createElement("div")
|
||||
optionHolder.appendChild(optionElement)
|
||||
section.initElement(optionElement, option)
|
||||
optionElement.addEventListener("click", target => this.selectOption(section.name, index))
|
||||
optionsContainer.appendChild(optionHolder)
|
||||
this.optionElements[section.name].push(optionElement)
|
||||
})
|
||||
this.selectOption(section.name, section.options.indexOf(section.default))
|
||||
|
||||
sectionElement.appendChild(optionsContainer)
|
||||
|
||||
this.popup.querySelector(".editor-controls-left").appendChild(sectionElement)
|
||||
})
|
||||
|
||||
this.custom_color_input = this.popup.querySelector(`input[type="color"]`)
|
||||
this.custom_color_input.addEventListener("change", () => {
|
||||
this.custom_color_input.parentElement.style.background = this.custom_color_input.value
|
||||
this.selectOption("color", 0)
|
||||
})
|
||||
|
||||
if (this.inpainter) {
|
||||
this.selectOption("color", IMAGE_EDITOR_SECTIONS.find(s => s.name == "color").options.indexOf("#ffffff"))
|
||||
}
|
||||
|
||||
// initialize the right-side controls
|
||||
var buttonContainer = document.createElement("div")
|
||||
IMAGE_EDITOR_BUTTONS.forEach(button => {
|
||||
var element = document.createElement("div")
|
||||
var icon = document.createElement("i")
|
||||
element.className = "image-editor-button button"
|
||||
icon.className = button.icon
|
||||
element.appendChild(icon)
|
||||
element.append(button.name)
|
||||
buttonContainer.appendChild(element)
|
||||
element.addEventListener("click", event => button.handler(this))
|
||||
})
|
||||
var actionsContainer = document.createElement("div")
|
||||
var actionsTitle = document.createElement("h4")
|
||||
actionsTitle.textContent = "Actions"
|
||||
actionsContainer.appendChild(actionsTitle);
|
||||
IMAGE_EDITOR_ACTIONS.forEach(action => {
|
||||
var element = document.createElement("div")
|
||||
var icon = document.createElement("i")
|
||||
element.className = "image-editor-button button"
|
||||
icon.className = action.icon
|
||||
element.appendChild(icon)
|
||||
element.append(action.name)
|
||||
actionsContainer.appendChild(element)
|
||||
element.addEventListener("click", event => this.runAction(action.id))
|
||||
})
|
||||
this.popup.querySelector(".editor-controls-right").appendChild(actionsContainer)
|
||||
this.popup.querySelector(".editor-controls-right").appendChild(buttonContainer)
|
||||
|
||||
this.keyHandlerBound = this.keyHandler.bind(this)
|
||||
|
||||
this.setSize(512, 512)
|
||||
}
|
||||
show() {
|
||||
this.popup.classList.add("active")
|
||||
document.addEventListener("keydown", this.keyHandlerBound)
|
||||
document.addEventListener("keyup", this.keyHandlerBound)
|
||||
}
|
||||
hide() {
|
||||
this.popup.classList.remove("active")
|
||||
document.removeEventListener("keydown", this.keyHandlerBound)
|
||||
document.removeEventListener("keyup", this.keyHandlerBound)
|
||||
}
|
||||
setSize(width, height) {
|
||||
if (width == this.width && height == this.height) {
|
||||
return
|
||||
}
|
||||
|
||||
var max_size = Math.min(parseInt(window.innerWidth * 0.9), width, 768)
|
||||
if (width > height) {
|
||||
var multiplier = max_size / width
|
||||
width = (multiplier * width).toFixed()
|
||||
height = (multiplier * height).toFixed()
|
||||
}
|
||||
else {
|
||||
var multiplier = max_size / height
|
||||
width = (multiplier * width).toFixed()
|
||||
height = (multiplier * height).toFixed()
|
||||
}
|
||||
this.width = width
|
||||
this.height = height
|
||||
|
||||
this.container.style.width = width + "px"
|
||||
this.container.style.height = height + "px"
|
||||
|
||||
Object.values(this.layers).forEach(layer => {
|
||||
layer.canvas.width = width
|
||||
layer.canvas.height = height
|
||||
})
|
||||
|
||||
if (this.inpainter) {
|
||||
this.saveImage() // We've reset the size of the image so inpainting is different
|
||||
}
|
||||
this.setBrush()
|
||||
this.history.clear()
|
||||
}
|
||||
get tool() {
|
||||
var tool_id = this.getOptionValue("tool")
|
||||
return IMAGE_EDITOR_TOOLS.find(t => t.id == tool_id);
|
||||
}
|
||||
loadTool() {
|
||||
this.drawing = false
|
||||
this.container.style.cursor = this.tool.cursor;
|
||||
}
|
||||
setImage(url, width, height) {
|
||||
this.setSize(width, height)
|
||||
this.layers.drawing.ctx.clearRect(0, 0, this.width, this.height)
|
||||
this.layers.background.ctx.clearRect(0, 0, this.width, this.height)
|
||||
if (url) {
|
||||
var image = new Image()
|
||||
image.onload = () => {
|
||||
this.layers.background.ctx.drawImage(image, 0, 0, this.width, this.height)
|
||||
}
|
||||
image.src = url
|
||||
}
|
||||
else {
|
||||
this.layers.background.ctx.fillStyle = "#ffffff"
|
||||
this.layers.background.ctx.beginPath()
|
||||
this.layers.background.ctx.rect(0, 0, this.width, this.height)
|
||||
this.layers.background.ctx.fill()
|
||||
}
|
||||
this.history.clear()
|
||||
}
|
||||
saveImage() {
|
||||
if (!this.inpainter) {
|
||||
// This is not an inpainter, so save the image as the new img2img input
|
||||
this.layers.background.ctx.drawImage(this.layers.drawing.canvas, 0, 0, this.width, this.height)
|
||||
var base64 = this.layers.background.canvas.toDataURL()
|
||||
initImagePreview.src = base64 // this will trigger the rest of the app to use it
|
||||
}
|
||||
else {
|
||||
// This is an inpainter, so make sure the toggle is set accordingly
|
||||
var is_blank = !this.layers.drawing.ctx
|
||||
.getImageData(0, 0, this.width, this.height).data
|
||||
.some(channel => channel !== 0)
|
||||
maskSetting.checked = !is_blank
|
||||
}
|
||||
this.hide()
|
||||
}
|
||||
getImg() { // a drop-in replacement of the drawingboard version
|
||||
return this.layers.drawing.canvas.toDataURL()
|
||||
}
|
||||
setImg(dataUrl) { // a drop-in replacement of the drawingboard version
|
||||
var image = new Image()
|
||||
image.onload = () => {
|
||||
var ctx = this.layers.drawing.ctx;
|
||||
ctx.clearRect(0, 0, this.width, this.height)
|
||||
ctx.globalCompositeOperation = "source-over"
|
||||
ctx.globalAlpha = 1
|
||||
ctx.filter = "none"
|
||||
ctx.drawImage(image, 0, 0, this.width, this.height)
|
||||
this.setBrush(this.layers.drawing)
|
||||
}
|
||||
image.src = dataUrl
|
||||
}
|
||||
runAction(action_id) {
|
||||
var action = IMAGE_EDITOR_ACTIONS.find(a => a.id == action_id)
|
||||
this.history.pushAction(action_id)
|
||||
action.handler(this)
|
||||
}
|
||||
setBrush(layer = null, options = null) {
|
||||
if (options == null) {
|
||||
options = this.options
|
||||
}
|
||||
if (layer) {
|
||||
layer.ctx.lineCap = "round"
|
||||
layer.ctx.lineJoin = "round"
|
||||
layer.ctx.lineWidth = options.brush_size
|
||||
layer.ctx.fillStyle = options.color
|
||||
layer.ctx.strokeStyle = options.color
|
||||
var sharpness = parseInt(options.sharpness * options.brush_size)
|
||||
layer.ctx.filter = sharpness == 0 ? `none` : `blur(${sharpness}px)`
|
||||
layer.ctx.globalAlpha = (1 - options.opacity)
|
||||
layer.ctx.globalCompositeOperation = "source-over"
|
||||
var tool = IMAGE_EDITOR_TOOLS.find(t => t.id == options.tool)
|
||||
if (tool && tool.setBrush) {
|
||||
tool.setBrush(editor, layer)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object.values([ "drawing", "overlay" ]).map(name => this.layers[name]).forEach(l => {
|
||||
this.setBrush(l)
|
||||
})
|
||||
}
|
||||
}
|
||||
get ctx_overlay() {
|
||||
return this.layers.overlay.ctx
|
||||
}
|
||||
get ctx_current() { // the idea is this will help support having custom layers and editing each one
|
||||
return this.layers.drawing.ctx
|
||||
}
|
||||
get canvas_current() {
|
||||
return this.layers.drawing.canvas
|
||||
}
|
||||
keyHandler(event) { // handles keybinds like ctrl+z, ctrl+y
|
||||
if (!this.popup.classList.contains("active")) {
|
||||
document.removeEventListener("keydown", this.keyHandlerBound)
|
||||
document.removeEventListener("keyup", this.keyHandlerBound)
|
||||
return // this catches if something else closes the window but doesnt properly unbind the key handler
|
||||
}
|
||||
|
||||
// keybindings
|
||||
if (event.type == "keydown") {
|
||||
if ((event.key == "z" || event.key == "Z") && event.ctrlKey) {
|
||||
if (!event.shiftKey) {
|
||||
this.history.undo()
|
||||
}
|
||||
else {
|
||||
this.history.redo()
|
||||
}
|
||||
}
|
||||
if (event.key == "y" && event.ctrlKey) {
|
||||
this.history.redo()
|
||||
}
|
||||
}
|
||||
|
||||
// dropper ctrl holding handler stuff
|
||||
var dropper_active = this.temp_previous_tool != null;
|
||||
if (dropper_active && !event.ctrlKey) {
|
||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == this.temp_previous_tool))
|
||||
this.temp_previous_tool = null
|
||||
}
|
||||
else if (!dropper_active && event.ctrlKey) {
|
||||
this.temp_previous_tool = this.getOptionValue("tool")
|
||||
this.selectOption("tool", IMAGE_EDITOR_TOOLS.findIndex(t => t.id == "colorpicker"))
|
||||
}
|
||||
}
|
||||
mouseHandler(event) {
|
||||
var bbox = this.layers.overlay.canvas.getBoundingClientRect()
|
||||
var x = (event.clientX || 0) - bbox.left
|
||||
var y = (event.clientY || 0) - bbox.top
|
||||
var type = event.type;
|
||||
var touchmap = {
|
||||
touchstart: "mousedown",
|
||||
touchmove: "mousemove",
|
||||
touchend: "mouseup",
|
||||
touchcancel: "mouseup"
|
||||
}
|
||||
if (type in touchmap) {
|
||||
type = touchmap[type]
|
||||
if (event.touches && event.touches[0]) {
|
||||
var touch = event.touches[0]
|
||||
var x = (touch.clientX || 0) - bbox.left
|
||||
var y = (touch.clientY || 0) - bbox.top
|
||||
}
|
||||
}
|
||||
event.preventDefault()
|
||||
// do drawing-related stuff
|
||||
if (type == "mousedown" || (type == "mouseenter" && event.buttons == 1)) {
|
||||
this.drawing = true
|
||||
this.tool.begin(this, this.ctx_current, x, y)
|
||||
this.tool.begin(this, this.ctx_overlay, x, y, true)
|
||||
this.history.editBegin(x, y)
|
||||
}
|
||||
if (type == "mouseup" || type == "mousemove") {
|
||||
if (this.drawing) {
|
||||
if (x > 0 && y > 0) {
|
||||
this.tool.move(this, this.ctx_current, x, y)
|
||||
this.tool.move(this, this.ctx_overlay, x, y, true)
|
||||
this.history.editMove(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == "mouseup" || type == "mouseout") {
|
||||
if (this.drawing) {
|
||||
this.drawing = false
|
||||
this.tool.end(this, this.ctx_current, x, y)
|
||||
this.tool.end(this, this.ctx_overlay, x, y, true)
|
||||
this.history.editEnd(x, y)
|
||||
}
|
||||
}
|
||||
}
|
||||
getOptionValue(section_name) {
|
||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
||||
return this.options && section_name in this.options ? this.options[section_name] : section.default
|
||||
}
|
||||
selectOption(section_name, option_index) {
|
||||
var section = IMAGE_EDITOR_SECTIONS.find(s => s.name == section_name)
|
||||
var value = section.options[option_index]
|
||||
this.options[section_name] = value == "custom" ? section.getCustom(this) : value
|
||||
|
||||
this.optionElements[section_name].forEach(element => element.classList.remove("active"))
|
||||
this.optionElements[section_name][option_index].classList.add("active")
|
||||
|
||||
// change the editor
|
||||
this.setBrush()
|
||||
if (section.name == "tool") {
|
||||
this.loadTool()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function rgbToHex(rgb) {
|
||||
function componentToHex(c) {
|
||||
var hex = parseInt(c).toString(16)
|
||||
return hex.length == 1 ? "0" + hex : hex
|
||||
}
|
||||
return "#" + componentToHex(rgb.r) + componentToHex(rgb.g) + componentToHex(rgb.b)
|
||||
}
|
||||
|
||||
const imageEditor = new ImageEditor(document.getElementById("image-editor"))
|
||||
const imageInpainter = new ImageEditor(document.getElementById("image-inpainter"), true)
|
||||
|
||||
imageEditor.setImage(null, 512, 512)
|
||||
imageInpainter.setImage(null, 512, 512)
|
||||
|
||||
document.getElementById("init_image_button_draw").addEventListener("click", () => {
|
||||
imageEditor.show()
|
||||
})
|
||||
document.getElementById("init_image_button_inpaint").addEventListener("click", () => {
|
||||
imageInpainter.show()
|
||||
})
|
@ -91,7 +91,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) {
|
||||
if (activeTags.map(x => trimModifiers(x.name)).includes(trimmedName)) {
|
||||
// remove modifier from active array
|
||||
activeTags = activeTags.filter(x => trimModifiers(x.name) != trimmedName)
|
||||
toggleCardState(modifierCard, false)
|
||||
toggleCardState(trimmedName, false)
|
||||
} else {
|
||||
// add modifier to active array
|
||||
activeTags.push({
|
||||
@ -100,7 +100,7 @@ function createModifierGroup(modifierGroup, initiallyExpanded) {
|
||||
'originElement': modifierCard,
|
||||
'previews': modifierPreviews
|
||||
})
|
||||
toggleCardState(modifierCard, true)
|
||||
toggleCardState(trimmedName, true)
|
||||
}
|
||||
|
||||
refreshTagsList()
|
||||
@ -219,10 +219,10 @@ function refreshTagsList() {
|
||||
editorModifierTagsList.appendChild(tag.element)
|
||||
|
||||
tag.element.addEventListener('click', () => {
|
||||
let idx = activeTags.indexOf(tag)
|
||||
let idx = activeTags.findIndex(o => { return o.name === tag.name })
|
||||
|
||||
if (idx !== -1 && activeTags[idx].originElement !== undefined) {
|
||||
toggleCardState(activeTags[idx].originElement, false)
|
||||
if (idx !== -1) {
|
||||
toggleCardState(activeTags[idx].name, false)
|
||||
|
||||
activeTags.splice(idx, 1)
|
||||
refreshTagsList()
|
||||
@ -235,14 +235,21 @@ function refreshTagsList() {
|
||||
editorModifierTagsList.appendChild(brk)
|
||||
}
|
||||
|
||||
function toggleCardState(card, makeActive) {
|
||||
if (makeActive) {
|
||||
card.classList.add(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
} else {
|
||||
card.classList.remove(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
}
|
||||
function toggleCardState(modifierName, makeActive) {
|
||||
document.querySelector('#editor-modifiers').querySelectorAll('.modifier-card').forEach(card => {
|
||||
const name = card.querySelector('.modifier-card-label').innerText
|
||||
if ( trimModifiers(modifierName) == trimModifiers(name)
|
||||
|| trimModifiers(modifierName) == 'by ' + trimModifiers(name)) {
|
||||
if(makeActive) {
|
||||
card.classList.add(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '-'
|
||||
}
|
||||
else{
|
||||
card.classList.remove(activeCardClass)
|
||||
card.querySelector('.modifier-card-image-overlay').innerText = '+'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function changePreviewImages(val) {
|
||||
@ -319,31 +326,7 @@ function saveCustomModifiers() {
|
||||
}
|
||||
|
||||
function loadCustomModifiers() {
|
||||
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '')
|
||||
customModifiersTextBox.value = customModifiers
|
||||
|
||||
if (customModifiersGroupElement !== undefined) {
|
||||
customModifiersGroupElement.remove()
|
||||
}
|
||||
|
||||
if (customModifiers && customModifiers.trim() !== '') {
|
||||
customModifiers = customModifiers.split('\n')
|
||||
customModifiers = customModifiers.filter(m => m.trim() !== '')
|
||||
customModifiers = customModifiers.map(function(m) {
|
||||
return {
|
||||
"modifier": m
|
||||
}
|
||||
})
|
||||
|
||||
let customGroup = {
|
||||
'category': 'Custom Modifiers',
|
||||
'modifiers': customModifiers
|
||||
}
|
||||
|
||||
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
||||
|
||||
createCollapsibles(customModifiersGroupElement)
|
||||
}
|
||||
PLUGINS['MODIFIERS_LOAD'].forEach(fn=>fn.loader.call())
|
||||
}
|
||||
|
||||
customModifiersTextBox.addEventListener('change', saveCustomModifiers)
|
||||
|
@ -1,41 +0,0 @@
|
||||
const INPAINTING_EDITOR_SIZE = 450
|
||||
|
||||
let inpaintingEditorContainer = document.querySelector('#inpaintingEditor')
|
||||
let inpaintingEditor = new DrawingBoard.Board('inpaintingEditor', {
|
||||
color: "#ffffff",
|
||||
background: false,
|
||||
size: 30,
|
||||
webStorage: false,
|
||||
controls: [{'DrawingMode': {'filler': false}}, 'Size', 'Navigation']
|
||||
})
|
||||
let inpaintingEditorCanvasBackground = document.querySelector('.drawing-board-canvas-wrapper')
|
||||
|
||||
function resizeInpaintingEditor(widthValue, heightValue) {
|
||||
if (widthValue === heightValue) {
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
} else if (widthValue > heightValue) {
|
||||
heightValue = (heightValue / widthValue) * INPAINTING_EDITOR_SIZE
|
||||
widthValue = INPAINTING_EDITOR_SIZE
|
||||
} else {
|
||||
widthValue = (widthValue / heightValue) * INPAINTING_EDITOR_SIZE
|
||||
heightValue = INPAINTING_EDITOR_SIZE
|
||||
}
|
||||
if (inpaintingEditor.opts.aspectRatio === (widthValue / heightValue).toFixed(3)) {
|
||||
// Same ratio, don't reset the canvas.
|
||||
return
|
||||
}
|
||||
inpaintingEditor.opts.aspectRatio = (widthValue / heightValue).toFixed(3)
|
||||
|
||||
inpaintingEditorContainer.style.width = widthValue + 'px'
|
||||
inpaintingEditorContainer.style.height = heightValue + 'px'
|
||||
inpaintingEditor.opts.enlargeYourContainer = true
|
||||
|
||||
inpaintingEditor.opts.size = inpaintingEditor.ctx.lineWidth
|
||||
inpaintingEditor.resize()
|
||||
|
||||
inpaintingEditor.ctx.lineCap = "round"
|
||||
inpaintingEditor.ctx.lineJoin = "round"
|
||||
inpaintingEditor.ctx.lineWidth = inpaintingEditor.opts.size
|
||||
inpaintingEditor.setColor(inpaintingEditor.opts.color)
|
||||
}
|
@ -16,6 +16,9 @@ let numOutputsParallelField = document.querySelector('#num_outputs_parallel')
|
||||
let numInferenceStepsField = document.querySelector('#num_inference_steps')
|
||||
let guidanceScaleSlider = document.querySelector('#guidance_scale_slider')
|
||||
let guidanceScaleField = document.querySelector('#guidance_scale')
|
||||
let outputQualitySlider = document.querySelector('#output_quality_slider')
|
||||
let outputQualityField = document.querySelector('#output_quality')
|
||||
let outputQualityRow = document.querySelector('#output_quality_row')
|
||||
let randomSeedField = document.querySelector("#random_seed")
|
||||
let seedField = document.querySelector('#seed')
|
||||
let widthField = document.querySelector('#width')
|
||||
@ -59,14 +62,6 @@ let serverStatusColor = document.querySelector('#server-status-color')
|
||||
let serverStatusMsg = document.querySelector('#server-status-msg')
|
||||
|
||||
|
||||
document.querySelector('.drawing-board-control-navigation-back').innerHTML = '<i class="fa-solid fa-rotate-left"></i>'
|
||||
document.querySelector('.drawing-board-control-navigation-forward').innerHTML = '<i class="fa-solid fa-rotate-right"></i>'
|
||||
|
||||
let maskResetButton = document.querySelector('.drawing-board-control-navigation-reset')
|
||||
maskResetButton.innerHTML = 'Clear'
|
||||
maskResetButton.style.fontWeight = 'normal'
|
||||
maskResetButton.style.fontSize = '10pt'
|
||||
|
||||
let serverState = {'status': 'Offline', 'time': Date.now()}
|
||||
let bellPending = false
|
||||
|
||||
@ -335,11 +330,7 @@ function onUseAsInputClick(req, img) {
|
||||
initImageSelector.value = null
|
||||
initImagePreview.src = imgData
|
||||
|
||||
initImagePreviewContainer.style.display = 'block'
|
||||
inpaintingEditorContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = 'table-row'
|
||||
maskSetting.checked = false
|
||||
samplerSelectionContainer.style.display = 'none'
|
||||
}
|
||||
|
||||
function onDownloadImageClick(req, img) {
|
||||
@ -387,11 +378,21 @@ function onMakeSimilarClick(req, img) {
|
||||
function enqueueImageVariationTask(req, img, reqDiff) {
|
||||
const imageSeed = img.getAttribute('data-seed')
|
||||
|
||||
const newTaskRequest = modifyCurrentRequest(req, reqDiff, {
|
||||
const newRequestBody = {
|
||||
num_outputs: 1, // this can be user-configurable in the future
|
||||
seed: imageSeed
|
||||
})
|
||||
}
|
||||
|
||||
// If the user is editing pictures, stop modifyCurrentRequest from importing
|
||||
// new values by setting the missing properties to undefined
|
||||
if (!('init_image' in req) && !('init_image' in reqDiff)) {
|
||||
newRequestBody.init_image = undefined
|
||||
newRequestBody.mask = undefined
|
||||
} else if (!('mask' in req) && !('mask' in reqDiff)) {
|
||||
newRequestBody.mask = undefined
|
||||
}
|
||||
|
||||
const newTaskRequest = modifyCurrentRequest(req, reqDiff, newRequestBody)
|
||||
newTaskRequest.numOutputsTotal = 1 // this can be user-configurable in the future
|
||||
newTaskRequest.batchCount = 1
|
||||
|
||||
@ -696,6 +697,12 @@ async function checkTasks() {
|
||||
|
||||
const genSeeds = Boolean(typeof task.reqBody.seed !== 'number' || (task.reqBody.seed === task.seed && task.numOutputsTotal > 1))
|
||||
const startSeed = task.reqBody.seed || task.seed
|
||||
|
||||
// Update the seed *before* starting the processing so it's retained if user stops the task
|
||||
if (randomSeedField.checked) {
|
||||
seedField.value = task.seed
|
||||
}
|
||||
|
||||
for (let i = 0; i < task.batchCount; i++) {
|
||||
let newTask = task
|
||||
if (task.batchCount > 1) {
|
||||
@ -742,10 +749,6 @@ async function checkTasks() {
|
||||
}
|
||||
}
|
||||
|
||||
if (randomSeedField.checked) {
|
||||
seedField.value = task.seed
|
||||
}
|
||||
|
||||
currentTask = null
|
||||
|
||||
if (typeof requestIdleCallback === 'function') {
|
||||
@ -791,6 +794,7 @@ function getCurrentUserRequest() {
|
||||
stream_image_progress: (numOutputsTotal > 50 ? false : streamImageProgressField.checked),
|
||||
show_only_filtered_image: showOnlyFilteredImageField.checked,
|
||||
output_format: outputFormatField.value,
|
||||
output_quality: outputQualityField.value,
|
||||
original_prompt: promptField.value,
|
||||
active_tags: (activeTags.map(x => x.name))
|
||||
}
|
||||
@ -803,7 +807,7 @@ function getCurrentUserRequest() {
|
||||
// newTask.reqBody.mask = maskImagePreview.src
|
||||
// }
|
||||
if (maskSetting.checked) {
|
||||
newTask.reqBody.mask = inpaintingEditor.getImg()
|
||||
newTask.reqBody.mask = imageInpainter.getImg()
|
||||
}
|
||||
newTask.reqBody.sampler = 'ddim'
|
||||
} else {
|
||||
@ -936,8 +940,9 @@ function getPrompts() {
|
||||
prompts = prompts.map(prompt => prompt.trim())
|
||||
prompts = prompts.filter(prompt => prompt !== '')
|
||||
|
||||
if (activeTags.length > 0) {
|
||||
const promptTags = activeTags.map(x => x.name).join(", ")
|
||||
const newTags = activeTags.filter(tag => tag.inactive === undefined || tag.inactive === false)
|
||||
if (newTags.length > 0) {
|
||||
const promptTags = newTags.map(x => x.name).join(", ")
|
||||
prompts = prompts.map((prompt) => `${prompt}, ${promptTags}`)
|
||||
}
|
||||
|
||||
@ -1089,13 +1094,14 @@ numOutputsTotalField.addEventListener('change', renameMakeImageButton)
|
||||
numOutputsParallelField.addEventListener('change', renameMakeImageButton)
|
||||
|
||||
function onDimensionChange() {
|
||||
if (!maskSetting.checked) {
|
||||
return
|
||||
}
|
||||
let widthValue = parseInt(widthField.value)
|
||||
let heightValue = parseInt(heightField.value)
|
||||
|
||||
resizeInpaintingEditor(widthValue, heightValue)
|
||||
if (!initImagePreviewContainer.classList.contains("has-image")) {
|
||||
imageEditor.setImage(null, widthValue, heightValue)
|
||||
}
|
||||
else {
|
||||
imageInpainter.setImage(initImagePreview.src, widthValue, heightValue)
|
||||
}
|
||||
}
|
||||
|
||||
diskPathField.disabled = !saveToDiskField.checked
|
||||
@ -1114,6 +1120,7 @@ document.onkeydown = function(e) {
|
||||
}
|
||||
}
|
||||
|
||||
/********************* Guidance **************************/
|
||||
function updateGuidanceScale() {
|
||||
guidanceScaleField.value = guidanceScaleSlider.value / 10
|
||||
guidanceScaleField.dispatchEvent(new Event("change"))
|
||||
@ -1134,6 +1141,7 @@ guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
||||
guidanceScaleField.addEventListener('input', updateGuidanceScaleSlider)
|
||||
updateGuidanceScale()
|
||||
|
||||
/********************* Prompt Strength *******************/
|
||||
function updatePromptStrength() {
|
||||
promptStrengthField.value = promptStrengthSlider.value / 100
|
||||
promptStrengthField.dispatchEvent(new Event("change"))
|
||||
@ -1154,6 +1162,36 @@ promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
||||
promptStrengthField.addEventListener('input', updatePromptStrengthSlider)
|
||||
updatePromptStrength()
|
||||
|
||||
/********************* JPEG Quality **********************/
|
||||
function updateOutputQuality() {
|
||||
outputQualityField.value = 0 | outputQualitySlider.value
|
||||
outputQualityField.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
function updateOutputQualitySlider() {
|
||||
if (outputQualityField.value < 10) {
|
||||
outputQualityField.value = 10
|
||||
} else if (outputQualityField.value > 95) {
|
||||
outputQualityField.value = 95
|
||||
}
|
||||
|
||||
outputQualitySlider.value = 0 | outputQualityField.value
|
||||
outputQualitySlider.dispatchEvent(new Event("change"))
|
||||
}
|
||||
|
||||
outputQualitySlider.addEventListener('input', updateOutputQuality)
|
||||
outputQualityField.addEventListener('input', debounce(updateOutputQualitySlider))
|
||||
updateOutputQuality()
|
||||
|
||||
outputFormatField.addEventListener('change', e => {
|
||||
if (outputFormatField.value == 'jpeg') {
|
||||
outputQualityRow.style.display='table-row'
|
||||
} else {
|
||||
outputQualityRow.style.display='none'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
async function getModels() {
|
||||
try {
|
||||
var sd_model_setting_key = "stable_diffusion_model"
|
||||
@ -1208,7 +1246,7 @@ async function getModels() {
|
||||
function checkRandomSeed() {
|
||||
if (randomSeedField.checked) {
|
||||
seedField.disabled = true
|
||||
seedField.value = "0"
|
||||
//seedField.value = "0" // This causes the seed to be lost if the user changes their mind after toggling the checkbox
|
||||
} else {
|
||||
seedField.disabled = false
|
||||
}
|
||||
@ -1216,12 +1254,8 @@ function checkRandomSeed() {
|
||||
randomSeedField.addEventListener('input', checkRandomSeed)
|
||||
checkRandomSeed()
|
||||
|
||||
function showInitImagePreview() {
|
||||
function loadImg2ImgFromFile() {
|
||||
if (initImageSelector.files.length === 0) {
|
||||
initImagePreviewContainer.style.display = 'none'
|
||||
// inpaintingEditorContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = 'none'
|
||||
// maskSetting.style.display = 'none'
|
||||
return
|
||||
}
|
||||
|
||||
@ -1229,51 +1263,41 @@ function showInitImagePreview() {
|
||||
let file = initImageSelector.files[0]
|
||||
|
||||
reader.addEventListener('load', function(event) {
|
||||
// console.log(file.name, reader.result)
|
||||
initImagePreview.src = reader.result
|
||||
initImagePreviewContainer.style.display = 'block'
|
||||
inpaintingEditorContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = 'table-row'
|
||||
samplerSelectionContainer.style.display = 'none'
|
||||
// maskSetting.checked = false
|
||||
})
|
||||
|
||||
if (file) {
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
initImageSelector.addEventListener('change', showInitImagePreview)
|
||||
showInitImagePreview()
|
||||
initImageSelector.addEventListener('change', loadImg2ImgFromFile)
|
||||
loadImg2ImgFromFile()
|
||||
|
||||
function img2imgLoad() {
|
||||
promptStrengthContainer.style.display = 'table-row'
|
||||
samplerSelectionContainer.style.display = "none"
|
||||
initImagePreviewContainer.classList.add("has-image")
|
||||
|
||||
initImagePreview.addEventListener('load', function() {
|
||||
inpaintingEditorCanvasBackground.style.backgroundImage = "url('" + this.src + "')"
|
||||
// maskSetting.style.display = 'block'
|
||||
// inpaintingEditorContainer.style.display = 'block'
|
||||
initImageSizeBox.textContent = initImagePreview.naturalWidth + " x " + initImagePreview.naturalHeight
|
||||
initImageSizeBox.style.display = 'block'
|
||||
})
|
||||
imageEditor.setImage(this.src, initImagePreview.naturalWidth, initImagePreview.naturalHeight)
|
||||
imageInpainter.setImage(this.src, parseInt(widthField.value), parseInt(heightField.value))
|
||||
}
|
||||
|
||||
initImageClearBtn.addEventListener('click', function() {
|
||||
function img2imgUnload() {
|
||||
initImageSelector.value = null
|
||||
// maskImageSelector.value = null
|
||||
|
||||
initImagePreview.src = ''
|
||||
// maskImagePreview.src = ''
|
||||
maskSetting.checked = false
|
||||
|
||||
initImagePreviewContainer.style.display = 'none'
|
||||
// inpaintingEditorContainer.style.display = 'none'
|
||||
// maskImagePreviewContainer.style.display = 'none'
|
||||
promptStrengthContainer.style.display = "none"
|
||||
samplerSelectionContainer.style.display = ""
|
||||
initImagePreviewContainer.classList.remove("has-image")
|
||||
imageEditor.setImage(null, parseInt(widthField.value), parseInt(heightField.value))
|
||||
|
||||
// maskSetting.style.display = 'none'
|
||||
|
||||
promptStrengthContainer.style.display = 'none'
|
||||
samplerSelectionContainer.style.display = 'table-row'
|
||||
initImageSizeBox.style.display = 'none'
|
||||
})
|
||||
}
|
||||
initImagePreview.addEventListener('load', img2imgLoad)
|
||||
initImageClearBtn.addEventListener('click', img2imgUnload)
|
||||
|
||||
maskSetting.addEventListener('click', function() {
|
||||
inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none')
|
||||
onDimensionChange()
|
||||
})
|
||||
|
||||
@ -1313,9 +1337,22 @@ document.querySelectorAll('.popup').forEach(popup => {
|
||||
}
|
||||
})
|
||||
|
||||
var tabElements = [];
|
||||
var tabElements = []
|
||||
function selectTab(tab_id) {
|
||||
let tabInfo = tabElements.find(t => t.tab.id == tab_id)
|
||||
if (!tabInfo.tab.classList.contains("active")) {
|
||||
tabElements.forEach(info => {
|
||||
if (info.tab.classList.contains("active")) {
|
||||
info.tab.classList.toggle("active")
|
||||
info.content.classList.toggle("active")
|
||||
}
|
||||
})
|
||||
tabInfo.tab.classList.toggle("active")
|
||||
tabInfo.content.classList.toggle("active")
|
||||
}
|
||||
}
|
||||
function linkTabContents(tab) {
|
||||
var name = tab.id.replace("tab-", "");
|
||||
var name = tab.id.replace("tab-", "")
|
||||
var content = document.getElementById(`tab-content-${name}`)
|
||||
tabElements.push({
|
||||
name: name,
|
||||
@ -1323,18 +1360,7 @@ function linkTabContents(tab) {
|
||||
content: content
|
||||
})
|
||||
|
||||
tab.addEventListener("click", event => {
|
||||
if (!tab.classList.contains("active")) {
|
||||
tabElements.forEach(tabInfo => {
|
||||
if (tabInfo.tab.classList.contains("active")) {
|
||||
tabInfo.tab.classList.toggle("active")
|
||||
tabInfo.content.classList.toggle("active")
|
||||
}
|
||||
})
|
||||
tab.classList.toggle("active")
|
||||
content.classList.toggle("active")
|
||||
}
|
||||
})
|
||||
tab.addEventListener("click", event => selectTab(tab.id))
|
||||
}
|
||||
|
||||
document.querySelectorAll(".tab").forEach(linkTabContents)
|
||||
|
@ -24,7 +24,8 @@ const PLUGINS = {
|
||||
* }
|
||||
* })
|
||||
*/
|
||||
IMAGE_INFO_BUTTONS: []
|
||||
IMAGE_INFO_BUTTONS: [],
|
||||
MODIFIERS_LOAD: []
|
||||
}
|
||||
|
||||
async function loadUIPlugins() {
|
||||
|
@ -347,6 +347,16 @@ function asyncDelay(timeout) {
|
||||
})
|
||||
}
|
||||
|
||||
/* Simple debounce function, placeholder for the one in engine.js for simple use cases */
|
||||
function debounce(func, timeout = 300){
|
||||
let timer;
|
||||
return (...args) => {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => { func.apply(this, args); }, timeout);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function preventNonNumericalInput(e) {
|
||||
e = e || window.event;
|
||||
let charCode = (typeof e.which == "undefined") ? e.keyCode : e.which;
|
||||
|
@ -17,8 +17,17 @@
|
||||
prettifyInputs(document);
|
||||
let autoScroll = document.querySelector("#auto_scroll")
|
||||
|
||||
SETTINGS_IDS_LIST.push("auto_scroll")
|
||||
initSettings()
|
||||
/**
|
||||
* the use of initSettings() in the autoscroll plugin seems to be breaking the models dropdown and the save-to-disk folder field
|
||||
* in the settings tab. They're both blank, because they're being re-initialized. Their earlier values came from the API call,
|
||||
* but those values aren't stored in localStorage, since they aren't user-specified.
|
||||
* So when initSettings() is called a second time, it overwrites the values with an empty string.
|
||||
*
|
||||
* We could either rework how new components can register themselves to be auto-saved, without having to call initSettings() again.
|
||||
* Or we could move the autoscroll code into the main code, and include it in the list of fields in auto-save.js
|
||||
*/
|
||||
// SETTINGS_IDS_LIST.push("auto_scroll")
|
||||
// initSettings()
|
||||
|
||||
// observe for changes in the preview pane
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
|
31
ui/plugins/ui/custom-modifiers.plugin.js
Normal file
31
ui/plugins/ui/custom-modifiers.plugin.js
Normal file
@ -0,0 +1,31 @@
|
||||
(function() {
|
||||
PLUGINS['MODIFIERS_LOAD'].push({
|
||||
loader: function() {
|
||||
let customModifiers = localStorage.getItem(CUSTOM_MODIFIERS_KEY, '')
|
||||
customModifiersTextBox.value = customModifiers
|
||||
|
||||
if (customModifiersGroupElement !== undefined) {
|
||||
customModifiersGroupElement.remove()
|
||||
}
|
||||
|
||||
if (customModifiers && customModifiers.trim() !== '') {
|
||||
customModifiers = customModifiers.split('\n')
|
||||
customModifiers = customModifiers.filter(m => m.trim() !== '')
|
||||
customModifiers = customModifiers.map(function(m) {
|
||||
return {
|
||||
"modifier": m
|
||||
}
|
||||
})
|
||||
|
||||
let customGroup = {
|
||||
'category': 'Custom Modifiers',
|
||||
'modifiers': customModifiers
|
||||
}
|
||||
|
||||
customModifiersGroupElement = createModifierGroup(customGroup, true)
|
||||
|
||||
createCollapsibles(customModifiersGroupElement)
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
53
ui/plugins/ui/modifiers-toggle.plugin.js
Normal file
53
ui/plugins/ui/modifiers-toggle.plugin.js
Normal file
@ -0,0 +1,53 @@
|
||||
(function () {
|
||||
"use strict"
|
||||
|
||||
var styleSheet = document.createElement("style");
|
||||
styleSheet.textContent = `
|
||||
.modifier-card-tiny.modifier-toggle-inactive {
|
||||
background: transparent;
|
||||
border: 2px dashed red;
|
||||
opacity:0.2;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(styleSheet);
|
||||
|
||||
// observe for changes in tag list
|
||||
var observer = new MutationObserver(function (mutations) {
|
||||
// mutations.forEach(function (mutation) {
|
||||
if (editorModifierTagsList.childNodes.length > 0) {
|
||||
ModifierToggle()
|
||||
}
|
||||
// })
|
||||
})
|
||||
|
||||
observer.observe(editorModifierTagsList, {
|
||||
childList: true
|
||||
})
|
||||
|
||||
function ModifierToggle() {
|
||||
let overlays = document.querySelector('#editor-inputs-tags-list').querySelectorAll('.modifier-card-overlay')
|
||||
overlays.forEach (i => {
|
||||
i.oncontextmenu = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (i.parentElement.classList.contains('modifier-toggle-inactive')) {
|
||||
i.parentElement.classList.remove('modifier-toggle-inactive')
|
||||
}
|
||||
else
|
||||
{
|
||||
i.parentElement.classList.add('modifier-toggle-inactive')
|
||||
}
|
||||
// refresh activeTags
|
||||
let modifierName = i.parentElement.getElementsByClassName('modifier-card-label')[0].getElementsByTagName("p")[0].innerText
|
||||
activeTags = activeTags.map(obj => {
|
||||
if (obj.name === modifierName) {
|
||||
return {...obj, inactive: (obj.element.classList.contains('modifier-toggle-inactive'))};
|
||||
}
|
||||
|
||||
return obj;
|
||||
});
|
||||
console.log(activeTags)
|
||||
}
|
||||
})
|
||||
}
|
||||
})()
|
@ -25,6 +25,7 @@ class Request:
|
||||
use_vae_model: str = None
|
||||
show_only_filtered_image: bool = False
|
||||
output_format: str = "jpeg" # or "png"
|
||||
output_quality: int = 75
|
||||
|
||||
stream_progress_updates: bool = False
|
||||
stream_image_progress: bool = False
|
||||
@ -47,6 +48,7 @@ class Request:
|
||||
"use_stable_diffusion_model": self.use_stable_diffusion_model,
|
||||
"use_vae_model": self.use_vae_model,
|
||||
"output_format": self.output_format,
|
||||
"output_quality": self.output_quality,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
@ -70,6 +72,7 @@ class Request:
|
||||
use_vae_model: {self.use_vae_model}
|
||||
show_only_filtered_image: {self.show_only_filtered_image}
|
||||
output_format: {self.output_format}
|
||||
output_quality: {self.output_quality}
|
||||
|
||||
stream_progress_updates: {self.stream_progress_updates}
|
||||
stream_image_progress: {self.stream_image_progress}'''
|
||||
|
@ -29,6 +29,7 @@ from basicsr.archs.rrdbnet_arch import RRDBNet
|
||||
from realesrgan import RealESRGANer
|
||||
|
||||
from threading import Lock
|
||||
from safetensors.torch import load_file
|
||||
|
||||
import uuid
|
||||
|
||||
@ -71,8 +72,6 @@ def thread_init(device):
|
||||
thread_data.device_name = None
|
||||
thread_data.unet_bs = 1
|
||||
thread_data.precision = 'autocast'
|
||||
thread_data.sampler_plms = None
|
||||
thread_data.sampler_ddim = None
|
||||
|
||||
thread_data.turbo = False
|
||||
thread_data.force_full_precision = False
|
||||
@ -98,7 +97,12 @@ def isSD2():
|
||||
|
||||
def load_model_ckpt():
|
||||
if not thread_data.ckpt_file: raise ValueError(f'Thread ckpt_file is undefined.')
|
||||
if not os.path.exists(thread_data.ckpt_file + '.ckpt'): raise FileNotFoundError(f'Cannot find {thread_data.ckpt_file}.ckpt')
|
||||
if os.path.exists(thread_data.ckpt_file + '.ckpt'):
|
||||
thread_data.ckpt_file += '.ckpt'
|
||||
elif os.path.exists(thread_data.ckpt_file + '.safetensors'):
|
||||
thread_data.ckpt_file += '.safetensors'
|
||||
elif not os.path.exists(thread_data.ckpt_file):
|
||||
raise FileNotFoundError(f'Cannot find {thread_data.ckpt_file}.ckpt or .safetensors')
|
||||
|
||||
if not thread_data.precision:
|
||||
thread_data.precision = 'full' if thread_data.force_full_precision else 'autocast'
|
||||
@ -109,7 +113,7 @@ def load_model_ckpt():
|
||||
if thread_data.device == 'cpu':
|
||||
thread_data.precision = 'full'
|
||||
|
||||
print('loading', thread_data.ckpt_file + '.ckpt', 'to device', thread_data.device, 'using precision', thread_data.precision)
|
||||
print('loading', thread_data.ckpt_file, 'to device', thread_data.device, 'using precision', thread_data.precision)
|
||||
|
||||
if thread_data.test_sd2:
|
||||
load_model_ckpt_sd2()
|
||||
@ -117,7 +121,7 @@ def load_model_ckpt():
|
||||
load_model_ckpt_sd1()
|
||||
|
||||
def load_model_ckpt_sd1():
|
||||
sd = load_model_from_config(thread_data.ckpt_file + '.ckpt')
|
||||
sd = load_model_from_config(thread_data.ckpt_file)
|
||||
li, lo = [], []
|
||||
for key, value in sd.items():
|
||||
sp = key.split(".")
|
||||
@ -204,7 +208,7 @@ def load_model_ckpt_sd1():
|
||||
thread_data.model_fs_is_half = False
|
||||
|
||||
print(f'''loaded model
|
||||
model file: {thread_data.ckpt_file}.ckpt
|
||||
model file: {thread_data.ckpt_file}
|
||||
model.device: {model.device}
|
||||
modelCS.device: {modelCS.cond_stage_model.device}
|
||||
modelFS.device: {thread_data.modelFS.device}
|
||||
@ -215,7 +219,7 @@ def load_model_ckpt_sd2():
|
||||
config = OmegaConf.load(config_file)
|
||||
verbose = False
|
||||
|
||||
sd = load_model_from_config(thread_data.ckpt_file + '.ckpt')
|
||||
sd = load_model_from_config(thread_data.ckpt_file)
|
||||
|
||||
thread_data.model = instantiate_from_config(config.model)
|
||||
m, u = thread_data.model.load_state_dict(sd, strict=False)
|
||||
@ -230,6 +234,8 @@ def load_model_ckpt_sd2():
|
||||
thread_data.model.eval()
|
||||
del sd
|
||||
|
||||
thread_data.model.cond_stage_model.device = torch.device(thread_data.device)
|
||||
|
||||
if thread_data.device != "cpu" and thread_data.precision == "autocast":
|
||||
thread_data.model.half()
|
||||
thread_data.model_is_half = True
|
||||
@ -239,7 +245,7 @@ def load_model_ckpt_sd2():
|
||||
thread_data.model_fs_is_half = False
|
||||
|
||||
print(f'''loaded model
|
||||
model file: {thread_data.ckpt_file}.ckpt
|
||||
model file: {thread_data.ckpt_file}
|
||||
using precision: {thread_data.precision}''')
|
||||
|
||||
def unload_filters():
|
||||
@ -401,7 +407,12 @@ def is_model_reload_necessary(req: Request):
|
||||
# custom model support:
|
||||
# the req.use_stable_diffusion_model needs to be a valid path
|
||||
# to the ckpt file (without the extension).
|
||||
if not os.path.exists(req.use_stable_diffusion_model + '.ckpt'): raise FileNotFoundError(f'Cannot find {req.use_stable_diffusion_model}.ckpt')
|
||||
if os.path.exists(req.use_stable_diffusion_model + '.ckpt'):
|
||||
req.use_stable_diffusion_model += '.ckpt'
|
||||
elif os.path.exists(req.use_stable_diffusion_model + '.safetensors'):
|
||||
req.use_stable_diffusion_model += '.safetensors'
|
||||
elif not os.path.exists(req.use_stable_diffusion_model):
|
||||
raise FileNotFoundError(f'Cannot find {req.use_stable_diffusion_model}.ckpt or .safetensors')
|
||||
|
||||
needs_model_reload = False
|
||||
if not thread_data.model or thread_data.ckpt_file != req.use_stable_diffusion_model or thread_data.vae_file != req.use_vae_model:
|
||||
@ -664,12 +675,12 @@ def do_mk_img(req: Request, data_queue: queue.Queue, task_temp_images: list, ste
|
||||
if req.save_to_disk_path is not None:
|
||||
if return_orig_img:
|
||||
img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format)
|
||||
save_image(img, img_out_path)
|
||||
save_image(img, img_out_path, req.output_format, req.output_quality)
|
||||
meta_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, 'txt')
|
||||
save_metadata(meta_out_path, req, prompts[0], opt_seed)
|
||||
|
||||
if return_orig_img:
|
||||
img_buffer = img_to_buffer(img, req.output_format)
|
||||
img_buffer = img_to_buffer(img, req.output_format, req.output_quality)
|
||||
img_str = buffer_to_base64_str(img_buffer, req.output_format)
|
||||
res_image_orig = ResponseImage(data=img_str, seed=opt_seed)
|
||||
res.images.append(res_image_orig)
|
||||
@ -689,14 +700,14 @@ def do_mk_img(req: Request, data_queue: queue.Queue, task_temp_images: list, ste
|
||||
filters_applied.append(req.use_upscale)
|
||||
if (len(filters_applied) > 0):
|
||||
filtered_image = Image.fromarray(img_data[i])
|
||||
filtered_buffer = img_to_buffer(filtered_image, req.output_format)
|
||||
filtered_buffer = img_to_buffer(filtered_image, req.output_format, req.output_quality)
|
||||
filtered_img_data = buffer_to_base64_str(filtered_buffer, req.output_format)
|
||||
response_image = ResponseImage(data=filtered_img_data, seed=opt_seed)
|
||||
res.images.append(response_image)
|
||||
task_temp_images[i] = filtered_buffer
|
||||
if req.save_to_disk_path is not None:
|
||||
filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied))
|
||||
save_image(filtered_image, filtered_img_out_path)
|
||||
save_image(filtered_image, filtered_img_out_path, req.output_format, req.output_quality)
|
||||
response_image.path_abs = filtered_img_out_path
|
||||
del filtered_image
|
||||
# Filter Applied, move to next seed
|
||||
@ -717,9 +728,12 @@ def do_mk_img(req: Request, data_queue: queue.Queue, task_temp_images: list, ste
|
||||
|
||||
return res
|
||||
|
||||
def save_image(img, img_out_path):
|
||||
def save_image(img, img_out_path, output_format="", output_quality=75):
|
||||
try:
|
||||
img.save(img_out_path)
|
||||
if output_format.upper() == "JPEG":
|
||||
img.save(img_out_path, quality=output_quality)
|
||||
else:
|
||||
img.save(img_out_path)
|
||||
except:
|
||||
print('could not save the file', traceback.format_exc())
|
||||
|
||||
@ -753,7 +767,7 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code,
|
||||
if not thread_data.test_sd2:
|
||||
move_to_cpu(thread_data.modelCS)
|
||||
|
||||
if thread_data.test_sd2 and sampler_name not in ('plms', 'ddim'):
|
||||
if thread_data.test_sd2 and sampler_name not in ('plms', 'ddim', 'dpm2'):
|
||||
raise Exception('Only plms and ddim samplers are supported right now, in SD 2.0')
|
||||
|
||||
|
||||
@ -768,18 +782,18 @@ def _txt2img(opt_W, opt_H, opt_n_samples, opt_ddim_steps, opt_scale, start_code,
|
||||
# x_T=start_code)
|
||||
|
||||
if thread_data.test_sd2:
|
||||
from ldm.models.diffusion.ddim import DDIMSampler
|
||||
from ldm.models.diffusion.plms import PLMSSampler
|
||||
|
||||
shape = [opt_C, opt_H // opt_f, opt_W // opt_f]
|
||||
|
||||
if sampler_name == 'plms':
|
||||
from ldm.models.diffusion.plms import PLMSSampler
|
||||
sampler = PLMSSampler(thread_data.model)
|
||||
elif sampler_name == 'ddim':
|
||||
from ldm.models.diffusion.ddim import DDIMSampler
|
||||
sampler = DDIMSampler(thread_data.model)
|
||||
|
||||
sampler.make_schedule(ddim_num_steps=opt_ddim_steps, ddim_eta=opt_ddim_eta, verbose=False)
|
||||
elif sampler_name == 'dpm2':
|
||||
from ldm.models.diffusion.dpm_solver import DPMSolverSampler
|
||||
sampler = DPMSolverSampler(thread_data.model)
|
||||
|
||||
shape = [opt_C, opt_H // opt_f, opt_W // opt_f]
|
||||
|
||||
samples_ddim, intermediates = sampler.sample(
|
||||
S=opt_ddim_steps,
|
||||
@ -869,13 +883,21 @@ def chunk(it, size):
|
||||
|
||||
def load_model_from_config(ckpt, verbose=False):
|
||||
print(f"Loading model from {ckpt}")
|
||||
pl_sd = torch.load(ckpt, map_location="cpu")
|
||||
|
||||
if ckpt.endswith(".safetensors"):
|
||||
print("Loading from safetensors")
|
||||
pl_sd = load_file(ckpt, device="cpu")
|
||||
else:
|
||||
pl_sd = torch.load(ckpt, map_location="cpu")
|
||||
|
||||
if "global_step" in pl_sd:
|
||||
print(f"Global Step: {pl_sd['global_step']}")
|
||||
sd = pl_sd["state_dict"]
|
||||
return sd
|
||||
|
||||
# utils
|
||||
if "state_dict" in pl_sd:
|
||||
return pl_sd["state_dict"]
|
||||
else:
|
||||
return pl_sd
|
||||
|
||||
class UserInitiatedStop(Exception):
|
||||
pass
|
||||
|
||||
@ -919,13 +941,16 @@ def load_mask(mask_str, h0, w0, newH, newW, invert=False):
|
||||
return image
|
||||
|
||||
# https://stackoverflow.com/a/61114178
|
||||
def img_to_base64_str(img, output_format="PNG"):
|
||||
buffered = img_to_buffer(img, output_format)
|
||||
def img_to_base64_str(img, output_format="PNG", output_quality=75):
|
||||
buffered = img_to_buffer(img, output_format, quality=output_quality)
|
||||
return buffer_to_base64_str(buffered, output_format)
|
||||
|
||||
def img_to_buffer(img, output_format="PNG"):
|
||||
def img_to_buffer(img, output_format="PNG", output_quality=75):
|
||||
buffered = BytesIO()
|
||||
img.save(buffered, format=output_format)
|
||||
if ( output_format.upper() == "JPEG" ):
|
||||
img.save(buffered, format=output_format, quality=output_quality)
|
||||
else:
|
||||
img.save(buffered, format=output_format)
|
||||
buffered.seek(0)
|
||||
return buffered
|
||||
|
||||
|
@ -79,6 +79,7 @@ class ImageRequest(BaseModel):
|
||||
use_vae_model: str = None
|
||||
show_only_filtered_image: bool = False
|
||||
output_format: str = "jpeg" # or "png"
|
||||
output_quality: int = 75
|
||||
|
||||
stream_progress_updates: bool = False
|
||||
stream_image_progress: bool = False
|
||||
@ -95,6 +96,7 @@ class FilterRequest(BaseModel):
|
||||
render_device: str = None
|
||||
use_full_precision: bool = False
|
||||
output_format: str = "jpeg" # or "png"
|
||||
output_quality: int = 75
|
||||
|
||||
# Temporary cache to allow to query tasks results for a short time after they are completed.
|
||||
class TaskCache():
|
||||
@ -504,6 +506,7 @@ def render(req : ImageRequest):
|
||||
r.use_vae_model = req.use_vae_model
|
||||
r.show_only_filtered_image = req.show_only_filtered_image
|
||||
r.output_format = req.output_format
|
||||
r.output_quality = req.output_quality
|
||||
|
||||
r.stream_progress_updates = True # the underlying implementation only supports streaming
|
||||
r.stream_image_progress = req.stream_image_progress
|
||||
|
@ -24,7 +24,7 @@ USER_UI_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'plugins', 'ui'
|
||||
CORE_UI_PLUGINS_DIR = os.path.abspath(os.path.join(SD_UI_DIR, 'plugins', 'ui'))
|
||||
UI_PLUGINS_SOURCES = ((CORE_UI_PLUGINS_DIR, 'core'), (USER_UI_PLUGINS_DIR, 'user'))
|
||||
|
||||
STABLE_DIFFUSION_MODEL_EXTENSIONS = ['.ckpt']
|
||||
STABLE_DIFFUSION_MODEL_EXTENSIONS = ['.ckpt', '.safetensors']
|
||||
VAE_MODEL_EXTENSIONS = ['.vae.pt', '.ckpt']
|
||||
|
||||
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
||||
|
Loading…
Reference in New Issue
Block a user