code init
This commit is contained in:
34
.gitignore
vendored
Normal file
34
.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
lib
|
||||
demo
|
||||
realesrgan
|
||||
*.ttf
|
||||
.vscode
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# canvastest
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
npm run build
|
||||
```
|
||||
21
index.html
Normal file
21
index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
|
||||
/>
|
||||
<meta
|
||||
name="description"
|
||||
content="Run Super Resulotion in Your Local Browser. Your images never leave your device. Powered by TensorFlow.js and RealESRGAN. Support computing with WebGL and WebGPU."
|
||||
/>
|
||||
<title>Run Super Resulotion in Your Browser</title>
|
||||
<script src="/lib/jimp.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
1340
package-lock.json
generated
Normal file
1340
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "canvastest",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tensorflow/tfjs": "^4.17.0",
|
||||
"@tensorflow/tfjs-backend-webgpu": "^4.17.0",
|
||||
"vue": "^3.4.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/node": "^20.11.28",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"@vue/tsconfig": "^0.5.1",
|
||||
"npm-run-all2": "^6.1.2",
|
||||
"typescript": "~5.4.0",
|
||||
"vite": "^5.1.6",
|
||||
"vue-tsc": "^2.0.6"
|
||||
}
|
||||
}
|
||||
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
736
src/App.vue
Normal file
736
src/App.vue
Normal file
@@ -0,0 +1,736 @@
|
||||
<template>
|
||||
<div
|
||||
ref="canvasContainer"
|
||||
class="canvas-container"
|
||||
:class="{ 'canvas-container': true, bg: true, dark: imgLoaded }"
|
||||
@drop.prevent="handleDrop"
|
||||
@dragover.prevent
|
||||
@mousedown="startDragging"
|
||||
@mouseup="stopDragging"
|
||||
@mousemove="dragImage"
|
||||
@wheel="resizeImage"
|
||||
@touchstart="touchStart"
|
||||
@touchmove="touchMove"
|
||||
@touchend="touchEnd"
|
||||
>
|
||||
<div v-if="!imgLoaded" class="title">
|
||||
<div>SuperResolution in Your Browser</div>
|
||||
<img
|
||||
style="
|
||||
width: 50px;
|
||||
display: block;
|
||||
margin: auto;
|
||||
transform: translate(-18%, 0);
|
||||
"
|
||||
src="/demo/2.png"
|
||||
alt="favicon"
|
||||
class="favicon"
|
||||
@click="testdemo"
|
||||
/>
|
||||
</div>
|
||||
<canvas ref="canvas"></canvas>
|
||||
<button v-show="!imgLoaded" class="upload-button" @click="handleClick">
|
||||
<div class="upload-container">
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M19 7v3h-2V7h-3V5h3V2h2v3h3v2h-3zm-3 4V8h-3V5H5a2 2 0 00-2 2v12c0 1.1.9 2 2 2h12a2 2 0 002-2v-8h-3zM5 19l3-4 2 3 3-4 4 5H5z"
|
||||
fill0="rgba(255, 182, 193, 1)"
|
||||
fill00="#ff568a"
|
||||
fill="white"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
<button v-show="imgLoaded" class="goback" @click="reloadPage">
|
||||
<svg width="24" height="24" viewBox="0 0 1024 1024">
|
||||
<g
|
||||
fill="rgba(255, 255, 255, 1)"
|
||||
stroke-width="50"
|
||||
stroke="rgba(255, 255, 255, 1)"
|
||||
>
|
||||
<path
|
||||
d="M511.4 175.3l-31.6 31.6-74.8 74.8-87.7 87.7-71.5 71.5-20.1 20.1c-7.1 7.1-13.9 14.3-18.1 23.7-11.2 25.4-6 53.9 13.6 73.7l13.2 13.2 62.7 62.7 86.8 86.8 80.8 80.8 44.7 44.7 2.1 2.1c6.7 6.7 18.9 7.2 25.5 0 6.6-7.2 7.1-18.3 0-25.5l-30.9-30.9-73.8-73.8-87.1-87.1-71.7-71.7-21.1-21.1-5.3-5.3-1.1-1.1-0.1-0.1c-0.3-0.3-3.9-4.4-2.4-2.6 1.3 1.7-0.1-0.2-0.3-0.5-0.8-1.2-1.5-2.4-2.2-3.6-0.3-0.6-0.7-1.2-1-1.9-0.3-0.6-1.3-3.3-0.5-1 0.7 2.3-0.7-2.4-0.9-3.1-0.4-1.4-1.7-6-0.7-2-0.5-1.9-0.3-4.2-0.3-6.2 0-0.1 0.3-4.8 0.3-4.8 0.5 0.1-0.7 3.6 0 0.7l0.6-2.7c0.3-1.2 2.3-6.2 0.5-2.2 0.8-1.7 1.6-3.4 2.6-5 0.6-0.9 4-5.1 1.3-2.2 1-1.1 1.9-2.2 3-3.3l0.2-0.2 1.2-1.2 14.3-14.3 63.6-63.6 86-86 79.8-79.8 44-44 2.1-2.1c6.7-6.7 7.2-18.9 0-25.5-7.4-6.3-18.6-6.8-25.7 0.3z"
|
||||
></path>
|
||||
<path
|
||||
d="M804.6 494H432.9c-17.2 0-34.5-0.5-51.7 0h-0.7c-9.4 0-18.4 8.3-18 18 0.4 9.8 7.9 18 18 18h371.7c17.2 0 34.5 0.5 51.7 0h0.7c9.4 0 18.4-8.3 18-18-0.5-9.8-8-18-18-18z"
|
||||
></path>
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="floating-menu" v-if="imgLoaded" @mousedown.stop>
|
||||
<div>
|
||||
<div class="info" v-if="info">{{ info }}</div>
|
||||
<div class="progressbar" v-if="isProcessing || isDone">
|
||||
<progress max="100" :value="progress"></progress>
|
||||
</div>
|
||||
</div>
|
||||
<div class="opt" v-if="!isProcessing && !isDone">
|
||||
<div>
|
||||
<span class="description">Model</span>
|
||||
<select v-model="model">
|
||||
<option value="anime_4x">Anime (fast)</option>
|
||||
<option value="anime_4x_plus">Anime (plus)</option>
|
||||
<option value="general">General (fast)</option>
|
||||
<!-- <option value="realx2plus">realx2plus</option> -->
|
||||
<option value="realx4plus">General (plus)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<span class="description">Run on</span>
|
||||
<select v-model="backend">
|
||||
<option value="webgl">WebGL</option>
|
||||
<option value="webgpu">WebGPU</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="run-button"
|
||||
v-if="!isProcessing && !isDone"
|
||||
@click="startTask"
|
||||
>
|
||||
<svg viewBox="0 0 24 24">
|
||||
<path d="M8 5v14l11-7z" fill="rgba(255, 255, 255, 1)"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="save-button" v-if="isDone" @click="saveImage">
|
||||
<svg width="22" viewBox="0 -4 23.9 30">
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M6.6 2.7h-4v13.2h2.7A2.7 2.7 0 018 18.6a2.7 2.7 0 002.6 2.6h2.7a2.7 2.7 0 002.6-2.6 2.7 2.7 0 012.7-2.7h2.6V2.7h-4a1.3 1.3 0 110-2.7h4A2.7 2.7 0 0124 2.7v18.5a2.7 2.7 0 01-2.7 2.7H2.7A2.7 2.7 0 010 21.2V2.7A2.7 2.7 0 012.7 0h4a1.3 1.3 0 010 2.7zm4 7.4V1.3a1.3 1.3 0 112.7 0v8.8L15 8.4a1.3 1.3 0 011.9 1.8l-4 4a1.3 1.3 0 01-1.9 0l-4-4A1.3 1.3 0 019 8.4z"
|
||||
></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="dragLine"
|
||||
ref="dragLine"
|
||||
v-show="isDone"
|
||||
@mousedown.stop="startDraggingLine"
|
||||
@mousemove.stop="dragLine"
|
||||
>
|
||||
<div class="dragBall">
|
||||
<svg width="30" viewBox="0 0 27 20">
|
||||
<path fill="#ff3484" d="M9.6 0L0 9.6l9.6 9.6z"></path>
|
||||
<path fill="#5fb3e5" d="M17 19.2l9.5-9.6L16.9 0z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!imgLoaded" class="bottom-svg">
|
||||
<svg width="100%" viewBox="0 0 1920 140" class="_top-wave_vzxu7_106">
|
||||
<path
|
||||
fill="#76c8fe"
|
||||
d="M1920 0l-107 28c-106 29-320 85-533 93-213 7-427-36-640-50s-427 0-533 7L0 85v171h1920z"
|
||||
class="_sub-wave_vzxu7_117"
|
||||
></path>
|
||||
<path
|
||||
fill="#009aff"
|
||||
d="M0 129l64-26c64-27 192-81 320-75 128 5 256 69 384 64 128-6 256-80 384-91s256 43 384 70c128 26 256 26 320 26h64v96H0z"
|
||||
class="_main-wave_vzxu7_113"
|
||||
></path>
|
||||
</svg>
|
||||
<div class="demo">
|
||||
<div>No ideas? Try one of these:</div>
|
||||
<br />
|
||||
<div>
|
||||
<img class="demoimg" src="/demo/1.jpg" alt="demo" @click="testdemo" />
|
||||
<img class="demoimg" src="/demo/2.jpg" alt="demo" @click="testdemo" />
|
||||
<img class="demoimg" src="/demo/3.jpg" alt="demo" @click="testdemo" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div v-if="!imgLoaded" class="placeholder"></div> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Img from "./image";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
dragging: false,
|
||||
touching: false,
|
||||
imgX: 0,
|
||||
imgY: 0,
|
||||
imgScale: 1,
|
||||
imgInitScale: 1,
|
||||
linePosition: 0,
|
||||
drawLine: false,
|
||||
draggingLine: false,
|
||||
imgLoaded: false,
|
||||
dpr: window.devicePixelRatio || 1,
|
||||
imgName: "output",
|
||||
img: new Image(),
|
||||
processedImg: new Image(),
|
||||
hasAlpha: false,
|
||||
touchStartImgX: null,
|
||||
touchStartImgY: null,
|
||||
touchStartX: null,
|
||||
touchStartY: null,
|
||||
touchStartDistance: null,
|
||||
imgScaleStart: 1,
|
||||
|
||||
imgLoaded: false,
|
||||
input: null,
|
||||
output: null,
|
||||
isDragOver: false,
|
||||
isProcessing: false,
|
||||
isDone: false,
|
||||
progress: 0,
|
||||
model: "anime_4x",
|
||||
scale: 4,
|
||||
backend: "webgl",
|
||||
modelzoo: {
|
||||
anime_4x: {
|
||||
fixed: true,
|
||||
factor: 4,
|
||||
},
|
||||
anime_4x_plus: {
|
||||
fixed: false,
|
||||
factor: 4,
|
||||
},
|
||||
general: {
|
||||
fixed: true,
|
||||
factor: 4,
|
||||
},
|
||||
realx2plus: {
|
||||
fixed: false,
|
||||
factor: 4,
|
||||
},
|
||||
realx4plus: {
|
||||
fixed: false,
|
||||
factor: 4,
|
||||
},
|
||||
},
|
||||
info: "",
|
||||
worker: new Worker(new URL("./worker.js", import.meta.url), {
|
||||
type: "module",
|
||||
}),
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
model() {
|
||||
localStorage.setItem("model", this.model);
|
||||
},
|
||||
backend() {
|
||||
localStorage.setItem("backend", this.backend);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.model = localStorage.getItem("model") || "anime_4x";
|
||||
this.backend = localStorage.getItem("backend") || "webgl";
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
this.initializeCanvas();
|
||||
this.linePosition = this.$refs.canvas.width * 2;
|
||||
this.$refs.dragLine.style.left = this.linePosition / this.dpr + "px";
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener("resize", this.handleResize);
|
||||
},
|
||||
methods: {
|
||||
initializeCanvas() {
|
||||
this.updateCanvasSize();
|
||||
},
|
||||
updateCanvasSize() {
|
||||
const container = this.$refs.canvasContainer;
|
||||
const canvas = this.$refs.canvas;
|
||||
if (this.imgLoaded) {
|
||||
this.imgX =
|
||||
((this.imgX + (this.img.width * this.imgScale) / 2) / canvas.width) *
|
||||
container.offsetWidth *
|
||||
this.dpr -
|
||||
(this.img.width * this.imgScale) / 2;
|
||||
this.imgY =
|
||||
((this.imgY + (this.img.height * this.imgScale) / 2) /
|
||||
canvas.height) *
|
||||
container.offsetHeight *
|
||||
this.dpr -
|
||||
(this.img.height * this.imgScale) / 2;
|
||||
this.linePosition =
|
||||
(this.linePosition / canvas.width) * container.offsetWidth * this.dpr;
|
||||
this.$refs.dragLine.style.left = this.linePosition / this.dpr + "px";
|
||||
}
|
||||
canvas.width = container.offsetWidth * this.dpr;
|
||||
canvas.height = container.offsetHeight * this.dpr;
|
||||
canvas.style.width = `${container.offsetWidth}px`;
|
||||
canvas.style.height = `${container.offsetHeight}px`;
|
||||
this.drawImage();
|
||||
},
|
||||
handleResize() {
|
||||
this.updateCanvasSize();
|
||||
},
|
||||
loadImg(src) {
|
||||
this.img.src = src;
|
||||
this.img.onload = async () => {
|
||||
this.imgLoaded = true;
|
||||
this.drawLine = true;
|
||||
|
||||
const imageData = await Jimp.read(this.img.src);
|
||||
this.input = new Img(imageData.bitmap.width, imageData.bitmap.height);
|
||||
this.input.data = imageData.bitmap.data;
|
||||
//check if has alpha channel
|
||||
for (let i = 3; i < this.input.data.length; i += 4) {
|
||||
if (this.input.data[i] !== 255) {
|
||||
this.hasAlpha = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// copy alpha channel
|
||||
if (this.hasAlpha) {
|
||||
this.inputAlpha = new Img(
|
||||
imageData.bitmap.width,
|
||||
imageData.bitmap.height
|
||||
);
|
||||
this.inputAlpha.data = new Uint8Array(this.input.data);
|
||||
for (let i = 0; i < this.inputAlpha.data.length; i += 4) {
|
||||
this.inputAlpha.data[i] = this.input.data[i + 3];
|
||||
this.inputAlpha.data[i + 1] = this.input.data[i + 3];
|
||||
this.inputAlpha.data[i + 2] = this.input.data[i + 3];
|
||||
}
|
||||
}
|
||||
const canvas = this.$refs.canvas;
|
||||
const containerWidth = canvas.width;
|
||||
const containerHeight = canvas.height;
|
||||
|
||||
const scaleX = (0.8 * containerWidth) / this.img.width;
|
||||
const scaleY = (0.8 * containerHeight) / this.img.height;
|
||||
this.imgScale = Math.min(scaleX, scaleY, 4);
|
||||
this.imgInitScale = this.imgScale;
|
||||
|
||||
this.imgX = (containerWidth - this.img.width * this.imgScale) / 2;
|
||||
this.imgY = (containerHeight - this.img.height * this.imgScale) * 0.4;
|
||||
|
||||
this.drawImage();
|
||||
};
|
||||
},
|
||||
testdemo(event) {
|
||||
const img = event.target;
|
||||
this.loadImg(img.src);
|
||||
},
|
||||
handleDrop(event) {
|
||||
if (this.imgLoaded) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const files = event.dataTransfer.files;
|
||||
if (files && files.length > 0) {
|
||||
const file = files[0];
|
||||
this.imgName = file.name
|
||||
.replace(".jpg", "")
|
||||
.replace(".jpeg", "")
|
||||
.replace(".png", "");
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.loadImg(e.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
},
|
||||
handleClick() {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = "image/*";
|
||||
input.onchange = (e) => {
|
||||
const file = e.target.files[0];
|
||||
this.imgName = file.name
|
||||
.replace(".jpg", "")
|
||||
.replace(".jpeg", "")
|
||||
.replace(".png", "");
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
this.loadImg(e.target.result);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
input.click();
|
||||
},
|
||||
drawImage() {
|
||||
requestAnimationFrame(() => this.drawImage_());
|
||||
// this.drawImage_();
|
||||
},
|
||||
drawImage_() {
|
||||
const canvas = this.$refs.canvas;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
// 绘制第一张图(完整图像)
|
||||
ctx.drawImage(
|
||||
this.img,
|
||||
this.imgX,
|
||||
this.imgY,
|
||||
this.img.width * this.imgScale,
|
||||
this.img.height * this.imgScale
|
||||
);
|
||||
|
||||
// 绘制第二张图(处理后的图像)
|
||||
if (this.processedImg.src) {
|
||||
ctx.drawImage(
|
||||
this.processedImg,
|
||||
((this.processedImg.width / this.img.width) *
|
||||
(this.linePosition - this.imgX)) /
|
||||
this.imgScale,
|
||||
0,
|
||||
this.processedImg.width -
|
||||
((this.processedImg.width / this.img.width) *
|
||||
(this.linePosition - this.imgX)) /
|
||||
this.imgScale,
|
||||
this.processedImg.height,
|
||||
this.linePosition,
|
||||
this.imgY,
|
||||
this.imgX + this.img.width * this.imgScale - this.linePosition,
|
||||
this.img.height * this.imgScale
|
||||
);
|
||||
}
|
||||
|
||||
// 如果需要绘制分割线
|
||||
// if (this.drawLine) {
|
||||
// ctx.globalAlpha = 0.5;
|
||||
// ctx.beginPath();
|
||||
// ctx.moveTo(this.linePosition, 0);
|
||||
// ctx.lineTo(this.linePosition, canvas.height);
|
||||
// ctx.strokeStyle = "black";
|
||||
// ctx.lineWidth = 20;
|
||||
// ctx.stroke();
|
||||
// ctx.globalAlpha = 1;
|
||||
// }
|
||||
},
|
||||
startDragging(event) {
|
||||
const rect = this.$refs.canvas.getBoundingClientRect();
|
||||
const mouseX = event.clientX - rect.left;
|
||||
if (Math.abs(mouseX - this.linePosition / this.dpr) < 12) {
|
||||
this.startDraggingLine(event);
|
||||
return;
|
||||
}
|
||||
this.dragging = true;
|
||||
},
|
||||
stopDragging() {
|
||||
if (this.draggingLine) {
|
||||
this.stopDraggingLine();
|
||||
return;
|
||||
}
|
||||
this.dragging = false;
|
||||
},
|
||||
dragImage(event) {
|
||||
if (this.dragging) {
|
||||
this.imgX += event.movementX * this.dpr;
|
||||
this.imgY += event.movementY * this.dpr;
|
||||
this.drawImage();
|
||||
}
|
||||
if (this.draggingLine) {
|
||||
this.updateLinePosition(event);
|
||||
this.drawImage();
|
||||
}
|
||||
},
|
||||
touchDragImage(event) {
|
||||
if (this.touching) {
|
||||
const touch = event.touches[0];
|
||||
this.imgX += touch.clientX - this.touchStartX;
|
||||
this.imgY += touch.clientY - this.touchStartY;
|
||||
this.drawImage();
|
||||
}
|
||||
if (this.draggingLine) {
|
||||
this.updateLinePosition(event);
|
||||
this.drawImage();
|
||||
}
|
||||
},
|
||||
resizeImage(event) {
|
||||
if (!this.imgLoaded) return;
|
||||
event.preventDefault();
|
||||
const canvas = this.$refs.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mouseX = (event.clientX - rect.left) * this.dpr;
|
||||
const mouseY = (event.clientY - rect.top) * this.dpr;
|
||||
const prevScale = this.imgScale;
|
||||
const maxSize = 20 * this.imgInitScale;
|
||||
const minSize = 0.05 * this.imgInitScale;
|
||||
if (event.deltaY > 0) {
|
||||
const newScale = this.imgScale * 0.8;
|
||||
this.imgScale = Math.min(Math.max(minSize, newScale), maxSize);
|
||||
} else {
|
||||
const newScale = this.imgScale * 1.2;
|
||||
this.imgScale = Math.min(Math.max(minSize, newScale), maxSize);
|
||||
}
|
||||
|
||||
const scaleRatio = this.imgScale / prevScale;
|
||||
this.imgX = mouseX - (mouseX - this.imgX) * scaleRatio;
|
||||
this.imgY = mouseY - (mouseY - this.imgY) * scaleRatio;
|
||||
|
||||
this.drawImage();
|
||||
},
|
||||
touchStart(event) {
|
||||
this.touching = true;
|
||||
this.touchStartImgX = this.imgX;
|
||||
this.touchStartImgY = this.imgY;
|
||||
if (event.touches.length == 1) {
|
||||
if (
|
||||
Math.abs(event.touches[0].clientX - this.linePosition / this.dpr) < 12
|
||||
) {
|
||||
this.draggingLine = true;
|
||||
return;
|
||||
}
|
||||
this.touchStartX = event.touches[0].clientX * this.dpr;
|
||||
this.touchStartY = event.touches[0].clientY * this.dpr;
|
||||
} else {
|
||||
this.imgScaleStart = this.imgScale;
|
||||
const touch1 = event.touches[0];
|
||||
const touch2 = event.touches[1];
|
||||
this.touchStartDistance =
|
||||
Math.sqrt(
|
||||
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
||||
Math.pow(touch2.clientY - touch1.clientY, 2)
|
||||
) * this.dpr;
|
||||
this.touchStartX = ((touch1.clientX + touch2.clientX) / 2) * this.dpr;
|
||||
this.touchStartY = ((touch1.clientY + touch2.clientY) / 2) * this.dpr;
|
||||
}
|
||||
},
|
||||
touchMove(event) {
|
||||
event.preventDefault();
|
||||
if (!this.touching) {
|
||||
return;
|
||||
}
|
||||
if (event.touches.length == 1) {
|
||||
const touch = event.touches[0];
|
||||
const movementX =
|
||||
touch.clientX * this.dpr -
|
||||
this.touchStartX +
|
||||
this.touchStartImgX -
|
||||
this.imgX;
|
||||
const movementY =
|
||||
touch.clientY * this.dpr -
|
||||
this.touchStartY +
|
||||
this.touchStartImgY -
|
||||
this.imgY;
|
||||
if (this.draggingLine) {
|
||||
this.updateLinePosition(event.touches[0]);
|
||||
this.drawImage();
|
||||
return;
|
||||
}
|
||||
if (this.touching) {
|
||||
this.imgX += movementX;
|
||||
this.imgY += movementY;
|
||||
this.drawImage();
|
||||
}
|
||||
} else {
|
||||
const touch1 = event.touches[0];
|
||||
const touch2 = event.touches[1];
|
||||
const distance =
|
||||
Math.sqrt(
|
||||
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
||||
Math.pow(touch2.clientY - touch1.clientY, 2)
|
||||
) * this.dpr;
|
||||
const canvas = this.$refs.canvas;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const mouseX = this.touchStartX - rect.left;
|
||||
const mouseY = this.touchStartY - rect.top;
|
||||
const scaleChange = distance / this.touchStartDistance;
|
||||
const prevScale = this.imgScale;
|
||||
const maxSize = 20 * this.imgInitScale;
|
||||
const minSize = 0.05 * this.imgInitScale;
|
||||
const newScale = this.imgScaleStart * scaleChange;
|
||||
this.imgScale = Math.min(Math.max(minSize, newScale), maxSize);
|
||||
|
||||
const scaleRatio = this.imgScale / prevScale;
|
||||
const movementX =
|
||||
((touch1.clientX + touch2.clientX) / 2) * this.dpr - this.touchStartX;
|
||||
const movementY =
|
||||
((touch1.clientY + touch2.clientY) / 2) * this.dpr - this.touchStartY;
|
||||
this.imgX = mouseX - (mouseX - this.imgX) * scaleRatio + movementX;
|
||||
this.imgY = mouseY - (mouseY - this.imgY) * scaleRatio + movementY;
|
||||
this.touchStartX = ((touch1.clientX + touch2.clientX) / 2) * this.dpr;
|
||||
this.touchStartY = ((touch1.clientY + touch2.clientY) / 2) * this.dpr;
|
||||
this.drawImage();
|
||||
}
|
||||
},
|
||||
touchEnd(event) {
|
||||
if (event.touches.length == 2) {
|
||||
this.touchStartImgX = this.imgX;
|
||||
this.touchStartImgY = this.imgY;
|
||||
const touch1 = event.touches[0];
|
||||
const touch2 = event.touches[1];
|
||||
this.touchStartDistance =
|
||||
Math.sqrt(
|
||||
Math.pow(touch2.clientX - touch1.clientX, 2) +
|
||||
Math.pow(touch2.clientY - touch1.clientY, 2)
|
||||
) * this.dpr;
|
||||
this.touchStartX = ((touch1.clientX + touch2.clientX) / 2) * this.dpr;
|
||||
this.touchStartY = ((touch1.clientY + touch2.clientY) / 2) * this.dpr;
|
||||
return;
|
||||
}
|
||||
if (event.touches.length == 1) {
|
||||
this.touchStartImgX = this.imgX;
|
||||
this.touchStartImgY = this.imgY;
|
||||
this.touchStartX = event.touches[0].clientX * this.dpr;
|
||||
this.touchStartY = event.touches[0].clientY * this.dpr;
|
||||
return;
|
||||
}
|
||||
this.touching = false;
|
||||
this.draggingLine = false;
|
||||
this.touchStartImgX = null;
|
||||
this.touchStartImgY = null;
|
||||
this.touchStartX = null;
|
||||
this.touchStartY = null;
|
||||
this.touchStartDistance = null;
|
||||
},
|
||||
startDraggingLine(event) {
|
||||
event.preventDefault();
|
||||
if (!this.isDone) return;
|
||||
this.draggingLine = true;
|
||||
},
|
||||
stopDraggingLine() {
|
||||
this.draggingLine = false;
|
||||
},
|
||||
dragLine(event) {
|
||||
event.preventDefault();
|
||||
if (this.draggingLine) {
|
||||
this.updateLinePosition(event);
|
||||
this.drawImage();
|
||||
}
|
||||
},
|
||||
updateLinePosition(event) {
|
||||
const rect = this.$refs.canvas.getBoundingClientRect();
|
||||
this.linePosition = event.clientX * this.dpr - rect.left;
|
||||
const line = this.$refs.dragLine;
|
||||
line.style.left = Math.floor(this.linePosition / this.dpr) + "px";
|
||||
},
|
||||
startTask() {
|
||||
if (this.input === null) return;
|
||||
this.isProcessing = true;
|
||||
// const worker = new Worker(new URL("./worker.js", import.meta.url), {
|
||||
// type: "module",
|
||||
// });
|
||||
let worker = this.worker;
|
||||
let start = Date.now();
|
||||
worker.addEventListener("message", (e) => {
|
||||
const { progress, done, output, alertmsg, info } = e.data;
|
||||
if (info) {
|
||||
this.info = info;
|
||||
}
|
||||
if (alertmsg) {
|
||||
alert(alertmsg);
|
||||
this.isProcessing = false;
|
||||
worker.terminate();
|
||||
return;
|
||||
}
|
||||
this.progress = progress;
|
||||
if (done) {
|
||||
if (!this.hasAlpha || (this.hasAlpha && this.inputAlpha)) {
|
||||
this.output = output;
|
||||
}
|
||||
this.info = "Processing Image...";
|
||||
if (this.inputAlpha) {
|
||||
worker.postMessage({
|
||||
input: this.inputAlpha.data,
|
||||
fixed: this.modelzoo[this.model].fixed,
|
||||
factor: this.modelzoo[this.model].factor,
|
||||
width: this.inputAlpha.width,
|
||||
height: this.inputAlpha.height,
|
||||
model: this.model,
|
||||
backend: this.backend,
|
||||
hasAlpha: true,
|
||||
});
|
||||
this.inputAlpha = null;
|
||||
return;
|
||||
}
|
||||
if (this.hasAlpha) {
|
||||
for (let i = 0; i < output.data.length; i += 4) {
|
||||
if (output.data[i] < 128) this.output.data[i + 3] = 0;
|
||||
else this.output.data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
new Jimp(this.output.width, this.output.height, (err, image) => {
|
||||
if (err) throw err;
|
||||
image.bitmap.data = this.output.data;
|
||||
// if (this.scale === 2) {
|
||||
// image.resize(
|
||||
// output.width / 2,
|
||||
// output.height / 2,
|
||||
// Jimp.RESIZE_BICUBIC
|
||||
// );
|
||||
// }
|
||||
// image.quality(75);
|
||||
let type = Jimp.MIME_JPEG;
|
||||
if (this.hasAlpha) type = Jimp.MIME_PNG;
|
||||
image.getBase64(type, (err, src) => {
|
||||
if (err) throw err;
|
||||
this.processedImg.src = src;
|
||||
this.processedImg.onload = () => {
|
||||
this.linePosition = this.$refs.canvas.width * 0.5;
|
||||
this.$refs.dragLine.style.left =
|
||||
this.linePosition / this.dpr + "px";
|
||||
this.drawImage();
|
||||
this.info =
|
||||
"Done! Time used: " + (Date.now() - start) / 1000 + "s";
|
||||
};
|
||||
this.isProcessing = false;
|
||||
this.isDone = true;
|
||||
});
|
||||
});
|
||||
worker.terminate();
|
||||
}
|
||||
});
|
||||
worker.postMessage({
|
||||
input: this.input.data,
|
||||
fixed: this.modelzoo[this.model].fixed,
|
||||
factor: this.modelzoo[this.model].factor,
|
||||
width: this.input.width,
|
||||
height: this.input.height,
|
||||
model: this.model,
|
||||
backend: this.backend,
|
||||
hasAlpha: false,
|
||||
});
|
||||
},
|
||||
saveImage() {
|
||||
const a = document.createElement("a");
|
||||
a.href = this.processedImg.src;
|
||||
if (this.hasAlpha) a.download = this.imgName + ".png";
|
||||
else a.download = this.imgName + ".jpg";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
},
|
||||
reloadPage() {
|
||||
this.worker.terminate();
|
||||
this.worker = new Worker(new URL("./worker.js", import.meta.url), {
|
||||
type: "module",
|
||||
});
|
||||
//reset
|
||||
const canvas = this.$refs.canvas;
|
||||
const ctx = canvas.getContext("2d");
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
this.dragging = false;
|
||||
this.touching = false;
|
||||
this.imgX = 0;
|
||||
this.imgY = 0;
|
||||
this.imgScale = 1;
|
||||
this.imgInitScale = 1;
|
||||
this.linePosition = 0;
|
||||
this.drawLine = false;
|
||||
this.draggingLine = false;
|
||||
this.imgLoaded = false;
|
||||
this.dpr = window.devicePixelRatio || 1;
|
||||
this.img = new Image();
|
||||
this.processedImg = new Image();
|
||||
this.hasAlpha = false;
|
||||
this.touchStartImgX = null;
|
||||
this.touchStartImgY = null;
|
||||
this.touchStartX = null;
|
||||
this.touchStartY = null;
|
||||
this.touchStartDistance = null;
|
||||
this.imgScaleStart = 1;
|
||||
|
||||
this.imgLoaded = false;
|
||||
this.input = null;
|
||||
this.inputAlpha = null;
|
||||
this.output = null;
|
||||
this.isDragOver = false;
|
||||
this.isProcessing = false;
|
||||
this.isDone = false;
|
||||
this.progress = 0;
|
||||
this.model = localStorage.getItem("model") || "anime_4x";
|
||||
this.scale = 4;
|
||||
this.backend = localStorage.getItem("backend") || "webgl";
|
||||
this.info = "";
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
248
src/assets/main.css
Normal file
248
src/assets/main.css
Normal file
@@ -0,0 +1,248 @@
|
||||
@font-face {
|
||||
font-family: "Roboto-Bold";
|
||||
src: url("/src/assets/Roboto-Bold.ttf") format("truetype");
|
||||
}
|
||||
body,
|
||||
html,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.canvas-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
.bg {
|
||||
background-color: #ffffff;
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
#f3f3f3 25%,
|
||||
transparent 25%,
|
||||
transparent 75%,
|
||||
#f3f3f3 75%,
|
||||
#f3f3f3
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
#f3f3f3 25%,
|
||||
#ffffff 25%,
|
||||
#ffffff 75%,
|
||||
#f3f3f3 75%,
|
||||
#f3f3f3
|
||||
);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 10px 10px;
|
||||
}
|
||||
.bg.dark {
|
||||
background-color: #313131;
|
||||
background-image: linear-gradient(
|
||||
45deg,
|
||||
#333333 25%,
|
||||
transparent 25%,
|
||||
transparent 75%,
|
||||
#333333 75%,
|
||||
#333333
|
||||
),
|
||||
linear-gradient(
|
||||
45deg,
|
||||
#333333 25%,
|
||||
#313131 25%,
|
||||
#313131 75%,
|
||||
#333333 75%,
|
||||
#333333
|
||||
);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 10px 10px;
|
||||
}
|
||||
canvas {
|
||||
height: 100vh;
|
||||
}
|
||||
.title {
|
||||
position: fixed;
|
||||
top: 6%;
|
||||
left: 50%;
|
||||
width: 80%;
|
||||
transform: translate(-50%, 0%);
|
||||
text-align: center;
|
||||
color: brown;
|
||||
font-size: 32px;
|
||||
font-family: "Roboto-Bold";
|
||||
font-weight: 700;
|
||||
font-style: normal; /* font-weight: 900;font-style: normal; */
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.upload-button {
|
||||
background-color: #ff568a;
|
||||
border-width: 0px;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -60%);
|
||||
z-index: 10;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.upload-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.upload-container {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.bottom-svg {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: -20px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
z-index: 10;
|
||||
}
|
||||
.placeholder {
|
||||
height: 300px;
|
||||
background-color: #009aff;
|
||||
}
|
||||
.demo {
|
||||
margin-top: -10px;
|
||||
padding: 20px;
|
||||
height: 200px;
|
||||
background-color: #009aff;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 20px;
|
||||
font-family: "Roboto-Bold";
|
||||
}
|
||||
.favicon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.demoimg {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
margin: 12px;
|
||||
}
|
||||
.demoimg:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.description {
|
||||
display: inline-block;
|
||||
color: white;
|
||||
min-width: 60px;
|
||||
font-family: "Roboto-Bold";
|
||||
}
|
||||
select {
|
||||
width: 120px;
|
||||
background-color: #1d1d1d;
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-family: "Roboto-Bold";
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
.run-button {
|
||||
margin-left: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: #1d90ee;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.run-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.save-button {
|
||||
margin-left: 20px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background-color: rgb(157, 202, 90);
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
display: inline-block;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.save-button:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.goback {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
background-color: #d21d5a;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
padding: 5px;
|
||||
margin: 5px;
|
||||
}
|
||||
.goback:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.floating-menu {
|
||||
position: absolute;
|
||||
background-color: #1d1d1d;
|
||||
border-radius: 20px;
|
||||
color: white;
|
||||
max-width: 100%;
|
||||
width: 280px;
|
||||
padding: 20px;
|
||||
bottom: 25px;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0%);
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition: all 0.5s ease;
|
||||
}
|
||||
.dragLine {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 9px;
|
||||
transform: translate(-100%, 0);
|
||||
background-color: rgba(0, 0, 0, 0.6);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.dragLine:hover {
|
||||
cursor: ew-resize;
|
||||
}
|
||||
.dragBall {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
background-color: rgba(0, 0, 0, 0.9);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
30
src/image.ts
Normal file
30
src/image.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export default class Image {
|
||||
width: number;
|
||||
height: number;
|
||||
data: Uint8Array;
|
||||
constructor(width: number, height: number) {
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.data = new Uint8Array(width * height * 4);
|
||||
}
|
||||
getImageCrop(
|
||||
x: number,
|
||||
y: number,
|
||||
image: Image,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
) {
|
||||
for (let j = y1; j < y2; j++) {
|
||||
for (let i = x1; i < x2; i++) {
|
||||
let index = (y + j - y1) * this.width * 4 + (x + i - x1) * 4;
|
||||
let imageIndex = j * image.width * 4 + i * 4;
|
||||
this.data[index] = image.data[imageIndex];
|
||||
this.data[index + 1] = image.data[imageIndex + 1];
|
||||
this.data[index + 2] = image.data[imageIndex + 2];
|
||||
this.data[index + 3] = image.data[imageIndex + 3];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/main.ts
Normal file
6
src/main.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import "./assets/main.css";
|
||||
|
||||
import { createApp } from "vue";
|
||||
import App from "./App.vue";
|
||||
|
||||
createApp(App).mount("#app");
|
||||
42
src/upscale.ts
Normal file
42
src/upscale.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import * as tf from "@tensorflow/tfjs";
|
||||
import Image from "./image";
|
||||
|
||||
export default async function upscale(
|
||||
image: Image,
|
||||
model: any
|
||||
): Promise<Image> {
|
||||
let tensor = img2tensor(image);
|
||||
let result = model.predict(tensor) as tf.Tensor;
|
||||
let resultImage = await tensor2img(result);
|
||||
return resultImage;
|
||||
}
|
||||
|
||||
function img2tensor(image: Image): tf.Tensor {
|
||||
let arr = new Float32Array(image.width * image.height * 3);
|
||||
for (let i = 0; i < image.width * image.height; i++) {
|
||||
arr[i * 3] = image.data[i * 4] / 255;
|
||||
arr[i * 3 + 1] = image.data[i * 4 + 1] / 255;
|
||||
arr[i * 3 + 2] = image.data[i * 4 + 2] / 255;
|
||||
}
|
||||
let tensor = tf.tensor4d(arr, [1, image.height, image.width, 3]);
|
||||
return tensor;
|
||||
}
|
||||
|
||||
async function tensor2img(tensor: tf.Tensor): Promise<Image> {
|
||||
let [_, height, width, __] = tensor.shape;
|
||||
let arr = await tensor.data();
|
||||
let clipped = new Uint8Array(
|
||||
arr.map((x) => {
|
||||
x = Math.min(1, Math.max(0, x));
|
||||
return Math.floor(x * 255);
|
||||
})
|
||||
);
|
||||
let image = new Image(width, height);
|
||||
for (let i = 0; i < width * height; i++) {
|
||||
image.data[i * 4] = clipped[i * 3];
|
||||
image.data[i * 4 + 1] = clipped[i * 3 + 1];
|
||||
image.data[i * 4 + 2] = clipped[i * 3 + 2];
|
||||
image.data[i * 4 + 3] = 255;
|
||||
}
|
||||
return image;
|
||||
}
|
||||
5
src/vue-shim.d.ts
vendored
Normal file
5
src/vue-shim.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module "*.vue" {
|
||||
import { DefineComponent } from "vue";
|
||||
const component: DefineComponent<{}, {}, any>;
|
||||
export default component;
|
||||
}
|
||||
360
src/worker.js
Normal file
360
src/worker.js
Normal file
@@ -0,0 +1,360 @@
|
||||
import * as tf from "@tensorflow/tfjs";
|
||||
import "@tensorflow/tfjs-backend-webgpu";
|
||||
import Img from "./image";
|
||||
import upscale from "./upscale";
|
||||
|
||||
self.addEventListener("message", async (e) => {
|
||||
const { data } = e;
|
||||
|
||||
let model_url = "";
|
||||
if (data?.model === "anime_4x") {
|
||||
model_url = `/realesrgan/anime_4x/model.json`;
|
||||
}
|
||||
if (data?.model === "anime_4x_plus") {
|
||||
model_url = `/realesrgan/anime_4x_plus/model.json`;
|
||||
}
|
||||
if (data?.model === "general") {
|
||||
model_url = `/realesrgan/general/model.json`;
|
||||
}
|
||||
if (data?.model === "realx4plus") {
|
||||
model_url = `/realesrgan/realx4plus/model.json`;
|
||||
}
|
||||
if (!(await tf.setBackend(data?.backend || "webgl"))) {
|
||||
postMessage({
|
||||
alertmsg: `${data?.backend} is not supported in your browser.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
let model;
|
||||
try {
|
||||
model = await tf.loadGraphModel(`indexeddb://${data?.model}`);
|
||||
console.log("Model loaded successfully");
|
||||
// self.postMessage({ info: "Model loaded from cache" });
|
||||
} catch (error) {
|
||||
self.postMessage({ info: "Downloading model" });
|
||||
model = await (async () => {
|
||||
const fetchedModel = await tf.loadGraphModel(model_url);
|
||||
await fetchedModel.save(`indexeddb://${data?.model}`);
|
||||
return fetchedModel;
|
||||
})();
|
||||
}
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
const input = new Img(data.width, data.height);
|
||||
input.data = data.input;
|
||||
let hasAlpha = data.hasAlpha;
|
||||
function sendprogress(progress) {
|
||||
if (hasAlpha) {
|
||||
self.postMessage({
|
||||
progress: progress,
|
||||
info: `Processing Alpha ${progress.toFixed(2)}%`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
self.postMessage({
|
||||
progress: progress,
|
||||
info: `Processing ${progress.toFixed(2)}%`,
|
||||
});
|
||||
}
|
||||
async function enlargeImage(
|
||||
model,
|
||||
inputImg,
|
||||
factor = 4,
|
||||
tilesize = 32,
|
||||
padsize = 8
|
||||
) {
|
||||
if (hasAlpha) {
|
||||
tilesize = 16;
|
||||
padsize = 4;
|
||||
}
|
||||
const width = inputImg.width;
|
||||
const height = inputImg.height;
|
||||
const output = new Img(width * factor, height * factor);
|
||||
const total = Math.ceil(width / tilesize) * Math.ceil(height / tilesize);
|
||||
let current = 0;
|
||||
let useModel = new Array(total).fill(false);
|
||||
if (hasAlpha) {
|
||||
for (let i = 0; i < width; i += tilesize) {
|
||||
for (let j = 0; j < height; j += tilesize) {
|
||||
const x1 = Math.max(i, 0);
|
||||
const y1 = Math.max(j, 0);
|
||||
const x2 = Math.min(i + tilesize, width);
|
||||
const y2 = Math.min(j + tilesize, height);
|
||||
const tile = new Img(x2 - x1, y2 - y1);
|
||||
tile.getImageCrop(0, 0, input, x1, y1, x2, y2);
|
||||
for (let k = 4; k < tile.data.length; k += 4) {
|
||||
if (tile.data[k + 3] !== tile.data[3]) {
|
||||
useModel[current] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (useModel[current]) {
|
||||
current++;
|
||||
continue;
|
||||
}
|
||||
let scaled = new Img(tile.width * factor, tile.height * factor);
|
||||
for (let k = 0; k < scaled.data.length; k += 4) {
|
||||
scaled.data[k] = tile.data[3];
|
||||
scaled.data[k + 1] = tile.data[3];
|
||||
scaled.data[k + 2] = tile.data[3];
|
||||
}
|
||||
output.getImageCrop(
|
||||
i * factor,
|
||||
j * factor,
|
||||
scaled,
|
||||
0,
|
||||
0,
|
||||
scaled.width,
|
||||
scaled.height
|
||||
);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
current = 0;
|
||||
for (let i = 0; i < width; i += tilesize) {
|
||||
for (let j = 0; j < height; j += tilesize) {
|
||||
if (!useModel[current]) {
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
continue;
|
||||
}
|
||||
const x1 = Math.max(i - padsize, 0);
|
||||
const y1 = Math.max(j - padsize, 0);
|
||||
const x2 = Math.min(i + tilesize + padsize, width);
|
||||
const y2 = Math.min(j + tilesize + padsize, height);
|
||||
const pad_left = i - x1;
|
||||
const pad_top = j - y1;
|
||||
const pad_right = Math.max(0, x2 - (i + tilesize));
|
||||
const pad_bottom = Math.max(0, y2 - (j + tilesize));
|
||||
const tile = new Img(x2 - x1, y2 - y1);
|
||||
tile.getImageCrop(0, 0, input, x1, y1, x2, y2);
|
||||
let scaled = await upscale(tile, model);
|
||||
output.getImageCrop(
|
||||
i * factor,
|
||||
j * factor,
|
||||
scaled,
|
||||
pad_left * factor,
|
||||
pad_top * factor,
|
||||
scaled.width - pad_right * factor,
|
||||
scaled.height - pad_bottom * factor
|
||||
);
|
||||
// console.log(i, j, x2 - x1, y2 - y1);
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < width; i += tilesize) {
|
||||
for (let j = 0; j < height; j += tilesize) {
|
||||
const x1 = Math.max(i - padsize, 0);
|
||||
const y1 = Math.max(j - padsize, 0);
|
||||
const x2 = Math.min(i + tilesize + padsize, width);
|
||||
const y2 = Math.min(j + tilesize + padsize, height);
|
||||
const pad_left = i - x1;
|
||||
const pad_top = j - y1;
|
||||
const pad_right = Math.max(0, x2 - (i + tilesize));
|
||||
const pad_bottom = Math.max(0, y2 - (j + tilesize));
|
||||
const tile = new Img(x2 - x1, y2 - y1);
|
||||
tile.getImageCrop(0, 0, input, x1, y1, x2, y2);
|
||||
let scaled = await upscale(tile, model);
|
||||
output.getImageCrop(
|
||||
i * factor,
|
||||
j * factor,
|
||||
scaled,
|
||||
pad_left * factor,
|
||||
pad_top * factor,
|
||||
scaled.width - pad_right * factor,
|
||||
scaled.height - pad_bottom * factor
|
||||
);
|
||||
// console.log(i, j, x2 - x1, y2 - y1);
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
async function enlargeImageWithFixedInput(
|
||||
model,
|
||||
inputImg,
|
||||
factor = 4,
|
||||
input_size = 64,
|
||||
min_lap = 12
|
||||
) {
|
||||
const width = inputImg.width;
|
||||
const height = inputImg.height;
|
||||
const output = new Img(width * factor, height * factor);
|
||||
let num_x = 1;
|
||||
for (; (input_size * num_x - width) / (num_x - 1) < min_lap; num_x++);
|
||||
let num_y = 1;
|
||||
for (; (input_size * num_y - height) / (num_y - 1) < min_lap; num_y++);
|
||||
const locs_x = new Array(num_x);
|
||||
const locs_y = new Array(num_y);
|
||||
const pad_left = new Array(num_x);
|
||||
const pad_top = new Array(num_y);
|
||||
const pad_right = new Array(num_x);
|
||||
const pad_bottom = new Array(num_y);
|
||||
const total_lap_x = input_size * num_x - width;
|
||||
const total_lap_y = input_size * num_y - height;
|
||||
const base_lap_x = Math.floor(total_lap_x / (num_x - 1));
|
||||
const base_lap_y = Math.floor(total_lap_y / (num_y - 1));
|
||||
const extra_lap_x = total_lap_x - base_lap_x * (num_x - 1);
|
||||
const extra_lap_y = total_lap_y - base_lap_y * (num_y - 1);
|
||||
locs_x[0] = 0;
|
||||
for (let i = 1; i < num_x; i++) {
|
||||
if (i <= extra_lap_x) {
|
||||
locs_x[i] = locs_x[i - 1] + input_size - base_lap_x - 1;
|
||||
} else {
|
||||
locs_x[i] = locs_x[i - 1] + input_size - base_lap_x;
|
||||
}
|
||||
}
|
||||
locs_y[0] = 0;
|
||||
for (let i = 1; i < num_y; i++) {
|
||||
if (i <= extra_lap_y) {
|
||||
locs_y[i] = locs_y[i - 1] + input_size - base_lap_y - 1;
|
||||
} else {
|
||||
locs_y[i] = locs_y[i - 1] + input_size - base_lap_y;
|
||||
}
|
||||
}
|
||||
pad_left[0] = 0;
|
||||
pad_top[0] = 0;
|
||||
pad_right[num_x - 1] = 0;
|
||||
pad_bottom[num_y - 1] = 0;
|
||||
for (let i = 1; i < num_x; i++) {
|
||||
pad_left[i] = Math.floor((locs_x[i - 1] + input_size - locs_x[i]) / 2);
|
||||
}
|
||||
for (let i = 1; i < num_y; i++) {
|
||||
pad_top[i] = Math.floor((locs_y[i - 1] + input_size - locs_y[i]) / 2);
|
||||
}
|
||||
for (let i = 0; i < num_x - 1; i++) {
|
||||
pad_right[i] = locs_x[i] + input_size - locs_x[i + 1] - pad_left[i + 1];
|
||||
}
|
||||
for (let i = 0; i < num_y - 1; i++) {
|
||||
pad_bottom[i] = locs_y[i] + input_size - locs_y[i + 1] - pad_top[i + 1];
|
||||
}
|
||||
const total = num_x * num_y;
|
||||
let current = 0;
|
||||
let useModel = new Array(total).fill(false);
|
||||
if (hasAlpha) {
|
||||
for (let i = 0; i < num_x; i++) {
|
||||
for (let j = 0; j < num_y; j++) {
|
||||
const x1 = locs_x[i];
|
||||
const y1 = locs_y[j];
|
||||
const x2 = locs_x[i] + input_size;
|
||||
const y2 = locs_y[j] + input_size;
|
||||
const tile = new Img(input_size, input_size);
|
||||
tile.getImageCrop(0, 0, inputImg, x1, y1, x2, y2);
|
||||
let scaled;
|
||||
for (let k = 4; k < tile.data.length; k += 4) {
|
||||
if (tile.data[k + 3] !== tile.data[3]) {
|
||||
useModel[current] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (useModel[current]) {
|
||||
current++;
|
||||
continue;
|
||||
}
|
||||
scaled = new Img(tile.width * factor, tile.height * factor);
|
||||
for (let k = 0; k < scaled.data.length; k += 4) {
|
||||
scaled.data[k] = tile.data[3];
|
||||
scaled.data[k + 1] = tile.data[3];
|
||||
scaled.data[k + 2] = tile.data[3];
|
||||
}
|
||||
output.getImageCrop(
|
||||
(x1 + pad_left[i]) * factor,
|
||||
(y1 + pad_top[j]) * factor,
|
||||
scaled,
|
||||
pad_left[i] * factor,
|
||||
pad_top[j] * factor,
|
||||
scaled.width - pad_right[i] * factor,
|
||||
scaled.height - pad_bottom[j] * factor
|
||||
);
|
||||
// console.log(i, j, x2 - x1, y2 - y1);
|
||||
current++;
|
||||
}
|
||||
}
|
||||
current = 0;
|
||||
for (let i = 0; i < num_x; i++) {
|
||||
for (let j = 0; j < num_y; j++) {
|
||||
if (!useModel[current]) {
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
continue;
|
||||
}
|
||||
const x1 = locs_x[i];
|
||||
const y1 = locs_y[j];
|
||||
const x2 = locs_x[i] + input_size;
|
||||
const y2 = locs_y[j] + input_size;
|
||||
const tile = new Img(input_size, input_size);
|
||||
tile.getImageCrop(0, 0, inputImg, x1, y1, x2, y2);
|
||||
let scaled = await upscale(tile, model);
|
||||
output.getImageCrop(
|
||||
(x1 + pad_left[i]) * factor,
|
||||
(y1 + pad_top[j]) * factor,
|
||||
scaled,
|
||||
pad_left[i] * factor,
|
||||
pad_top[j] * factor,
|
||||
scaled.width - pad_right[i] * factor,
|
||||
scaled.height - pad_bottom[j] * factor
|
||||
);
|
||||
// console.log(i, j, x2 - x1, y2 - y1);
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < num_x; i++) {
|
||||
for (let j = 0; j < num_y; j++) {
|
||||
const x1 = locs_x[i];
|
||||
const y1 = locs_y[j];
|
||||
const x2 = locs_x[i] + input_size;
|
||||
const y2 = locs_y[j] + input_size;
|
||||
const tile = new Img(input_size, input_size);
|
||||
tile.getImageCrop(0, 0, inputImg, x1, y1, x2, y2);
|
||||
let scaled = await upscale(tile, model);
|
||||
output.getImageCrop(
|
||||
(x1 + pad_left[i]) * factor,
|
||||
(y1 + pad_top[j]) * factor,
|
||||
scaled,
|
||||
pad_left[i] * factor,
|
||||
pad_top[j] * factor,
|
||||
scaled.width - pad_right[i] * factor,
|
||||
scaled.height - pad_bottom[j] * factor
|
||||
);
|
||||
// console.log(i, j, x2 - x1, y2 - y1);
|
||||
current++;
|
||||
let progress = (current / total) * 100;
|
||||
sendprogress(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
let factor = data?.factor || 4;
|
||||
const start = Date.now();
|
||||
let output;
|
||||
try {
|
||||
if (data?.fixed) {
|
||||
output = await enlargeImageWithFixedInput(model, input, factor);
|
||||
} else {
|
||||
output = await enlargeImage(model, input, factor);
|
||||
}
|
||||
} catch (e) {
|
||||
postMessage({ alertmsg: e.toString() });
|
||||
}
|
||||
const end = Date.now();
|
||||
console.log("Time:", end - start);
|
||||
postMessage({
|
||||
progress: 100,
|
||||
done: true,
|
||||
output: output,
|
||||
info: `Processing image...`,
|
||||
});
|
||||
});
|
||||
14
tsconfig.app.json
Normal file
14
tsconfig.app.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
11
tsconfig.json
Normal file
11
tsconfig.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
19
tsconfig.node.json
Normal file
19
tsconfig.node.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"]
|
||||
}
|
||||
}
|
||||
16
vite.config.ts
Normal file
16
vite.config.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user