add side panel of txt2img and img2img to load local generated images

pull/15/head
jtydhr88 2023-04-17 21:25:19 -04:00
parent 549b49397a
commit eedae87ea9
7 changed files with 474 additions and 39 deletions

View File

View File

@ -1,4 +1,4 @@
console.log('[3D Model Loader] loading...'); console.log('[Canvas Editor] loading...');
async function _import() { async function _import() {
if (!globalThis.canvasEditor || !globalThis.canvasEditor.import) { if (!globalThis.canvasEditor || !globalThis.canvasEditor.import) {
@ -10,6 +10,8 @@ async function _import() {
await _import(); await _import();
let _r = 0;
(async function () { (async function () {
const container = gradioApp().querySelector('#canvas-editor-container'); const container = gradioApp().querySelector('#canvas-editor-container');
@ -19,6 +21,21 @@ await _import();
const apiKey = gradioApp().querySelector('#canvas-editor-polotno-api-key'); const apiKey = gradioApp().querySelector('#canvas-editor-polotno-api-key');
const apiKeyValue = apiKey.value; const apiKeyValue = apiKey.value;
setLoadMoreFunc(py2js);
const txt2ImgFilePaths = await py2js('getImgFilePaths', '{"type":"txt2img", "num":1, "size":15}');
const img2ImgFilePaths = await py2js('getImgFilePaths', '{"type":"img2img", "num":1, "size":15}');
const txt2ImgFilePathsJsonData = JSON.parse(txt2ImgFilePaths);
const img2ImgFilePathsJsonData = JSON.parse(img2ImgFilePaths);
function to_gradio(v) {
return [v, _r++];
}
setTxt2imgInfoJSON(txt2ImgFilePathsJsonData);
setImg2imgInfoJSON(img2ImgFilePathsJsonData);
createPolotnoApp({ createPolotnoApp({
key: apiKeyValue, key: apiKeyValue,
container: container container: container
@ -28,16 +45,16 @@ await _import();
function dataURLtoFile(dataurl, filename) { function dataURLtoFile(dataurl, filename) {
var arr = dataurl.split(','), var arr = dataurl.split(','),
mime = arr[0].match(/:(.*?);/)[1], mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]), bstr = atob(arr[1]),
n = bstr.length, n = bstr.length,
u8arr = new Uint8Array(n); u8arr = new Uint8Array(n);
while(n--){ while (n--) {
u8arr[n] = bstr.charCodeAt(n); u8arr[n] = bstr.charCodeAt(n);
} }
return new File([u8arr], filename, {type:mime}); return new File([u8arr], filename, {type: mime});
} }
window.sendImageCanvasEditor = async function (type) { window.sendImageCanvasEditor = async function (type) {
@ -63,17 +80,17 @@ await _import();
updateGradioImage(imageElems[0], dt); updateGradioImage(imageElems[0], dt);
} }
function getCanvasEditorTabIndex(){ function getCanvasEditorTabIndex() {
const tabCanvasEditorDiv = document.getElementById('tab_canvas_editor'); const tabCanvasEditorDiv = document.getElementById('tab_canvas_editor');
const parent = tabCanvasEditorDiv.parentNode; const parent = tabCanvasEditorDiv.parentNode;
const siblings = parent.childNodes; const siblings = parent.childNodes;
let index = -1; let index = -1;
for (let i = 0; i < siblings.length; i++) { for (let i = 0; i < siblings.length; i++) {
if (siblings[i] === tabCanvasEditorDiv) { if (siblings[i] === tabCanvasEditorDiv) {
index = i; index = i;
break; break;
} }
} }
return index / 3; return index / 3;
@ -88,7 +105,7 @@ await _import();
} }
window.sendImageToCanvasEditor = function () { window.sendImageToCanvasEditor = function () {
const gallerySelector = isTxt2Img()? '#txt2img_gallery': '#img2img_gallery'; const gallerySelector = isTxt2Img() ? '#txt2img_gallery' : '#img2img_gallery';
const txt2imgGallery = gradioApp().querySelector(gallerySelector); const txt2imgGallery = gradioApp().querySelector(gallerySelector);
@ -115,8 +132,7 @@ await _import();
removable: true, removable: true,
resizable: true, resizable: true,
}); });
} } else {
else {
alert("No image selected"); alert("No image selected");
} }
} }
@ -198,7 +214,7 @@ await _import();
}; };
function updateGradioImage (element, dt) { function updateGradioImage(element, dt) {
let clearButton = element.querySelector("button[aria-label='Clear']"); let clearButton = element.querySelector("button[aria-label='Clear']");
if (clearButton) { if (clearButton) {
@ -215,4 +231,51 @@ await _import();
}) })
) )
} }
function py2js(pyname, ...args) {
// call python's function
// (1) Set args to gradio's field
// (2) Click gradio's button
// (3) JS callback will be kicked with return value from gradio
// (1)
return (args.length === 0 ? Promise.resolve() : js2py(pyname + '_args', JSON.stringify(args)))
.then(() => {
return new Promise(resolve => {
const callback_name = `canvas-editor-${pyname}`;
// (3)
globalThis[callback_name] = value => {
delete globalThis[callback_name];
resolve(value);
}
// (2)
gradioApp().querySelector(`#${callback_name}_get`).click();
});
});
}
function js2py(gradio_field, value) {
return new Promise(resolve => {
const callback_name = `canvas-editor-${gradio_field}`;
// (2)
globalThis[callback_name] = () => {
delete globalThis[callback_name];
// (3)
const callback_after = callback_name + '_after';
globalThis[callback_after] = () => {
delete globalThis[callback_after];
resolve();
};
return to_gradio(value);
};
// (1)
gradioApp().querySelector(`#${callback_name}_set`).click();
});
}
})(); })();

134
js/photos-panel.js Normal file
View File

@ -0,0 +1,134 @@
"use strict";
var __importDefault = this && this.__importDefault || function (e) {
return e && e.__esModule ? e : {default: e}
};
Object.defineProperty(exports, "__esModule", {value: !0}), exports.ImagesGrid = void 0;
const react_1 = __importDefault(require("react")), styled_1 = __importDefault(require("../utils/styled")),
core_1 = require("@blueprintjs/core"), l10n_1 = require("../utils/l10n"), page_1 = require("../canvas/page"),
ImagesListContainer = (0, styled_1.default)("div", react_1.default.forwardRef)`
height: 100%;
overflow: auto;
`, ImagesRow = (0, styled_1.default)("div")`
width: 33%;
float: left;
`, ImgWrapDiv = (0, styled_1.default)("div")`
padding: 5px;
width: 100%;
&:hover .credit {
opacity: 1;
}
@media screen and (max-width: 500px) {
.credit {
opacity: 1;
}
}
`, ImgContainerDiv = (0, styled_1.default)("div")`
border-radius: 5px;
position: relative;
overflow: hidden;
box-shadow: ${e => e["data-shadowenabled"] ? "0 0 5px rgba(16, 22, 26, 0.3)" : ""};
`, Img = (0, styled_1.default)("img")`
width: 100%;
cursor: pointer;
display: block;
`, CreditWrap = (0, styled_1.default)("div")`
position: absolute;
bottom: 0px;
left: 0px;
font-size: 10px;
padding: 3px;
padding-top: 10px;
text-align: center;
background: linear-gradient(
to bottom,
rgba(0, 0, 0, 0),
rgba(0, 0, 0, 0.4),
rgba(0, 0, 0, 0.6)
);
width: 100%;
opacity: 0;
color: white;
`, NoResults = (0, styled_1.default)("p")`
text-align: center;
padding: 30px;
`, Image = ({
url: e,
credit: t,
onSelect: r,
crossOrigin: a,
shadowEnabled: l,
itemHeight: o,
className: i,
onLoad: n
}) => {
const d = null == l || l;
return react_1.default.createElement(ImgWrapDiv, {
onClick: () => {
r()
}, className: "polotno-close-panel"
}, react_1.default.createElement(ImgContainerDiv, {"data-shadowenabled": d}, react_1.default.createElement(Img, {
className: i,
style: {height: null != o ? o : "auto"},
src: e,
draggable: !0,
crossOrigin: a,
onDragStart: () => {
(0, page_1.registerNextDomDrop)((({x: e, y: t}, a) => {
r({x: e, y: t}, a)
}))
},
onDragEnd: () => {
(0, page_1.registerNextDomDrop)(null)
},
onLoad: n
}), t && react_1.default.createElement(CreditWrap, {className: "credit"}, t)))
}, ImagesGrid = ({
images: e,
onSelect: t,
isLoading: r,
getPreview: a,
loadMore: l,
getCredit: o,
getImageClassName: i,
rowsNumber: n,
crossOrigin: d = "anonymous",
shadowEnabled: s,
itemHeight: c,
error: u
}) => {
const g = n || 2, m = react_1.default.useRef(null), p = [];
for (var f = 0; f < g; f++) p.push((e || []).filter(((e, t) => t % g === f)));
const _ = react_1.default.useRef(null), h = () => {
var t, a, o;
const i = (null === (t = m.current) || void 0 === t ? void 0 : t.scrollHeight) > (null === (a = m.current) || void 0 === a ? void 0 : a.offsetHeight) + 5,
n = e && e.length,
d = Array.from(null === (o = m.current) || void 0 === o ? void 0 : o.querySelectorAll("img")).every((e => e.complete));
!i && l && !r && n && d && (_.current || (_.current = window.setTimeout((() => {
_.current = null, l && l()
}), 100)))
}, v = () => {
h()
};
return react_1.default.useEffect((() => (h(), () => {
window.clearTimeout(_.current), _.current = null
})), [e && e.length, r]), react_1.default.createElement(ImagesListContainer, {
onScroll: e => {
const t = e.target.scrollHeight - e.target.scrollTop - e.target.offsetHeight;
l && !r && t < 200 && l()
}, ref: m
}, p.map(((e, l) => react_1.default.createElement(ImagesRow, {
key: l,
style: {width: 100 / g + "%"}
}, e.map((e => react_1.default.createElement(Image, {
url: a(e),
onSelect: (r, a) => t(e, r, a),
key: a(e),
credit: o && o(e),
crossOrigin: d,
shadowEnabled: s,
itemHeight: c,
className: i && i(e),
onLoad: v
}))), r && react_1.default.createElement("div", {style: {padding: "30px"}}, react_1.default.createElement(core_1.Spinner, null))))), !r && (!e || !e.length) && !u && react_1.default.createElement(NoResults, null, (0, l10n_1.t)("sidePanel.noResults")), u && react_1.default.createElement(NoResults, null, (0, l10n_1.t)("sidePanel.error")))
};
exports.ImagesGrid = ImagesGrid;

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,8 @@
"webpack-cli": "^5.0.1" "webpack-cli": "^5.0.1"
}, },
"dependencies": { "dependencies": {
"@meronex/icons": "^4.0.0",
"mobx-react-lite": "^3.4.3",
"polotno": "^1.8.6", "polotno": "^1.8.6",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0"

View File

@ -1,3 +1,5 @@
import json
import gradio as gr import gradio as gr
import modules.scripts as scripts import modules.scripts as scripts
@ -8,6 +10,59 @@ from typing import Callable
from modules.shared import opts from modules.shared import opts
from modules import shared from modules import shared
def get_file_paths(folder):
file_paths = []
for root, directories, files in os.walk(folder):
for filename in files:
file_path = os.path.join(root, filename)
file_url = 'file=' + file_path.replace('\\', '/')
file_paths.append({'url': file_url})
file_paths.reverse()
return file_paths
def get_img_file_paths(args):
args = args[2: len(args) - 2]
args = args.replace("\\", "")
page_info = json.loads(args)
page_type = page_info['type']
page_num = int(page_info['num'])
page_size = int(page_info['size'])
path = os.path.dirname(os.path.realpath(__file__))
path = os.path.dirname(path)
path = os.path.dirname(path)
path = os.path.dirname(path)
if page_type == 'txt2img':
path = os.path.join(path, "outputs", "txt2img-images")
elif page_type == 'img2img':
path = os.path.join(path, "outputs", "img2img-images")
img_file_paths = get_file_paths(path)
total_len = len(img_file_paths)
start = (page_num - 1) * page_size
end = start + page_size
if start > total_len:
return json.dumps("[false]")
elif end > total_len:
end = total_len
img_file_paths = img_file_paths[start: end]
return json.dumps(img_file_paths)
# 要遍历的文件夹路径
class Script(scripts.Script): class Script(scripts.Script):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
@ -109,6 +164,10 @@ def on_ui_tabs():
gr.HTML(value='\n'.join(js_), elem_id=import_id, visible=False) gr.HTML(value='\n'.join(js_), elem_id=import_id, visible=False)
with gr.Group(visible=False):
sink = gr.HTML(value='', visible=False) # to suppress error in javascript
jscall('getImgFilePaths', get_img_file_paths, id, js, sink)
with gr.Row(): with gr.Row():
gr.HTML('<div id="canvas-editor-container"></div>') gr.HTML('<div id="canvas-editor-container"></div>')
with gr.Row(): with gr.Row():

View File

@ -1,31 +1,205 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import { PolotnoContainer, SidePanelWrap, WorkspaceWrap } from 'polotno'; import {PolotnoContainer, SidePanelWrap, WorkspaceWrap} from 'polotno';
import { Toolbar } from 'polotno/toolbar/toolbar'; import {Toolbar} from 'polotno/toolbar/toolbar';
import { ZoomButtons } from 'polotno/toolbar/zoom-buttons'; import {ZoomButtons} from 'polotno/toolbar/zoom-buttons';
import { SidePanel } from 'polotno/side-panel'; import {SidePanel} from 'polotno/side-panel';
import { Workspace } from 'polotno/canvas/workspace'; import {Workspace} from 'polotno/canvas/workspace';
import {getImageSize} from 'polotno/utils/image';
import {createStore} from 'polotno/model/store';
import {ImagesGrid} from 'polotno/side-panel/images-grid';
import {observer} from 'mobx-react-lite';
import {SectionTab} from 'polotno/side-panel';
import {
TemplatesSection,
TextSection,
PhotosSection,
ElementsSection,
UploadSection,
BackgroundSection,
LayersSection,
SizeSection,
} from 'polotno/side-panel';
import {DEFAULT_SECTIONS} from "polotno/side-panel/side-panel";
import { createStore } from 'polotno/model/store'; let _txt2imgInfoJSON;
let _img2imgInfoJSON;
export const App = ({ store }) => { export const setTxt2imgInfoJSON = (txt2imgInfoJSON) => {
return ( _txt2imgInfoJSON = txt2imgInfoJSON;
<PolotnoContainer style={{ width: '95vw', height: '100vh' }}> }
<SidePanelWrap>
<SidePanel store={store} /> export const setImg2imgInfoJSON = (img2imgInfoJSON) => {
</SidePanelWrap> _img2imgInfoJSON = img2imgInfoJSON;
<WorkspaceWrap> }
<Toolbar store={store} />
<Workspace store={store} /> let txt2ImgPageNum = 2;
<ZoomButtons store={store} /> let img2ImgPageNum = 2;
</WorkspaceWrap>
</PolotnoContainer> export const Txt2ImgPhotosPanel = observer(({store}) => {
); const [images, setImages] = React.useState([]);
async function loadImages() {
setImages(_txt2imgInfoJSON);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
React.useEffect(() => {
loadImages();
},
[]
);
return (
<div style={{height: '100%', display: 'flex', flexDirection: 'column'}}>
<p>txt2img Library</p>
<ImagesGrid
images={images}
getPreview={(image) => image.url}
onSelect={async (image, pos) => {
const {width, height} = await getImageSize(image.url);
store.activePage.addElement({
type: 'image',
src: image.url,
width,
height,
x: pos ? pos.x : store.width / 2 - width / 2,
y: pos ? pos.y : store.height / 2 - height / 2,
});
}}
rowsNumber={2}
isLoading={!images.length}
loadMore={async () => {
let pageInfo = '{"type": "txt2img", "num": pageNum, "size": 15}';
pageInfo = pageInfo.replace("pageNum", txt2ImgPageNum.toString());
const txt2ImgFilePaths = await _loadMoreFunc('getImgFilePaths', pageInfo)
const txt2ImgFilePathsJsonData = JSON.parse(txt2ImgFilePaths);
_txt2imgInfoJSON = _txt2imgInfoJSON.concat(txt2ImgFilePathsJsonData);
await loadImages();
txt2ImgPageNum = txt2ImgPageNum + 1;
}
}
/>
</div>
);
});
let _loadMoreFunc;
export const setLoadMoreFunc = (loadMoreFunc) => {
_loadMoreFunc = loadMoreFunc;
}
export const Img2TxtPhotosPanel = observer(({store}) => {
const [images, setImages] = React.useState([]);
async function loadImages() {
// here we should implement your own API requests
setImages(_img2imgInfoJSON);
await new Promise((resolve) => setTimeout(resolve, 3000));
}
React.useEffect(() => {
loadImages();
}, []);
return (
<div style={{height: '100%', display: 'flex', flexDirection: 'column'}}>
<p>img2img Library</p>
<ImagesGrid
images={images}
getPreview={(image) => image.url}
onSelect={async (image, pos) => {
const {width, height} = await getImageSize(image.url);
store.activePage.addElement({
type: 'image',
src: image.url,
width,
height,
x: pos ? pos.x : store.width / 2 - width / 2,
y: pos ? pos.y : store.height / 2 - height / 2,
});
}}
rowsNumber={2}
isLoading={!images.length}
loadMore={async () => {
let pageInfo = '{"type": "img2img", "num": pageNum, "size": 15}';
pageInfo = pageInfo.replace("pageNum", img2ImgPageNum.toString());
const img2ImgFilePaths = await _loadMoreFunc('getImgFilePaths', pageInfo)
const img2ImgFilePathsJsonData = JSON.parse(img2ImgFilePaths);
_img2imgInfoJSON = _img2imgInfoJSON.concat(img2ImgFilePathsJsonData);
await loadImages();
img2ImgPageNum = img2ImgPageNum + 1;
}
}
/>
</div>
);
});
const Txt2ImgPhotos = {
name: 'sd-txt2img',
Tab: (props) => (
<SectionTab name="Txt2img" {...props}>
</SectionTab>
),
Panel: Txt2ImgPhotosPanel,
};
const Img2ImgPhotos = {
name: 'sd-img2img',
Tab: (props) => (
<SectionTab name="Img2img" {...props}>
</SectionTab>
),
// we need observer to update component automatically on any store changes
Panel: Img2TxtPhotosPanel,
};
const sections = [
TemplatesSection,
TextSection,
PhotosSection,
ElementsSection,
UploadSection,
BackgroundSection,
LayersSection,
SizeSection,
Txt2ImgPhotos,
Img2ImgPhotos,
];
export const App = ({store}) => {
return (
<PolotnoContainer style={{width: '95vw', height: '90vh'}}>
<SidePanelWrap>
<SidePanel store={store} sections={sections}/>
</SidePanelWrap>
<WorkspaceWrap>
<Toolbar store={store}/>
<Workspace store={store}/>
<ZoomButtons store={store}/>
</WorkspaceWrap>
</PolotnoContainer>
);
}; };
let _store; let _store;
export const createPolotnoApp = ({ key, container }) => { export const createPolotnoApp = ({key, container}) => {
const store = createStore({ const store = createStore({
key: key, key: key,
showCredit: true, showCredit: true,
@ -35,7 +209,7 @@ export const createPolotnoApp = ({ key, container }) => {
const root = ReactDOM.createRoot(container); const root = ReactDOM.createRoot(container);
root.render(<App store={store} />); root.render(<App store={store}/>);
_store = store; _store = store;
}; };
@ -45,4 +219,7 @@ export const getPolotnoStore = () => {
}; };
window.createPolotnoApp = createPolotnoApp; window.createPolotnoApp = createPolotnoApp;
window.getPolotnoStore = getPolotnoStore; window.getPolotnoStore = getPolotnoStore;
window.setTxt2imgInfoJSON = setTxt2imgInfoJSON;
window.setImg2imgInfoJSON = setImg2imgInfoJSON;
window.setLoadMoreFunc = setLoadMoreFunc;