Files
web-realesrgan/src/App.vue
2024-04-15 08:41:15 -04:00

764 lines
25 KiB
Vue

<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>
<canvas ref="imgCanvas" style="display: none"></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";
import Module from "./imghelper";
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",
}),
wasmModule: null,
};
},
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";
(async () => {
await Module();
})();
},
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;
let wasmModule = await Module();
this.wasmModule = wasmModule;
const imgCanvas = this.$refs.imgCanvas;
imgCanvas.width = this.img.width;
imgCanvas.height = this.img.height;
const imgCtx = imgCanvas.getContext("2d");
imgCtx.drawImage(this.img, 0, 0);
let data = imgCtx.getImageData(
0,
0,
this.img.width,
this.img.height
).data;
this.input = new Img(this.img.width, this.img.height, data);
const numPixels = this.input.width * this.input.height;
const bytesPerImage = numPixels * 4;
let sourcePtr = wasmModule._malloc(bytesPerImage);
let targetPtr = wasmModule._malloc(bytesPerImage);
wasmModule.HEAPU8.set(this.input.data, sourcePtr);
this.hasAlpha = wasmModule._check_alpha(sourcePtr, numPixels);
if (this.hasAlpha) {
this.inputAlpha = new Img(this.img.width, this.img.height);
wasmModule._copy_alpha_to_rgb(sourcePtr, targetPtr, numPixels);
this.inputAlpha.data.set(
wasmModule.HEAPU8.subarray(targetPtr, targetPtr + bytesPerImage)
);
}
wasmModule._free(sourcePtr);
wasmModule._free(targetPtr);
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
);
}
},
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;
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)) {
let factor = this.modelzoo[this.model].factor;
this.output = new Img(
factor * this.input.width,
factor * this.input.height,
new Uint8ClampedArray(output)
);
}
this.info = "Processing Image...";
if (this.inputAlpha) {
worker.postMessage(
{
input: this.inputAlpha.data.buffer,
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.data.buffer]
);
this.inputAlpha = null;
return;
}
if (this.hasAlpha) {
let outputArray = new Uint8Array(output);
let wasmModule = this.wasmModule;
let sourcePtr = wasmModule._malloc(outputArray.length);
let targetPtr = wasmModule._malloc(outputArray.length);
let numPixels = outputArray.length / 4;
wasmModule.HEAPU8.set(outputArray, sourcePtr);
wasmModule.HEAPU8.set(this.output.data, targetPtr);
wasmModule._copy_alpha_channel(sourcePtr, targetPtr, numPixels);
this.output.data.set(
wasmModule.HEAPU8.subarray(
targetPtr,
targetPtr + outputArray.length
)
);
wasmModule._free(sourcePtr);
wasmModule._free(targetPtr);
wasmModule = null;
this.wasmModule = null;
}
const imgCanvas = this.$refs.imgCanvas;
const imgCtx = imgCanvas.getContext("2d");
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
imgCanvas.width = this.output.width;
imgCanvas.height = this.output.height;
let outImg = imgCtx.createImageData(
this.output.width,
this.output.height
);
outImg.data.set(this.output.data);
this.input = null;
this.inputAlpha = null;
this.output = null;
imgCtx.putImageData(outImg, 0, 0);
let type = "image/jpeg";
let quality = 0.92;
if (this.hasAlpha) type = "image/png";
imgCanvas.toBlob(
(blob) => {
this.processedImg.src = URL.createObjectURL(blob);
},
type,
quality
);
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.buffer,
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,
},
[this.input.data.buffer]
);
},
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>