Merge pull request #308 from AbdullahAlfaraj/dev_1.2.7

Dev_1.3.0
pull/346/head v1.3.0
Abdullah Alfaraj 2023-07-15 23:23:51 +03:00 committed by GitHub
commit 6b10a67f30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
107 changed files with 18005 additions and 3076 deletions

4
.gitignore vendored
View File

@ -32,6 +32,4 @@ original_mask.png
# comments when packaging (include in the package,uxp packager will use .gitignore to ignore files):
*/dist/*LICENSE.txt
*/dist/*.bundle.js
# uncomments when packaging (don't include in the package)
# /docs
typescripts/dist

View File

@ -1,3 +1,9 @@
server/python_server/output/*
*.md
manifest.json
manifest.json
jimp/**
server_env/**
.github\workflows\wiki-sync-action.yml
**/dist
.github\workflows\wiki-sync-action.yml
tsconfig.json

View File

View File

10
enum.js
View File

@ -17,6 +17,7 @@ const AutomaticStatusEnum = {
Offline: 'offline',
RunningNoApi: 'running_no_api',
RunningWithApi: 'running_with_api',
AutoPhotoshopSDExtensionMissing: 'auto_photoshop_sd_missing',
}
const ViewerObjectTypeEnum = {
@ -30,12 +31,7 @@ const RequestStateEnum = {
Interrupted: 'interrupted', // canceled/ interrupted
Finished: 'finished', // finished generating
}
const DocumentTypeEnum = {
NoBackground: 'no_background',
ImageBackground: 'image_background',
SolidBackground: 'solid_background',
ArtBoard: 'artboard',
}
const BackgroundHistoryEnum = {
CorrectBackground: 'correct_background',
NoBackground: 'no_background',
@ -51,7 +47,7 @@ module.exports = {
AutomaticStatusEnum,
ViewerObjectTypeEnum,
RequestStateEnum,
DocumentTypeEnum,
BackgroundHistoryEnum,
PresetTypeEnum,
}

184
i18n/zh_CN/ps-plugin.json Normal file
View File

@ -0,0 +1,184 @@
{
"Auto-Photoshop-SD": "SD插件明空汉化",
"'A' for Automatic1111 server (webui-user.bat), Green is connected. Red Means there is a problem with your Automatic1111. Run 'webui-user.bat' and hit 'Refresh' button ": "A代表SD服务器(webui-user.bat)绿色已连接。红色表示SD服务器有问题。运行“webui-user.bat”并点击刷新按钮",
"'P' for proxy server (start_server.bat), Green is connected. Red means you need to run 'start_server.bat' or hit Refresh button": "P为代理服务器(start_server.bat),绿色已连接。红色表示您需要运行'start_server.bat'或点击刷新按钮",
"Stable Diffusion": "稳定扩散",
"Stable Diffusion UI": "稳定扩散 UI",
"Refresh": "刷新",
"Refresh the plugin, only fixes minor issues.": "刷新插件,仅修复小问题。",
"Update": "更新",
"Update the plugin if you encounter bugs. Get the latest features": "如果遇到错误,请更新插件。 获取最新功能",
"Select Lora": "选择 Lora",
"use lora in your prompt": "在提示中使用 lora",
"Generate": "生成",
"Generate txt2img": "生成 txt2img",
"Progress...": "进度...",
"Toggle the visibility of the Preview Image on the canvas": "切换画布上预览图像的可见性",
"Move and reSize the highlighted layer to fit into the Selection Area": "移动和调整突出显示的图层以适合选择区域",
"create a snapshot of what you see on the canvas and place on a new layer": "创建画布上看到的快照并放置在新图层上",
"reset the ui settings to their default values": "将 UI 设置重置为默认值",
"Interrogate the selected area, convert Image to Prompt": "审问所选区域,将图像转换为提示",
"use this mode to generate images from text only": "使用此模式仅从文本生成图像",
"use this mode to generate variation of an image": "使用此模式生成图像的变体",
"use this mode to generate variation of a small area of an image, while keeping the rest of the image intact": "使用此模式生成图像的小区域的变体,同时保持图像的其余部分完好无损",
"use this mode to (1) fill any missing area of an image,(2) expand an image": "使用此模式来1填充图像的任何缺失区域2扩展图像",
"Image": "图像",
"Mask": "蒙版",
"Batch Size:": "批量大小:",
"Batch Count:": "批量计数:",
"Steps:": "步数:",
"Selection Mode:": "选择模式:",
"ratio": "比率",
"precise": "精确",
"use the selection area width and height to fill the width and height sliders": "使用选择区域的宽度和高度来填充宽度和高度滑块",
"ignore": "忽略",
"fill the width and height sliders manually": "手动填充宽度和高度滑块",
"Smart Preset": "智能预设",
"auto fill the plugin with smart settings, to speed up your working process.": "自动填充智能设置的插件,以加快您的工作流程。",
"Width:": "宽度:",
"maintain the ratio between width and height slider": "保持宽度和高度滑块之间的比例",
"Height:": "高度:",
"CFG Scale:": "CFG 比例:",
"larger value will put more emphasis on the prompt": "较大的值将更加强调提示",
"Denoising Strength:": "降噪强度:",
"Image CFG Scale:": "图像 CFG 比例:",
"Pix2Pix CFG Scale (larger value will put more emphasis on the image)": "Pix2Pix CFG 比例(较大的值将更加强调图像)",
"Mask Blur:": "蒙版模糊:",
"Mask Expansion:": "蒙版扩展:",
"the larger the value the more the mask will expand, '0' means use precise masking, use in combination with the mask blur": "值越大,蒙版就会扩展得越多,“ 0”表示使用精确的蒙版与蒙版模糊一起使用",
"Inpainting conditioning mask strength:": "修复条件蒙版强度:",
"0 will keep the composition; 1 will allow composition to change": "0将保持构图 1将允许构图发生变化",
"Mask Content:": "蒙版内容:",
"fill": "填充",
"original": "原始",
"latent noise": "潜在噪声",
"latent nothing": "潜在无",
"Inpaint at Full Res": "在全分辨率下修复",
"Restore Faces": "面部修复",
"Hi Res Fix": "高分修复",
"Upscaler: ": "放大器:",
"Hi Res Steps:": "高分步数:",
"Hi Res Scale:": "高分比例:",
"Hi Res Denoising Strength:": "高分降噪强度:",
"Hi Res Output Width:": "高分输出宽度:",
"Hi Res Output Height:": "高分输出高度:",
"Inpaint Padding:": "修复填充:",
"Seed:": "种子:",
"Random": "随机",
"Last": "最后",
"Show Samplers": "显示采样器",
"Select A Script": "选择脚本",
"Activate": "激活",
"Viewer": "查看器",
"Preview": "预览器",
"View your generated images on the canvas": "在画布上查看生成的图像",
"Set Mask": "设置蒙版",
"Set Init Image": "设置初始图像",
"Interrupt": "中断",
"Selection Area": "选择区域",
"Thumbnail Size": "缩略图大小",
"Square 1:1": "正方形 1:1",
"Prompts Library": "提示库",
"Prompt Shortcut: a single word that represent a prompt": "提示快捷方式:代表提示的单个单词",
"Key for new prompt shortcut": "新提示快捷方式的键",
"to be replaced": "要被替换的",
"Value for new prompt shortcut": "新提示快捷方式的值",
"to be replaced with": "要被替换为",
"Add to Prompt Shortcut": "添加到提示快捷方式",
"prompt shortcut": "提示快捷方式",
"Selection a prompt": "选择提示词",
"Refresh Menu": "刷新菜单",
"Load": "加载",
"Save": "保存",
"History": "历史记录",
"history of all the images you generated": "您生成的所有图像的历史记录",
"Load Previous Generations": "加载以前的生成",
"Clear Results": "清除结果",
"Lexica": "Lexica",
"Explore Lexica for prompts and inspiration": "探索提示词和灵感的词典",
"Search:": "搜索:",
"user prompt(text) to Search Lexica": "用户提示(文本)搜索词典",
"User the selected area (image) on canvas to Search Lexica": "用户在画布上选择的区域(图像)搜索词典",
"Image Search": "图像搜索",
"Image Search Engine": "图像搜索引擎",
"ControlNet": "ControlNet",
"The Controlnet Extension is missing from Automatic1111.Please install it to use it through the plugin.": "Automatic1111缺少ControlNet扩展。请安装它以通过插件使用它。",
"ControlNet Preset": "ControlNet预设",
"auto fill the ControlNet with smart settings, to speed up your working process.": "自动填充智能设置的ControlNet以加快您的工作流程。",
"Set All CtrlNet Images": "设置所有 CtrlNet 图像",
"Disable ControlNet Tab": "禁用ControlNet选项卡",
"Control Net Settings Slot 0": "ControlNet设置插槽0",
"Set CtrlNet Img": "设置 CtrlNet 图像",
"Preview Annotator": "预览注释器",
"Enable": "启用",
"Low VRAM": "低显存",
"Guess Mode": "猜测模式",
"Weight:": "权重:",
"2 will keep the composition; 0 will allow composition to change": "2将保持构图 0将允许构图发生变化",
"Guidance strength start:": "Guidance strength start:",
"Guidance strength end:": "Guidance strength end:",
"Horde": "Horde",
"Horde Key:": "Horde密钥",
"Select Backend:": "选择后端:",
"Native Horde": "本机 Horde",
"use the horde with the plugin no need to install anything else": "使用插件的 Horde无需安装其他任何内容",
"Auto1111 Horde Extension": "Auto1111 Horde 扩展",
"Use the horde extension from Automatic1111 Extension tab": "使用 Automatic1111 扩展选项卡中的 Horde 扩展",
"Auto1111 Only": "仅限 Auto1111",
"use Auto1111 disable the Horde": "使用 Auto1111 禁用 Horde",
"Refresh Models": "刷新模型",
"NSFW": "NSFW",
"Share with LION": "与 LION 共享",
"Extras": "额外",
"Resize": "调整大小",
"Resize scale of current selection size": "调整当前选择大小的比例",
"Generate upscale": "生成放大",
"No work in progress": "没有正在进行的工作",
"Upscaler 1:": "放大算法 1",
"Upscaler 2:": "放大算法 2",
"Upscaler 2 visibility:": "放大算法 2 可见性:",
"GFPGAN visibility:": "GFPGAN 可见性:",
"CodeFormer visibility:": "CodeFormer 可见性:",
"CodeFormer weight:": "CodeFormer 权重:",
"Settings": "设置",
"Custom Presets": "自定义预设",
"SD Url:": "SD Url",
"Submit": "提交",
"use sharp mask": "使用锐化蒙版",
"Smart Object": "智能对象",
"Live Progress Image": "实时进度图像",
"Restore Original Prompt": "恢复原始提示",
"Image Cfg Scale Slider": "图像配置比例滑块",
"Use Silent Mode": "使用静默模式",
"Your PC Speed(optimization):": "电脑性能(优化):",
"Slow PC": "节能模式",
"Fast PC": "高性能模式",
"Use Colab": "使用 Colab",
"Select Extension:": "选择扩展:",
"Proxy Server": "代理服务器",
"use the proxy server, need to run 'start_server.bat' ": "使用代理服务器需要运行“start_server.bat”",
"Auto111 Extension": "SD扩展",
"use Automatic1111 Photoshop SD Extension, need to install the extension in Auto1111": "使用Photoshop SD 扩展,需要在 Auto1111 中安装扩展",
"None": "无",
"Use the Plugin Only No Additional Component": "仅使用插件,无需其他组件",
"Folder Path (read only):": "文件夹路径(只读):",
"copy paste the address to access the folder where the images are stored": "复制粘贴地址以访问存储图像的文件夹",
"Get Path": "获取路径",
"Preset Name": "预设名称",
"New Preset": "新建预设",
"Preset Type:": "预设类型:",
"SD Preset": "SD 预设",
"Save Preset": "保存预设",
"Delete Preset": "删除预设",
"The Controlnet Extension is missing from Automatic1111.\nPlease install it to use it through the plugin.": "本地SD中缺少ControlNet扩展。\n请安装该插件后再使用。",
"Set CtrlImg": "设置原始图",
"ControlNet Tab": "ControlNet",
"ControlNet Unit": "ControlNet #",
"Select Filter": "选择过滤器",
"Select Module": "选择预处理器",
"Select Model": "选择模型",
"Keep all generated images on the canvas": "在画布上保留所有生成图像",
"Delete all generated images from the canvas": "在画布上删除所有生成图像",
"Keep only the highlighted images": "在画布上保留选中的图像",
"Generate More": "生成更多"
}

3515
i18n/zh_CN/sd-official.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +1,29 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><style>.cls-1{fill:#ff13dc;fill-opacity:0;}.cls-2{fill:#32cd32;}.cls-3{fill:none;}.cls-4{fill:#464646;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="check_all"><rect id="Canvas" class="cls-1" width="18" height="18"/><path class="cls-2" d="M13.32,10.06a3.25,3.25,0,1,0,3.25,3.25,3.25,3.25,0,0,0-3.25-3.25Zm-.86,5.23L11,13.78a.19.19,0,0,1,0-.26l.38-.38a.19.19,0,0,1,.26,0l1,1,2.23-2.23a.19.19,0,0,1,.26,0l.38.38a.19.19,0,0,1,0,.26l-2.74,2.74A.19.19,0,0,1,12.46,15.29Z"/><path class="cls-3" d="M3.74,7.78,9,10.46l5.26-2.68L16.56,9c.06,0,.09.07.09.11v-4s0,.08-.09.11L9.21,8.91a.45.45,0,0,1-.42,0L1.44,5.18c-.12-.07-.12-.17,0-.23L8.79,1.21A.5.5,0,0,1,9,1.16H1.35v7.9s0-.08.09-.11Z"/><path class="cls-3" d="M1.44,13.18q-.09-.06-.09-.12V17H9a.39.39,0,0,1-.21-.06Z"/><path class="cls-3" d="M9.21,16.91A.39.39,0,0,1,9,17h3a3.94,3.94,0,0,1-1.3-.8Z"/><path class="cls-3" d="M16.56,5c.06,0,.09.07.09.11V1.16H9a.5.5,0,0,1,.21.05Z"/><path class="cls-3" d="M15.16,9.89a4,4,0,0,1,1.49,1.41V9.06s0,.08-.09.12Z"/><path class="cls-3" d="M3.74,11.78,9,14.46l.53-.27a3.59,3.59,0,0,1-.12-.88,3.19,3.19,0,0,1,.06-.53l-.26.13a.45.45,0,0,1-.42,0L1.44,9.18q-.09-.06-.09-.12v4s0-.08.09-.11Z"/><path class="cls-4" d="M1.44,5c-.12.06-.12.16,0,.23L8.79,8.91a.45.45,0,0,0,.42,0l7.35-3.74c.06,0,.09-.07.09-.11h0s0-.08-.09-.11L9.21,1.21A.5.5,0,0,0,9,1.16H9a.5.5,0,0,0-.21.05Z"/><path class="cls-4" d="M10.69,16.17a3.84,3.84,0,0,1-1.16-2L9,14.46,3.74,11.78,1.44,13c-.06,0-.09.07-.09.11s0,.08.09.12l7.35,3.73a.4.4,0,0,0,.42,0Z"/><path class="cls-4" d="M8.79,12.91a.45.45,0,0,0,.42,0l.26-.13a3.89,3.89,0,0,1,3.85-3.37,3.82,3.82,0,0,1,1.84.48l1.4-.71q.09-.06.09-.12h0s0-.08-.09-.11l-2.3-1.17L9,10.46,3.74,7.78,1.44,9c-.06,0-.09.07-.09.11s0,.08.09.12Z"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="Layer_+">
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
<path class="d" d="m15.96,18.88h26.45c1.49,0,2.69,1.2,2.69,2.69v27.21"/>
<polyline class="d" points="22.29 32.85 28.56 41.81 36.62 29.27"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 746 B

View File

@ -1 +1,28 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><style>.cls-1{fill:#ff13dc;fill-opacity:0;}.cls-2{fill:#32cd32;}.cls-3{fill:#464646;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="check_one"><rect id="Canvas" class="cls-1" width="18" height="18"/><path class="cls-2" d="M13.32,10.06a3.25,3.25,0,1,0,3.25,3.25,3.25,3.25,0,0,0-3.25-3.25Zm-.86,5.23L11,13.78a.19.19,0,0,1,0-.26l.38-.38a.19.19,0,0,1,.26,0l1,1,2.23-2.23a.19.19,0,0,1,.26,0l.38.38a.19.19,0,0,1,0,.26l-2.74,2.74A.19.19,0,0,1,12.46,15.29Z"/><path class="cls-3" d="M16.65,9.06s0-.08-.09-.11L9.21,5.21a.51.51,0,0,0-.42,0L3.71,7.8,1.44,9c-.12.06-.12.16,0,.23l7.35,3.73a.45.45,0,0,0,.42,0l.26-.13a3.89,3.89,0,0,1,3.85-3.37,3.82,3.82,0,0,1,1.84.48l1.4-.71q.09-.06.09-.12Z"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="Layer_+1">
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
<polyline class="d" points="24.98 29.27 31.25 38.23 39.31 25.68"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 770 B

After

Width:  |  Height:  |  Size: 670 B

View File

@ -1,14 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 18 18" style="enable-background:new 0 0 18 18;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;fill-opacity:0;}
.st1{fill:#FFFFFF;}
</style>
<rect id="Canvas" class="st0" width="18" height="18"/>
<path class="st1" d="M9,6C7.3,6,6,7.3,6,9s1.3,3,3,3s3-1.3,3-3S10.7,6,9,6z"/>
<path class="st1" d="M16.5,4h-3l-1.7-1.8C11.7,2.1,11.6,2,11.4,2H6.6C6.4,2,6.3,2.1,6.2,2.2L4.5,4h-3C1.2,4,1,4.2,1,4.5v10
C1,14.8,1.2,15,1.5,15h15c0.3,0,0.5-0.2,0.5-0.5v-10C17,4.2,16.8,4,16.5,4z M9,13.1c-2.3,0-4.1-1.8-4.1-4.1c0-2.3,1.8-4.1,4.1-4.1
c2.3,0,4.1,1.8,4.1,4.1S11.3,13.1,9,13.1L9,13.1z"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="camera">
<circle class="c" cx="32.59" cy="34.2" r="10.02"/>
<polyline class="d" points="15.12 20.31 22.29 20.31 26.69 12.3 38.34 12.3 42.89 20.31 50.06 20.31"/>
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 873 B

After

Width:  |  Height:  |  Size: 757 B

View File

@ -1,4 +1,32 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M55 9.22988C53.7379 7.92856 52.227 6.89417 50.5573 6.18813C48.8875 5.48209 47.0929 5.11882 45.28 5.11988H45.17C41.3631 5.11593 37.7084 6.61447 35 9.28988L22.68 21.6399C20.458 23.8816 19.0645 26.8123 18.7283 29.9507C18.3921 33.089 19.1333 36.2484 20.83 38.9099C21.1056 39.3419 21.4755 39.7058 21.912 39.9743C22.3485 40.2428 22.8402 40.4088 23.35 40.4599C23.4765 40.4698 23.6036 40.4698 23.73 40.4599C24.65 40.4648 25.5344 40.1053 26.19 39.4599C26.7477 38.907 27.0994 38.18 27.1867 37.3995C27.274 36.6191 27.0917 35.8323 26.67 35.1699C25.8204 33.8426 25.4485 32.2653 25.6158 30.6983C25.783 29.1312 26.4794 27.668 27.59 26.5499L40.17 13.9999C40.8505 13.3239 41.6643 12.7969 42.5595 12.4526C43.4548 12.1083 44.4119 11.9541 45.37 11.9999C46.3325 12.041 47.2754 12.2849 48.1371 12.7158C48.9987 13.1466 49.7597 13.7546 50.37 14.4999C51.4441 15.873 51.9746 17.5937 51.8602 19.3332C51.7459 21.0728 50.9946 22.7092 49.75 23.9299L47.29 26.3899C46.9662 26.7115 46.7092 27.094 46.5338 27.5153C46.3585 27.9366 46.2682 28.3885 46.2682 28.8449C46.2682 29.3013 46.3585 29.7531 46.5338 30.1745C46.7092 30.5958 46.9662 30.9783 47.29 31.2999C47.9422 31.949 48.8249 32.3134 49.745 32.3134C50.6652 32.3134 51.5479 31.949 52.2 31.2999L54.83 28.6699C57.3937 26.0948 58.8471 22.6181 58.8789 18.9845C58.9106 15.351 57.5183 11.8494 55 9.22988V9.22988Z" fill="black"/>
<path d="M40.5401 23.4198C39.8914 23.3466 39.2352 23.4579 38.6469 23.7408C38.0586 24.0238 37.5621 24.4669 37.2143 25.0194C36.8665 25.5718 36.6817 26.2112 36.6809 26.864C36.6802 27.5168 36.8636 28.1566 37.2101 28.7098C38.0618 30.0376 38.4357 31.6159 38.2703 33.1846C38.1049 34.7534 37.41 36.2189 36.3001 37.3398L23.7201 49.9198C23.0386 50.5944 22.2246 51.1203 21.3296 51.4646C20.4346 51.8088 19.478 51.9638 18.5201 51.9198C17.5573 51.8756 16.6145 51.6293 15.753 51.1969C14.8916 50.7644 14.1308 50.1556 13.5201 49.4098C12.4453 48.0408 11.9142 46.3229 12.0286 44.5862C12.143 42.8494 12.895 41.2161 14.1401 39.9998L16.6001 37.5398C17.2446 36.8837 17.604 35.9996 17.6001 35.0798C17.6029 34.6258 17.516 34.1756 17.3444 33.7552C17.1728 33.3347 16.9198 32.9523 16.6001 32.6298C15.9405 31.9949 15.0606 31.6403 14.1451 31.6403C13.2296 31.6403 12.3497 31.9949 11.6901 32.6298L9.29012 34.9998C7.95287 36.3527 6.89608 37.9564 6.18046 39.7188C5.46484 41.4812 5.10449 43.3677 5.12012 45.2698C5.11771 47.0844 5.48034 48.8809 6.18641 50.5525C6.89249 52.224 7.92759 53.7365 9.23012 54.9998C11.8477 57.5051 15.3395 58.8901 18.9626 58.8602C22.5857 58.8303 26.0542 57.3879 28.6301 54.8398L41.2101 42.2498C43.424 40.0076 44.8123 37.0811 45.1484 33.948C45.4844 30.8149 44.7482 27.6605 43.0601 24.9998C42.7867 24.5632 42.4179 24.1943 41.9815 23.9206C41.545 23.647 41.0522 23.4758 40.5401 23.4198Z" fill="black"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c {
stroke-linejoin: round;
}
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="chain_black.svg">
<path class="d" d="m36.42,34.84c2.15,1.45,3.55,3.99,3.55,6.64,0,4.41-3.57,7.98-7.98,7.98s-7.98-3.57-7.98-7.98c0-2.7,1.46-5.24,3.57-6.65"/>
<path class="d" d="m27.59,27.46c-.31-.22-3.58-2.63-3.57-6.47.01-3.85,3.31-7.98,7.98-7.98,4.41,0,7.98,3.57,7.98,7.98,0,2.65-1.4,5.19-3.56,6.64"/>
<line class="c" x1="32" y1="24.19" x2="32" y2="38.83"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 890 B

View File

@ -1,4 +1,41 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M55 9.22988C53.7379 7.92856 52.227 6.89417 50.5573 6.18813C48.8875 5.48209 47.0929 5.11882 45.28 5.11988H45.17C41.3631 5.11593 37.7084 6.61447 35 9.28988L22.68 21.6399C20.458 23.8816 19.0645 26.8123 18.7283 29.9507C18.3921 33.089 19.1333 36.2484 20.83 38.9099C21.1056 39.3419 21.4755 39.7058 21.912 39.9743C22.3485 40.2428 22.8402 40.4088 23.35 40.4599C23.4765 40.4698 23.6036 40.4698 23.73 40.4599C24.65 40.4648 25.5344 40.1053 26.19 39.4599C26.7477 38.907 27.0994 38.18 27.1867 37.3995C27.274 36.6191 27.0917 35.8323 26.67 35.1699C25.8204 33.8426 25.4485 32.2653 25.6158 30.6983C25.783 29.1312 26.4794 27.668 27.59 26.5499L40.17 13.9999C40.8505 13.3239 41.6643 12.7969 42.5595 12.4526C43.4548 12.1083 44.4119 11.9541 45.37 11.9999C46.3325 12.041 47.2754 12.2849 48.1371 12.7158C48.9987 13.1466 49.7597 13.7546 50.37 14.4999C51.4441 15.873 51.9746 17.5937 51.8602 19.3332C51.7459 21.0728 50.9946 22.7092 49.75 23.9299L47.29 26.3899C46.9662 26.7115 46.7092 27.094 46.5338 27.5153C46.3585 27.9366 46.2682 28.3885 46.2682 28.8449C46.2682 29.3013 46.3585 29.7531 46.5338 30.1745C46.7092 30.5958 46.9662 30.9783 47.29 31.2999C47.9422 31.949 48.8249 32.3134 49.745 32.3134C50.6652 32.3134 51.5479 31.949 52.2 31.2999L54.83 28.6699C57.3937 26.0948 58.8471 22.6181 58.8789 18.9845C58.9106 15.351 57.5183 11.8494 55 9.22988V9.22988Z" fill="white"/>
<path d="M40.5401 23.4198C39.8914 23.3466 39.2352 23.4579 38.6469 23.7408C38.0586 24.0238 37.5621 24.4669 37.2143 25.0194C36.8665 25.5718 36.6817 26.2112 36.6809 26.864C36.6802 27.5168 36.8636 28.1566 37.2101 28.7098C38.0618 30.0376 38.4357 31.6159 38.2703 33.1846C38.1049 34.7534 37.41 36.2189 36.3001 37.3398L23.7201 49.9198C23.0386 50.5944 22.2246 51.1203 21.3296 51.4646C20.4346 51.8088 19.478 51.9638 18.5201 51.9198C17.5573 51.8756 16.6145 51.6293 15.753 51.1969C14.8916 50.7644 14.1308 50.1556 13.5201 49.4098C12.4453 48.0408 11.9142 46.3229 12.0286 44.5862C12.143 42.8494 12.895 41.2161 14.1401 39.9998L16.6001 37.5398C17.2446 36.8837 17.604 35.9996 17.6001 35.0798C17.6029 34.6258 17.516 34.1756 17.3444 33.7552C17.1728 33.3347 16.9198 32.9523 16.6001 32.6298C15.9405 31.9949 15.0606 31.6403 14.1451 31.6403C13.2296 31.6403 12.3497 31.9949 11.6901 32.6298L9.29012 34.9998C7.95287 36.3527 6.89608 37.9564 6.18046 39.7188C5.46484 41.4812 5.10449 43.3677 5.12012 45.2698C5.11771 47.0844 5.48034 48.8809 6.18641 50.5525C6.89249 52.224 7.92759 53.7365 9.23012 54.9998C11.8477 57.5051 15.3395 58.8901 18.9626 58.8602C22.5857 58.8303 26.0542 57.3879 28.6301 54.8398L41.2101 42.2498C43.424 40.0076 44.8123 37.0811 45.1484 33.948C45.4844 30.8149 44.7482 27.6605 43.0601 24.9998C42.7867 24.5632 42.4179 24.1943 41.9815 23.9206C41.545 23.647 41.0522 23.4758 40.5401 23.4198Z" fill="white"/>
</svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c {
stroke-linejoin: round;
}
.c, .d, .e {
stroke: #d6d6d6;
stroke-linecap: round;
}
.c, .d, .e, .f {
fill: none;
}
.c, .e {
stroke-width: 4px;
}
.d {
stroke-width: 8px;
}
.d, .e {
stroke-miterlimit: 10;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="f" width="64" height="64"/>
</g>
<g id="b" data-name="chain_white">
<path class="e" d="m36.42,34.84c2.15,1.45,3.55,3.99,3.55,6.64,0,4.41-3.57,7.98-7.98,7.98s-7.98-3.57-7.98-7.98c0-2.7,1.46-5.24,3.57-6.65"/>
<path class="e" d="m27.59,27.46c-.31-.22-3.58-2.63-3.57-6.47.01-3.85,3.31-7.98,7.98-7.98,4.41,0,7.98,3.57,7.98,7.98,0,2.65-1.4,5.19-3.56,6.64"/>
<line class="c" x1="32" y1="24.19" x2="32" y2="38.83"/>
<line class="d" x1="44.83" y1="31.51" x2="59.93" y2="31.51"/>
<line class="d" x1="4.02" y1="31.51" x2="19.6" y2="31.51"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><style>.cls-1{fill:#ff13dc;fill-opacity:0;}.cls-2{fill:#ff4500;}.cls-3{fill:#464646;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="cross_all"><rect id="Canvas" class="cls-1" width="18" height="18"/><path class="cls-2" d="M13.32,10.06a3.25,3.25,0,1,0,3.25,3.25A3.25,3.25,0,0,0,13.32,10.06ZM15,14.55a.31.31,0,0,1-.44.44l-1.23-1.24L12.08,15h0a.31.31,0,0,1-.44-.44l1.24-1.24-1.24-1.23a.31.31,0,0,1,0-.44.31.31,0,0,1,.44,0l1.24,1.23,1.23-1.23h0a.3.3,0,0,1,.44,0,.3.3,0,0,1,0,.44l-1.23,1.23Z"/><path class="cls-3" d="M16.56,5,9.21,1.21A.5.5,0,0,0,9,1.16H9a.5.5,0,0,0-.21.05L1.44,5c-.12.06-.12.16,0,.23L8.79,8.91a.45.45,0,0,0,.42,0l7.35-3.74c.06,0,.09-.07.09-.11h0S16.62,5,16.56,5Z"/><path class="cls-3" d="M9,14.46,3.74,11.78,1.44,13c-.06,0-.09.07-.09.11s0,.08.09.12l7.35,3.73a.4.4,0,0,0,.42,0l1.48-.74a3.84,3.84,0,0,1-1.16-2Z"/><path class="cls-3" d="M14.26,7.78,9,10.46,3.74,7.78,1.44,9c-.06,0-.09.07-.09.11s0,.08.09.12l7.35,3.73a.45.45,0,0,0,.42,0l.26-.13a3.89,3.89,0,0,1,3.85-3.37,3.82,3.82,0,0,1,1.84.48l1.4-.71q.09-.06.09-.12h0s0-.08-.09-.11Z"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="Layer_-">
<line class="c" x1="35.42" y1="29.57" x2="23.48" y2="41.51"/>
<line class="c" x1="35.42" y1="41.51" x2="23.48" y2="29.57"/>
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
<path class="d" d="m15.96,18.88h26.43c1.48,0,2.69,1.2,2.69,2.69v27.21"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 807 B

View File

@ -1 +1,25 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><defs><style>.cls-1{fill:#ff13dc;fill-opacity:0;}.cls-2{fill:#ff4500;}.cls-3{fill:#464646;}</style></defs><g id="Layer_2" data-name="Layer 2"><g id="cross_one"><rect id="Canvas" class="cls-1" width="18" height="18"/><path class="cls-2" d="M13.32,10.06a3.25,3.25,0,1,0,3.25,3.25A3.25,3.25,0,0,0,13.32,10.06ZM15,14.55a.31.31,0,0,1-.44.44l-1.23-1.24L12.08,15h0a.31.31,0,0,1-.44-.44l1.24-1.24-1.24-1.23a.31.31,0,0,1,0-.44.31.31,0,0,1,.44,0l1.24,1.23,1.23-1.23h0a.3.3,0,0,1,.44,0,.3.3,0,0,1,0,.44l-1.23,1.23Z"/><path class="cls-3" d="M16.65,9h0s0-.08-.09-.11L9.21,5.15a.45.45,0,0,0-.42,0L1.44,8.89c-.12.06-.12.16,0,.22l7.35,3.74a.45.45,0,0,0,.42,0l.26-.13a3.91,3.91,0,0,1,3.85-3.31,3.84,3.84,0,0,1,1.78.44l1.46-.74C16.62,9.08,16.65,9,16.65,9Z"/></g></g></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d {
fill: none;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="d" width="64" height="64"/>
</g>
<g id="b" data-name="Layer_-1">
<line class="c" x1="38.11" y1="25.99" x2="26.17" y2="37.92"/>
<line class="c" x1="38.11" y1="37.92" x2="26.17" y2="25.99"/>
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 814 B

After

Width:  |  Height:  |  Size: 675 B

View File

@ -1,15 +1,33 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" width="800px" height="800px" viewBox="0 0 32 32" id="icon" xmlns="http://www.w3.org/2000/svg">
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.cls-1 {
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
.f {
fill: #d6d6d6;
}
</style>
</defs>
<title>image--search</title>
<path d="M24,14a5.99,5.99,0,0,0-4.885,9.4712L14,28.5859,15.4141,30l5.1147-5.1147A5.9971,5.9971,0,1,0,24,14Zm0,10a4,4,0,1,1,4-4A4.0045,4.0045,0,0,1,24,24Z"/>
<path d="M17,12a3,3,0,1,0-3-3A3.0033,3.0033,0,0,0,17,12Zm0-4a1,1,0,1,1-1,1A1.0009,1.0009,0,0,1,17,8Z"/>
<path d="M12,24H4V17.9966L9,13l5.5859,5.5859L16,17.168l-5.5859-5.5855a2,2,0,0,0-2.8282,0L4,15.168V4H24v6h2V4a2.0023,2.0023,0,0,0-2-2H4A2.002,2.002,0,0,0,2,4V24a2.0023,2.0023,0,0,0,2,2h8Z"/>
<rect id="_Transparent_Rectangle_" data-name="&lt;Transparent Rectangle&gt;" class="cls-1" width="32" height="32"/>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="search1">
<path class="d" d="m16.03,40.05c-1.48,0-2.69-1.23-2.69-2.74V14.98c0-1.52,1.2-2.74,2.69-2.74h33.14c1.48,0,2.69,1.23,2.69,2.74v22.33c0,1.52-1.2,2.74-2.69,2.74"/>
<path class="f" d="m33.26,28.43c3.38,0,6.13,2.75,6.13,6.13s-2.75,6.13-6.13,6.13-6.13-2.75-6.13-6.13,2.75-6.13,6.13-6.13m0-4c-5.59,0-10.13,4.54-10.13,10.13s4.54,10.13,10.13,10.13,10.13-4.54,10.13-10.13-4.54-10.13-10.13-10.13h0Z"/>
<line class="c" x1="25.36" y1="45.3" x2="18.08" y2="52.7"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 933 B

After

Width:  |  Height:  |  Size: 1012 B

View File

@ -1,8 +1,25 @@
<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
<?xml version="1.0" encoding="UTF-8"?>
<svg id="_Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.cls-1 {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
<g>
<title>Layer 1</title>
<rect stroke-dasharray="2,2" stroke="#000" id="svg_5" height="299" width="299" y="56.21698" x="46.78302" stroke-width="5" fill="none"/>
<path id="svg_7" d="m105.54971,171.35925l0,300.2682l362.99997,0l0,-300.2682l-362.99997,0zm305.86109,274.4952l-248.7222,0l0,-248.7222l248.7222,0l0,248.7222zm-188.22221,-151.24999c19.17178,0 34.727,-15.55522 34.727,-34.727c0,-19.18522 -15.55522,-34.727 -34.727,-34.727c-19.18522,0 -34.727,15.55522 -34.727,34.727c0,19.17178 15.54178,34.727 34.727,34.727zm174.79121,30.25l-66.10633,-66.10633l-92.99521,92.99521l-25.773,-25.773l-36.99911,36.99911l0,69.44055l221.87365,0l0,-107.55555z" stroke-width="2" stroke="#000" fill="#8691a0"/>
</g>
.cls-1, .cls-2 {
fill: none;
}
</style>
</defs>
<g id="a">
<rect class="cls-2" width="64" height="64"/>
</g>
<g id="b">
<rect class="cls-1" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
<polyline class="cls-1" points="13.33 45.4 22.29 33.75 28.56 41.81 42.89 26.58 51.85 39.12"/>
<circle class="cls-1" cx="25.87" cy="22.1" r="1.79"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 778 B

After

Width:  |  Height:  |  Size: 720 B

36
icon/move_to_canvas.svg Normal file
View File

@ -0,0 +1,36 @@
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
width="18.000000pt"
height="18.000000pt"
viewBox="0 0 127.000000 127.000000"
preserveAspectRatio="xMidYMid meet"
style="
width: 32px;
height: 27px;
position: absolute;
top: 3px;
left: -1px;
"
>
<g
transform="translate(0.000000,115.000000) scale(0.100000,-0.100000)"
fill="#000000"
stroke="none"
>
<path
d="M577 1172 c-10 -11 -17 -36 -17 -60 l0 -42 -133 0 c-117 0 -136 -2
-150 -18 -15 -16 -17 -49 -17 -254 l0 -236 -24 -6 c-53 -13 -74 -67 -38 -99
15 -13 39 -17 115 -17 53 0 97 -4 97 -9 0 -5 -13 -69 -30 -143 -35 -155 -36
-169 -12 -191 45 -41 84 -11 104 78 l13 60 150 0 150 0 13 -60 c15 -67 35 -95
67 -95 27 0 55 29 55 56 0 11 -13 78 -30 150 -16 71 -30 135 -30 142 0 9 26
12 98 12 83 0 102 3 115 18 33 36 14 85 -39 98 l-24 6 0 238 c0 217 -2 238
-18 253 -15 14 -42 17 -150 17 l-132 0 0 43 c0 30 -6 49 -18 60 -26 23 -94 22
-115 -1z m393 -377 l0 -235 -335 0 -335 0 0 235 0 235 335 0 335 0 0 -235z
m77 -291 c3 -9 0 -20 -8 -25 -18 -11 -790 -11 -808 0 -8 5 -11 16 -8 25 6 14
49 16 412 16 363 0 406 -2 412 -16z m-293 -133 c9 -39 16 -75 16 -80 0 -7 -47
-11 -135 -11 -88 0 -135 4 -135 11 0 5 7 41 16 80 l16 69 103 0 103 0 16 -69z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

9
icon/pen.svg Normal file
View File

@ -0,0 +1,9 @@
<svg
viewBox="0 0 36 36"
style="width: 18px; height: 18px"
>
<path
d="M33.567 8.2L27.8 2.432a1.215 1.215 0 0 0-.866-.353H26.9a1.371 1.371 0 0 0-.927.406L5.084 23.372a.99.99 0 0 0-.251.422L2.055 33.1c-.114.377.459.851.783.851a.251.251 0 0 0 .062-.007c.276-.063 7.866-2.344 9.311-2.778a.972.972 0 0 0 .414-.249l20.888-20.889a1.372 1.372 0 0 0 .4-.883 1.221 1.221 0 0 0-.346-.945zM11.4 29.316c-2.161.649-4.862 1.465-6.729 2.022l2.009-6.73z"
/>
</svg>

After

Width:  |  Height:  |  Size: 729 B

56
icon/preview.svg Normal file
View File

@ -0,0 +1,56 @@
<svg
version="1.0"
xmlns="http://www.w3.org/2000/svg"
width="512.000000pt"
height="512.000000pt"
viewBox="0 0 512.000000 512.000000"
preserveAspectRatio="xMidYMid meet"
style="
width: 32px;
height: 32px;
position: absolute;
top: 0px;
left: -1px;
"
>
<g
transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
fill="#000000"
stroke="none"
>
<path
d="M920 4311 c-118 -36 -212 -129 -259 -254 -18 -50 -21 -79 -21 -217
l0 -160 80 0 80 0 0 128 c0 147 14 208 60 264 51 62 99 78 231 78 63 0 131 3
152 6 l37 7 0 78 0 79 -167 -1 c-93 0 -179 -4 -193 -8z"
/>
<path
d="M3840 4242 l0 -79 38 -7 c20 -3 88 -6 151 -6 132 0 180 -16 231 -78
46 -56 60 -117 60 -264 l0 -128 80 0 80 0 0 158 c0 131 -3 169 -19 215 -36
104 -104 182 -204 234 -50 27 -58 28 -234 31 l-183 4 0 -80z"
/>
<path
d="M2450 3404 c-252 -30 -471 -107 -694 -243 -199 -122 -420 -320 -578
-519 -32 -40 -58 -79 -58 -85 0 -20 105 -146 213 -256 296 -302 597 -483 937
-562 122 -29 364 -37 495 -16 318 51 613 200 903 457 102 90 249 249 310 334
l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
-290 27 -353 19z m279 -317 c189 -54 340 -214 386 -409 79 -338 -192 -670
-549 -669 -158 0 -278 47 -390 151 -123 116 -177 238 -178 400 0 161 54 284
178 400 153 143 345 187 553 127z"
/>
<path
d="M2485 2841 c-59 -17 -86 -31 -129 -71 -153 -141 -103 -397 92 -471
116 -43 262 -7 333 82 46 59 62 106 63 184 0 52 -6 82 -23 117 -27 55 -94 121
-148 143 -45 19 -146 28 -188 16z"
/>
<path
d="M640 1280 c0 -138 3 -167 21 -217 40 -107 103 -178 202 -230 50 -27
58 -28 235 -31 l182 -4 0 80 0 79 -37 7 c-21 3 -89 6 -152 6 -132 0 -180 16
-231 78 -46 56 -60 117 -60 264 l0 128 -80 0 -80 0 0 -160z"
/>
<path
d="M4320 1312 c0 -147 -14 -208 -60 -264 -51 -62 -99 -78 -231 -78 -63
0 -131 -3 -151 -6 l-38 -7 0 -79 0 -80 183 4 c176 3 184 4 234 31 99 52 162
123 202 230 18 50 21 79 21 217 l0 160 -80 0 -80 0 0 -128z"
/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

35
icon/reselect-area.svg Normal file
View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 27.6.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 64 64" style="enable-background:new 0 0 64 64;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:none;stroke:#D6D6D6;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;}
</style>
<g id="frame">
<rect class="st0" width="64" height="64"/>
</g>
<g id="selection-area_x5F_2.svg">
<path id="frame_00000039099898607207917570000009294483814832552347_" class="st1" d="M13.34,36.93c0-0.03,0-4.95,0-4.98
c0-0.06,0-4.9,0-4.9"/>
<path id="frame_00000005248667001567519180000004593825086206177449_" class="st1" d="M21.72,51.66c-1.9,0-3.8,0-5.7,0
c-0.19,0-0.48-0.02-0.8-0.12c-0.42-0.13-0.8-0.36-1.1-0.67c-0.49-0.49-0.79-1.16-0.79-1.9c0-0.08,0-6.01,0-6.09"/>
<path id="frame_00000114036308278317986480000014951358649188744836_" class="st1" d="M37.61,51.66c-0.04,0-5,0-5.03,0
c-1.62,0-3.24,0-4.86,0"/>
<path id="frame_00000079472285635594814390000004521152521064386487_" class="st1" d="M51.83,42.93c0,2.02,0,4.03,0,6.05
c0,0.19-0.02,0.48-0.12,0.8c-0.13,0.42-0.36,0.8-0.67,1.1c-0.49,0.49-1.16,0.79-1.9,0.79c-0.07,0-5.47,0-5.54,0"/>
<path id="frame_00000014606341256593317910000012874094123312002953_" class="st1" d="M51.83,27.01c0,0.03,0,4.91,0,4.94
c0,1.66,0,3.31,0,4.97"/>
<path id="frame_00000181054580322392786830000004708723869888510346_" class="st1" d="M43.59,12.24c1.85,0,3.7,0,5.55,0
c0.19,0,0.48,0.02,0.8,0.12c0.42,0.13,0.8,0.36,1.1,0.67c0.49,0.49,0.79,1.16,0.79,1.9c0,0.08,0,6.04,0,6.12"/>
<path id="frame_00000070799153599262733950000005754557140857329592_" class="st1" d="M27.71,12.24c0.03,0,4.84,0,4.87,0
c1.68,0,3.36,0,5.03,0"/>
<path id="frame_00000165930517913269344000000003886705051876999349_" class="st1" d="M13.34,21.01c0,0,0-6.03,0-6.09
c0-0.19,0.02-0.48,0.12-0.8c0.13-0.42,0.36-0.8,0.67-1.1c0.49-0.49,1.16-0.79,1.9-0.79c0.08,0,5.64,0,5.71,0"/>
<polyline id="frame_00000099648434244861981470000013376370528818678665_" class="st1" points="38.09,18.4 31.84,21.18
36.01,25.35 "/>
<path id="frame_00000128454345919547781650000004815678695790207374_" class="st1" d="M25.72,23.35c-3.21,2.29-4.3,5.62-4.46,6.13
c-0.07,0.22-0.53,1.85-0.53,3.51c0,6.52,5.29,11.81,11.81,11.81s11.81-5.29,11.81-11.81c0-6.48-5.33-11.81-11.81-11.81"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

29
icon/reset_settings.svg Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c, .d {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d, .e {
fill: none;
}
.d {
fill-rule: evenodd;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="e" width="64" height="64"/>
</g>
<g id="b" data-name="reset">
<path class="d" d="m23.51,17.36c-5.8,3.19-9.73,9.35-9.73,16.44,0,10.35,8.39,18.75,18.75,18.75s18.75-8.39,18.75-18.75-8.39-18.75-18.75-18.75"/>
<circle class="c" cx="32.59" cy="34.2" r="4.93"/>
<polyline class="d" points="41.36 10.64 31.43 15.06 38.05 21.67"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 777 B

View File

@ -1,19 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 -0.5 21 21" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<title>search_right [#1507]</title>
<desc>Created with Sketch.</desc>
<defs>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-179.000000, -280.000000)" fill="#000000">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M128.93985,132.929 L130.42455,134.343 L124.4847,140 L123,138.586 L128.93985,132.929 Z M136.65,132 C133.75515,132 131.4,129.757 131.4,127 C131.4,124.243 133.75515,122 136.65,122 C139.54485,122 141.9,124.243 141.9,127 C141.9,129.757 139.54485,132 136.65,132 L136.65,132 Z M136.65,120 C132.5907,120 129.3,123.134 129.3,127 C129.3,130.866 132.5907,134 136.65,134 C140.7093,134 144,130.866 144,127 C144,123.134 140.7093,120 136.65,120 L136.65,120 Z" id="search_right-[#1507]">
.c, .d {
fill: none;
}
</path>
</g>
</g>
</g>
.e {
fill: #d6d6d6;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="d" width="64" height="64"/>
</g>
<g id="b" data-name="search">
<path class="e" d="m38.57,14.45c5.98,0,10.85,4.87,10.85,10.85s-4.87,10.85-10.85,10.85-10.85-4.87-10.85-10.85,4.87-10.85,10.85-10.85m0-4c-8.2,0-14.85,6.65-14.85,14.85s6.65,14.85,14.85,14.85,14.85-6.65,14.85-14.85-6.65-14.85-14.85-14.85h0Z"/>
<line class="c" x1="26.98" y1="41.06" x2="16.3" y2="51.91"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 803 B

29
icon/selection-area_2.svg Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg fill="#000000" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="800px" height="800px" viewBox="0 0 68.334 68.334"
xml:space="preserve">
<g>
<path style="stroke: #ffffff; fill: #ffffff; stroke-width:0.5px" d="M65.333,60.834h3v7.5h-7.5v-3h4.5V60.834z M18.167,68.334h10.667v-3H18.167V68.334z M39.501,68.334h10.666v-3H39.501
V68.334z M3.001,60.834h-3v7.5h7.5v-3h-4.5V60.834z M3.001,39.5h-3v10.667h3V39.5z M3.001,18.167h-3v10.667h3V18.167z M0.001,7.5h3
V3h4.5V0h-7.5V7.5z M28.833,0H18.167v3h10.666V0z M50.167,0H39.5v3h10.667V0z M60.833,0v3h4.5v4.5h3V0H60.833z M65.333,50.167h3
V39.5h-3V50.167z M65.333,28.834h3V18.167h-3V28.834z" />
</g>
<g> <path style="stroke: #000000; fill: #ffffff; stroke-width:0.5px" d="M56.313,22.29l-9.031-3.358c-1.404-0.52-2.975,0.005-3.781,1.263l-5.052,7.87
c-0.947,1.478-0.519,3.443,0.959,4.391c0.428,0.275,0.897,0.435,1.372,0.485c1.16,0.125,2.346-0.395,3.019-1.444l1.566-2.441
c3.058,10.698-3.319,16.024-3.628,16.273c-1.357,1.095-1.592,3.088-0.508,4.456c0.551,0.697,1.334,1.104,2.154,1.193
c0.784,0.084,1.6-0.125,2.271-0.646c3.824-2.969,9.186-11.21,5.804-23.066l2.642,0.981c1.641,0.61,3.473-0.226,4.086-1.872
C58.798,24.731,57.96,22.901,56.313,22.29z M14.594,26.495l9.266,1.25c1.74,0.235,3.34-0.985,3.575-2.725
c0.067-0.504,0.013-0.998-0.142-1.449c-0.376-1.104-1.348-1.958-2.583-2.124l-2.876-0.389c8.403-7.292,15.926-3.765,16.281-3.59
c1.567,0.767,3.472,0.136,4.254-1.422c0.399-0.796,0.437-1.679,0.17-2.459c-0.255-0.747-0.788-1.397-1.546-1.787
c-4.309-2.209-14.043-3.583-23.357,4.493l-0.227-2.807c-0.141-1.746-1.674-3.053-3.424-2.912c-1.748,0.141-3.053,1.673-2.911,3.423
l0.775,9.604C11.973,25.094,13.111,26.296,14.594,26.495z M32.264,42.794c-0.65-1.63-2.5-2.422-4.132-1.771
c-0.472,0.188-0.874,0.479-1.191,0.836c-0.776,0.871-1.04,2.139-0.577,3.293l1.076,2.695c-10.482-3.726-11.114-12.01-11.138-12.406
c-0.104-1.741-1.591-3.089-3.331-3.002c-0.89,0.044-1.677,0.447-2.225,1.063c-0.523,0.59-0.828,1.374-0.795,2.225
c0.196,4.838,3.791,13.988,15.406,18.121l-2.332,1.579c-1.45,0.981-1.834,2.959-0.85,4.413c0.983,1.454,2.96,1.835,4.412,0.852
l7.979-5.402c1.24-0.842,1.724-2.425,1.169-3.812L32.264,42.794z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1 +1,27 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 112.04"><defs><style>.cls-1{fill-rule:evenodd;}</style></defs><title>writing</title><path class="cls-1" d="M109.28,19.61l12.21,9.88a3.77,3.77,0,0,1,.56,5.29l-5.46,6.75L98.53,26.93,104,20.17a3.79,3.79,0,0,1,5.29-.56ZM21.07,30.81a3.18,3.18,0,0,1,0-6.36H74.12a3.18,3.18,0,0,1,0,6.36ZM9.49,0H85.71A9.53,9.53,0,0,1,95.2,9.49v5.63l-4.48,5.53a9.81,9.81,0,0,0-1.18,1.85c-.24.19-.48.4-.71.62V9.49a3.14,3.14,0,0,0-3.12-3.13H9.49A3.14,3.14,0,0,0,6.36,9.49v93.06a3.16,3.16,0,0,0,.92,2.21,3.11,3.11,0,0,0,2.21.92H85.71a3.12,3.12,0,0,0,3.12-3.13V88.2l1.91-.81a10,10,0,0,0,4.34-3.13l.12-.14v18.43A9.54,9.54,0,0,1,85.71,112H9.49A9.51,9.51,0,0,1,0,102.55V9.49A9.53,9.53,0,0,1,9.49,0ZM21.07,87.6a3.19,3.19,0,0,1,0-6.37H56.19a37.1,37.1,0,0,0-.3,6.37Zm0-18.93a3.19,3.19,0,0,1,0-6.37H59.22l0,.27-1.05,6.1Zm0-18.93a3.18,3.18,0,0,1,0-6.36H72.44l-5.11,6.36ZM87.25,78,74.43,83.47c-9.35,3.47-8.93,5.43-8-3.85L69.24,63.4h0l0,0,26.56-33,18,14.6L87.27,78ZM72.31,65.89l11.86,9.59-8.42,3.6c-6.6,2.83-6.42,4.23-5.27-2.53l1.83-10.66Z"/></svg>
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<defs>
<style>
.c {
stroke: #d6d6d6;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 4px;
}
.c, .d {
fill: none;
}
</style>
</defs>
<g id="a" data-name="frame">
<rect class="d" width="64" height="64"/>
</g>
<g id="b" data-name="Promt">
<rect class="c" x="13.34" y="12.24" width="38.49" height="39.43" rx="2.69" ry="2.69"/>
<line class="c" x1="23.18" y1="42.71" x2="41.1" y2="42.71"/>
<line class="c" x1="23.18" y1="35.54" x2="41.1" y2="35.54"/>
<line class="c" x1="23.18" y1="28.37" x2="41.1" y2="28.37"/>
<line class="c" x1="23.18" y1="21.2" x2="41.1" y2="21.2"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 798 B

View File

@ -495,7 +495,7 @@
font-family: Arial, Verdana;
background-image: url(./icon/search.svg);
background-color: #777;
background-size: 20px;
background-size: 30px;
width: 30px;
height: 30px;
background-repeat: no-repeat;
@ -571,7 +571,7 @@
.resetButton {
font-family: Arial, Verdana;
background-image: url(./icon/reset_settings2.png);
background-image: url(./icon/reset_settings.svg);
background-color: #777;
background-size: 30px;
width: 30px;
@ -588,16 +588,26 @@
height: 30px;
background-repeat: no-repeat;
}
.svgButton {
font-family: Arial, Verdana;
background-color: #777;
background-size: 30px;
width: 30px;
height: 30px;
background-repeat: no-repeat;
}
.selectionAreaButton {
background-image: url(./icon/reselect-area.svg);
}
.interrogateButton {
font-family: Arial, Verdana;
background-image: url(./icon/writing-icon.svg);
/* background-color: transparent; */
background-size: 25px;
background-size: 30px;
width: 30px;
height: 30px;
background-repeat: no-repeat;
background-position: center;
background-position-x: 4px;
}
.svg-buttons-container *:not(:last-child) {
margin-right: 3px;
@ -655,7 +665,7 @@
.refreshButton {
font-family: Arial, Verdana;
background-image: url(./icon/reset_settings2.png);
background-image: url(./icon/reset_settings.svg);
background-color: #777;
background-size: 30px;
width: 30px;
@ -699,6 +709,19 @@
color: white;
} */
</style>
<style>
.second_panel {
flex: 1 1 auto;
/* overflow: scroll; */
overflow-y: scroll;
padding-left: 10px;
padding-right: 10px;
padding-top: 12px;
padding-bottom: 12px;
flex-direction: column;
height: 100%; /* Set a fixed height for the container */
}
</style>
<body>
<!-- <sp-textarea id="tool_tip" open placement="top">use this when you want to fill empty areas of the canvas</sp-textarea> -->
@ -862,15 +885,24 @@
</div>
<div></div>
<div>
<button class="btnSquare" id="btnSetMaskViewer">
<button
class="btnSquare"
id="btnSetMaskViewer"
style="display: none"
>
Set Mask
</button>
<button class="btnSquare" id="btnSetInitImageViewer">
<button
class="btnSquare"
id="btnSetInitImageViewer"
style="display: none"
>
Set Init Image
</button>
<button
class="btnSquare btnGenerateClass"
id="btnGenerate"
style="display: none"
>
Generate txt2img
</button>
@ -881,9 +913,6 @@
>
Interrupt
</button>
<button class="btnSquare" id="btnSelectionArea">
Selection Area
</button>
<div
style="
display: flex;
@ -891,10 +920,6 @@
padding-top: 3px;
"
>
<!-- <button class="btnSquare acceptClass acceptAllImgBtn" style="display:none;">Accept All</button>
<button class="btnSquare discardClass discardAllImgBtn" style="display:none;">Discard All</button>
<button class="btnSquare acceptSelectedClass acceptSelectedImgBtn" style="display:none;">Accept Selected</button>
<button class="btnSquare discardSelectedClass discardSelectedImgBtn" style="display:none;">Discard Selected</button> -->
<button
title="Keep all generated images on the canvas"
class="btnSquare acceptClass acceptAllImgBtn"
@ -990,308 +1015,7 @@
</div>
<!-- </div> -->
</div>
<div class="sp-tab-page" id="sp-control_net-tab-page">
<!-- control net tab page -->
<sp-picker
title="auto fill the ControlNet with smart settings, to speed up your working process."
size="m"
label="ControlNet Preset"
>
<sp-menu id="mControlNetPresetMenu" slot="options">
<!-- <sp-menu-item> item </sp-menu-item> -->
</sp-menu>
</sp-picker>
<div></div>
<sp-label
id="controlnetMissingError"
style="color: #ff595e; display: none; white-space: normal"
>
The Controlnet Extension is missing from Automatic1111.
Please install it to use it through the plugin.
</sp-label>
<div
id="control_net_image_container"
class="imgContainer controlNetImaageContainer"
>
<!-- <button class="column-item button-style" id="btnSnapAndFill">Snap And Fill</button> -->
<!--
<div>
<img
id="all_control_net_image"
class="column-item-image"
src="https://source.unsplash.com/random"
width="300px"
height="100px"
/>
</div> -->
<div class="imgButton">
<button
class="column-item button-style btnSquare"
id="bSetAllControlImage"
>
Set All CtrlNet Images
</button>
</div>
<sp-checkbox id="chDisableControlNetTab"
>Disable ControlNet Tab</sp-checkbox
>
<!-- <span>
version:<span id="ControlNetVersion">0.0.0</span>
</span> -->
</div>
<div
id="controlnet_settings_template"
data-index="0"
style="display: none"
>
<div class="flexContainer">
<sp-label slot="label " class="controlnet_unit_label_"
>Control Net Settings Slot 0</sp-label
>
</div>
<div style="display: flex">
<div
id="control_net_image_container_0"
class="imgContainer controlNetImaageContainer"
>
<!-- <button class="column-item button-style" id="btnSnapAndFill">Snap And Fill</button> -->
<div>
<img
id="control_net_image_0"
class="column-item-image control_net_image_"
src="https://source.unsplash.com/random"
width="300px"
height="100px"
/>
</div>
<div class="imgButton">
<button
class="column-item button-style btnSquare bSetControlImage_"
id="bSetControlImage_0"
>
Set CtrlNet Img
</button>
</div>
</div>
<div
id="control_net_mask_container_0"
class="imgContainer controlNetImaageContainer"
>
<!-- <button class="column-item button-style" id="btnSnapAndFill">Snap And Fill</button> -->
<div>
<img
id="control_net_mask_0"
class="column-item-image control_net_mask_"
src="https://source.unsplash.com/random"
width="300px"
height="100px"
/>
</div>
<div class="imgButton btnClass">
<button
class="column-item button-style btnSquare bControlMask_"
id="bControlMask_0"
>
Preview Annotator
</button>
</div>
</div>
</div>
<sp-checkbox
id="chEnableControlNet_0"
class="chEnableControlNet_"
>Enable</sp-checkbox
>
<sp-checkbox checked id="chlowVram_0" class="chlowVram_"
>Low VRAM</sp-checkbox
>
<!-- <sp-checkbox id="chGuessMode_0" class="chGuessMode_"
>Guess Mode</sp-checkbox
> -->
<sp-checkbox class="chPixelPerfect_"
>Pixel Perfect</sp-checkbox
>
<div>
<sp-radio-group class="rgControlNetMode_">
<sp-label slot="label">Control Mode:</sp-label>
<!-- <sp-label slot="label">Select a Mode:</sp-label> -->
<sp-radio title="" value="0" checked
>Balanced</sp-radio
>
<sp-radio
title="My Prompt is More Important"
value="1"
>Prompt</sp-radio
><sp-radio
title="ControlNet is More Important"
value="2"
>ControlNet</sp-radio
>
</sp-radio-group>
</div>
<div>
<div>
<sp-slider
show-value="false"
id="slControlNetWeight_0"
class="slControlNetWeight_"
min="0"
max="100"
value="50"
title="2 will keep the composition; 0 will allow composition to change"
>
<sp-label slot="label">Weight:</sp-label>
<sp-label
slot="label"
id="lControlNetWeight_0"
class="lControlNetWeight_"
>1.0</sp-label
>
</sp-slider>
<sp-slider
show-value="false"
id="slControlNetGuidanceStrengthStart_0"
class="slControlNetGuidanceStrengthStart_"
min="0"
max="100"
value="0"
>
<sp-label slot="label"
>Guidance strength start:</sp-label
>
<sp-label
slot="label"
id="lControlNetGuidanceStrengthStart_0"
class="lControlNetGuidanceStrengthStart_"
>0.0</sp-label
>
</sp-slider>
<sp-slider
show-value="false"
id="slControlNetGuidanceStrengthEnd_0"
class="slControlNetGuidanceStrengthEnd_"
min="0"
max="100"
value="100"
>
<sp-label slot="label"
>Guidance strength end:</sp-label
>
<sp-label
slot="label"
id="lControlNetGuidanceStrengthEnd_0"
class="lControlNetGuidanceStrengthEnd_"
>1.0</sp-label
>
</sp-slider>
<sp-slider
show-value="true"
id="slControlNetProcessorRes_0"
class="slControlNetProcessorRes_"
style="display: none"
min="64"
max="2048"
value="512"
>
<sp-label
slot="label"
class="labelControlNetProcessorRes_"
>Annotator resolution</sp-label
>
<!-- <sp-label
slot="label"
id="lControlNetProcessorRes_0"
class="lControlNetProcessorRes_"
>1.0</sp-label
> -->
</sp-slider>
<sp-slider
show-value="false"
class="slControlNetThreshold_A_"
style="display: none"
min="0"
max="100"
value="50"
data-sd_min="64"
data-sd_max="1024"
>
<sp-label
slot="label"
class="labelControlNetThreshold_A_"
>Threshold A:
</sp-label>
<sp-label
slot="label"
class="lControlNetThreshold_A_"
>0.0</sp-label
>
</sp-slider>
<sp-slider
show-value="false"
class="slControlNetThreshold_B_"
style="display: none"
min="0"
max="100"
value="50"
data-sd_min="64"
data-sd_max="1024"
>
<sp-label
slot="label"
class="labelControlNetThreshold_B_"
>Threshold B:
</sp-label>
<sp-label
slot="label"
class="lControlNetThreshold_B_"
>0.0</sp-label
>
</sp-slider>
</div>
</div>
<div id="menu-bar-control_net_0" style="display: flex">
<sp-picker size="m" label="Select Module">
<sp-menu
id="mModulesMenuControlNet_0"
class="mModulesMenuControlNet_"
slot="options"
>
<!-- <sp-menu-item> item </sp-menu-item> -->
</sp-menu>
</sp-picker>
<sp-picker size="m" label="Selection type">
<sp-menu
id="mModelsMenuControlNet_0"
class="mModelsMenuControlNet_"
slot="options"
>
<!-- <sp-menu-item> item </sp-menu-item> -->
</sp-menu>
</sp-picker>
<!-- <button class="btnSquare" id="btnRefreshModelsHorde">
Refresh Models
</button> -->
</div>
<div></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div></div>
<div>
<sp-divider
class="line-divider"
size="large"
></sp-divider>
</div>
</div>
<div id="controlnet_parent_container"></div>
</div>
<div class="sp-tab-page" id="sp-control_net-tab-page"></div>
<div class="sp-tab-page" id="sp-history-tab-page">
<div class="subTabOptionsContainer"></div>
@ -1547,7 +1271,8 @@
>
</sp-slider>
</div>
<div
<div id="extraGenerateButtonsContainer"></div>
<!-- <div
style="
display: inline-flex;
justify-content: flex-start;
@ -1557,6 +1282,7 @@
<button
class="btnSquare btnGenerateClass"
id="btnGenerateUpscale"
style="display: none"
>
Generate upscale
</button>
@ -1578,7 +1304,6 @@
></button>
<button
class="btnSquare snapshotButton"
id="btnSnapshotUpscale"
style="margin-right: 3px"
title="create a snapshot of what you see on the canvas and place on a new layer"
></button>
@ -1588,7 +1313,7 @@
title="reset the ui settings to their default values"
></button>
</div>
</div>
</div> -->
<div
id="progressContainerUpscale"
@ -1600,7 +1325,9 @@
>
</div>
<div style="padding-bottom: 5px">
<sp-label style="width: 60px; display: inline-block"
<sp-label
class="title"
style="width: 60px; display: inline-block"
>Upscaler 1:
</sp-label>
<sp-picker size="m" label="Upscaler Options">
@ -1613,7 +1340,9 @@
</sp-menu>
</sp-picker>
<div></div>
<sp-label style="width: 60px; display: inline-block"
<sp-label
class="title"
style="width: 60px; display: inline-block"
>Upscaler 2:
</sp-label>
<sp-picker size="m" label="Upscaler Options">
@ -1637,6 +1366,7 @@
value="0"
>
<sp-label
class="title"
style="width: 110px; display: inline-block"
slot="label"
>Upscaler 2 visibility:
@ -1661,6 +1391,7 @@
value="0"
>
<sp-label
class="title"
style="width: 110px; display: inline-block"
slot="label"
>GFPGAN visibility:
@ -1685,6 +1416,7 @@
value="0"
>
<sp-label
class="title"
style="width: 110px; display: inline-block"
slot="label"
>CodeFormer visibility:
@ -1709,6 +1441,7 @@
value="0"
>
<sp-label
class="title"
style="width: 110px; display: inline-block"
slot="label"
>CodeFormer weight:
@ -1853,6 +1586,7 @@
</button>
</div>
<div id="settingsVAEContainer"></div>
<div id="reactSettingsContainer"></div>
<sp-checkbox id="chUseSharpMask">use sharp mask</sp-checkbox>
<sp-checkbox checked id="chUseSmartObject"
>Smart Object</sp-checkbox
@ -1976,12 +1710,6 @@
<!-- stable diffusion tab page -->
<div id="sdBtnContainer">
<!-- <button
class="btnSquare layerToSelection"
id="btnLayerToSelection"
style="margin-right: 3px"
title="Move and reSize the highlighted layer to fit into the Selection Area "
></button> -->
<sp-picker
title="use lora in your prompt"
size="m"
@ -2003,7 +1731,8 @@
style="
margin-top: 1px;
margin-bottom: 3px;
display: inline-block;
/* display: inline-block; */
display: none;
"
>
Generate txt2img
@ -2076,7 +1805,7 @@
class="chLiveProgressImageClass"
/>
</div>
<div
<!-- <div
class="svg-buttons-container"
style="
display: inline-flex;
@ -2096,13 +1825,11 @@
></button>
<button
class="btnSquare layerToSelection"
id="btnLayerToSelection"
class="btnSquare layerToSelection btnLayerToSelection"
title="Move and reSize the highlighted layer to fit into the Selection Area "
></button>
<button
class="btnSquare snapshotButton"
id="btnSnapshot"
title="create a snapshot of what you see on the canvas and place on a new layer"
></button>
<button
@ -2115,7 +1842,7 @@
id="btnInterrogate"
title="Interrogate the selected area, convert Image to Prompt"
></button>
</div>
</div> -->
<!-- <sp-picker
title="use lora in your prompt"
size="s"
@ -2133,6 +1860,7 @@
>prompt shortcut</sp-checkbox
> -->
</div>
<div id="generateButtonsContainer"></div>
<div>
<!-- icon only svg button -->
<sp-action-button
@ -2403,10 +2131,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
</div>
</sp-action-button>
</div>
<img
id="webp_container"
src="https://www.keycdn.com/img/blog/convert-to-webp-the-successor-of-jpeg-lg.webp"
/>
<!-- <custom-element>hello custom element</custom-element> -->
<div class="prompts">
<!-- <sp-label slot="label">Prompt</sp-label> -->
@ -2462,6 +2187,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
>
<!-- <sp-tooltip id="tool_tip" open placement="top">use this when you want to fill empty areas of the canvas</sp-tooltip> -->
</sp-radio-group>
<div id="reactModesContainer"></div>
<div id="image_viewer">
<div class="imgButton">
@ -2566,7 +2292,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
></sp-textfield>
</div>
<div
id="batchNumberSdUiTabContainer"
id="batchCountSdUiTabContainer"
style="
width: 20%;
display: flex;
@ -2592,7 +2318,8 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
align-items: flex-start;
"
>
<sp-label>Steps:</sp-label
<sp-label id="sdLabelSampleStep"
>Sampling Steps</sp-label
><sp-textfield
style="width: 100%"
title="the higher the steps the longer it will take to generate an image"
@ -2662,7 +2389,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
value="8"
data-old_value="512"
>
<sp-label slot="label">Width:</sp-label>
<sp-label slot="label" class="title"
>Width:</sp-label
>
<sp-label
class="labelNumber"
slot="label"
@ -2684,7 +2413,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
value="8"
data-old_value="512"
>
<sp-label slot="label">Height:</sp-label>
<sp-label slot="label" class="title"
>Height:</sp-label
>
<sp-label
class="labelNumber"
slot="label"
@ -2724,7 +2455,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
max="30"
value="7"
>
<sp-label slot="label">CFG Scale:</sp-label>
<sp-label slot="label" class="title"
>CFG Scale:</sp-label
>
<!-- <sp-label slot="label" id="lCfgScale">7</sp-label> -->
</sp-slider>
@ -2735,7 +2468,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
max="100"
value="70"
>
<sp-label slot="label"
<sp-label slot="label" class="title"
>Denoising Strength:</sp-label
>
<sp-label slot="label" id="lDenoisingStrength"
@ -2752,7 +2485,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
value="15"
style="display: none"
>
<sp-label slot="label">Image CFG Scale:</sp-label>
<sp-label slot="label" class="title"
>Image CFG Scale:</sp-label
>
<sp-label slot="label" id="lImageCfgScale"
>1.5</sp-label
>
@ -2763,7 +2498,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
id="slMaskBlur"
min="0"
max="64"
value="7"
value="0"
>
<sp-label slot="label">Mask Blur:</sp-label>
<!-- <sp-label slot="label" id="lDenoisingStrength">0.7</sp-label> -->
@ -2773,7 +2508,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
id="slMaskExpansion"
min="0"
max="256"
value="40"
value="0"
title="the larger the value the more the mask will expand, '0' means use precise masking, use in combination with the mask blur"
>
<sp-label slot="label">Mask Expansion:</sp-label>
@ -2789,7 +2524,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
value="100"
title="0 will keep the composition; 1 will allow composition to change"
>
<sp-label slot="label"
<sp-label slot="label" class="title"
>Inpainting conditioning mask
strength:</sp-label
>
@ -2803,7 +2538,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
<div id="slInpainting_fill">
<sp-radio-group id="Inpainting_fill_group" class="">
<sp-label slot="label">Mask Content:</sp-label>
<sp-label class="title" slot="label"
>Mask Content:</sp-label
>
<sp-radio
class="rbMaskContent"
checked
@ -2836,7 +2573,9 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
<div id="HiResDiv" style="display: none">
<div style="display: flex">
<div>
<sp-label>Upscaler: </sp-label>
<sp-label class="title"
>Upscaler:
</sp-label>
<sp-picker
size="m"
label="Upscaler Options"
@ -2851,7 +2590,8 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
</sp-picker>
</div>
<div>
<sp-label>Hi Res Steps:</sp-label
<sp-label id="HiResStep"
>Hi Res Steps:</sp-label
><sp-textfield
id="hrNumberOfSteps"
type="number"
@ -2872,7 +2612,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
max="100"
value="50"
>
<sp-label slot="label"
<sp-label slot="label" class="title"
>Hi Res Scale:</sp-label
>
<sp-label
@ -2896,7 +2636,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
max="100"
value="70"
>
<sp-label slot="label">
<sp-label slot="label" class="title">
High Res Denoising Strength:</sp-label
>
<sp-label
@ -2915,7 +2655,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
value="8"
style="display: none"
>
<sp-label slot="label"
<sp-label slot="label" class="title"
>Hi Res Output Width:</sp-label
>
<sp-label
@ -2970,7 +2710,7 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
<div>
<!-- <sp-checkbox id="chHiResFixs">Hi Res Fix</sp-checkbox> -->
<div style="display: flex">
<sp-label>Seed:</sp-label
<sp-label id="sdLabelSeed">Seed:</sp-label
><sp-textfield
id="tiSeed"
placeholder="Seed"
@ -3057,8 +2797,106 @@ l32 44 -47 62 c-67 90 -253 281 -358 368 -248 208 -538 351 -802 397 -90 16
<sp-divider class="line-divider" size="large"></sp-divider>
<div style="margin-bottom: 3px"></div>
<div id="scriptsContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div class="reactViewerContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div class="previewContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div class="samContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div class="oneButtonPromptContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
</div>
</div>
</div>
<uxp-panel panelid="second_panel">
<div id="search_second_panel" class="container second_panel">
<div class="previewContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div class="reactViewerContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div id="sp-control_net-tab-page2"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
</div>
</uxp-panel>
<uxp-panel panelid="toolbar">
<div
id="toolbar_panel"
class="_container"
style="
margin: 0;
display: flex;
justify-content: flex-start;
/* padding-bottom: 5px; */
flex-direction: column;
"
>
<div
style="
margin: 0;
display: flex;
justify-content: flex-start;
/* padding: 1px; */
flex-direction: column;
"
>
<div id="toolbarGenerateButtonsContainer"></div>
<div id="viewerButtonContainer"></div>
<div>
<button
class="btnSquare layerToSelection btnLayerToSelection"
title="Move and reSize the highlighted layer to fit into the Selection Area "
style="margin-right: 3px"
></button>
<button
class="btnSquare snapshotButton"
title="create a snapshot of what you see on the canvas and place on a new layer"
style="margin-right: 3px"
></button>
<button
class="btnSquare resetButton"
id="btnResetSettings"
title="reset the ui settings to their default values"
style="margin-right: 3px"
></button>
<button
class="btnSquare interrogateButton"
id="btnInterrogate"
title="Interrogate the selected area, convert Image to Prompt"
style="margin-right: 3px"
></button>
<button
class="btnSquare svgButton selectionAreaButton"
id="btnSelectionArea"
style="margin-right: 3px"
title="Reselect the selection area for the current session"
></button>
<button
id="scrollToPrompt"
class="btnSquare svgButton"
title="Quickly jump to the preview section"
>
P
</button>
<div id="scrollToControlNetUnitContainer"></div>
<!-- <button id="scrollToViewer" class="btnSquare svgButton">
V
</button> -->
</div>
</div>
</div>
</uxp-panel>
</body>
</html>

1204
index.js

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
export * as ultimate_sd_upscaler from '../../ultimate_sd_upscaler/src/ultimate_sd_upscaler'
export * as after_detailer_script from '../../after_detailer/src/after_detailer'
export * as scripts from '../../ultimate_sd_upscaler/src/scripts'
export * as main from './main'

View File

@ -1,5 +0,0 @@
declare module 'sdapi_py_re' {
const exports: any;
export = exports;
}

5
main/types/uxp.d.ts vendored
View File

@ -1,5 +0,0 @@
declare module 'uxp' {
// Add type declarations for the uxp module here
export const storage: any;
export const versions: any;
}

View File

@ -36,11 +36,12 @@
"entrypoints": [
{
"type": "panel",
"id": "vanilla",
"id": "main_panel",
"label": {
"default": "Auto-Photoshop-SD",
"en-US": "Auto-Photoshop-SD",
"es-ES": "Auto-Photoshop-SD"
"es-ES": "Auto-Photoshop-SD",
"zh-CN": "SD插件明空汉化"
},
"minimumSize": {
"width": 400,
@ -85,6 +86,111 @@
]
}
]
},
{
"type": "panel",
"id": "second_panel",
"label": {
"default": "Second Auto-Photoshop-SD",
"en-US": "Second Auto-Photoshop-SD",
"es-ES": "Second Auto-Photoshop-SD",
"zh-CN": "SD插件明空汉化"
},
"minimumSize": {
"width": 100,
"height": 100
},
"maximumSize": {
"width": 1200,
"height": 10000
},
"preferredDockedSize": {
"width": 150,
"height": 800
},
"preferredFloatingSize": {
"width": 300,
"height": 800
},
"commands": [
{
"id": "show_alert",
"label": {
"default": "Show Alert",
"en-US": "Show Alert (US)",
"es-ES": "Show Alert (ES)"
}
}
],
"icons": [
{
"width": 23,
"height": 23,
"path": "icon/panel.png",
"scale": [
1,
2
],
"theme": [
"all"
],
"species": [
"chrome"
]
}
]
},
{
"type": "panel",
"id": "toolbar",
"label": {
"default": "toolbar Auto-Photoshop-SD",
"en-US": "toolbar Auto-Photoshop-SD",
"es-ES": "toolbar Auto-Photoshop-SD"
},
"minimumSize": {
"width": 30,
"height": 100
},
"maximumSize": {
"width": 1200,
"height": 10000
},
"preferredDockedSize": {
"width": 30,
"height": 800
},
"preferredFloatingSize": {
"width": 30,
"height": 800
},
"commands": [
{
"id": "show_alert",
"label": {
"default": "Show Alert",
"en-US": "Show Alert (US)",
"es-ES": "Show Alert (ES)"
}
}
],
"icons": [
{
"width": 23,
"height": 23,
"path": "icon/panel.png",
"scale": [
1,
2
],
"theme": [
"all"
],
"species": [
"chrome"
]
}
]
}
],
"icons": [

4611
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,10 @@
"author": "Adobe Inc",
"license": "Apache-2.0",
"dependencies": {
"@types/photoshop": "^24.5.1",
"@types/react": "^18.2.6",
"@types/react-dom": "^18.2.4",
"changedpi": "^1.0.4",
"fastify": "^4.10.2",
"jimp": "^0.16.2",
"madge": "^6.0.0",
@ -14,7 +16,8 @@
"mobx": "^6.9.0",
"mobx-react": "^7.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"util": "^0.12.5"
},
"main": "index.js",
"directories": {
@ -25,6 +28,7 @@
"@babel/plugin-proposal-object-rest-spread": "^7.20.7",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-transform-react-jsx": "^7.21.5",
"@svgr/webpack": "^8.0.1",
"babel-loader": "^9.1.2",
"clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0",
@ -36,13 +40,15 @@
"terser-webpack-plugin": "^3.0.8",
"ts-loader": "^9.4.2",
"typescript": "^5.0.4",
"url-loader": "^4.1.1",
"webpack": "^5.82.1",
"webpack-cli": "^5.1.1"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"watch": "npx webpack --config webpack.config.js --watch",
"build": "npx webpack --config webpack.config.js"
"build": "npx webpack --config webpack.config.js",
"format": "npx prettier -w typescripts"
},
"repository": {
"type": "git",
@ -53,4 +59,4 @@
"url": "https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin/issues"
},
"homepage": "https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin#readme"
}
}

View File

@ -1,6 +1,8 @@
const app = window.require('photoshop').app
const batchPlay = require('photoshop').action.batchPlay
const { executeAsModal } = require('photoshop').core
const constants = require('photoshop').constants
// console.log(constants.ResampleMethod.BILINEAR)
// const export_png = require('./export_png')
// const { layerToSelection } = require('./helper')
@ -643,7 +645,7 @@ async function snapshot_layerExe() {
await snapshot_layer_new()
},
{
commandName: 'Action Commands',
commandName: `Canvas Snapshot...`,
}
)
} catch (e) {
@ -1400,7 +1402,12 @@ async function layerToSelection(selection_info) {
scale_x_ratio = (selection_info.width / layer_info.width) * 100
scale_y_ratio = (selection_info.height / layer_info.height) * 100
console.log('scale_x_y_ratio:', scale_x_ratio, scale_y_ratio)
await layer.scale(scale_x_ratio, scale_y_ratio)
// await layer.scale(
// scale_x_ratio,
// scale_y_ratio,
// constants.ResampleMethod.BILINEAR
// )
await layer_util.Layer.resizeImageExe(scale_x_ratio, scale_y_ratio)
}
async function moveLayerExe(layerToMove, selection_info) {

View File

@ -67,13 +67,14 @@ async def maskExpansionHandler(request:Request):
# keywords = json.get('keywords','cute dogs')
base64_mask_image = json['mask']
mask_expansion = json['mask_expansion']
blur = json['blur']
#convert base64 to img
await img2imgapi.base64ToPng(base64_mask_image,"original_mask.png")#save a copy of the mask
mask_image = img2imgapi.b64_2_img(base64_mask_image)
expanded_mask_img = img2imgapi.maskExpansion(mask_image,mask_expansion)
expanded_mask_img = img2imgapi.maskExpansion(mask_image,mask_expansion,blur)
base64_expanded_mask_image = img2imgapi.img_2_b64(expanded_mask_img)
await img2imgapi.base64ToPng(base64_expanded_mask_image,"expanded_mask.png")#save a copy of the mask

View File

@ -3,12 +3,9 @@ const { base64ToBase64Url } = require('./utility/general')
const { getExtensionType } = require('./utility/html_manip')
const py_re = require('./utility/sdapi/python_replacement')
const Enum = require('./enum')
// const control_net = require('./utility/tab/control_net')
const {
mapPluginSettingsToControlNet,
getEnableControlNet,
getModuleDetail,
} = require('./utility/tab/control_net')
const { control_net } = require('./typescripts/dist/bundle')
const { mapPluginSettingsToControlNet, getEnableControlNet, getModuleDetail } =
control_net
const api = require('./utility/api')
//javascript plugin can't read images from local directory so we send a request to local server to read the image file and send it back to plugin as image string base64
@ -21,7 +18,7 @@ async function requestSavePng(base64_image, image_name) {
const uniqueDocumentId = await getUniqueDocumentId()
const folder = `${uniqueDocumentId}/init_images`
const init_entry = await getInitImagesDir()
saveFileInSubFolder(base64_image, folder, image_name)
io.saveFileInSubFolder(base64_image, folder, image_name)
console.warn('this function is deprecated')
} catch (e) {
console.warn(e)
@ -42,6 +39,7 @@ async function requestTxt2Img(payload) {
}
}
//Refactor: move to modes.ts
async function requestImg2Img(payload) {
console.log('requestImg2Img(): about to send a fetch request')
try {
@ -56,21 +54,22 @@ async function requestImg2Img(payload) {
}
}
//Refactor: move to progress.ts
async function requestProgress() {
let json = {}
try {
console.log('requestProgress: ')
const full_url = `${g_sd_url}/sdapi/v1/progress?skip_current_image=false`
let request = await fetch(full_url)
json = await request.json()
console.log('progress json:', json)
// console.log('progress json:', json)
return json
} catch (e) {
console.warn(e)
// console.log('json: ', json)
}
return null
}
async function requestGetModels() {
@ -96,7 +95,7 @@ async function requestGetSamplers() {
const full_url = `${g_sd_url}/sdapi/v1/samplers`
let request = await fetch(full_url)
json = await request.json()
console.log('samplers json:', json)
// console.log('samplers json:', json)
} catch (e) {
console.warn(e)
}
@ -210,14 +209,12 @@ async function loadPromptShortcut() {
prompt_shortcut_json = await py_re.loadPromptShortcut(
'prompt_shortcut.json'
)
console.log('loadPromptShortcut:', prompt_shortcut_json)
// console.log('loadPromptShortcut: request: ',request)
// console.log('loadPromptShortcut:', prompt_shortcut_json)
} catch (e) {
console.warn(e)
prompt_shortcut_json = {}
}
return prompt_shortcut_json
// return json['prompt_shortcut']
}
async function savePromptShortcut(prompt_shortcut) {
@ -483,11 +480,13 @@ async function requestControlNetTxt2Img(plugin_settings) {
app.showAlert('you need to select a valid ControlNet Module')
throw 'you need to select a valid ControlNet Module'
}
if (
!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free) ||
control_net_settings['controlnet_units'][index]['model'] === 'none'
) {
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
@ -523,10 +522,7 @@ async function requestControlNetTxt2Img(plugin_settings) {
mask_index >= numberOfAnnotations
)
continue
html_manip.setControlMaskSrc(
base64ToBase64Url(base64_mask[mask_index]),
index
)
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}
@ -567,10 +563,11 @@ async function requestControlNetImg2Img(plugin_settings) {
throw 'you need to select a valid ControlNet Module'
}
if (
!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free) ||
control_net_settings['controlnet_units'][index]['model'] === 'none'
) {
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
@ -615,10 +612,7 @@ async function requestControlNetImg2Img(plugin_settings) {
mask_index >= numberOfAnnotations
)
continue
html_manip.setControlMaskSrc(
base64ToBase64Url(base64_mask[mask_index]),
index
)
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}

View File

@ -1,4 +1,5 @@
const psapi = require('./psapi')
const html_manip = require('./utility/html_manip')
function finalWidthHeight(
selectionWidth,
@ -125,33 +126,103 @@ async function createChannelIfNotExists(channelName) {
}
}
const makeMaskChannel = () => ({
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: 'inpaint_mask' },
const deleteChannel = (channel_name = 'mask') =>
app.activeDocument.channels.getByName(channel_name)
? [
{
_obj: 'delete',
_target: { _ref: 'channel', _name: channel_name },
options: {
failOnMissingProperty: false,
failOnMissingElement: false,
},
},
]
: []
const makeMaskChannel = (channel_name = 'mask') => ({
// _obj: 'set',
// _target: { _ref: 'channel', _property: 'selection' },
// to: { _ref: 'channel', _name: channel_name },
_obj: 'duplicate',
_target: [
{
_ref: 'channel',
_property: 'selection',
},
],
name: channel_name,
_isCommand: true,
options: { failOnMissingProperty: false, failOnMissingElement: false },
})
async function makeMaskChannelExe() {
async function makeMaskChannelExe(channel_name = 'mask') {
await executeAsModal(async () => {
// const channel = app.activeDocument.channels.getByName(channel_name)
// channel?.remove()
const result = await batchPlay(
[...deleteChannel(channel_name), makeMaskChannel(channel_name)],
// [
// {
// _obj: 'duplicate',
// _target: [
// {
// _ref: 'channel',
// _property: 'selection',
// },
// ],
// name: channel_name,
// _isCommand: true,
// },
// {
// _obj: 'make',
// new: { _class: 'channel' },
// at: { _ref: 'channel', _enum: 'channel', _value: 'mask' },
// using: {
// _enum: 'userMaskEnabled',
// _value: 'revealSelection',
// },
// },
// ],
{
synchronousExecution: true,
modalBehavior: 'execute',
}
)
console.log('result: ', result)
})
}
async function multiGetExe() {
desc = {
_obj: 'multiGet',
_target: { _ref: 'layer', _enum: 'ordinal', _value: 'targetEnum' },
extendedReference: [['layerID', 'itemIndex', 'count']],
options: { failOnMissingProperty: false, failOnMissingElement: false },
}
try {
const result = await batchPlay([desc], {
modalBehavior: 'execute',
synchronousExecution: true,
})
console.log('multiGetExe result: ', result)
// await executeAsModal(async () => {})
} catch (e) {
console.warn(e)
}
}
async function applyMaskChannelExe(channel_name = 'mask') {
await executeAsModal(async () => {
const result = await batchPlay(
[
// SelectionInfoDesc(),
// makeMaskChannel(),
{
_obj: 'duplicate',
_target: [
{
_ref: 'channel',
_property: 'selection',
},
],
name: 'inpaint_mask_2',
_isCommand: true,
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: channel_name },
},
// {
// _obj: 'set',
// _target: { _ref: 'channel', _property: 'selection' },
// to: { _ref: 'channel', _name: 'inpaint_mask' },
// },
{
_obj: 'make',
new: { _class: 'channel' },
@ -159,6 +230,7 @@ async function makeMaskChannelExe() {
using: {
_enum: 'userMaskEnabled',
_value: 'revealSelection',
_name: channel_name,
},
},
],
@ -171,9 +243,151 @@ async function makeMaskChannelExe() {
})
}
async function selectionToChannel(channel_name) {
const channelToSelection = {
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: channel_name },
}
let result
try {
await executeAsModal(async () => {
result = await batchPlay(
[
...deleteChannel(channel_name),
makeMaskChannel(channel_name),
channelToSelection,
],
{ modalBehavior: 'execute', synchronousExecution: true }
)
})
} catch (e) {
console.warn(e)
}
return result
}
async function createLayerFromMaskChannel(channel_name = 'mask') {
await executeAsModal(async () => {
const result = await batchPlay(
[
// SelectionInfoDesc(),
// makeMaskChannel(),
// {
// _obj: 'set',
// _target: { _ref: 'channel', _property: 'selection' },
// to: { _ref: 'channel', _name: channel_name },
// },
// {
// _obj: 'make',
// new: { _class: 'channel' },
// at: { _ref: 'channel', _enum: 'channel', _value: 'mask' },
// using: {
// _enum: 'userMaskEnabled',
// _value: 'revealSelection',
// _name: channel_name,
// },
// },
{
_obj: 'set',
_target: { _ref: 'layer' },
to: { _ref: 'channel', _name: channel_name },
},
],
{
synchronousExecution: true,
modalBehavior: 'execute',
}
)
console.log('result: ', result)
})
}
async function channelToSelectionExe(channel_name = 'mask') {
const channelToSelection = {
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: channel_name },
}
try {
await executeAsModal(async () => {
const result = await batchPlay([channelToSelection], {
modalBehavior: 'execute',
synchronousExecution: true,
})
})
} catch (e) {
console.warn(e)
}
}
async function inpaintLassoInitImageAndMask(channel_name = 'mask') {
async function getImageFromCanvas() {
const width = html_manip.getWidth()
const height = html_manip.getHeight()
const selectionInfo = await psapi.getSelectionInfoExe()
const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
width,
height,
selectionInfo,
true
)
return base64
}
const channelToSelection = {
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: channel_name },
}
// await executeAsModal(async () => {
// const result = await batchPlay(
// [
// ...deleteChannel(channel_name),
// makeMaskChannel(channel_name),
// channelToSelection,
// ],
// { modalBehavior: 'execute', synchronousExecution: true }
// )
// const selection_info = await psapi.getSelectionInfoExe()
// })
await selectionToChannel('mask') //lasso selection to channel called 'mask'
const init_base64 = await getImageFromCanvas()
html_manip.setInitImageSrc(general.base64ToBase64Url(init_base64))
let mask_base64
await executeAsModal(async () => {
const result = await batchPlay([channelToSelection], {
modalBehavior: 'execute',
synchronousExecution: true,
})
// const selection_info = await psapi.getSelectionInfoExe()
mask_base64 = await fillSelectionWhiteOutsideBlack()
})
//save laso selection to channel
//get laso selection
//make rect selection
//base64 from selection
//and display it in init image html element
//get laso selection:
//make mask layer
//get rectangular selection
//get base64 from selection
//display it in mask image html element
// let jimp_mask = await Jimp.read(Buffer.from(mask_base64, 'base64'))
// html_manip.setInitImageMaskSrc(
// await jimp_mask.getBase64Async(Jimp.MIME_PNG)
// )
html_manip.setInitImageMaskSrc(general.base64ToBase64Url(mask_base64))
return [init_base64, mask_base64]
// //return
// //init image
//mask
}
async function fillSelectionWhiteOutsideBlack() {
// Create a new layer
const layer_name = 'inpaint_laso_mask'
const layer_name = 'mask'
const getSelectionDesc = () => ({
_obj: 'get',
_target: [
@ -193,6 +407,9 @@ async function fillSelectionWhiteOutsideBlack() {
_obj: 'inverse',
_isCommand: true,
})
await psapi.unselectActiveLayers()
const mask_layer = await layer_util.createNewLayerExe('mask')
await moveToTopLayerStackExe()
await batchPlay(
[
getSelectionDesc(),
@ -295,73 +512,17 @@ async function fillSelectionWhiteOutsideBlack() {
)
//import the base64 image into the canvas as a new layer
await io.IO.base64ToLayer(
base64,
'base64_to_layer_temp.png',
rect_selection_info.left,
rect_selection_info.top,
rect_selection_info.width,
rect_selection_info.height
)
// Fill the current selection with white
// await batchPlay(
// [
// {
// _obj: 'fill',
// using: {
// _enum: 'fillContents',
// _value: 'white',
// },
// opacity: {
// _unit: 'percentUnit',
// _value: 100,
// },
// mode: {
// _enum: 'blendMode',
// _value: 'normal',
// },
// },
// ],
// { modalBehavior: 'execute' }
// await io.IO.base64ToLayer(
// base64,
// 'base64_to_layer_temp.png',
// rect_selection_info.left,
// rect_selection_info.top,
// rect_selection_info.width,
// rect_selection_info.height
// )
// // Invert the selection
// await batchPlay(
// [
// {
// _obj: 'invert',
// _target: [
// {
// _ref: 'channel',
// _property: 'selection',
// },
// ],
// },
// ],
// { modalBehavior: 'execute' }
// )
// // Fill the inverted selection with black
// await batchPlay(
// [
// {
// _obj: 'fill',
// using: {
// _enum: 'fillContents',
// _value: 'black',
// },
// opacity: {
// _unit: 'percentUnit',
// _value: 100,
// },
// mode: {
// _enum: 'blendMode',
// _value: 'normal',
// },
// },
// ],
// { modalBehavior: 'execute' }
// )
await psapi.cleanLayers([mask_layer])
return base64
}
async function inpaintLasoInitImage() {
@ -630,6 +791,337 @@ class Selection {
static {}
}
async function moveToTopLayerStackExe() {
const moveToTop = {
_obj: 'move',
_target: { _ref: 'layer', _enum: 'ordinal', _value: 'targetEnum' },
to: { _ref: 'layer', _enum: 'ordinal', _value: 'front' },
// _options: { dialogOptions: 'dontDisplay' },
options: { failOnMissingProperty: false, failOnMissingElement: false },
}
try {
await executeAsModal(async () => {
const layer = app.activeDocument.activeLayers[0]
while (layer.parent) {
console.log(layer.parent)
const result = await batchPlay([moveToTop], {
modalBehavior: 'execute',
synchronousExecution: true,
})
}
})
} catch (e) {
console.warn(e)
}
}
async function colorRange() {
// const select_current_layer_cmd = {
// _obj: 'set',
// _target: [
// {
// _ref: 'channel',
// _property: 'selection',
// },
// ],
// to: {
// _ref: 'channel',
// _enum: 'channel',
// _value: 'transparencyEnum',
// },
// _isCommand: true,
// }
const cmd = {
_obj: 'colorRange',
fuzziness: 0,
minimum: {
_obj: 'labColor',
luminance: 100,
a: 0,
b: 0,
},
maximum: {
_obj: 'labColor',
luminance: 100,
a: 0,
b: 0,
},
colorModel: 0,
_isCommand: true,
}
const result = await batchPlay([cmd], {
modalBehavior: 'execute',
synchronousExecution: true,
})
return result
}
async function colorRangeExe() {
let result
try {
await executeAsModal(
async () => {
result = await colorRange()
},
{ commandName: 'Convert Black and White Layer to mask selection' }
)
} catch (e) {
console.warn(e)
}
return result
}
async function base64ToLassoSelection(base64, selection_info) {
//place the mask on the canvas
const mask_layer = await io.IO.base64ToLayer(
base64,
'monochrome_mask.png',
selection_info.left,
selection_info.top,
selection_info.width,
selection_info.height
)
//reselect the selection area
await psapi.reSelectMarqueeExe(selection_info)
//reselect the layer
await psapi.selectLayersExe([mask_layer])
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // undo the toggling operation, active layer will be visible
//select the white pixels
await colorRange()
})
// await colorRangeExe()
//delete the mask layer. we only needed to select the white pixels
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // undo the toggling operation, active layer will be visible
})
await layer_util.deleteLayers([mask_layer])
}
async function base64ToChannel(base64, selection_info, channel_name) {
try {
await executeAsModal(async (context) => {
const history_id = await context.hostControl.suspendHistory({
documentID: app.activeDocument.id, //TODO: change this to the session document id
name: 'Mask Image',
})
const layer = app.activeDocument.activeLayers[0]
if (!layer_util.Layer.doesLayerExist(layer)) {
return null
}
await psapi.unSelectMarqueeExe()
await psapi.unselectActiveLayersExe()
await base64ToLassoSelection(base64, selection_info)
const expand_cmd = {
_obj: 'expand',
by: {
_unit: 'pixelsUnit',
_value: 10,
},
selectionModifyEffectAtCanvasBounds: true,
_isCommand: true,
}
const channelToSelection = {
_obj: 'set',
_target: { _ref: 'channel', _property: 'selection' },
to: { _ref: 'channel', _name: channel_name },
}
await executeAsModal(async () => {
const result = await batchPlay(
[
expand_cmd,
...deleteChannel(channel_name),
makeMaskChannel(channel_name),
channelToSelection,
],
{ modalBehavior: 'execute', synchronousExecution: true }
)
})
await psapi.selectLayersExe([layer])
await applyMaskChannelExe(channel_name)
// context = stored_context
await context.hostControl.resumeHistory(history_id)
})
} catch (e) {
console.warn(e)
}
}
async function black_white_layer_to_mask(mask_id, target_layer_id, mask_name) {
let result
let psAction = require('photoshop').action
let command = [
//
...deleteChannel(mask_name),
// Select layer “Layer 33”
{
_obj: 'select',
_target: [{ _id: mask_id, _ref: 'layer' }],
// layerID: [3862],
makeVisible: false,
},
// Set Selection
{
_obj: 'set',
_target: [{ _property: 'selection', _ref: 'channel' }],
to: {
_enum: 'channel',
_ref: 'channel',
_value: 'transparencyEnum',
},
},
// Color Range
{
_obj: 'colorRange',
colorModel: 0,
fuzziness: 0,
maximum: { _obj: 'labColor', a: 0.0, b: 0.0, luminance: 100.0 },
minimum: { _obj: 'labColor', a: 0.0, b: 0.0, luminance: 100.0 },
},
// Duplicate Selection, make sure you delete the any mask that share the same name
{
_obj: 'duplicate',
_target: [{ _property: 'selection', _ref: 'channel' }],
name: mask_name,
},
// Select channel “Alpha 1”
{ _obj: 'select', _target: [{ _name: mask_name, _ref: 'channel' }] },
// Set Selection
{
_obj: 'set',
_target: [{ _property: 'selection', _ref: 'channel' }],
to: { _enum: 'ordinal', _ref: 'channel', _value: 'targetEnum' },
},
// Select layer “Layer 43”
{
_obj: 'select',
_target: [{ _id: target_layer_id, _ref: 'layer' }],
// layerID: [3879],
makeVisible: false,
},
// Make
{
_obj: 'make',
at: { _enum: 'channel', _ref: 'channel', _value: 'mask' },
new: { _class: 'channel' },
using: { _enum: 'userMaskEnabled', _value: 'revealSelection' },
},
]
result = await psAction.batchPlay(command, {})
}
async function black_white_layer_to_mask_multi_batchplay(
mask_id,
target_layer_id,
mask_name
) {
let result
let psAction = require('photoshop').action
const timer = (ms) => new Promise((res) => setTimeout(res, ms))
let command1 = [
{
_obj: 'set',
_target: [{ _property: 'selection', _ref: 'channel' }],
to: { _enum: 'ordinal', _value: 'none' },
},
//
...deleteChannel(mask_name),
// Select layer “Layer 33”
{
_obj: 'select',
_target: [{ _id: mask_id, _ref: 'layer' }],
// layerID: [3862],
makeVisible: false,
},
// Set Selection
{
_obj: 'set',
_target: [{ _property: 'selection', _ref: 'channel' }],
to: {
_enum: 'channel',
_ref: 'channel',
_value: 'transparencyEnum',
},
},
]
let command2 = [
// Color Range
{
_obj: 'colorRange',
colorModel: 0,
fuzziness: 0,
maximum: { _obj: 'labColor', a: 0.0, b: 0.0, luminance: 100.0 },
minimum: { _obj: 'labColor', a: 0.0, b: 0.0, luminance: 100.0 },
},
{
_obj: 'expand',
by: {
_unit: 'pixelsUnit',
_value: 10,
},
selectionModifyEffectAtCanvasBounds: true,
_isCommand: true,
},
]
let command3 = [
// Duplicate Selection, make sure you delete the any mask that share the same name
{
_obj: 'duplicate',
_target: [{ _property: 'selection', _ref: 'channel' }],
name: mask_name,
},
// Select channel “Alpha 1”
{
_obj: 'select',
_target: [{ _name: mask_name, _ref: 'channel' }],
},
// Set Selection
{
_obj: 'set',
_target: [{ _property: 'selection', _ref: 'channel' }],
to: { _enum: 'ordinal', _ref: 'channel', _value: 'targetEnum' },
},
// Select layer “Layer 43”
{
_obj: 'select',
_target: [{ _id: target_layer_id, _ref: 'layer' }],
// layerID: [3879],
makeVisible: false,
},
// Make
{
_obj: 'make',
at: { _enum: 'channel', _ref: 'channel', _value: 'mask' },
new: { _class: 'channel' },
using: { _enum: 'userMaskEnabled', _value: 'revealSelection' },
},
]
await timer(g_timer_value)
// result = await psAction.batchPlay(command, {})
await executeAsModal(async () => {
result = await psAction.batchPlay(command1, {})
})
await timer(g_timer_value)
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // toggle active layer will be visible, note: doesn't solve the issue in outpaint mode
result = await psAction.batchPlay(command2, {})
})
await timer(g_timer_value)
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // undo the toggling operation, active layer will be visible, note: doesn't solve the issue in outpaint mode
result = await psAction.batchPlay(command3, {})
})
}
module.exports = {
finalWidthHeight,
selectionToFinalWidthHeight,
@ -640,4 +1132,18 @@ module.exports = {
makeMaskChannel,
makeMaskChannelExe,
fillSelectionWhiteOutsideBlack,
inpaintLasoInitImage,
applyMaskChannelExe,
createLayerFromMaskChannel,
multiGetExe,
inpaintLassoInitImageAndMask,
channelToSelectionExe,
moveToTopLayerStackExe,
colorRangeExe,
base64ToLassoSelection,
base64ToChannel,
deleteChannel,
selectionToChannel,
black_white_layer_to_mask,
black_white_layer_to_mask_multi_batchplay,
}

View File

@ -1,65 +0,0 @@
# controlnet original + txt2img
import requests
import cv2
import numpy as np
from base64 import b64encode , b64decode
from PIL import Image
import io
def readImage(path):
img = cv2.imread(path)
retval, buffer = cv2.imencode('.jpg', img)
b64img = b64encode(buffer).decode("utf-8")
return b64img
def readb64(uri):
nparr = np.fromstring(b64decode(uri), np.uint8)
img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
return img
b64img = readImage("output_image.png")
class controlnetRequest():
def __init__(self, prompt):
self.url = "http://127.0.0.1:7860/controlnet/txt2img" #openpose
self.body = {
"prompt": prompt,
"negative_prompt": "",
"seed": -1,
"subseed": -1,
"subseed_strength": 0,
"batch_size": 1,
"n_iter": 1,
"steps": 30,
"cfg_scale": 14,
"width": 512,
"height": 512,
"restore_faces": True,
"eta": 0,
"sampler_index": "DDIM",
"controlnet_model": "Test_ziva",
"controlnet_input_image": [b64img],
"controlnet_module": 'depth',
"ControlNet Weight": 1,
"controlnet_model": 'control_sd15_depth [fef5e48e]',
"controlnet_guidance": 1
}
def sendRequest(self):
# print(self.simple_txt2img)
r = requests.post(self.url, json=self.body)
print(r)
return r.json()
js = controlnetRequest("clothed busty bird").sendRequest()
for x,i in enumerate(js['images']):
image = Image.open(io.BytesIO(b64decode(i.split(",",1)[0])))
image.save(str(x)+'output.png')
len(js['images'])
print(js)

View File

@ -0,0 +1,154 @@
#code copied from controlnet repo global_state.py
preprocessor_filters = {
"All": "none",
"Canny": "canny",
"Depth": "depth_midas",
"Normal": "normal_bae",
"OpenPose": "openpose_full",
"MLSD": "mlsd",
"Lineart": "lineart_standard (from white bg & black line)",
"SoftEdge": "softedge_pidinet",
"Scribble": "scribble_pidinet",
"Seg": "seg_ofade20k",
"Shuffle": "shuffle",
"Tile": "tile_resample",
"Inpaint": "inpaint_only",
"IP2P": "none",
"Reference": "reference_only",
"T2IA": "none",
}
cn_preprocessor_modules = ["none",
"canny",
"depth",
"depth_leres",
"depth_leres++",
"hed",
"hed_safe",
"mediapipe_face",
"mlsd",
"normal_map",
"openpose",
"openpose_hand",
"openpose_face",
"openpose_faceonly",
"openpose_full",
"clip_vision",
"color",
"pidinet",
"pidinet_safe",
"pidinet_sketch",
"pidinet_scribble",
"scribble_xdog",
"scribble_hed",
"segmentation",
"threshold",
"depth_zoe",
"normal_bae",
"oneformer_coco",
"oneformer_ade20k",
"lineart",
"lineart_coarse",
"lineart_anime",
"lineart_standard",
"shuffle",
"tile_resample",
"invert",
"lineart_anime_denoise",
"reference_only",
"reference_adain",
"reference_adain+attn",
"inpaint",
"inpaint_only",
"inpaint_only+lama",
"tile_colorfix",
"tile_colorfix+sharp",
]
preprocessor_aliases = {
"invert": "invert (from white bg & black line)",
"lineart_standard": "lineart_standard (from white bg & black line)",
"lineart": "lineart_realistic",
"color": "t2ia_color_grid",
"clip_vision": "t2ia_style_clipvision",
"pidinet_sketch": "t2ia_sketch_pidi",
"depth": "depth_midas",
"normal_map": "normal_midas",
"hed": "softedge_hed",
"hed_safe": "softedge_hedsafe",
"pidinet": "softedge_pidinet",
"pidinet_safe": "softedge_pidisafe",
"segmentation": "seg_ufade20k",
"oneformer_coco": "seg_ofcoco",
"oneformer_ade20k": "seg_ofade20k",
"pidinet_scribble": "scribble_pidinet",
"inpaint": "inpaint_global_harmonious",
}
def filter_selected_helper(k,preprocessor_list,model_list):
if 'None' not in model_list:
model_list = ['None'] + model_list
ui_preprocessor_keys = ['none', preprocessor_aliases['invert']]
ui_preprocessor_keys += sorted([preprocessor_aliases.get(k, k)
for k in preprocessor_list
if preprocessor_aliases.get(k, k) not in ui_preprocessor_keys])
preprocessor_list = ui_preprocessor_keys
# print("preprocessor_list sorted: ",preprocessor_list)
model_list = list(model_list)
# print("list(model_list): ",model_list)
# print("k:",k,k.lower())
default_option = preprocessor_filters[k]
pattern = k.lower()
# model_list = list(cn_models.keys())
if pattern == "all":
return [
preprocessor_list,
model_list,
'none', #default option
"None" #default model
]
filtered_preprocessor_list = [
x
for x in preprocessor_list
if pattern in x.lower() or x.lower() == "none"
]
if pattern in ["canny", "lineart", "scribble", "mlsd"]:
filtered_preprocessor_list += [
x for x in preprocessor_list if "invert" in x.lower()
]
##Debug start
# for model in model_list:
# print("model: ",model)
# if pattern in model.lower():
# print('add to filtered')
# print("pattern:",pattern, "in model.lower():",model.lower())
# else:
# print("pattern:",pattern, "not in model.lower():",model.lower())
##Debug end
filtered_model_list = [
x for x in model_list if pattern in x.lower() or x.lower() == "none"
]
if default_option not in filtered_preprocessor_list:
default_option = filtered_preprocessor_list[0]
if len(filtered_model_list) == 1:
default_model = "None"
filtered_model_list = model_list
else:
default_model = filtered_model_list[1]
for x in filtered_model_list:
if "11" in x.split("[")[0]:
default_model = x
break
return [filtered_preprocessor_list,filtered_model_list, default_option,default_model]

View File

@ -32,7 +32,7 @@ def reserveBorderPixels(img,dilation_img):
width, height = img.size
dilation_pixels = dilation_img.load()
all_pixels = []
depth = 20 # five pixel depth
depth = 1 # five pixel depth
for x in range(width):
for d in range(depth):
dilation_pixels[x,d] = pixels[x, d]
@ -44,7 +44,7 @@ def reserveBorderPixels(img,dilation_img):
dilation_pixels[width-(d+1),y] = pixels[width-(d+1), y]
return dilation_img
def maskExpansion(mask_img,mask_expansion):
def maskExpansion(mask_img,mask_expansion,blur =10):
#only if image exist then try to open it
@ -53,8 +53,8 @@ def maskExpansion(mask_img,mask_expansion):
# if(payload['use_sharp_mask'] == False):# use blurry mask
iteration = mask_expansion
dilated_img = applyDilation(mask_img,iteration)
mask_with_border = reserveBorderPixels(mask_img,dilated_img)
mask_with_border = mask_with_border.filter(ImageFilter.GaussianBlur(radius = 10))
blurred_image = dilated_img.filter(ImageFilter.GaussianBlur(radius = blur))
mask_with_border = reserveBorderPixels(mask_img,blurred_image)
return mask_with_border
async def base64ToPng(base64_image,image_path):

View File

@ -10,7 +10,7 @@ except ImportError:
async def imageSearch(keywords="cute cats"):
with DDGS() as ddgs:
return [x for x in islice(ddgs.images(keywords), 30)]
return [x for x in islice(ddgs.images(keywords,safesearch='off'), 50)]
if __name__ == "__main__":

View File

@ -6,7 +6,7 @@ import base64
from PIL import Image, PngImagePlugin
import asyncio
import httpx
from typing import List
import os
import time
@ -14,6 +14,8 @@ import serverHelper
import prompt_shortcut
import metadata_to_json
import search
import global_state
sd_url = os.environ.get('SD_URL', 'http://127.0.0.1:7860')
@ -90,8 +92,8 @@ def img_2_b64(image):
from typing import Union
from fastapi import FastAPI
from fastapi import APIRouter, Request
from fastapi import FastAPI,APIRouter, Request,Query, Body
@ -276,7 +278,7 @@ async def savePng(request:Request):
except:
json = {}
print("json:",json)
# print("json:",json)
try:
folder = './init_images'
image_path = f"{folder}/{json['image_name']}"
@ -323,13 +325,14 @@ async def maskExpansionHandler(request:Request):
# keywords = json.get('keywords','cute dogs')
base64_mask_image = json['mask']
mask_expansion = json['mask_expansion']
blur = json['blur']
#convert base64 to img
await img2imgapi.base64ToPng(base64_mask_image,"original_mask.png")#save a copy of the mask for debugging
mask_image = img2imgapi.b64_2_img(base64_mask_image)
expanded_mask_img = img2imgapi.maskExpansion(mask_image,mask_expansion)
expanded_mask_img = img2imgapi.maskExpansion(mask_image,mask_expansion,blur)
base64_expanded_mask_image = img2imgapi.img_2_b64(expanded_mask_img)
await img2imgapi.base64ToPng(base64_expanded_mask_image,"expanded_mask.png")#save a copy of the mask of the expanded_mask for debugging
@ -417,8 +420,8 @@ async def loadPromptShortcut(request: Request):
json = {}
try:
print("json: ",json)
print("json['prompt_shortcut']: ",json['prompt_shortcut'])
# print("json: ",json)
# print("json['prompt_shortcut']: ",json['prompt_shortcut'])
# save the prompt shortcut to the prompt_shortcut.json
prompt_shortcut_json = json['prompt_shortcut']
# response.body = {"prompt_shortcut":prompt_shortcut}
@ -453,7 +456,7 @@ async def openUrl(request:Request):
json = {}
url = ""
print("json: ",json)
# print("json: ",json)
try:
url = json['url']
webbrowser.open(url) # Go to example.com
@ -503,5 +506,37 @@ async def list_available_vae():
return sd_vae_dict
@router.post("/controlnet/filter")
async def filter(keyword:str = Body('All',title="keyword to filter by"),
preprocessor_list: List[str]= Body([],title="complete preprocessor list"),
model_list: List[str] =Body([],title="complete model list"),):
filtered_preprocessor_list= []
filtered_model_list= []
default_option= 'none'
default_model= 'None'
print("preprocessor_list:",preprocessor_list)
print("model_list:",model_list)
try:
[filtered_preprocessor_list,filtered_model_list,default_option,default_model] = global_state.filter_selected_helper(keyword,preprocessor_list,model_list)
except Exception as e:
print("Invalid Keyword: ",e)
return {
"keywords": list(global_state.preprocessor_filters.keys()),
"module_list": filtered_preprocessor_list,
"model_list": filtered_model_list,
"default_option":default_option,
"default_model":default_model
}
@router.get('/heartbeat')
async def heartbeat():
return {'heartbeat':True}
app = FastAPI()
app.include_router(router)

View File

@ -31,7 +31,7 @@
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
"typeRoots": ["./*/types","./types", "./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */
"typeRoots": ["./*/@types","./*/types","./types", "./node_modules/@types"], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
@ -40,7 +40,7 @@
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
// "resolveJsonModule": true, /* Enable importing .json files. */
"resolveJsonModule": true, /* Enable importing .json files. */
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
@ -53,10 +53,10 @@
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./main/dist", /* Specify an output folder for all emitted files. */
"outDir": "./typescripts/dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
@ -108,4 +108,5 @@
"skipLibCheck": true /* Skip type checking all .d.ts files. */
},
"exclude": ["./jimp"],
// "include": ["./typescripts/@types/custom.d.ts"]
}

1
typescripts/@types/changedpi.d.ts vendored Normal file
View File

@ -0,0 +1 @@
declare module 'changedpi'

8
typescripts/@types/custom.d.ts vendored Normal file
View File

@ -0,0 +1,8 @@
declare module '*.svg' {
import React = require('react')
export const ReactComponent: React.FunctionComponent<
React.SVGProps<SVGSVGElement>
>
const src: string
export default src
}

4
typescripts/@types/sdapi_py_re.d.ts vendored Normal file
View File

@ -0,0 +1,4 @@
declare module 'sdapi_py_re' {
const exports: any
export = exports
}

6
typescripts/@types/uxp.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare module 'uxp' {
// Add type declarations for the uxp module here
export const storage: any
export const versions: any
export const host: any
}

View File

@ -2,18 +2,20 @@ import React, { ReactPropTypes } from 'react'
import ReactDOM from 'react-dom/client'
// import { action, makeAutoObservable, reaction, toJS } from 'mobx'
import { observer } from 'mobx-react'
import { observer, useObserver } from 'mobx-react'
import {
SliderType,
SpCheckBox,
SpMenu,
SpSliderWithLabel,
} from '../../ultimate_sd_upscaler/src/elements'
import { AStore } from '../../main/src/astore'
} from '../util/elements'
// import * as sdapi from '../../sdapi_py_re'
import { api } from '../util/oldSystem'
import { AStore } from '../main/astore'
import { ui_config, model_list } from './config'
import { requestGet } from '../../utility/api'
import { requestControlNetModelList } from '../../utility/tab/control_net'
const { requestGet } = api
import { requestControlNetModelList } from '../controlnet/entry'
import './style/after_detailer.css'
@ -88,32 +90,28 @@ export class AfterDetailerComponent extends React.Component<{
async componentDidUpdate(
prevProps: ReactPropTypes,
prevState: ReactPropTypes
) {
// if (store.data.refresh) {
// if (await this.isInstalled()) {
// await this.getInpaintModels()
// store.updateProperty('refresh', false)
// }
// }
}
) {}
handleRefresh = async () => {
if (await this.isInstalled()) {
await this.getInpaintModels()
// store.updateProperty('refresh', false)
}
}
async isInstalled() {
const full_url = `${g_sd_url}/sdapi/v1/scripts`
try {
const full_url = `${g_sd_url}/sdapi/v1/scripts`
const scripts = await requestGet(full_url)
const is_installed =
scripts?.txt2img?.includes(store.data.script_name) ||
scripts?.img2img?.includes(store.data.script_name) ||
false
const scripts = await requestGet(full_url)
const is_installed =
scripts?.txt2img?.includes(store.data.script_name) ||
scripts?.img2img?.includes(store.data.script_name) ||
false
console.log('is_installed: ', is_installed)
store.updateProperty('is_installed', is_installed)
return is_installed
console.log('is_installed: ', is_installed)
store.updateProperty('is_installed', is_installed)
return is_installed
} catch (e) {
console.error(e)
}
}
async getInpaintModels() {
try {
@ -140,6 +138,7 @@ export class AfterDetailerComponent extends React.Component<{
</sp-label>
<button
className="btnSquare refreshButton"
id="btnResetSettings"
title="Refresh the After Detailer Extension"
onClick={this.handleRefresh}
></button>
@ -202,7 +201,7 @@ export class AfterDetailerComponent extends React.Component<{
output_value={store.data['ad_conf']}
// title={ui_config[id].label}
label="Detection Confidence Threshold %:"
onSliderChange={(new_value: number) => {
onSliderInput={(new_value: number) => {
// console.log('slider_change: ', new_value)
store.updateProperty('ad_conf', new_value)
}}
@ -250,7 +249,7 @@ export class AfterDetailerComponent extends React.Component<{
? SliderType.Integer
: SliderType.Float
}
onSliderChange={(new_value: number) => {
onSliderInput={(new_value: number) => {
// console.log('slider_change: ', new_value)
store.updateProperty('controlNetWeight', new_value)
}}
@ -264,24 +263,66 @@ const domNode = document.getElementById('alwaysOnScriptsContainer')!
const root = ReactDOM.createRoot(domNode)
import { useState, ReactNode } from 'react'
import Locale from '../locale/locale'
import { ErrorBoundary } from '../util/errorBoundary'
interface CollapsibleProps {
label: string
labelStyle?: React.CSSProperties
containerStyle?: React.CSSProperties
defaultIsOpen?: boolean
checked?: boolean
checkboxCallback?: (checked: boolean) => void
children: ReactNode
}
const Collapsible = ({ label, children }: CollapsibleProps) => {
const [isOpen, setIsOpen] = useState(false)
function Collapsible({
label,
labelStyle,
containerStyle,
defaultIsOpen = false,
checkboxCallback,
checked,
children,
}: CollapsibleProps) {
const [isOpen, setIsOpen] = useState(defaultIsOpen)
const handleToggle = () => {
setIsOpen(!isOpen)
}
return (
<div>
<div className="collapsible" onClick={handleToggle}>
<span>{label}</span>
<span style={{ float: 'right' }} className="triangle">
{isOpen ? '' : '<'}
/*useObserver(()=>*/ <div>
<div
className="collapsible"
style={containerStyle}
onClick={handleToggle}
>
<span className="truncate" style={labelStyle}>
{label}
</span>
<span
style={{ float: 'right', display: 'flex' }}
className="triangle"
>
{checkboxCallback && checked !== void 0 ? (
<input
type="checkbox"
className="minimal-checkbox"
onClick={(event) => {
event.stopPropagation()
}}
onChange={(event: any) => {
checkboxCallback(event.target.checked)
}}
checked={checked}
/>
) : (
void 0
)}
<span>{isOpen ? '' : '<'}</span>
</span>
</div>
{/* {isOpen && <div>{children}</div>} */}
@ -294,38 +335,12 @@ export default Collapsible
root.render(
<React.StrictMode>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
{/* <button>test</button> */}
{/* <div
// type="button"
className="collapsible"
onClick={(event: any) => {
console.log('clicked')
event.target.classList.toggle('collapsible-active')
let content = event.target.nextElementSibling
console.log('collapsible content: ', content)
let triangle =
event.target.getElementsByClassName('triangle')[0]
if (content.style.display === 'block') {
content.style.display = 'none'
triangle.innerText = '<'
} else {
content.style.display = 'block'
triangle.innerText = ''
}
}}
>
<span>After Detailer</span>
<span style={{ float: 'right' }} className="triangle">
{'<'}
</span>
</div> */}
{/* <AfterDetailerComponent></AfterDetailerComponent> */}
<Collapsible label={'After Detailer'}>
<AfterDetailerComponent />
</Collapsible>
</div>
<ErrorBoundary>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Collapsible label={'After Detailer'}>
<AfterDetailerComponent />
</Collapsible>
</div>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -9,7 +9,7 @@ export const model_list = [
'person_yolov8n-seg.pt',
'person_yolov8s-seg.pt',
'None'
'None',
]
export let ui_config = {
ad_model: {

View File

@ -25,4 +25,16 @@
.triangle {
background-color: transparent;
}
.truncate {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.minimal-checkbox {
margin: 0;
padding: 0;
border: none;
}

View File

@ -0,0 +1,252 @@
import { observer } from 'mobx-react'
import React from 'react'
import ControlNetUnit from './ControlNetUnit'
import { store as ControlNetStore } from './main'
import { DefaultControlNetUnitData } from './store'
import {
Enum,
controlnet_preset,
note,
preset,
selection,
} from '../util/oldSystem'
import { SpMenuComponent } from '../util/elements'
import Locale from '../locale/locale'
import Collapsible from '../after_detailer/after_detailer'
let g_controlnet_presets: any
declare const g_generation_session: any
@observer
class ControlNetTab extends React.Component<{
appState: typeof ControlNetStore
}> {
state = {
maxControlNet: 0,
presetMenuChildren: [],
}
// private presetMenuChildren: JSX.Element[] = []
onPresetMenuChange(evt: any) {
const preset_index = evt.target.selectedIndex
const preset_name = evt.target.options[preset_index].textContent
ControlNetStore.controlNetUnitData.forEach((dataitem, index) => {
const presetData = g_controlnet_presets[preset_name][index] || {}
dataitem.enabled =
presetData.enabled || DefaultControlNetUnitData.enabled
dataitem.input_image =
presetData.input_image || DefaultControlNetUnitData.input_image
dataitem.mask = presetData.mask || DefaultControlNetUnitData.mask
dataitem.module =
presetData.module || DefaultControlNetUnitData.module
dataitem.model = presetData.model || DefaultControlNetUnitData.model
dataitem.weight =
presetData.weight || DefaultControlNetUnitData.weight
dataitem.resize_mode =
presetData.resize_mode || DefaultControlNetUnitData.resize_mode
dataitem.lowvram =
presetData.lowvram || DefaultControlNetUnitData.lowvram
dataitem.processor_res =
presetData.processor_res ||
DefaultControlNetUnitData.processor_res
dataitem.threshold_a =
presetData.threshold_a || DefaultControlNetUnitData.threshold_a
dataitem.threshold_b =
presetData.threshold_b || DefaultControlNetUnitData.threshold_b
dataitem.guidance_start =
presetData.guidance_start ||
DefaultControlNetUnitData.guidance_start
dataitem.guidance_end =
presetData.guidance_end ||
DefaultControlNetUnitData.guidance_end
dataitem.guessmode =
presetData.guessmode || DefaultControlNetUnitData.guessmode
dataitem.control_mode =
presetData.control_mode ||
DefaultControlNetUnitData.control_mode
dataitem.pixel_perfect =
presetData.pixel_perfect ||
DefaultControlNetUnitData.pixel_perfect
})
}
// function to update presetMenuChildren
updatePresetMenuChildren(newChildren: any) {
this.setState({ presetMenuChildren: newChildren })
}
async updatePresetMenuEvent() {
const custom_presets = await preset.getAllCustomPresetsSettings(
Enum.PresetTypeEnum['ControlNetPreset']
)
g_controlnet_presets = {
'Select CtrlNet Preset': {},
...controlnet_preset.ControlNetNativePresets,
...custom_presets,
}
const presets_names = Object.keys(g_controlnet_presets)
const presetMenuChildren = presets_names.map((preset_name) => {
if (preset_name == 'Select CtrlNet Preset')
return (
<sp-menu-item
key={preset_name}
className="mControlNetPresetMenuItem"
selected
>
{preset_name}
</sp-menu-item>
)
else
return (
<sp-menu-item
key={preset_name}
className="mControlNetPresetMenuItem"
>
{preset_name}
</sp-menu-item>
)
})
this.updatePresetMenuChildren(presetMenuChildren)
}
async onSetAllControlImage() {
const selectionInfo = await selection.Selection.getSelectionInfoExe()
if (selectionInfo) {
const base64_image =
await g_generation_session.setControlNetImageHelper()
ControlNetStore.controlNetUnitData.forEach(async (data) => {
data.input_image = base64_image
})
} else {
await note.Notification.inactiveSelectionArea()
}
}
componentDidMount(): void {
this.updatePresetMenuEvent()
}
render() {
return (
<div>
<sp-picker
title="auto fill the ControlNet with smart settings, to speed up your working process."
size="s"
label="ControlNet Preset"
style={{
width: '65%',
}}
>
<SpMenuComponent
id="mControlNetPresetMenu"
value="Select CtrlNet Preset"
onChange={this.onPresetMenuChange.bind(this)}
onUpdatePresetMenuEvent={this.updatePresetMenuEvent.bind(
this
)}
>
{this.state.presetMenuChildren.map((child) => child)}
</SpMenuComponent>
</sp-picker>
<div></div>
{this.props.appState.maxControlNet == 0 && (
<sp-label
id="controlnetMissingError"
style={{ color: '#ff595e', whiteSpace: 'normal' }}
>
{Locale(
'The Controlnet Extension is missing from Automatic1111.\nPlease install it to use it through the plugin.'
)}
</sp-label>
)}
<div
id="control_net_image_container"
className="imgContainer controlNetImaageContainer"
>
<div className="imgButton">
<button
className="column-item btnSquare"
id="bSetAllControlImage"
onClick={this.onSetAllControlImage.bind(this)}
>
{Locale('Set All CtrlNet Images')}
</button>
</div>
{/* <sp-checkbox id="chDisableControlNetTab"> */}
<sp-checkbox
checked={
this.props.appState.disableControlNetTab
? true
: void 0
}
onClick={(
event: React.ChangeEvent<HTMLInputElement>
) => {
this.props.appState.disableControlNetTab =
event.target.checked
}}
>
{Locale('Disable ControlNet Tab')}
</sp-checkbox>
</div>
<div>
{Array(this.props.appState.maxControlNet)
.fill(0)
.map((v, index) => {
const storeData =
this.props.appState.controlNetUnitData[index]
let controlNetLabel = `${Locale(
'ControlNet Unit'
)} ${index}: ${
storeData.module && storeData.module !== 'none'
? `${storeData.module}`
: ''
}`
return (
<div
key={index}
style={{
border: '2px solid #6d6c6c',
padding: '3px',
}}
>
<Collapsible
defaultIsOpen={false}
label={controlNetLabel}
labelStyle={{ fontSize: '12px' }}
containerStyle={{
alignItems: 'center',
backgroundColor: storeData.enabled
? '#2c4639'
: void 0,
}}
checkboxCallback={(checked) => {
storeData.enabled = checked
}}
checked={storeData.enabled}
>
<div style={{ paddingTop: '10px' }}>
<ControlNetUnit
appState={this.props.appState}
// key={index}
index={index}
/>
</div>
</Collapsible>
</div>
)
})}
</div>
</div>
)
}
}
export default ControlNetTab

View File

@ -0,0 +1,853 @@
import { observer } from 'mobx-react'
import React from 'react'
import {
MoveToCanvasSvg,
ActionButtonSVG,
SpCheckBox,
SpMenu,
SpSlider,
Thumbnail,
PenSvg,
PreviewSvg,
SpSliderWithLabel,
SliderType,
} from '../util/elements'
import ControlNetStore, { ControlnetMode, controlnetModes } from './store'
import { mapRange, versionCompare } from './util'
import {
note,
selection,
html_manip,
psapi,
api,
general,
} from '../util/oldSystem'
import Locale from '../locale/locale'
import { requestControlNetFiltersKeywords } from './entry'
declare const g_generation_session: any
declare const io: any
declare const app: any
declare let g_sd_url: string
@observer
export default class ControlNetUnit extends React.Component<
{ index: number; appState: typeof ControlNetStore },
{}
> {
onEnableChange(event: any) {
event.preventDefault()
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.enabled = !storeData.enabled
}
onLowVRamChange(event: any) {
event.preventDefault()
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.lowvram = !storeData.lowvram
}
onGuessModeChange(event: any) {
event.preventDefault()
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.guessmode = !storeData.guessmode
}
onPixelPerfectChange(event: any) {
event.preventDefault()
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
console.log('onPixelPerfectChange', storeData.pixel_perfect)
storeData.pixel_perfect = !storeData.pixel_perfect
}
onAutoImageChange(event: any) {
event.preventDefault()
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
console.log('onAutoImageChange', storeData.auto_image)
storeData.auto_image = !storeData.auto_image
}
onWeightMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.weight = +mapRange(
event.target.value,
0,
200,
0,
2,
0.01
).toFixed(2)
}
onGuidanceStartMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.guidance_start = +mapRange(
event.target.value,
0,
10,
0,
1,
0.1
).toFixed(1)
}
onGuidanceEndMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.guidance_end = +mapRange(
event.target.value,
0,
10,
0,
1,
0.1
).toFixed(1)
}
async onFilterChange(
event: any,
{ index, item }: { index: number; item: string }
) {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.filter_keyword = item
if (storeData.filter_keyword.toLowerCase() === 'none') {
storeData.module_list = this.props.appState.supportedPreprocessors
storeData.model_list = ['None'].concat(
this.props.appState.supportedModels
)
} else {
const filters = await requestControlNetFiltersKeywords(
storeData.filter_keyword,
this.props.appState.supportedPreprocessors,
this.props.appState.supportedModels
)
storeData.module_list = filters.module_list
storeData.model_list = filters.model_list
storeData.model = filters.default_model
storeData.module = filters.default_option
storeData.model = filters.default_model
}
}
onPreprocsesorChange(
event: any,
{ index, item }: { index: number; item: string }
) {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.module = item
}
onModelChange(
event: any,
{ index, item }: { index: number; item: string }
) {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.model = item
}
onResolutionMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
let resolutionConfig =
this.props.appState.preprocessorDetail[storeData.module] || {}
let sliderConfig = resolutionConfig.sliders[0]
storeData.processor_res = +(
event.target.value * (sliderConfig.step || 1)
)
}
onThresholdAMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
let resolutionConfig =
this.props.appState.preprocessorDetail[storeData.module] || {}
let sliderConfig = resolutionConfig.sliders[1]
storeData.threshold_a = +(event.target.value * (sliderConfig.step || 1))
}
onThresholdBMove(event: any) {
event.preventDefault()
if (event.target.tagName != 'SP-SLIDER') return
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
let resolutionConfig =
this.props.appState.preprocessorDetail[storeData.module] || {}
let sliderConfig = resolutionConfig.sliders[2]
storeData.threshold_b = +(event.target.value * (sliderConfig.step || 1))
}
async onSetImageButtonClick() {
const selectionInfo = await selection.Selection.getSelectionInfoExe()
if (selectionInfo) {
const base64_image =
await g_generation_session.setControlNetImageHelper()
this.props.appState.controlNetUnitData[
this.props.index
].input_image = base64_image
} else {
await note.Notification.inactiveSelectionArea()
}
}
async onMaskButtonClick() {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
if (g_generation_session.control_net_selection_info && storeData.mask) {
const selection_info =
g_generation_session.control_net_selection_info
const layer = await io.IO.base64ToLayer(
storeData.mask,
'ControlNet Mask.png',
selection_info.left,
selection_info.top,
selection_info.width,
selection_info.height
)
} else {
// await note.Notification.inactiveSelectionArea()
app.showAlert('Mask Image is not available')
}
}
async requestControlNetDetectMap(
controlnet_init_image: string,
_module: string,
processor_res: number,
threshold_a: number,
threshold_b: number
) {
try {
const payload = {
controlnet_module: _module,
controlnet_input_images: [controlnet_init_image],
controlnet_processor_res: processor_res,
controlnet_threshold_a: threshold_a,
controlnet_threshold_b: threshold_b,
}
const full_url = `${g_sd_url}/controlnet/detect`
const response_data = await api.requestPost(full_url, payload)
// update the mask preview with the new detectMap
if (response_data['images'].length === 0) {
app.showAlert(response_data['info'])
}
return response_data['images'][0]
} catch (e) {
console.warn('requestControlNetDetectMap(): ', _module, e)
}
}
async previewAnnotator() {
const index = this.props.index
try {
const storeData = this.props.appState.controlNetUnitData[index]
const controlnet_init_image = storeData.input_image
const _module = storeData.module || 'none'
const processor_res = storeData.processor_res
const threshold_a = storeData.threshold_a
const threshold_b = storeData.threshold_b
if (!controlnet_init_image) {
const error = 'ControlNet initial image is empty'
app.showAlert(error)
throw error
}
if (!_module || _module === 'none') {
const error = 'select a valid controlnet module (preprocessor)'
app.showAlert(error)
throw error
}
const detect_map = await this.requestControlNetDetectMap(
controlnet_init_image,
_module,
processor_res,
threshold_a,
threshold_b
)
const rgb_detect_map_url =
await io.convertBlackAndWhiteImageToRGBChannels3(detect_map)
const rgb_detect_map = general.base64UrlToBase64(rgb_detect_map_url)
// g_generation_session.controlNetMask[index] = rgb_detect_map
storeData.detect_map = rgb_detect_map
} catch (e) {
console.warn('PreviewAnnotator click(): index: ', index, e)
}
}
async setMask() {
try {
const selectionInfo = await psapi.getSelectionInfoExe()
if (selectionInfo) {
const mask_base64 = await io.getMaskFromCanvas()
this.props.appState.controlNetUnitData[this.props.index].mask =
mask_base64
} else {
// await note.Notification.inactiveSelectionArea()
app.showAlert('No Selection is available')
}
} catch (e) {
console.warn(e)
}
}
async resetMask() {
this.props.appState.controlNetUnitData[this.props.index].mask = ''
}
async toCanvas() {
if (
g_generation_session.control_net_preview_selection_info &&
this.props.appState.controlNetUnitData[this.props.index].detect_map
) {
const selection_info =
g_generation_session.control_net_preview_selection_info
const layer = await io.IO.base64ToLayer(
this.props.appState.controlNetUnitData[this.props.index]
.detect_map,
'ControlNet Detection Map.png',
selection_info.left,
selection_info.top,
selection_info.width,
selection_info.height
)
} else {
// await note.Notification.inactiveSelectionArea()
app.showAlert('Detection Map is not available')
}
}
async toControlNetInitImage() {
const preview_result_base64 =
g_generation_session.controlNetMask[this.props.index]
g_generation_session.controlNetImage[this.props.index] =
preview_result_base64
g_generation_session.control_net_selection_info =
g_generation_session.control_net_preview_selection_info
const rgb_detect_map_url =
await io.convertBlackAndWhiteImageToRGBChannels3(
preview_result_base64
)
// g_generation_session.controlNetMask[index] = rgb_detect_map
// html_manip.setControlImageSrc(rgb_detect_map_url, this.props.index)
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.input_image = storeData.detect_map
}
async previewAnnotatorFromCanvas() {
try {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
const _module = storeData.module || 'none'
const width = html_manip.getWidth()
const height = html_manip.getHeight()
const selectionInfo = await psapi.getSelectionInfoExe()
g_generation_session.control_net_preview_selection_info =
selectionInfo
const base64 =
await io.IO.getSelectionFromCanvasAsBase64Interface_New(
width,
height,
selectionInfo,
true
)
if (!_module || _module === 'none') {
const error = 'select a valid controlnet module (preprocessor)'
app.showAlert(error)
throw error
}
const processor_res = storeData.processor_res
const threshold_a = storeData.threshold_a
const threshold_b = storeData.threshold_b
const detect_map = await this.requestControlNetDetectMap(
base64,
_module,
processor_res,
threshold_a,
threshold_b
)
const rgb_detect_map_url =
await io.convertBlackAndWhiteImageToRGBChannels3(detect_map)
g_generation_session.controlNetMask[this.props.index] = detect_map
storeData.detect_map = general.base64UrlToBase64(rgb_detect_map_url)
} catch (e) {
console.warn(
'PreviewAnnotator click(): index: ',
this.props.index,
e
)
}
}
render() {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
const pd =
this.props.appState.preprocessorDetail[storeData.module] || {}
const ppSlider = pd.sliders || []
return (
<div id={`controlnet_settings_${this.props.index}`}>
{/* <div className="flexContainer">
<SpCheckBox
style={{ marginRight: '10px' }}
onChange={this.onEnableChange.bind(this)}
checked={storeData.enabled}
id={`chEnableControlNet_${this.props.index}`}
value={
this.props.appState.controlNetUnitData[
this.props.index
].enabled
}
>
ControlNet Unit {this.props.index}{' '}
{storeData.module && storeData.module !== 'none'
? `(${storeData.module})`
: void 0}
</SpCheckBox>
</div> */}
<div
style={{
display: 'block',
// display: storeData.enabled ? 'block' : 'none'
}}
>
<div style={{ display: 'flex' }}>
<div
id={`control_net_image_container_${this.props.index}`}
className="imgContainer controlNetImaageContainer"
>
<div>
<img
id={`control_net_image_${this.props.index}`}
className="column-item-image"
src={
storeData.input_image
? 'data:image/png;base64,' +
storeData.input_image
: 'https://source.unsplash.com/random'
}
width="300px"
height="100px"
/>
</div>
<div className="imgButton">
<button
className="column-item button-style btnSquare"
id={`bSetControlImage_${this.props.index}`}
onClick={this.onSetImageButtonClick.bind(
this
)}
title="Set CtrlNet Img"
>
{Locale('Set CtrlImg')}
</button>
</div>
</div>
<div
id={`control_net_mask_container_${this.props.index}`}
className="imgContainer controlNetImaageContainer"
>
<div>
<Thumbnail>
<img
id={`control_net_mask_${this.props.index}`}
className="column-item-image"
src={
storeData.detect_map
? 'data:image/png;base64,' +
storeData.detect_map
: 'https://source.unsplash.com/random'
}
width="300px"
height="100px"
/>
<ActionButtonSVG
ComponentType={PenSvg}
onClick={this.toControlNetInitImage.bind(
this
)}
title={Locale(
'use as controlnet input image'
)}
></ActionButtonSVG>
<ActionButtonSVG
ComponentType={MoveToCanvasSvg}
onClick={this.toCanvas.bind(this)}
title={Locale('Copy Image to Canvas')}
></ActionButtonSVG>
<ActionButtonSVG
ComponentType={PreviewSvg}
onClick={this.previewAnnotatorFromCanvas.bind(
this
)}
title={Locale(
'Preview Annotation From the Selected Area on Canvas'
)}
></ActionButtonSVG>
</Thumbnail>
</div>
<div className="imgButton btnClass">
<button
className="column-item button-style btnSquare"
id={`bControlMask_${this.props.index}`}
onClick={this.previewAnnotator.bind(this)}
title="Preview Annotator"
>
{Locale('Preview Annotator')}
</button>
</div>
</div>
{!this.props.appState.controlNetUnitData[
this.props.index
].model
.toLowerCase()
.includes('inpaint') ? (
void 0
) : (
<div className="imgContainer controlNetImaageContainer">
<div>
<Thumbnail>
<img
className="column-item-image"
src={
storeData.mask
? 'data:image/png;base64,' +
storeData.mask
: 'https://source.unsplash.com/random'
}
width="300px"
height="100px"
/>
<ActionButtonSVG
ComponentType={PenSvg}
onClick={this.setMask.bind(this)}
title={Locale(
'set the mask for controlnet inpaint mode'
)}
></ActionButtonSVG>
<ActionButtonSVG
ComponentType={PenSvg}
onClick={this.resetMask.bind(this)}
title={Locale('reset the mask')}
></ActionButtonSVG>
</Thumbnail>
</div>
<div className="imgButton btnClass">
<button
className="column-item button-style btnSquare"
id={`bControlMask_${this.props.index}`}
onClick={this.setMask.bind(this)}
title="Preview Annotator"
>
{Locale('Set Mask')}
</button>
</div>
</div>
)}
</div>
<SpCheckBox
style={{ marginRight: '10px' }}
onChange={this.onLowVRamChange.bind(this)}
checked={storeData.lowvram}
id={`chlowVram_${this.props.index}`}
>
{Locale('Low VRAM')}
</SpCheckBox>
<SpCheckBox
style={{
display:
this.props.appState.controlnetApiVersion > 1
? 'none'
: void 0,
marginRight: '10px',
}}
onChange={this.onGuessModeChange.bind(this)}
checked={storeData.guessmode}
id={`chGuessMode_${this.props.index}`}
>
{Locale('Guess Mode')}
</SpCheckBox>
<SpCheckBox
style={{
display:
this.props.appState.controlnetApiVersion > 1
? void 0
: 'none',
marginRight: '10px',
}}
onChange={this.onPixelPerfectChange.bind(this)}
checked={storeData.pixel_perfect}
id={`chPixelPerfect_${this.props.index}`}
>
{Locale('Pixel Perfect')}
</SpCheckBox>
<SpCheckBox
style={{
marginRight: '10px',
}}
onChange={this.onAutoImageChange.bind(this)}
checked={storeData.auto_image}
// id={`chPixelPerfect_${this.props.index}`}
title={Locale(
'load the input image from canvas automatically'
)}
>
{
//@ts-ignore
Locale('Auto Image')
}
</SpCheckBox>
{this.props.appState.controlnetApiVersion > 1 && (
<sp-radio-group
style={{ display: 'flex' }}
selected={
this.props.appState.controlNetUnitData[
this.props.index
].control_mode
}
onClick={(event: any) => {
this.props.appState.controlNetUnitData[
this.props.index
].control_mode = event.target.value
}}
>
<sp-label slot="label">
{Locale('Control Mode')}
</sp-label>
{controlnetModes.map(
(mode: ControlnetMode, index: number) => {
console.log('mode:', mode, ' index:', index)
return (
<sp-radio
key={`mode-${index}`}
checked={
this.props.appState
.controlNetUnitData[
this.props.index
].control_mode === mode
? true
: void 0
}
value={`${mode}`}
>
{Locale(mode)}
</sp-radio>
)
}
)}
</sp-radio-group>
)}
<div>
<div>
<SpSlider
show-value="false"
min={0}
max={200}
value={storeData.weight * 100}
onInput={this.onWeightMove.bind(this)}
title="2 will keep the composition; 0 will allow composition to change"
>
<sp-label slot="label">
{Locale('Control Weight')}
</sp-label>
<sp-label slot="label">
{storeData.weight}
</sp-label>
</SpSlider>
<SpSlider
show-value="false"
min="0"
max="10"
value={
+mapRange(
storeData.guidance_start,
0,
1,
0,
10,
1
).toFixed(1)
}
onInput={this.onGuidanceStartMove.bind(this)}
>
<sp-label slot="label">
{Locale('Guidance Start (T)')}
</sp-label>
<sp-label
slot="label"
id={`lControlNetGuidanceStrengthStart_${this.props.index}`}
>
{storeData.guidance_start}
</sp-label>
</SpSlider>
<SpSlider
show-value="false"
min="0"
max="10"
value={
+mapRange(
storeData.guidance_end,
0,
1,
0,
10,
1
).toFixed(1)
}
onInput={this.onGuidanceEndMove.bind(this)}
>
<sp-label slot="label">
{Locale('Guidance End (T)')}
</sp-label>
<sp-label
slot="label"
id={`lControlNetGuidanceStrengthEnd_${this.props.index}`}
>
{storeData.guidance_end}
</sp-label>
</SpSlider>
{ppSlider &&
ppSlider[0] &&
!storeData.pixel_perfect && (
<SpSlider
show-value="false"
min={
ppSlider[0].min /
(ppSlider[0].step || 1)
}
max={
ppSlider[0].max /
(ppSlider[0].step || 1)
}
value={
storeData.processor_res /
(ppSlider[0].step || 1)
}
onInput={this.onResolutionMove.bind(
this
)}
>
<sp-label slot="label">
{ppSlider[0].name}:
</sp-label>
<sp-label slot="label">
{storeData.processor_res.toFixed(2)}
</sp-label>
</SpSlider>
)}
{ppSlider && ppSlider[1] && (
<SpSlider
show-value="false"
min={
ppSlider[1].min /
(ppSlider[1].step || 1)
}
max={
ppSlider[1].max /
(ppSlider[1].step || 1)
}
value={
storeData.threshold_a /
(ppSlider[1].step || 1)
}
onInput={this.onThresholdAMove.bind(this)}
>
<sp-label slot="label">
{ppSlider[1].name}:
</sp-label>
<sp-label slot="label">
{storeData.threshold_a.toFixed(2)}
</sp-label>
</SpSlider>
)}
{ppSlider && ppSlider[2] && (
<SpSlider
show-value="false"
min={
ppSlider[2].min /
(ppSlider[2].step || 1)
}
max={
ppSlider[2].max /
(ppSlider[2].step || 1)
}
value={
storeData.threshold_b /
(ppSlider[2].step || 1)
}
onInput={this.onThresholdBMove.bind(this)}
>
<sp-label slot="label">
{ppSlider[2].name}:
</sp-label>
<sp-label slot="label">
{storeData.threshold_b.toFixed(2)}
</sp-label>
</SpSlider>
)}
</div>
</div>
<div>
<SpMenu
onChange={this.onFilterChange.bind(this)}
items={this.props.appState.filterKeywords}
label_item={Locale('Select Filter')}
selected_index={this.props.appState.filterKeywords.indexOf(
storeData.filter_keyword || 'All'
)}
style={{ width: '50%', display: 'flex' }}
/>
</div>
<div
id={`menu-bar-control_net_${this.props.index}`}
style={{ display: 'flex' }}
>
<SpMenu
onChange={this.onPreprocsesorChange.bind(this)}
id={`mModulesMenuControlNet_${this.props.index}`}
items={storeData.module_list || ['none']}
label_item={Locale('Select Module')}
selected_index={storeData.module_list?.indexOf(
storeData.module
)}
style={{ width: '100%', display: 'flex' }}
/>
{!pd.model_free && (
<SpMenu
onChange={this.onModelChange.bind(this)}
id={`mModelsMenuControlNet_${this.props.index}`}
items={storeData.model_list || []}
label_item={Locale('Select Module')}
selected_index={storeData.model_list?.indexOf(
storeData.model
)}
style={{ width: '100%', display: 'flex' }}
/>
)}
</div>
</div>
</div>
)
}
}

View File

@ -0,0 +1,278 @@
import { setControlImageSrc } from '../../utility/html_manip'
import { session_ts } from '../entry'
import { Enum, api, python_replacement } from '../util/oldSystem'
import { GenerationModeEnum } from '../util/ts/enum'
import { store, versionCompare } from './main'
const { getExtensionUrl } = python_replacement
declare const g_sd_config_obj: any
declare let g_sd_url: string
async function requestControlNetPreprocessors() {
const control_net_json = await api.requestGet(
`${g_sd_url}/controlnet/module_list?alias_name=1`
)
return control_net_json
}
async function requestControlNetModelList(): Promise<any> {
const control_net_json = await api.requestGet(
`${g_sd_url}/controlnet/model_list`
)
const model_list = control_net_json?.model_list
return model_list
}
async function requestControlNetApiVersion() {
const json = await api.requestGet(`${g_sd_url}/controlnet/version`)
const version = json?.version
return version
}
async function requestControlNetMaxUnits() {
const json = await api.requestGet(`${g_sd_url}/controlnet/settings`)
const control_net_max_models_num = json?.control_net_max_models_num ?? 0
return control_net_max_models_num
}
async function requestControlNetFiltersKeywords(
keyword = 'All',
module_list: string[],
model_list: string[]
) {
try {
const extension_url = getExtensionUrl()
// const full_url = `${extension_url}/controlnet/filter?keyword=${keyword}`
const full_url = `${extension_url}/controlnet/filter`
const payload = {
keyword: keyword,
preprocessor_list: module_list,
model_list: model_list,
}
//const full_url = `${g_sd_url}/controlnet/filter?keyword=${keyword}`
const control_net_json = await api.requestPost(full_url, payload)
return control_net_json
} catch (e) {
console.warn(e)
}
}
async function initializeControlNetTab(controlnet_max_models: number) {
store.maxControlNet = controlnet_max_models || store.maxControlNet
store.controlnetApiVersion = await requestControlNetApiVersion()
try {
const models = await requestControlNetModelList()
store.supportedModels = models || []
} catch (e) {
console.warn(e)
}
try {
const pps = await requestControlNetPreprocessors()
store.supportedPreprocessors = pps ? pps.module_list : []
store.preprocessorDetail = pps ? pps.module_detail : {}
} catch (e) {
console.warn(e)
}
try {
//retrieve all keywords to popular the dropdown menu
const filters = await requestControlNetFiltersKeywords(
'All',
store.supportedPreprocessors,
store.supportedModels
)
store.filterKeywords = filters
? ['none'].concat(filters.keywords)
: ['none']
if (filters) {
store.controlNetUnitData.forEach((unitData) => {
unitData.module_list = filters.module_list
unitData.model_list = filters.model_list
unitData.model = filters.default_model
unitData.module = filters.default_option
unitData.model = filters.default_model
})
}
} catch (e) {
console.warn(e)
}
}
function getEnableControlNet(index: number) {
if (typeof index == 'undefined')
return (
store.controlNetUnitData.filter((item) => item.enabled).length > 0
)
else return store.controlNetUnitData[index || 0].enabled
}
function mapPluginSettingsToControlNet(plugin_settings: any) {
const ps = plugin_settings // for shortness
let controlnet_units: any[] = []
function getControlNetInputImage(index: number) {
try {
const b_sync_input_image =
store.controlNetUnitData[index].auto_image
let input_image = store.controlNetUnitData[index].input_image
if (
b_sync_input_image &&
[GenerationModeEnum.Txt2Img].includes(
session_ts.store.data.mode
)
) {
//conditions: 1) txt2img mode 2)auto image on 3)first generation of session
input_image = session_ts.store.data.controlnet_input_image ?? ''
store.controlNetUnitData[index].input_image = input_image
}
if (
b_sync_input_image &&
[
GenerationModeEnum.Img2Img,
GenerationModeEnum.Inpaint,
GenerationModeEnum.Outpaint,
GenerationModeEnum.LassoInpaint,
].includes(session_ts.store.data.mode)
) {
// img2img mode
input_image = session_ts.store.data.init_image
store.controlNetUnitData[index].input_image = input_image
} else if (
b_sync_input_image &&
store.controlNetUnitData[index].enabled
) {
//txt2img mode
}
return input_image
} catch (e) {
console.warn(e)
}
}
function getControlNetMask(index: number) {
try {
// const b_sync_input_image =
// store.controlNetUnitData[index].auto_image
// let mask = store.controlNetUnitData[index].mask // the user created mask
// if (b_sync_input_image && session_ts.store.data.expanded_mask) {
// //use the mask from inpaint and outpaint mode
// mask = '' // this will tell controlnet to use SD mask
// store.controlNetUnitData[index].mask = ''
// }
if (
[
GenerationModeEnum.Txt2Img,
GenerationModeEnum.Img2Img,
].includes(session_ts.store.data.mode)
) {
//maskless mode
} else {
//mask related mode
store.controlNetUnitData[index].mask = '' // use the mask from the sd mode
}
return store.controlNetUnitData[index].mask
} catch (e) {
console.warn(e)
}
}
for (let index = 0; index < store.maxControlNet; index++) {
controlnet_units[index] = {
enabled: getEnableControlNet(index),
input_image: getControlNetInputImage(index),
mask: getControlNetMask(index),
module: store.controlNetUnitData[index].module,
model: store.controlNetUnitData[index].model,
weight: store.controlNetUnitData[index].weight,
resize_mode: 'Scale to Fit (Inner Fit)',
lowvram: store.controlNetUnitData[index].lowvram,
processor_res: store.controlNetUnitData[index].processor_res || 512,
threshold_a: store.controlNetUnitData[index].threshold_a,
threshold_b: store.controlNetUnitData[index].threshold_b,
// guidance: ,
guidance_start: store.controlNetUnitData[index].guidance_start,
guidance_end: store.controlNetUnitData[index].guidance_end,
}
if (store.controlnetApiVersion > 1) {
//new controlnet v2
controlnet_units[index].control_mode =
store.controlNetUnitData[index].control_mode
controlnet_units[index].pixel_perfect =
store.controlNetUnitData[index].pixel_perfect
} else {
// old controlnet v1
controlnet_units[index].guessmode =
store.controlNetUnitData[index].guessmode
}
}
const controlnet_payload = {
...ps,
controlnet_units, //keep for backward compatibility for now
subseed: -1,
override_settings: {},
override_settings_restore_afterwards: true,
alwayson_scripts: {
...(ps?.alwayson_scripts || {}),
controlnet: {
args: controlnet_units,
},
},
}
return controlnet_payload
}
function getControlNetMaxModelsNumber() {
return store.maxControlNet
}
function getUnitsData() {
return store.controlNetUnitData
}
function setControlDetectMapSrc(base64: string, index: number) {
// store.controlNetUnitData[index].mask = base64
store.controlNetUnitData[index].detect_map = base64
}
function setControlInputImageSrc(base64: string, index: number) {
store.controlNetUnitData[index].input_image = base64
}
function isControlNetModeEnable() {
let is_tab_enabled = !store.disableControlNetTab
let numOfEnabled = 0
if (is_tab_enabled) {
for (let index = 0; index < store.maxControlNet; index++) {
if (getEnableControlNet(index)) {
numOfEnabled += 1
}
}
}
let is_mode_enabled = is_tab_enabled // could be true
if (is_tab_enabled === false || numOfEnabled === 0) {
is_mode_enabled = false
}
return is_mode_enabled
}
function getModuleDetail() {
return store.preprocessorDetail
}
export {
requestControlNetModelList,
requestControlNetMaxUnits,
requestControlNetFiltersKeywords,
initializeControlNetTab,
getEnableControlNet,
mapPluginSettingsToControlNet,
getControlNetMaxModelsNumber,
getUnitsData,
setControlDetectMapSrc,
setControlInputImageSrc,
isControlNetModeEnable,
getModuleDetail,
store,
}

View File

@ -0,0 +1,98 @@
import ReactDOM from 'react-dom/client'
import React from 'react'
import ControlNetTab from './ControlNetTab'
import store from './store'
import { versionCompare } from './util'
import Collapsible from '../after_detailer/after_detailer'
import Locale from '../locale/locale'
import { ErrorBoundary } from '../util/errorBoundary'
const elem = document.getElementById('sp-control_net-tab-page')
const elem2 = document.getElementById('sp-control_net-tab-page2')
if (elem) {
const root = ReactDOM.createRoot(elem)
root.render(
<ErrorBoundary>
<ControlNetTab appState={store} />
</ErrorBoundary>
)
}
if (elem2) {
const root = ReactDOM.createRoot(elem2)
root.render(
<React.StrictMode>
<ErrorBoundary>
<div
style={{
border: '2px solid #6d6c6c',
padding: '3px',
}}
>
<Collapsible
defaultIsOpen={true}
label={Locale('ControlNet Tab')}
>
<div
id="controlNetTabParentContainer"
style={{ marginTop: '10px' }}
>
<ControlNetTab appState={store} />
</div>
</Collapsible>
</div>
</ErrorBoundary>
</React.StrictMode>
)
}
function scrollToEnabledControlNetUnit() {}
const button = document.getElementById('scrollToControlNetUnitContainer')!
const button_root = ReactDOM.createRoot(button)
let controlnet_unit_index = 0
button_root.render(
<ErrorBoundary>
<button
className="btnSquare svgButton"
onClick={() => {
try {
const units = document.querySelectorAll(
'#controlNetTabParentContainer .collapsible'
)
const units_data = store.controlNetUnitData.map(
(data, index) => ({
enabled: data.enabled,
index,
})
)
// Find the next enabled unit
let counter = 0
while (
!units_data[controlnet_unit_index % units.length]
.enabled &&
counter < units.length
) {
controlnet_unit_index += 1
counter += 1
}
if (counter < units.length) {
controlnet_unit_index =
controlnet_unit_index % units.length
units[controlnet_unit_index].scrollIntoView()
controlnet_unit_index += 1
}
} catch (e) {
console.warn(e)
}
}}
title="Quickly jump to the active ControlNet Unit"
>
C
</button>
</ErrorBoundary>
)
export { store, versionCompare }

View File

@ -0,0 +1,112 @@
import { observable, reaction } from 'mobx'
export const DefaultControlNetUnitData = {
enabled: false,
input_image: '',
mask: '',
detect_map: '',
module: '',
model: '',
weight: 1.0,
resize_mode: 'Scale to Fit (Inner Fit)',
lowvram: true,
processor_res: 512,
threshold_a: 0,
threshold_b: 0,
guidance_start: 0,
guidance_end: 1,
guessmode: false,
control_mode: 'Balanced',
pixel_perfect: true,
auto_image: true,
}
export const controlnetModes = [
'Balanced',
'My prompt is more important',
'ControlNet is more important',
] as const
export type ControlnetMode = (typeof controlnetModes)[number]
export interface controlNetUnitData {
enabled: boolean
input_image: string
mask: string
detect_map: string
module_list: string[]
model_list: string[]
module: string
model: string
filter_keyword: string
weight: number
resize_mode: 'Just Resize' | 'Crop and Resize' | 'Resize and Fill'
lowvram: boolean
processor_res: number
threshold_a: number
threshold_b: number
guidance_start: number
guidance_end: number
guessmode: boolean
control_mode: ControlnetMode
pixel_perfect: boolean
auto_image: boolean // sync CtrlNet image with sd input image
}
interface ControlNetMobxStore {
disableControlNetTab: boolean
maxControlNet: number
controlnetApiVersion: number
supportedModels: string[]
supportedPreprocessors: string[]
filterKeywords: string[]
preprocessorDetail: { [key: string]: any }
controlNetUnitData: controlNetUnitData[]
}
var ControlNetStore = observable<ControlNetMobxStore>({
disableControlNetTab: false,
maxControlNet: 0,
controlnetApiVersion: 1,
supportedModels: [],
supportedPreprocessors: [],
filterKeywords: [],
preprocessorDetail: {},
controlNetUnitData: [],
})
reaction(
() => {
return ControlNetStore.controlNetUnitData.map((data) => data.module)
},
(module_, index) => {
ControlNetStore.controlNetUnitData.forEach((data, index) => {
const pd = ControlNetStore.preprocessorDetail[module_[index]] || {}
const pSlider = pd.sliders || []
data.processor_res = pSlider[0]?.value || 512
data.threshold_a = pSlider[1]?.value || 0
data.threshold_b = pSlider[2]?.value || 0
})
}
)
reaction(
() => ControlNetStore.maxControlNet,
(maxControlNet) => {
ControlNetStore.controlNetUnitData = Array(maxControlNet)
.fill(0)
.map((v, index) => {
return (
ControlNetStore.controlNetUnitData[index] ||
DefaultControlNetUnitData
)
})
}
)
export default ControlNetStore

View File

@ -0,0 +1,36 @@
export function mapRange(
x: number,
in_min: number,
in_max: number,
out_min: number,
out_max: number,
step: number
) {
return (
Math.round(
(((x - in_min) * (out_max - out_min)) / (in_max - in_min) +
out_min) /
step
) * step
)
}
export function versionCompare(to: string, from: string) {
const vTo = to.split('.')
const vFrom = from.split('.')
for (let i = 0; i < Math.max(vTo.length, vFrom.length); i++) {
const vFromI = +(vFrom[i] || 0)
const vToI = +(vTo[i] || 0)
if (isNaN(vFromI) || isNaN(vToI)) {
throw new Error(`invalid version ${vTo} or ${vFrom} `)
}
if (vFromI > vToI) {
return -1
} else if (vFromI < vToI) {
return 1
}
}
return 0
}

23
typescripts/entry.ts Normal file
View File

@ -0,0 +1,23 @@
// import { configure } from 'mobx'
// configure({
// enforceActions: 'never', // disable mobx warning temporarily
// })
export * as control_net from './controlnet/entry'
export * as after_detailer_script from './after_detailer/after_detailer'
export * as ultimate_sd_upscaler from './ultimate_sd_upscaler/ultimate_sd_upscaler'
export * as scripts from './ultimate_sd_upscaler/scripts'
export * as main from './main/main'
export * as logger from './util/logger'
export * as image_search from './image_search/image_search'
export * as history from './history/history'
export * as viewer from './viewer/viewer'
export * as session_ts from './session/session'
export * as progress from './session/progress'
export * as preview from './viewer/preview'
export * as generate from './session/generate'
export * as sd_tab_ts from './sd_tab/sd_tab'
export * as sam from './sam/sam'
export * as settings_tab_ts from './settings/settings'
export * as one_button_prompt from './one_button_prompt/one_button_prompt'
export * as enum_ts from './util/ts/enum'
export { toJS } from 'mobx'

View File

@ -0,0 +1,14 @@
import { observable } from 'mobx'
import { host } from 'uxp'
interface GlobalStore {
Locale: 'zh_CN' | 'en_US'
}
const initialLocale =
localStorage.getItem('last_selected_locale') || host.uiLocale
var globalStore = observable<GlobalStore>({
Locale: initialLocale == 'zh_CN' ? initialLocale : 'en_US',
})
export default globalStore

View File

@ -0,0 +1,151 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore, toJS } from '../main/astore'
import { Grid } from '../util/grid'
import { io, settings_tab } from '../util/oldSystem'
import { MoveToCanvasSvg, PenSvg } from '../util/elements'
import { ErrorBoundary } from '../util/errorBoundary'
import Locale from '../locale/locale'
declare let g_ui_settings_object: any
export const store = new AStore({
images: [],
refresh: false,
width: 50,
height: 50,
scale: 1,
metadata_jsons: [],
})
async function moveHistoryImageToLayer(
base64_image: string,
selection_info: any
) {
try {
const to_x = selection_info?.left
const to_y = selection_info?.top
const width = selection_info?.width
const height = selection_info?.height
await io.IO.base64ToLayer(
base64_image,
'History Image',
to_x,
to_y,
width,
height
)
} catch (e) {
console.warn(e)
}
}
function getHistoryMetadata(metadata_json: any) {
//auto fill the ui with metadata
// const metadata_json = JSON.parse(img.dataset.metadata_json_string)
console.log('metadata_json: ', metadata_json)
// document.querySelector('#tiSeed').value = metadata_json.Seed
//extract auto_metadata into the preset metadata
function convertAutoMetadataToPresset(metadata_json: any) {
metadata_json['seed'] = metadata_json?.auto_metadata?.Seed
}
convertAutoMetadataToPresset(metadata_json)
const b_use_original_prompt = settings_tab.getUseOriginalPrompt()
if (b_use_original_prompt) {
metadata_json['prompt'] = metadata_json?.original_prompt
? metadata_json['original_prompt']
: metadata_json['prompt']
metadata_json['negative_prompt'] =
metadata_json?.original_negative_prompt
? metadata_json['original_negative_prompt']
: metadata_json['negative_prompt']
} else {
metadata_json['prompt'] = metadata_json['prompt']
metadata_json['negative_prompt'] = metadata_json['negative_prompt']
}
// document.querySelector('#historySeedLabel').textContent =
// metadata_json?.seed
g_ui_settings_object.autoFillInSettings(toJS(metadata_json))
}
const History = observer(() => {
return (
<div style={{ width: '100%' }}>
{/* {store.data.refresh} */}
<sp-slider
min={85}
max={300}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
const new_value = event.target.value
store.updateProperty('height', new_value)
store.updateProperty('width', new_value)
}}
show-value="true"
value={100}
>
<sp-label slot="label">Image Size:</sp-label>
</sp-slider>
<Grid
// thumbnails_data={store.data.images?.map((base64: string) =>
// base64
// ? 'data:image/png;base64,' + base64
// : 'https://source.unsplash.com/random'
// )}
thumbnails={store.data.thumbnails?.map((base64: string) =>
base64
? 'data:image/png;base64,' + base64
: 'https://source.unsplash.com/random'
)}
width={store.data.width}
height={store.data.height}
action_buttons={[
{
ComponentType: PenSvg,
callback: (index: number) => {
try {
console.log(
store.toJsFunc().data.metadata_jsons[index]
)
getHistoryMetadata(
store.data.metadata_jsons[index]
)
} catch (e) {
console.warn(e)
}
},
title: Locale('Copy Metadata to Settings'),
},
{
ComponentType: MoveToCanvasSvg,
callback: (index: number) => {
moveHistoryImageToLayer(
store.data.images[index],
store.data.metadata_jsons[index][
'selection_info'
]
)
},
title: Locale('Copy Image to Canvas'),
},
]}
></Grid>
</div>
)
})
const gridContainerNode = document.getElementById('divHistoryImagesContainer')!
const gridRoot = ReactDOM.createRoot(gridContainerNode)
gridRoot.render(
<React.StrictMode>
<ErrorBoundary>
<History></History>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -0,0 +1,70 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { Grid } from '../util/grid'
import { MoveToCanvasSvg } from '../util/elements'
import { io } from '../util/oldSystem'
import { ErrorBoundary } from '../util/errorBoundary'
export const store = new AStore({
images: [],
refresh: false,
width: 50,
height: 50,
})
async function urlToCanvas(url: string) {
const image_file_name = 'search_image_temp.png'
await io.IO.urlToLayer(url, image_file_name)
}
const ImageSearch = observer(() => {
console.log('rendered')
return (
<div>
<sp-slider
min={85}
max={300}
onInput={(event: React.ChangeEvent<HTMLInputElement>) => {
const new_value = event.target.value
store.updateProperty('height', new_value)
store.updateProperty('width', new_value)
}}
show-value="true"
>
<sp-label slot="label">Image Size:</sp-label>
</sp-slider>
<Grid
// thumbnails_data={store.data.images}
thumbnails={store.data.thumbnails}
width={store.data.width}
height={store.data.height}
action_buttons={[
{
ComponentType: MoveToCanvasSvg,
callback: (index: number) => {
urlToCanvas(store.data.images[index])
},
title: 'Copy Image to Canvas',
},
]}
></Grid>
</div>
)
})
const gridContainerNode = document.getElementById(
'divImageSearchImagesContainer'
// 'search_second_panel'
)!
const gridRoot = ReactDOM.createRoot(gridContainerNode)
let images: string[] = []
gridRoot.render(
<React.StrictMode>
<ErrorBoundary>
<ImageSearch></ImageSearch>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -0,0 +1,98 @@
import { reaction } from 'mobx'
import globalStore from '../globalstore'
import Locale from './locale'
const elemSelectorForLocale = {
// tab bar
'#sp-stable-diffusion-ui-tab sp-label': 'Stable Diffusion',
'#sp-viewer-tab sp-label': 'Viewer',
'#sp-control_net-tab sp-label': 'ControlNet',
'#sp-history-tab sp-label': 'History',
'#sp-lexica-tab sp-label': 'Lexica',
'#sp-image_search-tab sp-label': 'Image Search',
'#sp-prompts-library-tab sp-label': 'Prompts library',
'#sp-horde-tab sp-label': 'Horde',
'#sp-extras-tab sp-label': 'Extras',
'#sp-presets-tab sp-label': 'Presets',
'#sp-settings-tab sp-label': 'Settings',
// viewer tab
'#rgSubTab .rbSubTab': 'Viewer',
'#rbHistoryTab': 'History',
'#rbImageSearch': 'Image Search',
'#rbPromptsLibrary': 'Prompts Library',
'#rbLexica': 'Lexica',
'#viewerSubTab .flexContainer sp-label':
'View your generated images on the canvas',
'#btnSetMaskViewer': 'Set Mask',
'#btnSetInitImageViewer': 'Set Init Image',
'#btnInterruptViewer': 'Interrupt',
// '#btnSelectionArea': 'Selection Area',
// extra tab
'#slThumbnailSize sp-label': 'Thumbnail Size',
'#chSquareThumbnail': 'Square 1:1',
'#btnGenerateUpscale': 'Generate upscale',
'#btnInterruptUpscale': 'Interrupt',
'#progressContainerUpscale sp-label': 'No work in progress',
'#slUpscaler2Visibility .title': 'Upscaler 2 visibility',
'#slGFPGANVisibility .title': 'GFPGAN visibility',
'#slCodeFormerVisibility .title': 'CodeFormer visibility',
'#slCodeFormerWeight .title': 'CodeFormer weight',
// sd tab
'#pViewerProgressBar .lProgressLabel': 'Progress...',
'#btnRefreshModels': 'Refresh',
'#btnUpdate': 'Update',
'#chUsePromptShortcut': 'prompt shortcut',
'#btnInterrupt': 'Interrupt',
'#bSetInitImage': 'Image',
'#bSetInitImageMask': 'Mask',
'#batchNumberSdUiTabContainer sp-label': 'Batch Size',
'#batchCountSdUiTabContainer sp-label': 'Batch count',
'#rbSelectionModeLabel': 'Selection Mode',
'#selectionModeGroup [value=ratio]': 'ratio',
'#selectionModeGroup [value=precise]': 'precise',
'#selectionModeGroup [value=ignore]': 'ignore',
'#slCfgScale .title': 'CFG Scale',
'#slImageCfgScale .title': 'Image CFG Scale',
'#slMaskBlur sp-label': 'Mask blur',
'#slMaskExpansion sp-label': 'Mask Expansion',
'#slInpaintingMaskWeight .title': 'Inpainting conditioning mask strength',
'#slInpainting_fill .title': 'Masked content',
'#slInpainting_fill [value=0]': 'fill',
'#slInpainting_fill [value=1]': 'original',
'#slInpainting_fill [value=2]': 'latent noise',
'#slInpainting_fill [value=3]': 'latent nothing',
'#chInpaintFullRes': 'Inpaint at Full Res',
'#chRestoreFaces': 'Restore Faces',
'#chHiResFixs': 'Highres. fix',
'#HiResDiv .title': 'Upscaler',
'#HiResStep': 'Hires steps',
'#hrScaleSlider .title': 'Hires Scale',
'#hrDenoisingStrength .title': 'High Res Denoising Strength',
'#hrWidth': 'Hi Res Output Width',
'#hrHeight': 'Hi Res Output Height',
'#lNameInpaintPdding': 'Inpaint Padding',
'#btnRandomSeed': 'Random',
'#btnLastSeed': 'Last',
'#sampler_group sp-label': 'Sampling method',
'#sdLabelSeed': 'Seed',
'#collapsible': 'Show Samplers',
'#slHeight .title': 'Height',
'#slWidth .title': 'Width',
'#sdLabelSampleStep': 'Sampling Steps',
}
function renderLocale(locale: string) {
Object.keys(elemSelectorForLocale).forEach((selector) => {
const elem = document.querySelector(selector)
if (elem) {
// @ts-ignore
elem.innerHTML = Locale(elemSelectorForLocale[selector])
}
})
}
reaction(() => globalStore.Locale, renderLocale)
renderLocale(globalStore.Locale)

View File

@ -0,0 +1,52 @@
import globalStore from '../globalstore'
import type zhHans from '../../i18n/zh_CN/sd-official.json'
import type zhHansForPSPlugin from '../../i18n/zh_CN/ps-plugin.json'
import { lstatSync, readFileSync } from 'fs'
const localeFileCache: any = {}
function isExists(path: string): boolean {
try {
lstatSync(path)
// console.log(path, 'exists')
return true
} catch (e) {
// console.log(path, 'not exists')
return false
}
}
export default function Locale(
key: keyof typeof zhHans | keyof typeof zhHansForPSPlugin | any
): string {
const locale = globalStore.Locale
const sdOfficialJSONPath = `plugin:/i18n/${locale}/sd-official.json`
let sdOfficialTranslate = localeFileCache[sdOfficialJSONPath]
if (!localeFileCache[sdOfficialJSONPath] && isExists(sdOfficialJSONPath)) {
console.log('readFile')
sdOfficialTranslate = JSON.parse(
readFileSync(sdOfficialJSONPath, 'utf-8')
)
localeFileCache[sdOfficialJSONPath] = sdOfficialTranslate
}
const psPluginJSONPath = `plugin:/i18n/${locale}/ps-plugin.json`
let psPluginTranslate = localeFileCache[psPluginJSONPath]
if (!localeFileCache[psPluginJSONPath] && isExists(psPluginJSONPath)) {
console.log('readFile')
psPluginTranslate = JSON.parse(readFileSync(psPluginJSONPath, 'utf-8'))
localeFileCache[psPluginJSONPath] = psPluginTranslate
}
let res = ''
//@ts-ignore
if (sdOfficialTranslate && key in sdOfficialTranslate)
res = sdOfficialTranslate[key]
//@ts-ignore
if (psPluginTranslate && key in psPluginTranslate)
res = psPluginTranslate[key]
res = res || key
return res
}

View File

@ -1,4 +1,5 @@
import { makeAutoObservable, reaction, toJS } from 'mobx'
export { toJS } from 'mobx'
// import { Provider, inject, observer } from 'mobx-react'
export class AStore {
data: any
@ -20,8 +21,16 @@ export class AStore {
updateProperty(key: keyof any, value: any) {
;(this.data as any)[key] = value
}
updatePropertyArray(key: keyof any, value: any) {
this.data[key] = this.data[key].concat(value)
}
updatePropertyArrayRemove(key: keyof any, valueToRemove: any) {
this.data[key] = this.data[key].filter(
(item: any) => item !== valueToRemove
)
}
toJsFunc() {
return toJS(this)
}
}
}

View File

@ -1,11 +1,14 @@
import React, { ReactEventHandler } from 'react'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from './astore'
import { SpMenu } from '../../ultimate_sd_upscaler/src/elements'
import { SpMenu } from '../util/elements'
import { api, python_replacement } from '../util/oldSystem'
const { getExtensionUrl } = python_replacement
import '../locale/locale-for-old-html'
import { ErrorBoundary } from '../util/errorBoundary'
import { getExtensionUrl } from '../../utility/sdapi/python_replacement'
import * as api from '../../utility/api'
declare let g_sd_url: string
// class SDStore extends AStore {
// constructor(data: any) {
@ -76,8 +79,6 @@ export class VAEComponent extends React.Component<{
const vaeContainerNode = document.getElementById('settingsVAEContainer')!
const vaeRoot = ReactDOM.createRoot(vaeContainerNode)
// let vae_model_list = ['None']
async function requestGetVAE() {
const full_url = `${g_sd_url}/sdapi/v1/options`
@ -85,23 +86,28 @@ async function requestGetVAE() {
return options?.sd_vae
}
export async function populateVAE() {
const extension_url = getExtensionUrl()
try {
const extension_url = getExtensionUrl()
const full_url = `${extension_url}/vae/list`
const vae_models = await api.requestGet(full_url)
const full_url = `${extension_url}/vae/list`
const vae_models = (await api.requestGet(full_url)) || []
console.log('populateVAE vae_models: ', vae_models)
store.updateProperty('vae_model_list', vae_models)
console.log('populateVAE vae_models: ', vae_models)
store.updateProperty('vae_model_list', vae_models)
const current_vae = await requestGetVAE()
if (current_vae && vae_models.includes(current_vae)) {
store.updateProperty('current_vae', current_vae)
const current_vae = await requestGetVAE()
if (current_vae && vae_models.includes(current_vae)) {
store.updateProperty('current_vae', current_vae)
}
} catch (e) {
console.warn('populateVAE():', e)
}
}
// populateVAE()
vaeRoot.render(
<React.StrictMode>
<VAEComponent></VAEComponent>
<ErrorBoundary>
<VAEComponent></VAEComponent>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -0,0 +1,300 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Collapsible from '../after_detailer/after_detailer'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { requestPost, requestGet, isScriptInstalled } from '../util/ts/api'
import {
ScriptInstallComponent,
SpMenu,
SpSliderWithLabel,
} from '../util/elements'
import { ErrorBoundary } from '../util/errorBoundary'
declare let g_sd_url: string
export const store = new AStore({
prompts: [],
number: 3,
prompt_complexity: 5,
subjects: [],
artists: [],
imagetypes: [],
subject: 'all',
artist: 'all',
imagetype: 'all',
script_name: 'one button prompt',
is_installed: false,
})
export async function requestRandomPrompts(
number_of_prompts: number = 1,
insanitylevel: number = 5,
subject: string = 'all',
artist: string = 'all',
imagetype: string = 'all'
) {
const payload = {
numberofprompts: number_of_prompts,
insanitylevel: insanitylevel,
forcesubject: subject,
artists: artist,
imagetype: imagetype,
onlyartists: false,
antivalues: '',
prefixprompt: '',
suffixprompt: '',
promptcompounderlevel: '1',
seperator: 'comma',
givensubject: '',
smartsubject: true,
giventypeofimage: '',
imagemodechance: 20,
}
try {
const full_url = `${g_sd_url}/one_button_prompt/prompt/random`
const randomPrompts = (await requestPost(full_url, payload))?.prompts
return randomPrompts
} catch (e) {
console.warn(e)
}
}
function autoResize(textarea: any) {
// const textarea = event.target
let measure = document.getElementById('measure')!
if (!measure) {
measure = document.createElement('div')
measure.setAttribute('id', 'measure')
measure.style.visibility = 'hidden'
measure.style.whiteSpace = 'pre-wrap'
measure.style.position = 'absolute'
measure.style.fontSize = '14px'
// measure.style.paddingBottom = '10px'
// measure.style.paddingTop = '10px'
// measure.style.lineHeight = '14px'
document.body.appendChild(measure)
}
measure.style.width = textarea.offsetWidth + 'px'
// getComputedStyle(textarea).width
measure.textContent = textarea.value
try {
clearTimeout(g_style_timeout)
g_style_timeout = setTimeout(() => {
let height = measure.offsetHeight
//height between [60,450]
height = Math.max(60, height)
height = Math.min(450, height)
textarea.style.height = height + 'px'
}, 300)
} catch (e) {
console.warn(e)
}
}
let g_timeout: any
let g_style_timeout: any
function handleInput(event: any) {
try {
// clearTimeout(g_timeout)
// g_timeout = setTimeout(() => autoResize(event.target), 1000)
autoResize(event.target)
} catch (e) {
console.warn(e)
}
}
export async function requestConfig() {
try {
const full_url = `${g_sd_url}/one_button_prompt/config`
const ui_config = await requestGet(full_url)
if (ui_config) {
store.data.subjects = ui_config?.subjects ?? []
store.data.artists = ui_config?.artists ?? []
store.data.imagetypes = ui_config?.imagetypes ?? []
}
return ui_config
} catch (e) {
console.warn(e)
}
}
@observer
class OneButtonPrompt extends React.Component {
async initScript() {
const is_installed = await isScriptInstalled(store.data.script_name)
await store.updateProperty('is_installed', is_installed)
}
async componentDidMount() {
await requestConfig()
await this.initScript()
}
renderContainer() {
return (
<div>
<div>
<SpSliderWithLabel
show-value={false}
steps={1}
out_min={1}
out_max={10}
output_value={store.data.prompt_complexity}
title={`Higher levels increases complexity and randomness of generated
prompt`}
label={`Prompt Complexity`}
onSliderInput={(output_value: number) => {
store.data.prompt_complexity = output_value
}}
/>
<sp-label>Subject:</sp-label>
<SpMenu
title="subjects"
items={store.data.subjects}
label_item="Select a Subject"
selected_index={store.data.subjects.indexOf(
store.data.subject
)}
onChange={(id: any, value: any) => {
// console.log('onChange value: ', value)
store.updateProperty('subject', value.item)
}}
></SpMenu>
<sp-label>Artist:</sp-label>
<SpMenu
title="artists"
items={store.data.artists}
label_item="Select an Artist"
selected_index={store.data.artists.indexOf(
store.data.artist
)}
onChange={(id: any, value: any) => {
// console.log('onChange value: ', value)
store.updateProperty('artist', value.item)
}}
></SpMenu>
<sp-label>Image Type:</sp-label>
<SpMenu
title="image types"
items={store.data.imagetypes}
label_item="Select an Image Type"
selected_index={store.data.imagetypes.indexOf(
store.data.imagetype
)}
onChange={(id: any, value: any) => {
// console.log('onChange value: ', value)
store.updateProperty('imagetype', value.item)
}}
></SpMenu>
<div
style={{
display: 'flex',
justifyContent: 'space-between',
width: '100%',
marginTop: '5px',
}}
>
<button
style={{ float: 'right' }}
className="btnSquare"
onClick={async () => {
const prompt_complexity =
store.data.prompt_complexity ?? 5
const subject = store.data.subject ?? 'all'
const artist = store.data.artist ?? 'all'
const imagetype = store.data.imagetype ?? 'all'
store.data.prompts = await requestRandomPrompts(
3,
prompt_complexity,
subject,
artist,
imagetype
)
}}
>
Random Prompts
</button>
</div>
</div>
{store.data.prompts.map((prompt: string, index: number) => {
return (
<div
key={`prompt-area-${index}`}
style={{
border: '2px solid #6d6c6c',
padding: '3px',
}}
>
<button
className="btnSquare"
style={{ textAlign: 'right' }}
onClick={() => {
//@ts-ignore
document.querySelector('#taPrompt').value =
prompt
}}
>
use
</button>
<sp-textarea
onInput={(event: any) => {
handleInput(event)
store.data.prompts[index] =
event.target.value
}}
placeholder={`random prompt ${index}`}
value={prompt}
></sp-textarea>
</div>
)
})}
</div>
)
}
render() {
return (
<div>
{store.data.is_installed ? (
<div style={{ padding: '4px' }}>
{this.renderContainer()}
</div>
) : (
<ScriptInstallComponent
onRefreshHandler={async (event: any) => {
console.log(`Refresh ${store.data.script_name}`)
await this.initScript()
}}
></ScriptInstallComponent>
)}
</div>
)
}
}
const containers = document.querySelectorAll('.oneButtonPromptContainer')!
containers.forEach((container) => {
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Collapsible
defaultIsOpen={false}
label={'One Button Prompt'}
>
<OneButtonPrompt />
</Collapsible>
</div>
</ErrorBoundary>
</React.StrictMode>
)
})

230
typescripts/sam/sam.tsx Normal file
View File

@ -0,0 +1,230 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Collapsible from '../after_detailer/after_detailer'
import { observer } from 'mobx-react'
import { isScriptInstalled } from '../util/ts/api'
import { api, general, io, psapi, selection } from '../util/oldSystem'
import { Grid } from '../util/grid'
import { AStore } from '../main/astore'
import {
MoveToCanvasSvg,
PenSvg,
ScriptInstallComponent,
} from '../util/elements'
import {
applyMaskFromBlackAndWhiteImage,
selectionFromBlackAndWhiteImage,
} from '../util/ts/selection'
import { app } from 'photoshop'
import { ErrorBoundary } from '../util/errorBoundary'
import Locale from '../locale/locale'
import { settings_tab_ts } from '../entry'
declare let g_sd_url: string
export async function getSamMap(base64: string, prompt: string) {
// const full_url = `${g_sd_url}/sam/dino-predict`
// const payload = {
// dino_model_name: 'GroundingDINO_SwinT_OGC (694MB)',
// input_image: base64,
// text_prompt: 'the dog',
// box_threshold: 0.3,
// }
const full_url = `${g_sd_url}/sam/sam-predict`
const payload = {
sam_model_name: 'sam_vit_h_4b8939.pth',
input_image: base64,
sam_positive_points: [],
sam_negative_points: [],
dino_enabled: true,
dino_model_name: 'GroundingDINO_SwinT_OGC (694MB)',
dino_text_prompt: prompt,
dino_box_threshold: 0.3,
dino_preview_checkbox: false,
dino_preview_boxes_selection: [0],
}
const result = await api.requestPost(full_url, payload)
return result
}
export const store = new AStore({
thumbnails: [],
selection_info_list: [],
prompt: '',
width: 85,
height: 85,
is_installed: false,
script_name: 'segment anything',
})
@observer
export class Sam extends React.Component<{
// store: AStore
}> {
async initScript() {
const is_installed = await isScriptInstalled(store.data.script_name)
await store.updateProperty('is_installed', is_installed)
}
async componentDidMount(): Promise<void> {
await this.initScript()
}
renderScript() {
return (
<div>
<sp-textarea
placeholder="Segment Anything Prompt"
value={store.data.prompt}
onInput={(event: any) => {
store.data.prompt = event.target.value
}}
></sp-textarea>
<button
className="btnSquare"
onClick={async () => {
const selection_info = await psapi.getSelectionInfoExe()
const base64 = await io.getImageFromCanvas()
const result = await getSamMap(
base64,
store.data.prompt
)
const masks = result?.masks ?? []
const masks_urls = []
for (const mask of masks) {
const url =
await io.convertBlackAndWhiteImageToRGBChannels3(
mask
)
masks_urls.push(url)
}
store.updateProperty('thumbnails', masks_urls)
store.updateProperty(
'selection_info_list',
Array(masks_urls.length).fill(selection_info)
)
}}
>
Generate Mask
</button>
<Grid
// thumbnails_data={store.data.images?.map((base64: string) =>
// base64
// ? 'data:image/png;base64,' + base64
// : 'https://source.unsplash.com/random'
// )}
thumbnails={store.data.thumbnails}
width={store.data.width}
height={store.data.height}
action_buttons={[
{
ComponentType: PenSvg,
callback: async (index: number) => {
try {
await psapi.unSelectMarqueeExe()
const base64 = general.base64UrlToBase64(
store.data.thumbnails[index]
)
await selectionFromBlackAndWhiteImage(
base64,
store.data.selection_info_list[index],
settings_tab_ts.store.data
.b_borders_or_corners
)
// try {
// const base64 =
// general.base64UrlToBase64(
// store.data.thumbnails[index]
// )
// await selection.base64ToLassoSelection(
// base64,
// store.data.selection_info_list[
// index
// ]
// )
// } catch (e) {
// console.warn(e)
// }
//@ts-ignore
await eventHandler() // this will trigger the recalculation of the width and height sliders
} catch (e) {
console.warn(e)
}
},
title: Locale('Select Masked Area'),
},
{
ComponentType: MoveToCanvasSvg,
callback: async (index: number) => {
try {
const to_x =
store.data.selection_info_list[index]
?.left
const to_y =
store.data.selection_info_list[index]
?.top
const width =
store.data.selection_info_list[index]
?.width
const height =
store.data.selection_info_list[index]
?.height
await io.IO.base64ToLayer(
general.base64UrlToBase64(
store.data.thumbnails[index]
),
'segment_anything_mask.png',
to_x,
to_y,
width,
height
)
} catch (e) {
console.warn(e)
}
},
title: Locale('Copy Image to Canvas'),
},
]}
></Grid>
</div>
)
}
render() {
return (
<div>
{store.data.is_installed ? (
this.renderScript()
) : (
<ScriptInstallComponent
onRefreshHandler={async (event: any) => {
console.log(`Refresh ${store.data.script_name}`)
await this.initScript()
}}
></ScriptInstallComponent>
)}
</div>
)
}
}
const containers = document.querySelectorAll('.samContainer')
containers.forEach((container) => {
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Collapsible
defaultIsOpen={false}
label={'Segment Anything'}
>
<Sam></Sam>
</Collapsible>
</div>
</ErrorBoundary>
</React.StrictMode>
)
})

View File

@ -0,0 +1,83 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { GenerationModeEnum } from '../util/ts/enum'
import { reaction } from 'mobx'
import { SpCheckBox } from '../util/elements'
import { ErrorBoundary } from '../util/errorBoundary'
export const store = new AStore({
is_lasso_mode: false,
mode: GenerationModeEnum.Txt2Img,
} as { is_lasso_mode: boolean; mode: GenerationModeEnum })
reaction(
() =>
[store.data.is_lasso_mode, store.data.mode] as [
boolean,
GenerationModeEnum
],
([is_lasso_mode, mode]: [boolean, GenerationModeEnum]) => {
if (is_lasso_mode && mode === GenerationModeEnum.Inpaint) {
store.data.mode = GenerationModeEnum.LassoInpaint
} else if (!is_lasso_mode && mode === GenerationModeEnum.LassoInpaint) {
store.data.mode = GenerationModeEnum.Inpaint
}
// if (is_lasso_mode && mode === GenerationModeEnum.Outpaint) {
// store.data.mode = GenerationModeEnum.LassoOutpaint
// } else if (
// !is_lasso_mode &&
// mode === GenerationModeEnum.LassoOutpaint
// ) {
// store.data.mode = GenerationModeEnum.Outpaint
// }
console.log('store.data.is_lasso_mode:', store.data.is_lasso_mode)
console.log('store.data.mode:', store.data.mode)
}
)
const handleLassoModeChange = (event: any) => {
store.updateProperty('is_lasso_mode', event.target.checked)
}
const Modes = observer(() => {
const renderLassoModeElement = () => {
if (
[
GenerationModeEnum.Inpaint,
// GenerationModeEnum.Outpaint,
GenerationModeEnum.LassoInpaint,
// GenerationModeEnum.LassoOutpaint,
].includes(store.data.mode)
) {
return (
<SpCheckBox
// style={{ marginRight: '10px' }}
onChange={handleLassoModeChange}
checked={store.data.is_lasso_mode}
// id={`chEnableControlNet_${this.props.index}`}
value={store.data.is_lasso_mode}
>
Lasso Mode
</SpCheckBox>
// <sp-checkbox checked={store.data.is_lasso_mode ? true : void 0}>
// lasso mode
// </sp-checkbox>
)
}
}
return <div>{renderLassoModeElement()}</div>
})
const container = document.getElementById('reactModesContainer')!
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<Modes />
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -0,0 +1,367 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { sd_tab_ts, session_ts, viewer } from '../entry'
import './style/generate.css'
import { io, note, psapi, selection } from '../util/oldSystem'
import { GenerationModeEnum } from '../util/ts/enum'
import { initializeBackground } from '../util/ts/document'
import Locale from '../locale/locale'
import { ErrorBoundary } from '../util/errorBoundary'
declare let g_automatic_status: any
declare let g_current_batch_index: number
//example: take 'oI' in 'LassoInpaint' and replace it with 'o I' thus creating 'Lasso Inpaint'
const modeDisplayNames = Object.fromEntries(
Object.entries(GenerationModeEnum).map(([key, value]) => [
value,
key.replace(/([a-z])([A-Z])/g, '$1 $2'),
])
)
const GenerateButtons = observer(() => {
return (
<div>
<button
id="btnNewGenerate"
className="btnSquare generateButtonMargin generateColor"
onClick={handleGenerateBatch}
style={{
display: session_ts.store.data.can_generate
? void 0
: 'none',
}}
>
Generate {modeDisplayNames[sd_tab_ts.store.data.mode]}
</button>
{session_ts.store.data.can_generate ? (
<button
onClick={handleGenerateMoreBatch}
disabled={
session_ts.store.data.can_generate_more ? void 0 : true
}
id="btnNewGenerateMore"
className={
'btnSquare generateButtonMargin generateMoreColor' +
(session_ts.store.data.can_generate_more
? ''
: 'disableBtn')
}
style={{
display: session_ts.store.data.can_generate_more
? 'inline-block'
: 'none',
}}
>
Generate more
</button>
) : (
void 0
)}
{!session_ts.store.data.can_generate ? (
<button
onClick={handleInterrupt}
id="btnNewInterrupt"
className="btnSquare generateButtonMargin"
>
Interrupt
</button>
) : (
void 0
)}
</div>
)
})
const ToolbarGenerateButtons = observer(() => {
const button_style: any = {
width: '30px',
height: '30px',
marginBottom: '3px',
}
const generate_display = session_ts.store.data.can_generate
? 'inline-flex'
: 'none'
const generate_more_display =
session_ts.store.data.can_generate &&
session_ts.store.data.can_generate_more
? 'inline-flex'
: 'none'
const interrupt_display = session_ts.store.data.can_generate
? 'none'
: 'inline-flex'
return (
<div>
<button
title={Locale('Generate')}
className="btnSquare generateColor"
onClick={handleGenerate}
style={{ ...button_style, display: generate_display }}
>
G
</button>
<button
title={Locale('Generate More')}
onClick={handleGenerateMore}
className={'btnSquare generateMoreColor'}
style={{
...button_style,
display: generate_more_display,
}}
>
M
</button>
<button
title={Locale('Interrupt')}
onClick={handleInterrupt}
className="btnSquare"
style={{
...button_style,
display: interrupt_display,
}}
>
I
</button>
</div>
)
})
const canStartSession = async () => {
// check for automatic1111 connection: fail if false
// check for automatic1111 api: fail if false
// check for having a background layer: create if false
// check for artboard: fail if true
// check for selection: fail if false
let can_start_session = false
try {
const selection_info = await psapi.getSelectionInfoExe()
if (selection_info) {
session_ts.Session.endSession()
can_start_session = true
} else {
can_start_session = await note.Notification.inactiveSelectionArea(
session_ts.store.data.is_active,
'Reuse Selection'
)
if (can_start_session) {
//end current session and start a new one
session_ts.Session.endSession()
await psapi.reSelectMarqueeExe(
session_ts.store.data.selectionInfo
)
}
}
//@ts-ignore
g_automatic_status = await checkAutoStatus()
//@ts-ignore
await displayNotification(g_automatic_status)
} catch (e) {
console.warn(e)
}
return can_start_session
}
const resetBatch = () => {
g_current_batch_index = -1
session_ts.store.data.is_interrupted = false
}
// declare let g_sd_mode: any
const handleGenerate = async () => {
//save user input for later
//1) save selection as channel
await selection.selectionToChannel('mask')
await initializeBackground() //fix background if there is a need
console.log('mode: ', sd_tab_ts.store.data.mode)
try {
if (!(await canStartSession())) {
return void 0
}
var { output_images, response_json } =
await session_ts.Session.generate(sd_tab_ts.store.data.mode)
if (session_ts.store.data.is_interrupted) {
return void 0
}
const thumbnail_list = []
for (const base64 of output_images) {
const thumbnail = await io.createThumbnail(base64, 300)
thumbnail_list.push(thumbnail)
}
viewer.store.updateProperty('thumbnails', thumbnail_list)
viewer.store.updateProperty('images', output_images)
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(session_ts.store.data.mode)
) {
viewer.mask_store.updateProperty(
'output_images_masks',
Array(output_images.length).fill(
session_ts.store.data.expanded_mask
)
)
}
console.log(
'session_ts.store.toJsFunc(): ',
session_ts.store.toJsFunc()
)
} catch (e) {
console.error(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
}
}
const handleGenerateMore = async () => {
try {
var { output_images, response_json } =
await session_ts.Session.generateMore()
if (session_ts.store.data.is_interrupted) {
return void 0
}
const thumbnail_list = []
for (const base64 of output_images) {
const thumbnail = await io.createThumbnail(base64, 300)
thumbnail_list.push(thumbnail)
}
viewer.store.data.thumbnails = [
...viewer.store.data.thumbnails,
...thumbnail_list,
]
viewer.store.data.images = [
...viewer.store.data.images,
...output_images,
]
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(session_ts.store.data.mode)
) {
viewer.mask_store.updatePropertyArray(
'output_images_masks',
Array(output_images.length).fill(
session_ts.store.data.expanded_mask
)
)
}
// viewer.store.updateProperty('images', output_images)
// console.log(
// 'session_ts.store.toJsFunc(): ',
// session_ts.store.toJsFunc()
// )
} catch (e) {
console.error(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
}
}
const handleGenerateBatch = async () => {
try {
const numberOfBatchCount: number = parseInt(
//@ts-ignore
document.querySelector('#tiNumberOfBatchCount').value
)
await handleGenerate() //first generation is always use handleGenerate
for (
let i = 1;
i < numberOfBatchCount && !session_ts.store.data.is_interrupted;
i++
) {
// if (g_batch_count_interrupt_status === true) {
// break
// }
// g_current_batch_index = i
await handleGenerateMore()
}
resetBatch()
// g_batch_count_interrupt_status = false // reset for next generation
// g_current_batch_index = 0 // reset curent_batch_number
} catch (e) {
console.error(e)
}
}
const handleGenerateMoreBatch = async () => {
try {
const numberOfBatchCount: number = parseInt(
//@ts-ignore
document.querySelector('#tiNumberOfBatchCount').value
)
// await handleGenerateMore() //first generation is always use handleGenerate
for (
let i = 0;
i < numberOfBatchCount && !session_ts.store.data.is_interrupted;
i++
) {
// if (g_batch_count_interrupt_status === true) {
// break
// }
// g_current_batch_index = i
await handleGenerateMore()
}
// g_batch_count_interrupt_status = false // reset for next generation
// g_current_batch_index = 0 // reset curent_batch_number
resetBatch()
} catch (e) {
console.error(e)
}
}
const handleInterrupt = async () => {
try {
// debugger
await session_ts.Session.interrupt()
} catch (e) {
console.error(e)
}
}
const container = document.getElementById('generateButtonsContainer')!
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<GenerateButtons></GenerateButtons>
</ErrorBoundary>
</React.StrictMode>
)
const extraContainer = document.getElementById('extraGenerateButtonsContainer')!
const extraRoot = ReactDOM.createRoot(extraContainer)
extraRoot.render(
<React.StrictMode>
<ErrorBoundary>
<GenerateButtons></GenerateButtons>
</ErrorBoundary>
</React.StrictMode>
)
const toolBarButtonsContainer = document.getElementById(
'toolbarGenerateButtonsContainer'
)!
const toolBarButtonsContainerRoot = ReactDOM.createRoot(toolBarButtonsContainer)
toolBarButtonsContainerRoot.render(
<React.StrictMode>
<ErrorBoundary>
<ToolbarGenerateButtons></ToolbarGenerateButtons>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -0,0 +1,597 @@
import { control_net, scripts, session_ts } from '../entry'
import {
html_manip,
io,
layer_util,
psapi,
python_replacement,
selection,
session,
} from '../util/oldSystem'
import { core } from 'photoshop'
import {
getEnableControlNet,
getModuleDetail,
mapPluginSettingsToControlNet,
} from '../controlnet/entry'
const executeAsModal = core.executeAsModal
declare let g_inpaint_mask_layer: any
declare let g_sd_url: any
declare let g_controlnet_max_models: any
declare let g_generation_session: any
declare let g_last_seed: any
interface SessionData {
init_image?: string
mask?: string
selectionInfo?: any
}
async function saveOutputImagesToDrive(images_info: any, settings: any) {
const base64OutputImages = [] //delete all previouse images, Note move this to session end ()
let index = 0
for (const image_info of images_info) {
const path = image_info['path']
const base64_image = image_info['base64']
base64OutputImages[index] = base64_image
const [document_name, image_name] = path.split('/')
await io.saveFileInSubFolder(base64_image, document_name, image_name) //save the output image
const json_file_name = `${image_name.split('.')[0]}.json`
settings['auto_metadata'] = image_info?.auto_metadata
await io.saveJsonFileInSubFolder(
settings,
document_name,
json_file_name
) //save the settings
index += 1
}
g_last_seed =
images_info?.length > 0 ? images_info[0]?.auto_metadata?.Seed : '-1'
return base64OutputImages
}
class Mode {
constructor() {}
async initializeSession(): Promise<SessionData> {
return {}
}
static async generate(settings: any): Promise<{
output_images: any
response_json: any
}> {
return { output_images: [], response_json: null }
}
//return settings that would be used by the restApi
static async getSettings(session_data: any) {
const ui_settings = await session.getSettings(session_data)
return ui_settings
}
//take the output from restapi and formate it to a standard formate the plugin ui understand
static async processOutput(images_info: any, settings: any): Promise<any> {
const base64OutputImages = await saveOutputImagesToDrive(
images_info,
settings
)
return base64OutputImages
}
static async interrupt() {
return await this.requestInterrupt()
}
static async requestInterrupt() {
const full_url = `${g_sd_url}/sdapi/v1/interrupt`
try {
console.log('requestInterrupt: ')
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
})
// console.log('interrupt request:', request)
let json = await request.json()
return json
} catch (e) {
console.warn(e)
}
}
}
export class Txt2ImgMode extends Mode {
// constructor() {
// }
static async initializeSession(): Promise<SessionData> {
const selectionInfo = await psapi.getSelectionInfoExe()
const init_image = ''
const mask = ''
return { selectionInfo, init_image, mask }
}
//return settings that would be used by the restApi
// static async getSettings() {
// const ui_settings = await session.getSettings()
// return ui_settings
// }
//@ts-ignore
static async requestTxt2Img(payload) {
try {
console.log('requestTxt2Img(): about to send a fetch request')
let json = await python_replacement.txt2ImgRequest(payload)
console.log('requestTxt2Img json:', json)
return json
} catch (e) {
console.warn(e)
return {}
}
}
//REFACTOR: reuse the same code for (requestControlNetTxt2Img,requestControlNetImg2Img)
static async requestControlNetTxt2Img(plugin_settings: any) {
console.log('requestControlNetTxt2Img: ')
const full_url = `${g_sd_url}/sdapi/v1/txt2img`
const control_net_settings =
mapPluginSettingsToControlNet(plugin_settings)
let control_networks = []
// let active_control_networks = 0
for (let index = 0; index < g_controlnet_max_models; index++) {
if (!getEnableControlNet(index)) {
control_networks[index] = false
continue
}
control_networks[index] = true
if (
!control_net_settings['controlnet_units'][index]['input_image']
) {
//@ts-ignore
app.showAlert('you need to add a valid ControlNet input image')
throw 'you need to add a valid ControlNet input image'
}
if (!control_net_settings['controlnet_units'][index]['module']) {
//@ts-ignore
app.showAlert('you need to select a valid ControlNet Module')
throw 'you need to select a valid ControlNet Module'
}
if (
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index][
'module'
]
].model_free) ||
control_net_settings['controlnet_units'][index][
'model'
].toLowerCase() === 'none'
) {
//@ts-ignore
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
}
}
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(control_net_settings),
})
let json = await request.json()
console.log('json:', json)
//update the mask in controlNet tab
const numOfImages = json['images'].length
let numberOfAnnotations =
numOfImages - session_ts.store.data.ui_settings.batch_size
if (numberOfAnnotations < 0) numberOfAnnotations = 0
const base64_mask = json['images'].slice(
numOfImages - numberOfAnnotations
)
let mask_index = 0
for (let index = 0; index < control_networks.length; index++) {
if (
control_networks[index] == false ||
mask_index >= numberOfAnnotations
)
continue
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}
// g_generation_session.controlNetMask = base64_mask
const standard_response =
await python_replacement.convertToStandardResponse(
control_net_settings,
json['images'].slice(0, numOfImages - numberOfAnnotations),
plugin_settings['uniqueDocumentId']
)
console.log('standard_response:', standard_response)
return standard_response
}
//REFACTOR: move to generation.js
static async generate(
settings: any
): Promise<{ output_images: any; response_json: any }> {
let response_json
let output_images
try {
// const b_enable_control_net = control_net.getEnableControlNet()
const b_enable_control_net = control_net.isControlNetModeEnable()
if (b_enable_control_net) {
//use control net
if (session_ts.store.data.generation_number === 1) {
session_ts.store.data.controlnet_input_image =
await io.getImg2ImgInitImage()
}
// console.log(
// 'session_ts.store.data.controlnet_input_image: ',
// session_ts.store.data.controlnet_input_image
// )
response_json = await this.requestControlNetTxt2Img(settings)
} else {
response_json = await this.requestTxt2Img(settings)
}
output_images = await this.processOutput(
response_json.images_info,
settings
)
} catch (e) {
console.warn(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
}
return { output_images, response_json }
}
//take the output from restapi and formate it to a standard formate the plugin ui understand
static async processOutput(images_info: any, settings: any): Promise<any> {
const base64OutputImages = await saveOutputImagesToDrive(
images_info,
settings
)
return base64OutputImages
}
}
export class Img2ImgMode extends Mode {
constructor() {
super()
}
//REFACTOR: reuse the same code for (requestControlNetTxt2Img,requestControlNetImg2Img)
static async requestControlNetImg2Img(plugin_settings: any) {
const full_url = `${g_sd_url}/sdapi/v1/img2img`
const control_net_settings =
mapPluginSettingsToControlNet(plugin_settings)
// let control_networks = 0
let control_networks = []
for (let index = 0; index < g_controlnet_max_models; index++) {
if (!getEnableControlNet(index)) {
control_networks[index] = false
continue
}
control_networks[index] = true
if (
!control_net_settings['controlnet_units'][index]['input_image']
) {
//@ts-ignore
app.showAlert('you need to add a valid ControlNet input image')
throw 'you need to add a valid ControlNet input image'
}
if (!control_net_settings['controlnet_units'][index]['module']) {
//@ts-ignore
app.showAlert('you need to select a valid ControlNet Module')
throw 'you need to select a valid ControlNet Module'
}
if (
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index][
'module'
]
].model_free) ||
control_net_settings['controlnet_units'][index][
'model'
].toLowerCase() === 'none'
) {
//@ts-ignore
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
}
}
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(control_net_settings),
// body: JSON.stringify(payload),
})
let json = await request.json()
console.log('json:', json)
//update the mask in controlNet tab
const numOfImages = json['images'].length
let numberOfAnnotations =
numOfImages - session_ts.store.data.ui_settings.batch_size
if (numberOfAnnotations < 0) numberOfAnnotations = 0
// To fix a bug: when Ultimate SD Upscale is active and running, the detection maps wont be retrieved.
// So set its value to 0 to avoid the result images being loaded in the annotation map interface.
if (
scripts.script_store.isInstalled() &&
scripts.script_store.is_active &&
scripts.script_store.selected_script_name !== 'None' &&
scripts.script_store.is_selected_script_available
) {
numberOfAnnotations = 0
}
const base64_mask = json['images'].slice(
numOfImages - numberOfAnnotations
)
let mask_index = 0
for (let index = 0; index < control_networks.length; index++) {
if (
control_networks[index] == false ||
mask_index >= numberOfAnnotations
)
continue
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}
const standard_response =
await python_replacement.convertToStandardResponse(
control_net_settings,
json['images'].slice(0, numOfImages - numberOfAnnotations),
plugin_settings['uniqueDocumentId']
)
console.log('standard_response:', standard_response)
return standard_response
}
static async requestImg2Img(payload: any) {
console.log('requestImg2Img(): about to send a fetch request')
try {
let json = await python_replacement.img2ImgRequest(
g_sd_url,
payload
)
console.log('requestImg2Img json:')
console.dir(json)
return json
} catch (e) {
console.warn(e)
return {}
}
}
static async initializeSession(): Promise<SessionData> {
const selectionInfo = await psapi.getSelectionInfoExe()
const init_image = await io.getImg2ImgInitImage()
const mask = ''
return { selectionInfo, init_image, mask }
}
static async generate(
settings: any
): Promise<{ output_images: any; response_json: any }> {
let response_json
let output_images
try {
//checks on index 0 as if not enabled ignores the rest
const b_enable_control_net = control_net.isControlNetModeEnable()
if (b_enable_control_net) {
//use control net
response_json = await this.requestControlNetImg2Img(settings)
} else {
response_json = await this.requestImg2Img(settings)
}
output_images = await this.processOutput(
response_json.images_info,
settings
)
} catch (e) {
console.warn(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
}
return { output_images, response_json }
}
}
export class InpaintMode extends Img2ImgMode {
constructor() {
super()
}
static async initializeSession() {
const selectionInfo = await psapi.getSelectionInfoExe()
let init_image
let mask
try {
await executeAsModal(
async () => {
if (layer_util.Layer.doesLayerExist(g_inpaint_mask_layer)) {
g_inpaint_mask_layer.opacity = 100
}
},
{ commandName: 'Set Inpaint Layer Opacity to 100%' }
)
const obj = await io.getInpaintInitImageAndMask()
init_image = obj.init_image
mask = obj.mask
} catch (e) {
console.warn(e)
}
return { selectionInfo, init_image, mask }
}
}
export class LassoInpaintMode extends Img2ImgMode {
constructor() {
super()
}
static async initializeSession() {
await selection.channelToSelectionExe('mask')
try {
await executeAsModal(
async () => {
if (layer_util.Layer.doesLayerExist(g_inpaint_mask_layer)) {
g_inpaint_mask_layer.opacity = 100
}
},
{ commandName: 'Set Inpaint Layer Opacity to 100%' }
)
} catch (e) {
console.warn(e)
}
const [init_image, mask] = await selection.inpaintLassoInitImageAndMask(
'mask'
)
const selectionInfo = await psapi.getSelectionInfoExe()
return { selectionInfo, init_image, mask }
}
}
export class OutpaintMode extends Img2ImgMode {
constructor() {
super()
}
static async initializeSession() {
const selectionInfo = await psapi.getSelectionInfoExe()
let init_image
let mask
try {
const obj = await io.getOutpaintInitImageAndMask()
init_image = obj.init_image
mask = obj.mask
} catch (e) {
console.warn(e)
}
return { selectionInfo, init_image, mask }
}
}
export class UpscaleMode extends Img2ImgMode {
static async requestExtraSingleImage(payload: any) {
try {
let json = await python_replacement.extraSingleImageRequest(
g_sd_url,
payload
)
return json
} catch (e) {
console.warn(e)
return {}
}
}
static async getSettings(session_data: any) {
//REFACTOR: move to generation_settings.js
let payload: any = {}
try {
const upscaling_resize = html_manip.getUpscaleSize()
const gfpgan_visibility = html_manip.getGFPGANVisibility()
const codeformer_visibility = html_manip.getCodeFormerVisibility()
const codeformer_weight = html_manip.getCodeFormerWeight()
// const selection_info = await psapi.getSelectionInfoExe()
const selection_info = session_data.selectionInfo
const width = selection_info.width * upscaling_resize
const height = selection_info.height * upscaling_resize
//resize_mode = 0 means "resize to upscaling_resize"
//resize_mode = 1 means "resize to width and height"
payload['resize_mode'] = 0
payload['show_extras_results'] = 0
payload['gfpgan_visibility'] = gfpgan_visibility
payload['codeformer_visibility'] = codeformer_visibility
payload['codeformer_weight'] = codeformer_weight
payload['upscaling_resize'] = upscaling_resize
payload['upscaling_resize_w'] = width
payload['upscaling_resize_h'] = height
payload['upscaling_crop'] = true
const upscaler1 =
//@ts-ignore
document.querySelector('#hrModelsMenuUpscale1').value
payload['upscaler_1'] = upscaler1 === undefined ? 'None' : upscaler1
const upscaler2 =
//@ts-ignore
document.querySelector('#hrModelsMenuUpscale2').value
payload['upscaler_2'] = upscaler2 === undefined ? 'None' : upscaler2
const extras_upscaler_2_visibility =
html_manip.getUpscaler2Visibility()
payload['extras_upscaler_2_visibility'] =
extras_upscaler_2_visibility
payload['upscale_first'] = false
payload['image'] = session_data.init_image
} catch (e) {
console.error(e)
}
return payload
}
static async generate(settings: any): Promise<{
output_images: any
response_json: any
}> {
let response_json
let output_images
try {
response_json = await this.requestExtraSingleImage(settings)
output_images = await this.processOutput(
response_json.images_info,
settings
)
} catch (e) {
console.warn(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
}
return { output_images, response_json }
}
}

View File

@ -0,0 +1,157 @@
import { reaction } from 'mobx'
import { AStore } from '../main/astore'
import { io, layer_util } from '../util/oldSystem'
import Locale from '../locale/locale'
import { session_ts } from '../entry'
import { app, core } from 'photoshop'
const executeAsModal = core.executeAsModal
export const store = new AStore({
progress_layer: null,
timer_id: null,
progress_value: 0,
progress_image: '',
progress_image_height: 0,
progress_label: Locale('Progress..'),
can_update: true,
can_update_progress_layer: true,
})
declare let g_sd_url: string
async function updateProgressImage(progress_base64: string) {
try {
store.data.can_update_progress_layer = false
await executeAsModal(
async (context: any) => {
const history_id = await context.hostControl.suspendHistory({
documentID: app.activeDocument.id, //TODO: change this to the session document id
name: 'Progress Image',
})
await Progress.deleteProgressLayer() // delete the old progress layer
//update the progress image
const selection_info = await session_ts.store.data.selectionInfo
const b_exsit = layer_util.Layer.doesLayerExist(
store.data.progress_layer
)
if (!b_exsit && progress_base64) {
const layer = await io.IO.base64ToLayer(
progress_base64,
'temp_progress_image.png',
selection_info.left,
selection_info.top,
selection_info.width,
selection_info.height
)
store.data.progress_layer = layer // sotre the new progress layer// TODO: make sure you delete the progress layer when the geneeration request end
}
await context.hostControl.resumeHistory(history_id)
},
{ commandName: 'update progress layer' }
)
} catch (e) {
console.warn(e)
} finally {
store.data.can_update_progress_layer = true
if (!store.data.can_update) {
//delete the last progress layer
await Progress.deleteProgressLayer() // delete the old progress layer
}
}
}
reaction(
() => {
return store.data.progress_image
},
async (progress_image) => {
if (store.data.progress_image_height === 0) {
const { width, height } = await io.getImageSize(progress_image)
store.data.progress_image_height = height
}
const b_update_progress_layer: Boolean = (
document.querySelector('.chLiveProgressImageClass') as any
).checked
if (
b_update_progress_layer &&
parseInt(session_ts.store.data.ui_settings?.batch_size) === 1 &&
store.data.can_update_progress_layer &&
store.data.can_update // progress is still active
) {
await updateProgressImage(progress_image)
}
}
)
export async function requestProgress() {
try {
console.log('requestProgress: ')
const full_url = `${g_sd_url}/sdapi/v1/progress?skip_current_image=false`
let request = await fetch(full_url)
const json = await request.json()
// console.log('progress json:', json)
return json
} catch (e) {
console.warn(e)
// console.log('json: ', json)
}
return null
}
export class Progress {
static timer_id: any = null
static async deleteProgressImage() {
// preview.store.updateProperty('image', null)
await this.deleteProgressLayer()
}
static async deleteProgressLayer() {
try {
await layer_util.deleteLayers([store.data.progress_layer]) // delete the old progress layer
} catch (e) {
console.warn(e)
}
}
static startTimer(callback: any, interval: number = 1500) {
store.data.can_update = true
//clear the old timer if it exist
try {
store.data.progress_value = 0
store.data.progress_image = ''
store.data.progress_image_height = 0
store.data.progress_label = ''
this.timer_id = clearInterval(this.timer_id)
} catch (e) {
console.warn(e)
}
this.timer_id = setInterval(callback, interval)
}
static async endTimer(callback: any) {
try {
this.timer_id = clearInterval(this.timer_id)
store.data.can_update = false
} catch (e) {
console.warn(e)
}
try {
if (callback?.constructor.name === 'AsyncFunction') {
await callback() // may cause an issue if this an async
} else {
callback() // may cause an issue if this an async
}
} catch (e) {
console.warn(e)
}
}
}
export class ProgressAutomatic extends Progress {}
export class ProgressHordeNative {}

View File

@ -0,0 +1,457 @@
import { app } from 'photoshop'
import { control_net, preview, viewer, progress } from '../entry'
import Locale from '../locale/locale'
import { AStore } from '../main/astore'
import {
html_manip,
io,
psapi,
python_replacement,
sdapi,
} from '../util/oldSystem'
import { GenerationModeEnum } from '../util/ts/enum'
import {
Img2ImgMode,
InpaintMode,
LassoInpaintMode,
OutpaintMode,
Txt2ImgMode,
UpscaleMode,
} from './modes'
import { Progress } from './progress'
import { reaction } from 'mobx'
declare let g_inpaint_mask_layer: any
declare const g_image_not_found_url: string
declare let g_current_batch_index: number
export const store = new AStore({
// activeBase64InitImage: '',
// activeBase64Mask: '',
init_image: '',
active_mask: '', // this is the mask that is been used in the current generation
mask: '', // the user inputted mask, also can be the mask generated by photoshop dependant on the generation mode
expanded_mask: '', // mask after expanded
monoMask: '', //monochrome mask, no gradation
preprocessed_mask: '', //
sd_mask: '', // mask send to sd as payload[mask]
mode: '',
ui_settings: {},
selectionInfo: {}, //the session selection info
current_selection_info: {}, // any new selection, could be undefined too
can_generate: true, // is generation currently in progress
can_generate_more: false, //
is_active: false, // is session active
is_interrupted: false, // did we interrupt the generation
generation_number: 0, // generation number per session, 0 mean first generation
controlnet_input_image: '', // the controlnet the image that will controlnet load
//plugin related state:
auto_photoshop_sd_extension_status: true,
})
reaction(
() => {
return [store.data.init_image, store.data.mask] as [string, string]
},
([init_image, mask]: [string, string]) => {
html_manip.setInitImageSrc(
init_image
? 'data:image/png;base64,' + init_image
: g_image_not_found_url
)
html_manip.setInitImageMaskSrc(
mask ? 'data:image/png;base64,' + mask : g_image_not_found_url
)
}
)
reaction(
() => {
return store.data.auto_photoshop_sd_extension_status
},
(auto_photoshop_sd_extension_status: boolean) => {
if (auto_photoshop_sd_extension_status) {
} else {
app.showAlert(
'Please install the Auto-Photoshop-SD Extension from Automatic1111 Extensions tab '
)
}
}
)
function hasSelectionChanged(new_selection: any, old_selection: any) {
try {
if (
new_selection.left === old_selection.left &&
new_selection.bottom === old_selection.bottom &&
new_selection.right === old_selection.right &&
new_selection.top === old_selection.top
) {
return false
} else {
return true
}
} catch (e) {
//if any properties is missing
// console.warn(e)
return false
}
}
reaction(
() => {
return store.data.current_selection_info
},
(new_selection_info) => {
console.log(
'store.data.current_selection_info: reaction is triggered ',
store.data.current_selection_info
)
if (hasSelectionChanged(new_selection_info, store.data.selectionInfo)) {
store.data.can_generate_more = false
} else {
if (store.data.is_active) store.data.can_generate_more = true
}
}
)
reaction(
() => {
return store.data.is_active
},
(is_active) => {
console.log(
'store.data.is_active: reaction is triggered ',
store.data.is_active
)
if (is_active) {
store.data.can_generate_more = true
} else {
store.data.can_generate_more = false
}
}
)
interface ModeToClassMap {
[key: string]:
| typeof Txt2ImgMode
| typeof Img2ImgMode
| typeof InpaintMode
| typeof LassoInpaintMode
| typeof OutpaintMode
| typeof UpscaleMode
}
const modeToClassMap: ModeToClassMap = {
[GenerationModeEnum.Txt2Img]: Txt2ImgMode,
[GenerationModeEnum.Img2Img]: Img2ImgMode,
[GenerationModeEnum.Inpaint]: InpaintMode,
[GenerationModeEnum.LassoInpaint]: LassoInpaintMode,
[GenerationModeEnum.Outpaint]: OutpaintMode,
[GenerationModeEnum.Upscale]: UpscaleMode,
}
export async function getExpandedMask(
mask: string,
expansion_value: number,
blur: number
) {
let expanded_mask = mask
try {
let use_sharp_mask = false
if (
use_sharp_mask === false &&
mask &&
expansion_value >= 0 &&
blur >= 0
) {
//only if mask is available and sharp_mask is off
// use blurry and expanded mask
const iterations = expansion_value
expanded_mask = await python_replacement.maskExpansionRequest(
mask,
iterations,
blur
)
}
// return expanded_mask
} catch (e) {
console.warn(e)
} finally {
return expanded_mask
}
}
export class Session {
constructor() {}
static async initializeSession(mode: GenerationModeEnum): Promise<any> {
try {
store.data.mode = mode
if (modeToClassMap.hasOwnProperty(store.data.mode)) {
const { selectionInfo, init_image, mask } =
await modeToClassMap[store.data.mode].initializeSession()
store.data.selectionInfo = selectionInfo
if (init_image) {
// const opaque_init_image = await io.fixTransparentEdges(
// init_image
// )
store.data.init_image = init_image
// store.data.init_image = opaque_init_image
await viewer.updateViewerStoreImageAndThumbnail(
viewer.init_store,
[store.data.init_image]
)
}
if (mask) {
// store.data.mask = mask
const mask_monochrome =
await io.convertGrayscaleToMonochrome(mask)
store.data.monoMask = mask_monochrome
store.data.mask = mask
const expansion_value: number = parseInt(
//@ts-ignore
document.getElementById('slMaskExpansion').value
)
const mask_blur = html_manip.getMaskBlur()
store.data.expanded_mask = await getExpandedMask(
mask,
expansion_value,
mask_blur
)
store.data.preprocessed_mask = mask
await viewer.updateViewerStoreImageAndThumbnail(
viewer.mask_store,
[
store.data.preprocessed_mask,
store.data.monoMask,
store.data.expanded_mask,
]
)
}
return {
selectionInfo,
init_image,
mask: store.data.preprocessed_mask,
}
}
} catch (e) {
console.warn(e)
}
}
static async getSettings(session_data: any) {
const ui_settings = await modeToClassMap[store.data.mode].getSettings(
session_data
)
store.data.ui_settings = ui_settings
return ui_settings
}
static processOutput() {}
static validate() {
if (store.data.is_active) {
//@ts-ignore
app.showAlert('You forgot to select images!')
return false
}
return true
}
static async initializeGeneration() {
store.data.is_interrupted = false
store.data.can_generate = false
store.data.generation_number += 1
g_current_batch_index += 1
}
static async generate(mode: GenerationModeEnum): Promise<{
output_images: any
response_json: any
}> {
if (!store.data.can_generate) {
// return null
throw Error(
'A Generation is progress, wait for to finish be fore you generate again'
)
}
if (store.data.is_active) {
//you can only use the generate button once per session
//@ts-ignore
app.showAlert(
'You must end the current session before starting a new one'
)
throw Error(
'Session is still Active. Need to end the Session before starting a new Session'
)
}
try {
this.initializeGeneration()
store.data.is_active = true
this.getProgress()
const { selectionInfo, init_image, mask } =
await this.initializeSession(mode)
const ui_settings = await this.getSettings({
selectionInfo,
init_image,
mask,
})
//this should be part of initialization method or gettingSettings()
//calculate the expanded mask from mask
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(mode)
) {
const expansion_value: number = parseInt(
//@ts-ignore
document.getElementById('slMaskExpansion').value
)
const mask_blur = html_manip.getMaskBlur()
store.data.expanded_mask = await getExpandedMask(
mask,
expansion_value,
mask_blur
)
ui_settings['mask'] = store.data.expanded_mask
}
var { output_images, response_json } = await modeToClassMap[
mode
].generate(ui_settings)
} catch (e) {
console.warn(e)
} finally {
store.data.can_generate = true
await this.endProgress()
}
return { output_images, response_json }
}
static async generateMore(): Promise<{
output_images: any
response_json: any
}> {
if (!store.data.can_generate) {
throw Error(
'A Generation is progress, wait for to finish be fore you generate again'
)
}
try {
this.initializeGeneration()
store.data.can_generate = false
this.getProgress()
const session_data = {
init_image: store.data.init_image,
mask: store.data.preprocessed_mask,
selectionInfo: store.data.selectionInfo,
}
const ui_settings = await this.getSettings(session_data)
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(store.data.mode)
) {
const expansion_value: number = parseInt(
//@ts-ignore
document.getElementById('slMaskExpansion').value
)
const mask_blur = html_manip.getMaskBlur()
store.data.expanded_mask = await getExpandedMask(
session_data.mask,
expansion_value,
mask_blur
)
ui_settings['mask'] = store.data.expanded_mask
}
var { output_images, response_json } = await modeToClassMap[
store.data.mode
].generate(ui_settings)
} catch (e) {
console.warn(e)
} finally {
store.data.can_generate = true
await this.endProgress()
}
return { output_images, response_json }
}
static async interrupt(): Promise<any> {
try {
await modeToClassMap[store.data.mode].interrupt()
store.data.is_interrupted = true
} catch (e) {
console.warn(e)
} finally {
//no need to reset progress since generate and generateMore will always finish executing after interrupt
// store.data.can_generate = true
// this.endProgress()
}
}
static async getProgress() {
// Progress.startSudoProgress()
progress.Progress.startTimer(async () => {
try {
let json = await progress.requestProgress()
const can_update = progress.store.data.can_update
if (!can_update) {
return null
}
if (json?.progress) {
progress.store.updateProperty(
'progress_value',
json?.progress * 100
)
}
if (json?.current_image) {
progress.store.updateProperty(
'progress_image',
json?.current_image
)
}
progress.store.data.progress_label = Locale('Progress...')
// console.log('progress object json: ', json)
} catch (e) {
console.warn(e)
}
}, 2000)
}
static async endProgress() {
await progress.Progress.endTimer(async () => {
progress.store.data.progress_value = 0
progress.store.data.progress_image = ''
progress.store.data.progress_image_height = 0
await progress.Progress.deleteProgressLayer()
})
}
static endSession() {
viewer.resetViewer() //may cause circular dependency
store.data.is_active = false //
store.data.init_image = ''
store.data.mask = ''
store.data.expanded_mask = ''
store.data.preprocessed_mask = ''
store.data.generation_number = 0
store.data.controlnet_input_image = ''
g_current_batch_index = -1 // first generation will add +1 to get => 0
}
static async getOutput() {}
}

View File

@ -0,0 +1,20 @@
.generateButtonMargin {
margin-top: 1px;
margin-bottom: 3px;
display: inline-block;
}
.generateColor {
background-color: #ff595e;
}
.generateMoreColor {
background-color: #6db579;
}

View File

@ -0,0 +1,186 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { SpCheckBox, SpMenu } from '../util/elements'
import Locale from '../locale/locale'
import globalStore from '../globalstore'
import { io } from '../util/oldSystem'
import { reaction } from 'mobx'
import { storage } from 'uxp'
import { ErrorBoundary } from '../util/errorBoundary'
import { MaskModeEnum } from '../util/ts/enum'
// import { Jimp } from '../util/oldSystem'
declare const Jimp: any // make sure you import jimp before importing settings.tsx
type InterpolationMethod = {
[key: string]: {
photoshop: string
jimp: string
}
}
const interpolationMethods: InterpolationMethod = {
nearestNeighbor: {
photoshop: 'nearestNeighbor',
jimp: Jimp.RESIZE_NEAREST_NEIGHBOR,
},
bicubic: {
photoshop: 'bicubicAutomatic',
jimp: Jimp.RESIZE_BICUBIC,
},
bilinear: {
photoshop: 'bilinear',
jimp: Jimp.RESIZE_BILINEAR,
},
}
export const store = new AStore({
scale_interpolation_method: interpolationMethods.bilinear,
should_log_to_file:
JSON.parse(storage.localStorage.getItem('should_log_to_file')) || false,
delete_log_file_timer_id: null,
b_borders_or_corners: MaskModeEnum.Borders,
})
function onShouldLogToFileChange(event: any) {
try {
const should_log_to_file: boolean = event.target.checked
store.data.should_log_to_file = should_log_to_file
storage.localStorage.setItem('should_log_to_file', should_log_to_file)
if (should_log_to_file && !store.data.delete_log_file_timer_id) {
store.data.delete_log_file_timer_id = setDeleteLogTimer()
} else {
//don't log and clear delete file timer
try {
store.data.delete_log_file_timer_id = clearInterval(
store.data.delete_log_file_timer_id
)
} catch (e) {
console.warn(e)
}
}
//@ts-ignore
setLogMethod(should_log_to_file)
} catch (e) {
console.warn(e)
}
}
function setDeleteLogTimer() {
const timer_id = setInterval(async () => {
await io.deleteFileIfLargerThan('log.txt', 200)
}, 2 * 60 * 1000)
console.log('setDeleteLogTimer() timer_id :', timer_id)
return timer_id
}
@observer
export class Settings extends React.Component<{}> {
componentDidMount(): void {}
render() {
return (
<div style={{ width: '100%' }}>
<SpMenu
title="select an interploation method for resizing images"
items={Object.keys(interpolationMethods)}
label_item="Select Interpolation Method"
selected_index={Object.keys(interpolationMethods).findIndex(
(key) => {
return (
interpolationMethods[key].photoshop ===
store.data.scale_interpolation_method
.photoshop &&
interpolationMethods[key].jimp ===
store.data.scale_interpolation_method.jimp
)
}
)}
onChange={(id: any, value: any) => {
store.updateProperty(
'scale_interpolation_method',
interpolationMethods[value.item]
)
}}
></SpMenu>
<sp-label>select language</sp-label>
<SpMenu
title="select language"
items={['en_US', 'zh_CN']}
label_item="select language"
selected_index={['en_US', 'zh_CN'].indexOf(
globalStore.Locale
)}
onChange={(id: any, value: any) => {
globalStore.Locale = value.item
localStorage.setItem('last_selected_locale', value.item)
console.log(
localStorage.getItem('last_selected_locale')
)
}}
></SpMenu>
<SpCheckBox
style={{
marginRight: '10px',
}}
onChange={onShouldLogToFileChange}
checked={store.data.should_log_to_file}
>
{
//@ts-ignore
Locale('Log Errors To File')
}
</SpCheckBox>
<sp-radio-group
style={{ display: 'flex' }}
selected={store.data.b_borders_or_corners}
onClick={(event: any) => {
store.data.b_borders_or_corners = event.target.value
}}
>
<sp-label slot="label">
{Locale('Mask Layer Mode:')}
</sp-label>
{[
// {
// label: 'fully transparent',
// value: MaskModeEnum.Transparent,
// },
{ label: 'keep borders', value: MaskModeEnum.Borders },
{ label: 'keep corners', value: MaskModeEnum.Corners },
].map((mode: any, index: number) => {
console.log('mode:', mode.label, ' index:', index)
return (
<sp-radio
key={`mode-${index}`}
checked={
store.data.b_borders_or_corners ===
mode.value
? true
: void 0
}
value={mode.value}
>
{Locale(mode.label)}
</sp-radio>
)
})}
</sp-radio-group>
</div>
)
}
}
const containerNode = document.getElementById('reactSettingsContainer')!
const root = ReactDOM.createRoot(containerNode)
root.render(
<React.StrictMode>
<ErrorBoundary>
<Settings></Settings>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -4,9 +4,10 @@ import ReactDOM from 'react-dom/client'
import { makeAutoObservable, toJS } from 'mobx'
import { observer } from 'mobx-react'
import { SpMenu } from './elements'
import { SpMenu } from '../util/elements'
import * as ultimate_sd_upscale_script from './ultimate_sd_upscaler'
import { ScriptMode } from './ultimate_sd_upscaler'
import { ErrorBoundary } from '../util/errorBoundary'
export function toJsFunc(store: any) {
return toJS(store)
}
@ -164,10 +165,10 @@ const root = ReactDOM.createRoot(domNode)
root.render(
<React.StrictMode>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<ScriptComponent></ScriptComponent>
</div>
{/* <SliderValuesDisplay /> */}
<ErrorBoundary>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<ScriptComponent></ScriptComponent>
</div>
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -4,13 +4,12 @@ import ReactDOM from 'react-dom/client'
import { action, makeAutoObservable, reaction, toJS } from 'mobx'
import { Provider, inject, observer } from 'mobx-react'
import { SliderType, SpMenu, SpSliderWithLabel } from './elements'
import * as sdapi from '../../sdapi_py_re'
import { SliderType, SpMenu, SpSliderWithLabel } from '../util/elements'
import { ui_config } from './config'
import { requestGet } from '../../utility/api'
import { api } from '../util/oldSystem'
const { requestGet } = api
declare let g_sd_url: string
export let script_name: string = 'ultimate sd upscale'
@ -68,6 +67,23 @@ export const script_args_ordered = [
'custom_scale',
]
//for some reason oldSystem.sdapi is empty {}, could be caused by a circular dependency
//so I've copy pasted requestGetUpscalers() to the ultimate sd upscaler module, as a temporary solution
async function requestGetUpscalers() {
console.log('requestGetUpscalers: ')
let json = []
const full_url = `${g_sd_url}/sdapi/v1/upscalers`
try {
let request = await fetch(full_url)
json = await request.json()
console.log('upscalers json:')
console.dir(json)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}
return json
}
class UltimateSDUpscalerStore {
data: UltimateSDUpscalerData
@ -143,12 +159,16 @@ export class UltimateSDUpscalerForm extends React.Component<{
}
async getUpscalers() {
const sd_upscalers_json = await sdapi.requestGetUpscalers()
const sd_upscalers = sd_upscalers_json.map(
(upscaler: any) => upscaler.name
)
this.setState({ sd_upscalers: sd_upscalers })
return sd_upscalers
try {
const sd_upscalers_json = await requestGetUpscalers()
const sd_upscalers = sd_upscalers_json.map(
(upscaler: any) => upscaler.name
)
this.setState({ sd_upscalers: sd_upscalers })
return sd_upscalers
} catch (e) {
console.warn('ultimate sd upscaler getUpscalers(): ', e)
}
}
handleRefresh = async () => {
@ -203,7 +223,7 @@ export class UltimateSDUpscalerForm extends React.Component<{
output_value={this.props.store.data[id]}
title={ui_config[id].label}
label={ui_config[id].label}
onSliderChange={this.handleSliderChange}
onSliderInput={this.handleSliderChange}
/>
))
const seamfix_ids = [
@ -223,7 +243,7 @@ export class UltimateSDUpscalerForm extends React.Component<{
output_value={this.props.store.data[id]}
title={ui_config[id].label}
label={ui_config[id].label}
onSliderChange={this.handleSliderChange}
onSliderInput={this.handleSliderChange}
slider_type={
Number.isInteger(ui_config[id].step)
? SliderType.Integer
@ -255,7 +275,7 @@ export class UltimateSDUpscalerForm extends React.Component<{
id={'custom_scale'}
out_min={ui_config.custom_scale.minimum}
out_max={ui_config.custom_scale.maximum}
onSliderChange={this.handleSliderChange}
onSliderInput={this.handleSliderChange}
steps={0.01}
slider_type={SliderType.Float}
/>

View File

@ -1,7 +1,13 @@
import React, { ReactEventHandler, useState } from 'react'
import React, { CSSProperties, ComponentType } from 'react'
// import ReactDOM from 'react-dom'
import ReactDOM from 'react-dom/client'
import Locale from '../locale/locale'
import { observer } from 'mobx-react'
// import { versions } from 'uxp'
export { ReactComponent as MoveToCanvasSvg } from '../../icon/move_to_canvas.svg'
export { ReactComponent as PenSvg } from '../../icon/pen.svg'
export { ReactComponent as PreviewSvg } from '../../icon/preview.svg'
declare global {
namespace JSX {
interface IntrinsicElements {
@ -16,6 +22,9 @@ declare global {
'sp-divider': any
'sp-detail': any
'sp-textarea': any
'sp-textfield': any
'sp-action-button': any
'sp-progressbar': any
}
}
}
@ -34,8 +43,10 @@ export enum SliderType {
Integer = 'integer',
Float = 'float',
}
@observer
export class SpSliderWithLabel extends React.Component<{
onSliderChange?: any
onSliderInput?: any
id?: string
'show-value'?: boolean
steps?: number
@ -115,17 +126,19 @@ export class SpSliderWithLabel extends React.Component<{
this.setState({ output_value: to_value })
}
onSliderValueInputHandler(event: React.ChangeEvent<HTMLInputElement>) {
const newValue: string = event.target.value
let output_value = this.stepToOutputValue(parseInt(newValue))
this.setState({ output_value: output_value })
if (this.props.onSliderInput && this.props.id) {
this.props.onSliderInput(this.props.id, output_value)
} else if (this.props.onSliderInput) {
this.props.onSliderInput(output_value)
}
}
onSliderValueChangeHandler(event: React.ChangeEvent<HTMLInputElement>) {
const newValue: string = event.target.value
console.log('onSliderValueChangeHandler value: ', newValue)
this.setState({ output_value: newValue })
console.log({
in_min: this.in_min,
in_max: this.in_max,
out_min: this.out_min,
out_max: this.out_max,
})
let output_value = this.stepToOutputValue(parseInt(newValue))
this.setState({ output_value: output_value })
@ -146,17 +159,24 @@ export class SpSliderWithLabel extends React.Component<{
// <div>{versions.plugin}</div>
// </div>
<div>
<sp-slider
<SpSlider
show-value="false"
// id="slControlNetWeight_0"
class="slControlNetWeight_"
min={this.in_min}
max={this.in_max}
value={this.state.slider_value}
title="2 will keep the composition; 0 will allow composition to change"
onInput={this.onSliderValueChangeHandler.bind(this)}
title={this.props?.title ?? ''}
onInput={
this.props.onSliderInput
? this.onSliderValueInputHandler.bind(this)
: void 0
}
onChange={this.onSliderValueChangeHandler.bind(this)}
>
<sp-label slot="label">{this.props.label}:</sp-label>
<sp-label slot="label">
{Locale(this.props.label as any)}:
</sp-label>
<sp-label
slot="label"
// id="lControlNetWeight_0"
@ -164,7 +184,7 @@ export class SpSliderWithLabel extends React.Component<{
>
{this.state.output_value}
</sp-label>
</sp-slider>
</SpSlider>
</div>
)
}
@ -174,7 +194,7 @@ export class SpMenu extends React.Component<{
id?: string
title?: string
style?: string
style?: CSSProperties
items?: string[]
disabled?: boolean[]
label_item?: string
@ -212,11 +232,11 @@ export class SpMenu extends React.Component<{
render() {
return (
<div>
<div style={this.props.style}>
<sp-picker
title={this.props.title}
size="m"
style={{ width: '199px', marginRight: '5px' }}
// style={{ width: '199px', marginRight: '5px' }}
>
<sp-menu id={this.props.id} slot="options">
{this.props.label_item && (
@ -258,3 +278,214 @@ export class SpMenu extends React.Component<{
)
}
}
class PhotoshopElem extends React.Component<{ [key: string]: any }, {}> {
protected elem: Element | null = null
protected curEvents: { [key: string]: (evt: Event) => any } = {}
componentDidMount(): void {
this.updateEventListener()
}
// componentDidUpdate(): void {
// this.updateEventListener()
// }
updateEventListener() {
if (!this.elem) throw new Error('elem is not rendered with ref')
const [, newEvent] = this.splitProps(this.props)
Object.keys(this.curEvents).forEach((evkey) => {
if (this.curEvents[evkey] != newEvent[evkey]) {
this.elem?.removeEventListener(evkey, this.curEvents[evkey])
}
})
Object.keys(newEvent).forEach((evkey) => {
this.elem?.addEventListener(evkey, newEvent[evkey])
})
}
componentWillUnmount(): void {
Object.keys(this.curEvents).forEach((evkey) => {
this.elem?.removeEventListener(evkey, this.curEvents[evkey])
})
}
splitProps(props: any): [any, any] {
const attr: any = {}
const event: any = {}
Object.keys(props).forEach((propKey: string) => {
if (propKey.startsWith('on')) {
const key = propKey[2].toLocaleLowerCase() + propKey.slice(3)
event[key] = props[propKey]
} else {
attr[propKey] = props[propKey]
}
})
return [attr, event]
}
}
export class SpPicker extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-picker
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-picker>
)
}
}
export class SpMenuComponent extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-menu
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-menu>
)
}
}
export class SpMenuItem extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-menu-item
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-menu-item>
)
}
}
export class SpLabel extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-label
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-label>
)
}
}
export class SpCheckBox extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
if (!attr['checked']) delete attr['checked']
return (
<sp-checkbox
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-checkbox>
)
}
}
export class SpSlider extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-slider
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-slider>
)
}
}
export class SpRadioGroup extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-radio-group
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-radio-group>
)
}
}
export class SpRadio extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-radio
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-radio>
)
}
}
export class SpDivider extends PhotoshopElem {
render() {
const [attr] = this.splitProps(this.props)
return (
<sp-divider
ref={(elem: Element) => (this.elem = elem)}
{...attr}
></sp-divider>
)
}
}
export class Thumbnail extends React.Component<{
style?: any
children: React.ReactNode
}> {
render() {
return (
<div style={this.props?.style} className="viewer-image-container">
{this.props.children}
</div>
)
}
}
export class ActionButtonSVG extends React.Component<{
onClick?: any
ComponentType: ComponentType
title?: string
}> {
render() {
if (!this.props.ComponentType) {
return null
}
return (
<sp-action-button
onClick={this.props?.onClick}
style={{
padding: 0,
maxWidth: '32px',
maxHeight: '32px' /* display: none; */,
}}
class="thumbnail-image-button"
title={this.props.title ?? void 0}
>
<div slot="icon" style={{ fill: 'currentColor' }}>
{<this.props.ComponentType />}
</div>
</sp-action-button>
)
}
}
interface ScriptInstallComponentProps {
onRefreshHandler: any
}
export const ScriptInstallComponent = observer(
({ onRefreshHandler }: ScriptInstallComponentProps) => {
return (
<div>
<sp-label class="missing-error">
Script is not available; Make sure to install it from
Automatic1111 webui
</sp-label>
<button
className="btnSquare refreshButton"
id="btnResetSettings"
title="Refresh the After Detailer Extension"
onClick={onRefreshHandler}
></button>
</div>
)
}
)

View File

@ -0,0 +1,31 @@
import React, { Component, ErrorInfo, ReactNode } from 'react'
interface Props {
children: ReactNode
}
interface State {
hasError: boolean
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
}
public static getDerivedStateFromError(_: Error): State {
return { hasError: true }
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Uncaught error:', error, errorInfo)
}
public render() {
if (this.state.hasError) {
return <h1>Sorry.. there was an error</h1>
}
return this.props.children
}
}

101
typescripts/util/grid.tsx Normal file
View File

@ -0,0 +1,101 @@
import React from 'react'
import { ActionButtonSVG, Thumbnail } from './elements'
export class Grid extends React.Component<{
// thumbnails_data?: any[]
thumbnails?: string[]
width?: number
height?: number
action_buttons?: any[]
children?: React.ReactNode
callback?: any
clicked_index?: number
permanent_indices?: number[]
thumbnails_styles?: []
}> {
static defaultProps = {
width: 100,
height: 100,
thumbnails: [],
thumbnails_styles: [],
}
render() {
const img_style = {
width: `${this.props.width}px`,
// height: `${this.props.height}px`,
height: 'auto',
}
return (
<div className="viewer-container">
{this.props?.thumbnails?.map((thumbnail, index: number) => {
// const thumbnail = this.props?.thumbnails
// thumbnail
// ? this.props?.thumbnails[index]
// : 'https://source.unsplash.com/random'
const thumbnail_class = this.props?.thumbnails_styles
? this.props?.thumbnails_styles[index]
: ''
return (
<Thumbnail style={img_style} key={`thumbnail-${index}`}>
<img
style={img_style}
onClick={async (event: any) => {
try {
console.log('image clicked')
if (this.props.callback) {
if (
this.props.callback.constructor
.name === 'AsyncFunction'
) {
await this.props?.callback(
index,
event
)
} else {
this.props?.callback(
index,
event
)
}
this.props?.callback(index, event) //todo: is this a typo why do we call callback twice?
}
} catch (e) {
console.warn(
'error was thrown while calling a callback method'
)
console.warn(e)
}
}}
src={
thumbnail ??
'https://source.unsplash.com/random'
}
className={`viewer-image-container ${thumbnail_class}`}
/>
{this.props?.action_buttons?.map((button, i) => {
return (
i < 4 && (
<ActionButtonSVG
key={`action-button-${i}`}
ComponentType={button.ComponentType}
onClick={() => {
button.callback(index)
}}
title={button?.title}
></ActionButtonSVG>
)
)
})}
</Thumbnail>
)
})}
{/* <div className="viewer-image-container">
{this.props?.children}
</div> */}
</div>
)
}
}

View File

@ -0,0 +1,5 @@
import { format } from 'util'
export function formateLog(data: any, ...optional_param: any[]) {
const formattedOutput = format(data, ...optional_param)
return formattedOutput
}

View File

@ -0,0 +1,43 @@
import type Jimp from 'jimp'
//@ts-ignore
const req = window['require']
// because we use window['require'], so the base path of this require function is the root path of plugin.
const selection = req('./selection')
const note = req('./utility/notification')
const controlnet_preset = req('./utility/presets/controlnet_preset')
const preset = req('./utility/presets/preset')
const Enum = req('./enum')
const api = req('./utility/api')
const python_replacement = req('./utility/sdapi/python_replacement')
const sdapi = req('./sdapi_py_re')
const html_manip = req('./utility/html_manip')
const psapi = req('./psapi')
const general = req('./utility/general')
const io = req('./utility/io')
const settings_tab = req('./utility/tab/settings')
const layer_util = req('./utility/layer')
const session = req('./utility/session')
interface _Jimp extends Jimp {}
const _Jimp: typeof Jimp = (window as any)['Jimp']
export {
selection,
note,
controlnet_preset,
preset,
Enum,
api,
python_replacement,
sdapi,
html_manip,
psapi,
general,
io,
settings_tab,
layer_util,
session,
_Jimp as Jimp,
}

View File

@ -0,0 +1,92 @@
declare let g_sd_url: string
export async function requestGet(url: string) {
let json = null
const full_url = url
try {
let request = await fetch(full_url)
if (request.status === 404) {
return null
}
json = await request.json()
// console.log('json: ', json)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}
return json
}
export async function requestPost(url: string, payload: any) {
let json = null
const full_url = url
try {
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
})
if (request.status === 404) {
return null
}
json = await request.json()
// console.log('json: ', json)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}
return json
}
export async function requestFormDataPost(url: string, payload: any) {
try {
var myHeaders = new Headers()
myHeaders.append('Cookie', 'PHPSESSID=n70fa2vmvm6tfmktf4jmstmd1i')
var formdata = new FormData()
for (const [key, value] of Object.entries(payload)) {
//@ts-ignore
formdata.append(key, value)
}
// formdata.append(
// 'source',
// 'iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAApgAAAKYB3X3/OAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAANCSURBVEiJtZZPbBtFFMZ/M7ubXdtdb1xSFyeilBapySVU8h8OoFaooFSqiihIVIpQBKci6KEg9Q6H9kovIHoCIVQJJCKE1ENFjnAgcaSGC6rEnxBwA04Tx43t2FnvDAfjkNibxgHxnWb2e/u992bee7tCa00YFsffekFY+nUzFtjW0LrvjRXrCDIAaPLlW0nHL0SsZtVoaF98mLrx3pdhOqLtYPHChahZcYYO7KvPFxvRl5XPp1sN3adWiD1ZAqD6XYK1b/dvE5IWryTt2udLFedwc1+9kLp+vbbpoDh+6TklxBeAi9TL0taeWpdmZzQDry0AcO+jQ12RyohqqoYoo8RDwJrU+qXkjWtfi8Xxt58BdQuwQs9qC/afLwCw8tnQbqYAPsgxE1S6F3EAIXux2oQFKm0ihMsOF71dHYx+f3NND68ghCu1YIoePPQN1pGRABkJ6Bus96CutRZMydTl+TvuiRW1m3n0eDl0vRPcEysqdXn+jsQPsrHMquGeXEaY4Yk4wxWcY5V/9scqOMOVUFthatyTy8QyqwZ+kDURKoMWxNKr2EeqVKcTNOajqKoBgOE28U4tdQl5p5bwCw7BWquaZSzAPlwjlithJtp3pTImSqQRrb2Z8PHGigD4RZuNX6JYj6wj7O4TFLbCO/Mn/m8R+h6rYSUb3ekokRY6f/YukArN979jcW+V/S8g0eT/N3VN3kTqWbQ428m9/8k0P/1aIhF36PccEl6EhOcAUCrXKZXXWS3XKd2vc/TRBG9O5ELC17MmWubD2nKhUKZa26Ba2+D3P+4/MNCFwg59oWVeYhkzgN/JDR8deKBoD7Y+ljEjGZ0sosXVTvbc6RHirr2reNy1OXd6pJsQ+gqjk8VWFYmHrwBzW/n+uMPFiRwHB2I7ih8ciHFxIkd/3Omk5tCDV1t+2nNu5sxxpDFNx+huNhVT3/zMDz8usXC3ddaHBj1GHj/As08fwTS7Kt1HBTmyN29vdwAw+/wbwLVOJ3uAD1wi/dUH7Qei66PfyuRj4Ik9is+hglfbkbfR3cnZm7chlUWLdwmprtCohX4HUtlOcQjLYCu+fzGJH2QRKvP3UNz8bWk1qMxjGTOMThZ3kvgLI5AzFfo379UAAAAASUVORK5CYII='
// )
// formdata.append('key', '6d207e02198a847aa98d0a2a901485a5')
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: formdata,
redirect: 'follow',
}
//@ts-ignore
const response = await fetch(url, requestOptions)
const result_json = response.json()
return result_json
} catch (e) {
console.warn(e)
}
}
export async function isScriptInstalled(script_name: string): Promise<boolean> {
let is_installed = false
try {
const full_url = `${g_sd_url}/sdapi/v1/scripts`
const scripts = await requestGet(full_url)
is_installed =
scripts?.txt2img?.includes(script_name) ||
scripts?.img2img?.includes(script_name)
} catch (e) {
console.error(e)
}
console.log('is_installed: ', is_installed)
return is_installed
}

View File

@ -0,0 +1,313 @@
import { app, core, action } from 'photoshop'
import { Jimp, layer_util, psapi } from '../oldSystem'
import { storage } from 'uxp'
import { Layer } from 'photoshop/dom/Layer'
import { changeDpiDataUrl } from 'changedpi'
const executeAsModal = core.executeAsModal
const batchPlay = action.batchPlay
enum DocumentTypeEnum {
NoBackground = 'no_background',
ImageBackground = 'image_background',
SolidBackground = 'solid_background',
ArtBoard = 'artboard',
}
async function isCorrectBackground() {
const historylist = app.activeDocument.historyStates.filter(
(h) => h.name === 'Correct Background'
)
console.log('historylist:', historylist)
const is_correct_background = historylist.length > 0 ? true : false
return is_correct_background
}
async function getColor(X: any, Y: any) {
// const background_layer_id = await app.activeDocument.backgroundLayer.id
try {
const result = await batchPlay(
[
{
_obj: 'colorSampler',
_target: {
_ref: 'document',
_enum: 'ordinal',
_value: 'targetEnum',
},
samplePoint: {
horizontal: X,
vertical: Y,
},
},
],
{}
)
const red = result[0].colorSampler.red
const green = result[0].colorSampler.grain
const blue = result[0].colorSampler.blue
return [red, green, blue]
} catch (e) {
console.warn(e)
}
}
//REFACTOR: move to document.js
async function findDocumentType() {
//check if the background layer exsit
//if it doesn't return false
//if it does:
//duplicate the background layer and place it on the top of the document.
//sampler 10 random pixles
//and check if all the pixels has the same values.
//if it doesn't duplicate the background layer and place it above the background layer.
// make a white background layer.
//return true
let document_type
const background_layer = await app.activeDocument.backgroundLayer
const has_background_layer = app.activeDocument.backgroundLayer
? true
: false
const artboards = Array.from(await app.activeDocument.artboards)
if (artboards.length > 0) {
document_type = DocumentTypeEnum['ArtBoard']
// } else if (layer_util.Layer.doesLayerExist(background_layer)) {
} else if (has_background_layer) {
//assume it's solid white background if correctHistory > 1 || layers.length > 5
const b_correct_background = await isCorrectBackground() // check the history for correct operation
if (b_correct_background) {
document_type = DocumentTypeEnum['SolidBackground']
} else {
//else
//background layer does exist
//check if it's solid color background or an image background
//sampler 10 random pixels
let width = app.activeDocument.width
let height = app.activeDocument.height
let old_rgb: any
let same_color = true
await executeAsModal(
async () => {
if (app.activeDocument.layers.length > 1) {
await layer_util.toggleBackgroundLayerExe() // hide all layers except the background layer
}
for (let i = 0; i < 10; ++i) {
let x = Math.floor(Math.random() * width)
let y = Math.floor(Math.random() * height)
const rgb = (await getColor(x, y))!
if (old_rgb) {
if (
Math.round(old_rgb[0]) === Math.round(rgb[0]) &&
Math.round(old_rgb[1]) === Math.round(rgb[1]) &&
Math.round(old_rgb[2]) === Math.round(rgb[2])
) {
} else {
same_color = false //it's an image background
break
}
}
old_rgb = rgb
}
if (app.activeDocument.layers.length > 1) {
await layer_util.toggleBackgroundLayerExe() // undo the toggle operation; display all layers
}
},
{
commandName: 'Checking Document Type...',
}
)
document_type = same_color
? DocumentTypeEnum['SolidBackground']
: DocumentTypeEnum['ImageBackground']
}
} else {
//create the background layer since it doesn't exsit
document_type = DocumentTypeEnum['NoBackground']
}
return document_type
}
async function correctDocumentType(documentType: any) {
if (documentType === DocumentTypeEnum['SolidBackground']) {
//do nothing
} else if (documentType === DocumentTypeEnum['ImageBackground']) {
//duplicate the layer
await executeAsModal(
async () => {
const image_layer: any =
await app.activeDocument.backgroundLayer!.duplicate() //
image_layer.name = 'Image'
await app.activeDocument.backgroundLayer!.delete()
await layer_util.createBackgroundLayer(255, 255, 255)
},
{
commandName: 'Correct Background',
}
)
} else if (documentType === DocumentTypeEnum['ArtBoard']) {
//duplicate the layer
await app.showAlert(
"the plugin doesn't work with artboards, create normal document with no artboard to use the plugin"
)
throw "the plugin doesn't work with artboards, create normal document with no artboard to use the plugin"
} else if (documentType === DocumentTypeEnum['NoBackground']) {
await layer_util.createBackgroundLayer(255, 255, 255)
}
}
export async function initializeBackground() {
await executeAsModal(
async (context) => {
const document_type = await findDocumentType()
const history_id = await context.hostControl.suspendHistory({
documentID: app.activeDocument.id, //TODO: change this to the session document id
name: 'Correct Background',
})
//store selection
//store active layer
const selectionInfo = await psapi.getSelectionInfoExe()
await psapi.unSelectMarqueeExe()
const active_layers = app.activeDocument.activeLayers
//1)check if the documnet has a background layer
await correctDocumentType(document_type)
//retore selection
//restore active layer
await psapi.reSelectMarqueeExe(selectionInfo)
await psapi.selectLayersExe(active_layers)
await context.hostControl.resumeHistory(history_id)
},
{
commandName: 'Initialize Background',
}
)
}
/**
* transfer a base64image to a layer.
* the image will located at the top-left corner of canvas.
* @param b64Image
* @param options
* @returns
*/
export async function base64ToFileAndGetLayer(
b64Image: string,
options: {
image_name?: string
} = {}
): Promise<{ layer: Layer; width: number; height: number }> {
const imageName = options.image_name || 'output_image.png'
b64Image = changeDpiDataUrl(
'data:image/png;base64,' + b64Image,
app.activeDocument.resolution
)
const img = Buffer.from(b64Image.split(',')[1], 'base64')
const jimp_image = await Jimp.read(img)
const folder = await storage.localFileSystem.getTemporaryFolder()
const file = await folder.createFile(imageName + '.png', {
overwrite: true,
})
await file.write(img.buffer, { format: storage.formats.binary })
const token = await storage.localFileSystem.createSessionToken(file) // batchPlay requires a token on _path
const selection_info = await psapi.getSelectionInfoExe()
let imported_layer
await executeAsModal(
() =>
batchPlay(
[
{
_obj: 'set',
_target: [
{
_property: 'selection',
_ref: 'channel',
},
],
to: {
_obj: 'rectangle',
bottom: {
_unit: 'pixelsUnit',
_value: jimp_image.bitmap.height,
},
left: {
_unit: 'pixelsUnit',
_value: 0.0,
},
right: {
_unit: 'pixelsUnit',
_value: jimp_image.bitmap.width,
},
top: {
_unit: 'pixelsUnit',
_value: 0.0,
},
},
},
],
{}
),
{
commandName: 'select import area',
}
)
await executeAsModal(
async () => {
const result = await batchPlay(
[
{
_obj: 'placeEvent',
// ID: 6,
null: {
_path: token,
_kind: 'local',
},
freeTransformCenterState: {
_enum: 'quadCenterState',
_value: 'QCSAverage',
},
_isCommand: true,
_options: {
dialogOptions: 'dontDisplay',
},
},
],
{}
)
console.log('placeEmbedd batchPlay result: ', result)
imported_layer = await app.activeDocument.activeLayers[0]
},
{
commandName: 'import base64',
}
)
await psapi.reSelectMarqueeExe(selection_info)
if (!imported_layer) {
throw new Error('base64ToFileAndGetLayer failed: layer is empty')
}
return {
layer: imported_layer,
height: jimp_image.bitmap.height,
width: jimp_image.bitmap.width,
}
}

View File

@ -0,0 +1,15 @@
export enum GenerationModeEnum {
Txt2Img = 'txt2img',
Img2Img = 'img2img',
Inpaint = 'inpaint',
Outpaint = 'outpaint',
Upscale = 'upscale',
LassoInpaint = 'lasso_inpaint',
LassoOutpaint = 'lasso_outpaint',
}
export enum MaskModeEnum {
Transparent = 'transparent',
Borders = 'border',
Corners = 'corner',
}

130
typescripts/util/ts/io.ts Normal file
View File

@ -0,0 +1,130 @@
import { app, core, action } from 'photoshop'
import { Jimp, io, psapi } from '../oldSystem'
import { base64ToFileAndGetLayer } from './document'
import { transformCurrentLayerTo } from './layer'
import { Layer } from 'photoshop/dom/Layer'
const executeAsModal = core.executeAsModal
export async function moveImageToLayer_old(
base64_image: string,
selection_info: any,
layer_name: string = 'output_image.png'
) {
let layer
try {
const to_x = selection_info?.left
const to_y = selection_info?.top
const width = selection_info?.width
const height = selection_info?.height
layer = await io.IO.base64ToLayer(
base64_image,
layer_name,
to_x,
to_y,
width,
height
)
} catch (e) {
console.warn(e)
layer = null
}
return layer
}
export async function moveImageToLayer(
base64_image: string,
selection_info: any,
layer_name: string = 'output_image.png'
): Promise<Layer> {
if (!base64_image) throw new Error('moveImageToLayer: image is empty')
let layer: Layer | null
try {
const to_x = selection_info?.left
const to_y = selection_info?.top
const width = selection_info?.width
const height = selection_info?.height
const res = await base64ToFileAndGetLayer(base64_image, {
image_name: layer_name,
})
layer = res.layer
await psapi.setVisibleExe(layer, true)
await transformCurrentLayerTo(
{
left: to_x,
top: to_y,
width,
height,
},
{
width: res.width,
height: res.height,
left: 0,
top: 0,
}
)
await psapi.setVisibleExe(layer, true)
} catch (e) {
console.warn(e)
layer = null
}
if (!layer) {
throw new Error('moveImageToLayer failed: layer is empty')
}
return layer
}
export async function convertGrayscaleToWhiteAndTransparent(
base64: string
): Promise<{
base64: string
width: number
height: number
}> {
function grayToWhiteAndTransparent(
this: Jimp,
x: number,
y: number,
idx: number
) {
let color
if (
this.bitmap.data[idx] !== 0 &&
this.bitmap.data[idx + 1] !== 0 &&
this.bitmap.data[idx + 2] !== 0
) {
color = 0xffffffff
} else {
color = 0x00000000
}
this.setPixelColor(color, x, y)
}
try {
const jimp_image = await Jimp.read(Buffer.from(base64, 'base64'))
const jimp_mask = await jimp_image.scan(
0,
0,
jimp_image.bitmap.width,
jimp_image.bitmap.height,
grayToWhiteAndTransparent
)
const base64_monochrome_mask = await getBase64FromJimp(jimp_mask)
return {
base64: base64_monochrome_mask,
height: jimp_image.bitmap.height,
width: jimp_image.bitmap.width,
}
} catch (e) {
console.warn(e)
throw e
}
}
async function getBase64FromJimp(jimp_image: Jimp) {
const dataURL = await jimp_image.getBase64Async(Jimp.MIME_PNG)
const base64 = dataURL.replace(/^data:image\/png;base64,/, '')
return base64
}

View File

@ -0,0 +1,133 @@
import { app, core, action } from 'photoshop'
import { layer_util, psapi } from '../oldSystem'
import { settings_tab_ts } from '../../entry'
const executeAsModal = core.executeAsModal
const { batchPlay } = action
export interface RectArea {
top: number
left: number
height: number
width: number
}
async function transformBatchPlay(
centerX: number,
centerY: number,
scaleRatioX: number,
scaleRatioY: number,
translateX: number,
translateY: number
) {
const setInterpolationMethodDesc = {
_obj: 'set',
_target: [
{
_ref: 'property',
_property: 'generalPreferences',
},
{
_ref: 'application',
_enum: 'ordinal',
_value: 'targetEnum',
},
],
to: {
_obj: 'generalPreferences',
interpolationMethod: {
_enum: 'interpolationType',
// _value: 'bilinear',
_value: settings_tab_ts.store.data.scale_interpolation_method
.photoshop,
},
},
_isCommand: true,
}
let imageSizeDescriptor = {
_obj: 'transform',
_target: [
{
_ref: 'layer',
_enum: 'ordinal',
_value: 'targetEnum',
},
],
freeTransformCenterState: {
_enum: 'quadCenterState',
_value: 'QCSIndependent',
},
position: {
_obj: 'paint',
horizontal: { _unit: 'pixelsUnit', _value: centerX },
vertical: { _unit: 'pixelsUnit', _value: centerY },
},
offset: {
_obj: 'offset',
horizontal: {
_unit: 'pixelsUnit',
_value: translateX,
},
vertical: {
_unit: 'pixelsUnit',
_value: translateY,
},
},
width: {
_unit: 'percentUnit',
_value: scaleRatioX,
},
height: {
_unit: 'percentUnit',
_value: scaleRatioY,
},
linked: true,
interfaceIconFrameDimmed: {
_enum: 'interpolationType',
// _value: 'bilinear',
_value: settings_tab_ts.store.data.scale_interpolation_method
.photoshop,
},
_isCommand: true,
}
return batchPlay([setInterpolationMethodDesc, imageSizeDescriptor], {
synchronousExecution: true,
modalBehavior: 'execute',
})
}
export async function transformCurrentLayerTo(
toRect: RectArea,
fromRect: RectArea
) {
const selection_info = await psapi.getSelectionInfoExe()
await psapi.unSelectMarqueeExe()
const scale_x_ratio = (toRect.width / fromRect.width) * 100
const scale_y_ratio = (toRect.height / fromRect.height) * 100
const top_dist = toRect.top - fromRect.top
const left_dist = toRect.left - fromRect.left
console.log(
'transformCurrentLayer',
top_dist,
left_dist,
scale_x_ratio,
scale_y_ratio
)
await executeAsModal(
() =>
transformBatchPlay(
fromRect.left,
fromRect.top,
scale_x_ratio,
scale_y_ratio,
left_dist,
top_dist
),
{ commandName: 'transform' }
)
await psapi.reSelectMarqueeExe(selection_info)
}

View File

@ -0,0 +1,158 @@
import { moveImageToLayer, moveImageToLayer_old } from './io'
import { io, layer_util } from '../oldSystem'
import { session_ts } from '../../entry'
import { action, core } from 'photoshop'
import { MaskModeEnum } from './enum'
const executeAsModal = core.executeAsModal
const batchPlay = action.batchPlay
export async function applyMaskFromBlackAndWhiteImage(
black_and_white_base64: string,
layer_id: any,
selectionInfo: any,
b_borders_or_corners: MaskModeEnum = MaskModeEnum.Transparent
) {
let mask_layer
try {
const transparent_mask_base64 =
await io.convertBlackToTransparentKeepBorders(
black_and_white_base64,
b_borders_or_corners
)
mask_layer = await moveImageToLayer_old(
transparent_mask_base64,
selectionInfo
)
let cmd = [
{
_obj: 'select',
_target: [{ _id: mask_layer.id, _ref: 'layer' }],
makeVisible: false,
},
{
_obj: 'set',
_target: [
{
_ref: 'channel',
_property: 'selection',
},
],
to: {
_ref: 'channel',
_enum: 'channel',
_value: 'transparencyEnum',
},
_isCommand: true,
},
{
_obj: 'expand',
by: {
_unit: 'pixelsUnit',
_value: 10,
},
selectionModifyEffectAtCanvasBounds: true,
_isCommand: true,
},
{
_obj: 'select',
_target: [{ _id: layer_id, _ref: 'layer' }],
makeVisible: false,
},
{
_obj: 'make',
new: {
_class: 'channel',
},
at: {
_ref: 'channel',
_enum: 'channel',
_value: 'mask',
},
using: {
_enum: 'userMaskEnabled',
_value: 'revealSelection',
},
_isCommand: true,
},
]
//@ts-ignore
// await timer(g_timer_value)
await executeAsModal(
async () => {
const result = await batchPlay(cmd, {
synchronousExecution: true,
modalBehavior: 'execute',
})
},
{
commandName: 'select opaque pixels',
}
)
} catch (e) {
console.error(e)
} finally {
await layer_util.deleteLayers([mask_layer])
}
}
export async function selectionFromBlackAndWhiteImage(
black_and_white_base64: string,
selectionInfo: any,
b_borders_or_corners: MaskModeEnum = MaskModeEnum.Transparent
) {
let mask_layer
try {
const transparent_mask_base64 =
await io.convertBlackToTransparentKeepBorders(
black_and_white_base64,
b_borders_or_corners
)
mask_layer = await moveImageToLayer_old(
transparent_mask_base64,
selectionInfo
)
let cmd = [
{
_obj: 'select',
_target: [{ _id: mask_layer.id, _ref: 'layer' }],
makeVisible: false,
},
{
_obj: 'set',
_target: [
{
_ref: 'channel',
_property: 'selection',
},
],
to: {
_ref: 'channel',
_enum: 'channel',
_value: 'transparencyEnum',
},
_isCommand: true,
},
]
//@ts-ignore
// await timer(g_timer_value)
await executeAsModal(
async () => {
const result = await batchPlay(cmd, {
synchronousExecution: true,
modalBehavior: 'execute',
})
},
{
commandName: 'select opaque pixels',
}
)
} catch (e) {
console.error(e)
} finally {
await layer_util.deleteLayers([mask_layer])
}
}

View File

@ -0,0 +1,92 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Collapsible from '../after_detailer/after_detailer'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { progress } from '../entry'
import './style/preview.css'
import { reaction } from 'mobx'
import Locale from '../locale/locale'
import { ErrorBoundary } from '../util/errorBoundary'
export const store = new AStore({
// image: '',
// progress_value: 0,
})
// update all progress bar when progress store progress_value update
reaction(
() => {
return progress.store.data.progress_value
},
(value: number) => {
document.querySelectorAll('.pProgressBars').forEach((progress: any) => {
progress.value = value?.toFixed(2)
})
}
)
const Previewer = observer(() => {
const renderImage = () => {
let preview_img_html
if (progress.store.data.progress_image) {
preview_img_html = (
<img
style={{ maxWidth: '100%' }}
src={
'data:image/png;base64,' +
progress.store.data.progress_image
}
/>
)
}
return (
<div
className="progressImageContainer"
style={{
minHeight: progress.store.data.progress_image_height,
}}
>
<sp-progressbar
class="pProgressBars preview_progress_bar"
max="100"
value={`${progress.store.data.progress_value}`}
></sp-progressbar>
{progress.store.data.progress_image ? preview_img_html : void 0}
</div>
)
}
return <div style={{ padding: '4px' }}>{renderImage()}</div>
})
const containers = document.querySelectorAll('.previewContainer')
const PreviewerContainer = observer(() => {
return (
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Collapsible
defaultIsOpen={true}
label={
Locale('Preview') +
' ' +
(progress.store.data.progress_value
? `: ${
progress.store.data.progress_label
} ${progress.store.data.progress_value?.toFixed(2)}%`
: '')
}
>
<Previewer></Previewer>
</Collapsible>
</div>
)
})
containers.forEach((container) => {
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<PreviewerContainer />
</ErrorBoundary>
</React.StrictMode>
)
})

View File

@ -0,0 +1,6 @@
.preview_progress_bar {
border-left: 2px solid #3e3e3e;
border-right: 2px solid #3e3e3e;
margin: 0;
width: 100%;
}

View File

@ -0,0 +1,703 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
// import ReactDOM from 'react-dom'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { Grid } from '../util/grid'
import {
MoveToCanvasSvg,
SpCheckBox,
SpSlider,
SpSliderWithLabel,
} from '../util/elements'
import {
convertGrayscaleToWhiteAndTransparent,
moveImageToLayer,
moveImageToLayer_old,
} from '../util/ts/io'
import { io, layer_util, psapi, selection } from '../util/oldSystem'
import Collapsible from '../after_detailer/after_detailer'
import { progress, session_ts, settings_tab_ts } from '../entry'
import { reaction } from 'mobx'
import { GenerationModeEnum, MaskModeEnum } from '../util/ts/enum'
import { base64ToLassoSelection } from '../../selection'
import { action, app, core } from 'photoshop'
import Locale from '../locale/locale'
import { applyMaskFromBlackAndWhiteImage } from '../util/ts/selection'
import { ErrorBoundary } from '../util/errorBoundary'
const executeAsModal = core.executeAsModal
const batchPlay = action.batchPlay
declare let g_generation_session: any
enum ClickTypeEnum {
Click = 'click',
ShiftClick = 'shift_click',
AltClick = 'alt_click',
SecondClick = 'second_click', //when we click a thumbnail that is active/ has orange border
}
enum OutputImageStateEnum {
Add = 'add',
remove = 'remove',
}
enum ClassNameEnum {
Green = 'viewerImgSelected',
Orange = 'viewerImgActive',
None = '',
}
function findClickType(event: any) {
let click_type: ClickTypeEnum = ClickTypeEnum.Click
if (event.shiftKey) {
click_type = ClickTypeEnum.ShiftClick
} else if (event.altKey) {
click_type = ClickTypeEnum.AltClick
}
return click_type
}
export const store = new AStore({
images: [],
thumbnails: [],
metadata: [], // metadata for each image
width: 50,
height: 50,
prev_layer: null,
clicked_index: null,
permanent_indices: [],
prev_index: -1,
output_image_obj_list: [],
is_stored: [],
layers: [],
class_name: [],
can_click: true,
auto_mask: true,
})
const timer = (ms: any) => new Promise((res) => setTimeout(res, ms))
//when a generation is done, add the last generated image from the viewer to tha canvas
reaction(
() => {
return store.data.images
},
async (images: string[]) => {
try {
if (images.length > 0) {
let attempts: number = 5
store.data.is_stored = Array(images.length).fill(false)
while (attempts > 0) {
if (!progress.store.data.can_update) {
await timer(2000)
await handleOutputImageThumbnailClick(images.length - 1)
break
}
attempts -= 1
console.log('waiting 1000:')
console.log(
'progress.store.data.can_update:',
progress.store.data.can_update
)
await timer(2000)
}
}
} catch (e) {
console.warn(e)
}
}
)
export const init_store = new AStore({
images: [],
thumbnails: [],
width: 50,
height: 50,
prev_layer: null,
clicked_index: null,
permanent_indices: [],
prev_index: -1,
output_image_obj_list: [],
is_stored: [],
layers: [],
class_name: [],
can_click: true,
})
export const mask_store = new AStore({
images: [],
thumbnails: [],
output_images_masks: [],
width: 50,
height: 50,
prev_layer: null,
clicked_index: null,
permanent_indices: [],
prev_index: -1,
output_image_obj_list: [],
is_stored: [],
layers: [],
class_name: [],
can_click: true,
})
export async function updateViewerStoreImageAndThumbnail(
store: AStore,
images: string[]
) {
try {
if (typeof images === 'undefined' || !images) {
return null
}
store.data.images = images
const thumbnail_list = []
for (const base64 of images) {
const thumbnail = await io.createThumbnail(base64, 300)
thumbnail_list.push(thumbnail)
}
store.data.thumbnails = thumbnail_list
} catch (e) {
console.warn(e)
console.warn('images: ', images)
}
}
const add_new = async (base64: string, mask?: string) => {
//change the color of thumbnail border
//add image to the canvas
await psapi.unselectActiveLayersExe()
const layer = await moveImageToLayer_old(
base64,
session_ts.store.data.selectionInfo
)
// create channel if the generated mode support masking
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(session_ts.store.data.mode) &&
store.data.auto_mask &&
mask
) {
const channel_mask_monochrome =
await convertGrayscaleToWhiteAndTransparent(
// session_ts.store.data.expanded_mask
mask
)
if (
settings_tab_ts.store.data.b_borders_or_corners ===
MaskModeEnum.Transparent
) {
//will use colorRange() which may or may not break
const mask_layer = await moveImageToLayer(
channel_mask_monochrome.base64,
session_ts.store.data.selectionInfo
)
if (!mask_layer) {
throw new Error('mask_layer is empty')
}
await selection.black_white_layer_to_mask_multi_batchplay(
mask_layer.id,
layer.id,
'mask'
)
await layer_util.deleteLayers([mask_layer])
} else {
// if MaskModeEnum.Borders or MaskModeEnum.Corners
// another option that doesn't use colorRange()
await applyMaskFromBlackAndWhiteImage(
channel_mask_monochrome.base64,
layer.id,
session_ts.store.data.selectionInfo,
settings_tab_ts.store.data.b_borders_or_corners
)
}
}
return layer
}
const add = async (base64: string, mask?: string) => {
try {
//change the color of thumbnail border
//add image to the canvas
const layer = await moveImageToLayer_old(
base64,
session_ts.store.data.selectionInfo
)
// create channel if the generated mode support masking
if (
[
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(session_ts.store.data.mode) &&
store.data.auto_mask
) {
// const base64_monochrome_mask = await io.convertGrayscaleToMonochrome(
// session_ts.store.data.selected_mask
// )
const timer = (ms: any) => new Promise((res) => setTimeout(res, ms))
const mask_monochrome = await io.convertGrayscaleToMonochrome(
// session_ts.store.data.expanded_mask
mask
)
const channel_mask = mask_monochrome
const selectionInfo = session_ts.store.data.selectionInfo
// await selection.base64ToChannel(channel_mask, selectionInfo, 'mask')
await applyMaskFromBlackAndWhiteImage(
channel_mask,
layer.id,
selectionInfo,
settings_tab_ts.store.data.b_borders_or_corners
)
}
return layer
} catch (e) {
console.error(e)
}
}
const addWithHistory = async (base64: string, mask?: string) => {
let layer
await executeAsModal(
async (context: any) => {
let history_id
try {
history_id = await context.hostControl.suspendHistory({
documentID: app.activeDocument.id,
name: 'Add Image to Canvas',
})
} catch (e) {
console.warn(e)
}
layer = await add(base64, mask)
try {
await context.hostControl.resumeHistory(history_id)
} catch (e) {
console.warn(e)
}
},
{
commandName: 'Add Image to Canvas',
}
)
return layer
}
const remove = async (layer: any) => {
await layer_util.deleteLayers([layer]) // delete previous layer
}
export const resetViewer = () => {
store.updateProperty('images', [])
store.data.thumbnails = []
store.data.prev_index = -1
store.data.is_stored = []
store.data.layers = []
store.data.class_name = []
store.data.can_click = true
mask_store.data.images = []
mask_store.data.thumbnails = []
init_store.data.images = []
init_store.data.thumbnails = []
}
const addAll = async () => {
let i = 0
for (let i = 0; i < store.data.images.length; i++) {
if (
store.data.is_stored[i] ||
layer_util.Layer.doesLayerExist(store.data.layers?.[i])
) {
continue
}
await addWithHistory(
store.data.images[i],
mask_store.data?.output_images_masks?.[i] ?? void 0
)
}
session_ts.Session.endSession()
}
const discardAll = async () => {
for (let i = 0; i < store.data.images.length; i++) {
await remove(store.data.layers[i])
}
session_ts.Session.endSession()
}
const onlySelected = () => {
session_ts.Session.endSession()
}
export const handleOutputImageThumbnailClick = async (
index: number,
event?: any
) => {
try {
if (!store.data.can_click) return null
store.data.can_click = false
const prev_index = store.data.prev_index
const image = store.data.images[index] || ''
const is_stored = store.data.is_stored[index] || false
const is_prev_stored = store.data.is_stored[prev_index] || false
const prev_layer = store.data.layers[prev_index] || null
const prev_image = store.data.images[prev_index] || ''
console.log('prev_index:', prev_index)
console.log('is_stored:', is_stored)
console.log('is_prev_stored:', is_prev_stored)
console.log('prev_layer:', prev_layer)
// store.updateProperty('clicked_index', index)
let click_type: ClickTypeEnum = event
? findClickType(event)
: ClickTypeEnum.Click
if (
index === store.data.prev_index &&
click_type === ClickTypeEnum.Click
) {
click_type = ClickTypeEnum.SecondClick
//toggle functionality
}
console.log('click_type:', click_type)
if (click_type === ClickTypeEnum.Click) {
//1) modify layer stacks
const layer = await addWithHistory(
image,
mask_store.data?.output_images_masks?.[index] ?? void 0
)
await remove(store.data.layers[index])
console.log('layer:', layer)
store.data.layers[index] = layer
if (is_prev_stored) {
} else {
await remove(prev_layer)
}
//2)change style
store.data.class_name[prev_index] = is_prev_stored
? ClassNameEnum.Green
: ClassNameEnum.None
store.data.class_name[index] = is_stored
? ClassNameEnum.Green
: ClassNameEnum.Orange
//3)modify index
store.data.prev_index = index
} else if (click_type === ClickTypeEnum.ShiftClick) {
//1) modify layer stacks
if (prev_index === index) {
store.data.class_name[index] = ClassNameEnum.Green
} else {
if (is_prev_stored) {
} else {
// await remove(prev_layer)
}
const layer = await addWithHistory(
image,
mask_store.data?.output_images_masks?.[index] ?? void 0
)
await remove(store.data.layers[index])
store.data.layers[index] = layer
//2)change style
store.data.class_name[prev_index] = ClassNameEnum.Green
store.data.class_name[index] = ClassNameEnum.Green
//3)store index
store.data.is_stored[prev_index] = true
store.data.is_stored[index] = true
store.data.prev_index = index
}
} else if (click_type === ClickTypeEnum.AltClick) {
//1) modify layer stacks
if (is_prev_stored) {
} else {
await remove(prev_layer)
}
await remove(store.data.layers[index])
//2)change style
store.data.class_name[prev_index] = is_prev_stored
? ClassNameEnum.Green
: ClassNameEnum.None
store.data.class_name[index] = ClassNameEnum.None
//3)store index
store.data.prev_index = -1
store.data.is_stored[index] = false
} else if (click_type === ClickTypeEnum.SecondClick) {
//1) modify layer stacks
if (is_prev_stored) {
} else {
await remove(prev_layer)
}
//2)change style
store.data.class_name[prev_index] = is_prev_stored
? ClassNameEnum.Green
: ClassNameEnum.None
//3)store index
store.data.prev_index = -1
}
store.data.class_name = [...store.data.class_name]
} catch (e) {
console.warn(e)
}
store.data.can_click = true
}
const Viewer = observer(() => {
// console.log('rendered', store.toJsFunc())
const display_button: Boolean =
session_ts.store.data.is_active && session_ts.store.data.can_generate
const button_style = {
display: display_button ? 'block' : 'none',
marginRight: '3px',
}
return (
<div>
<SpSliderWithLabel
out_min={50}
out_max={300}
in_min={1}
in_max={10}
// min={85}
// max={300}
onSliderChange={(new_value: number) =>
// event: React.ChangeEvent<HTMLInputElement>
{
try {
console.log('change event triggered!')
// const new_value = event.target.value
// const base_width = 100
// const scale_ratio = new_value / base_width
// store.updateProperty('height', scale_ratio)
store.updateProperty('width', new_value)
init_store.updateProperty('width', new_value)
} catch (e) {
console.warn(e)
}
}
}
show-value={false}
steps={1}
output_value={store.data.width}
label="Thumbnail Size"
></SpSliderWithLabel>
<div
style={{
display: 'flex',
justifyContent: 'space-evenly',
paddingTop: '3px',
}}
>
<button
title={Locale('Keep all generated images on the canvas')}
className="btnSquare acceptClass acceptAllImgBtn"
style={button_style}
onClick={addAll}
></button>
<button
title={Locale(
'Delete all generated images from the canvas'
)}
className="btnSquare discardClass discardAllImgBtn"
style={button_style}
onClick={discardAll}
></button>
<button
title={Locale('Keep only the highlighted images')}
className="btnSquare acceptSelectedClass acceptSelectedImgBtn"
style={button_style}
onClick={onlySelected}
></button>
</div>
<div>
<SpCheckBox
style={{
display: [
GenerationModeEnum.Inpaint,
GenerationModeEnum.LassoInpaint,
GenerationModeEnum.Outpaint,
].includes(session_ts.store.data.mode)
? void 0
: 'none',
marginRight: '10px',
}}
onChange={(event: any) => {
store.data.auto_mask = event.target.checked
}}
checked={store.data.auto_mask}
>
{
//@ts-ignore
Locale('Apply Auto Masking')
}
</SpCheckBox>
</div>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Grid
// images={init_store.data.images}
thumbnails={init_store.data.thumbnails}
thumbnails_styles={init_store.data.class_name}
callback={(index: number, event: any) => {
console.log(index)
}}
width={init_store.data.width}
height={init_store.data.height}
// clicked_index={init_store.data.clicked_index}
// permanent_indices={init_store.data.permanent_indices}
></Grid>
</div>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Grid
// images={mask_store.data.images}
thumbnails={mask_store.data.thumbnails}
thumbnails_styles={mask_store.data.class_name}
callback={(index: number, event: any) => {
console.log(index)
}}
width={mask_store.data.width}
height={mask_store.data.height}
// clicked_index={init_store.data.clicked_index}
// permanent_indices={init_store.data.permanent_indices}
action_buttons={[
{
ComponentType: MoveToCanvasSvg,
callback: async (index: number) => {
await moveImageToLayer_old(
mask_store.data.images[index],
session_ts.store.data.selectionInfo,
'mask'
)
},
},
]}
></Grid>
</div>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Grid
// images={store.data.images}
thumbnails={store.data.thumbnails}
thumbnails_styles={store.data.class_name}
callback={handleOutputImageThumbnailClick}
width={store.data.width}
height={store.data.height}
clicked_index={store.data.clicked_index}
permanent_indices={store.data.permanent_indices}
// action_buttons={[
// {
// ComponentType: MoveToCanvasSvg,
// callback: (index: number) => {
// console.log(
// 'viewer callback:',
// store.data.images[index],
// g_generation_session.selectionInfo
// )
// moveImageToLayer(
// store.data.images[index],
// g_generation_session.selectionInfo
// )
// },
// },
// ]}
></Grid>
</div>
</div>
)
})
const ToolbarViewerButtons = observer(() => {
const display_button: Boolean =
session_ts.store.data.is_active && session_ts.store.data.can_generate
const button_style = {
display: display_button ? 'block' : 'none',
marginRight: '3px',
marginBottom: '3px',
}
return (
<div
// style={{
// display: 'flex',
// justifyContent: 'space-evenly',
// paddingTop: '3px',
// }}
>
<button
title={Locale('Keep all generated images on the canvas')}
className="btnSquare acceptClass acceptAllImgBtn"
style={button_style}
onClick={addAll}
></button>
<button
title={Locale('Delete all generated images from the canvas')}
className="btnSquare discardClass discardAllImgBtn"
style={button_style}
onClick={discardAll}
></button>
<button
title={Locale('Keep only the highlighted images')}
className="btnSquare acceptSelectedClass acceptSelectedImgBtn"
style={button_style}
onClick={onlySelected}
></button>
</div>
)
})
// const node = document.getElementById('reactViewerContainer')!
const containers = document.querySelectorAll('.reactViewerContainer')
containers.forEach((container) => {
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<div style={{ border: '2px solid #6d6c6c', padding: '3px' }}>
<Collapsible defaultIsOpen={true} label={Locale('Viewer')}>
<Viewer></Viewer>
</Collapsible>
</div>
</ErrorBoundary>
</React.StrictMode>
)
})
const button_container = document.getElementById('viewerButtonContainer')!
const root = ReactDOM.createRoot(button_container)
root.render(
<React.StrictMode>
<ErrorBoundary>
<ToolbarViewerButtons />
</ErrorBoundary>
</React.StrictMode>
)

View File

@ -1,3 +1,7 @@
//deprecated file don't use
console.warn('api.js is deprecated, use typescript/util/ts/api.ts')
async function requestGet(url) {
let json = null
@ -10,7 +14,7 @@ async function requestGet(url) {
json = await request.json()
console.log('json: ', json)
// console.log('json: ', json)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}
@ -36,7 +40,7 @@ async function requestPost(url, payload) {
json = await request.json()
console.log('json: ', json)
// console.log('json: ', json)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}

View File

@ -552,15 +552,6 @@ function setControlImageSrc(image_src, element_index = 0) {
// )
control_net_image_element.src = image_src
}
function setControlMaskSrc(image_src, element_index = 0) {
const control_net_image_element = document.querySelector(
`#controlnet_settings_${element_index} .control_net_mask_`
)
// const control_net_image_element = document.getElementById(
// 'control_net_mask' + '_' + element_index
// )
control_net_image_element.src = image_src
}
function setProgressImageSrc(image_src) {
// const progress_image_element = document.getElementById('progressImage')
@ -1153,7 +1144,6 @@ module.exports = {
setLinkWidthHeightState,
isSquareThumbnail,
setControlImageSrc,
setControlMaskSrc,
setHordeApiKey,
populateMenu,

View File

@ -2,7 +2,6 @@ const psapi = require('../psapi')
const layer_util = require('../utility/layer')
const general = require('./general')
const Jimp = require('../jimp/browser/lib/jimp.min')
const { executeAsModal } = require('photoshop').core
const batchPlay = require('photoshop').action.batchPlay
@ -216,6 +215,7 @@ class IO {
new_doc.width,
new_doc.height
) //
await layer_util.Layer.moveTo(new_layer, 0, 0) //move to the top left corner
//
await IOHelper.saveAsWebpExe(doc_entry) //save current document as .webp file, save it into doc_entry folder
@ -312,15 +312,37 @@ class IO {
) {
let layer
if (format === 'png') {
layer = await IOBase64ToLayer.base64PngToLayer(
base64_png,
image_name
)
try {
await executeAsModal(async (context) => {
// let history_id
// try {
// history_id = await context.hostControl.suspendHistory({
// documentID: app.activeDocument.id,
// name: 'Place Image',
// })
// } catch (e) {
// console.warn(e)
// }
psapi.setVisibleExe(layer, true)
await layer_util.Layer.scaleTo(layer, width, height) //
await layer_util.Layer.moveTo(layer, to_x, to_y) //move to the top left corner
psapi.setVisibleExe(layer, true)
layer = await IOBase64ToLayer.base64PngToLayer(
base64_png,
image_name
)
await psapi.setVisibleExe(layer, true)
await layer_util.Layer.scaleTo(layer, width, height) //
await layer_util.Layer.moveTo(layer, to_x, to_y) //move to the top left corner
await psapi.setVisibleExe(layer, true)
// try {
// await context.hostControl.resumeHistory(history_id)
// } catch (e) {
// console.warn(e)
// }
})
} catch (e) {
console.warn(e)
}
}
return layer
}
@ -565,13 +587,24 @@ class IOHelper {
const crop_h = selectionInfo.height
const base64_url_result = await Jimp.read(arrayBuffer)
.then(async (img) => {
let cropped_img = await img.crop(crop_x, crop_y, crop_w, crop_h)
let cropped_img = await img.crop(
crop_x,
crop_y,
crop_w,
crop_h
// crop_w - 1,
// crop_h - 1
)
let resized_img
if (b_resize) {
resized_img = await cropped_img.resize(
resize_width,
resize_height
resize_height,
// Jimp.RESIZE_BILINEAR
// Jimp.RESIZE_NEAREST_NEIGHBOR
settings_tab_ts.store.data.scale_interpolation_method
.jimp
)
} else {
resized_img = cropped_img
@ -728,7 +761,7 @@ class IOLog {
append: true,
})
} catch (e) {
console.warn(e)
_warn(e)
}
}
}
@ -821,6 +854,530 @@ class IOJson {
}
}
async function createThumbnail(base64Image, width = 100) {
const image = await Jimp.read(Buffer.from(base64Image, 'base64'))
image.resize(
width,
Jimp.AUTO,
settings_tab_ts.store.data.scale_interpolation_method.jimp
)
const thumbnail = await image.getBase64Async(Jimp.MIME_PNG)
return thumbnail
}
async function getImageFromCanvas() {
const width = html_manip.getWidth()
const height = html_manip.getHeight()
const selectionInfo = await psapi.getSelectionInfoExe()
const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
width,
height,
selectionInfo,
true
)
return base64
}
async function getBase64FromJimp(jimp_image) {
const dataURL = await jimp_image.getBase64Async(Jimp.MIME_PNG)
const base64 = dataURL.replace(/^data:image\/png;base64,/, '')
return base64
}
function transparentToMask(x, y, idx) {
const alpha = this.bitmap.data[idx + 3]
let color
if (alpha === 0) {
color = 0xffffffff
} else if (alpha === 255) {
color = 0x000000ff
} else {
color = Jimp.rgbaToInt(alpha, alpha, alpha, 255)
}
this.setPixelColor(color, x, y)
}
function inpaintTransparentToMask(x, y, idx) {
const alpha = this.bitmap.data[idx + 3]
let color
// if (alpha === 0) {
// color = 0x000000ff
// } else if (alpha === 255) {
// color = 0xffffffff
// } else {
// color = Jimp.rgbaToInt(alpha, alpha, alpha, 255)
// }
if (alpha === 0) {
color = 0x000000ff
} else {
color = 0xffffffff
}
this.setPixelColor(color, x, y)
}
function transparentToWhiteBackground(x, y, idx) {
const alpha = this.bitmap.data[idx + 3]
let color
if (alpha === 0) {
color = 0xffffffff
} else {
color = Jimp.rgbaToInt(
this.bitmap.data[idx],
this.bitmap.data[idx + 1],
this.bitmap.data[idx + 2],
255
) // remove transparency but keep the color, This is bad. used as workaround Auto1111 not able to handle alpha channels
}
this.setPixelColor(color, x, y)
}
async function getMask() {
try {
let b = app.activeDocument.backgroundLayer
await executeAsModal(() => (b.visible = false))
const base64 = await getImageFromCanvas()
await executeAsModal(() => (b.visible = true))
const jimp_image = await Jimp.read(Buffer.from(base64, 'base64'))
const jimp_mask = await jimp_image.scan(
0,
0,
jimp_image.bitmap.width,
jimp_image.bitmap.height,
transparentToMask
)
html_manip.setInitImageSrc(
await jimp_mask.getBase64Async(Jimp.MIME_PNG)
)
const mask = await getBase64FromJimp(jimp_mask)
return mask
} catch (e) {
console.warn(e)
}
}
async function getImg2ImgInitImage() {
//the init image will has transparent pixel in it
//the mask will be a grayscale image/white and black
try {
let b = app.activeDocument.backgroundLayer
await executeAsModal(() => (b.visible = false))
const base64 = await getImageFromCanvas()
await executeAsModal(() => (b.visible = true))
const init_image = base64
html_manip.setInitImageSrc(general.base64ToBase64Url(init_image)) // convert jimp_image to img.src data
// console.log('mask: ', mask)
return init_image
} catch (e) {
console.warn(e)
}
}
async function getOutpaintInitImageAndMask() {
//the init image will has transparent pixel in it
//the mask will be a grayscale image/white and black
try {
let b = app.activeDocument.backgroundLayer
await executeAsModal(() => (b.visible = false))
const base64 = await getImageFromCanvas()
await executeAsModal(() => (b.visible = true))
const init_image = base64
let jimp_init = await Jimp.read(Buffer.from(base64, 'base64'))
let jimp_mask = await jimp_init
.clone()
.scan(
0,
0,
jimp_init.bitmap.width,
jimp_init.bitmap.height,
transparentToMask
)
// jimp_init = await jimp_init.scan(
// 0,
// 0,
// jimp_init.bitmap.width,
// jimp_init.bitmap.height,
// transparentToWhiteBackground
// // transparentToMask
// )
html_manip.setInitImageMaskSrc(
await jimp_mask.getBase64Async(Jimp.MIME_PNG)
) // convert jimp_image to img.src data
html_manip.setInitImageSrc(
await jimp_init.getBase64Async(Jimp.MIME_PNG)
) // convert jimp_image to img.src data
const mask = await getBase64FromJimp(jimp_mask)
// console.log('mask: ', mask)
return {
init_image,
mask,
}
} catch (e) {
console.warn(e)
}
}
//generate black and white mask image from
async function getMaskFromCanvas() {
try {
await executeAsModal(async () => await layer_util.toggleActiveLayer()) //only white mark layer should be visible
let mask_base64 = await getImageFromCanvas()
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // undo the toggling operation, active layer will be visible
app.activeDocument.activeLayers[0].visible = false //hide the white mark
})
let jimp_mask = await Jimp.read(Buffer.from(mask_base64, 'base64')) //make jimp object
jimp_mask = await jimp_mask.scan(
0,
0,
jimp_mask.bitmap.width,
jimp_mask.bitmap.height,
inpaintTransparentToMask
) //convert transparent image to black and white image
mask_base64 = await getBase64FromJimp(jimp_mask)
return mask_base64
} catch (e) {
warn(e)
}
}
async function getInpaintInitImageAndMask() {
try {
await executeAsModal(async () => await layer_util.toggleActiveLayer()) //only white mark layer should be visible
const mask_base64 = await getImageFromCanvas()
await executeAsModal(async () => {
await layer_util.toggleActiveLayer() // undo the toggling operation, active layer will be visible
app.activeDocument.activeLayers[0].visible = false //hide the white mark
})
const init_base64 = await getImageFromCanvas()
let jimp_mask = await Jimp.read(Buffer.from(mask_base64, 'base64')) //make jimp object
let jimp_init = await Jimp.read(Buffer.from(init_base64, 'base64')) //make jimp object, jimp_init will have transparent pixels, should we convert to white??
jimp_mask = await jimp_mask.scan(
0,
0,
jimp_mask.bitmap.width,
jimp_mask.bitmap.height,
inpaintTransparentToMask
) //convert transparent image to black and white image
html_manip.setInitImageMaskSrc(
await jimp_mask.getBase64Async(Jimp.MIME_PNG)
)
html_manip.setInitImageSrc(
await jimp_init.getBase64Async(Jimp.MIME_PNG)
)
const mask = await getBase64FromJimp(jimp_mask)
const init_image = await getBase64FromJimp(jimp_init)
return { init_image, mask }
} catch (e) {
console.warn(e)
}
}
async function saveFileInSubFolder(b64Image, sub_folder_name, file_name) {
// const b64Image =
// 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC'
const img = _base64ToArrayBuffer(b64Image)
// const img_name = 'temp_output_image.png'
const img_name = file_name
const folder = await storage.localFileSystem.getDataFolder()
const documentFolderName = sub_folder_name
let documentFolder
try {
documentFolder = await folder.getEntry(documentFolderName)
} catch (e) {
console.warn(e)
//create document folder
documentFolder = await folder.createFolder(documentFolderName)
}
console.log('documentFolder.nativePath: ', documentFolder.nativePath)
const file = await documentFolder.createFile(img_name, { overwrite: true })
await file.write(img, { format: storage.formats.binary })
const token = await storage.localFileSystem.createSessionToken(file) // batchPlay requires a token on _path
}
//REFACTOR: move to document.js
async function saveJsonFileInSubFolder(json, sub_folder_name, file_name) {
// const b64Image =
// 'iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAIAAADTED8xAAADMElEQVR4nOzVwQnAIBQFQYXff81RUkQCOyDj1YOPnbXWPmeTRef+/3O/OyBjzh3CD95BfqICMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMK0CMO0TAAD//2Anhf4QtqobAAAAAElFTkSuQmCC'
// const img_name = 'temp_output_image.png'
const json_file_name = file_name
const folder = await storage.localFileSystem.getDataFolder()
const documentFolderName = sub_folder_name
let documentFolder
try {
documentFolder = await folder.getEntry(documentFolderName)
} catch (e) {
console.warn(e)
//create document folder
documentFolder = await folder.createFolder(documentFolderName)
}
console.log('documentFolder.nativePath: ', documentFolder.nativePath)
const file = await documentFolder.createFile(json_file_name, {
type: storage.types.file,
overwrite: true,
})
const JSONInPrettyFormat = JSON.stringify(json, undefined, 4)
await file.write(JSONInPrettyFormat, {
format: storage.formats.utf8,
append: false,
})
const token = await storage.localFileSystem.createSessionToken(file) // batchPlay requires a token on _path
}
async function fixTransparentEdges(base64) {
function transparentToOpaque(x, y, idx) {
const alpha = this.bitmap.data[idx + 3]
if (alpha > 0 && alpha < 255) {
this.bitmap.data[idx + 3] = 0 //make semi transparent pixels completely transparent
}
}
try {
let jimp_img = await Jimp.read(Buffer.from(base64, 'base64'))
jimp_img = await jimp_img.scan(
0,
0,
jimp_img.bitmap.width,
jimp_img.bitmap.height,
transparentToOpaque
)
const opaque_base64 = await getBase64FromJimp(jimp_img)
return opaque_base64
} catch (e) {
console.warn(e)
}
}
async function maskFromInitImage(base64) {
function setTransparentToBlack(x, y, idx) {
let alpha = this.bitmap.data[idx + 3]
if (alpha !== 0) {
this.bitmap.data[idx] = 0
this.bitmap.data[idx + 1] = 0
this.bitmap.data[idx + 2] = 0
this.bitmap.data[idx + 3] = 255
} else {
//alpha === 0
this.bitmap.data[idx] = 255
this.bitmap.data[idx + 1] = 255
this.bitmap.data[idx + 2] = 255
this.bitmap.data[idx + 3] = 255
}
}
try {
let jimp_img = await Jimp.read(Buffer.from(base64, 'base64'))
jimp_img = await jimp_img.scan(
0,
0,
jimp_img.bitmap.width,
jimp_img.bitmap.height,
setTransparentToBlack
)
const mask_base64 = await getBase64FromJimp(jimp_img)
return mask_base64
} catch (e) {
console.warn(e)
}
}
async function fixMaskEdges(base64) {
function grayScaleToBlack(x, y, idx) {
if (
this.bitmap.data[idx] !== 255 ||
this.bitmap.data[idx + 1] !== 255 ||
this.bitmap.data[idx + 2] !== 255
) {
this.bitmap.data[idx] = 0
this.bitmap.data[idx + 1] = 0
this.bitmap.data[idx + 2] = 0
}
}
try {
let jimp_img = await Jimp.read(Buffer.from(base64, 'base64'))
jimp_img = await jimp_img.scan(
0,
0,
jimp_img.bitmap.width,
jimp_img.bitmap.height,
grayScaleToBlack
)
const opaque_base64 = await getBase64FromJimp(jimp_img)
return opaque_base64
} catch (e) {
console.warn(e)
}
}
async function getUniqueDocumentId() {
try {
let uniqueDocumentId = await psapi.readUniqueDocumentIdExe()
console.log(
'getUniqueDocumentId(): uniqueDocumentId: ',
uniqueDocumentId
)
// Regular expression to check if string is a valid UUID
const regexExp =
/^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi
// String with valid UUID separated by dash
// const str = 'a24a6ea4-ce75-4665-a070-57453082c256'
const isValidId = regexExp.test(uniqueDocumentId) // true
console.log('isValidId: ', isValidId)
if (isValidId == false) {
let uuid = self.crypto.randomUUID()
console.log(uuid) // for example "36b8f84d-df4e-4d49-b662-bcde71a8764f"
await psapi.saveUniqueDocumentIdExe(uuid)
uniqueDocumentId = uuid
}
return uniqueDocumentId
} catch (e) {
console.warn('warning Document Id may not be valid', e)
}
}
async function getImageSize(base64) {
const image = await Jimp.read(Buffer.from(base64, 'base64'))
const width = image.bitmap.width
const height = image.bitmap.height
return { width, height }
}
async function convertGrayscaleToMonochrome(base64) {
function grayToMonoPixel(x, y, idx) {
// convert any grayscale value to white, resulting in black and white image
// if (this.bitmap.data[idx] > 0) {
// this.bitmap.data[idx] = 255
// }
// if (this.bitmap.data[idx + 1] > 0) {
// this.bitmap.data[idx + 1] = 255
// }
// if (this.bitmap.data[idx + 2] > 0) {
// this.bitmap.data[idx + 2] = 255
// }
let color
if (
this.bitmap.data[idx] !== 0 &&
this.bitmap.data[idx + 1] !== 0 &&
this.bitmap.data[idx + 2] !== 0
) {
color = 0xffffffff
} else {
color = 0x000000ff
}
this.setPixelColor(color, x, y)
}
try {
const jimp_image = await Jimp.read(Buffer.from(base64, 'base64'))
const jimp_mask = await jimp_image.scan(
0,
0,
jimp_image.bitmap.width,
jimp_image.bitmap.height,
grayToMonoPixel
)
const base64_monochrome_mask = await getBase64FromJimp(jimp_mask)
return base64_monochrome_mask
} catch (e) {
console.warn(e)
}
}
async function convertBlackToTransparentKeepBorders(
base64,
b_borders_or_corners = enum_ts.MaskModeEnum.Transparent // false for borders, true for corners
) {
try {
let jimp_mask = await Jimp.read(Buffer.from(base64, 'base64'))
const width = jimp_mask.bitmap.width
const height = jimp_mask.bitmap.height
jimp_mask = await jimp_mask.scan(
0,
0,
width,
height,
function (x, y, idx) {
if (b_borders_or_corners === enum_ts.MaskModeEnum.Borders) {
// keep borders
if (
x === 0 ||
y === 0 ||
x === width - 1 ||
y === height - 1
)
return
} else if (
b_borders_or_corners === enum_ts.MaskModeEnum.Corners
) {
// keep corners
if (
(x === 0 && y === 0) ||
(x === 0 && y === height - 1) ||
(x === width - 1 && y === 0) ||
(x === width - 1 && y === height - 1)
)
return
}
const red = this.bitmap.data[idx + 0]
const green = this.bitmap.data[idx + 1]
const blue = this.bitmap.data[idx + 2]
if (red === 0 && green === 0 && blue === 0) {
this.bitmap.data[idx + 3] = 0
}
}
)
const base64_mask = await getBase64FromJimp(jimp_mask)
return base64_mask
} catch (e) {
console.warn(e)
}
}
async function deleteFileIfLargerThan(file_name, size_mb = 200) {
// const file = await fs.getEntry('path/to/file.txt')
try {
const plugin_folder = await fs.getDataFolder()
try {
var file = await plugin_folder.getEntry(file_name)
} catch (e) {
_warn(e)
}
if (file) {
const contents = await file.read({ format: storage.formats.binary })
// storage.formats.utf8
const fileSizeInBytes = contents.byteLength
const fileSizeInMegabytes = fileSizeInBytes / (1024 * 1024)
if (fileSizeInMegabytes > size_mb) {
await fs.removeEntry(file)
}
}
} catch (e) {
// console.warn(e)
_warn(e)
}
}
module.exports = {
IO,
snapShotLayerExe,
@ -832,4 +1389,21 @@ module.exports = {
convertBlackAndWhiteImageToRGBChannels2,
convertBlackAndWhiteImageToRGBChannels3,
isBlackAndWhiteImage,
createThumbnail,
getMask,
getOutpaintInitImageAndMask,
getInpaintInitImageAndMask,
getImg2ImgInitImage,
saveFileInSubFolder,
saveJsonFileInSubFolder,
fixTransparentEdges,
fixMaskEdges,
maskFromInitImage,
getImageFromCanvas,
getUniqueDocumentId,
getImageSize,
convertGrayscaleToMonochrome,
deleteFileIfLargerThan,
getMaskFromCanvas,
convertBlackToTransparentKeepBorders,
}

View File

@ -11,6 +11,9 @@ const {
} = require('../psapi')
const psapi = require('../psapi')
// const Jimp = require('../jimp/browser/lib/jimp.min')
// const { settings_tab_ts } = require('../typescripts/dist/bundle')
const constants = require('photoshop').constants
async function createNewLayerExe(layerName, opacity = 100) {
await executeAsModal(async () => {
@ -157,13 +160,128 @@ class Layer {
const scale_x_ratio = (new_width / layer_info.width) * 100
const scale_y_ratio = (new_height / layer_info.height) * 100
console.log('scale_x_y_ratio:', scale_x_ratio, scale_y_ratio)
await layer.scale(scale_x_ratio, scale_y_ratio)
// await layer.scale(
// scale_x_ratio,
// scale_y_ratio,
// constants.ResampleMethod.BILINEAR
// )
await layer_util.Layer.resizeImageExe(
scale_x_ratio,
scale_y_ratio
)
await psapi.reSelectMarqueeExe(selection_info)
} catch (e) {
console.warn(e)
}
})
}
static async resizeImage(percent_width, percent_height) {
// let imageSizeDescriptor = {
// _obj: 'imageSize',
// scaleStyles: true,
// constrainProportions: true,
// interfaceIconFrameDimmed: {
// _enum: 'interpolationType',
// // _value: 'automaticInterpolation',
// _value: 'bilinear',
// },
// _options: {
// dialogOptions: 'dontDisplay',
// },
// }
// if (width !== undefined && width !== null) {
// imageSizeDescriptor.width = {
// _unit: 'pixelsUnit',
// _value: width,
// }
// }
// if (height !== undefined && height !== null) {
// imageSizeDescriptor.height = {
// _unit: 'pixelsUnit',
// _value: height,
// }
// }
const setInterpolationMethodDesc = {
_obj: 'set',
_target: [
{
_ref: 'property',
_property: 'generalPreferences',
},
{
_ref: 'application',
_enum: 'ordinal',
_value: 'targetEnum',
},
],
to: {
_obj: 'generalPreferences',
interpolationMethod: {
_enum: 'interpolationType',
// _value: 'bilinear',
_value: settings_tab_ts.store.data
.scale_interpolation_method.photoshop,
},
},
_isCommand: true,
}
let imageSizeDescriptor = {
_obj: 'transform',
_target: [
{
_ref: 'layer',
_enum: 'ordinal',
_value: 'targetEnum',
},
],
freeTransformCenterState: {
_enum: 'quadCenterState',
_value: 'QCSAverage',
},
// offset: {
// _obj: 'offset',
// horizontal: {
// _unit: 'pixelsUnit',
// _value: 24.4388124547429,
// },
// vertical: {
// _unit: 'pixelsUnit',
// _value: -24.4388124547429,
// },
// },
width: {
_unit: 'percentUnit',
_value: percent_width,
},
height: {
_unit: 'percentUnit',
_value: percent_height,
},
linked: true,
interfaceIconFrameDimmed: {
_enum: 'interpolationType',
// _value: 'bilinear',
_value: settings_tab_ts.store.data.scale_interpolation_method
.photoshop,
},
_isCommand: true,
}
return batchPlay([setInterpolationMethodDesc, imageSizeDescriptor], {
synchronousExecution: true,
modalBehavior: 'execute',
})
}
static async resizeImageExe(percent_width, percent_height) {
try {
await executeAsModal(async () => {
await this.resizeImage(percent_width, percent_height)
})
} catch (e) {
console.warn(e)
}
}
static async moveTo(layer, to_x, to_y) {
try {
@ -277,6 +395,27 @@ const createSolidLayerDesc = (r, g, b) => ({
},
})
async function toggleActiveLayer() {
const result = await batchPlay(
[
{
_obj: 'show',
null: [
{
_ref: 'layer',
_enum: 'ordinal',
_value: 'targetEnum',
},
],
toggleOptionsPalette: true,
_options: {
dialogOptions: 'dontDisplay',
},
},
],
{}
)
}
const toggleBackgroundLayerDesc = () => ({
_obj: 'show',
null: [
@ -354,4 +493,5 @@ module.exports = {
createSolidLayerDesc,
makeBackgroundLayerDesc,
toggleBackgroundLayerExe,
toggleActiveLayer,
}

View File

@ -71,10 +71,13 @@ class Notification {
}
return false
}
static async inactiveSelectionArea(is_active_session) {
static async inactiveSelectionArea(
is_active_session,
button_label = 'Continue Session'
) {
let buttons = ['Cancel', 'Rectangular Marquee']
if (is_active_session) {
buttons.push('Continue Session')
buttons.push(button_label)
}
const r1 = await dialog_box.prompt(
'Please Select a Rectangular Area',
@ -89,7 +92,7 @@ class Notification {
console.log('Rectangular Marquee')
psapi.selectMarqueeRectangularToolExe()
return false // should this be false?! what does true and false means in this context?! Yes: it should be false since boolean value represent wither we have an active selection area or not
} else if (r1 === 'Continue Session') {
} else if (r1 === button_label) {
await activateSessionSelectionArea()
return true
}

View File

@ -3,7 +3,6 @@ const html_manip = require('../html_manip')
const Enum = require('../../enum')
const event = require('../event')
// const control_net = require('../../utility/tab/control_net')
let settings = {
model: null,
prompt_shortcut: null,
@ -161,10 +160,9 @@ function getPresetSettings(preset_type) {
if (preset_type === Enum.PresetTypeEnum['SDPreset']) {
preset_settings = g_ui_settings_object.getSettings()
} else if (preset_type === Enum.PresetTypeEnum['ControlNetPreset']) {
const { ControlNetUnit } = require('../../utility/tab/control_net') // only import ControlNetUnit to avoid circular dependency
// preset_settings = control_net.ControlNetUnit.getUnits()
preset_settings = ControlNetUnit.getUnits()
const { getUnitsData } =
require('../../typescripts/dist/bundle').control_net // only import ControlNetUnit to avoid circular dependency
preset_settings = getUnitsData()
}
return preset_settings
}

View File

@ -139,7 +139,7 @@ class hordeGenerator {
const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
//send the base64 to the server to save the file in the desired directory
// await sdapi.requestSavePng(base64_image, image_name)
await saveFileInSubFolder(base64_image, document_name, image_name)
await io.saveFileInSubFolder(base64_image, document_name, image_name)
return base64_image
}
@ -156,7 +156,7 @@ class hordeGenerator {
const base64_image = _arrayBufferToBase64(image_buffer) //convert the buffer to base64
//send the base64 to the server to save the file in the desired directory
// await sdapi.requestSavePng(base64_image, image_name)
await saveFileInSubFolder(base64_image, document_name, image_name)
await io.saveFileInSubFolder(base64_image, document_name, image_name)
return base64_image
}
@ -249,7 +249,7 @@ class hordeGenerator {
g_generation_session.base64OutputImages[path] =
image_info['base64']
await saveJsonFileInSubFolder(
await io.saveJsonFileInSubFolder(
this.plugin_settings,
document_name,
json_file_name

View File

@ -17,23 +17,15 @@ function replaceShortcut(text, prompt_shortcut_json) {
return content
})
// original_substrings = list(map(lambda s: '{'+s+'}',raw_keywords))
// print("strip_keywords: ", strip_keywords)
// print("original_substrings: ",original_substrings)
// # print ("text:",text)
let i = 0
for (const word of strip_keywords) {
// # word = word.strip()
// print("word: ",word)
if (word.length > 0 && prompt_shortcut_json.hasOwnProperty(word)) {
const prompt = prompt_shortcut_json[word]
console.log('prompt: ', prompt)
// console.log('prompt: ', prompt)
text = text.replace(original_keywords[i], prompt)
}
}
console.log('final text: ', text)
// console.log('final text: ', text)
return text
}
module.exports = {

Some files were not shown because too many files have changed in this diff Show More