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() {
if (!globalThis.canvasEditor || !globalThis.canvasEditor.import) {
@ -10,6 +10,8 @@ async function _import() {
await _import();
let _r = 0;
(async function () {
const container = gradioApp().querySelector('#canvas-editor-container');
@ -19,6 +21,21 @@ await _import();
const apiKey = gradioApp().querySelector('#canvas-editor-polotno-api-key');
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({
key: apiKeyValue,
container: container
@ -115,8 +132,7 @@ await _import();
removable: true,
resizable: true,
});
}
else {
} else {
alert("No image selected");
}
}
@ -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"
},
"dependencies": {
"@meronex/icons": "^4.0.0",
"mobx-react-lite": "^3.4.3",
"polotno": "^1.8.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"

View File

@ -1,3 +1,5 @@
import json
import gradio as gr
import modules.scripts as scripts
@ -8,6 +10,59 @@ from typing import Callable
from modules.shared import opts
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):
def __init__(self) -> None:
super().__init__()
@ -109,6 +164,10 @@ def on_ui_tabs():
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():
gr.HTML('<div id="canvas-editor-container"></div>')
with gr.Row():

View File

@ -5,14 +5,188 @@ import { Toolbar } from 'polotno/toolbar/toolbar';
import {ZoomButtons} from 'polotno/toolbar/zoom-buttons';
import {SidePanel} from 'polotno/side-panel';
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";
let _txt2imgInfoJSON;
let _img2imgInfoJSON;
export const setTxt2imgInfoJSON = (txt2imgInfoJSON) => {
_txt2imgInfoJSON = txt2imgInfoJSON;
}
export const setImg2imgInfoJSON = (img2imgInfoJSON) => {
_img2imgInfoJSON = img2imgInfoJSON;
}
let txt2ImgPageNum = 2;
let img2ImgPageNum = 2;
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: '100vh' }}>
<PolotnoContainer style={{width: '95vw', height: '90vh'}}>
<SidePanelWrap>
<SidePanel store={store} />
<SidePanel store={store} sections={sections}/>
</SidePanelWrap>
<WorkspaceWrap>
<Toolbar store={store}/>
@ -46,3 +220,6 @@ export const getPolotnoStore = () => {
window.createPolotnoApp = createPolotnoApp;
window.getPolotnoStore = getPolotnoStore;
window.setTxt2imgInfoJSON = setTxt2imgInfoJSON;
window.setImg2imgInfoJSON = setImg2imgInfoJSON;
window.setLoadMoreFunc = setLoadMoreFunc;