├── .github.ignore
└── workflows
│ └── pylint.yml
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ └── feature_request.yml
├── .gitignore
├── .gitmodules
├── .prettierrc.json
├── LICENSE
├── README.MD
├── install.py
├── javascript
├── openoutpaint-ext.js
└── openoutpaint-imagehistory.js
├── preload.py
├── scripts
├── api.py
├── interface.py
└── main.py
└── style.css
/.github.ignore/workflows/pylint.yml:
--------------------------------------------------------------------------------
1 | name: Pylint
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | strategy:
9 | matrix:
10 | python-version: ["3.8", "3.9", "3.10"]
11 | steps:
12 | - uses: actions/checkout@v3
13 | - name: Set up Python ${{ matrix.python-version }}
14 | uses: actions/setup-python@v3
15 | with:
16 | python-version: ${{ matrix.python-version }}
17 | - name: Install dependencies
18 | run: |
19 | python -m pip install --upgrade pip
20 | pip install pylint
21 | - name: Analysing the code with pylint
22 | run: |
23 | pylint $(git ls-files '*.py')
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.yml:
--------------------------------------------------------------------------------
1 | name: Bug Report
2 | description: You think somethings is broken in the UI
3 | title: "[Bug]: "
4 | labels: ["bug"]
5 | assignees: zero01101, seijihariki
6 |
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | *Please complete this form with as much detailed information as possible.*
12 | - type: checkboxes
13 | attributes:
14 | label: Is ths issue about the extension?
15 | description: Most issues should likely be submitted to the main [openOutpaint issue tracker](https://github.com/zero01101/openOutpaint/issues) unless they are truly an issue with Graido or other A1111 extension functionality; e.g. the send-to-* interface buttons.
16 | options:
17 | - label: Yup, this is for sure about the extension
18 | required: true
19 | - type: textarea
20 | id: what-did
21 | attributes:
22 | label: What happened?
23 | description: What happened that you weren't expecting, or what happened incorrectly?
24 | validations:
25 | required: true
26 | - type: textarea
27 | id: steps
28 | attributes:
29 | label: Steps to reproduce the problem
30 | description: Please provide us with precise step-by-step information on how to reproduce the issue
31 | value: |
32 | 1. Go to ....
33 | 2. Press ....
34 | 3. ... [etc]
35 | validations:
36 | required: true
37 | - type: textarea
38 | id: what-should
39 | attributes:
40 | label: What should have happened?
41 | description: Describe what you believe should have ocurred instead of what actually happened.
42 | validations:
43 | required: true
44 | - type: input
45 | id: commit
46 | attributes:
47 | label: Commit where the problem happens
48 | description: Which commit are you running? (i.e. https://github.com/zero01101/openOutpaint/commit/bf21c19ae352800d9e1b37bb490e817b6848e533, bf21c19)
49 | validations:
50 | required: true
51 | - type: dropdown
52 | id: platforms
53 | attributes:
54 | label: What platforms do you use to access openOutpaint?
55 | multiple: true
56 | options:
57 | - Windows
58 | - Linux
59 | - MacOS
60 | - iOS
61 | - Android
62 | - Other/Cloud
63 | validations:
64 | required: true
65 | - type: dropdown
66 | id: browsers
67 | attributes:
68 | label: What browsers do you use to access the UI ?
69 | multiple: true
70 | options:
71 | - Mozilla Firefox
72 | - Google Chrome
73 | - Brave
74 | - Apple Safari
75 | - Microsoft Edge
76 | - Opera
77 | - Other (please list in additional information)
78 | validations:
79 | required: true
80 | - type: textarea
81 | id: browser-extensions
82 | attributes:
83 | label: Browser Extensions/Addons
84 | description: Please list all browser extensions/addons you have running. Some have been known to cause issues with openOutpaint.
85 | validations:
86 | required: true
87 | - type: textarea
88 | id: webui-commandline
89 | attributes:
90 | label: AUTOMATIC1111 webUI Commandline Arguments
91 | description: Please list all used commandline arguments passed to A1111 webUI (i.e. `--api`).
92 | validations:
93 | required: true
94 | - type: textarea
95 | id: misc
96 | attributes:
97 | label: Additional information
98 | description: Please provide us with any relevant additional information, context, screenshots, etc.
99 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for this project
3 | title: "[Feature Request]: "
4 | labels: ["enhancement"]
5 | assignees: zero01101, seijihariki
6 |
7 | body:
8 | - type: markdown
9 | attributes:
10 | value: |
11 | *Please complete this form with as much detailed information as possible.*
12 | - type: textarea
13 | id: related
14 | attributes:
15 | label: Is your feature request related to a problem? Please describe.
16 | description: A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
17 | validations:
18 | required: true
19 | - type: textarea
20 | id: feature
21 | attributes:
22 | label: Describe the solution you'd like
23 | description: A clear and concise description of what you want to happen, preferably with example use-case scenario.
24 | validations:
25 | required: true
26 | - type: textarea
27 | id: workflow
28 | attributes:
29 | label: Proposed workflow
30 | description: Please provide us with step by step information on how you'd like the feature to be accessed and used
31 | value: |
32 | 1. Go to ....
33 | 2. Press ....
34 | 3. ...
35 | validations:
36 | required: true
37 | - type: textarea
38 | id: alternatives
39 | attributes:
40 | label: Describe alternatives you've considered
41 | description: A clear and concise description of any alternative solutions or features you've considered.
42 | validations:
43 | required: true
44 | - type: textarea
45 | id: misc
46 | attributes:
47 | label: Additional context
48 | description: Add any other context or screenshots about the feature request here.
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # IDEs
10 | .vscode/
11 |
12 | # Distribution / packaging
13 | .Python
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | pip-wheel-metadata/
27 | share/python-wheels/
28 | *.egg-info/
29 | .installed.cfg
30 | *.egg
31 | MANIFEST
32 |
33 | # PyInstaller
34 | # Usually these files are written by a python script from a template
35 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
36 | *.manifest
37 | *.spec
38 |
39 | # Installer logs
40 | pip-log.txt
41 | pip-delete-this-directory.txt
42 |
43 | # Unit test / coverage reports
44 | htmlcov/
45 | .tox/
46 | .nox/
47 | .coverage
48 | .coverage.*
49 | .cache
50 | nosetests.xml
51 | coverage.xml
52 | *.cover
53 | *.py,cover
54 | .hypothesis/
55 | .pytest_cache/
56 |
57 | # Translations
58 | *.mo
59 | *.pot
60 |
61 | # Django stuff:
62 | *.log
63 | local_settings.py
64 | db.sqlite3
65 | db.sqlite3-journal
66 |
67 | # Flask stuff:
68 | instance/
69 | .webassets-cache
70 |
71 | # Scrapy stuff:
72 | .scrapy
73 |
74 | # Sphinx documentation
75 | docs/_build/
76 |
77 | # PyBuilder
78 | target/
79 |
80 | # Jupyter Notebook
81 | .ipynb_checkpoints
82 |
83 | # IPython
84 | profile_default/
85 | ipython_config.py
86 |
87 | # pyenv
88 | .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "app"]
2 | path = app
3 | url = https://github.com/zero01101/openOutpaint.git
4 | branch = main
5 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "bracketSameLine": true,
4 | "bracketSpacing": false,
5 | "embeddedLanguageFormatting": "auto",
6 | "htmlWhitespaceSensitivity": "ignore",
7 | "insertPragma": false,
8 | "jsxSingleQuote": false,
9 | "printWidth": 80,
10 | "proseWrap": "preserve",
11 | "quoteProps": "as-needed",
12 | "requirePragma": false,
13 | "semi": true,
14 | "singleQuote": false,
15 | "tabWidth": 2,
16 | "trailingComma": "es5",
17 | "useTabs": true,
18 | "vueIndentScriptAndStyle": false
19 | }
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 tim h
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | In this repo lives a mighty handy little wrapper for adding [openOutpaint](https://github.com/zero01101/openOutpaint) to [AUTOMATIC1111 webUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) directly as a native extension.
2 |
3 | Please see the respective READMEs and wikis for each of the above projects for a more comprehensive understanding of their feature sets.
4 |
5 | This extension also adds buttons to send output from webUI txt2img and img2img tools directly to openOutpaint which will also include the prompts used for convenience.
6 |
7 | **_2023-01-23: new `--lock-oo-submodule` commandline argument if you want to roll back to a previous version of openOutpaint and keep it there - be sure to install/run openOutpaint extension at least once before enabling this flag_**
8 |
9 | **Note: Requires `--api` flag enabled in your webui-user launch script!**
10 |
11 | **_FURTHER NOTE: the commandline flag `--gradio-debug` disables custom API routes and completely breaks openOutpaint. please remove it from your COMMANDLINE_ARGS before running openOutpaint._**
12 |
13 | **_EVEN FURTHER NOTE: [PLEASE SEE DOCUMENTATION REGARDING NEW HRfix FEATURES](https://github.com/zero01101/openOutpaint/wiki/Manual#hrfix) IMPLEMENTED AS OF webUI COMMIT [ef27a18](https://github.com/AUTOMATIC1111/stable-diffusion-webui/commit/ef27a18b6b7cb1a8eebdc9b2e88d25baf2c2414d)_**
14 |
15 | 
16 |
17 | ### surprising incompatibilities
18 |
19 | **_COLAB USERS: you may experience issues installing openOutpaint (and other webUI extensions) - there is a workaround that has been discovered and tested against [TheLastBen's fast-stable-diffusion](https://github.com/TheLastBen/fast-stable-diffusion). Please see [this discussion](https://github.com/TheLastBen/fast-stable-diffusion/discussions/1161) containing the workaround, which requires adding a command into the final cell of the colab, as well as setting `Enable_API` to `True`._**
20 |
21 | - [microsoft editor extension for chrome/edge seems to disable the overmask slider](https://github.com/zero01101/openOutpaint/discussions/88#discussioncomment-4498341)
22 | - ~~[duckduckgo privacy extension for firefox breaks outpainting, resulting in pure black output](https://github.com/zero01101/openOutpaint-webUI-extension/issues/3#issuecomment-1367694000) - add an exception for your openOutpaint host (likely localhost or 127.0.0.1)~~ should be fixed as of [b128943](https://github.com/zero01101/openOutpaint/commit/b128943f0c94970600fdc1c98bfec22de619866f)
23 |
--------------------------------------------------------------------------------
/install.py:
--------------------------------------------------------------------------------
1 | import sys
2 | import os
3 | from modules import scripts
4 |
5 | git = os.environ.get('GIT', "git")
6 | usefulDirs = sys.argv[0].split(os.sep)[-3:]
7 |
8 | installDir = os.path.join(scripts.basedir(), usefulDirs[0], usefulDirs[1])
9 |
10 | # Attempt to use launch module from webui
11 | command = f'"{git}" -C "' + installDir +\
12 | '" submodule update --init --recursive --remote'
13 | if not os.path.isfile(os.path.join(installDir, "app", "index.html")):
14 | try:
15 | from launch import run
16 | stdout = run(command)
17 | if stdout is not None:
18 | print(stdout)
19 | except ImportError:
20 | print("[openoutpaint-extension] We failed to import the 'launch' module. Using 'os'")
21 | try:
22 | os.system(command)
23 | except:
24 | # TODO: find exception type
25 | print(f"[openOutpaint-extension-submodule] failed to download update, check network")
26 |
--------------------------------------------------------------------------------
/javascript/openoutpaint-ext.js:
--------------------------------------------------------------------------------
1 | // Txt2Img Send to Resource
2 | const openoutpaint = {
3 | frame: null,
4 | key: null,
5 | };
6 |
7 | /**
8 | * Converts a Data URL string to a file object
9 | *
10 | * Based on https://stackoverflow.com/questions/28041840/convert-dataurl-to-file-using-javascript
11 | *
12 | * @param {string} dataurl Data URL to load into a file
13 | * @returns
14 | */
15 | function openoutpaint_dataURLtoFile(dataurl) {
16 | var arr = dataurl.split(","),
17 | mime = arr[0].match(/:(.*?);/)[1],
18 | bstr = atob(arr[1]),
19 | n = bstr.length,
20 | u8arr = new Uint8Array(n);
21 | while (n--) {
22 | u8arr[n] = bstr.charCodeAt(n);
23 | }
24 | return new File([u8arr], "openOutpaint-file", {type: mime});
25 | }
26 |
27 | async function openoutpaint_get_image_from_gallery() {
28 | var buttons = gradioApp().querySelectorAll(
29 | '[style="display: block;"].tabitem div[id$=_gallery] .thumbnail-item.thumbnail-small.selected'
30 | );
31 | var buttonsArray = Array.from(buttons);
32 | var hiddenButtons = gradioApp().querySelectorAll(
33 | '[style="display: none;"].tabitem div[id$=_gallery] .thumbnail-item.thumbnail-small.selected'
34 | );
35 | var hiddenButtonsArray = Array.from(hiddenButtons);
36 | var selectedButton = buttonsArray.filter(
37 | (value) => !hiddenButtonsArray.includes(value)
38 | );
39 | var button = selectedButton[0];
40 |
41 | if (!button) button = buttons[0];
42 |
43 | if (!button)
44 | throw new Error("[openoutpaint] No image available in the gallery");
45 |
46 | const canvas = document.createElement("canvas");
47 | const image = document.createElement("img");
48 | image.src = button.querySelector("img").src;
49 |
50 | await image.decode();
51 |
52 | canvas.width = image.width;
53 | canvas.height = image.height;
54 |
55 | canvas.getContext("2d").drawImage(image, 0, 0);
56 |
57 | return canvas.toDataURL();
58 | }
59 |
60 | function openoutpaint_send_image(dataURL, name = "Embed Resource") {
61 | openoutpaint.frame.contentWindow.postMessage({
62 | key: openoutpaint.key,
63 | type: "openoutpaint/add-resource",
64 | image: {
65 | dataURL,
66 | resourceName: name,
67 | },
68 | });
69 | }
70 |
71 | function openoutpaint_gototab(tabname = "openOutpaint", tabsId = "tabs") {
72 | Array.from(
73 | gradioApp().querySelectorAll(`#${tabsId} > div:first-child button`)
74 | ).forEach((button) => {
75 | if (button.textContent.trim() === tabname) {
76 | button.click();
77 | }
78 | });
79 | }
80 |
81 | function openoutpaint_send_gallery(name = "Embed Resource") {
82 | openoutpaint_get_image_from_gallery()
83 | .then((dataURL) => {
84 | // Send to openOutpaint
85 | openoutpaint_send_image(dataURL, name);
86 |
87 | // Send prompt to openOutpaint
88 | const tab = get_uiCurrentTabContent().id;
89 |
90 | if (["tab_txt2img", "tab_img2img"].includes(tab)) {
91 | const prompt =
92 | tab === "tab_txt2img"
93 | ? gradioApp().querySelector("#txt2img_prompt textarea").value
94 | : gradioApp().querySelector("#img2img_prompt textarea").value;
95 | const negPrompt =
96 | tab === "tab_txt2img"
97 | ? gradioApp().querySelector("#txt2img_neg_prompt textarea").value
98 | : gradioApp().querySelector("#img2img_neg_prompt textarea").value;
99 | openoutpaint.frame.contentWindow.postMessage({
100 | key: openoutpaint.key,
101 | type: "openoutpaint/set-prompt",
102 | prompt,
103 | negPrompt,
104 | });
105 | }
106 |
107 | // Change Tab
108 | openoutpaint_gototab();
109 | })
110 | .catch((error) => {
111 | console.warn("[openoutpaint] No image selected to send to openOutpaint");
112 | });
113 | }
114 |
115 | const openoutpaintjs = async () => {
116 | const frame = gradioApp().getElementById("openoutpaint-iframe");
117 | const key = gradioApp().getElementById("openoutpaint-key").value;
118 |
119 | openoutpaint.frame = frame;
120 | openoutpaint.key = key;
121 |
122 | // Listens for messages from the frame
123 | console.info("[openoutpaint] Add message listener");
124 | window.addEventListener("message", ({data, origin, source}) => {
125 | if (source === frame.contentWindow) {
126 | switch (data.type) {
127 | case "openoutpaint/ack":
128 | if (data.message.type === "openoutpaint/init") {
129 | console.info("[openoutpaint] Received Init Ack");
130 | clearTimeout(initLoop);
131 | initLoop = null;
132 | }
133 | break;
134 | case "openoutpaint/sendto":
135 | console.info(
136 | `[openoutpaint] Exported image to '${data.message.destination}'`
137 | );
138 | const container = new DataTransfer();
139 | const file = openoutpaint_dataURLtoFile(data.message.image);
140 | container.items.add(file);
141 |
142 | const setImageInput = (selector) => {
143 | const inputel = gradioApp().querySelector(selector);
144 | inputel.files = container.files;
145 | inputel.dispatchEvent(new Event("change"));
146 | };
147 |
148 | switch (data.message.destination) {
149 | case "img2img":
150 | openoutpaint_gototab("img2img");
151 | openoutpaint_gototab("img2img", "mode_img2img");
152 | setImageInput("#img2img_img2img_tab input[type=file]");
153 | break;
154 | case "img2img_sketch":
155 | openoutpaint_gototab("img2img");
156 | openoutpaint_gototab("Sketch", "mode_img2img");
157 | setImageInput("#img2img_img2img_sketch_tab input[type=file]");
158 | break;
159 | case "img2img_inpaint":
160 | openoutpaint_gototab("img2img");
161 | openoutpaint_gototab("Inpaint", "mode_img2img");
162 | setImageInput("#img2img_inpaint_tab input[type=file]");
163 | break;
164 | case "img2img_sketch_inpaint":
165 | openoutpaint_gototab("img2img");
166 | openoutpaint_gototab("Inpaint sketch", "mode_img2img");
167 | setImageInput("#img2img_inpaint_sketch_tab input[type=file]");
168 | break;
169 | case "extras":
170 | openoutpaint_gototab("Extras");
171 | setImageInput("#extras_single_tab input[type=file]");
172 | break;
173 | case "pnginfo":
174 | openoutpaint_gototab("PNG Info");
175 | setImageInput("#tab_pnginfo input[type=file]");
176 | break;
177 | default:
178 | console.warn(
179 | `[openoutpaint] Unknown destination ${data.message.destination}`
180 | );
181 | }
182 | break;
183 | }
184 | }
185 | });
186 |
187 | // Initializes communication channel
188 | let initLoop = null;
189 | const sendInit = () => {
190 | console.info("[openoutpaint] Sending init message");
191 | const pathname = window.location.pathname;
192 | const host = `${window.location.origin}${
193 | pathname.endsWith("/")
194 | ? pathname.substring(0, pathname.length - 1)
195 | : pathname
196 | }`;
197 | frame.contentWindow.postMessage({
198 | type: "openoutpaint/init",
199 | key,
200 | host,
201 | destinations: [
202 | {
203 | name: "Image to Image",
204 | id: "img2img",
205 | },
206 | {
207 | name: "Sketch",
208 | id: "img2img_sketch",
209 | },
210 | {
211 | name: "Inpaint",
212 | id: "img2img_inpaint",
213 | },
214 | {
215 | name: "Sketch & Inpaint",
216 | id: "img2img_sketch_inpaint",
217 | },
218 | {
219 | name: "Extras",
220 | id: "extras",
221 | },
222 | {
223 | name: "PNG Info",
224 | id: "pnginfo",
225 | },
226 | ],
227 | });
228 | initLoop = setTimeout(sendInit, 1000);
229 | };
230 |
231 | frame.addEventListener("load", () => {
232 | sendInit();
233 | });
234 |
235 | // Setup openOutpaint tab scaling
236 | const tabEl = gradioApp().getElementById("tab_openOutpaint");
237 | frame.style.left = "0px";
238 |
239 | const refreshBtn = document.createElement("button");
240 | refreshBtn.id = "openoutpaint-refresh";
241 | refreshBtn.textContent = "🔄";
242 | refreshBtn.title = "Refresh openOutpaint";
243 | refreshBtn.style.width = "fit-content";
244 | refreshBtn.classList.add("gr-button", "gr-button-lg", "gr-button-secondary");
245 | refreshBtn.addEventListener("click", async () => {
246 | if (confirm("Are you sure you want to refresh openOutpaint?")) {
247 | frame.contentWindow.location.reload();
248 | }
249 | });
250 | tabEl.appendChild(refreshBtn);
251 |
252 | const recalculate = () => {
253 | // If we are on the openoutpaint tab, recalculate
254 | if (tabEl.style.display !== "none") {
255 | frame.style.height = window.innerHeight + "px";
256 | const current = document.body.scrollHeight;
257 | const bb = frame.getBoundingClientRect();
258 | const iframeh = bb.height;
259 | const innerh = window.innerHeight;
260 | frame.style.height = `${Math.floor(iframeh + (innerh - current)) - 1}px`;
261 | frame.style.width = `${Math.floor(window.innerWidth) - 1}px`;
262 | frame.style.left = `${Math.floor(
263 | parseInt(frame.style.left, 10) - bb.x
264 | )}px`;
265 | }
266 | };
267 |
268 | window.addEventListener("resize", () => {
269 | recalculate();
270 | });
271 |
272 | new MutationObserver((e) => {
273 | recalculate();
274 | }).observe(tabEl, {
275 | attributes: true,
276 | });
277 |
278 | // Add button to other tabs
279 | const createButton = (tabname = "default", tool = true) => {
280 | const button = document.createElement("button");
281 | button.id = tabname + "_openOutpaint_button";
282 | button.classList.add("lg", "secondary", "gradio-button", "svelte-cmf5ev");
283 | button.title = "Send image to openOutpaint.";
284 | if (tool) {
285 | button.classList.add("tool");
286 | button.textContent = "🐠";
287 | } else {
288 | button.textContent = "Send to openOutpaint";
289 | }
290 | return button;
291 | };
292 |
293 | const extrasBtn = createButton("extras");
294 | extrasBtn.addEventListener("click", () =>
295 | openoutpaint_send_gallery("WebUI Extras Resource")
296 | );
297 | gradioApp()
298 | .querySelector("#tab_extras button#extras_send_to_extras")
299 | .after(extrasBtn);
300 |
301 | const pnginfoBtn = createButton("pnginfo", false);
302 | pnginfoBtn.addEventListener("click", () => {
303 | const image = gradioApp().querySelector("#pnginfo_image img");
304 | if (image && image.src) {
305 | openoutpaint_send_image(image.src, "WebUI PNGInfo Resource");
306 | openoutpaint_gototab();
307 | }
308 | });
309 | gradioApp().querySelector("#tab_pnginfo button#extras_tab").after(pnginfoBtn);
310 |
311 | // Initial calculations
312 | sendInit();
313 | recalculate();
314 |
315 | new MutationObserver((mutations) => {
316 | if (
317 | mutations.some(
318 | (mutation) =>
319 | mutation.attributeName === "style" &&
320 | mutation.target.style.display !== "none"
321 | )
322 | )
323 | frame.contentWindow.focus();
324 | }).observe(tabEl, {
325 | attributes: true,
326 | });
327 |
328 | if (tabEl.style.display !== "none") frame.contentWindow.focus();
329 | };
330 | document.addEventListener("DOMContentLoaded", () => {
331 | const onload = () => {
332 | if (gradioApp().getElementById("openoutpaint-iframe")) {
333 | openoutpaintjs();
334 | } else {
335 | setTimeout(onload, 10);
336 | }
337 | };
338 | onload();
339 | });
340 |
--------------------------------------------------------------------------------
/javascript/openoutpaint-imagehistory.js:
--------------------------------------------------------------------------------
1 | async function openoutpaint_get_image_from_history() {
2 | return new Promise(function (resolve, reject) {
3 | var buttons = gradioApp().querySelectorAll(
4 | '#tab_images_history [style="display: block;"].tabitem div[id$=_gallery] .gallery-item'
5 | );
6 | var button = gradioApp().querySelector(
7 | '#tab_images_history [style="display: block;"].tabitem div[id$=_gallery] .gallery-item.\\!ring-2'
8 | );
9 |
10 | if (!button) button = buttons[0];
11 |
12 | if (!button)
13 | reject(new Error("[openoutpaint] No image available in the gallery"));
14 |
15 | const canvas = document.createElement("canvas");
16 | const image = document.createElement("img");
17 | image.onload = () => {
18 | canvas.width = image.width;
19 | canvas.height = image.height;
20 |
21 | canvas.getContext("2d").drawImage(image, 0, 0);
22 |
23 | resolve(canvas.toDataURL());
24 | };
25 | image.src = button.querySelector("img").src;
26 | });
27 | }
28 |
29 | function openoutpaint_send_history_gallery(
30 | name = "Image Browser Resource",
31 | tab
32 | ) {
33 | openoutpaint_get_image_from_history()
34 | .then((dataURL) => {
35 | // Send to openOutpaint
36 | openoutpaint.frame.contentWindow.postMessage({
37 | key: openoutpaint.key,
38 | type: "openoutpaint/add-resource",
39 | image: {
40 | dataURL,
41 | resourceName: name,
42 | },
43 | });
44 |
45 | // Send prompt to openOutpaint
46 | const tab = get_uiCurrentTabContent().id;
47 | const prompt =
48 | tab === "tab_txt2img"
49 | ? gradioApp().querySelector("#txt2img_prompt textarea").value
50 | : gradioApp().querySelector("#img2img_prompt textarea").value;
51 | const negPrompt =
52 | tab === "tab_txt2img"
53 | ? gradioApp().querySelector("#txt2img_neg_prompt textarea").value
54 | : gradioApp().querySelector("#img2img_neg_prompt textarea").value;
55 | openoutpaint.frame.contentWindow.postMessage({
56 | key: openoutpaint.key,
57 | type: "openoutpaint/set-prompt",
58 | prompt,
59 | negPrompt,
60 | });
61 |
62 | // Change Tab
63 | Array.from(
64 | gradioApp().querySelectorAll("#tabs > div:first-child button")
65 | ).forEach((button) => {
66 | if (button.textContent.trim() === "openOutpaint") {
67 | button.click();
68 | }
69 | });
70 | })
71 | .catch((error) => {
72 | console.warn("[openoutpaint] No image selected to send to openOutpaint");
73 | });
74 | }
75 |
76 | document.addEventListener("DOMContentLoaded", () => {
77 | let tries = 3;
78 | const onload = () => {
79 | const element = gradioApp().getElementById("tab_images_history");
80 | const tabsEl = gradioApp().getElementById("images_history_tab");
81 | if (element) {
82 | console.debug(`[oo-ext] Detected image history extension`);
83 |
84 | // Gets tab buttons
85 | const tabs = Array.from(tabsEl.firstChild.querySelectorAll("button")).map(
86 | (button) => button.textContent.trim()
87 | );
88 |
89 | tabs.forEach((tab) => {
90 | const buttonPanel = gradioApp().getElementById(
91 | `${tab}_images_history_button_panel`
92 | );
93 |
94 | if (!buttonPanel) return;
95 |
96 | const button = document.createElement("button");
97 | button.textContent = "Send to openOutpaint";
98 | button.classList.add(
99 | "gr-button",
100 | "gr-button-lg",
101 | "gr-button-secondary"
102 | );
103 | button.addEventListener("click", () => {
104 | openoutpaint_send_history_gallery(`Image Browser (${tab}) Resource`);
105 | });
106 |
107 | buttonPanel.appendChild(button);
108 | });
109 | } else if (tries-- > 0) {
110 | // Tries n times every 1 second before giving up
111 | setTimeout(onload, 1000);
112 | }
113 | };
114 | onload();
115 | });
116 |
--------------------------------------------------------------------------------
/preload.py:
--------------------------------------------------------------------------------
1 | import argparse
2 |
3 | def preload(parser: argparse.ArgumentParser):
4 | parser.add_argument("--lock-oo-submodule", action='store_true',
5 | help="(openOutpaint-webUI-extension) Prevent checking for main openOutpaint submodule updates.")
6 |
--------------------------------------------------------------------------------
/scripts/api.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, Response, Form
2 | from fastapi.responses import JSONResponse
3 | from modules import sd_models, shared, scripts
4 | import asyncio
5 | import gradio as gr
6 |
7 |
8 | def test_api(_: gr.Blocks, app: FastAPI):
9 | """
10 | it kept yelling at me without the stupid gradio import there, i'm sure i did something wrong
11 | --------------------------
12 | Error executing callback app_started_callback for E:\storage\stable-diffusion-webui\extensions\apitest\scripts\api.py
13 | Traceback (most recent call last):
14 | File "E:\storage\stable-diffusion-webui\modules\script_callbacks.py", line 88, in app_started_callback
15 | c.callback(demo, app)
16 | TypeError: test_api() takes 1 positional argument but 2 were given
17 | """
18 | @app.post("/openOutpaint/unet-count")
19 | async def return_model_unet_channel_count(
20 | model_name: str = Form(description="the model to be inspected")
21 | ):
22 | err_msg = ""
23 | try:
24 | model = sd_models.checkpoints_list[model_name]
25 | except:
26 | err_msg = "submitted model failed loading, falling back to loaded model"
27 | model = sd_models.checkpoints_list[get_current_model()]
28 | theta_0 = sd_models.read_state_dict(model.filename, map_location='cpu')
29 | channelCount = theta_0["model.diffusion_model.input_blocks.0.0.weight"].shape[1]
30 | return {
31 | "unet_channels": channelCount,
32 | "estimated_type": switchAssumption(channelCount),
33 | "tested_model": model,
34 | "additional_data": err_msg
35 | }
36 |
37 | def switchAssumption(channelCount):
38 | return {
39 | 4: "traditional",
40 | 5: "sdv2 depth2img",
41 | 7: "sdv2 upscale 4x",
42 | 8: "instruct-pix2pix",
43 | 9: "inpainting"
44 | }.get(channelCount, "¯\_(ツ)_/¯")
45 |
46 | def get_current_model():
47 | options = {}
48 | for key in shared.opts.data.keys():
49 | metadata = shared.opts.data_labels.get(key)
50 | if(metadata is not None):
51 | options.update({key: shared.opts.data.get(key, shared.opts.data_labels.get(key).default)})
52 | else:
53 | options.update({key: shared.opts.data.get(key, None)})
54 |
55 | return options["sd_model_checkpoint"] # super inefficient but i'm a moron
56 |
57 |
58 | try:
59 | import modules.script_callbacks as script_callbacks
60 | script_callbacks.on_app_started(test_api)
61 | except:
62 | print("[openOutpaint-webui-extension] UNET API failed to initialize")
63 |
64 |
65 |
--------------------------------------------------------------------------------
/scripts/interface.py:
--------------------------------------------------------------------------------
1 | import gradio as gr
2 | from modules import scripts
3 | from modules.ui_components import ToolButton
4 |
5 |
6 | class Script(scripts.Script):
7 | def title(self):
8 | return "OpenOutpaint"
9 |
10 | def show(self, is_img2img):
11 | return scripts.AlwaysVisible
12 |
13 | def after_component(self, component, **kwargs):
14 | # Add button to both txt2img and img2img tabs
15 | if kwargs.get("elem_id") == "txt2img_send_to_extras" or kwargs.get("elem_id") == "img2img_send_to_extras":
16 | tabname = kwargs.get("elem_id").replace("_send_to_extras", "")
17 | new_send_button = ToolButton('🐠', elem_id=f'{tabname}_openOutpaint_button', tooltip="Send image and prompt parameters to openOutpaint.")
18 | new_send_button.click(None, [], None,
19 | _js="() => openoutpaint_send_gallery('WebUI " + tabname + " Resource')")
20 |
21 | def ui(self, is_img2img):
22 | return []
23 |
--------------------------------------------------------------------------------
/scripts/main.py:
--------------------------------------------------------------------------------
1 | from modules import script_callbacks, scripts, shared
2 | import gradio as gr
3 | from fastapi import FastAPI
4 | import os
5 | from launch import run
6 | import pathlib
7 | import inspect
8 |
9 | import string
10 | import random as rd
11 |
12 |
13 | extension_dir = pathlib.Path(inspect.getfile(lambda: None)).parent.parent
14 | key_characters = (string.ascii_letters + string.digits)
15 |
16 |
17 | def random_string(length=20):
18 | return ''.join([rd.choice(key_characters) for _ in range(length)])
19 |
20 |
21 | key = random_string()
22 |
23 |
24 | def get_files(path):
25 | # Gets all files
26 | directories = set()
27 | for root, _, files in os.walk(path.resolve()):
28 | for file in files:
29 | directories.add(root + '/' + file)
30 |
31 | return directories
32 |
33 |
34 | def started(demo, app: FastAPI):
35 | try:
36 | # Force allow paths for fixing symlinked extension directory references
37 | force_allow = get_files(extension_dir / "app")
38 |
39 | # Add to allowed files list
40 | app.blocks.temp_file_sets.append(force_allow)
41 |
42 | # Force allow paths for fixing symlinked extension directory references (base javascript files now)
43 | force_allow = get_files(extension_dir / "javascript")
44 |
45 | # Add to allowed files list
46 | app.blocks.temp_file_sets.append(force_allow)
47 | except Exception:
48 | print(f"[openOutpaint] Could not force allowed files. Skipping...")
49 | pass
50 |
51 |
52 | def update_app():
53 | try:
54 | git = os.environ.get('GIT', "git")
55 | # print(scripts.basedir)
56 | run(f'"{git}" -C "' + os.path.join(scripts.basedir(), usefulDirs[0], usefulDirs[1]) +
57 | '" submodule update --init --recursive --remote', live=True)
58 | except:
59 | # TODO: find exception type
60 | print(f"[openOutpaint-extension-submodule] failed to download update, check network")
61 |
62 |
63 | def add_tab():
64 | try:
65 | if shared.cmd_opts.lock_oo_submodule:
66 | print(f"[openOutpaint] Submodule locked. Will skip submodule update.")
67 | else:
68 | update_app()
69 | except Exception:
70 | update_app()
71 |
72 | with gr.Blocks(analytics_enabled=False) as ui:
73 | #refresh = gr.Button(value="refresh", variant="primary")
74 | canvas = gr.HTML(
75 | f"")
76 | keyinput = gr.HTML(
77 | f"")
78 |
79 | return [(ui, "openOutpaint", "openOutpaint")]
80 |
81 |
82 | usefulDirs = scripts.basedir().split(os.sep)[-2:]
83 |
84 | with open(f"{scripts.basedir()}/app/key.json", "w") as keyfile:
85 | keyfile.write('{\n')
86 | keyfile.write(f" \"key\": \"{key}\"\n")
87 | keyfile.write('}\n')
88 | keyfile.close()
89 |
90 | script_callbacks.on_ui_tabs(add_tab)
91 | script_callbacks.on_app_started(started)
92 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | #tab_openOutpaint {
2 | position: relative;
3 |
4 | padding: 0;
5 | border: 0;
6 | }
7 |
8 | #openoutpaint-iframe {
9 | position: absolute;
10 |
11 | top: -2px;
12 |
13 | box-sizing: border-box;
14 |
15 | z-index: 1;
16 | }
17 |
18 | #openoutpaint-refresh {
19 | position: absolute;
20 |
21 | top: 10px;
22 | left: 0;
23 | right: 0;
24 |
25 | margin: auto;
26 |
27 | z-index: 9999; /* little guy gets bashful and hides behind the canvas in firefox */
28 | }
29 |
--------------------------------------------------------------------------------