diff --git a/3rd-PARTY-LICENSES b/3rd-PARTY-LICENSES
index bd29393a..78bfe3bb 100644
--- a/3rd-PARTY-LICENSES
+++ b/3rd-PARTY-LICENSES
@@ -712,3 +712,31 @@ FileSaver.js is licensed under the MIT license:
SOFTWARE.
[1]: http://eligrey.com
+
+croppr.js
+=========
+https://github.com/jamesssooi/Croppr.js
+
+croppr.js is licensed under the MIT license:
+
+ MIT License
+
+ Copyright (c) 2017 James Ooi
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
diff --git a/CHANGES.md b/CHANGES.md
index 1ac36410..9e72b44f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,6 +22,8 @@
Our focus continues to remain on an easy installation experience, and an easy user-interface. While still remaining pretty powerful, in terms of features and speed.
### Detailed changelog
+* 3.0.0 - 3 Aug 2023 - Enabled diffusers for everyone by default. The old v2 engine can be used by disabling the "Use v3 engine" option in the Settings tab.
+* 2.5.48 - 1 Aug 2023 - (beta-only) Full support for ControlNets. You can select a control image to guide the AI. You can pick a filter to pre-process the image, and one of the known (or custom) controlnet models. Supports `OpenPose`, `Canny`, `Straight Lines`, `Depth`, `Line Art`, `Scribble`, `Soft Edge`, `Shuffle` and `Segment`.
* 2.5.47 - 30 Jul 2023 - An option to use `Strict Mask Border` while inpainting, to avoid touching areas outside the mask. But this might show a slight outline of the mask, which you will have to touch up separately.
* 2.5.47 - 29 Jul 2023 - (beta-only) Fix long prompts with SDXL.
* 2.5.47 - 29 Jul 2023 - (beta-only) Fix red dots in some SDXL images.
diff --git a/How to install and run.txt b/How to install and run.txt
index af783b64..8e83ab7c 100644
--- a/How to install and run.txt
+++ b/How to install and run.txt
@@ -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/easydiffusion/easydiffusion/issues
Thanks
-cmdr2 (and contributors to the project)
\ No newline at end of file
+cmdr2 (and contributors to the project)
diff --git a/README.md b/README.md
index b97c35d1..c7848a7c 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,9 @@ Does not require technical knowledge, does not require pre-installed software. 1
Click the download button for your operating system:
-
-
-
+
+
+
**Hardware requirements:**
@@ -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:
@@ -132,6 +133,15 @@ We could really use help on these aspects (click to view tasks that need your he
If you have any code contributions in mind, please feel free to say Hi to us on the [discord server](https://discord.com/invite/u9yhsFmEkB). We use the Discord server for development-related discussions, and for helping users.
+# Credits
+* Stable Diffusion: https://github.com/Stability-AI/stablediffusion
+* CodeFormer: https://github.com/sczhou/CodeFormer (license: https://github.com/sczhou/CodeFormer/blob/master/LICENSE)
+* GFPGAN: https://github.com/TencentARC/GFPGAN
+* RealESRGAN: https://github.com/xinntao/Real-ESRGAN
+* k-diffusion: https://github.com/crowsonkb/k-diffusion
+* Code contributors and artists on the cmdr2 UI: https://github.com/cmdr2/stable-diffusion-ui and Discord (https://discord.com/invite/u9yhsFmEkB)
+* Lots of contributors on the internet
+
# Disclaimer
The authors of this project are not responsible for any content generated using this interface.
diff --git a/scripts/check_modules.py b/scripts/check_modules.py
index 301b6163..aecf7576 100644
--- a/scripts/check_modules.py
+++ b/scripts/check_modules.py
@@ -18,7 +18,7 @@ os_name = platform.system()
modules_to_check = {
"torch": ("1.11.0", "1.13.1", "2.0.0"),
"torchvision": ("0.12.0", "0.14.1", "0.15.1"),
- "sdkit": "1.0.151",
+ "sdkit": "1.0.167",
"stable-diffusion-sdkit": "2.1.4",
"rich": "12.6.0",
"uvicorn": "0.19.0",
diff --git a/ui/easydiffusion/app.py b/ui/easydiffusion/app.py
index 6f8d731e..e2c190f8 100644
--- a/ui/easydiffusion/app.py
+++ b/ui/easydiffusion/app.py
@@ -61,6 +61,7 @@ APP_CONFIG_DEFAULTS = {
"ui": {
"open_browser_on_start": True,
},
+ "test_diffusers": True,
}
IMAGE_EXTENSIONS = [
@@ -116,7 +117,7 @@ def getConfig(default_val=APP_CONFIG_DEFAULTS):
def set_config_on_startup(config: dict):
if getConfig.__test_diffusers_on_startup is None:
- getConfig.__test_diffusers_on_startup = config.get("test_diffusers", False)
+ getConfig.__test_diffusers_on_startup = config.get("test_diffusers", True)
config["config_on_startup"] = {"test_diffusers": getConfig.__test_diffusers_on_startup}
if os.path.isfile(config_yaml_path):
diff --git a/ui/easydiffusion/bucket_manager.py b/ui/easydiffusion/bucket_manager.py
index 4400fd17..0d72ed06 100644
--- a/ui/easydiffusion/bucket_manager.py
+++ b/ui/easydiffusion/bucket_manager.py
@@ -71,9 +71,8 @@ def init():
bucket = crud.get_bucket_by_path(db, path)
if bucket == None:
- bucket_id = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path))
- else:
- bucket_id = bucket.id
+ bucket = crud.create_bucket(db=db, bucket=schemas.BucketCreate(path=path))
+ bucket_id = bucket.id
bucketfile = schemas.BucketFileCreate(filename=filename, data=file)
result = crud.create_bucketfile(db=db, bucketfile=bucketfile, bucket_id=bucket_id)
@@ -92,25 +91,19 @@ def init():
@server_api.get("/image/{image_path:path}")
def get_image(image_path: str, db: Session = Depends(get_db)):
- from easydiffusion.easydb.mappings import Image
+ from easydiffusion.easydb.mappings import GalleryImage
image_path = str(abspath(image_path))
- amount = len(db.query(Image).filter(Image.path == image_path).all())
- if amount > 0:
- image = db.query(Image).filter(Image.path == image_path).first()
+ try:
+ image = db.query(GalleryImage).filter(GalleryImage.path == image_path).first()
return FileResponse(image.path)
- else:
+ except Exception as e:
raise HTTPException(status_code=404, detail="Image not found")
@server_api.get("/all_images")
def get_all_images(db: Session = Depends(get_db)):
- from easydiffusion.easydb.mappings import Image
- images = db.query(Image).all()
- sum_string = "
@@ -713,7 +810,7 @@ async function init() {
ping: onPing
}
})
- splashScreen()
+ // splashScreen()
// load models again, but scan for malicious this time
await getModels(true)
diff --git a/ui/main.py b/ui/main.py
index a7568ba6..0239c829 100644
--- a/ui/main.py
+++ b/ui/main.py
@@ -1,11 +1,12 @@
from easydiffusion import model_manager, app, server, bucket_manager
from easydiffusion.server import server_api # required for uvicorn
+app.init()
+
server.init()
# Init the app
model_manager.init()
-app.init()
app.init_render_threads()
bucket_manager.init()
diff --git a/ui/media/css/croppr.css b/ui/media/css/croppr.css
new file mode 100644
index 00000000..19eb2f7b
--- /dev/null
+++ b/ui/media/css/croppr.css
@@ -0,0 +1,58 @@
+.croppr-container * {
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ -ms-user-select: none;
+ box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+}
+
+.croppr-container img {
+ vertical-align: middle;
+ max-width: 100%;
+}
+
+.croppr {
+ position: relative;
+ display: inline-block;
+}
+
+.croppr-overlay {
+ background: rgba(0,0,0,0.5);
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1;
+ cursor: crosshair;
+}
+
+.croppr-region {
+ border: 1px dashed rgba(0, 0, 0, 0.5);
+ position: absolute;
+ z-index: 3;
+ cursor: move;
+ top: 0;
+}
+
+.croppr-imageClipped {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 2;
+ pointer-events: none;
+}
+
+.croppr-handle {
+ border: 1px solid black;
+ background-color: white;
+ width: 10px;
+ height: 10px;
+ position: absolute;
+ z-index: 4;
+ top: 0;
+}
\ No newline at end of file
diff --git a/ui/media/css/main.css b/ui/media/css/main.css
index f04da78f..e0d1d02d 100644
--- a/ui/media/css/main.css
+++ b/ui/media/css/main.css
@@ -794,7 +794,7 @@ div.img-preview img {
margin-bottom: 8px;
}
-#init_image_preview_container:not(.has-image) #init_image_wrapper,
+#init_image_preview_container:not(.has-image) .preview_image_wrapper,
#init_image_preview_container:not(.has-image) #inpaint_button_container {
display: none;
}
@@ -831,14 +831,14 @@ div.img-preview img {
gap: 8px;
}
-#init_image_wrapper {
+.preview_image_wrapper {
grid-row: span 3;
position: relative;
width: fit-content;
max-height: 150px;
}
-#init_image_preview {
+.image_preview {
max-height: 150px;
height: 100%;
width: 100%;
@@ -1650,6 +1650,35 @@ body.wait-pause {
}
}
+.spinner-container {
+ width: 80px;
+ height: 100px;
+ margin: 100px auto;
+ margin-top: 30vH;
+}
+
+.spinner-block {
+ position: relative;
+ box-sizing: border-box;
+ float: left;
+ margin: 0 10px 10px 0;
+ width: 12px;
+ height: 12px;
+ border-radius: 3px;
+ background: var(--accent-color);
+}
+
+.spinner-block:nth-child(4n+1) { animation: spinner-wave 2s ease .0s infinite; }
+.spinner-block:nth-child(4n+2) { animation: spinner-wave 2s ease .2s infinite; }
+.spinner-block:nth-child(4n+3) { animation: spinner-wave 2s ease .4s infinite; }
+.spinner-block:nth-child(4n+4) { animation: spinner-wave 2s ease .6s infinite; margin-right: 0; }
+
+@keyframes spinner-wave {
+ 0% { top: 0; opacity: 1; }
+ 50% { top: 30px; opacity: .2; }
+ 100% { top: 0; opacity: 1; }
+}
+
#embeddings-dialog {
overflow: clip;
}
@@ -1741,6 +1770,32 @@ body.wait-pause {
float: right;
}
+.use-as-thumb-grid { display: grid;
+ grid-template-columns: 1fr auto;
+ grid-template-rows: 1fr auto;
+ gap: 8px 8px;
+ grid-auto-flow: row;
+ grid-template-areas:
+ "uat-preview uat-select"
+ "uat-preview uat-buttons";
+}
+
+.use-as-thumb-preview {
+ justify-self: center;
+ align-self: center;
+ grid-area: uat-preview;
+}
+
+.use-as-thumb-select {
+ grid-area: uat-select;
+}
+
+.use-as-thumb-buttons {
+ justify-self: center;
+ grid-area: uat-buttons;
+}
+
+
.diffusers-disabled-on-startup .diffusers-restart-needed {
font-size: 0;
}
@@ -1818,23 +1873,57 @@ div#enlarge-buttons {
font-size: 10pt;
}
-/* Gallery CSS */
-#imagecontainer {
- display: flex;
- justify-content: space-around;
- flex-flow: row wrap;
- align-items: center;
+#controlnet_model_container small {
+ color: var(--text-color)
+}
+#control_image {
+ width: 130pt;
+}
+#controlnet_model {
+ width: 77%;
}
-#imagecontainer>img {
- width: 30vw;
- min-width: 256px;
- max-width: 1024px;
- height: auto;
- margin-block: 1vh;
- border: 4px white solid;
+/* hack for fixing Image Modifier Improvements plugin */
+#imageTagPopupContainer {
+ position: absolute;
}
+/* Gallery CSS */
+.gallery {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-direction: column;
+ font-family: sans-serif;
+}
+
+.gallery-container {
+ columns: 5 ;
+ column-gap: 1.5rem;
+ width: 95%;
+ margin: 0 0 ;
+}
+.gallery-container div {
+ margin: 0 1.5rem 1.5rem 0;
+ display: inline-block;
+ width: 100%;
+ padding: 5px;
+
+ transition: all .75s ease-in-out;
+}
+
+.gallery-container div img {
+ width: 100%;
+ border-radius: 5px;
+ transition: all .25s ease-in-out;
+ box-shadow: 5px 5px 5px rgba(0,0,0,0.4);
+}
+
+.gallery-container div img:hover {
+ box-shadow: 1px 1px 15px rgba(32,0,128,0.8);
+}
+
+
#tab-content-gallery>button {
margin: 8px;
-}
\ No newline at end of file
+}
diff --git a/ui/media/js/auto-save.js b/ui/media/js/auto-save.js
index 670fee0d..bcfd4bc6 100644
--- a/ui/media/js/auto-save.js
+++ b/ui/media/js/auto-save.js
@@ -45,6 +45,7 @@ const SETTINGS_IDS_LIST = [
"sound_toggle",
"vram_usage_level",
"confirm_dangerous_actions",
+ "profileName",
"metadata_output_format",
"auto_save_settings",
"apply_color_correction",
@@ -54,6 +55,8 @@ const SETTINGS_IDS_LIST = [
"zip_toggle",
"tree_toggle",
"json_toggle",
+ "extract_lora_from_prompt",
+ "embedding-card-size-selector",
]
const IGNORE_BY_DEFAULT = ["prompt"]
diff --git a/ui/media/js/croppr.js b/ui/media/js/croppr.js
new file mode 100755
index 00000000..f5c3f3b8
--- /dev/null
+++ b/ui/media/js/croppr.js
@@ -0,0 +1,1189 @@
+/**
+ * Croppr.js
+ * https://github.com/jamesssooi/Croppr.js
+ *
+ * A JavaScript image cropper that's lightweight, awesome, and has
+ * zero dependencies.
+ *
+ * (C) 2017 James Ooi. Released under the MIT License.
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global.Croppr = factory());
+}(this, (function () { 'use strict';
+
+(function () {
+ var lastTime = 0;
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
+ for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
+ }
+ if (!window.requestAnimationFrame) window.requestAnimationFrame = function (callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function () {
+ callback(currTime + timeToCall);
+ }, timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+ if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function (id) {
+ clearTimeout(id);
+ };
+})();
+(function () {
+ if (typeof window.CustomEvent === "function") return false;
+ function CustomEvent(event, params) {
+ params = params || { bubbles: false, cancelable: false, detail: undefined };
+ var evt = document.createEvent('CustomEvent');
+ evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
+ return evt;
+ }
+ CustomEvent.prototype = window.Event.prototype;
+ window.CustomEvent = CustomEvent;
+})();
+(function (window) {
+ try {
+ new CustomEvent('test');
+ return false;
+ } catch (e) {}
+ function MouseEvent(eventType, params) {
+ params = params || { bubbles: false, cancelable: false };
+ var mouseEvent = document.createEvent('MouseEvent');
+ mouseEvent.initMouseEvent(eventType, params.bubbles, params.cancelable, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ return mouseEvent;
+ }
+ MouseEvent.prototype = Event.prototype;
+ window.MouseEvent = MouseEvent;
+})(window);
+
+var classCallCheck = function (instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+};
+
+var createClass = function () {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ return function (Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+}();
+
+
+
+
+
+
+
+var get = function get(object, property, receiver) {
+ if (object === null) object = Function.prototype;
+ var desc = Object.getOwnPropertyDescriptor(object, property);
+
+ if (desc === undefined) {
+ var parent = Object.getPrototypeOf(object);
+
+ if (parent === null) {
+ return undefined;
+ } else {
+ return get(parent, property, receiver);
+ }
+ } else if ("value" in desc) {
+ return desc.value;
+ } else {
+ var getter = desc.get;
+
+ if (getter === undefined) {
+ return undefined;
+ }
+
+ return getter.call(receiver);
+ }
+};
+
+var inherits = function (subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ enumerable: false,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
+};
+
+
+
+
+
+
+
+
+
+
+
+var possibleConstructorReturn = function (self, call) {
+ if (!self) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return call && (typeof call === "object" || typeof call === "function") ? call : self;
+};
+
+
+
+
+
+var slicedToArray = function () {
+ function sliceIterator(arr, i) {
+ var _arr = [];
+ var _n = true;
+ var _d = false;
+ var _e = undefined;
+
+ try {
+ for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
+ _arr.push(_s.value);
+
+ if (i && _arr.length === i) break;
+ }
+ } catch (err) {
+ _d = true;
+ _e = err;
+ } finally {
+ try {
+ if (!_n && _i["return"]) _i["return"]();
+ } finally {
+ if (_d) throw _e;
+ }
+ }
+
+ return _arr;
+ }
+
+ return function (arr, i) {
+ if (Array.isArray(arr)) {
+ return arr;
+ } else if (Symbol.iterator in Object(arr)) {
+ return sliceIterator(arr, i);
+ } else {
+ throw new TypeError("Invalid attempt to destructure non-iterable instance");
+ }
+ };
+}();
+
+var Handle =
+/**
+ * Creates a new Handle instance.
+ * @constructor
+ * @param {Array} position The x and y ratio position of the handle
+ * within the crop region. Accepts a value between 0 to 1 in the order
+ * of [X, Y].
+ * @param {Array} constraints Define the side of the crop region that
+ * is to be affected by this handle. Accepts a value of 0 or 1 in the
+ * order of [TOP, RIGHT, BOTTOM, LEFT].
+ * @param {String} cursor The CSS cursor of this handle.
+ * @param {Element} eventBus The element to dispatch events to.
+ */
+function Handle(position, constraints, cursor, eventBus) {
+ classCallCheck(this, Handle);
+ var self = this;
+ this.position = position;
+ this.constraints = constraints;
+ this.cursor = cursor;
+ this.eventBus = eventBus;
+ this.el = document.createElement('div');
+ this.el.className = 'croppr-handle';
+ this.el.style.cursor = cursor;
+ this.el.addEventListener('mousedown', onMouseDown);
+ function onMouseDown(e) {
+ e.stopPropagation();
+ document.addEventListener('mouseup', onMouseUp);
+ document.addEventListener('mousemove', onMouseMove);
+ self.eventBus.dispatchEvent(new CustomEvent('handlestart', {
+ detail: { handle: self }
+ }));
+ }
+ function onMouseUp(e) {
+ e.stopPropagation();
+ document.removeEventListener('mouseup', onMouseUp);
+ document.removeEventListener('mousemove', onMouseMove);
+ self.eventBus.dispatchEvent(new CustomEvent('handleend', {
+ detail: { handle: self }
+ }));
+ }
+ function onMouseMove(e) {
+ e.stopPropagation();
+ self.eventBus.dispatchEvent(new CustomEvent('handlemove', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+};
+
+var Box = function () {
+ /**
+ * Creates a new Box instance.
+ * @constructor
+ * @param {Number} x1
+ * @param {Number} y1
+ * @param {Number} x2
+ * @param {Number} y2
+ */
+ function Box(x1, y1, x2, y2) {
+ classCallCheck(this, Box);
+ this.x1 = x1;
+ this.y1 = y1;
+ this.x2 = x2;
+ this.y2 = y2;
+ }
+ /**
+ * Sets the new dimensions of the box.
+ * @param {Number} x1
+ * @param {Number} y1
+ * @param {Number} x2
+ * @param {Number} y2
+ */
+ createClass(Box, [{
+ key: 'set',
+ value: function set$$1() {
+ var x1 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ var y1 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+ var x2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+ var y2 = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+ this.x1 = x1 == null ? this.x1 : x1;
+ this.y1 = y1 == null ? this.y1 : y1;
+ this.x2 = x2 == null ? this.x2 : x2;
+ this.y2 = y2 == null ? this.y2 : y2;
+ return this;
+ }
+ /**
+ * Calculates the width of the box.
+ * @returns {Number}
+ */
+ }, {
+ key: 'width',
+ value: function width() {
+ return Math.abs(this.x2 - this.x1);
+ }
+ /**
+ * Calculates the height of the box.
+ * @returns {Number}
+ */
+ }, {
+ key: 'height',
+ value: function height() {
+ return Math.abs(this.y2 - this.y1);
+ }
+ /**
+ * Resizes the box to a new size.
+ * @param {Number} newWidth
+ * @param {Number} newHeight
+ * @param {Array} [origin] The origin point to resize from.
+ * Defaults to [0, 0] (top left).
+ */
+ }, {
+ key: 'resize',
+ value: function resize(newWidth, newHeight) {
+ var origin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
+ var fromX = this.x1 + this.width() * origin[0];
+ var fromY = this.y1 + this.height() * origin[1];
+ this.x1 = fromX - newWidth * origin[0];
+ this.y1 = fromY - newHeight * origin[1];
+ this.x2 = this.x1 + newWidth;
+ this.y2 = this.y1 + newHeight;
+ return this;
+ }
+ /**
+ * Scale the box by a factor.
+ * @param {Number} factor
+ * @param {Array} [origin] The origin point to resize from.
+ * Defaults to [0, 0] (top left).
+ */
+ }, {
+ key: 'scale',
+ value: function scale(factor) {
+ var origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0, 0];
+ var newWidth = this.width() * factor;
+ var newHeight = this.height() * factor;
+ this.resize(newWidth, newHeight, origin);
+ return this;
+ }
+ }, {
+ key: 'move',
+ value: function move() {
+ var x = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+ var width = this.width();
+ var height = this.height();
+ x = x === null ? this.x1 : x;
+ y = y === null ? this.y1 : y;
+ this.x1 = x;
+ this.y1 = y;
+ this.x2 = x + width;
+ this.y2 = y + height;
+ return this;
+ }
+ /**
+ * Get relative x and y coordinates of a given point within the box.
+ * @param {Array} point The x and y ratio position within the box.
+ * @returns {Array} The x and y coordinates [x, y].
+ */
+ }, {
+ key: 'getRelativePoint',
+ value: function getRelativePoint() {
+ var point = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [0, 0];
+ var x = this.width() * point[0];
+ var y = this.height() * point[1];
+ return [x, y];
+ }
+ /**
+ * Get absolute x and y coordinates of a given point within the box.
+ * @param {Array} point The x and y ratio position within the box.
+ * @returns {Array} The x and y coordinates [x, y].
+ */
+ }, {
+ key: 'getAbsolutePoint',
+ value: function getAbsolutePoint() {
+ var point = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [0, 0];
+ var x = this.x1 + this.width() * point[0];
+ var y = this.y1 + this.height() * point[1];
+ return [x, y];
+ }
+ /**
+ * Constrain the box to a fixed ratio.
+ * @param {Number} ratio
+ * @param {Array} [origin] The origin point to resize from.
+ * Defaults to [0, 0] (top left).
+ * @param {String} [grow] The axis to grow to maintain the ratio.
+ * Defaults to 'height'.
+ */
+ }, {
+ key: 'constrainToRatio',
+ value: function constrainToRatio(ratio) {
+ var origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [0, 0];
+ var grow = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'height';
+ if (ratio === null) {
+ return;
+ }
+ var width = this.width();
+ var height = this.height();
+ switch (grow) {
+ case 'height':
+ this.resize(this.width(), this.width() * ratio, origin);
+ break;
+ case 'width':
+ this.resize(this.height() * 1 / ratio, this.height(), origin);
+ break;
+ default:
+ this.resize(this.width(), this.width() * ratio, origin);
+ }
+ return this;
+ }
+ /**
+ * Constrain the box within a boundary.
+ * @param {Number} boundaryWidth
+ * @param {Number} boundaryHeight
+ * @param {Array} [origin] The origin point to resize from.
+ * Defaults to [0, 0] (top left).
+ */
+ }, {
+ key: 'constrainToBoundary',
+ value: function constrainToBoundary(boundaryWidth, boundaryHeight) {
+ var origin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [0, 0];
+ var _getAbsolutePoint = this.getAbsolutePoint(origin),
+ _getAbsolutePoint2 = slicedToArray(_getAbsolutePoint, 2),
+ originX = _getAbsolutePoint2[0],
+ originY = _getAbsolutePoint2[1];
+ var maxIfLeft = originX;
+ var maxIfTop = originY;
+ var maxIfRight = boundaryWidth - originX;
+ var maxIfBottom = boundaryHeight - originY;
+ var directionX = -2 * origin[0] + 1;
+ var directionY = -2 * origin[1] + 1;
+ var maxWidth = null,
+ maxHeight = null;
+ switch (directionX) {
+ case -1:
+ maxWidth = maxIfLeft;break;
+ case 0:
+ maxWidth = Math.min(maxIfLeft, maxIfRight) * 2;break;
+ case +1:
+ maxWidth = maxIfRight;break;
+ }
+ switch (directionY) {
+ case -1:
+ maxHeight = maxIfTop;break;
+ case 0:
+ maxHeight = Math.min(maxIfTop, maxIfBottom) * 2;break;
+ case +1:
+ maxHeight = maxIfBottom;break;
+ }
+ if (this.width() > maxWidth) {
+ var factor = maxWidth / this.width();
+ this.scale(factor, origin);
+ }
+ if (this.height() > maxHeight) {
+ var _factor = maxHeight / this.height();
+ this.scale(_factor, origin);
+ }
+ return this;
+ }
+ /**
+ * Constrain the box to a maximum/minimum size.
+ * @param {Number} [maxWidth]
+ * @param {Number} [maxHeight]
+ * @param {Number} [minWidth]
+ * @param {Number} [minHeight]
+ * @param {Array} [origin] The origin point to resize from.
+ * Defaults to [0, 0] (top left).
+ * @param {Number} [ratio] Ratio to maintain.
+ */
+ }, {
+ key: 'constrainToSize',
+ value: function constrainToSize() {
+ var maxWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ var maxHeight = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+ var minWidth = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+ var minHeight = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+ var origin = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : [0, 0];
+ var ratio = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : null;
+ if (ratio) {
+ if (ratio > 1) {
+ maxWidth = maxHeight * 1 / ratio;
+ minHeight = minHeight * ratio;
+ } else if (ratio < 1) {
+ maxHeight = maxWidth * ratio;
+ minWidth = minHeight * 1 / ratio;
+ }
+ }
+ if (maxWidth && this.width() > maxWidth) {
+ var newWidth = maxWidth,
+ newHeight = ratio === null ? this.height() : maxHeight;
+ this.resize(newWidth, newHeight, origin);
+ }
+ if (maxHeight && this.height() > maxHeight) {
+ var _newWidth = ratio === null ? this.width() : maxWidth,
+ _newHeight = maxHeight;
+ this.resize(_newWidth, _newHeight, origin);
+ }
+ if (minWidth && this.width() < minWidth) {
+ var _newWidth2 = minWidth,
+ _newHeight2 = ratio === null ? this.height() : minHeight;
+ this.resize(_newWidth2, _newHeight2, origin);
+ }
+ if (minHeight && this.height() < minHeight) {
+ var _newWidth3 = ratio === null ? this.width() : minWidth,
+ _newHeight3 = minHeight;
+ this.resize(_newWidth3, _newHeight3, origin);
+ }
+ return this;
+ }
+ }]);
+ return Box;
+}();
+
+/**
+ * Binds an element's touch events to be simulated as mouse events.
+ * @param {Element} element
+ */
+function enableTouch(element) {
+ element.addEventListener('touchstart', simulateMouseEvent);
+ element.addEventListener('touchend', simulateMouseEvent);
+ element.addEventListener('touchmove', simulateMouseEvent);
+}
+/**
+ * Translates a touch event to a mouse event.
+ * @param {Event} e
+ */
+function simulateMouseEvent(e) {
+ e.preventDefault();
+ var touch = e.changedTouches[0];
+ var eventMap = {
+ 'touchstart': 'mousedown',
+ 'touchmove': 'mousemove',
+ 'touchend': 'mouseup'
+ };
+ touch.target.dispatchEvent(new MouseEvent(eventMap[e.type], {
+ bubbles: true,
+ cancelable: true,
+ view: window,
+ clientX: touch.clientX,
+ clientY: touch.clientY,
+ screenX: touch.screenX,
+ screenY: touch.screenY
+ }));
+}
+
+/**
+ * Define a list of handles to create.
+ *
+ * @property {Array} position - The x and y ratio position of the handle within
+ * the crop region. Accepts a value between 0 to 1 in the order of [X, Y].
+ * @property {Array} constraints - Define the side of the crop region that is to
+ * be affected by this handle. Accepts a value of 0 or 1 in the order of
+ * [TOP, RIGHT, BOTTOM, LEFT].
+ * @property {String} cursor - The CSS cursor of this handle.
+ */
+var HANDLES = [{ position: [0.0, 0.0], constraints: [1, 0, 0, 1], cursor: 'nw-resize' }, { position: [0.5, 0.0], constraints: [1, 0, 0, 0], cursor: 'n-resize' }, { position: [1.0, 0.0], constraints: [1, 1, 0, 0], cursor: 'ne-resize' }, { position: [1.0, 0.5], constraints: [0, 1, 0, 0], cursor: 'e-resize' }, { position: [1.0, 1.0], constraints: [0, 1, 1, 0], cursor: 'se-resize' }, { position: [0.5, 1.0], constraints: [0, 0, 1, 0], cursor: 's-resize' }, { position: [0.0, 1.0], constraints: [0, 0, 1, 1], cursor: 'sw-resize' }, { position: [0.0, 0.5], constraints: [0, 0, 0, 1], cursor: 'w-resize' }];
+var CropprCore = function () {
+ function CropprCore(element, options) {
+ var _this = this;
+ var deferred = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+ classCallCheck(this, CropprCore);
+ this.options = CropprCore.parseOptions(options || {});
+ if (!element.nodeName) {
+ element = document.querySelector(element);
+ if (element == null) {
+ throw 'Unable to find element.';
+ }
+ }
+ if (!element.getAttribute('src')) {
+ throw 'Image src not provided.';
+ }
+ this._initialized = false;
+ this._restore = {
+ parent: element.parentNode,
+ element: element
+ };
+ if (!deferred) {
+ if (element.width === 0 || element.height === 0) {
+ element.onload = function () {
+ _this.initialize(element);
+ };
+ } else {
+ this.initialize(element);
+ }
+ }
+ }
+ createClass(CropprCore, [{
+ key: 'initialize',
+ value: function initialize(element) {
+ this.createDOM(element);
+ this.options.convertToPixels(this.cropperEl);
+ this.attachHandlerEvents();
+ this.attachRegionEvents();
+ this.attachOverlayEvents();
+ this.box = this.initializeBox(this.options);
+ this.redraw();
+ this._initialized = true;
+ if (this.options.onInitialize !== null) {
+ this.options.onInitialize(this);
+ }
+ }
+ }, {
+ key: 'createDOM',
+ value: function createDOM(targetEl) {
+ this.containerEl = document.createElement('div');
+ this.containerEl.className = 'croppr-container';
+ this.eventBus = this.containerEl;
+ enableTouch(this.containerEl);
+ this.cropperEl = document.createElement('div');
+ this.cropperEl.className = 'croppr';
+ this.imageEl = document.createElement('img');
+ this.imageEl.setAttribute('src', targetEl.getAttribute('src'));
+ this.imageEl.setAttribute('alt', targetEl.getAttribute('alt'));
+ this.imageEl.className = 'croppr-image';
+ this.imageClippedEl = this.imageEl.cloneNode();
+ this.imageClippedEl.className = 'croppr-imageClipped';
+ this.regionEl = document.createElement('div');
+ this.regionEl.className = 'croppr-region';
+ this.overlayEl = document.createElement('div');
+ this.overlayEl.className = 'croppr-overlay';
+ var handleContainerEl = document.createElement('div');
+ handleContainerEl.className = 'croppr-handleContainer';
+ this.handles = [];
+ for (var i = 0; i < HANDLES.length; i++) {
+ var handle = new Handle(HANDLES[i].position, HANDLES[i].constraints, HANDLES[i].cursor, this.eventBus);
+ this.handles.push(handle);
+ handleContainerEl.appendChild(handle.el);
+ }
+ this.cropperEl.appendChild(this.imageEl);
+ this.cropperEl.appendChild(this.imageClippedEl);
+ this.cropperEl.appendChild(this.regionEl);
+ this.cropperEl.appendChild(this.overlayEl);
+ this.cropperEl.appendChild(handleContainerEl);
+ this.containerEl.appendChild(this.cropperEl);
+ targetEl.parentElement.replaceChild(this.containerEl, targetEl);
+ }
+ /**
+ * Changes the image src.
+ * @param {String} src
+ */
+ }, {
+ key: 'setImage',
+ value: function setImage(src) {
+ var _this2 = this;
+ this.imageEl.onload = function () {
+ _this2.box = _this2.initializeBox(_this2.options);
+ _this2.redraw();
+ };
+ this.imageEl.src = src;
+ this.imageClippedEl.src = src;
+ return this;
+ }
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ this._restore.parent.replaceChild(this._restore.element, this.containerEl);
+ }
+ /**
+ * Create a new box region with a set of options.
+ * @param {Object} opts The options.
+ * @returns {Box}
+ */
+ }, {
+ key: 'initializeBox',
+ value: function initializeBox(opts) {
+ var width = opts.startSize.width;
+ var height = opts.startSize.height;
+ var box = new Box(0, 0, width, height);
+ box.constrainToRatio(opts.aspectRatio, [0.5, 0.5]);
+ var min = opts.minSize;
+ var max = opts.maxSize;
+ box.constrainToSize(max.width, max.height, min.width, min.height, [0.5, 0.5], opts.aspectRatio);
+ var parentWidth = this.cropperEl.offsetWidth;
+ var parentHeight = this.cropperEl.offsetHeight;
+ box.constrainToBoundary(parentWidth, parentHeight, [0.5, 0.5]);
+ var x = this.cropperEl.offsetWidth / 2 - box.width() / 2;
+ var y = this.cropperEl.offsetHeight / 2 - box.height() / 2;
+ box.move(x, y);
+ return box;
+ }
+ }, {
+ key: 'redraw',
+ value: function redraw() {
+ var _this3 = this;
+ var width = Math.round(this.box.width()),
+ height = Math.round(this.box.height()),
+ x1 = Math.round(this.box.x1),
+ y1 = Math.round(this.box.y1),
+ x2 = Math.round(this.box.x2),
+ y2 = Math.round(this.box.y2);
+ window.requestAnimationFrame(function () {
+ _this3.regionEl.style.transform = 'translate(' + x1 + 'px, ' + y1 + 'px)';
+ _this3.regionEl.style.width = width + 'px';
+ _this3.regionEl.style.height = height + 'px';
+ _this3.imageClippedEl.style.clip = 'rect(' + y1 + 'px, ' + x2 + 'px, ' + y2 + 'px, ' + x1 + 'px)';
+ var center = _this3.box.getAbsolutePoint([.5, .5]);
+ var xSign = center[0] - _this3.cropperEl.offsetWidth / 2 >> 31;
+ var ySign = center[1] - _this3.cropperEl.offsetHeight / 2 >> 31;
+ var quadrant = (xSign ^ ySign) + ySign + ySign + 4;
+ var foregroundHandleIndex = -2 * quadrant + 8;
+ for (var i = 0; i < _this3.handles.length; i++) {
+ var handle = _this3.handles[i];
+ var handleWidth = handle.el.offsetWidth;
+ var handleHeight = handle.el.offsetHeight;
+ var left = x1 + width * handle.position[0] - handleWidth / 2;
+ var top = y1 + height * handle.position[1] - handleHeight / 2;
+ handle.el.style.transform = 'translate(' + Math.round(left) + 'px, ' + Math.round(top) + 'px)';
+ handle.el.style.zIndex = foregroundHandleIndex == i ? 5 : 4;
+ }
+ });
+ }
+ }, {
+ key: 'attachHandlerEvents',
+ value: function attachHandlerEvents() {
+ var eventBus = this.eventBus;
+ eventBus.addEventListener('handlestart', this.onHandleMoveStart.bind(this));
+ eventBus.addEventListener('handlemove', this.onHandleMoveMoving.bind(this));
+ eventBus.addEventListener('handleend', this.onHandleMoveEnd.bind(this));
+ }
+ }, {
+ key: 'attachRegionEvents',
+ value: function attachRegionEvents() {
+ var eventBus = this.eventBus;
+ var self = this;
+ this.regionEl.addEventListener('mousedown', onMouseDown);
+ eventBus.addEventListener('regionstart', this.onRegionMoveStart.bind(this));
+ eventBus.addEventListener('regionmove', this.onRegionMoveMoving.bind(this));
+ eventBus.addEventListener('regionend', this.onRegionMoveEnd.bind(this));
+ function onMouseDown(e) {
+ e.stopPropagation();
+ document.addEventListener('mouseup', onMouseUp);
+ document.addEventListener('mousemove', onMouseMove);
+ eventBus.dispatchEvent(new CustomEvent('regionstart', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+ function onMouseMove(e) {
+ e.stopPropagation();
+ eventBus.dispatchEvent(new CustomEvent('regionmove', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+ function onMouseUp(e) {
+ e.stopPropagation();
+ document.removeEventListener('mouseup', onMouseUp);
+ document.removeEventListener('mousemove', onMouseMove);
+ eventBus.dispatchEvent(new CustomEvent('regionend', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+ }
+ }, {
+ key: 'attachOverlayEvents',
+ value: function attachOverlayEvents() {
+ var SOUTHEAST_HANDLE_IDX = 4;
+ var self = this;
+ var tmpBox = null;
+ this.overlayEl.addEventListener('mousedown', onMouseDown);
+ function onMouseDown(e) {
+ e.stopPropagation();
+ document.addEventListener('mouseup', onMouseUp);
+ document.addEventListener('mousemove', onMouseMove);
+ var container = self.cropperEl.getBoundingClientRect();
+ var mouseX = e.clientX - container.left;
+ var mouseY = e.clientY - container.top;
+ tmpBox = self.box;
+ self.box = new Box(mouseX, mouseY, mouseX + 1, mouseY + 1);
+ self.eventBus.dispatchEvent(new CustomEvent('handlestart', {
+ detail: { handle: self.handles[SOUTHEAST_HANDLE_IDX] }
+ }));
+ }
+ function onMouseMove(e) {
+ e.stopPropagation();
+ self.eventBus.dispatchEvent(new CustomEvent('handlemove', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+ function onMouseUp(e) {
+ e.stopPropagation();
+ document.removeEventListener('mouseup', onMouseUp);
+ document.removeEventListener('mousemove', onMouseMove);
+ if (self.box.width() === 1 && self.box.height() === 1) {
+ self.box = tmpBox;
+ return;
+ }
+ self.eventBus.dispatchEvent(new CustomEvent('handleend', {
+ detail: { mouseX: e.clientX, mouseY: e.clientY }
+ }));
+ }
+ }
+ }, {
+ key: 'onHandleMoveStart',
+ value: function onHandleMoveStart(e) {
+ var handle = e.detail.handle;
+ var originPoint = [1 - handle.position[0], 1 - handle.position[1]];
+ var _box$getAbsolutePoint = this.box.getAbsolutePoint(originPoint),
+ _box$getAbsolutePoint2 = slicedToArray(_box$getAbsolutePoint, 2),
+ originX = _box$getAbsolutePoint2[0],
+ originY = _box$getAbsolutePoint2[1];
+ this.activeHandle = { handle: handle, originPoint: originPoint, originX: originX, originY: originY };
+ if (this.options.onCropStart !== null) {
+ this.options.onCropStart(this.getValue());
+ }
+ }
+ }, {
+ key: 'onHandleMoveMoving',
+ value: function onHandleMoveMoving(e) {
+ var _e$detail = e.detail,
+ mouseX = _e$detail.mouseX,
+ mouseY = _e$detail.mouseY;
+ var container = this.cropperEl.getBoundingClientRect();
+ mouseX = mouseX - container.left;
+ mouseY = mouseY - container.top;
+ if (mouseX < 0) {
+ mouseX = 0;
+ } else if (mouseX > container.width) {
+ mouseX = container.width;
+ }
+ if (mouseY < 0) {
+ mouseY = 0;
+ } else if (mouseY > container.height) {
+ mouseY = container.height;
+ }
+ var origin = this.activeHandle.originPoint.slice();
+ var originX = this.activeHandle.originX;
+ var originY = this.activeHandle.originY;
+ var handle = this.activeHandle.handle;
+ var TOP_MOVABLE = handle.constraints[0] === 1;
+ var RIGHT_MOVABLE = handle.constraints[1] === 1;
+ var BOTTOM_MOVABLE = handle.constraints[2] === 1;
+ var LEFT_MOVABLE = handle.constraints[3] === 1;
+ var MULTI_AXIS = (LEFT_MOVABLE || RIGHT_MOVABLE) && (TOP_MOVABLE || BOTTOM_MOVABLE);
+ var x1 = LEFT_MOVABLE || RIGHT_MOVABLE ? originX : this.box.x1;
+ var x2 = LEFT_MOVABLE || RIGHT_MOVABLE ? originX : this.box.x2;
+ var y1 = TOP_MOVABLE || BOTTOM_MOVABLE ? originY : this.box.y1;
+ var y2 = TOP_MOVABLE || BOTTOM_MOVABLE ? originY : this.box.y2;
+ x1 = LEFT_MOVABLE ? mouseX : x1;
+ x2 = RIGHT_MOVABLE ? mouseX : x2;
+ y1 = TOP_MOVABLE ? mouseY : y1;
+ y2 = BOTTOM_MOVABLE ? mouseY : y2;
+ var isFlippedX = false,
+ isFlippedY = false;
+ if (LEFT_MOVABLE || RIGHT_MOVABLE) {
+ isFlippedX = LEFT_MOVABLE ? mouseX > originX : mouseX < originX;
+ }
+ if (TOP_MOVABLE || BOTTOM_MOVABLE) {
+ isFlippedY = TOP_MOVABLE ? mouseY > originY : mouseY < originY;
+ }
+ if (isFlippedX) {
+ var tmp = x1;x1 = x2;x2 = tmp;
+ origin[0] = 1 - origin[0];
+ }
+ if (isFlippedY) {
+ var _tmp = y1;y1 = y2;y2 = _tmp;
+ origin[1] = 1 - origin[1];
+ }
+ var box = new Box(x1, y1, x2, y2);
+ if (this.options.aspectRatio) {
+ var ratio = this.options.aspectRatio;
+ var isVerticalMovement = false;
+ if (MULTI_AXIS) {
+ isVerticalMovement = mouseY > box.y1 + ratio * box.width() || mouseY < box.y2 - ratio * box.width();
+ } else if (TOP_MOVABLE || BOTTOM_MOVABLE) {
+ isVerticalMovement = true;
+ }
+ var ratioMode = isVerticalMovement ? 'width' : 'height';
+ box.constrainToRatio(ratio, origin, ratioMode);
+ }
+ var min = this.options.minSize;
+ var max = this.options.maxSize;
+ box.constrainToSize(max.width, max.height, min.width, min.height, origin, this.options.aspectRatio);
+ var parentWidth = this.cropperEl.offsetWidth;
+ var parentHeight = this.cropperEl.offsetHeight;
+ box.constrainToBoundary(parentWidth, parentHeight, origin);
+ this.box = box;
+ this.redraw();
+ if (this.options.onCropMove !== null) {
+ this.options.onCropMove(this.getValue());
+ }
+ }
+ }, {
+ key: 'onHandleMoveEnd',
+ value: function onHandleMoveEnd(e) {
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ }
+ }, {
+ key: 'onRegionMoveStart',
+ value: function onRegionMoveStart(e) {
+ var _e$detail2 = e.detail,
+ mouseX = _e$detail2.mouseX,
+ mouseY = _e$detail2.mouseY;
+ var container = this.cropperEl.getBoundingClientRect();
+ mouseX = mouseX - container.left;
+ mouseY = mouseY - container.top;
+ this.currentMove = {
+ offsetX: mouseX - this.box.x1,
+ offsetY: mouseY - this.box.y1
+ };
+ if (this.options.onCropStart !== null) {
+ this.options.onCropStart(this.getValue());
+ }
+ }
+ }, {
+ key: 'onRegionMoveMoving',
+ value: function onRegionMoveMoving(e) {
+ var _e$detail3 = e.detail,
+ mouseX = _e$detail3.mouseX,
+ mouseY = _e$detail3.mouseY;
+ var _currentMove = this.currentMove,
+ offsetX = _currentMove.offsetX,
+ offsetY = _currentMove.offsetY;
+ var container = this.cropperEl.getBoundingClientRect();
+ mouseX = mouseX - container.left;
+ mouseY = mouseY - container.top;
+ this.box.move(mouseX - offsetX, mouseY - offsetY);
+ if (this.box.x1 < 0) {
+ this.box.move(0, null);
+ }
+ if (this.box.x2 > container.width) {
+ this.box.move(container.width - this.box.width(), null);
+ }
+ if (this.box.y1 < 0) {
+ this.box.move(null, 0);
+ }
+ if (this.box.y2 > container.height) {
+ this.box.move(null, container.height - this.box.height());
+ }
+ this.redraw();
+ if (this.options.onCropMove !== null) {
+ this.options.onCropMove(this.getValue());
+ }
+ }
+ }, {
+ key: 'onRegionMoveEnd',
+ value: function onRegionMoveEnd(e) {
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ }
+ }, {
+ key: 'getValue',
+ value: function getValue() {
+ var mode = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+ if (mode === null) {
+ mode = this.options.returnMode;
+ }
+ if (mode == 'real') {
+ var actualWidth = this.imageEl.naturalWidth;
+ var actualHeight = this.imageEl.naturalHeight;
+ var _imageEl$getBoundingC = this.imageEl.getBoundingClientRect(),
+ elementWidth = _imageEl$getBoundingC.width,
+ elementHeight = _imageEl$getBoundingC.height;
+ var factorX = actualWidth / elementWidth;
+ var factorY = actualHeight / elementHeight;
+ return {
+ x: Math.round(this.box.x1 * factorX),
+ y: Math.round(this.box.y1 * factorY),
+ width: Math.round(this.box.width() * factorX),
+ height: Math.round(this.box.height() * factorY)
+ };
+ } else if (mode == 'ratio') {
+ var _imageEl$getBoundingC2 = this.imageEl.getBoundingClientRect(),
+ _elementWidth = _imageEl$getBoundingC2.width,
+ _elementHeight = _imageEl$getBoundingC2.height;
+ return {
+ x: round(this.box.x1 / _elementWidth, 3),
+ y: round(this.box.y1 / _elementHeight, 3),
+ width: round(this.box.width() / _elementWidth, 3),
+ height: round(this.box.height() / _elementHeight, 3)
+ };
+ } else if (mode == 'raw') {
+ return {
+ x: Math.round(this.box.x1),
+ y: Math.round(this.box.y1),
+ width: Math.round(this.box.width()),
+ height: Math.round(this.box.height())
+ };
+ }
+ }
+ }], [{
+ key: 'parseOptions',
+ value: function parseOptions(opts) {
+ var defaults$$1 = {
+ aspectRatio: null,
+ maxSize: { width: null, height: null },
+ minSize: { width: null, height: null },
+ startSize: { width: 100, height: 100, unit: '%' },
+ returnMode: 'real',
+ onInitialize: null,
+ onCropStart: null,
+ onCropMove: null,
+ onCropEnd: null
+ };
+ var aspectRatio = null;
+ if (opts.aspectRatio !== undefined) {
+ if (typeof opts.aspectRatio === 'number') {
+ aspectRatio = opts.aspectRatio;
+ } else if (opts.aspectRatio instanceof Array) {
+ aspectRatio = opts.aspectRatio[1] / opts.aspectRatio[0];
+ }
+ }
+ var maxSize = null;
+ if (opts.maxSize !== undefined && opts.maxSize !== null) {
+ maxSize = {
+ width: opts.maxSize[0] || null,
+ height: opts.maxSize[1] || null,
+ unit: opts.maxSize[2] || 'px'
+ };
+ }
+ var minSize = null;
+ if (opts.minSize !== undefined && opts.minSize !== null) {
+ minSize = {
+ width: opts.minSize[0] || null,
+ height: opts.minSize[1] || null,
+ unit: opts.minSize[2] || 'px'
+ };
+ }
+ var startSize = null;
+ if (opts.startSize !== undefined && opts.startSize !== null) {
+ startSize = {
+ width: opts.startSize[0] || null,
+ height: opts.startSize[1] || null,
+ unit: opts.startSize[2] || '%'
+ };
+ }
+ var onInitialize = null;
+ if (typeof opts.onInitialize === 'function') {
+ onInitialize = opts.onInitialize;
+ }
+ var onCropStart = null;
+ if (typeof opts.onCropStart === 'function') {
+ onCropStart = opts.onCropStart;
+ }
+ var onCropEnd = null;
+ if (typeof opts.onCropEnd === 'function') {
+ onCropEnd = opts.onCropEnd;
+ }
+ var onCropMove = null;
+ if (typeof opts.onUpdate === 'function') {
+ console.warn('Croppr.js: `onUpdate` is deprecated and will be removed in the next major release. Please use `onCropMove` or `onCropEnd` instead.');
+ onCropMove = opts.onUpdate;
+ }
+ if (typeof opts.onCropMove === 'function') {
+ onCropMove = opts.onCropMove;
+ }
+ var returnMode = null;
+ if (opts.returnMode !== undefined) {
+ var s = opts.returnMode.toLowerCase();
+ if (['real', 'ratio', 'raw'].indexOf(s) === -1) {
+ throw "Invalid return mode.";
+ }
+ returnMode = s;
+ }
+ var convertToPixels = function convertToPixels(container) {
+ var width = container.offsetWidth;
+ var height = container.offsetHeight;
+ var sizeKeys = ['maxSize', 'minSize', 'startSize'];
+ for (var i = 0; i < sizeKeys.length; i++) {
+ var key = sizeKeys[i];
+ if (this[key] !== null) {
+ if (this[key].unit == '%') {
+ if (this[key].width !== null) {
+ this[key].width = this[key].width / 100 * width;
+ }
+ if (this[key].height !== null) {
+ this[key].height = this[key].height / 100 * height;
+ }
+ }
+ delete this[key].unit;
+ }
+ }
+ };
+ var defaultValue = function defaultValue(v, d) {
+ return v !== null ? v : d;
+ };
+ return {
+ aspectRatio: defaultValue(aspectRatio, defaults$$1.aspectRatio),
+ maxSize: defaultValue(maxSize, defaults$$1.maxSize),
+ minSize: defaultValue(minSize, defaults$$1.minSize),
+ startSize: defaultValue(startSize, defaults$$1.startSize),
+ returnMode: defaultValue(returnMode, defaults$$1.returnMode),
+ onInitialize: defaultValue(onInitialize, defaults$$1.onInitialize),
+ onCropStart: defaultValue(onCropStart, defaults$$1.onCropStart),
+ onCropMove: defaultValue(onCropMove, defaults$$1.onCropMove),
+ onCropEnd: defaultValue(onCropEnd, defaults$$1.onCropEnd),
+ convertToPixels: convertToPixels
+ };
+ }
+ }]);
+ return CropprCore;
+}();
+function round(value, decimals) {
+ return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
+}
+
+var Croppr$1 = function (_CropprCore) {
+ inherits(Croppr, _CropprCore);
+ /**
+ * @constructor
+ * Calls the CropprCore's constructor.
+ */
+ function Croppr(element, options) {
+ var _deferred = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
+ classCallCheck(this, Croppr);
+ return possibleConstructorReturn(this, (Croppr.__proto__ || Object.getPrototypeOf(Croppr)).call(this, element, options, _deferred));
+ }
+ /**
+ * Gets the value of the crop region.
+ * @param {String} [mode] Which mode of calculation to use: 'real', 'ratio' or
+ * 'raw'.
+ */
+ createClass(Croppr, [{
+ key: 'getValue',
+ value: function getValue(mode) {
+ return get(Croppr.prototype.__proto__ || Object.getPrototypeOf(Croppr.prototype), 'getValue', this).call(this, mode);
+ }
+ /**
+ * Changes the image src.
+ * @param {String} src
+ */
+ }, {
+ key: 'setImage',
+ value: function setImage(src) {
+ return get(Croppr.prototype.__proto__ || Object.getPrototypeOf(Croppr.prototype), 'setImage', this).call(this, src);
+ }
+ }, {
+ key: 'destroy',
+ value: function destroy() {
+ return get(Croppr.prototype.__proto__ || Object.getPrototypeOf(Croppr.prototype), 'destroy', this).call(this);
+ }
+ /**
+ * Moves the crop region to a specified coordinate.
+ * @param {Number} x
+ * @param {Number} y
+ */
+ }, {
+ key: 'moveTo',
+ value: function moveTo(x, y) {
+ this.box.move(x, y);
+ this.redraw();
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ return this;
+ }
+ /**
+ * Resizes the crop region to a specified width and height.
+ * @param {Number} width
+ * @param {Number} height
+ * @param {Array} origin The origin point to resize from.
+ * Defaults to [0.5, 0.5] (center).
+ */
+ }, {
+ key: 'resizeTo',
+ value: function resizeTo(width, height) {
+ var origin = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [.5, .5];
+ this.box.resize(width, height, origin);
+ this.redraw();
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ return this;
+ }
+ /**
+ * Scale the crop region by a factor.
+ * @param {Number} factor
+ * @param {Array} origin The origin point to resize from.
+ * Defaults to [0.5, 0.5] (center).
+ */
+ }, {
+ key: 'scaleBy',
+ value: function scaleBy(factor) {
+ var origin = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [.5, .5];
+ this.box.scale(factor, origin);
+ this.redraw();
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ return this;
+ }
+ }, {
+ key: 'reset',
+ value: function reset() {
+ this.box = this.initializeBox(this.options);
+ this.redraw();
+ if (this.options.onCropEnd !== null) {
+ this.options.onCropEnd(this.getValue());
+ }
+ return this;
+ }
+ }]);
+ return Croppr;
+}(CropprCore);
+
+return Croppr$1;
+
+})));
diff --git a/ui/media/js/main.js b/ui/media/js/main.js
index c0ed9112..a4476252 100644
--- a/ui/media/js/main.js
+++ b/ui/media/js/main.js
@@ -93,6 +93,11 @@ let initImagePreview = document.querySelector("#init_image_preview")
let initImageSizeBox = document.querySelector("#init_image_size_box")
let maskImageSelector = document.querySelector("#mask")
let maskImagePreview = document.querySelector("#mask_preview")
+let controlImageSelector = document.querySelector("#control_image")
+let controlImagePreview = document.querySelector("#control_image_preview")
+let controlImageClearBtn = document.querySelector(".control_image_clear")
+let controlImageContainer = document.querySelector("#control_image_wrapper")
+let controlImageFilterField = document.querySelector("#control_image_filter")
let applyColorCorrectionField = document.querySelector("#apply_color_correction")
let strictMaskBorderField = document.querySelector("#strict_mask_border")
let colorCorrectionSetting = document.querySelector("#apply_color_correction_setting")
@@ -114,6 +119,7 @@ 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")
+let controlnetModelField = new ModelDropdown(document.querySelector("#controlnet_model"), "controlnet", "None", false)
let vaeModelField = new ModelDropdown(document.querySelector("#vae_model"), "vae", "None")
let hypernetworkModelField = new ModelDropdown(document.querySelector("#hypernetwork_model"), "hypernetwork", "None")
let hypernetworkStrengthSlider = document.querySelector("#hypernetwork_strength_slider")
@@ -135,6 +141,7 @@ let embeddingsDialogCloseBtn = embeddingsDialog.querySelector("#embeddings-dialo
let embeddingsSearchBox = document.querySelector("#embeddings-search-box")
let embeddingsList = document.querySelector("#embeddings-list")
let embeddingsModeField = document.querySelector("#embeddings-mode")
+let embeddingsCardSizeSelector = document.querySelector("#embedding-card-size-selector")
let positiveEmbeddingText = document.querySelector("#positive-embedding-text")
let negativeEmbeddingText = document.querySelector("#negative-embedding-text")
@@ -164,6 +171,12 @@ let saveAllTreeToggle = document.querySelector("#tree_toggle")
let saveAllJSONToggle = document.querySelector("#json_toggle")
let saveAllFoldersOption = document.querySelector("#download-add-folders")
let splashScreenPopup = document.querySelector("#splash-screen")
+let useAsThumbDialog = document.querySelector("#use-as-thumb-dialog")
+let useAsThumbDialogCloseBtn = document.querySelector("#use-as-thumb-dialog-close-button")
+let useAsThumbImageContainer = document.querySelector("#use-as-thumb-img-container")
+let useAsThumbSelect = document.querySelector("#use-as-thumb-select")
+let useAsThumbSaveBtn = document.querySelector("#use-as-thumb-save")
+let useAsThumbCancelBtn = document.querySelector("#use-as-thumb-cancel")
let maskSetting = document.querySelector("#enable_mask")
@@ -177,6 +190,8 @@ let undoBuffer = []
const UNDO_LIMIT = 20
const MAX_IMG_UNDO_ENTRIES = 5
+let IMAGE_STEP_SIZE = 64
+
let loraModels = []
imagePreview.addEventListener("drop", function(ev) {
@@ -670,20 +685,127 @@ function onMakeSimilarClick(req, img) {
createTask(newTaskRequest)
}
-function onUseAsThumbnailClick(req, img) {
- console.log(req)
- console.log(img)
- let embedding = prompt("Embedding name")
- fetch(img.src)
- .then(response => response.blob())
- .then(async function(blob) {
- const formData = new FormData()
- formData.append("file", blob)
- const response = await fetch(`bucket/embeddings/${embedding}.jpg`, { method: 'POST', body: formData });
- console.log(response)
- })
+// gets a flat list of all models of a certain type, ignoring directories
+function getAllModelNames(type) {
+ function f(tree) {
+ if (tree == undefined) {
+ return []
+ }
+ let result=[];
+ tree.forEach( e => {
+ if (typeof(e) == "object") {
+ result = result.concat( f(e[1]))
+ } else {
+ result.push(e)
+ }
+ });
+ return result
+ }
+ return f(modelsOptions[type])
}
+function onUseAsThumbnailClick(req, img) {
+ let scale = 1
+ let targetWidth = img.naturalWidth
+ let targetHeight = img.naturalHeight
+ let resize = false
+ onUseAsThumbnailClick.img = img
+
+ if ( typeof(onUseAsThumbnailClick.croppr) == 'undefined' ) {
+ onUseAsThumbnailClick.croppr = new Croppr("#use-as-thumb-image", { aspectRatio: 1, minSize: [384,384,'px'], startSize: [512, 512, 'px'], returnMode:"real" })
+ }
+
+ if (img.naturalWidth > img.naturalHeight) {
+ if (img.naturalWidth > 768) {
+ scale = 768 / img.naturalWidth
+ targetWidth = 768
+ targetHeight = (img.naturalHeight*scale)>>>0
+ resize = true
+ }
+ } else {
+ if (img.naturalHeight > 768) {
+ scale = 768 / img.naturalHeight
+ targetHeight = 768
+ targetWidth = (img.naturalWidth*scale)>>>0
+ resize = true
+ }
+ }
+
+ onUseAsThumbnailClick.croppr.options.minSize = {width: 384*scale>>>0, height: 384*scale>>>0}
+ onUseAsThumbnailClick.croppr.options.startSize = {width: 512*scale>>>0, height: 512*scale>>>0}
+
+ if (resize) {
+ const canvas = document.createElement('canvas')
+ canvas.width = targetWidth
+ canvas.height = targetHeight
+ const ctx = canvas.getContext('2d')
+ ctx.drawImage(img, 0, 0, targetWidth, targetHeight)
+
+ onUseAsThumbnailClick.croppr.setImage(canvas.toDataURL('image/png'))
+ } else {
+ onUseAsThumbnailClick.croppr.setImage(img.src)
+ }
+
+ let embeddings = getAllModelNames("embeddings").filter( e => req.prompt.includes(e) || req.negative_prompt.includes(e) )
+ let LORA = []
+
+ if ("use_lora_model" in req) {
+ LORA=req.use_lora_model
+ }
+
+ let optgroup = document.createElement("optgroup")
+ optgroup.label = "Embeddings"
+ optgroup.replaceChildren(...embeddings.map(e => {
+ let option = document.createElement("option")
+ option.innerText = e
+ option.dataset["type"] = "embeddings"
+ return option
+ }))
+
+ useAsThumbSelect.replaceChildren(optgroup)
+ useAsThumbDialog.showModal()
+ onUseAsThumbnailClick.scale = scale
+}
+
+modalDialogCloseOnBackdropClick(useAsThumbDialog)
+makeDialogDraggable(useAsThumbDialog)
+
+useAsThumbDialogCloseBtn.addEventListener("click", () => {
+ useAsThumbDialog.close()
+})
+
+useAsThumbCancelBtn.addEventListener("click", () => {
+ useAsThumbDialog.close()
+})
+
+useAsThumbSaveBtn.addEventListener("click", (e) => {
+ let scale = 1/onUseAsThumbnailClick.scale
+ let crop = onUseAsThumbnailClick.croppr.getValue()
+
+ let len = Math.max(crop.width*scale, 384)
+ let profileName = profileNameField.value
+
+ cropImageDataUrl(onUseAsThumbnailClick.img.src, crop.x*scale, crop.y*scale, len, len)
+ .then(thumb => fetch(thumb))
+ .then(response => response.blob())
+ .then(async function(blob) {
+ const formData = new FormData()
+ formData.append("file", blob)
+ let options = useAsThumbSelect.selectedOptions
+ let promises = []
+ for (let embedding of options) {
+ promises.push(fetch(`bucket/${profileName}/${embedding.dataset["type"]}/${embedding.value}.png`, { method: 'POST', body: formData }))
+ }
+ return Promise.all(promises)
+ }).then(() => {
+ useAsThumbDialog.close()
+ })
+ .catch(error => {
+ console.error(error)
+ showToast("Couldn't save thumbnail. "+error)
+ })
+})
+
function enqueueImageVariationTask(req, img, reqDiff) {
const imageSeed = img.getAttribute("data-seed")
@@ -1361,9 +1483,25 @@ function createTask(task) {
function getCurrentUserRequest() {
const numOutputsTotal = parseInt(numOutputsTotalField.value)
- const numOutputsParallel = parseInt(numOutputsParallelField.value)
+ let numOutputsParallel = parseInt(numOutputsParallelField.value)
const seed = randomSeedField.checked ? Math.floor(Math.random() * (2 ** 32 - 1)) : parseInt(seedField.value)
+ // if (
+ // testDiffusers.checked &&
+ // document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall" &&
+ // document.querySelector("#convert_to_tensorrt").checked
+ // ) {
+ // // TRT enabled
+
+ // numOutputsParallel = 1 // force 1 parallel
+ // }
+
+ // clamp to multiple of 8
+ let width = parseInt(widthField.value)
+ let height = parseInt(heightField.value)
+ width = width - (width % IMAGE_STEP_SIZE)
+ height = height - (height % IMAGE_STEP_SIZE)
+
const newTask = {
batchesDone: 0,
numOutputsTotal: numOutputsTotal,
@@ -1376,8 +1514,8 @@ function getCurrentUserRequest() {
num_outputs: numOutputsParallel,
num_inference_steps: parseInt(numInferenceStepsField.value),
guidance_scale: parseFloat(guidanceScaleField.value),
- width: parseInt(widthField.value),
- height: parseInt(heightField.value),
+ width: width,
+ height: height,
// allow_nsfw: allowNSFWField.checked,
vram_usage_level: vramUsageLevelField.value,
sampler_name: samplerField.value,
@@ -1451,6 +1589,29 @@ function getCurrentUserRequest() {
if (testDiffusers.checked && document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall") {
// TRT is installed
newTask.reqBody.convert_to_tensorrt = document.querySelector("#convert_to_tensorrt").checked
+ let trtBuildConfig = {
+ batch_size_range: [
+ parseInt(document.querySelector("#trt-build-min-batch").value),
+ parseInt(document.querySelector("#trt-build-max-batch").value),
+ ],
+ dimensions_range: [],
+ }
+
+ let sizes = [512, 768, 1024, 1280, 1536]
+ sizes.forEach((i) => {
+ let el = document.querySelector("#trt-build-res-" + i)
+ if (el.checked) {
+ trtBuildConfig["dimensions_range"].push([i, i + 256])
+ }
+ })
+ newTask.reqBody.trt_build_config = trtBuildConfig
+ }
+ if (controlnetModelField.value !== "" && IMAGE_REGEX.test(controlImagePreview.src)) {
+ newTask.reqBody.use_controlnet_model = controlnetModelField.value
+ newTask.reqBody.control_image = controlImagePreview.src
+ if (controlImageFilterField.value !== "") {
+ newTask.reqBody.control_filter_to_apply = controlImageFilterField.value
+ }
}
return newTask
@@ -1858,6 +2019,51 @@ function onFixFaceModelChange() {
gfpganModelField.addEventListener("change", onFixFaceModelChange)
onFixFaceModelChange()
+function onControlnetModelChange() {
+ let configBox = document.querySelector("#controlnet_config")
+ if (IMAGE_REGEX.test(controlImagePreview.src)) {
+ configBox.classList.remove("displayNone")
+ controlImageContainer.classList.remove("displayNone")
+ } else {
+ configBox.classList.add("displayNone")
+ controlImageContainer.classList.add("displayNone")
+ }
+}
+controlImagePreview.addEventListener("load", onControlnetModelChange)
+controlImagePreview.addEventListener("unload", onControlnetModelChange)
+onControlnetModelChange()
+
+function onControlImageFilterChange() {
+ let filterId = controlImageFilterField.value
+ if (filterId.includes("openpose")) {
+ controlnetModelField.value = "control_v11p_sd15_openpose"
+ } else if (filterId === "canny") {
+ controlnetModelField.value = "control_v11p_sd15_canny"
+ } else if (filterId === "mlsd") {
+ controlnetModelField.value = "control_v11p_sd15_mlsd"
+ } else if (filterId === "mlsd") {
+ controlnetModelField.value = "control_v11p_sd15_mlsd"
+ } else if (filterId.includes("scribble")) {
+ controlnetModelField.value = "control_v11p_sd15_scribble"
+ } else if (filterId.includes("softedge")) {
+ controlnetModelField.value = "control_v11p_sd15_softedge"
+ } else if (filterId === "normal_bae") {
+ controlnetModelField.value = "control_v11p_sd15_normalbae"
+ } else if (filterId.includes("depth")) {
+ controlnetModelField.value = "control_v11f1p_sd15_depth"
+ } else if (filterId === "lineart_anime") {
+ controlnetModelField.value = "control_v11p_sd15s2_lineart_anime"
+ } else if (filterId.includes("lineart")) {
+ controlnetModelField.value = "control_v11p_sd15_lineart"
+ } else if (filterId === "shuffle") {
+ controlnetModelField.value = "control_v11e_sd15_shuffle"
+ } else if (filterId === "segment") {
+ controlnetModelField.value = "control_v11p_sd15_seg"
+ }
+}
+controlImageFilterField.addEventListener("change", onControlImageFilterChange)
+onControlImageFilterChange()
+
upscaleModelField.disabled = !useUpscalingField.checked
upscaleAmountField.disabled = !useUpscalingField.checked
useUpscalingField.addEventListener("change", function(e) {
@@ -2087,6 +2293,7 @@ function checkRandomSeed() {
randomSeedField.addEventListener("input", checkRandomSeed)
checkRandomSeed()
+// warning: the core plugin `image-editor-improvements.js:172` replaces loadImg2ImgFromFile() with a custom version
function loadImg2ImgFromFile() {
if (initImageSelector.files.length === 0) {
return
@@ -2148,6 +2355,47 @@ promptsFromFileBtn.addEventListener("click", function() {
promptsFromFileSelector.click()
})
+function loadControlnetImageFromFile() {
+ if (controlImageSelector.files.length === 0) {
+ return
+ }
+
+ let reader = new FileReader()
+ let file = controlImageSelector.files[0]
+
+ reader.addEventListener("load", function(event) {
+ controlImagePreview.src = reader.result
+ })
+
+ if (file) {
+ reader.readAsDataURL(file)
+ }
+}
+controlImageSelector.addEventListener("change", loadControlnetImageFromFile)
+
+function controlImageLoad() {
+ let w = controlImagePreview.naturalWidth
+ let h = controlImagePreview.naturalHeight
+ w = w - (w % IMAGE_STEP_SIZE)
+ h = h - (h % IMAGE_STEP_SIZE)
+
+ addImageSizeOption(w)
+ addImageSizeOption(h)
+
+ widthField.value = w
+ heightField.value = h
+ widthField.dispatchEvent(new Event("change"))
+ heightField.dispatchEvent(new Event("change"))
+}
+controlImagePreview.addEventListener("load", controlImageLoad)
+
+function controlImageUnload() {
+ controlImageSelector.value = null
+ controlImagePreview.src = ""
+ controlImagePreview.dispatchEvent(new Event("unload"))
+}
+controlImageClearBtn.addEventListener("click", controlImageUnload)
+
promptsFromFileSelector.addEventListener("change", async function() {
if (promptsFromFileSelector.files.length === 0) {
return
@@ -2276,6 +2524,8 @@ function tunnelUpdate(event) {
}
}
+let trtSettingsForced = false
+
function packagesUpdate(event) {
let trtBtn = document.getElementById("toggle-tensorrt-install")
let trtInstalled = "packages_installed" in event && "tensorrt" in event["packages_installed"]
@@ -2290,6 +2540,23 @@ function packagesUpdate(event) {
if (document.getElementById("toggle-tensorrt-install").innerHTML == "Uninstall") {
document.querySelector("#enable_trt_config").classList.remove("displayNone")
+ document.querySelector("#trt-build-config").classList.remove("displayNone")
+
+ if (!trtSettingsForced) {
+ // settings for demo
+ promptField.value = "Dragons fighting with a knight, castle, war scene, fantasy, cartoon, flames, HD"
+ seedField.value = 3187947173
+ widthField.value = 1024
+ heightField.value = 768
+ randomSeedField.checked = false
+ seedField.disabled = false
+ stableDiffusionModelField.value = "sd-v1-4"
+
+ // numOutputsParallelField.classList.add("displayNone")
+ // document.querySelector("#num_outputs_parallel_label").classList.add("displayNone")
+
+ trtSettingsForced = true
+ }
}
}
@@ -2371,40 +2638,53 @@ document.getElementById("toggle-tensorrt-install").addEventListener("click", fun
/* Embeddings */
-let icl = []
function updateEmbeddingsList(filter = "") {
function html(model, iconlist = [], prefix = "", filter = "") {
filter = filter.toLowerCase()
- let toplevel = ""
- let folders = ""
- console.log(iconlist)
+ let toplevel = document.createElement("div")
+ let folders = document.createElement("div")
let embIcon = Object.assign({}, ...iconlist.map( x=> ({[x.toLowerCase().split('.').slice(0,-1).join('.')]:x})))
+ let profileName = profileNameField.value
model?.forEach((m) => {
if (typeof m == "string") {
let token=m.toLowerCase()
if (token.search(filter) != -1) {
- let img = '/media/images/noimg.png'
- if (token in embIcon) {
- img = `/bucket/embeddings/${embIcon[token]}`
+ let button
+ if (iconlist.length==0) {
+ button = document.createElement("button")
+ button.innerText="m"
+ } else {
+ let img = '/media/images/noimg.png'
+ if (token in embIcon) {
+ img = `/bucket/${profileName}/embeddings/${embIcon[token]}`
+ }
+ button = createModifierCard(m, [img,img], true)
}
- toplevel += ` `
+ button.dataset["embedding"] = m
+ button.addEventListener("click", onButtonClick)
+ toplevel.appendChild(button)
}
} else {
let subdir = html(m[1], iconlist, prefix + m[0] + "/", filter)
- if (subdir != "") {
- folders +=
- `
${prefix}${m[0]}
` +
- subdir +
- "
"
+ if (typeof(subdir) == "object") {
+ let div1 = document.createElement("div")
+ let div2 = document.createElement("div")
+ div1.classList.add("collapsible-content")
+ div1.classList.add("embedding-category")
+ div1.appendChild(subdir)
+ div2.replaceChildren(htmlToElement(`
${prefix}${m[0]}
`), div1)
+ folders.appendChild(div2)
}
}
})
- return toplevel + folders
+ let result = document.createElement("div")
+ result.replaceChildren(toplevel, htmlToElement(' '), folders)
+ return result
}
function onButtonClick(e) {
- let text = e.target.closest("button").dataset["embedding"]
+ let text = e.target.closest("[data-embedding]").dataset["embedding"]
const insertIntoNegative = e.shiftKey || positiveEmbeddingText.classList.contains("displayNone")
if (embeddingsModeField.value == "insert") {
@@ -2429,8 +2709,18 @@ function updateEmbeddingsList(filter = "") {
}
}
+ // Usually the rendering of the Embeddings HTML takes less than a second. In case it takes longer, show a spinner
+ embeddingsList.innerHTML = `
+
+
+
+
+
+
+ `
+
// Remove after fixing https://github.com/huggingface/diffusers/issues/3922
- let warning = ""
+ let warning = ""
if (vramUsageLevelField.value == "low") {
warning = `
@@ -2439,17 +2729,17 @@ function updateEmbeddingsList(filter = "") {
}
// END of remove block
- fetch("/bucket/embeddings/")
- .then(response => response.json())
- .then(iconlist => {
- embeddingsList.innerHTML = warning + html(modelsOptions.embeddings, iconlist, "", filter)
- embeddingsList.querySelectorAll("button").forEach((b) => {
- b.addEventListener("click", onButtonClick)
- })
+ let profileName = profileNameField.value
+ fetch(`/bucket/${profileName}/embeddings/`)
+ .then(response => response.status==200 ? response.json(): [])
+ .then(async function(iconlist) {
+
+ embeddingsList.replaceChildren(htmlToElement(warning), html(modelsOptions.embeddings, iconlist, "", filter))
createCollapsibles(embeddingsList)
if (filter != "") {
embeddingsExpandAll()
}
+ resizeModifierCards(embeddingsCardSizeSelector.value)
})
}
@@ -2458,23 +2748,33 @@ function showEmbeddingDialog() {
embeddingsSearchBox.value = ""
embeddingsDialog.showModal()
}
+
embeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.remove("displayNone")
negativeEmbeddingText.classList.add("displayNone")
showEmbeddingDialog()
})
+
negativeEmbeddingsButton.addEventListener("click", () => {
positiveEmbeddingText.classList.add("displayNone")
negativeEmbeddingText.classList.remove("displayNone")
showEmbeddingDialog()
})
+
embeddingsDialogCloseBtn.addEventListener("click", (e) => {
embeddingsDialog.close()
})
+
embeddingsSearchBox.addEventListener("input", (e) => {
updateEmbeddingsList(embeddingsSearchBox.value)
})
+embeddingsCardSizeSelector.addEventListener("change", (e) => {
+ resizeModifierCards(embeddingsCardSizeSelector.value)
+})
+
+
+
modalDialogCloseOnBackdropClick(embeddingsDialog)
makeDialogDraggable(embeddingsDialog)
@@ -2522,10 +2822,6 @@ embeddingsCollapsiblesBtn.addEventListener("click", (e) => {
}
})
-if (testDiffusers.checked) {
- document.getElementById("embeddings-container").classList.remove("displayNone")
-}
-
/* Pause function */
document.querySelectorAll(".tab").forEach(linkTabContents)
@@ -2789,13 +3085,25 @@ let recentResolutionsValues = []
})()
/* Gallery JS */
+function galleryImage(item) {
+ let div = document.createElement("div")
+ let img = document.createElement("img")
+
+ img.src = "/image/" + item.path
+ img.dataset["request"] = JSON.stringify(item)
+ div.appendChild(img)
+ return div
+}
function refreshGallery() {
let container = document.getElementById("imagecontainer")
- container.remove()
+ container.innerHTML=""
fetch('/all_images')
- .then(response => response.text())
- .then(text => new DOMParser().parseFromString(text, 'text/html'))
- .then(html_like => html_like.getElementsByTagName('div')[0])
- .then(div => document.getElementById("tab-content-gallery").appendChild(div))
-}
\ No newline at end of file
+ .then(response => response.json())
+ .then(json => {
+ console.log(json)
+ json.forEach( item => {
+ container.appendChild(galleryImage(item))
+ })
+ })
+}
diff --git a/ui/media/js/parameters.js b/ui/media/js/parameters.js
index 19892c94..7fdf8632 100644
--- a/ui/media/js/parameters.js
+++ b/ui/media/js/parameters.js
@@ -121,6 +121,15 @@ var PARAMETERS = [
icon: "fa-arrow-down-short-wide",
default: false,
},
+ {
+ id: "extract_lora_from_prompt",
+ type: ParameterType.checkbox,
+ label: "Extract LoRA tags from the prompt",
+ note:
+ "Automatically extract lora tags like <lora:name:0.4> from the prompt, and apply the correct LoRA (if present)",
+ icon: "fa-code",
+ default: true,
+ },
{
id: "ui_open_browser_on_start",
type: ParameterType.checkbox,
@@ -185,6 +194,16 @@ var PARAMETERS = [
icon: "fa-check-double",
default: true,
},
+ {
+ id: "profileName",
+ type: ParameterType.custom,
+ label: "Profile Name",
+ note: "Name of the profile for model manager settings, e.g. thumbnails for embeddings. Use this to have different settings for different users.",
+ render: (parameter) => {
+ return ``
+ },
+ icon: "fa-user-gear",
+ },
{
id: "listen_to_network",
type: ParameterType.checkbox,
@@ -220,11 +239,11 @@ var PARAMETERS = [
{
id: "test_diffusers",
type: ParameterType.checkbox,
- label: "Test Diffusers",
+ label: "Use the new v3 engine (diffusers)",
note:
- "Experimental! Can have bugs! Use upcoming features (like LoRA) in our new engine. Please press Save, then restart the program after changing this.",
+ "Use our new v3 engine, with additional features like LoRA, ControlNet, SDXL, Embeddings, Tiling and lots more! Please press Save, then restart the program after changing this.",
icon: "fa-bolt",
- default: false,
+ default: true,
saveInAppConfig: true,
},
{
@@ -248,7 +267,19 @@ var PARAMETERS = [
label: "NVIDIA TensorRT",
note: `Faster image generation by converting your Stable Diffusion models to the NVIDIA TensorRT format. You can choose the
models to convert. Download size: approximately 2 GB.
- Early access version: support for LoRA is still under development.`,
+ Early access version: support for LoRA is still under development.
+
+
Build Config:
+ Batch size range:
+
+
+ Build for resolutions:
+ 512x512 to 768x768
+ 768x768 to 1024x1024
+ 1024x1024 to 1280x1280
+ 1280x1280 to 1536x1536
+ 1536x1536 to 1792x1792
+
`,
icon: "fa-angles-up",
render: () => '',
table: installExtrasTable,
@@ -389,6 +420,7 @@ let useBetaChannelField = document.querySelector("#use_beta_channel")
let uiOpenBrowserOnStartField = document.querySelector("#ui_open_browser_on_start")
let confirmDangerousActionsField = document.querySelector("#confirm_dangerous_actions")
let testDiffusers = document.querySelector("#test_diffusers")
+let profileNameField = document.querySelector("#profileName")
let saveSettingsBtn = document.querySelector("#save-system-settings-btn")
@@ -433,7 +465,10 @@ async function getAppConfig() {
listenPortField.value = config.net.listen_port
}
- const testDiffusersEnabled = config.test_diffusers && config.update_branch !== "main"
+ let testDiffusersEnabled = config.update_branch !== "main"
+ if (config.test_diffusers === false) {
+ testDiffusersEnabled = false
+ }
testDiffusers.checked = testDiffusersEnabled
if (config.config_on_startup) {
@@ -449,15 +484,22 @@ async function getAppConfig() {
if (!testDiffusersEnabled) {
document.querySelector("#lora_model_container").style.display = "none"
document.querySelector("#tiling_container").style.display = "none"
+ document.querySelector("#controlnet_model_container").style.display = "none"
+ document.querySelector("#hypernetwork_model_container").style.display = ""
+ document.querySelector("#hypernetwork_strength_container").style.display = ""
document.querySelectorAll("#sampler_name option.diffusers-only").forEach((option) => {
option.style.display = "none"
})
- customWidthField.step=64
- customHeightField.step=64
+ IMAGE_STEP_SIZE = 64
+ customWidthField.step = IMAGE_STEP_SIZE
+ customHeightField.step = IMAGE_STEP_SIZE
} else {
document.querySelector("#lora_model_container").style.display = ""
document.querySelector("#tiling_container").style.display = ""
+ document.querySelector("#controlnet_model_container").style.display = ""
+ document.querySelector("#hypernetwork_model_container").style.display = "none"
+ document.querySelector("#hypernetwork_strength_container").style.display = "none"
document.querySelectorAll("#sampler_name option.k_diffusion-only").forEach((option) => {
option.style.display = "none"
@@ -465,8 +507,9 @@ async function getAppConfig() {
document.querySelector("#clip_skip_config").classList.remove("displayNone")
document.querySelector("#embeddings-button").classList.remove("displayNone")
document.querySelector("#negative-embeddings-button").classList.remove("displayNone")
- customWidthField.step=8
- customHeightField.step=8
+ IMAGE_STEP_SIZE = 8
+ customWidthField.step = IMAGE_STEP_SIZE
+ customHeightField.step = IMAGE_STEP_SIZE
}
console.log("get config status response", config)
diff --git a/ui/media/js/searchable-models.js b/ui/media/js/searchable-models.js
index 85b9bbe9..299c60dc 100644
--- a/ui/media/js/searchable-models.js
+++ b/ui/media/js/searchable-models.js
@@ -552,17 +552,23 @@ class ModelDropdown {
this.createModelNodeList(`${folderName || ""}/${childFolderName}`, childModels, false)
)
} else {
+ let modelId = model
+ let modelName = model
+ if (typeof model === "object") {
+ modelId = Object.keys(model)[0]
+ modelName = model[modelId]
+ }
const classes = ["model-file"]
if (isRootFolder) {
classes.push("in-root-folder")
}
// Remove the leading slash from the model path
- const fullPath = folderName ? `${folderName.substring(1)}/${model}` : model
+ const fullPath = folderName ? `${folderName.substring(1)}/${modelId}` : modelId
modelsMap.set(
- model,
+ modelId,
createElement("li", { "data-path": fullPath }, classes, [
createElement("i", undefined, ["fa-regular", "fa-file", "icon"]),
- model,
+ modelName,
])
)
}
@@ -643,22 +649,6 @@ async function getModels(scanForMalicious = true) {
makeImageBtn.disabled = true
}
- /* This code should no longer be needed. Commenting out for now, will cleanup later.
- const sd_model_setting_key = "stable_diffusion_model"
- const vae_model_setting_key = "vae_model"
- const hypernetwork_model_key = "hypernetwork_model"
-
- const stableDiffusionOptions = modelsOptions['stable-diffusion']
- const vaeOptions = modelsOptions['vae']
- const hypernetworkOptions = modelsOptions['hypernetwork']
-
- // TODO: set default for model here too
- SETTINGS[sd_model_setting_key].default = stableDiffusionOptions[0]
- if (getSetting(sd_model_setting_key) == '' || SETTINGS[sd_model_setting_key].value == '') {
- setSetting(sd_model_setting_key, stableDiffusionOptions[0])
- }
- */
-
// notify ModelDropdown objects to refresh
document.dispatchEvent(new Event("refreshModels"))
} catch (e) {
@@ -667,4 +657,7 @@ async function getModels(scanForMalicious = true) {
}
// reload models button
-document.querySelector("#reload-models").addEventListener("click", () => getModels())
+document.querySelector("#reload-models").addEventListener("click", (e) => {
+ e.stopPropagation()
+ getModels()
+})
diff --git a/ui/media/js/utils.js b/ui/media/js/utils.js
index 9db0d1cc..8fef9450 100644
--- a/ui/media/js/utils.js
+++ b/ui/media/js/utils.js
@@ -1097,6 +1097,48 @@ async function deleteKeys(keyToDelete) {
}
}
+/**
+ * @param {String} Data URL of the image
+ * @param {Integer} Top left X-coordinate of the crop area
+ * @param {Integer} Top left Y-coordinate of the crop area
+ * @param {Integer} Width of the crop area
+ * @param {Integer} Height of the crop area
+ * @return {String}
+ */
+function cropImageDataUrl(dataUrl, x, y, width, height) {
+ return new Promise((resolve, reject) => {
+ const image = new Image()
+ image.src = dataUrl
+
+ image.onload = () => {
+ const canvas = document.createElement('canvas')
+ canvas.width = width
+ canvas.height = height
+
+ const ctx = canvas.getContext('2d')
+ ctx.drawImage(image, x, y, width, height, 0, 0, width, height)
+
+ const croppedDataUrl = canvas.toDataURL('image/png')
+ resolve(croppedDataUrl)
+ }
+
+ image.onerror = (error) => {
+ reject(error)
+ }
+ })
+}
+
+/**
+ * @param {String} HTML representing a single element
+ * @return {Element}
+ */
+function htmlToElement(html) {
+ var template = document.createElement('template');
+ html = html.trim(); // Never return a text node of whitespace as the result
+ template.innerHTML = html;
+ return template.content.firstChild;
+}
+
function modalDialogCloseOnBackdropClick(dialog) {
dialog.addEventListener('mousedown', function (event) {
// Firefox creates an event with clientX|Y = 0|0 when choosing an