V 2.2.7 lama iopaint 去水印

This commit is contained in:
lq1405 2024-06-06 13:12:04 +08:00
parent 8b011856c2
commit f27ec08724
39 changed files with 2495 additions and 547 deletions

8
.gitignore vendored
View File

@ -4,6 +4,14 @@ out
resources/scripts/build*
resources/scripts/dist
resources/scripts/model
resources/scripts/lama/model
resources/scripts/lama/lama.7z
resources/scripts/lama/_internal
resources/scripts/lama/dist
resources/scripts/lama/build
resources/scripts/lama/lama_inpaint.exe
resources/scripts/virtual py
resources/logger
resources/scripts/Temp
resources/image/Temp*
resources/package/ffmpeg-2023*

244
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "laitool",
"version": "2.2.6",
"version": "2.2.7",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "laitool",
"version": "2.2.5",
"version": "2.2.6",
"hasInstallScript": true,
"dependencies": {
"@alicloud/alimt20181012": "^1.2.0",
@ -19,6 +19,7 @@
"7zip-min": "^1.4.4",
"awesome-js": "^2.0.0",
"axios": "^1.6.5",
"blob-to-buffer": "^1.2.9",
"compressing": "^1.10.0",
"crypto-js": "^4.2.0",
"electron-store": "^9.0.0",
@ -33,13 +34,16 @@
"node-machine-id": "^1.1.12",
"node-reg": "^0.2.4",
"npm": "^10.7.0",
"paddle": "^1.0.0",
"sharp": "^0.33.2",
"systeminformation": "^5.22.10",
"tencentcloud-sdk-nodejs": "^4.0.821",
"uuid": "^9.0.1",
"vue-router": "^4.2.5",
"wav-file-info": "^0.0.10",
"winreg": "^1.2.5"
"winreg": "^1.2.5",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.1",
@ -849,6 +853,14 @@
"node": ">=6.9.0"
}
},
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/@colors/colors/-/colors-1.6.0.tgz",
"integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
"engines": {
"node": ">=0.1.90"
}
},
"node_modules/@css-render/plugin-bem": {
"version": "0.15.12",
"license": "MIT",
@ -863,6 +875,16 @@
"vue": "^3.0.11"
}
},
"node_modules/@dabh/diagnostics": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
"integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==",
"dependencies": {
"colorspace": "1.1.x",
"enabled": "2.0.x",
"kuler": "^2.0.0"
}
},
"node_modules/@develar/schema-utils": {
"version": "2.6.5",
"dev": true,
@ -2201,6 +2223,11 @@
"@types/node": "*"
}
},
"node_modules/@types/triple-beam": {
"version": "1.3.5",
"resolved": "https://registry.npmmirror.com/@types/triple-beam/-/triple-beam-1.3.5.tgz",
"integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw=="
},
"node_modules/@types/xml2js": {
"version": "0.4.14",
"license": "MIT",
@ -3036,6 +3063,25 @@
"safe-buffer": "~5.1.0"
}
},
"node_modules/blob-to-buffer": {
"version": "1.2.9",
"resolved": "https://registry.npmmirror.com/blob-to-buffer/-/blob-to-buffer-1.2.9.tgz",
"integrity": "sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bluebird": {
"version": "3.7.2",
"dev": true,
@ -3309,8 +3355,9 @@
},
"node_modules/canvas": {
"version": "2.11.2",
"resolved": "https://registry.npmmirror.com/canvas/-/canvas-2.11.2.tgz",
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
@ -3429,6 +3476,37 @@
"color-support": "bin.js"
}
},
"node_modules/colorspace": {
"version": "1.1.4",
"resolved": "https://registry.npmmirror.com/colorspace/-/colorspace-1.1.4.tgz",
"integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==",
"dependencies": {
"color": "^3.1.3",
"text-hex": "1.0.x"
}
},
"node_modules/colorspace/node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/color/-/color-3.2.1.tgz",
"integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==",
"dependencies": {
"color-convert": "^1.9.3",
"color-string": "^1.6.0"
}
},
"node_modules/colorspace/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmmirror.com/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/colorspace/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"license": "MIT",
@ -4401,6 +4479,11 @@
"devOptional": true,
"license": "MIT"
},
"node_modules/enabled": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/enabled/-/enabled-2.0.0.tgz",
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"license": "MIT",
@ -4839,6 +4922,11 @@
"pend": "^1.2.0"
}
},
"node_modules/fecha": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/fecha/-/fecha-4.2.3.tgz",
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
},
"node_modules/file-entry-cache": {
"version": "6.0.1",
"dev": true,
@ -4850,6 +4938,14 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-stream-rotator": {
"version": "0.6.1",
"resolved": "https://registry.npmmirror.com/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
"integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
"dependencies": {
"moment": "^2.29.1"
}
},
"node_modules/file-type": {
"version": "16.5.4",
"license": "MIT",
@ -4931,6 +5027,11 @@
"version": "1.0.0",
"license": "MIT"
},
"node_modules/fn.name": {
"version": "1.1.0",
"resolved": "https://registry.npmmirror.com/fn.name/-/fn.name-1.1.0.tgz",
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"funding": [
@ -5926,6 +6027,11 @@
"version": "12.20.55",
"license": "MIT"
},
"node_modules/kuler": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/kuler/-/kuler-2.0.0.tgz",
"integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A=="
},
"node_modules/lazy-val": {
"version": "1.0.5",
"license": "MIT"
@ -6125,6 +6231,22 @@
"license": "MIT",
"peer": true
},
"node_modules/logform": {
"version": "2.6.0",
"resolved": "https://registry.npmmirror.com/logform/-/logform-2.6.0.tgz",
"integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==",
"dependencies": {
"@colors/colors": "1.6.0",
"@types/triple-beam": "^1.3.2",
"fecha": "^4.2.0",
"ms": "^2.1.1",
"safe-stable-stringify": "^2.3.1",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/long": {
"version": "5.2.3",
"license": "Apache-2.0"
@ -6307,6 +6429,14 @@
"node": ">=10"
}
},
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmmirror.com/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.2",
"license": "MIT"
@ -8927,6 +9057,14 @@
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"license": "MIT",
@ -8946,6 +9084,14 @@
"wrappy": "1"
}
},
"node_modules/one-time": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/one-time/-/one-time-1.0.0.tgz",
"integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
"dependencies": {
"fn.name": "1.x.x"
}
},
"node_modules/optionator": {
"version": "0.9.3",
"dev": true,
@ -8996,6 +9142,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/paddle": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/paddle/-/paddle-1.0.0.tgz",
"integrity": "sha512-zqzikf2FmYZZNR2QBb3+B1nYF60jJBWke9B8WknDHLViY/ergS3/rgq8eVL5W5KIZ48Lj+NVN77J56bfqJFYzA==",
"engines": {
"node": "*"
}
},
"node_modules/pako": {
"version": "1.0.11",
"license": "(MIT AND Zlib)"
@ -9574,6 +9728,14 @@
],
"license": "MIT"
},
"node_modules/safe-stable-stringify": {
"version": "2.4.3",
"resolved": "https://registry.npmmirror.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
"integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
"engines": {
"node": ">=10"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"license": "MIT"
@ -9867,6 +10029,14 @@
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/stack-trace": {
"version": "0.0.10",
"resolved": "https://registry.npmmirror.com/stack-trace/-/stack-trace-0.0.10.tgz",
"integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
"engines": {
"node": "*"
}
},
"node_modules/stat-mode": {
"version": "1.0.0",
"dev": true,
@ -10182,6 +10352,11 @@
"version": "1.13.0",
"license": "0BSD"
},
"node_modules/text-hex": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/text-hex/-/text-hex-1.0.0.tgz",
"integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg=="
},
"node_modules/text-table": {
"version": "0.2.0",
"dev": true,
@ -10269,6 +10444,14 @@
"version": "0.3.11",
"license": "MIT"
},
"node_modules/triple-beam": {
"version": "1.4.1",
"resolved": "https://registry.npmmirror.com/triple-beam/-/triple-beam-1.4.1.tgz",
"integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
"engines": {
"node": ">= 14.0.0"
}
},
"node_modules/truncate-utf8-bytes": {
"version": "1.0.2",
"dev": true,
@ -10673,6 +10856,57 @@
"version": "1.2.5",
"license": "BSD-2-Clause"
},
"node_modules/winston": {
"version": "3.13.0",
"resolved": "https://registry.npmmirror.com/winston/-/winston-3.13.0.tgz",
"integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==",
"dependencies": {
"@colors/colors": "^1.6.0",
"@dabh/diagnostics": "^2.0.2",
"async": "^3.2.3",
"is-stream": "^2.0.0",
"logform": "^2.4.0",
"one-time": "^1.0.0",
"readable-stream": "^3.4.0",
"safe-stable-stringify": "^2.3.1",
"stack-trace": "0.0.x",
"triple-beam": "^1.3.0",
"winston-transport": "^4.7.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/winston-daily-rotate-file": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
"integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
"dependencies": {
"file-stream-rotator": "^0.6.1",
"object-hash": "^3.0.0",
"triple-beam": "^1.4.1",
"winston-transport": "^4.7.0"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"winston": "^3"
}
},
"node_modules/winston-transport": {
"version": "4.7.0",
"resolved": "https://registry.npmmirror.com/winston-transport/-/winston-transport-4.7.0.tgz",
"integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==",
"dependencies": {
"logform": "^2.3.2",
"readable-stream": "^3.6.0",
"triple-beam": "^1.3.0"
},
"engines": {
"node": ">= 12.0.0"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"dev": true,
@ -10886,4 +11120,4 @@
}
}
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "laitool",
"version": "2.2.6",
"version": "2.2.7",
"description": "An Electron application with Vue",
"main": "./out/main/index.js",
"author": "example.com",
@ -27,6 +27,7 @@
"7zip-min": "^1.4.4",
"awesome-js": "^2.0.0",
"axios": "^1.6.5",
"blob-to-buffer": "^1.2.9",
"compressing": "^1.10.0",
"crypto-js": "^4.2.0",
"electron-store": "^9.0.0",
@ -41,13 +42,16 @@
"node-machine-id": "^1.1.12",
"node-reg": "^0.2.4",
"npm": "^10.7.0",
"paddle": "^1.0.0",
"sharp": "^0.33.2",
"systeminformation": "^5.22.10",
"tencentcloud-sdk-nodejs": "^4.0.821",
"uuid": "^9.0.1",
"vue-router": "^4.2.5",
"wav-file-info": "^0.0.10",
"winreg": "^1.2.5"
"winreg": "^1.2.5",
"winston": "^3.13.0",
"winston-daily-rotate-file": "^5.0.0"
},
"devDependencies": {
"@electron-toolkit/eslint-config": "^1.0.1",
@ -78,6 +82,8 @@
"resources/image/zhanwei.png",
"resources/scripts/model/**",
"resources/scripts/Lai.exe",
"resources/scripts/lama/lama_inpaint.exe",
"resources/scripts/lama/model/**",
"resources/scripts/discordScript.js",
"resources/tmp/**",
"resources/icon.ico"

View File

@ -0,0 +1,2 @@
@echo off
pyinstaller -F --upx-dir="C:\\Users\\27698\\Desktop\\upx-4.2.4-win64\upx.exe" lama_inpaint.py

View File

@ -0,0 +1,173 @@
import io
import os
import sys
from typing import Union
import cv2
import torch
import numpy as np
from PIL import Image
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
# 判断sys.argv 的长度如果小于2说明没有传入参数设置初始参数
# if len(sys.argv) < 2:
# sys.argv = [
# "C:/Users/27698/Desktop/LAITool/resources/scripts/lama/lama_inpaint.exe",
# "-l",
# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\temp\\1717508661218.png",
# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\mask_temp_1717508662659.png",
# "C:\\Users\\27698\\Desktop\\测试\\mjTest\\data\\mask\\temp\\1717508564042.png",
# ]
print(sys.argv)
if getattr(sys, "frozen", False):
cript_directory = os.path.dirname(sys.executable)
elif __file__:
cript_directory = os.path.dirname(__file__)
link_name = os.path.join(os.path.expanduser("~"), "big_lama.pt")
cu_name = os.path.join(cript_directory, "model\\big-lama.pt")
mode_pa = link_name
if len(sys.argv) < 2:
# # 判断model_path是否存在如果不存在设置默认值
if not os.path.exists(link_name):
os.system(f'mklink "{link_name}" "{cu_name}"')
print("Params: <runtime-config.json>")
sys.exit(0)
def get_image(image):
if isinstance(image, Image.Image):
img = np.array(image)
elif isinstance(image, np.ndarray):
img = image.copy()
else:
raise Exception("Input image should be either PIL Image or numpy array!")
if img.ndim == 3:
img = np.transpose(img, (2, 0, 1)) # chw
elif img.ndim == 2:
img = img[np.newaxis, ...]
assert img.ndim == 3
img = img.astype(np.float32) / 255
return img
def ceil_modulo(x, mod):
if x % mod == 0:
return x
return (x // mod + 1) * mod
def scale_image(img, factor, interpolation=cv2.INTER_AREA):
if img.shape[0] == 1:
img = img[0]
else:
img = np.transpose(img, (1, 2, 0))
img = cv2.resize(img, dsize=None, fx=factor, fy=factor, interpolation=interpolation)
if img.ndim == 2:
img = img[None, ...]
else:
img = np.transpose(img, (2, 0, 1))
return img
def pad_img_to_modulo(img, mod):
channels, height, width = img.shape
out_height = ceil_modulo(height, mod)
out_width = ceil_modulo(width, mod)
return np.pad(
img,
((0, 0), (0, out_height - height), (0, out_width - width)),
mode="symmetric",
)
def prepare_img_and_mask(image, mask, device, pad_out_to_modulo=8, scale_factor=None):
out_image = get_image(image)
out_mask = get_image(mask)
if scale_factor is not None:
out_image = scale_image(out_image, 1)
out_mask = scale_image(out_mask, scale_factor, interpolation=cv2.INTER_NEAREST)
if pad_out_to_modulo is not None and pad_out_to_modulo > 1:
out_image = pad_img_to_modulo(out_image, pad_out_to_modulo)
out_mask = pad_img_to_modulo(out_mask, pad_out_to_modulo)
out_image = torch.from_numpy(out_image).unsqueeze(0).to(device)
out_mask = torch.from_numpy(out_mask).unsqueeze(0).to(device)
out_mask = (out_mask > 0) * 1
return out_image, out_mask
class LamaInpaint:
def __init__(
self,
device,
model_path=None,
) -> None:
if model_path is None:
model_path = os.path.join(cript_directory, "model\\big-lama.pt")
self.model = torch.jit.load(model_path, map_location=device)
self.model.eval()
self.model.to(device)
self.device = device
def run(
self,
image: Union[Image.Image, np.ndarray],
mask: Union[Image.Image, np.ndarray],
):
if isinstance(image, np.ndarray):
orig_height, orig_width = image.shape[:2]
else:
orig_height, orig_width = np.array(image).shape[:2]
# image_width = image.shape[1]
# mask_width = mask.shape[1]
scale = image.width / mask.width
image, mask = prepare_img_and_mask(image, mask, self.device, 8, scale)
with torch.inference_mode():
inpainted = self.model(image, mask)
cur_res = inpainted[0].permute(1, 2, 0).detach().cpu().numpy()
cur_res = np.clip(cur_res * 255, 0, 255).astype("uint8")
cur_res = cur_res[:orig_height, :orig_width]
return cur_res
try:
de = "cpu"
if torch.cuda.is_available():
de = "cuda"
lama = LamaInpaint(de, mode_pa)
image_path = sys.argv[2]
mask_path = sys.argv[3]
output_path = sys.argv[4]
# 若是没有传递mask_path需要自己计算mask区域
# 使用Image.open打开图片
image = Image.open(image_path).convert("RGB")
mask = Image.open(mask_path).convert("L")
res = lama.run(image, mask)
# 将修复后的图片保存到本地
img = Image.fromarray(res)
# 使用 save 方法将图像保存到文件
img.save(output_path)
sys.exit(0)
except Exception as e:
print(e)
sys.exit(str(e))

View File

@ -0,0 +1,43 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['lama_inpaint.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='lama_inpaint',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='lama_inpaint',
)

View File

@ -0,0 +1,37 @@
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['lama_inpaint.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='lama_inpaint',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)

View File

@ -51,27 +51,45 @@ let basicApi = {
/**
* 使用electron的net模块实现的post方法
* @param {*} url 请求的url
* @param {*} data 传输的数据json格式
* @param {*} data 传输的数据,,所有格式
* @param {*} headers 请求头
* @returns
*/
post: (url, data = {}, headers = {}) => {
post: (url, data = {}, headers = {}, others = {}) => {
return new Promise((resolve, reject) => {
const request = net.request({
let req_obj = {
method: 'POST',
url: url,
headers: Object.assign({
'Content-Type': 'application/json',
}, headers)
});
}
req_obj = Object.assign(req_obj, others);
const request = net.request(req_obj);
request.write(JSON.stringify(data));
request.on('response', (response) => {
let responseData = '';
let responseData;
if (response.headers["content-type"] === "application/json") {
responseData = '';
} else if (response.headers["content-type"].startsWith("image/")) {
// 处理图片数据
responseData = []
}
else {
throw new Error("未知的请求返回数据类型");
}
response.on('data', (chunk) => {
responseData += chunk;
if (response.headers['content-type'] === 'application/json') {
// 处理 JSON 数据
responseData += chunk;
} else if (response.headers['content-type'].startsWith('image/')) {
// 处理图片数据
responseData.push(chunk);
}
});
response.on('end', () => {
@ -83,8 +101,11 @@ let basicApi = {
let parsedData;
if (response.headers['content-type'].includes('application/json')) {
parsedData = JSON.parse(responseData);
} else if (response.headers['content-type'].startsWith('image/')) {
// parsedData = responseData;
parsedData = Buffer.concat(responseData);
} else {
parsedData = responseData;
throw new Error("未知的请求返回数据类型");
}
resolve({

View File

@ -0,0 +1,4 @@
import fs from 'node:fs';
const fspromises = fs.promises;

105
src/define/Tools/file.js Normal file
View File

@ -0,0 +1,105 @@
import fs from "fs"
import path from "path";
const fspromises = fs.promises;
/**
* 判断文件或目录是否存在
* @param {*} path 文件或目录的路径
* @returns true表示存在false表示不存在
*/
export async function CheckFileOrDirExist(path) {
try {
await fspromises.access(path);
return true; // 文件或目录存在
} catch (error) {
return false; // 文件或目录不存在
}
}
/** *
* @param {*} path 输入的文件地址
* @returns true false 不是
*/
export async function IsDirectory(path) {
try {
const stat = await fspromises.stat(path);
return stat.isDirectory();
} catch (error) {
throw new Error(`获取文件夹信息失败: ${path}`);
}
}
/**
* 将文件或者是文件夹备份到指定的文职
* @param {*} source_path 源文件/文件夹地址
* @param {*} target_path 目标文件/文件夹地址
*/
export async function BackupFileOrFolder(source_path, target_path) {
try {
// 判断父文件夹是否存在,不存在创建
const parent_path = path.dirname(target_path);
if (!(await CheckFileOrDirExist(parent_path))) {
await fspromises.mkdir(parent_path, { recursive: true });
}
// 判断是不是文件夹
const isDirectory = await IsDirectory(source_path);
if (isDirectory) {
// 复制文件夹
await fspromises.rename(source_path, target_path);
} else {
// 复制文件
await fspromises.copyFile(source, target);
}
} catch (error) {
throw new Error(error);
}
}
/**
* 获取指定的文件夹下面的所有的指定的拓展名的文件
* @param {*} folderPath 文件夹地址
* @param {*} extensions 拓展地址
* @returns 返回文件中指定的后缀文件地址绝对地址
*/
export async function GetFilesWithExtensions(folderPath, extensions) {
try {
// 判断当前是不是文件夹
if (!(await IsDirectory(folderPath))) {
throw new Error("输入的不是有效的文件夹地址")
}
let entries = await fspromises.readdir(folderPath, { withFileTypes: true });
let files = [];
// 使用Promise.all来并行处理所有的stat调用
const fileStats = await Promise.all(entries.map(async (entry) => {
const entryPath = path.join(folderPath, entry.name);
if (entry.isFile()) {
return {
name: entry.name,
path: entryPath,
isFile: true,
};
} else {
return {
isFile: false,
};
}
}));
// 过滤出文件并且满足扩展名要求的文件
files = fileStats.filter(fileStat => fileStat.isFile && extensions.includes(path.extname(fileStat.name).toLowerCase()));
// 对files数组进行排序基于文件名
files.sort((a, b) => a.name.localeCompare(b.name));
// 返回文件名数组(完整的)
return files.map(fileStat => path.join(folderPath, fileStat.name));
} catch (error) {
throw new Error(error);
}
}

72
src/define/Tools/image.js Normal file
View File

@ -0,0 +1,72 @@
import sharp from "sharp";
import { CheckFileOrDirExist } from "./file"
/**
* 将指定的图片的尺寸修改返回修改后的图片数据(base64或buffer)
* @param {*} image_path
* @param {*} width
* @param {*} height
* @param {*} type
* @returns 返回修改后的图片数据(base64或buffer)
*/
export async function ResizeImage(image_path, width, height, type) {
try {
// 检查 type 参数
if (type !== 'base64' && type !== 'buffer') {
throw new Error('type 参数必须是 "base64" 或 "buffer"');
}
// 判断是不是图片文件
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
throw new Error("输入的文件地址不是图片文件地址");
}
// 判断文件是否存在
if (!(await CheckFileOrDirExist(image_path))) {
throw new Error("文件不存在");
}
// 修改图片尺寸
const image = sharp(image_path);
image.resize(width, height);
let data = await image.toBuffer();
if (type === 'base64') {
return data.toString('base64');
} else {
return data;
}
} catch (error) {
throw error;
}
}
/**
* 获取指定图片文件的宽高
* @param {*} image_path 图片文件的路径
* @returns 返回以一个对象包含width和height属性
*/
export async function GetImageSize(image_path) {
try {
// 判断文件是否存在
if (!(await CheckFileOrDirExist(image_path))) {
throw new Error("文件不存在");
}
// 判断是不是图片文件
if (!image_path.match(/\.(jpg|jpeg|png)$/)) {
throw new Error("输入的文件地址不是图片文件地址");
}
// 获取图片的宽高
const metadata = await sharp(image_path).metadata();
return {
width: metadata.width,
height: metadata.height
}
} catch (error) {
throw error;
}
}

View File

@ -0,0 +1,9 @@
import * as image from './image';
import * as common from './common';
import * as file from './file'
export {
image,
common,
file
};

View File

@ -13,6 +13,7 @@ if (!app.isPackaged) {
img_base: path.join(__dirname, "../../resources/config/img_base.json"),
video_config: path.join(__dirname, "../../resources/config/video_config.json"),
scripts_path: path.join(__dirname, "../../resources/scripts"),
logger_path: path.join(__dirname, "../../resources/logger"),
package_path: path.join(__dirname, "../../resources/package"),
image_path: path.join(__dirname, "../../resources/image"),
temp_sd_image: path.join(__dirname, "../../resources/image/TempSDImage"),
@ -43,6 +44,7 @@ if (!app.isPackaged) {
video_config: path.join(__dirname, "../../../resources/config/video_config.json"),
img_base: path.join(__dirname, "../../../resources/config/img_base.json"),
scripts_path: path.join(__dirname, "../../../resources/scripts"),
logger_path: path.join(__dirname, "../../../resources/logger"),
package_path: path.join(__dirname, "../../../resources/package"),
discordScript: path.join(__dirname, "../../../resources/scripts/discordScript.js"),
image_path: path.join(__dirname, "../../../resources/image"),

View File

@ -180,6 +180,13 @@ export const DEFINE_STRING = {
OPEN_DISCORD_WINDOW: "OPEN_DISCORD_WINDOW"
},
IMG: {
ONE_SPLIT_FOUR: "ONE_SPLIT_FOUR"
ONE_SPLIT_FOUR: "ONE_SPLIT_FOUR",
BASE64_TO_FILE: "BASE64_TO_FILE",
PROCESS_IMAGE: "PROCESS_IMAGE",
BATCH_PROCESS_IMAGE: "BATCH_PROCESS_IMAGE",
BATCH_PROCESS_IMAGE_RESULT: "BATCH_PROCESS_IMAGE_RESULT"
},
SYSTEM: {
OPEN_FILE: "OPEN_FILE",
}
}

View File

@ -0,0 +1,3 @@
export const LOGGER_DEFINE = {
REMOVE_WATERMARK: "去除水印",
}

View File

@ -1,6 +1,8 @@
import { ipcMain } from "electron";
import { DEFINE_STRING } from '../../define/define_string'
import { Image } from "../Public/Image";
import { LOGGER_DEFINE } from "../../define/logger_define";
import { errorMessage } from "../generalTools";
let image = new Image(global);
@ -8,6 +10,21 @@ function ImageIpc() {
// 一拆四
ipcMain.handle(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, async (event, value) => await image.OneSplitFour(value));
// 将base64的图片转换为文件
ipcMain.handle(DEFINE_STRING.IMG.BASE64_TO_FILE, async (event, value) => await image.Base64ToFile(value));
// t图片处理去除水印
ipcMain.handle(DEFINE_STRING.IMG.PROCESS_IMAGE, async (event, value) => {
try {
return await image.ProcessImage(value)
} catch (error) {
return errorMessage(error, LOGGER_DEFINE.REMOVE_WATERMARK)
}
});
// 批量处理,去除所有水印
ipcMain.handle(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, async (event, value) => await image.BatchProcessImage(value));
}
export {
ImageIpc

View File

@ -0,0 +1,17 @@
import { ipcMain } from "electron";
import { DEFINE_STRING } from '../../define/define_string'
const { shell } = require('electron')
function SystemIpc() {
// 打开指定的文件
ipcMain.on(DEFINE_STRING.SYSTEM.OPEN_FILE, async (event, value) => {
await shell.openPath(value);
});
}
export {
SystemIpc
}

View File

@ -1,6 +1,16 @@
import { errorMessage, successMessage } from "../generalTools";
import path from "path";
import path, { resolve } from "path";
import { Tools } from "../tools";
import fs from "fs";
import { ImageSetting } from "../../define/setting/imageSetting";
import { isEmpty, reject } from "lodash";
import { basicApi } from "../../api/apiBasic";
import sharp from 'sharp';
import { file, image } from "../../define/Tools";
import { define } from "../../define/define";
import { spawn } from 'child_process'
import { LOGGER_DEFINE } from "../../define/logger_define";
import { DEFINE_STRING } from "../../define/define_string";
export class Image {
constructor(global) {
@ -45,4 +55,281 @@ export class Image {
return errorMessage(error.message);
}
}
/**
* 将base64转换为文件
* @param {*} value
* @returns
*/
async Base64ToFile(value) {
try {
value = JSON.parse(value);
let base64 = value[0];
let out_file_str = value[1];
let base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
let dataBuffer = Buffer.from(base64Data, 'base64');
let out_file = path.join(this.global.config.project_path, out_file_str);
let out_folder = path.dirname(out_file);
await this.tools.checkFolderExistsOrCreate(out_folder);
await fs.promises.writeFile(out_file, dataBuffer);
// await this.tools.writeArrayToFile(dataBuffer, out_file);
return successMessage(out_file, "base64保存到本地图片成功");
} catch (error) {
return errorMessage("base64保存到本地图片失败失败原因如下" + error.message);
}
}
/**
* 图片处理去除水印
* @param {*} value
* @returns
*/
async ProcessImage(value) {
return new Promise(async (resolve, reject) => {
try {
value = JSON.parse(value);
value.out_file = value.out_file ? value.out_file : `data/mask/temp/${new Date().getTime()}.png`;
// 判断当前使用的是什么
let mask_setting = (await ImageSetting.GetDefineConfigJsonByProperty(JSON.stringify(["img_base", "mask_setting", false, {}]))).data;
let isRemote = mask_setting.isRemote ? mask_setting.isRemote : false;
let urls = mask_setting.localUrl ? mask_setting.localUrl + "api/v1/inpaint" : "";
if (isRemote && isEmpty(urls)) {
throw new Error("使用iopaint图片处理但是没有配置图片处理地址");
}
if (!isRemote && isEmpty(value.out_file)) {
throw new Error("水印处理使用软件直接处理类型为file需要指定输出的文件地址");
}
let out_file;
if (!isEmpty(value.out_file)) {
out_file = path.join(this.global.config.project_path, value.out_file);
let out_folder = path.dirname(out_file);
await this.tools.checkFolderExistsOrCreate(out_folder);
}
let res;
if (isRemote) {
let headers = {
"accept": '*/*',
'accept-language': 'zh-CN,zh;q=0.9',
'content-type': 'application/json'
}
let data = {
"image": value.image,
"mask": value.mask,
"ldm_steps": 30,
"ldm_sampler": "ddim",
"zits_wireframe": true,
"cv2_flag": "INPAINT_NS",
"cv2_radius": 5,
"hd_strategy": "Crop",
"hd_strategy_crop_triger_size": 640,
"hd_strategy_crop_margin": 128,
"hd_trategy_resize_imit": 2048 * 5,
"prompt": "",
"negative_prompt": "out of frame, lowres, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, disfigured, gross proportions, malformed limbs, watermark, signature",
"use_croper": false,
"croper_x": 284,
"croper_y": 284,
"croper_height": 512,
"croper_width": 512,
"use_extender": false,
"extender_x": 0,
"extender_y": 0,
"extender_height": 1080,
"extender_width": 1080,
"sd_mask_blur": 12,
"sd_strength": 1,
"sd_steps": 50,
"sd_guidance_scale": 7.5,
"sd_sampler": "DPM++ 2M",
"sd_seed": -1,
"sd_match_histograms": false,
"sd_lcm_lora": false,
"paint_by_example_example_image": null,
"p2p_image_guidance_scale": 1.5,
"enable_controlnet": false,
"controlnet_conditioning_scale": 0.4,
"controlnet_method": "",
"enable_brushnet": false,
"brushnet_method": "random_mask",
"brushnet_conditioning_scale": 1,
"enable_powerpaint_v2": false,
"powerpaint_task": "text-guided"
};
res = await basicApi.post(urls, data, headers);
this.global.logger.info(LOGGER_DEFINE.REMOVE_WATERMARK, `iopaint去除水印请求成功`);
if (value.type == 'arrayBuffer') {
resolve(successMessage(res.data, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK));
} else if (value.type == "file") {
let buffer = Buffer.from(res.data);
await fs.promises.writeFile(out_file, buffer)
resolve(successMessage(out_file, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK));
}
} else {
let lama_script = path.resolve(define.scripts_path, `lama/lama_inpaint.exe`);
// 就是判断指定的文件和文件夹是不是存在
let has_exe = await file.CheckFileOrDirExist(lama_script);
if (!has_exe) {
throw new Error("图片水印处理组件不存在,请看教程自行下载");
}
let has_model = await file.CheckFileOrDirExist(path.resolve(define.scripts_path, 'lama/model/big-lama.pt'))
if (!has_model) {
throw new Error("图片水印处理的模型不存在,请看教程自行下载")
}
this.global.logger.info(LOGGER_DEFINE.REMOVE_WATERMARK, `开始使用lama去除水印开始调用lama程序`);
// 先将对应的base64文件写道本地
let image_path = await this.Base64ToFile(JSON.stringify([value.image, `data/mask/temp/${new Date().getTime()}.png`]))
let mask_path = await this.Base64ToFile(JSON.stringify([value.mask, `data/mask/mask_temp_${new Date().getTime()}.png`]))
if (image_path.code == 0) {
throw new Error(image_path.message)
}
if (mask_path.code == 0) {
throw new Error(mask_path.message)
}
let child = spawn(lama_script, ['-l', image_path.data, mask_path.data, out_file], { encoding: 'utf-8' });
// let child = spawn('python', [lama_script, '-l', image_path.data, mask_path.data, out_file], { encoding: 'utf-8' });
child.on('error', (error) => {
reject(error.toString())
return
})
child.stdout.on('data', (data) => {
console.log(data.toString())
})
child.stderr.on('data', (data) => {
reject(data.toString())
return
})
child.on('close', async (data) => {
if (data != 0) {
this.global.logger.error(LOGGER_DEFINE.REMOVE_WATERMARK, `lama去除水印失败错误码${data.toString()}`);
reject("lama去除水印错误。请看日志详细信息")
return
}
// 判断是不是有输出文件
let has_out = await file.CheckFileOrDirExist(out_file);
if (!has_out) {
reject("lama去除水印失败没有输出文件")
return
}
if (value.type == 'arrayBuffer') {
// 读取导出的文件
let res_data = await fs.promises.readFile(out_file);
resolve(successMessage(res_data, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK));
} else if (value.type == "file") {
resolve(successMessage(out_file, "图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK));
}
})
}
} catch (error) {
reject("图片处理失败,失败原因如下:" + error.message)
}
})
}
// 批量处理所有的图片,去除水印
async BatchProcessImage(value) {
try {
let input_folder = value;
input_folder = path.resolve(this.global.config.project_path, input_folder)
if (!(await this.tools.checkExists(input_folder))) {
throw new Error("输入的文件夹不存在");
}
let new_input_folder = path.join(this.global.config.project_path, `tmp/bak/${path.basename(input_folder)}`);
// 在备份之前判断,旧的文件是不是存在,里面是不是有图片,没有图片,直接删除,在备份过去,存在直接开始下面的请求
let has_files = await file.CheckFileOrDirExist(new_input_folder);
if (has_files) {
let files = await file.GetFilesWithExtensions(new_input_folder, ['.png']);
if (files.length <= 0) {
// 删除指定的文件夹
await fs.promises.rm(new_input_folder, { recursive: true });
await file.BackupFileOrFolder(input_folder, new_input_folder);
// 创建新的input_folder
await fs.promises.mkdir(input_folder, { recursive: true })
}
} else {
await file.BackupFileOrFolder(input_folder, new_input_folder);
// 创建新的input_folder
await fs.promises.mkdir(input_folder, { recursive: true })
}
// 开始备份
// 获取蒙板
let mask_setting = (await ImageSetting.GetDefineConfigJsonByProperty(JSON.stringify(["img_base", "mask_setting", false, {}]))).data;
if (mask_setting.isRemote && isEmpty(mask_setting.localUrl)) {
throw new Error("使用iopaint图片处理但是没有配置图片处理地址");
}
if (isEmpty(mask_setting.mask_path)) {
throw new Error("没有配置蒙板的路径");
}
// 获取文件夹里面所有的图片文件
// let png_files = await this.tools.getFilesWithExtensions(input_folder, '.png');
let png_files = await file.GetFilesWithExtensions(new_input_folder, ['.png']);
if (png_files.length == 0) {
throw new Error("没有找到任何的抽帧图片文件");
}
// 获取图片的总数,将数据返回前端,更新进度条
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT, successMessage({
total: png_files.length,
current: 0
}))
// 默认所有的的宽高都是一样的,获取第一张图片的宽高
let first_image = png_files[0];
let first_image_size = await image.GetImageSize(first_image);
// 重新设置蒙板的宽高,和图片的宽高一样
let mask_base = await image.ResizeImage(mask_setting.mask_path, first_image_size.width, first_image_size.height, 'base64');
mask_base = `data:image/png;base64,${mask_base}`;
// 开始处理所有的图片
for (let i = 0; i < png_files.length; i++) {
const element = png_files[i];
// 获取指定的图片并且转换为base64
let image_base = await fs.promises.readFile(element);
image_base = image_base.toString('base64');
image_base = `data:image/png;base64,${image_base}`;
// 开始处理图片
let res = await this.ProcessImage(JSON.stringify({
image: image_base,
mask: mask_base,
type: "file",
out_file: `tmp/input_crop/${path.basename(element)}`
}))
if (res.code == 0) {
throw new Error(res.message);
}
// 删除之前的
await this.tools.deleteFileOrDirectory(element)
await this.tools.delay(1000)
// 当前图片处理成功,将信息返回前端,更新进度条
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT, successMessage({
total: png_files.length,
current: i + 1
}))
}
return successMessage("所有的图片处理成功", LOGGER_DEFINE.REMOVE_WATERMARK);
} catch (error) {
return errorMessage("图片处理失败,失败原因如下:" + error.message, LOGGER_DEFINE.REMOVE_WATERMARK);
}
}
}

View File

@ -263,7 +263,7 @@ export class ImageGenerate {
let task_list = task_list_json.task_list.filter(item => item.id == element)[0];
let seed = -1;
await fspromises.mkdir(path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder), { recursive: true });
this.global.requestQuene.enqueue(async () => {
await this.global.requestQuene.enqueue(async () => {
let res = await this.sd.OneImageGeneration(images[0], task_list, seed);
let tmp_seed = -1;
if (seed == -1) {
@ -293,7 +293,7 @@ export class ImageGenerate {
let copy_path = path.join(this.global.config.project_path, 'tmp/' + task_list.out_folder, base_name);
await this.tools.copyFileOrDirectory(randomData, copy_path);
} else {
this.global.requestQuene.enqueue(async () => {
await this.global.requestQuene.enqueue(async () => {
await this.sd.OneImageGeneration(item, task_list, tmp_seed);
}, `${task_list.out_folder}_${images[j]}`, batch, task_list.out_folder)
}
@ -339,6 +339,7 @@ export class ImageGenerate {
// 监听总批次完成
this.global.requestQuene.setBatchCompletionCallback(batch, (failedTasks) => {
debugger
if (failedTasks.length > 0) {
let message = `
批次生成任务都已完成
@ -353,6 +354,12 @@ export class ImageGenerate {
message: message
})
} else {
//判断当前batch是不是还有任务没有跑完
let current_batch = this.global.requestQuene.getBatch(batch);
if (current_batch && current_batch.length > 0) {
return;
}
this.global.newWindow[0].win.webContents.send(DEFINE_STRING.SHOW_MESSAGE_DIALOG, {
code: 1,
message: "所有生成任务完成"

View File

@ -640,7 +640,7 @@ async function SaveSDConfig(value) {
sd_config.webui.width = value.width ? value.width : sd_config.webui.width;
sd_config.webui.height = value.height ? value.height : sd_config.webui.height;
sd_config.webui.cfg_scale = value.cfg_scale ? value.cfg_scale : sd_config.webui.cfg_scale;
sd_config.webui.adetailer = value.adetailer ? value.adetailer : sd_config.webui.adetailer;
sd_config.webui.adetailer = value.hasOwnProperty("adetailer") ? value.adetailer : sd_config.webui.adetailer;
await fspromises.writeFile(define.sd_setting, JSON.stringify(sd_config));
return {

View File

@ -66,11 +66,15 @@ function checkStringValueDeletePrefix(value, prefix) {
}
/**
* 返回成功的消息包含codedatamessage
* @param {*} data
* @param {*} message
* @param {*} data 返回的数据
* @param {*} message 成功消息
* @param {*} service 消息日志类型
* @returns
*/
function successMessage(data, message = null) {
function successMessage(data, message = null, service = null) {
if (service && message) {
global.logger.info(service, `成功返回数据`);
}
return {
code: 1,
data: data,
@ -79,11 +83,15 @@ function successMessage(data, message = null) {
}
/**
* 返回失败的消息包含codemessage
* @param {*} message
* 返回失败的消息
* @param {*} message 错误信息
* @param {*} service 错误日志类型
* @returns
*/
function errorMessage(message) {
function errorMessage(message, service = null) {
if (service && message) {
global.logger.error(service, message);
}
return {
code: 0,
message: message

View File

@ -31,7 +31,9 @@ import { DiscordIpc, RemoveDiscordIpc } from './IPCEvent/discordIpc.js'
import { MainIpc } from './IPCEvent/mainIpc.js'
import { GlobalIpc } from "./IPCEvent/globalIpc.js";
import { ImageIpc } from "./IPCEvent/imageIpc.js";
import { SystemIpc } from "./IPCEvent/system.js";
import { system } from "systeminformation";
import { Logger } from "./logger.js";
let tools = new Tools();
let imageGenerate = new ImageGenerate(global);
@ -44,6 +46,7 @@ async function InitData(gl) {
gl.config = res;
gl.requestQuene = new AsyncQueue(gl, res.task_number);
gl.fileQueue = new AsyncQueue(gl, 1);
gl.logger = new Logger(define.logger_path);
return res;
}
@ -194,7 +197,13 @@ app.whenReady().then(async () => {
// 判断动态文件是不是存在
tools.checkJsonFileExistsOrCreate(path.normalize(define.dynamic_setting));
// 判断标签文件是不是存在
tools.checkJsonFileExistsOrCreate(path.normalize(define.tag_setting));
tools.checkJsonFileExistsOrCreate(path.normalize(define.tag_setting), JSON.stringify({
"character_tags": [],
"style_tags": [],
"scene_tags": [],
"prefix_tags": [],
"suffix_tags": []
}));
// 判断SD图片缓存文件是不是存在不存在创建
tools.checkFolderExistsOrCreate(path.normalize(define.temp_sd_image));
tools.checkFolderExistsOrCreate(path.normalize(path.join(define.image_path, "c_s")));
@ -230,6 +239,7 @@ MainIpc(createWindow);
OriginalImageGenerateIpc();
GlobalIpc();
ImageIpc();
SystemIpc();
ipcMain.handle('dark-mode:toggle', (event, value) => {

59
src/main/logger.js Normal file
View File

@ -0,0 +1,59 @@
// const winston = require('winston');
import winston from 'winston';
import path from 'path';
import DailyRotateFile from 'winston-daily-rotate-file';
export class Logger {
constructor(log_folder) {
this.log_folder = log_folder;
this.logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info => `${info.timestamp} [${info.level.toUpperCase()}] [${info.service}] ${info.message}`)
),
transports: [
new DailyRotateFile({
filename: path.resolve(this.log_folder, `LAITool-%DATE%.log`),
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '10m',
maxFiles: '14d'
})
],
});
if (process.env.NODE_ENV !== 'production') {
this.logger.add(new winston.transports.Console({
format: winston.format.simple(),
}));
}
}
/**
* 保存info级别的日志
* @param {*} service service 名称
* @param {*} message 消息
*/
info(service, message) {
this.logger.info(message, { service });
}
/**
* 保存error级别的日志
* @param {*} service service 名称
* @param {*} message 消息
*/
error(service, message) {
this.logger.error(message, { service });
}
/**
* 保存warn级别的日志
* @param {*} service service 名称
* @param {*} message 消息
*/
warn(service, message) {
this.logger.warn(message, { service });
}
}

View File

@ -38,7 +38,7 @@ export class AsyncQueue {
this.batchCompletion[batchId].subBatches[subBatchId].remaining++;
this.tasks.push({ task, taskId, batchId, subBatchId });
if (!this.manualMode) {
this.process();
await this.process();
}
}
@ -79,7 +79,7 @@ export class AsyncQueue {
while (this.tasks.length > 0 && (this.manualMode ? this.taskProgress.length < task_count : this.currentConcurrency < this.concurrencyLimit)) {
const { task, taskId, batchId, subBatchId } = this.tasks.shift();
this.currentConcurrency++;
task().then(() => {
Promise.resolve(task()).then(() => {
this.currentConcurrency--;
this.handleTaskCompletion(batchId, subBatchId, null);
if (!this.manualMode) {
@ -243,6 +243,12 @@ export class AsyncQueue {
}
}
// 获取指定batch的任务列表
getTasks(batchId) {
return this.tasks.filter(item => item.batchId === batchId);
}
// 获取失败的任务
getFailedTasks(batchId, subBatchId = 'default') {
if (this.batchCompletion[batchId] && this.batchCompletion[batchId].subBatches[subBatchId]) {

View File

@ -45,7 +45,7 @@ export class Tools {
* 判断json文件是不是存在不存在的话协议一个空的json文件
* @param {*} filePath 文件地址
*/
async checkJsonFileExistsOrCreate(filePath) {
async checkJsonFileExistsOrCreate(filePath, defaultValue = "{}") {
try {
if (!(await this.checkExists(filePath))) {
// 判断传入的json文件的父文件夹是不是存在不存在的话创建
@ -54,7 +54,7 @@ export class Tools {
// 创建文件夹
await fspromises.mkdir(parentFolder, { recursive: true });
}
await fspromises.writeFile(filePath, "{}");
await fspromises.writeFile(filePath, defaultValue);
}
} catch (error) {
throw new Error(error);
@ -101,7 +101,7 @@ export class Tools {
/**
* 延时多少秒
* @param {等待时间} time
* @param {*} time
* @returns
*/
async delay(time) {
@ -123,8 +123,8 @@ export class Tools {
/**
* 获取指定路径下面的指定的拓展的文件
* @param {指定的文件夹路径} folderPath
* @param {后缀名} extensions
* @param {*} folderPath
* @param {*} extensions
* @returns
*/
async getFilesWithExtensions(folderPath, extensions) {

View File

@ -5,6 +5,15 @@ import { DEFINE_STRING } from "../define/define_string"
const img = {
// 加载当前链接的SD服务数据
OneSplitFour: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.ONE_SPLIT_FOUR, value)),
// 将base64的图片转换为文件
Base64ToFile: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BASE64_TO_FILE, value)),
// 请求图片处理,去除水印
ProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.PROCESS_IMAGE, value)),
//批量处理图片,去除水印
BatchProcessImage: async (value, callback) => callback(await ipcRenderer.invoke(DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE, value)),
}
export {
img

View File

@ -5,6 +5,7 @@ import { discord } from './discord.js';
import { mj } from './mj.js';
import { sd } from './sd.js';
import { img } from './img.js';
import { system } from './system.js';
// Custom APIs for renderer
let events = [];
@ -413,6 +414,7 @@ if (process.contextIsolated) {
contextBridge.exposeInMainWorld('discord', discord)
contextBridge.exposeInMainWorld("sd", sd)
contextBridge.exposeInMainWorld("img", img)
contextBridge.exposeInMainWorld("system", system)
contextBridge.exposeInMainWorld('darkMode', {
toggle: (value) => ipcRenderer.invoke('dark-mode:toggle', value),
})
@ -426,5 +428,6 @@ if (process.contextIsolated) {
window.discord = discord;
window.sd = sd;
window.img = img;
window.system = system;
}

11
src/preload/system.js Normal file
View File

@ -0,0 +1,11 @@
import { ipcRenderer } from "electron"
import { DEFINE_STRING } from "../define/define_string"
const system = {
// 打开指定的文件
OpenFile: async (value, callback) => callback(ipcRenderer.send(DEFINE_STRING.SYSTEM.OPEN_FILE, value)),
}
export {
system
}

View File

@ -1,190 +1,250 @@
<template>
<n-tabs animated default-value="inGetFrame">
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
<n-spin :show="show">
<template #description>
正在抽帧
</template>
<n-space vertical>
<div style="color: red;">
注意该项是使用剪映内的草稿进行分镜
</div>
<n-select v-model:value="selectedValue" filterable placeholder="选择草稿" :options="options" />
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
</n-space>
</n-spin>
<n-divider />
</n-tab-pane>
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
<div style="color: red; font-size: large;">
注意该功能需要对环境要求较高环境需要自行安装第一次执行需要下载模型需要魔法<span class="url_class" @click="OpenTeachDoc">环境安装教程</span>
<br>
分割敏感度值越小分割越精细值越大分割越粗糙
</div>
<n-form style="margin-top: 50px;" ref="formRef" label-placement="left" inline :model="frameValue"
:rules="rules" size="medium">
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px;">
<n-slider v-model:value=frameValue.sensitivity show-tooltip />
</n-form-item>
<n-form-item label="分镜视频地址" path="video_path">
<n-input v-model:value="frameValue.video_path" placeholder="选择或输入要分镜的视频地址" style="width: 300px;" />
<n-button type="primary" @click="GetVideoFile">
选择文件(MP4)
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
开始分镜
</n-button>
</n-form-item>
</n-form>
<n-divider />
</n-tab-pane>
</n-tabs>
<n-code style="padding: 0; font-size: small;" :code="code" language="js" word-wrap />
<n-tabs animated default-value="inGetFrame">
<n-tab-pane size="large" name="draftGetFrame" tab="剪映草稿抽帧">
<n-spin :show="show">
<template #description> 正在抽帧 </template>
<n-space vertical>
<div style="color: red">注意该项是使用剪映内的草稿进行分镜</div>
<n-select
v-model:value="selectedValue"
filterable
placeholder="选择草稿"
:options="options"
/>
<div>
<n-button round type="primary" @click="getFrameFunc">开始抽帧</n-button>
<n-button style="margin-left: 10px" round type="primary" @click="openExportFolder"
>打开抽帧文件夹</n-button
>
</div>
</n-space>
</n-spin>
<n-divider />
</n-tab-pane>
<n-tab-pane name="inGetFrame" tab="软件内抽帧">
<div style="color: red; font-size: large">
注意该功能需要对环境要求较高环境需要自行安装第一次执行需要下载模型需要魔法<span
class="url_class"
@click="OpenTeachDoc"
>环境安装教程</span
>
<br />
分割敏感度值越小分割越精细值越大分割越粗糙
</div>
<n-form
style="margin-top: 50px"
ref="formRef"
label-placement="left"
inline
:model="frameValue"
:rules="rules"
size="medium"
>
<n-form-item label="分割敏感度" path="sensitivity" style="width: 300px">
<n-slider v-model:value="frameValue.sensitivity" show-tooltip />
</n-form-item>
<n-form-item label="分镜视频地址" path="video_path">
<n-input
v-model:value="frameValue.video_path"
placeholder="选择或输入要分镜的视频地址"
style="width: 300px"
/>
<n-button type="primary" @click="GetVideoFile"> 选择文件(MP4) </n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="StartStoryboarding" :loading="storyLoading">
开始分镜
</n-button>
</n-form-item>
<n-form-item>
<n-button type="info" @click="openExportFolder" :loading="storyLoading">
打开抽帧文件夹
</n-button>
</n-form-item>
</n-form>
<n-divider />
</n-tab-pane>
</n-tabs>
<n-code style="padding: 0; font-size: small" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, toRaw } from "vue";
import { NSelect, NButton, useMessage, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string.js"
import { defineComponent, ref, onMounted, toRaw } from 'vue'
import {
NSelect,
NButton,
useMessage,
NSpin,
NDivider,
NCode,
NTabs,
NTabPane,
NSpace,
NForm,
NFormItem,
NSlider,
NInput
} from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string.js'
export default defineComponent({
components: {
NSelect, NButton, NSpin, NDivider, NCode, NTabs, NTabPane, NSpace, NForm, NFormItem, NSlider, NInput
},
setup() {
let options = [];
let out_dir = ref(null);
let selectedValue = ref(null);
let show = ref(false);
let code = ref("");
const message = useMessage();
let storyLoading = ref(false);
components: {
NSelect,
NButton,
NSpin,
NDivider,
NCode,
NTabs,
NTabPane,
NSpace,
NForm,
NFormItem,
NSlider,
NInput
},
setup() {
let options = []
let out_dir = ref(null)
let selectedValue = ref(null)
let show = ref(false)
let code = ref('')
const message = useMessage()
let storyLoading = ref(false)
let frameValue = ref({
sensitivity: 30,
video_path: null
let frameValue = ref({
sensitivity: 30,
video_path: null
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach((element) => {
let obj = {
label: element,
value: element
}
options.push(obj)
})
onMounted(() => {
window.api.getDraftFileList((value) => {
value.forEach(element => {
let obj = {
label: element,
value: element
}
options.push(obj)
});
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message);
code.value = code.value + "\n" + value.data;
return;
}
//
if (value.type == 0) {
storyLoading.value = false;
message.success("分镜抽帧完成");
}
code.value = code.value + "\n" + value.data;
})
})
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error("请选择剪映草稿和输出草稿")
return;
}
show.value = true
//
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error("抽帧失败");
code.value = value.message;
return;
}
message.success("抽帧成功");
code.value = value.data;
show.value = false;
})
})
window.api.setEventListen([DEFINE_STRING.GET_FRAME_RETUN], (value) => {
if (value.code == 0) {
message.error(value.message)
code.value = code.value + '\n' + value.data
return
}
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error("必须选择输出文件夹位置");
return;
}
out_dir.value = value[0]
})
//
if (value.type == 0) {
storyLoading.value = false
message.success('分镜抽帧完成')
}
code.value = code.value + '\n' + value.data
})
})
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
debugger
if (value.code == 0) {
message.error(value.message);
return;
}
frameValue.value.video_path = value.value;
});
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true;
if (frameValue.value.video_path == null) {
message.error("选择分镜的视频地址");
return;
}
debugger
if (toRaw(frameValue.value).video_path.split('.')[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != "MP4") {
message.error("目前只支持MP4格式");
return;
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl("https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink");
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading
async function getFrameFunc(e) {
out_dir = window.config.project_path
if (selectedValue.value == null || selectedValue.value == undefined) {
message.error('请选择剪映草稿和输出草稿')
return
}
show.value = true
//
await window.api.getFrame([selectedValue.value, out_dir], (value) => {
if (value.code == 0) {
message.error('抽帧失败')
code.value = value.message
return
}
message.success('抽帧成功')
code.value = value.data
show.value = false
})
}
});
function selectExportFolder(e) {
window.api.selectFolder(null, (value) => {
if (value.length <= 0) {
message.error('必须选择输出文件夹位置')
return
}
out_dir.value = value[0]
})
}
/**
* 选择指定的视频文件
*/
async function GetVideoFile() {
await window.api.SelectFile(['mp4'], (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
frameValue.value.video_path = value.value
})
}
/**
* 开始分镜执行分镜任务
*/
async function StartStoryboarding() {
storyLoading.value = true
if (frameValue.value.video_path == null) {
message.error('选择分镜的视频地址')
return
}
debugger
if (
toRaw(frameValue.value)
.video_path.split('.')
[toRaw(frameValue.value).video_path.split('.').length - 1].toUpperCase() != 'MP4'
) {
message.error('目前只支持MP4格式')
return
}
await window.api.StartStoryboarding(toRaw(frameValue.value))
}
/**
* 打开环境安装网站
*/
function OpenTeachDoc() {
window.api.OpenUrl(
'https://pvwu1oahp5m.feishu.cn/docx/VrBVd2KUDosmNfxat3OceWuInjd?from=from_copylink'
)
}
function openExportFolder() {
window.api.OpenFolder("input_crop")
}
return {
selectedValue,
options,
out_dir,
getFrameFunc,
selectExportFolder,
show,
code,
frameValue,
GetVideoFile,
StartStoryboarding,
OpenTeachDoc,
storyLoading,
openExportFolder
}
}
})
</script>
<style>
.url_class {
color: blue;
color: blue;
}
.url_class:hover {
color: brown;
cursor: pointer;
color: brown;
cursor: pointer;
}
</style>
</style>

View File

@ -1,99 +1,123 @@
<template>
<n-spin :show="show">
<template #description>
正在反推
</template>
<n-button type="info" @click="PushBackPrompt"> 开始反推 </n-button>
<n-button type="error" style="margin-left: 50px;" @click="DeleteBadPrompt"> 一键去除所有不想要的值 </n-button>
<n-button type="warning" style="margin-left: 50px;" @click="ManagePrompt"> 管理不想要的值 </n-button>
<n-divider />
</n-spin>
<n-code style="padding: 0;" :code="code" language="js" word-wrap />
<n-spin :show="show">
<template #description> 正在反推 </template>
<n-button type="info" @click="RemoveWather"> 去水印 </n-button>
<n-button type="info" style="margin-left: 20px" @click="PushBackPrompt"> 开始反推 </n-button>
<n-button type="error" style="margin-left: 20px" @click="DeleteBadPrompt">
一键去除所有不想要的值
</n-button>
<n-button type="warning" style="margin-left: 20px" @click="ManagePrompt">
管理不想要的值
</n-button>
<n-divider />
</n-spin>
<n-code style="padding: 0" :code="code" language="js" word-wrap />
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw } from "vue";
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from "naive-ui"
import { DEFINE_STRING } from "../../../../define/define_string";
import ManageBadPrompt from "../Components/ManageBadPrompt.vue"
import { defineComponent, ref, onMounted, h, toRaw } from 'vue'
import { NButton, useMessage, useDialog, NDivider, NCode, NSpin } from 'naive-ui'
import { DEFINE_STRING } from '../../../../define/define_string'
import ManageBadPrompt from '../Components/ManageBadPrompt.vue'
import GetWaterMask from '../Watermark/GetWaterMask.vue'
export default defineComponent({
components: {
NButton, NDivider, NCode, NSpin
},
setup() {
let dialog = useDialog();
let code = ref("");
let show = ref(false);
let promprRef = ref();
let message = useMessage();
components: {
NButton,
NDivider,
NCode,
NSpin
},
setup() {
let dialog = useDialog()
let code = ref('')
let show = ref(false)
let promprRef = ref()
let message = useMessage()
//
async function PushBackPrompt() {
show.value = true;
await window.api.PushBackPrompt((value) => {
if (value.code == 0) {
message.error("反推报错");
code.value = value.message;
return;
}
message.success("反推成功");
code.value = value.data;
show.value = false;
})
}
/**
* 管理不想要的值
*/
async function ManagePrompt() {
//
dialog.create({
showIcon: false,
title: "管理不想要的值",
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: "确定",
negativeText: "取消",
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags));
//
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message);
return false;
}
message.success("添加成功");
})
return true;
}
})
}
/**
* 一键删除不想要的值
*/
async function DeleteBadPrompt() {
await window.api.DeleteBadPrompt((value) => {
debugger
console.log(value);
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("去除成功");
})
}
return {
code,
show,
PushBackPrompt,
ManagePrompt,
DeleteBadPrompt
//
async function PushBackPrompt() {
show.value = true
await window.api.PushBackPrompt((value) => {
if (value.code == 0) {
message.error('反推报错')
code.value = value.message
return
}
message.success('反推成功')
code.value = value.data
show.value = false
})
}
/**
* 管理不想要的值
*/
async function ManagePrompt() {
//
dialog.create({
showIcon: false,
title: '管理不想要的值',
content: () => h(ManageBadPrompt, { ref: promprRef }),
style: `width : 650px;`,
maskClosable: false,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
console.log(toRaw(promprRef.value.tags))
//
await window.api.SaveBadPrompt(toRaw(promprRef.value.tags), (value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
return false
}
message.success('添加成功')
})
return true
}
})
}
/**
* 一键删除不想要的值
*/
async function DeleteBadPrompt() {
await window.api.DeleteBadPrompt((value) => {
debugger
console.log(value)
if (value.code == 0) {
message.error(value.message)
return
}
message.success('去除成功')
})
}
async function RemoveWather() {
//
//
let dialogWidth = window.innerWidth * 0.7
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(GetWaterMask, { width: dialogWidth, height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {}
})
}
return {
code,
show,
PushBackPrompt,
ManagePrompt,
DeleteBadPrompt,
RemoveWather
}
}
})
</script>
</script>

View File

@ -0,0 +1,50 @@
<template>
<n-progress type="circle" :percentage="percentage">
<span style="text-align: center; font-size: 20px">{{ text }}</span>
</n-progress>
</template>
<script>
import { ref, h, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { NProgress, useMessage } from 'naive-ui'
export default defineComponent({
components: {
NProgress
},
props: ['tatol', 'current'],
setup(props) {
let text = ref('等待中')
let percentage = ref(0)
let tatol = ref(props.tatol ? props.tatol : 1)
let current = ref(props.current ? props.current : 0)
//
function modifyTotal(value) {
tatol.value = value
if (!value || value == 0) {
text.value = '等待中'
}
}
function modifyCurrent(value) {
current.value = value ? value : 0
percentage.value = Math.round((current.value / (tatol.value ? tatol.value : 1)) * 100)
if (tatol.value && tatol.value > 0) {
text.value = percentage.value + '%'
}
}
onMounted(async () => {})
return {
text,
percentage,
tatol,
current,
modifyTotal,
modifyCurrent
}
}
})
</script>

View File

@ -63,7 +63,7 @@ export default defineComponent({
let data = ref(props.initData)
let AnalyzeCharacter = ref(props.Character)
let tagTreeData = ref(props.treeData)
let auto_save = ref(window.config.auto_save ? window.config.auto_save : false)
let auto_save = ref(window.config?.auto_save ? window.config.auto_save : false)
watch(
() => props.initData,

View File

@ -1,274 +1,318 @@
<template>
<div>
<n-tabs class="card-tabs" default-value="signin" size="large" animated pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;">
<n-tab-pane name="signin" tab="GPT服务商">
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
<n-form-item label="选择自定义的GPT服务商">
<n-select style="width: 300px;" :options="gpt_options"
v-model:value="gpt_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 名称">
<n-input v-model:value="gpt_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT请求网址写全">
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="signup" tab="GPT模型">
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
<n-form-item label="选择自定义的GPT模型">
<n-select style="width: 300px;" :options="gpt_model_options"
v-model:value="gpt_model_select_key"></n-select>
<n-button color="#e18a3b" style="margin-left: 10px;" @click="EditModelOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px;" @click="AddModelOption">添加</n-button>
<n-button type="error" style="margin-left: 10px;" @click="DeleteModelOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 模型名称">
<n-input v-model:value="gpt_model_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT 模型值">
<n-input v-model:value="gpt_model_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right;">
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
</n-tabs>
</div>
<div>
<n-tabs
class="card-tabs"
default-value="signin"
size="large"
animated
pane-wrapper-style="margin: 0 -4px"
pane-style="padding-left: 4px; padding-right: 4px; box-sizing: border-box;"
>
<n-tab-pane name="signin" tab="GPT服务商">
<n-form ref="formRef" :model="gpt_select_value" :rules="rules">
<n-form-item label="选择自定义的GPT服务商">
<n-select
style="width: 300px"
:options="gpt_options"
v-model:value="gpt_select_key"
></n-select>
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditOption">编辑</n-button>
<n-button color="#984f31" style="margin-left: 10px" @click="AddOption">添加</n-button>
<n-button type="error" style="margin-left: 10px" @click="DeleteOption">删除</n-button>
</n-form-item>
<n-form-item path="label" label="GPT 名称">
<n-input v-model:value="gpt_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT请求网址写全">
<n-input v-model:value="gpt_select_value.gpt_url"></n-input>
</n-form-item>
<div style="text-align: right">
<n-button type="primary" @click="SaveGptOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
<n-tab-pane name="signup" tab="GPT模型">
<n-form ref="modelFormRef" :model="gpt_model_select_value" :rules="modelRules">
<n-form-item label="选择自定义的GPT模型">
<n-select
style="width: 300px"
:options="gpt_model_options"
v-model:value="gpt_model_select_key"
></n-select>
<n-button color="#e18a3b" style="margin-left: 10px" @click="EditModelOption"
>编辑</n-button
>
<n-button color="#984f31" style="margin-left: 10px" @click="AddModelOption"
>添加</n-button
>
<n-button type="error" style="margin-left: 10px" @click="DeleteModelOption"
>删除</n-button
>
</n-form-item>
<n-form-item path="label" label="GPT 模型名称">
<n-input v-model:value="gpt_model_select_value.label"></n-input>
</n-form-item>
<n-form-item path="value" label="GPT 模型值">
<n-input v-model:value="gpt_model_select_value.value"></n-input>
</n-form-item>
<div style="text-align: right">
<n-button type="primary" @click="SaveGptModelOption">保存</n-button>
</div>
</n-form>
</n-tab-pane>
</n-tabs>
</div>
</template>
<script>
import { ref, toRaw, onMounted, defineComponent } from "vue"
import { NForm, NFormItem, NInput, NInputNumber, useDialog, NButton, useMessage, NSelect, NTabs, NTabPane } from "naive-ui"
import { cloneDeep } from "lodash";
import { ref, toRaw, onMounted, defineComponent } from 'vue'
import {
NForm,
NFormItem,
NInput,
NInputNumber,
useDialog,
NButton,
useMessage,
NSelect,
NTabs,
NTabPane
} from 'naive-ui'
import { cloneDeep } from 'lodash'
export default defineComponent({
components: {
NForm, NFormItem, NInput, NInputNumber, NButton, NSelect, NTabs, NTabPane
},
setup() {
let message = useMessage();
let gpt_model_options = ref([]);
let gpt_options = ref([]);
let gpt_select_value = ref({});
let gpt_select_key = ref("");
let gpt_model_select_value = ref({});
let gpt_model_select_key = ref("");
let formRef = ref(null);
let modelFormRef = ref(null);
components: {
NForm,
NFormItem,
NInput,
NInputNumber,
NButton,
NSelect,
NTabs,
NTabPane
},
setup() {
let message = useMessage()
let gpt_model_options = ref([])
let gpt_options = ref([])
let gpt_select_value = ref({})
let gpt_select_key = ref('')
let gpt_model_select_value = ref({})
let gpt_model_select_key = ref('')
let formRef = ref(null)
let modelFormRef = ref(null)
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
await window.api.getGptBusinessOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT服务商");
return;
}
gpt_options.value = value.data;
gpt_select_key.value = value.data[0].value;
gpt_select_value.value = {
label: null,
value: null
};
})
await window.api.getGptModelOption("dynamic", (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
if (value.data.length == 0) {
message.error("请先添加自定义GPT模型");
return;
}
gpt_model_options.value = value.data;
gpt_model_select_key.value = value.data[0].value;
gpt_model_select_value.value = {
label: null,
value: null
}
})
/**
* 加载GPT的配置信息
*/
async function InitGptOptions() {
await window.api.getGptBusinessOption('dynamic', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
onMounted(async () => {
await InitGptOptions();
})
/**
* 保存当前的GPT配置信息
*/
async function SaveGptOption(e) {
e.preventDefault();
formRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
//
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_select_value.value)), "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
if (value.data.length == 0) {
message.error('请先添加自定义GPT服务商')
return
}
/**
* 将当前选中的GPT配置信息写入下面已编辑
*/
async function EditOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
gpt_select_value.value = cloneDeep(temp);
gpt_options.value = value.data
gpt_select_key.value = value.data[0].value
gpt_select_value.value = {
label: null,
value: null,
gpt_url: null
}
})
/**
* 将当前选中的GPT模型配置信息写入下面已编辑
*/
async function EditModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
gpt_model_select_value.value = cloneDeep(temp);
await window.api.getGptModelOption('dynamic', (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteOption() {
let temp = gpt_options.value.find(x => x.value == gpt_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
if (value.data.length == 0) {
message.error('请先添加自定义GPT模型')
return
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteModelOption() {
let temp = gpt_model_options.value.find(x => x.value == gpt_model_select_key.value);
let id = temp.id;
await window.api.DeleteDynamicGptOption([id, "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("删除成功");
await InitGptOptions();
})
}
/**
* 保存当前的GPT配置信息
*/
async function SaveGptModelOption(e) {
e.preventDefault();
modelFormRef.value?.validate(async (errors) => {
if (errors) {
message.error("请检查必填字段");
return
}
//
await window.api.SaveDynamicGptOption([JSON.stringify(toRaw(gpt_model_select_value.value)), "gpt_model_options"], async (value) => {
if (value.code == 0) {
message.error(value.message);
return;
}
message.success("添加成功");
await InitGptOptions();
})
});
}
/**
* 添加一个GPT服务商只是清楚当前的数据
*/
async function AddOption() {
gpt_select_value.value = {
label: null,
value: null
};
}
/**
* 添加一个GPT服务商只是清楚当前的数据
*/
async function AddModelOption() {
gpt_model_select_value.value = {
label: null,
value: null
};
}
let ruleObj = (errorMessage) => {
return [{
required: true,
validator(rule, value) {
if (value == null || value == "")
return new Error(errorMessage);
return true;
},
trigger: ["input", "blur"]
}]
}
let rules = {
label: ruleObj("必填GPT名称"),
gpt_url: ruleObj("必填GPT请求网址"),
};
let modelRules = {
label: ruleObj("必填GPT名称"),
value: ruleObj("必填GPT请求网址"),
};
return {
gpt_model_options,
gpt_options,
gpt_select_value,
gpt_model_select_value,
gpt_select_key,
gpt_model_select_key,
SaveGptOption,
EditOption,
DeleteOption,
AddOption,
SaveGptModelOption,
rules,
modelRules,
formRef,
modelFormRef,
EditModelOption,
AddModelOption,
DeleteModelOption
gpt_model_options.value = value.data
gpt_model_select_key.value = value.data[0].value
gpt_model_select_value.value = {
label: null,
value: null
}
})
}
})
onMounted(async () => {
await InitGptOptions()
})
/**
* 保存当前的GPT配置信息
*/
async function SaveGptOption(e) {
e.preventDefault()
formRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
//
await window.api.SaveDynamicGptOption(
[JSON.stringify(toRaw(gpt_select_value.value)), 'gpt_options'],
async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加成功')
await InitGptOptions()
}
)
})
}
/**
* 将当前选中的GPT配置信息写入下面已编辑
*/
async function EditOption() {
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
gpt_select_value.value = cloneDeep(temp)
}
/**
* 将当前选中的GPT模型配置信息写入下面已编辑
*/
async function EditModelOption() {
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
gpt_model_select_value.value = cloneDeep(temp)
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteOption() {
let temp = gpt_options.value.find((x) => x.value == gpt_select_key.value)
let id = temp.id
await window.api.DeleteDynamicGptOption([id, 'gpt_options'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('删除成功')
await InitGptOptions()
})
}
/**
* 删除当前选中的GPT服务商信息
*/
async function DeleteModelOption() {
let temp = gpt_model_options.value.find((x) => x.value == gpt_model_select_key.value)
let id = temp.id
await window.api.DeleteDynamicGptOption([id, 'gpt_model_options'], async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('删除成功')
await InitGptOptions()
})
}
/**
* 保存当前的GPT配置信息
*/
async function SaveGptModelOption(e) {
e.preventDefault()
modelFormRef.value?.validate(async (errors) => {
if (errors) {
message.error('请检查必填字段')
return
}
//
await window.api.SaveDynamicGptOption(
[JSON.stringify(toRaw(gpt_model_select_value.value)), 'gpt_model_options'],
async (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success('添加成功')
await InitGptOptions()
}
)
})
}
/**
* 添加一个GPT服务商只是清楚当前的数据
*/
async function AddOption() {
gpt_select_value.value = {
label: null,
value: null,
gpt_url: null
}
}
/**
* 添加一个GPT服务商只是清楚当前的数据
*/
async function AddModelOption() {
gpt_model_select_value.value = {
label: null,
value: null
}
}
let ruleObj = (errorMessage) => {
return [
{
required: true,
validator(rule, value) {
if (value == null || value == '') return new Error(errorMessage)
return true
},
trigger: ['input', 'blur']
}
]
}
let rules = {
label: ruleObj('必填GPT名称'),
gpt_url: ruleObj('必填GPT请求网址')
}
let modelRules = {
label: ruleObj('必填GPT名称'),
value: ruleObj('必填GPT请求网址')
}
return {
gpt_model_options,
gpt_options,
gpt_select_value,
gpt_model_select_value,
gpt_select_key,
gpt_model_select_key,
SaveGptOption,
EditOption,
DeleteOption,
AddOption,
SaveGptModelOption,
rules,
modelRules,
formRef,
modelFormRef,
EditModelOption,
AddModelOption,
DeleteModelOption
}
}
})
</script>
<style scoped>
.card-tabs .n-tabs-nav--bar-type {
padding-left: 4px;
padding-left: 4px;
}
</style>
</style>

View File

@ -14,7 +14,7 @@
</n-form-item>
<n-form-item label="出图方式(文生图、图生图)" path="type">
<n-select
style="width: 250px"
style="width: 250px"
v-model:value="formValue.type"
:options="modelOption"
placeholder="选择出图方式"

View File

@ -0,0 +1,555 @@
<template>
<div style="margin-top: 30px">
<div style="display: flex; margin-right: 10px">
<ImageFileSelect :modifyImage="modifyImage"></ImageFileSelect>
<div style="width: 250px; margin-left: 10px">
<n-checkbox size="small" v-model:checked="mask_setting.isRemote"> 使用iopaint </n-checkbox>
<!-- <div>
<n-checkbox v-model:checked="mask_setting.isRemote"> 软件内去除 </n-checkbox>
</div> -->
<div style="display: flex; justify-content: flex-end; align-items: flex-end">
<n-input
size="tiny"
style="margin-top: 5px"
v-model:value="mask_setting.localUrl"
placeholder="请输入本地iopaint地址"
></n-input>
<n-button type="success" @click="SaveMaskConfig" size="tiny">保存</n-button>
</div>
<div style="margin-top: 5px">
<span class="url_class" @click="OpenTeach('lama')">lama安装教程</span>
<span class="url_class" @click="OpenTeach('iopaint')" style="margin-left: 5px"
>iopaint安装教程</span
>
</div>
</div>
</div>
<div
style="margin-top: 10px; display: flex; justify-content: center; align-items: center"
id="cnvas_ss"
>
<!-- <canvas ref="imageCanvas" /> -->
<canvas
ref="imageCanvas"
:style="{
cursor: 'grab',
clipPath: `inset(0 ${sliderPos}% 0 0)`,
transition: `clip-path 300ms`
}"
@mousemove="draw"
@mouseup="stopDrawing"
@mousedown="startDrawing"
/>
</div>
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
<n-slider v-model:value="radius" :step="1" style="width: 120px" />
<n-button style="margin-left: 10px" @click="undo" type="success">回撤</n-button>
<n-button style="margin-left: 10px" @click="Check" type="success">查看效果</n-button>
<n-button style="margin-left: 10px" @click="SaveMask" type="success">保存蒙板</n-button>
<n-button style="margin-left: 10px" @click="ViewMaskImage" type="success">查看蒙板</n-button>
<n-popover trigger="hover">
<template #trigger>
<n-button style="margin-left: 10px" @click="BatchMask" type="success"
>开始批量处理</n-button
>
</template>
<span>批量处理的图片的尺寸要和上面的蒙板一致</span>
</n-popover>
</div>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
import {
NButton,
useMessage,
NInput,
NTag,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress,
useDialog,
useDialogReactiveList
} from 'naive-ui'
import ImageFileSelect from './ImageFileSelect.vue'
import ProgressDialog from '../Components/ProgressDialog.vue'
import { DEFINE_STRING } from '../../../../define/define_string'
import { isEmpty } from 'lodash'
export default defineComponent({
components: {
NButton,
NInput,
NTag,
ImageFileSelect,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress
},
props: ['width', 'height'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
const imageCanvas = ref(null)
let imageContext = ref(null)
let height = ref(props.height)
let width = ref(props.width)
let isDrawing = false
let radius = ref(20) //
let points = [] //
let canvasHistory = [] //
let step = 0
let mask_setting = ref({
isRemote: true,
localUrl: null,
mask_path: null
})
let progressDialogRef = ref(null)
let total = ref(0)
let current = ref(0)
let dp
onMounted(async () => {
if (imageCanvas.value) {
const ctx = imageCanvas.value.getContext('2d', { willReadFrequently: true })
if (ctx) {
imageContext.value = ctx
}
}
let div = document.getElementById('cnvas_ss')
div.style.height = height.value - 220 + 'px'
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['img_base', 'mask_setting', false, null]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
//
if (value.data) {
mask_setting.value = Object.assign(mask_setting.value, value.data)
}
}
)
window.api.setEventListen([DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
total.value = value.data.total
current.value = value.data.current
if (progressDialogRef.value == null) {
return
}
progressDialogRef.value?.modifyTotal(total.value)
progressDialogRef.value?.modifyCurrent(current.value)
if (value.data.total == value.data.current) {
//
setTimeout(() => {
if (dp) {
dp.destroy()
}
}, 1000)
}
})
})
let hasImage = false
/**
* 获取图片的高度并且计算适应的宽高
* @param image
*/
function getCurrentWidthHeight(image) {
debugger
let image_width = 512
let image_height = 512
//
if (image) {
image_width = image.width
image_height = image.height
}
// window
// let min_size = Math.min(width.value,height.value);
let image_height_scale = (height.value - 220) / image_height
let image_width_scale = (width.value - 220) / image_width
let scale = Math.min(image_height_scale, image_width_scale)
return [image_width * scale, image_height * scale]
}
/**
* 选择图片初始化
* @param image_d
*/
function modifyImage(image_d) {
let [image_width, image_height] = getCurrentWidthHeight(image_d)
imageContext.value.canvas.width = image_width
imageContext.value.canvas.height = image_height
imageContext.value.drawImage(image_d, 0, 0, image_width, image_height)
canvasHistory = []
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
hasImage = true
}
/**
* 停止画
*/
function stopDrawing() {
debugger
isDrawing = false
canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
console.log(canvasHistory)
step++
}
/**
* 计算点和点直接的所有像素点
* @param x0
* @param y0
* @param x1
* @param y1
*/
function bresenhamLine(x0, y0, x1, y1) {
let dx = Math.abs(x1 - x0)
let dy = Math.abs(y1 - y0)
let sx = x0 < x1 ? 1 : -1
let sy = y0 < y1 ? 1 : -1
let err = dx - dy
let points = []
while (true) {
points.push({ x: x0, y: y0 })
if (x0 === x1 && y0 === y1) break
let e2 = 2 * err
if (e2 > -dy) {
err -= dy
x0 += sx
}
if (e2 < dx) {
err += dx
y0 += sy
}
}
return points
}
/**
* 填充颜色
* @param event
*/
function draw(event) {
requestAnimationFrame(() => {
const rect = imageContext.value.canvas.getBoundingClientRect()
const x = event.clientX - rect.left
const y = event.clientY - rect.top
if (isDrawing && hasImage) {
//
const lastPoint = points[points.length - 1]
// 使 Bresenham's line algorithm
const linePoints = bresenhamLine(lastPoint.x, lastPoint.y, x, y)
for (const point of linePoints) {
//
imageContext.value.beginPath()
imageContext.value.arc(point.x, point.y, radius.value, 0, Math.PI * 2, false)
imageContext.value.fillStyle = 'rgba(255, 255, 0)'
imageContext.value.fill()
}
// points
points.push({ x, y })
}
})
}
/**
* 开始话
* @param event
*/
function startDrawing(event) {
isDrawing = true
const rect = imageContext.value.canvas.getBoundingClientRect()
points.push({ x: event.clientX - rect.left, y: event.clientY - rect.top })
}
/**
* 回撤
*/
async function undo() {
debugger
if (step <= 0) {
//
imageContext.value.clearRect(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
canvasHistory = []
} else {
step--
let canvasPic = new Image()
canvasHistory.pop()
canvasPic.src = canvasHistory[step]
canvasPic.onload = function () {
imageContext.value.clearRect(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
imageContext.value.drawImage(canvasPic, 0, 0)
}
}
}
/**
* 获取当前的蒙板
*/
function getCanvasMaskBase64() {
//
const imageData = imageContext.value.getImageData(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
const newImageData = imageContext.value.createImageData(imageData.width, imageData.height)
for (let i = 0; i < imageData.data.length; i += 4) {
if (
imageData.data[i] === 255 &&
imageData.data[i + 1] === 255 &&
imageData.data[i + 2] === 0
) {
// rgba(255, 255, 0, 255)
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255
newImageData.data[i + 3] = 255
} else {
//
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0
newImageData.data[i + 3] = 255
}
}
// // Canvas
const newCanvas = document.createElement('canvas')
newCanvas.width = imageData.width
newCanvas.height = imageData.height
const newContext = newCanvas.getContext('2d')
// ImageData Canvas
newContext.putImageData(newImageData, 0, 0)
// Canvas base64 URL
const base64 = newCanvas.toDataURL('image/png')
return base64
}
/**
* 查看效果
*/
async function Check() {
//
if (canvasHistory.length <= 1) {
message.error('请先绘制蒙板')
return
}
let base64 = getCanvasMaskBase64()
let baseUrl = mask_setting.value.localUrl
if (mask_setting.value.isRemote) {
}
let data = {
image: canvasHistory[0],
mask: base64,
type: 'arrayBuffer'
}
await window.img.ProcessImage(JSON.stringify(data), (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
debugger
const arrayBuffer = value.data
const blob = new Blob([arrayBuffer], { type: 'image/png' })
const reader = new FileReader()
reader.onloadend = function () {
let img = new Image()
img.src = reader.result
img.onload = function () {
imageContext.value.drawImage(img, 0, 0)
canvasHistory.push(imageCanvas.value.toDataURL())
step++
}
}
reader.readAsDataURL(blob)
})
}
async function SaveMask() {
debugger
//
if (canvasHistory.length <= 1) {
message.error('请先绘制蒙板')
return
}
let base64 = getCanvasMaskBase64()
// base64
await window.img.Base64ToFile(
JSON.stringify([base64, `data/mask/mask_${new Date().getTime()}.png`]),
async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
return
}
mask_setting.value.mask_path = value.data
await SaveMaskConfig()
message.success('蒙板保存成功')
}
)
}
/**
* 保存蒙板配置信息
*/
async function SaveMaskConfig() {
// iopaintlocalUrl
if (mask_setting.value.isRemote && isEmpty(mask_setting.value.localUrl)) {
message.error('请输入iopaint地址')
return
}
await window.api.SaveDefineConfigJsonByProperty(
JSON.stringify(['img_base', 'mask_setting', toRaw(mask_setting.value), false]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
message.success(value.message)
}
)
}
/**
*查看蒙板
*/
async function ViewMaskImage() {
window.system.OpenFile(mask_setting.value.mask_path)
}
/**
* 开始批量处理
*/
async function BatchMask() {
//
if (mask_setting.value.mask_path == null) {
message.error('请先绘制蒙板')
return
}
//
//
dp = dialog.create({
name: 'progressDialog',
showIcon: false,
content: () =>
h(ProgressDialog, {
ref: progressDialogRef,
total: total,
current: current
}),
style: `width : 180px; opacity: 0.8;;`,
maskClosable: false,
closable: false,
onPositiveClick: async () => {}
})
// PNG
await window.img.BatchProcessImage('tmp/input_crop', (value) => {
debugger
//
setTimeout(() => {
if (dp) {
dp.destroy()
}
}, 1000)
if (value.code == 0) {
message.error(value.message)
return
}
window.api.showGlobalMessageDialog(value)
})
}
function OpenTeach(type) {
switch (type) {
case 'lama':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#QOIRdJQvEouJB7xv0Xqcx3zInyb'
)
break
case 'iopaint':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#MLoUdn5cfo6NJTxL0xTcUGyLn43'
)
default:
break
}
}
return {
modifyImage,
imageCanvas,
height,
width,
stopDrawing,
startDrawing,
draw,
undo,
Check,
SaveMask,
mask_setting,
SaveMaskConfig,
ViewMaskImage,
radius,
BatchMask,
progressDialogRef,
total,
current,
OpenTeach
}
}
})
</script>
<style>
.url_class {
color: #e18a3b;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>

View File

@ -0,0 +1,55 @@
<template>
<n-upload :show-file-list="false" directory-dnd :default-upload="false" @change="selectImage">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3">
<archive-icon />
</n-icon>
</div>
<n-text style="font-size: 16px"> 点击或者拖动文件到该区域来上传 </n-text>
</n-upload-dragger>
</n-upload>
</template>
<script>
import { defineComponent, ref, onMounted, h, toRaw, watch } from 'vue'
import { NButton, useMessage, NInput, NTag, NUpload, NUploadDragger } from 'naive-ui'
export default defineComponent({
components: {
NButton,
NInput,
NTag,
NUpload,
NUploadDragger
},
props: ['modifyImage'],
setup(props) {
let message = useMessage()
let image = ref(props.image)
function getBase64(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
const img = new Image()
img.onload = () => resolve(img)
img.onerror = reject
img.src = reader.result
}
reader.onerror = (error) => reject(error)
reader.readAsDataURL(file)
})
}
async function selectImage(value) {
let img = await getBase64(value.file.file)
props.modifyImage(img)
}
return {
selectImage,
image
}
}
})
</script>