853 lines
23 KiB
Vue
853 lines
23 KiB
Vue
<template>
|
|
<div class="enhancer-modal-container">
|
|
<AFlex class="enhancer-modal-content" :vertical="false" align="center" justify="space-between">
|
|
<!-- 左侧控制面板 -->
|
|
<div class="enhancer-modal-left">
|
|
<ACard class="enhancer-modal-left-container" :bordered="false">
|
|
<!-- 上传区域 -->
|
|
<div class="enhancer-modal-upload">
|
|
<Spin :spinning="enhancer.uploading" indicator="magic-ring">
|
|
<AUploadDragger
|
|
name="image"
|
|
accept="image/*"
|
|
:multiple="false"
|
|
:directory="false"
|
|
:maxCount="1"
|
|
:beforeUpload="enhancer.beforeUpload"
|
|
:custom-request="enhancer.customUploadRequest"
|
|
:disabled="enhancer.uploading || enhancer.isProcessing"
|
|
:showUploadList="false">
|
|
<div class="enhancer-modal-upload-content">
|
|
<ABadge :offset="[-10, 10]">
|
|
<template #count>
|
|
<AAvatar :size="22" :src="successIcon" v-if="enhancer.imageData"/>
|
|
<AAvatar :size="22" :src="warnIcon" v-if="!enhancer.imageData"/>
|
|
</template>
|
|
<AAvatar shape="square" :size="60" :src="fileIcon"/>
|
|
</ABadge>
|
|
<span class="enhancer-modal-upload-text">
|
|
点击或拖拽上传图片
|
|
</span>
|
|
</div>
|
|
</AUploadDragger>
|
|
</Spin>
|
|
</div>
|
|
|
|
<!-- 功能选择区 -->
|
|
<ADivider orientation="center" :plain="true">
|
|
<span class="enhancer-modal-divider-title">增强功能</span>
|
|
</ADivider>
|
|
|
|
<div class="enhancer-modal-function-selector">
|
|
<ARadioGroup v-model:value="enhancer.selectedFunction" button-style="solid" size="small"
|
|
style="width: 100%">
|
|
<ARadioButton value="upscale">图像升级</ARadioButton>
|
|
<ARadioButton value="deblur">去模糊</ARadioButton>
|
|
<ARadioButton value="denoise">去噪</ARadioButton>
|
|
<ARadioButton value="lowlight">弱光增强</ARadioButton>
|
|
</ARadioGroup>
|
|
</div>
|
|
|
|
<!-- 参数设置区 -->
|
|
<ADivider orientation="center" :plain="true">
|
|
<span class="enhancer-modal-divider-title">参数设置</span>
|
|
</ADivider>
|
|
|
|
<!-- 图像升级参数 -->
|
|
<div v-if="enhancer.selectedFunction === 'upscale'" class="enhancer-modal-params">
|
|
<div class="enhancer-modal-params-item">
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">模型:</span>
|
|
<ASelect style="width: 100%" size="middle"
|
|
v-model:value="enhancer.upscaleParams.model"
|
|
:options="enhancer.upscaleModels">
|
|
</ASelect>
|
|
</div>
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">比例:</span>
|
|
<ASelect style="width: 100%" size="middle"
|
|
v-model:value="enhancer.upscaleParams.scale"
|
|
:options="enhancer.upscaleScales">
|
|
</ASelect>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 去模糊参数 -->
|
|
<div v-if="enhancer.selectedFunction === 'deblur'" class="enhancer-modal-params">
|
|
<div class="enhancer-modal-params-item">
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">强度:</span>
|
|
<ASlider v-model:value="enhancer.deblurParams.strength" :min="0" :max="100" :step="1"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 去噪参数 -->
|
|
<div v-if="enhancer.selectedFunction === 'denoise'" class="enhancer-modal-params">
|
|
<div class="enhancer-modal-params-item">
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">强度:</span>
|
|
<ASlider v-model:value="enhancer.denoiseParams.strength" :min="0" :max="100" :step="1"/>
|
|
</div>
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">保留细节:</span>
|
|
<ASlider v-model:value="enhancer.denoiseParams.preserveDetail" :min="0" :max="100" :step="1"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 弱光增强参数 -->
|
|
<div v-if="enhancer.selectedFunction === 'lowlight'" class="enhancer-modal-params">
|
|
<div class="enhancer-modal-params-item">
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">亮度:</span>
|
|
<ASlider v-model:value="enhancer.lowlightParams.brightness" :min="0" :max="100" :step="1"/>
|
|
</div>
|
|
<div class="enhancer-modal-params-item-content">
|
|
<span class="enhancer-modal-params-title">对比度:</span>
|
|
<ASlider v-model:value="enhancer.lowlightParams.contrast" :min="0" :max="100" :step="1"/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 处理按钮 -->
|
|
<ADivider></ADivider>
|
|
<AButton style="width: 100%;" size="middle" shape="default" type="primary" :loading="enhancer.isProcessing"
|
|
:disabled="!enhancer.imageData"
|
|
@click="startEnhance">
|
|
<template #icon>
|
|
<AAvatar shape="square" :size="20" :src="runIcon"/>
|
|
</template>
|
|
<span class="enhancer-modal-params-btn">开始处理</span>
|
|
</AButton>
|
|
</ACard>
|
|
</div>
|
|
|
|
<!-- 右侧图像预览区 -->
|
|
<div class="enhancer-modal-right">
|
|
<div
|
|
ref="canvasContainer"
|
|
class="canvas-container"
|
|
@mousedown="startDragging"
|
|
@mouseup="stopDragging"
|
|
@mouseleave="stopDragging"
|
|
@mousemove="dragImage"
|
|
@wheel="resizeImage"
|
|
@touchstart="touchStart"
|
|
@touchmove="touchMove"
|
|
@touchend="touchEnd"
|
|
>
|
|
<!-- 进度条 -->
|
|
<div class="canvas-progressbar">
|
|
<span class="canvas-progressbar-text">
|
|
{{ enhancer.msg }}
|
|
</span>
|
|
<AProgress
|
|
v-if="enhancer.isProcessing"
|
|
:stroke-color="{
|
|
'0%': '#108ee9',
|
|
'100%': '#87d068',}"
|
|
:percent="enhancer.progressBar"
|
|
:showInfo="false"
|
|
status="active"
|
|
/>
|
|
</div>
|
|
|
|
<!-- 图片 -->
|
|
<canvas ref="canvas"></canvas>
|
|
|
|
<!-- 拖动条 -->
|
|
<div
|
|
class="dragLine"
|
|
v-if="enhancer.isDone"
|
|
ref="dragLine">
|
|
<div class="dragBall"
|
|
@mousedown.stop="startDraggingLine"
|
|
@mousemove.stop="dragLineFn"
|
|
@mouseup.stop="stopDraggingLine"
|
|
>
|
|
<svg width="24" 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 class="floating-menu" @mousedown.stop v-if="enhancer.isDone && enhancer.processedImg">
|
|
<AFlex :vertical="false" align="center" justify="space-between" :gap="8">
|
|
<ATooltip placement="top" title="下载图片">
|
|
<AButton type="text" size="small" @click="downloadImage" class="menu-btn">
|
|
<template #icon>
|
|
<AAvatar :src="downloadIcon" class="menu-icon" :size="20"/>
|
|
</template>
|
|
</AButton>
|
|
</ATooltip>
|
|
<ATooltip placement="top" title="保存图片">
|
|
<AButton type="text" size="small" class="menu-btn" @click="saveImage">
|
|
<template #icon>
|
|
<AAvatar :src="saveIcon" :size="20" class="menu-icon"/>
|
|
</template>
|
|
</AButton>
|
|
</ATooltip>
|
|
<ATooltip placement="top" title="删除图片">
|
|
<AButton type="text" size="small" danger class="menu-btn" @click="deleteImage">
|
|
<template #icon>
|
|
<AAvatar :src="deleteIcon" :size="20" class="menu-icon"/>
|
|
</template>
|
|
</AButton>
|
|
</ATooltip>
|
|
</AFlex>
|
|
</div>
|
|
|
|
<!-- 图片信息 -->
|
|
<div class="image-info">
|
|
<ATag color="cyan" :bordered="false" v-if="enhancer.imageData">原图: {{ originalImageSize }}</ATag>
|
|
<ATag color="purple" :bordered="false" v-if="enhancer.processedImg">处理后: {{ processedImageSize }}</ATag>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</AFlex>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, reactive, computed, onMounted, onUnmounted, defineEmits } from 'vue';
|
|
import { message } from "ant-design-vue";
|
|
import Spin from "@/components/MyUI/Spin/Spin.vue";
|
|
import Upscaler from 'upscaler';
|
|
|
|
// 图标导入
|
|
import fileIcon from "@/assets/svgs/file.svg";
|
|
import successIcon from '@/assets/svgs/success.svg';
|
|
import warnIcon from '@/assets/svgs/warn.svg';
|
|
import runIcon from '@/assets/svgs/run.svg';
|
|
import downloadIcon from '@/assets/svgs/download.svg';
|
|
import saveIcon from '@/assets/svgs/save.svg';
|
|
import deleteIcon from '@/assets/svgs/deleted.svg';
|
|
|
|
// 定义事件
|
|
const emit = defineEmits(['save-image', 'close']);
|
|
|
|
// DOM引用
|
|
const canvasContainer = ref<HTMLDivElement | null>(null);
|
|
const canvas = ref<HTMLCanvasElement | null>(null);
|
|
const dragLine = ref<HTMLDivElement | null>(null);
|
|
|
|
// 图像处理实例
|
|
let upscaler: any = null;
|
|
|
|
// 状态管理
|
|
const enhancer = reactive({
|
|
// 基本状态
|
|
uploading: false,
|
|
isProcessing: false,
|
|
isDone: false,
|
|
msg: "",
|
|
progressBar: 0,
|
|
|
|
// 图像数据
|
|
imageData: "",
|
|
fileData: "",
|
|
processedImg: "",
|
|
input: null as any,
|
|
|
|
// 拖拽状态
|
|
dragging: false,
|
|
linePosition: 0,
|
|
draggingLine: false,
|
|
|
|
// 功能选择
|
|
selectedFunction: "upscale",
|
|
|
|
// 图像升级参数
|
|
upscaleParams: {
|
|
model: "x4",
|
|
scale: 4
|
|
},
|
|
upscaleModels: [
|
|
{label: "通用模型 x2", value: "x2"},
|
|
{label: "通用模型 x4", value: "x4"},
|
|
{label: "照片增强", value: "photo"},
|
|
{label: "动漫风格", value: "anime"}
|
|
],
|
|
upscaleScales: [
|
|
{label: "2x", value: 2},
|
|
{label: "4x", value: 4}
|
|
],
|
|
|
|
// 去模糊参数
|
|
deblurParams: {
|
|
strength: 50,
|
|
radius: 5
|
|
},
|
|
|
|
// 去噪参数
|
|
denoiseParams: {
|
|
strength: 50,
|
|
preserveDetail: 70
|
|
},
|
|
|
|
// 弱光增强参数
|
|
lowlightParams: {
|
|
brightness: 60,
|
|
contrast: 50
|
|
},
|
|
|
|
// 图片上传前的校验
|
|
async beforeUpload(file: File) {
|
|
enhancer.uploading = true;
|
|
const urlData = URL.createObjectURL(file);
|
|
const image = new Image();
|
|
image.src = urlData;
|
|
|
|
// 等待图片加载完成
|
|
await new Promise(resolve => {
|
|
image.onload = resolve;
|
|
});
|
|
|
|
await clear();
|
|
enhancer.fileData = urlData;
|
|
enhancer.uploading = false;
|
|
return true;
|
|
},
|
|
|
|
// 自定义上传图片请求
|
|
async customUploadRequest(_file: any) {
|
|
enhancer.imageData = enhancer.fileData;
|
|
await loadImage();
|
|
}
|
|
});
|
|
|
|
// 图片尺寸信息
|
|
const originalImageSize = computed(() => {
|
|
if (!enhancer.imageData) return "";
|
|
const img = new Image();
|
|
img.src = enhancer.imageData;
|
|
return `${img.width}x${img.height}`;
|
|
});
|
|
|
|
const processedImageSize = computed(() => {
|
|
if (!enhancer.processedImg) return "";
|
|
const img = new Image();
|
|
img.src = enhancer.processedImg;
|
|
return `${img.width}x${img.height}`;
|
|
});
|
|
|
|
// 清空数据
|
|
async function clear() {
|
|
enhancer.imageData = "";
|
|
enhancer.processedImg = "";
|
|
enhancer.isDone = false;
|
|
enhancer.msg = "";
|
|
enhancer.progressBar = 0;
|
|
enhancer.isProcessing = false;
|
|
enhancer.dragging = false;
|
|
enhancer.linePosition = 0;
|
|
enhancer.draggingLine = false;
|
|
enhancer.input = null;
|
|
}
|
|
|
|
// 加载图片
|
|
async function loadImage() {
|
|
if (!canvas.value || !enhancer.imageData) return;
|
|
|
|
const ctx = canvas.value.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
const img = new Image();
|
|
img.src = enhancer.imageData;
|
|
|
|
await new Promise(resolve => {
|
|
img.onload = resolve;
|
|
});
|
|
|
|
canvas.value.width = img.width;
|
|
canvas.value.height = img.height;
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
// 初始化拖动线位置
|
|
enhancer.linePosition = img.width / 2;
|
|
|
|
// 初始化Upscaler
|
|
if (!upscaler) {
|
|
upscaler = new Upscaler();
|
|
}
|
|
}
|
|
|
|
// 开始图像增强处理
|
|
async function startEnhance() {
|
|
if (!enhancer.imageData || !canvas.value) {
|
|
message.warning("请先上传图片");
|
|
return;
|
|
}
|
|
|
|
enhancer.isProcessing = true;
|
|
enhancer.msg = "正在处理图片...";
|
|
const start = Date.now();
|
|
|
|
try {
|
|
const img = new Image();
|
|
img.src = enhancer.imageData;
|
|
|
|
await new Promise(resolve => {
|
|
img.onload = resolve;
|
|
});
|
|
|
|
let processedImage;
|
|
|
|
// 根据选择的功能进行不同的处理
|
|
switch (enhancer.selectedFunction) {
|
|
case 'upscale':
|
|
enhancer.msg = "正在进行图像升级...";
|
|
processedImage = await upscaler.upscale(img, {
|
|
model: enhancer.upscaleParams.model,
|
|
scale: enhancer.upscaleParams.scale,
|
|
progress: (progress: number) => {
|
|
enhancer.progressBar = Math.round(progress * 100);
|
|
}
|
|
});
|
|
break;
|
|
|
|
case 'deblur':
|
|
enhancer.msg = "正在进行去模糊处理...";
|
|
// 使用upscaler的去模糊功能
|
|
processedImage = await simulateImageProcessing(img, 'deblur');
|
|
break;
|
|
|
|
case 'denoise':
|
|
enhancer.msg = "正在进行去噪处理...";
|
|
processedImage = await simulateImageProcessing(img, 'denoise');
|
|
break;
|
|
|
|
case 'lowlight':
|
|
enhancer.msg = "正在进行弱光增强...";
|
|
processedImage = await simulateImageProcessing(img, 'lowlight');
|
|
break;
|
|
}
|
|
|
|
if (processedImage) {
|
|
enhancer.processedImg = processedImage.src || processedImage;
|
|
enhancer.isDone = true;
|
|
enhancer.msg = `处理完成! 用时: ${((Date.now() - start) / 1000).toFixed(2)}秒`;
|
|
|
|
// 更新画布显示处理后的图片
|
|
updateCanvasWithProcessedImage();
|
|
}
|
|
} catch (error) {
|
|
console.error("图像处理失败", error);
|
|
message.error("图像处理失败,请重试");
|
|
enhancer.msg = "处理失败";
|
|
} finally {
|
|
enhancer.isProcessing = false;
|
|
}
|
|
}
|
|
|
|
// 模拟图像处理(在实际实现中应替换为真实的处理逻辑)
|
|
async function simulateImageProcessing(img: HTMLImageElement, _type: string) {
|
|
// 模拟进度
|
|
let progress = 0;
|
|
const interval = setInterval(() => {
|
|
progress += Math.random() * 5;
|
|
enhancer.progressBar = Math.min(Math.round(progress), 99);
|
|
if (progress >= 100) {
|
|
clearInterval(interval);
|
|
}
|
|
}, 200);
|
|
|
|
// 这里应该是实际的图像处理逻辑
|
|
// 目前使用upscaler的基本功能模拟其他处理效果
|
|
try {
|
|
const result = await upscaler.upscale(img, {
|
|
model: 'x2',
|
|
scale: 1.5,
|
|
progress: (p: number) => {
|
|
enhancer.progressBar = Math.round(p * 100);
|
|
}
|
|
});
|
|
clearInterval(interval);
|
|
return result;
|
|
} catch (error) {
|
|
clearInterval(interval);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// 更新画布显示处理后的图片
|
|
function updateCanvasWithProcessedImage() {
|
|
if (!canvas.value || !enhancer.processedImg) return;
|
|
|
|
const ctx = canvas.value.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
const img = new Image();
|
|
img.src = enhancer.processedImg;
|
|
|
|
img.onload = () => {
|
|
canvas.value!.width = img.width;
|
|
canvas.value!.height = img.height;
|
|
ctx.drawImage(img, 0, 0);
|
|
|
|
// 重置拖动线位置
|
|
enhancer.linePosition = img.width / 2;
|
|
|
|
// 绘制对比效果
|
|
drawComparisonView();
|
|
};
|
|
}
|
|
|
|
// 绘制对比视图
|
|
function drawComparisonView() {
|
|
if (!canvas.value || !enhancer.imageData || !enhancer.processedImg || !enhancer.isDone) return;
|
|
|
|
const ctx = canvas.value.getContext('2d');
|
|
if (!ctx) return;
|
|
|
|
const originalImg = new Image();
|
|
originalImg.src = enhancer.imageData;
|
|
|
|
const processedImg = new Image();
|
|
processedImg.src = enhancer.processedImg;
|
|
|
|
originalImg.onload = () => {
|
|
processedImg.onload = () => {
|
|
// 清除画布
|
|
ctx.clearRect(0, 0, canvas.value!.width, canvas.value!.height);
|
|
|
|
// 绘制处理后的图片
|
|
ctx.drawImage(processedImg, 0, 0, canvas.value!.width, canvas.value!.height);
|
|
|
|
// 绘制原图(左侧部分)
|
|
ctx.drawImage(
|
|
originalImg,
|
|
0, 0, originalImg.width, originalImg.height,
|
|
0, 0, enhancer.linePosition, canvas.value!.height
|
|
);
|
|
|
|
// 绘制分割线
|
|
ctx.beginPath();
|
|
ctx.moveTo(enhancer.linePosition, 0);
|
|
ctx.lineTo(enhancer.linePosition, canvas.value!.height);
|
|
ctx.strokeStyle = '#ffffff';
|
|
ctx.lineWidth = 2;
|
|
ctx.stroke();
|
|
};
|
|
};
|
|
}
|
|
|
|
// 拖动相关函数
|
|
function startDragging(_e: MouseEvent) {
|
|
enhancer.dragging = true;
|
|
}
|
|
|
|
function stopDragging() {
|
|
enhancer.dragging = false;
|
|
}
|
|
|
|
function dragImage(e: MouseEvent) {
|
|
if (!enhancer.dragging || !canvasContainer.value) return;
|
|
|
|
if (enhancer.isDone) {
|
|
// 更新拖动线位置
|
|
const rect = canvasContainer.value.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
enhancer.linePosition = Math.max(0, Math.min(canvas.value?.width || 0, x));
|
|
drawComparisonView();
|
|
}
|
|
}
|
|
|
|
function startDraggingLine(e: MouseEvent) {
|
|
e.preventDefault();
|
|
enhancer.draggingLine = true;
|
|
}
|
|
|
|
function stopDraggingLine() {
|
|
enhancer.draggingLine = false;
|
|
}
|
|
|
|
function dragLineFn(e: MouseEvent) {
|
|
if (!enhancer.draggingLine || !canvas.value || !canvasContainer.value) return;
|
|
|
|
const rect = canvasContainer.value.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
|
|
// 限制拖动范围在画布内
|
|
enhancer.linePosition = Math.max(0, Math.min(canvas.value.width, x));
|
|
|
|
// 重新绘制对比视图
|
|
drawComparisonView();
|
|
}
|
|
|
|
// 缩放相关函数
|
|
function resizeImage(e: WheelEvent) {
|
|
e.preventDefault();
|
|
// 实现简单的缩放功能
|
|
if (!canvas.value || !enhancer.isDone) return;
|
|
|
|
// 这里可以实现缩放逻辑
|
|
}
|
|
|
|
// 触摸事件处理
|
|
function touchStart(e: TouchEvent) {
|
|
if (e.touches.length === 1) {
|
|
enhancer.dragging = true;
|
|
}
|
|
}
|
|
|
|
function touchMove(e: TouchEvent) {
|
|
if (!enhancer.dragging || !canvasContainer.value || e.touches.length !== 1) return;
|
|
|
|
const touch = e.touches[0];
|
|
const rect = canvasContainer.value.getBoundingClientRect();
|
|
const x = touch.clientX - rect.left;
|
|
|
|
if (enhancer.isDone) {
|
|
// 更新拖动线位置
|
|
enhancer.linePosition = Math.max(0, Math.min(canvas.value?.width || 0, x));
|
|
drawComparisonView();
|
|
}
|
|
}
|
|
|
|
function touchEnd() {
|
|
enhancer.dragging = false;
|
|
}
|
|
|
|
// 下载图片
|
|
function downloadImage() {
|
|
if (!enhancer.processedImg) return;
|
|
|
|
const link = document.createElement('a');
|
|
link.href = enhancer.processedImg;
|
|
link.download = `enhanced_image_${Date.now()}.png`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
message.success('图片下载成功');
|
|
}
|
|
|
|
// 保存图片
|
|
function saveImage() {
|
|
if (!enhancer.processedImg) return;
|
|
emit('save-image', enhancer.processedImg);
|
|
message.success('图片已保存');
|
|
}
|
|
|
|
// 删除图片
|
|
function deleteImage() {
|
|
clear();
|
|
message.success('图片已删除');
|
|
}
|
|
|
|
// 组件挂载和卸载
|
|
onMounted(() => {
|
|
// 初始化Upscaler
|
|
upscaler = new Upscaler();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
// 清理资源
|
|
if (enhancer.processedImg) {
|
|
URL.revokeObjectURL(enhancer.processedImg);
|
|
}
|
|
if (enhancer.imageData) {
|
|
URL.revokeObjectURL(enhancer.imageData);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="scss">
|
|
.enhancer-modal-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
padding: 10px;
|
|
|
|
.enhancer-modal-content {
|
|
width: 100%;
|
|
height: 100%;
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 15px;
|
|
|
|
.enhancer-modal-left {
|
|
width: 35%;
|
|
height: 100%;
|
|
|
|
.enhancer-modal-left-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: auto;
|
|
border-radius: 8px;
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
backdrop-filter: blur(10px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
padding: 15px;
|
|
|
|
.enhancer-modal-divider-title {
|
|
font-size: 13px;
|
|
color: rgba(80, 80, 90, 0.9);
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
}
|
|
|
|
.enhancer-modal-right {
|
|
width: 65%;
|
|
height: 100%;
|
|
border-radius: 8px;
|
|
overflow: hidden;
|
|
background-color: rgba(245, 245, 245, 0.5);
|
|
backdrop-filter: blur(5px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
}
|
|
}
|
|
}
|
|
|
|
.enhancer-modal-upload {
|
|
width: 100%;
|
|
margin-bottom: 15px;
|
|
|
|
.enhancer-modal-upload-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px 0;
|
|
gap: 10px;
|
|
|
|
.enhancer-modal-upload-text {
|
|
font-size: 14px;
|
|
color: rgba(80, 80, 90, 0.8);
|
|
margin-top: 8px;
|
|
}
|
|
}
|
|
}
|
|
|
|
.enhancer-modal-function-selector {
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.enhancer-modal-params {
|
|
margin-bottom: 15px;
|
|
|
|
.enhancer-modal-params-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
|
|
.enhancer-modal-params-item-content {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
|
|
.enhancer-modal-params-title {
|
|
font-size: 13px;
|
|
color: rgba(80, 80, 90, 0.9);
|
|
margin-bottom: 2px;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.enhancer-modal-params-btn {
|
|
margin-left: 5px;
|
|
}
|
|
|
|
.canvas-container {
|
|
width: 100%;
|
|
height: 100%;
|
|
position: relative;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background-color: rgba(0, 0, 0, 0.02);
|
|
border-radius: 8px;
|
|
|
|
canvas {
|
|
max-width: 100%;
|
|
max-height: 100%;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.canvas-progressbar {
|
|
position: absolute;
|
|
top: 10px;
|
|
left: 10px;
|
|
right: 10px;
|
|
z-index: 10;
|
|
background-color: rgba(255, 255, 255, 0.7);
|
|
padding: 8px;
|
|
border-radius: 6px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
|
.canvas-progressbar-text {
|
|
font-size: 13px;
|
|
color: rgba(80, 80, 90, 0.9);
|
|
margin-bottom: 5px;
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
.dragLine {
|
|
position: absolute;
|
|
top: 0;
|
|
bottom: 0;
|
|
width: 2px;
|
|
background-color: #ffffff;
|
|
z-index: 5;
|
|
|
|
.dragBall {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
cursor: ew-resize;
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
border-radius: 50%;
|
|
padding: 2px;
|
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
|
|
}
|
|
}
|
|
|
|
.floating-menu {
|
|
position: absolute;
|
|
bottom: 15px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background-color: rgba(255, 255, 255, 0.8);
|
|
backdrop-filter: blur(5px);
|
|
border-radius: 8px;
|
|
padding: 5px 10px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
z-index: 10;
|
|
|
|
.menu-btn {
|
|
margin: 0 2px;
|
|
}
|
|
|
|
.menu-icon {
|
|
opacity: 0.8;
|
|
transition: all 0.2s;
|
|
|
|
&:hover {
|
|
opacity: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
.image-info {
|
|
position: absolute;
|
|
top: 10px;
|
|
right: 10px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 5px;
|
|
z-index: 10;
|
|
}
|
|
}
|
|
</style>
|