♿ optimize and improve image enhancement
This commit is contained in:
7
components.d.ts
vendored
7
components.d.ts
vendored
@@ -62,7 +62,6 @@ declare module 'vue' {
|
||||
CommentList: typeof import('./src/components/CommentReply/src/CommentList/CommentList.vue')['default']
|
||||
CommentReply: typeof import('./src/components/CommentReply/index.vue')['default']
|
||||
CompareImage: typeof import('./src/views/Upscale/CompareImage.vue')['default']
|
||||
CompareResult: typeof import('./src/views/Upscale/CompareResult.vue')['default']
|
||||
Countdown: typeof import('./src/components/MyUI/Countdown/Countdown.vue')['default']
|
||||
DatePicker: typeof import('./src/components/MyUI/DatePicker/DatePicker.vue')['default']
|
||||
Descriptions: typeof import('./src/components/MyUI/Descriptions/Descriptions.vue')['default']
|
||||
@@ -85,6 +84,7 @@ declare module 'vue' {
|
||||
LandingPage: typeof import('./src/views/Landing/LandingPage.vue')['default']
|
||||
List: typeof import('./src/components/MyUI/List/List.vue')['default']
|
||||
LoadingBar: typeof import('./src/components/MyUI/LoadingBar/LoadingBar.vue')['default']
|
||||
LoadingGraphic: typeof import('./src/components/LoadingGraphic/LoadingGraphic.vue')['default']
|
||||
LocationAlbum: typeof import('./src/views/Album/LocationAlbum/LocationAlbum.vue')['default']
|
||||
LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined']
|
||||
LoginFooter: typeof import('./src/views/Login/LoginFooter.vue')['default']
|
||||
@@ -97,11 +97,11 @@ declare module 'vue' {
|
||||
Notification: typeof import('./src/components/MyUI/Notification/Notification.vue')['default']
|
||||
NumberAnimation: typeof import('./src/components/MyUI/NumberAnimation/NumberAnimation.vue')['default']
|
||||
Pagination: typeof import('./src/components/MyUI/Pagination/Pagination.vue')['default']
|
||||
ParameterSetting: typeof import('./src/views/Upscale/ParameterSetting.vue')['default']
|
||||
PeopleAlbum: typeof import('./src/views/Album/PeopleAlbum/PeopleAlbum.vue')['default']
|
||||
Phoalbum: typeof import('./src/views/Album/Phoalbum/Phoalbum.vue')['default']
|
||||
Popconfirm: typeof import('./src/components/MyUI/Popconfirm/Popconfirm.vue')['default']
|
||||
Popover: typeof import('./src/components/MyUI/Popover/Popover.vue')['default']
|
||||
ProcessResult: typeof import('./src/views/Upscale/ProcessResult.vue')['default']
|
||||
Progress: typeof import('./src/components/MyUI/Progress/Progress.vue')['default']
|
||||
QRCode: typeof import('./src/components/MyUI/QRCode/QRCode.vue')['default']
|
||||
QRLogin: typeof import('./src/views/QRLogin/QRLogin.vue')['default']
|
||||
@@ -134,7 +134,6 @@ declare module 'vue' {
|
||||
TabletOutlined: typeof import('@ant-design/icons-vue')['TabletOutlined']
|
||||
Tabs: typeof import('./src/components/MyUI/Tabs/Tabs.vue')['default']
|
||||
Tag: typeof import('./src/components/MyUI/Tag/Tag.vue')['default']
|
||||
TestCompare: typeof import('./src/views/Upscale/TestCompare.vue')['default']
|
||||
Textarea: typeof import('./src/components/MyUI/Textarea/Textarea.vue')['default']
|
||||
TextScroll: typeof import('./src/components/MyUI/TextScroll/TextScroll.vue')['default']
|
||||
ThingAlbum: typeof import('./src/views/Album/ThingAlbum/ThingAlbum.vue')['default']
|
||||
@@ -143,7 +142,7 @@ declare module 'vue' {
|
||||
TreeChart: typeof import('./src/components/MyUI/TreeChart/TreeChart.vue')['default']
|
||||
Upload: typeof import('./src/components/MyUI/Upload/Upload.vue')['default']
|
||||
UploadImage: typeof import('./src/views/Upscale/UploadImage.vue')['default']
|
||||
Upscale: typeof import('./src/views/Upscale/Upscale.vue')['default']
|
||||
Upscale: typeof import('./src/views/Upscale/index.vue')['default']
|
||||
UserInfoCard: typeof import('./src/components/CommentReply/src/UserInfoCard/UserInfoCard.vue')['default']
|
||||
UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined']
|
||||
Video: typeof import('./src/components/MyUI/Video/Video.vue')['default']
|
||||
|
12
package.json
12
package.json
@@ -33,7 +33,7 @@
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"echarts": "^5.5.1",
|
||||
"eslint": "9.16.0",
|
||||
"eslint": "9.17.0",
|
||||
"go-captcha-vue": "^2.0.5",
|
||||
"gsap": "^3.12.5",
|
||||
"jsencrypt": "^3.3.2",
|
||||
@@ -43,7 +43,7 @@
|
||||
"nprogress": "^0.2.0",
|
||||
"nsfwjs": "^4.2.1",
|
||||
"pinia": "^2.3.0",
|
||||
"pinia-plugin-persistedstate-2": "^2.0.27",
|
||||
"pinia-plugin-persistedstate-2": "^2.0.28",
|
||||
"qrcode": "^1",
|
||||
"seedrandom": "^3.0.5",
|
||||
"swiper": "^11.1.15",
|
||||
@@ -58,17 +58,17 @@
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.16.0",
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"globals": "^15.13.0",
|
||||
"sass": "^1.83.0",
|
||||
"typescript": "^5.7.2",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.18.0",
|
||||
"unplugin-vue-components": "^0.27.5",
|
||||
"unplugin-vue-components": "^0.28.0",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-bundle-obfuscator": "1.4.0",
|
||||
"vite-plugin-chunk-split": "^0.5.0",
|
||||
"vue-tsc": "https://pkg.pr.new/vuejs/language-tools/vue-tsc@3fb59af"
|
||||
"vue-tsc": "2.1.10"
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1,11 @@
|
||||
<svg t="1733761095875" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="50680" width="200" height="200"><path d="M471.87259 871.430008a41.629505 41.629505 0 0 0 41.629506 41.629506 40.98575 40.98575 0 0 0 38.625314-41.629506 40.55658 40.55658 0 0 0-39.912825-40.98575h-1.072925a40.98575 40.98575 0 0 0-39.26907 40.98575z" fill="#B8BDCC" p-id="50681"></path><path d="M738.601844 55.792121a77.036044 77.036044 0 0 1 77.036044 77.036043v758.343672a77.036044 77.036044 0 0 1-77.036044 77.036043H285.398156a77.036044 77.036044 0 0 1-77.036044-77.036043V132.828164A77.036044 77.036044 0 0 1 285.398156 55.792121h453.203688m0-55.792121H285.398156A132.828164 132.828164 0 0 0 152.569992 132.828164v758.343672a132.828164 132.828164 0 0 0 132.828164 132.828164h453.203688a132.828164 132.828164 0 0 0 132.828164-132.828164V132.828164A132.828164 132.828164 0 0 0 738.601844 0z" fill="#B8BDCC" p-id="50682"></path><path d="M713.49539 793.964795H310.50461a39.483655 39.483655 0 0 1-41.629505-36.694049v-519.295893a39.26907 39.26907 0 0 1 41.629505-36.479463h402.99078a39.26907 39.26907 0 0 1 41.629505 36.479463v518.437553A39.483655 39.483655 0 0 1 713.49539 793.964795z" fill="#E3E5EB" p-id="50683"></path><path d="M425.736798 103.430008m25.964795 0l120.596814 0q25.964795 0 25.964795 25.964795l0 0q0 25.964795-25.964795 25.964795l-120.596814 0q-25.964795 0-25.964795-25.964795l0 0q0-25.964795 25.964795-25.964795Z" fill="#B8BDCC" p-id="50684"></path></svg>
|
||||
<svg t="1733761095875" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
p-id="50680" width="200" height="200">
|
||||
<path d="M471.87259 871.430008a41.629505 41.629505 0 0 0 41.629506 41.629506 40.98575 40.98575 0 0 0 38.625314-41.629506 40.55658 40.55658 0 0 0-39.912825-40.98575h-1.072925a40.98575 40.98575 0 0 0-39.26907 40.98575z"
|
||||
fill="#B8BDCC" p-id="50681"></path>
|
||||
<path d="M738.601844 55.792121a77.036044 77.036044 0 0 1 77.036044 77.036043v758.343672a77.036044 77.036044 0 0 1-77.036044 77.036043H285.398156a77.036044 77.036044 0 0 1-77.036044-77.036043V132.828164A77.036044 77.036044 0 0 1 285.398156 55.792121h453.203688m0-55.792121H285.398156A132.828164 132.828164 0 0 0 152.569992 132.828164v758.343672a132.828164 132.828164 0 0 0 132.828164 132.828164h453.203688a132.828164 132.828164 0 0 0 132.828164-132.828164V132.828164A132.828164 132.828164 0 0 0 738.601844 0z"
|
||||
fill="#B8BDCC" p-id="50682"></path>
|
||||
<path d="M713.49539 793.964795H310.50461a39.483655 39.483655 0 0 1-41.629505-36.694049v-519.295893a39.26907 39.26907 0 0 1 41.629505-36.479463h402.99078a39.26907 39.26907 0 0 1 41.629505 36.479463v518.437553A39.483655 39.483655 0 0 1 713.49539 793.964795z"
|
||||
fill="#E3E5EB" p-id="50683"></path>
|
||||
<path d="M425.736798 103.430008m25.964795 0l120.596814 0q25.964795 0 25.964795 25.964795l0 0q0 25.964795-25.964795 25.964795l-120.596814 0q-25.964795 0-25.964795-25.964795l0 0q0-25.964795 25.964795-25.964795Z"
|
||||
fill="#B8BDCC" p-id="50684"></path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
@@ -1 +1 @@
|
||||
<svg t="1733748093314" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3982" width="200" height="200"><path d="M170.666667 0h682.666666c93.866667 0 170.666667 76.8 170.666667 170.666667v682.666666c0 93.866667-76.8 170.666667-170.666667 170.666667H170.666667c-93.866667 0-170.666667-76.8-170.666667-170.666667V170.666667c0-93.866667 76.8-170.666667 170.666667-170.666667z" fill="#32B84C" opacity=".2" p-id="3983"></path><path d="M200.533333 72.533333h622.933334c72.533333 0 128 55.466667 128 128v622.933334c0 72.533333-55.466667 128-128 128H200.533333c-72.533333 0-128-55.466667-128-128V200.533333c0-68.266667 59.733333-128 128-128z" fill="#32B84C" opacity=".3" p-id="3984"></path><path d="M230.4 145.066667h558.933333c46.933333 0 85.333333 38.4 85.333334 85.333333v558.933333c0 46.933333-38.4 85.333333-85.333334 85.333334H230.4c-46.933333 0-85.333333-38.4-85.333333-85.333334V230.4c0-46.933333 38.4-85.333333 85.333333-85.333333z" fill="#32B84C" opacity=".38" p-id="3985"></path><path d="M669.866667 537.6l-234.666667 149.333333c-29.866667 21.333333-68.266667-8.533333-68.266667-42.666666V341.333333c0-34.133333 38.4-64 68.266667-42.666666l234.666667 149.333333c34.133333 21.333333 34.133333 68.266667 0 89.6" fill="#26AE40" p-id="3986"></path></svg>
|
||||
<svg t="1734340504075" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="21926" width="200" height="200"><path d="M512 1024a512 512 0 1 1 512-512 512 512 0 0 1-512 512z m192-577.194667l-225.066667-178.730666a58.154667 58.154667 0 0 0-61.653333-7.296 55.125333 55.125333 0 0 0-33.194667 51.2v357.461333a55.125333 55.125333 0 0 0 33.194667 51.2 58.112 58.112 0 0 0 61.653333-7.296L704 534.613333a55.466667 55.466667 0 0 0 0-87.808z" fill="#00CAA0" p-id="21927"></path></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 515 B |
1
src/assets/svgs/success.svg
Normal file
1
src/assets/svgs/success.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1734340833280" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="27444" width="200" height="200"><path d="M0 0h1024v1024H0V0z" fill="#202425" opacity=".01" p-id="27445"></path><path d="M955.733333 512c0 245.077333-198.656 443.733333-443.733333 443.733333S68.266667 757.077333 68.266667 512 266.922667 68.266667 512 68.266667s443.733333 198.656 443.733333 443.733333z" fill="#11AA66" p-id="27446"></path><path d="M512 102.4C285.7984 102.4 102.4 285.7984 102.4 512s183.3984 409.6 409.6 409.6 409.6-183.3984 409.6-409.6S738.2016 102.4 512 102.4zM34.133333 512C34.133333 248.081067 248.081067 34.133333 512 34.133333s477.866667 213.947733 477.866667 477.866667-213.947733 477.866667-477.866667 477.866667S34.133333 775.918933 34.133333 512z" fill="#11AA66" p-id="27447"></path><path d="M787.114667 339.285333a51.2 51.2 0 0 1 0 72.362667l-307.2 307.2a51.2 51.2 0 0 1-72.362667 0l-170.666667-170.666667a51.2 51.2 0 0 1 72.362667-72.362666L443.733333 610.235733l271.018667-271.018666a51.2 51.2 0 0 1 72.362667 0z" fill="#FFFFFF" p-id="27448"></path></svg>
|
After Width: | Height: | Size: 1.1 KiB |
1
src/assets/svgs/warn.svg
Normal file
1
src/assets/svgs/warn.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg t="1734341153983" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="33694" width="200" height="200"><path d="M512 58.181818C260.654545 58.181818 58.181818 260.654545 58.181818 512S260.654545 965.818182 512 965.818182 965.818182 763.345455 965.818182 512 763.345455 58.181818 512 58.181818zM546.909091 768c0 18.618182-16.290909 34.909091-34.909091 34.909091s-34.909091-16.290909-34.909091-34.909091V395.636364c0-18.618182 16.290909-34.909091 34.909091-34.909091s34.909091 16.290909 34.909091 34.909091v372.363636zM512 325.818182c-25.6 0-46.545455-20.945455-46.545455-46.545455s20.945455-46.545455 46.545455-46.545454 46.545455 20.945455 46.545455 46.545454-20.945455 46.545455-46.545455 46.545455z" fill="#A8ABB0" p-id="33695"></path></svg>
|
After Width: | Height: | Size: 787 B |
145
src/components/LoadingGraphic/LoadingGraphic.vue
Normal file
145
src/components/LoadingGraphic/LoadingGraphic.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul class="loading-animation" hidden="" style="display: none;">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
|
||||
<ul class="loading-animation alternate" style="display: block;">
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
<li></li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
.loading-animation {
|
||||
list-style: none;
|
||||
position: relative;
|
||||
height: 1px;
|
||||
width: 200px;
|
||||
border-bottom: 1px dashed #aaa;
|
||||
}
|
||||
|
||||
.loading-animation li {
|
||||
margin-top: -3px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
border-radius: 10px;
|
||||
background-color: #000000;
|
||||
opacity: 0;
|
||||
box-shadow: 0 0 4px rgba(126, 126, 135, 0.99), 0 0 8px rgba(126, 126, 135, 0.99), 0 0 12px rgba(126, 126, 135, 0.99), 0 0 18px rgba(126, 126, 135, 0.99);
|
||||
position: absolute;
|
||||
left: -10%;
|
||||
animation-name: loading;
|
||||
animation-duration: 2s;
|
||||
animation-timing-function: ease-in-out;
|
||||
animation-iteration-count: infinite;
|
||||
animation-direction: normal;
|
||||
}
|
||||
|
||||
/* alternate animation */
|
||||
.loading-animation.alternate li {
|
||||
animation-name: loading2;
|
||||
}
|
||||
|
||||
.loading-animation li:nth-child(1) {
|
||||
animation-delay: 250ms;
|
||||
}
|
||||
|
||||
.loading-animation li:nth-child(2) {
|
||||
animation-delay: 500ms;
|
||||
}
|
||||
|
||||
.loading-animation li:nth-child(3) {
|
||||
animation-delay: 750ms;
|
||||
}
|
||||
|
||||
.loading-animation li:nth-child(4) {
|
||||
animation-delay: 1s;
|
||||
}
|
||||
|
||||
.loading-animation li:nth-child(5) {
|
||||
animation-delay: 1.25s;
|
||||
}
|
||||
|
||||
/* fading only */
|
||||
@keyframes loading {
|
||||
0% {
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
20%, 60% {
|
||||
left: 50%;
|
||||
opacity: 1;
|
||||
}
|
||||
90% {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* shrinking and fading */
|
||||
@keyframes loading2 {
|
||||
0% {
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin-top: -2px;
|
||||
}
|
||||
20%, 60% {
|
||||
left: 50%;
|
||||
opacity: 1;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
margin-top: -3px;
|
||||
}
|
||||
90% {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
margin-top: 0;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* all of this is extra for the demo */
|
||||
.loading-animation {
|
||||
margin: 100px auto 0;
|
||||
}
|
||||
|
||||
.loading-animation:before, .loading-animation:after {
|
||||
color: grey;
|
||||
font-family: "Merienda One",serif;
|
||||
}
|
||||
|
||||
.loading-animation:before {
|
||||
//content: "Loading";
|
||||
position: absolute;
|
||||
top: -32px;
|
||||
left: 39%;
|
||||
}
|
||||
|
||||
.loading-animation:after {
|
||||
//content: "Please wait";
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 36%;
|
||||
}
|
||||
</style>
|
@@ -599,7 +599,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
.icon-info {
|
||||
color: @themeColor;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.icon-success {
|
||||
|
@@ -19,7 +19,7 @@ export default [
|
||||
{
|
||||
path: '/main/photo/upscale',
|
||||
name: 'upscale',
|
||||
component: () => import('@/views/Upscale/Upscale.vue'),
|
||||
component: () => import('@/views/Upscale/index.vue'),
|
||||
meta: {
|
||||
requiresAuth: false,
|
||||
title: '图像修复'
|
||||
|
@@ -5,131 +5,120 @@ import i18n from "@/locales";
|
||||
import {NSFWJS} from "nsfwjs";
|
||||
import localForage from "localforage";
|
||||
import {message} from "ant-design-vue";
|
||||
import Module from "@/workers/imghelper.ts";
|
||||
import Img from "@/workers/image.ts";
|
||||
import Module from "@/workers/imghelper.ts";
|
||||
|
||||
|
||||
export const useUpscaleStore = defineStore(
|
||||
'upscale',
|
||||
() => {
|
||||
const image: HTMLImageElement = document.createElement('img');
|
||||
const imageList = ref<string[]>([]);
|
||||
const fileList = ref<string[]>([]);
|
||||
const imageData = ref<string>();
|
||||
const fileData = ref<string>();
|
||||
const uploading = ref<boolean>(false);
|
||||
|
||||
// 加载图片数据
|
||||
const img = ref<HTMLImageElement>(new Image());
|
||||
const wasmModule = ref<any>();
|
||||
const hasAlpha = ref(false);
|
||||
const input = ref<Img | null>(null);
|
||||
const inputAlpha = ref<Img | null>(null);
|
||||
|
||||
const wasmModule = ref<any>(null);
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d', {willReadFrequently: true});
|
||||
|
||||
const imgLoaded = ref<boolean>(false);
|
||||
|
||||
// 处理后的图片
|
||||
const processedImg = ref<HTMLImageElement>(new Image());
|
||||
const processedImg = ref<string>('');
|
||||
const isDone = ref<boolean>(false);
|
||||
const isProcessing = ref<boolean>(false);
|
||||
const msg = ref<string>("");
|
||||
const progressBar = ref<number>(0);
|
||||
|
||||
/**
|
||||
* 图片上传前的校验
|
||||
* @param file
|
||||
*/
|
||||
async function beforeUpload(file: any) {
|
||||
if (fileList.value.length >= 5) {
|
||||
async function beforeUpload(file: File) {
|
||||
uploading.value = true;
|
||||
const urlData = URL.createObjectURL(file);
|
||||
image.src = urlData;
|
||||
// 图片 NSFW 检测
|
||||
const nsfw: NSFWJS = await initNSFWJs();
|
||||
const isNSFW: boolean = await predictNSFW(nsfw, image);
|
||||
if (isNSFW) {
|
||||
message.error(i18n.global.t('comment.illegalImage'));
|
||||
fileData.value = '';
|
||||
uploading.value = false;
|
||||
return false;
|
||||
}
|
||||
uploading.value = true;
|
||||
if (!window.FileReader) return false;
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file); // 文件转换
|
||||
reader.onload = async function () {
|
||||
image.src = reader.result as string;
|
||||
// 图片 NSFW 检测
|
||||
const nsfw: NSFWJS = await initNSFWJs();
|
||||
const isNSFW: boolean = await predictNSFW(nsfw, image);
|
||||
if (isNSFW) {
|
||||
message.error(i18n.global.t('comment.illegalImage'));
|
||||
fileList.value.pop();
|
||||
uploading.value = false;
|
||||
return false;
|
||||
}
|
||||
fileList.value.push(image.src);
|
||||
// 加载图片
|
||||
await loadImg(image.src);
|
||||
uploading.value = false;
|
||||
return true;
|
||||
};
|
||||
fileData.value = urlData;
|
||||
await loadImg(image);
|
||||
uploading.value = false;
|
||||
|
||||
imageData.value = "";
|
||||
processedImg.value = "";
|
||||
isDone.value = false;
|
||||
msg.value = "";
|
||||
progressBar.value = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义上传图片请求
|
||||
*/
|
||||
async function customUploadRequest() {
|
||||
imageList.value = fileList.value;
|
||||
}
|
||||
async function customUploadRequest(_file: any) {
|
||||
|
||||
/**
|
||||
* 移除图片
|
||||
* @param index
|
||||
*/
|
||||
async function removeImage(index: number) {
|
||||
fileList.value.splice(index, 1);
|
||||
imageList.value.splice(index, 1);
|
||||
imageData.value = fileData.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载图片
|
||||
* @param src
|
||||
* @param img
|
||||
*/
|
||||
async function loadImg(src: string) {
|
||||
img.value.src = src;
|
||||
img.value.onload = async () => {
|
||||
wasmModule.value = await Module();
|
||||
if (ctx) {
|
||||
canvas.width = img.value.width;
|
||||
canvas.height = img.value.height;
|
||||
ctx.drawImage(img.value, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.value.width, img.value.height);
|
||||
const data = new Uint8Array(imageData.data.buffer);
|
||||
input.value = new Img(img.value.width, img.value.height, data);
|
||||
const numPixels = input.value.width * input.value.height;
|
||||
const bytesPerImage = numPixels * 4;
|
||||
const sourcePtr = wasmModule.value._malloc(bytesPerImage);
|
||||
const targetPtr = wasmModule.value._malloc(bytesPerImage);
|
||||
wasmModule.value.HEAPU8.set(input.value.data, sourcePtr);
|
||||
hasAlpha.value = wasmModule.value._check_alpha(sourcePtr, numPixels);
|
||||
if (hasAlpha.value) {
|
||||
inputAlpha.value = new Img(img.value.width, img.value.height);
|
||||
wasmModule.value._copy_alpha_to_rgb(sourcePtr, targetPtr, numPixels);
|
||||
inputAlpha.value.data.set(
|
||||
wasmModule.value.HEAPU8.subarray(targetPtr, targetPtr + bytesPerImage)
|
||||
);
|
||||
}
|
||||
wasmModule.value._free(sourcePtr);
|
||||
wasmModule.value._free(targetPtr);
|
||||
imgLoaded.value = true;
|
||||
async function loadImg(img: HTMLImageElement) {
|
||||
wasmModule.value = await Module();
|
||||
if (ctx && wasmModule.value) {
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, img.width, img.height);
|
||||
const data = new Uint8Array(imageData.data.buffer);
|
||||
input.value = new Img(img.width, img.height, data);
|
||||
const numPixels = input.value.width * input.value.height;
|
||||
const bytesPerImage = numPixels * 4;
|
||||
const sourcePtr = wasmModule.value._malloc(bytesPerImage);
|
||||
const targetPtr = wasmModule.value._malloc(bytesPerImage);
|
||||
wasmModule.value.HEAPU8.set(input.value.data, sourcePtr);
|
||||
hasAlpha.value = wasmModule.value._check_alpha(sourcePtr, numPixels);
|
||||
if (hasAlpha.value) {
|
||||
inputAlpha.value = new Img(img.width, img.height);
|
||||
wasmModule.value._copy_alpha_to_rgb(sourcePtr, targetPtr, numPixels);
|
||||
inputAlpha.value.data.set(
|
||||
wasmModule.value.HEAPU8.subarray(targetPtr, targetPtr + bytesPerImage)
|
||||
);
|
||||
}
|
||||
};
|
||||
wasmModule.value._free(sourcePtr);
|
||||
wasmModule.value._free(targetPtr);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
imageList,
|
||||
fileList,
|
||||
uploading,
|
||||
imageData,
|
||||
input,
|
||||
hasAlpha,
|
||||
inputAlpha,
|
||||
wasmModule,
|
||||
img,
|
||||
processedImg,
|
||||
imgLoaded,
|
||||
wasmModule,
|
||||
isDone,
|
||||
isProcessing,
|
||||
msg,
|
||||
progressBar,
|
||||
beforeUpload,
|
||||
customUploadRequest,
|
||||
removeImage,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -137,15 +126,10 @@ export const useUpscaleStore = defineStore(
|
||||
{
|
||||
// 开启数据持久化
|
||||
persistedState: {
|
||||
persist: true,
|
||||
persist: false,
|
||||
storage: localForage,
|
||||
key: 'upscale',
|
||||
includePaths: [
|
||||
'imageList',
|
||||
'fileList',
|
||||
'img',
|
||||
'processedImg',
|
||||
]
|
||||
includePaths: [],
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@@ -1,6 +1,5 @@
|
||||
<template>
|
||||
|
||||
<CompareImage/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
|
@@ -1,11 +1,9 @@
|
||||
<template>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
|
||||
</style>
|
||||
|
@@ -1,11 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
<style scoped lang="scss" src="./index.scss">
|
||||
|
||||
</style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
ref="canvasContainer"
|
||||
class="canvas-container drag-over bg dark"
|
||||
class="canvas-container bg drag-over dark"
|
||||
@mousedown="startDragging"
|
||||
@mouseup="stopDragging"
|
||||
@mouseleave="stopDragging"
|
||||
@@ -11,9 +11,23 @@
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
>
|
||||
<canvas ref="canvas"></canvas>
|
||||
<div v-if="store.isProcessing" class="canvas-progressbar">
|
||||
<span class="canvas-progressbar-text">
|
||||
{{ store.msg }}
|
||||
</span>
|
||||
<AProgress
|
||||
:stroke-color="{
|
||||
'0%': '#108ee9',
|
||||
'100%': '#87d068',
|
||||
}"
|
||||
:percent="store.progressBar"
|
||||
:showInfo="false"
|
||||
/>
|
||||
</div>
|
||||
<canvas ref="canvas" v-if="store.imageData || store.processedImg"></canvas>
|
||||
<div
|
||||
class="dragLine"
|
||||
v-if="store.isDone"
|
||||
ref="dragLine">
|
||||
<div class="dragBall"
|
||||
@mousedown.stop="startDraggingLine"
|
||||
@@ -26,11 +40,20 @@
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="canvas-qr" v-if="!store.isDone && !store.imageData">
|
||||
<AQrcode :bordered="false" color="rgba(126, 126, 135, 0.48)" :size="200"
|
||||
:value="'https://git.landaiqing.cn'"
|
||||
:icon="phone"
|
||||
:iconSize="40"
|
||||
/>
|
||||
<span class="canvas-qr-text">手机扫码上传</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import img1 from "@/assets/images/1.png";
|
||||
import img2 from "@/assets/images/2.png";
|
||||
import useStore from "@/store";
|
||||
import phone from '@/assets/svgs/qr-phone.svg';
|
||||
|
||||
const canvasContainer = ref<HTMLDivElement | null>(null);
|
||||
const dragging = ref<boolean>(false);
|
||||
@@ -52,6 +75,7 @@ const touchStartY = ref(0);
|
||||
const touchStartDistance = ref(0);
|
||||
const imgScaleStart = ref(1);
|
||||
|
||||
const store = useStore().upscale;
|
||||
const img = ref<HTMLImageElement>(new Image());
|
||||
const processedImg = ref<HTMLImageElement>(new Image());
|
||||
|
||||
@@ -395,30 +419,48 @@ function initCanvasSize() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
img.value.src = img1;
|
||||
processedImg.value.src = img2;
|
||||
initCanvasSize();
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
});
|
||||
|
||||
watch(
|
||||
() => store.imageData,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
img.value.src = newValue;
|
||||
img.value.onload = () => {
|
||||
initCanvasSize();
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => store.processedImg,
|
||||
(newValue) => {
|
||||
if (newValue) {
|
||||
processedImg.value.src = newValue;
|
||||
processedImg.value.onload = () => {
|
||||
initCanvasSize();
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
store.isDone = false;
|
||||
store.imageData = "";
|
||||
store.processedImg = "";
|
||||
store.isProcessing = false;
|
||||
store.msg = "";
|
||||
store.progressBar = 0;
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
img.value.onload = () => {
|
||||
drawImage();
|
||||
};
|
||||
processedImg.value.onload = () => {
|
||||
drawImage();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.canvas-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
@@ -496,6 +538,18 @@ canvas {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.canvas-qr {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.canvas-qr-text {
|
||||
font-size: 16px;
|
||||
color: rgba(126, 126, 135, 0.48);
|
||||
}
|
||||
|
||||
.dragLine {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -508,6 +562,7 @@ canvas {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.dragLine:hover {
|
||||
@@ -527,6 +582,28 @@ canvas {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: ew-resize;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.canvas-progressbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
//left: 50%;
|
||||
//transform: translate(-50%, -50%);
|
||||
width: 300px;
|
||||
//height: 100px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
//background-color: rgba(255, 255, 255, 0.5);
|
||||
//padding: 10px;
|
||||
}
|
||||
|
||||
.canvas-progressbar-text {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
|
298
src/views/Upscale/ParameterSetting.vue
Normal file
298
src/views/Upscale/ParameterSetting.vue
Normal file
@@ -0,0 +1,298 @@
|
||||
<template>
|
||||
<div class="upscale-params">
|
||||
<div class="upscale-params-item">
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">类型:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="model_type"
|
||||
:options="TypeData.map(item => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">模型:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="model"
|
||||
:options="modes.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upscale-params-item">
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">比列:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="factor"
|
||||
:options="scales.map((item: any) => ({label: item, value: item}))">
|
||||
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">分块大小:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="tile_size"
|
||||
:options="tileSize.map((item: any) => ({label: item, value: item}))">
|
||||
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upscale-params-item">
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">重复:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="min_lap"
|
||||
:options="overlapList.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-params-item-content">
|
||||
<span class="upscale-params-title">运行环境:</span>
|
||||
<ASelect style="width: 100%" size="large"
|
||||
v-model:value="backend"
|
||||
:options="backendList.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider></ADivider>
|
||||
<AButton style="width: 100%;" size="large" shape="default" type="default" :loading="upscale.isProcessing"
|
||||
@click="startTask">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="25" :src="run"/>
|
||||
</template>
|
||||
<span class="upscale-params-btn">开始</span>
|
||||
</AButton>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import {message} from "ant-design-vue";
|
||||
import Img from "@/workers/image.ts";
|
||||
import useStore from "@/store";
|
||||
import run from '@/assets/svgs/run.svg';
|
||||
|
||||
const upscale = useStore().upscale;
|
||||
// ***************参数设置***************
|
||||
const TypeData = ['realesrgan', 'realcugan'];
|
||||
|
||||
const model_type = ref<string>(TypeData[0]);
|
||||
|
||||
const ModelConfig = reactive({
|
||||
realesrgan: {
|
||||
model: ["anime_fast", "anime_plus", "general_fast", "general_plus"],
|
||||
factor: [4],
|
||||
tile_size: [32, 48, 64, 96, 128, 192, 256],
|
||||
},
|
||||
realcugan: {
|
||||
factor: [2, 4],
|
||||
denoise: {
|
||||
2: [
|
||||
"conservative",
|
||||
"no-denoise",
|
||||
"denoise1x",
|
||||
"denoise2x",
|
||||
"denoise3x",
|
||||
],
|
||||
3: ["conservative", "denoise3x"],
|
||||
4: ["conservative", "no-denoise", "denoise3x"],
|
||||
},
|
||||
tile_size: [32, 48, 64, 96, 128, 192, 256, 384, 512],
|
||||
}
|
||||
});
|
||||
const factor = ref<number>(4);
|
||||
const model = ref(ModelConfig[model_type.value].model[0]);
|
||||
const modes = computed(() => {
|
||||
if (model_type.value === "realesrgan") {
|
||||
return ModelConfig[model_type.value].model;
|
||||
} else {
|
||||
return ModelConfig[model_type.value].denoise[factor.value];
|
||||
}
|
||||
});
|
||||
|
||||
watch(model_type, val => {
|
||||
if (model_type.value === "realesrgan") {
|
||||
model.value = ModelConfig[val].model[0];
|
||||
} else {
|
||||
model.value = ModelConfig[val].denoise[factor.value][0];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Scale
|
||||
const scales = computed(() => {
|
||||
return ModelConfig[model_type.value].factor;
|
||||
});
|
||||
|
||||
//tile size
|
||||
const tile_size = ref<number>(128);
|
||||
const tileSize = computed(() => {
|
||||
return ModelConfig[model_type.value].tile_size;
|
||||
});
|
||||
|
||||
// overlap
|
||||
const overlapList = [0, 4, 8, 12, 16, 20];
|
||||
const min_lap = ref<number>(overlapList[3]);
|
||||
|
||||
// run on
|
||||
const backendList = ['webgl', 'webgpu'];
|
||||
const backend = ref<string>(backendList[0]);
|
||||
|
||||
// ********************处理图片*******************
|
||||
const outputData = ref<any>();
|
||||
const imgCanvas = document.createElement("canvas");
|
||||
|
||||
|
||||
/**
|
||||
* WebWorker 处理图片
|
||||
*/
|
||||
async function startTask() {
|
||||
console.log(upscale.input);
|
||||
if (upscale.input === null) return;
|
||||
upscale.isProcessing = true;
|
||||
const start = Date.now();
|
||||
const worker = new Worker(new URL("@/workers/upscale.worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
worker.onmessage = (e: MessageEvent<any>) => {
|
||||
const {progress, done, output, error, info} = e.data;
|
||||
if (info) {
|
||||
upscale.msg = info;
|
||||
}
|
||||
if (error) {
|
||||
message.error(error);
|
||||
upscale.isProcessing = false;
|
||||
worker.terminate();
|
||||
return;
|
||||
}
|
||||
upscale.progressBar = progress;
|
||||
|
||||
if (done) {
|
||||
if (!upscale.hasAlpha || (upscale.hasAlpha && upscale.inputAlpha)) {
|
||||
if (upscale.input) {
|
||||
outputData.value = new Img(
|
||||
factor.value * upscale.input.width,
|
||||
factor.value * upscale.input.height,
|
||||
new Uint8Array(output)
|
||||
);
|
||||
}
|
||||
}
|
||||
upscale.msg = "Processing Image...";
|
||||
if (upscale.inputAlpha) {
|
||||
worker.postMessage(
|
||||
{
|
||||
input: upscale.inputAlpha.data.buffer,
|
||||
factor: factor.value,
|
||||
tile_size: tile_size.value,
|
||||
min_lap: min_lap.value,
|
||||
model_type: model_type.value,
|
||||
width: upscale.inputAlpha.width,
|
||||
height: upscale.inputAlpha.height,
|
||||
model: model.value,
|
||||
backend: backend.value,
|
||||
hasAlpha: true,
|
||||
},
|
||||
[upscale.inputAlpha.data.buffer]
|
||||
);
|
||||
upscale.inputAlpha = null;
|
||||
return;
|
||||
}
|
||||
if (upscale.hasAlpha && upscale.wasmModule) {
|
||||
const outputArray = new Uint8Array(output);
|
||||
const sourcePtr = upscale.wasmModule._malloc(outputArray.length);
|
||||
const targetPtr = upscale.wasmModule._malloc(outputArray.length);
|
||||
const numPixels = outputArray.length / 4;
|
||||
upscale.wasmModule.HEAPU8.set(outputArray, sourcePtr);
|
||||
upscale.wasmModule.HEAPU8.set(outputData.value.data, targetPtr);
|
||||
upscale.wasmModule._copy_alpha_channel(sourcePtr, targetPtr, numPixels);
|
||||
outputData.value.data.set(
|
||||
upscale.wasmModule.HEAPU8.subarray(
|
||||
targetPtr,
|
||||
targetPtr + outputArray.length
|
||||
)
|
||||
);
|
||||
upscale.wasmModule._free(sourcePtr);
|
||||
upscale.wasmModule._free(targetPtr);
|
||||
upscale.wasmModule = null;
|
||||
}
|
||||
|
||||
const imgCtx = imgCanvas.getContext("2d");
|
||||
if (imgCtx) {
|
||||
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
||||
imgCanvas.width = outputData.value.width;
|
||||
imgCanvas.height = outputData.value.height;
|
||||
let outImg = imgCtx.createImageData(
|
||||
outputData.value.width,
|
||||
outputData.value.height
|
||||
);
|
||||
outImg.data.set(outputData.value.data);
|
||||
upscale.input = null;
|
||||
upscale.inputAlpha = null;
|
||||
outputData.value = null;
|
||||
imgCtx.putImageData(outImg, 0, 0);
|
||||
let type = "image/jpeg";
|
||||
const quality = 0.92;
|
||||
if (upscale.hasAlpha) type = "image/png";
|
||||
|
||||
imgCanvas.toBlob(
|
||||
(blob: any) => {
|
||||
upscale.processedImg = URL.createObjectURL(blob);
|
||||
},
|
||||
type,
|
||||
quality
|
||||
);
|
||||
upscale.msg = "Done! Time used: " + (Date.now() - start) / 1000 + "s";
|
||||
upscale.isProcessing = false;
|
||||
upscale.isDone = true;
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (upscale.input) {
|
||||
worker.postMessage(
|
||||
{
|
||||
input: upscale.input.data.buffer,
|
||||
factor: factor.value,
|
||||
tile_size: tile_size.value,
|
||||
min_lap: min_lap.value,
|
||||
model_type: model_type.value,
|
||||
width: upscale.input.width,
|
||||
height: upscale.input.height,
|
||||
model: model.value,
|
||||
backend: backend.value,
|
||||
hasAlpha: false,
|
||||
},
|
||||
[upscale.input.data.buffer]
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-params {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
|
||||
.upscale-params-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
.upscale-params-item-content {
|
||||
width: 49%;
|
||||
|
||||
.upscale-params-title {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.upscale-params-btn {
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
@@ -1,184 +0,0 @@
|
||||
<template>
|
||||
<div class="upscale-comparison-result">
|
||||
<div class="upscale-comparison-top">
|
||||
<div class="upscale-comparison-top-left">
|
||||
|
||||
<VueCompareImage :left-image="bg" :right-image="bg2"
|
||||
style="width: 45vw; height: 60vh;border-radius: 10px;box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.5);"
|
||||
rightImageLabel="修复后"
|
||||
leftImageLabel="原图"
|
||||
/>
|
||||
|
||||
</div>
|
||||
<div class="upscale-comparison-top-right" ref="avatarContainer">
|
||||
<ABadge style="margin-top: 5px;" v-for="(item, index) in upscale.imageList"
|
||||
:key="index">
|
||||
<template #count>
|
||||
<AButton type="text" size="small" class="upscale-file-btn" @click="upscale.removeImage(index)">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="20" :src="remove"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</template>
|
||||
<AAvatar shape="square" :size="avatarSize" @click="bg = item">
|
||||
<template #icon>
|
||||
<AImage :src="item" width="100%" height="100%" :preview="true"/>
|
||||
</template>
|
||||
</AAvatar>
|
||||
</ABadge>
|
||||
</div>
|
||||
</div>
|
||||
<ADivider orientation="center" :plain="true"><span class="upscale-comparison-divider-text">保存下载</span>
|
||||
</ADivider>
|
||||
<div class="upscale-comparison-bottom">
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large">
|
||||
<AButton type="text" size="large" style="width: 50px; height: 50px;">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="40" :src="download"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<AButton type="text" style="width: 50px; height: 50px;">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="40" :src="save"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<AButton type="text" style="width: 50px; height: 50px;">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="35" :src="share"/>
|
||||
</template>
|
||||
</AButton>
|
||||
<AButton type="text" style="width: 50px; height: 50px;">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="35" :src="packageDownload"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</AFlex>
|
||||
<ADivider type="vertical" style="height: 30px;"/>
|
||||
<AFlex :vertical="false" align="center" justify="space-between" gap="large" style="margin-left: 1%">
|
||||
<AFlex :vertical="true" align="flex-start" justify="flex-start" gap="small">
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="upscale-comparison-bottom-text">处理数量:</span>
|
||||
<ATag color="orange">{{ 5 }}</ATag>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="upscale-comparison-bottom-text">处理大小:</span>
|
||||
<ATag color="green">{{ upscale.imageList.length }} /mb</ATag>
|
||||
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
<AFlex :vertical="true" align="flex-start" justify="center" gap="small">
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="upscale-comparison-bottom-text">消耗时间:</span>
|
||||
<ATag color="cyan">{{ upscale.imageList.length }} /s</ATag>
|
||||
</AFlex>
|
||||
<AFlex :vertical="false" align="center" justify="space-between">
|
||||
<span class="upscale-comparison-bottom-text">内存消耗:</span>
|
||||
<ATag color="purple">{{ upscale.imageList.length }} /mb</ATag>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</AFlex>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
import {VueCompareImage} from "@/components/VueCompareImage";
|
||||
import bg1 from "@/assets/images/background.jpg";
|
||||
import bg2 from "@/assets/images/background.png";
|
||||
import useStore from "@/store";
|
||||
import remove from "@/assets/svgs/remove.svg";
|
||||
import download from "@/assets/svgs/download.svg";
|
||||
import save from "@/assets/svgs/save.svg";
|
||||
import share from "@/assets/svgs/share.svg";
|
||||
import packageDownload from "@/assets/svgs/package-download.svg";
|
||||
|
||||
const upscale = useStore().upscale;
|
||||
const avatarSize = ref<number>(60);
|
||||
const avatarContainer = ref<HTMLDivElement | null>();
|
||||
const bg = ref<string>(bg1);
|
||||
|
||||
|
||||
/**
|
||||
* 更新大小
|
||||
*/
|
||||
const updateSize = () => {
|
||||
if (avatarContainer.value) {
|
||||
// 设置图片列表大小
|
||||
const container = avatarContainer.value?.clientWidth || 0;
|
||||
avatarSize.value = Math.min(0.7 * container, 300); // 设置头像大小为容器宽度的10%,最大不超过100
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateSize();
|
||||
window.addEventListener('resize', updateSize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', updateSize);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-comparison-result {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.upscale-comparison-top {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
.upscale-comparison-top-left {
|
||||
width: 85%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.upscale-comparison-image {
|
||||
width: 40vw;
|
||||
height: 60vh;
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-comparison-top-right {
|
||||
width: 14%;
|
||||
height: 60vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
overflow-y: scroll;
|
||||
border: 1px solid rgba(126, 126, 135, 0.48);
|
||||
border-radius: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-comparison-divider-text {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
|
||||
.upscale-comparison-bottom {
|
||||
width: 100%;
|
||||
height: 20%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.upscale-comparison-bottom-text {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -1,106 +1,46 @@
|
||||
<template>
|
||||
<div class="upscale-upload-container">
|
||||
<div class="upscale-upload-content">
|
||||
<div class="upscale-upload-container" ref="containerRef">
|
||||
<div class="upscale-upload-content" ref="uploadDraggerRef">
|
||||
<Spin :spinning="upscale.uploading" indicator="magic-ring">
|
||||
<AUploadDragger
|
||||
name="image"
|
||||
accept="image/*"
|
||||
:multiple="false"
|
||||
:directory="false"
|
||||
:maxCount="5"
|
||||
:maxCount="1"
|
||||
:beforeUpload="upscale.beforeUpload"
|
||||
:custom-request="upscale.customUploadRequest"
|
||||
:disabled="upscale.uploading"
|
||||
:showUploadList="false">
|
||||
<div class="upscale-upload-content-main">
|
||||
<div class="upscale-upload-content-left">
|
||||
<ABadge :count="upscale.imageList.length" :offset="[-10, 10]">
|
||||
<AAvatar shape="square" :size="70" :src="file"/>
|
||||
</ABadge>
|
||||
<span class="upscale-upload-text">
|
||||
<ABadge :offset="[-10, 10]">
|
||||
<template #count>
|
||||
<AAvatar :size="25" :src="sueccess" v-if="upscale.imageData"/>
|
||||
<AAvatar :size="25" :src="warn" v-if="!upscale.imageData"/>
|
||||
</template>
|
||||
<AAvatar shape="square" :size="70" :src="file"/>
|
||||
</ABadge>
|
||||
<span class="upscale-upload-text">
|
||||
点击或拖拽上传图片
|
||||
</span>
|
||||
<span class="upscale-upload-tip">最多一次处理5张图片</span>
|
||||
</div>
|
||||
<div class="upscale-upload-content-right" ref="qrcodeContainer">
|
||||
<AQrcode :value="'https://www.baidu.com'"
|
||||
class="upscale-upload-qrcode"
|
||||
:icon="qrphone"
|
||||
:iconSize="iconSize"
|
||||
:bordered="false"
|
||||
:size="qrcodeSize"
|
||||
color="rgba(126, 126, 135, 1)"/>
|
||||
<span class="upscale-upload-qr-text">手机扫码上传</span>
|
||||
</div>
|
||||
</div>
|
||||
</AUploadDragger>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ADivider orientation="center" :plain="true"><span class="upscale-file-list-title">图片列表</span></ADivider>
|
||||
<div class="upscale-file-list" ref="fileListContainer">
|
||||
<div v-if="upscale.imageList.length > 0">
|
||||
<AImagePreviewGroup>
|
||||
<ABadge style="margin-left: 10px;" v-for="(item, index) in upscale.imageList"
|
||||
:key="index">
|
||||
<template #count>
|
||||
<AButton type="text" size="small" class="upscale-file-btn" @click="upscale.removeImage(index)">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="20" :src="remove"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</template>
|
||||
<AAvatar shape="square" :size="avatarSize" v-if="item">
|
||||
<template #icon>
|
||||
<AImage :src="item" width="100%" height="100%"/>
|
||||
</template>
|
||||
</AAvatar>
|
||||
</ABadge>
|
||||
</AImagePreviewGroup>
|
||||
</div>
|
||||
</div>
|
||||
<AEmpty :image="empty" v-if="upscale.imageList.length === 0" :description="null"/>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import file from "@/assets/svgs/file.svg";
|
||||
import qrphone from "@/assets/svgs/qr-phone.svg";
|
||||
import empty from "@/assets/svgs/empty.svg";
|
||||
import useStore from "@/store";
|
||||
import Spin from "@/components/MyUI/Spin/Spin.vue";
|
||||
import remove from '@/assets/svgs/remove.svg';
|
||||
import sueccess from '@/assets/svgs/success.svg';
|
||||
import warn from '@/assets/svgs/warn.svg';
|
||||
|
||||
const upscale = useStore().upscale;
|
||||
const qrcodeSize = ref<number>(160);
|
||||
const iconSize = ref<number>(30);
|
||||
const qrcodeContainer = ref<HTMLElement | null>(null);
|
||||
const avatarSize = ref<number>(80);
|
||||
const fileListContainer = ref<HTMLDivElement | null>(null);
|
||||
|
||||
/**
|
||||
* 更新大小
|
||||
*/
|
||||
const updateSize = () => {
|
||||
if (qrcodeContainer.value && fileListContainer.value) {
|
||||
// 设置 QRCode 大小
|
||||
const containerWidth = qrcodeContainer.value.clientWidth;
|
||||
qrcodeSize.value = containerWidth * 0.5; // 设置 QRCode 为父盒子宽度的80%
|
||||
iconSize.value = Math.min(containerWidth * 0.1, 50); // 设置 icon 大小为父盒子宽度的10%
|
||||
const uploadDraggerRef = ref<HTMLDivElement | null>(null);
|
||||
const containerRef = ref<HTMLDivElement | null>(null);
|
||||
|
||||
// 设置图片列表大小
|
||||
const container = fileListContainer.value.clientWidth;
|
||||
avatarSize.value = Math.min(0.17 * container, 300); // 设置头像大小为容器宽度的10%,最大不超过100
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
updateSize();
|
||||
window.addEventListener('resize', updateSize);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', updateSize);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
@@ -122,44 +62,24 @@ onBeforeUnmount(() => {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
overflow: scroll;
|
||||
|
||||
.upscale-upload-content-left {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
.upscale-upload-text {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.upscale-upload-btn {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.upscale-upload-tip {
|
||||
font-size: 12px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
.upscale-upload-text {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.upscale-upload-content-right {
|
||||
width: 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.upscale-upload-qr-text {
|
||||
font-size: 12px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
.upscale-upload-btn {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.upscale-upload-tip {
|
||||
font-size: 12px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,8 +124,5 @@ onBeforeUnmount(() => {
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
.upscale-upload-content-right {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,388 +0,0 @@
|
||||
<template>
|
||||
<div class="upscale-container">
|
||||
<AFlex :vertical="false" align="center" justify="flex-start">
|
||||
<AAvatar shape="square" :size="22" :src="ai"/>
|
||||
<span class="upscale-title">图像修复</span>
|
||||
</AFlex>
|
||||
<AFlex class="upscale-content" :vertical="false" align="center" justify="flex-start">
|
||||
<div class="upscale-content-left">
|
||||
<ACard class="upscale-content-left-container">
|
||||
<UploadImage/>
|
||||
<ADivider orientation="center" :plain="true"><span class="upscale-divider-title">参数设置</span></ADivider>
|
||||
<div class="upscale-content-left-params">
|
||||
<div class="upscale-content-params-left">
|
||||
<div class="upscale-content-params-item">
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">类型:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="model_type"
|
||||
:options="TypeData.map(item => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">模型:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="model"
|
||||
:options="modes.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upscale-content-params-item">
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">比列:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="factor"
|
||||
:options="scales.map((item: any) => ({label: item, value: item}))">
|
||||
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">分块大小:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="tile_size"
|
||||
:options="tileSize.map((item: any) => ({label: item, value: item}))">
|
||||
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upscale-content-params-item">
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">重复:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="min_lap"
|
||||
:options="overlapList.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
<div class="upscale-content-params-item-content">
|
||||
<span class="upscale-content-params-title">运行环境:</span>
|
||||
<ASelect style="width: 100%" size="default"
|
||||
v-model:value="backend"
|
||||
:options="backendList.map((item: any) => ({label: item, value: item}))">
|
||||
</ASelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="upscale-content-params-right">
|
||||
<AButton type="text" style="width: 60px;height: 60px;">
|
||||
<template #icon>
|
||||
<AAvatar shape="square" :size="50" :src="run" @click="startTask()"/>
|
||||
</template>
|
||||
</AButton>
|
||||
</div>
|
||||
</div>
|
||||
</ACard>
|
||||
</div>
|
||||
<div class="upscale-content-right">
|
||||
<ACard class="upscale-content-right-container">
|
||||
<ProcessResult/>
|
||||
</ACard>
|
||||
</div>
|
||||
</AFlex>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import UploadImage from "@/views/Upscale/UploadImage.vue";
|
||||
import ai from "@/assets/svgs/ai.svg";
|
||||
import run from "@/assets/svgs/run.svg";
|
||||
import ProcessResult from "@/views/Upscale/ProcessResult.vue";
|
||||
import useStore from "@/store";
|
||||
import {message} from "ant-design-vue";
|
||||
import Img from "@/workers/image.ts";
|
||||
|
||||
|
||||
const upscale = useStore().upscale;
|
||||
|
||||
// ***************参数设置***************
|
||||
const TypeData = ['realesrgan', 'realcugan'];
|
||||
|
||||
const model_type = ref<string>(TypeData[0]);
|
||||
|
||||
const ModelConfig = reactive({
|
||||
realesrgan: {
|
||||
model: ["anime_fast", "anime_plus", "general_fast", "general_plus"],
|
||||
factor: [4],
|
||||
tile_size: [32, 48, 64, 96, 128, 192, 256],
|
||||
},
|
||||
realcugan: {
|
||||
factor: [2, 4],
|
||||
denoise: {
|
||||
2: [
|
||||
"conservative",
|
||||
"no-denoise",
|
||||
"denoise1x",
|
||||
"denoise2x",
|
||||
"denoise3x",
|
||||
],
|
||||
3: ["conservative", "denoise3x"],
|
||||
4: ["conservative", "no-denoise", "denoise3x"],
|
||||
},
|
||||
tile_size: [32, 48, 64, 96, 128, 192, 256, 384, 512],
|
||||
}
|
||||
});
|
||||
const factor = ref<number>(4);
|
||||
const model = ref(ModelConfig[model_type.value].model[0]);
|
||||
const modes = computed(() => {
|
||||
if (model_type.value === "realesrgan") {
|
||||
return ModelConfig[model_type.value].model;
|
||||
} else {
|
||||
return ModelConfig[model_type.value].denoise[factor.value];
|
||||
}
|
||||
});
|
||||
|
||||
watch(model_type, val => {
|
||||
if (model_type.value === "realesrgan") {
|
||||
model.value = ModelConfig[val].model[0];
|
||||
} else {
|
||||
model.value = ModelConfig[val].denoise[factor.value][0];
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Scale
|
||||
const scales = computed(() => {
|
||||
return ModelConfig[model_type.value].factor;
|
||||
});
|
||||
|
||||
//tile size
|
||||
const tile_size = ref<number>(128);
|
||||
const tileSize = computed(() => {
|
||||
return ModelConfig[model_type.value].tile_size;
|
||||
});
|
||||
|
||||
// overlap
|
||||
const overlapList = [0, 4, 8, 12, 16, 20];
|
||||
const min_lap = ref<number>(overlapList[3]);
|
||||
|
||||
// run on
|
||||
const backendList = ['webgl', 'webgpu'];
|
||||
const backend = ref<string>(backendList[0]);
|
||||
|
||||
// ********************处理图片*******************
|
||||
const isProcessing = ref<boolean>(false);
|
||||
const msg = ref<string>("");
|
||||
const progressBar = ref<number>(0);
|
||||
const outputData = ref<any>();
|
||||
const isDone = ref<boolean>(false);
|
||||
const imgCanvas = document.createElement("canvas");
|
||||
|
||||
const worker = new Worker(new URL("@/workers/upscale.worker.ts", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
|
||||
/**
|
||||
* WebWorker 处理图片
|
||||
*/
|
||||
async function startTask() {
|
||||
if (upscale.input === null) return;
|
||||
isProcessing.value = true;
|
||||
const start = Date.now();
|
||||
worker.onmessage = (e: MessageEvent<any>) => {
|
||||
const {progress, done, output, error, info} = e.data;
|
||||
if (info) {
|
||||
message.info(info);
|
||||
msg.value = info;
|
||||
}
|
||||
if (error) {
|
||||
message.error(error);
|
||||
isProcessing.value = false;
|
||||
worker.terminate();
|
||||
return;
|
||||
}
|
||||
progressBar.value = progress;
|
||||
|
||||
if (done) {
|
||||
if (!upscale.hasAlpha || (upscale.hasAlpha && upscale.inputAlpha)) {
|
||||
if (upscale.input) {
|
||||
outputData.value = new Img(
|
||||
factor.value * upscale.input.width,
|
||||
factor.value * upscale.input.height,
|
||||
new Uint8Array(output)
|
||||
);
|
||||
}
|
||||
}
|
||||
msg.value = "Processing Image...";
|
||||
if (upscale.inputAlpha) {
|
||||
worker.postMessage(
|
||||
{
|
||||
input: upscale.inputAlpha.data.buffer,
|
||||
factor: factor.value,
|
||||
tile_size: tile_size.value,
|
||||
min_lap: min_lap.value,
|
||||
model_type: model_type.value,
|
||||
width: upscale.inputAlpha.width,
|
||||
height: upscale.inputAlpha.height,
|
||||
model: model.value,
|
||||
backend: backend.value,
|
||||
hasAlpha: true,
|
||||
},
|
||||
[upscale.inputAlpha.data.buffer]
|
||||
);
|
||||
upscale.inputAlpha = null;
|
||||
return;
|
||||
}
|
||||
if (upscale.hasAlpha && upscale.wasmModule) {
|
||||
const outputArray = new Uint8Array(output);
|
||||
const sourcePtr = upscale.wasmModule._malloc(outputArray.length);
|
||||
const targetPtr = upscale.wasmModule._malloc(outputArray.length);
|
||||
const numPixels = outputArray.length / 4;
|
||||
upscale.wasmModule.HEAPU8.set(outputArray, sourcePtr);
|
||||
upscale.wasmModule.HEAPU8.set(outputData.value.data, targetPtr);
|
||||
upscale.wasmModule._copy_alpha_channel(sourcePtr, targetPtr, numPixels);
|
||||
outputData.value.data.set(
|
||||
upscale.wasmModule.HEAPU8.subarray(
|
||||
targetPtr,
|
||||
targetPtr + outputArray.length
|
||||
)
|
||||
);
|
||||
upscale.wasmModule._free(sourcePtr);
|
||||
upscale.wasmModule._free(targetPtr);
|
||||
upscale.wasmModule = null;
|
||||
}
|
||||
|
||||
const imgCtx = imgCanvas.getContext("2d");
|
||||
if (imgCtx) {
|
||||
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
||||
imgCanvas.width = outputData.value.width;
|
||||
imgCanvas.height = outputData.value.height;
|
||||
let outImg = imgCtx.createImageData(
|
||||
outputData.value.width,
|
||||
outputData.value.height
|
||||
);
|
||||
outImg.data.set(outputData.value.data);
|
||||
upscale.input = null;
|
||||
upscale.inputAlpha = null;
|
||||
outputData.value = null;
|
||||
imgCtx.putImageData(outImg, 0, 0);
|
||||
let type = "image/jpeg";
|
||||
const quality = 0.92;
|
||||
if (upscale.hasAlpha) type = "image/png";
|
||||
if (upscale.processedImg) {
|
||||
imgCanvas.toBlob(
|
||||
(blob: any) => {
|
||||
upscale.processedImg.src = URL.createObjectURL(blob);
|
||||
},
|
||||
type,
|
||||
quality
|
||||
);
|
||||
upscale.processedImg.onload = () => {
|
||||
msg.value = "Done! Time used: " + (Date.now() - start) / 1000 + "s";
|
||||
};
|
||||
}
|
||||
isProcessing.value = false;
|
||||
isDone.value = true;
|
||||
worker.terminate();
|
||||
}
|
||||
}
|
||||
};
|
||||
if (upscale.input) {
|
||||
worker.postMessage(
|
||||
{
|
||||
input: upscale.input.data.buffer,
|
||||
factor: factor.value,
|
||||
tile_size: tile_size.value,
|
||||
min_lap: min_lap.value,
|
||||
model_type: model_type.value,
|
||||
width: upscale.input.width,
|
||||
height: upscale.input.height,
|
||||
model: model.value,
|
||||
backend: backend.value,
|
||||
hasAlpha: false,
|
||||
},
|
||||
[upscale.input.data.buffer]
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
.upscale-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.upscale-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.upscale-content-left {
|
||||
width: 49%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.upscale-content-left-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.upscale-divider-title {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
|
||||
.upscale-content-left-params {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.upscale-content-params-left {
|
||||
width: 80%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.upscale-content-params-item {
|
||||
width: 30%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
.upscale-content-params-title {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-content-params-right {
|
||||
width: 20%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-content-right {
|
||||
width: 50%;
|
||||
height: 100%;
|
||||
|
||||
.upscale-content-right-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
78
src/views/Upscale/index.vue
Normal file
78
src/views/Upscale/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="upscale-container">
|
||||
<AFlex class="upscale-content" :vertical="false" align="center" justify="flex-start">
|
||||
<div class="upscale-content-left">
|
||||
<ACard class="upscale-content-left-container">
|
||||
<div class="upscale-content-left-upload">
|
||||
<UploadImage/>
|
||||
</div>
|
||||
<ADivider orientation="center" :plain="true">
|
||||
<span class="upscale-divider-title">参数设置</span>
|
||||
</ADivider>
|
||||
<ParameterSetting/>
|
||||
</ACard>
|
||||
</div>
|
||||
<div class="upscale-content-right">
|
||||
<CompareImage style="border-radius: 10px"/>
|
||||
</div>
|
||||
</AFlex>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import UploadImage from "@/views/Upscale/UploadImage.vue";
|
||||
import CompareImage from "@/views/Upscale/CompareImage.vue";
|
||||
import ParameterSetting from "@/views/Upscale/ParameterSetting.vue";
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.upscale-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
|
||||
//.upscale-title {
|
||||
// font-size: 16px;
|
||||
// font-weight: bold;
|
||||
// margin-left: 5px;
|
||||
//}
|
||||
|
||||
.upscale-content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.upscale-content-left {
|
||||
width: 29%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.upscale-content-left-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
|
||||
.upscale-divider-title {
|
||||
font-size: 13px;
|
||||
color: rgba(126, 126, 135, 0.99);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.upscale-content-right {
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user