V 3.0.1 LaiTool V3.0.1-preview.3

This commit is contained in:
2024-08-08 16:24:47 +08:00
parent f3a25e9474
commit 36178fbe5d
40 changed files with 1545 additions and 581 deletions
+1 -1
View File
@@ -6,7 +6,7 @@
<n-message-provider>
<n-dialog-provider>
<n-notification-provider>
<n-spin style="z-index: 100;" :show="softwareStore.spin.spinning">
<n-spin style="z-index: 1000;" :show="softwareStore.spin.spinning">
<RouterView></RouterView>
<template #description> {{ softwareStore.spin.tip }} </template>
</n-spin>
@@ -0,0 +1,32 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 32 32"
>
<path
d="M8 9H4a2 2 0 0 0-2 2v12h2v-5h4v5h2V11a2 2 0 0 0-2-2zm-4 7v-5h4v5z"
fill="currentColor"
></path>
<path d="M22 11h3v10h-3v2h8v-2h-3V11h3V9h-8v2z" fill="currentColor"></path>
<path
d="M14 23h-2V9h6a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-4zm0-7h4v-5h-4z"
fill="currentColor"
></path>
</svg>
</template>
<script>
import { ref, onMounted, defineComponent, onUnmounted, toRaw, watch } from 'vue'
import { useMessage } from 'naive-ui'
export default defineComponent({
components: {},
setup() {
onMounted(async () => {})
return {}
}
})
</script>
@@ -0,0 +1,16 @@
<template>
<div style="width: 100%; height: 100%">
<webview style="width: 100%; height: 100%" src="https://api.laitool.cc" partition="persist:your-session-name"></webview>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
components: {},
setup() {
return {}
}
})
</script>
@@ -74,7 +74,12 @@
>
</n-form-item>
<n-form-item label="背景音乐" path="srtPath">
<n-select v-model:value="reverseManageStore.selectBook.backgroundMusic" :options="backgroundMusicOptions" placeholder="请选择背景音乐"> </n-select>
<n-select
v-model:value="reverseManageStore.selectBook.backgroundMusic"
:options="backgroundMusicOptions"
placeholder="请选择背景音乐"
>
</n-select>
</n-form-item>
<n-form-item label="小说项目文件夹" path="bookFolderPath">
<n-input
@@ -99,7 +104,7 @@
>
</n-form-item>
<div style="margin-left: 10px; display: flex; justify-content: flex-end">
<n-button type="success" @click="AddOrModifyBook">{{
<n-button :loading="loading" type="success" @click="AddOrModifyBook">{{
type == 'add' ? '添加' : '保存'
}}</n-button>
</div>
@@ -113,6 +118,7 @@ import { useMessage, NButton, NForm, NFormItem, NInput, NSelect, NIcon } from 'n
import { useReverseManageStore } from '../../../../../stores/reverseManage.ts'
import { CloseSharp } from '@vicons/ionicons5'
import { cloneDeep } from 'lodash'
import { useSoftwareStore } from '../../../../../stores/software'
export default defineComponent({
components: {
@@ -128,7 +134,9 @@ export default defineComponent({
setup(props) {
let message = useMessage()
let reverseManageStore = useReverseManageStore()
let softwareStore = useSoftwareStore()
let bookRef = ref(null)
let loading = ref(false)
let backgroundMusicOptions = ref([])
// let data = ref(reverseManageStore.selectBook)
let type = ref(props.type ? props.type : 'add')
@@ -220,7 +228,9 @@ export default defineComponent({
return
}
debugger
loading.value = true
let res = await reverseManageStore.SaveSelectBook(cloneDeep(reverseManageStore.selectBook))
loading.value = false
// 判断当前的数据是不是有ID
if (res.code == 0) {
message.error(res.message)
@@ -279,8 +289,9 @@ export default defineComponent({
SelectMP4File,
AddOrModifyBook,
bookRef,
softwareStore,
OpenFolder,
backgroundMusicOptions
backgroundMusicOptions,loading
}
}
})
@@ -9,7 +9,7 @@
</n-dropdown>
<n-divider vertical style="height: 30px" />
<n-dropdown trigger="hover" :options="frameOptions" @select="handleSelect">
<n-button :color="softwareStore.SoftColor.ORANGE" @click=""> 开始分镜 </n-button>
<n-button :color="softwareStore.SoftColor.ORANGE" @click="ActionFrame"> 开始分镜 </n-button>
</n-dropdown>
<n-dropdown trigger="hover" :options="copywritingOptions" @select="handleSelect">
<n-button
@@ -102,18 +102,20 @@ import VideoCanvas from '../../VideoSubtitle/VideoCanvas.vue'
import { SubtitleSavePositionType } from '../../../../../define/enum/waterMarkAndSubtitle'
import { DEFINE_STRING } from '../../../../../define/define_string'
import Setting from './Setting.vue'
import { BookType } from '../../../../../define/enum/bookEnum'
import { BookType, OperateBookType } from '../../../../../define/enum/bookEnum'
import TranslateSetting from '../../Setting/TranslateSetting.vue'
import { TranslateType } from '../../../../../define/enum/translate'
import { ContainsChineseOrPunctuation } from '../../../../../define/Tools/common'
import ImportWordAndSrt from '../../Original/Components/ImportWordAndSrt.vue'
import GetWaterMaskRectangle from '../../Watermark/GetWaterMaskRectangle.vue'
export default defineComponent({
components: {
NButton,
NCheckbox,
NDropdown,
NDivider
NDivider,
GetWaterMaskRectangle
},
setup() {
@@ -268,7 +270,10 @@ export default defineComponent({
})
return
}
let res_frame = await window.book.RemoveWatermark(reverseManageStore.selectBook.id)
let res_frame = await window.book.RemoveWatermark(
reverseManageStore.selectBookTask.id,
OperateBookType.BOOKTASK
)
softwareStore.spin.spinning = false
window.api.showGlobalMessageDialog(res_frame)
}
@@ -292,21 +297,45 @@ export default defineComponent({
})
}
/**
* 获取水印位置
*/
async function GetWatermarkPosition() {
// 开始获取水印位置
// 判断当前数据是不是存在
// 处理数据。获取当前的所有的数据
let dialogWidth = window.innerWidth * 0.7
let dialogHeight = window.innerHeight * 0.9
// ImportWordAndSrt
dialog.create({
showIcon: false,
closeOnEsc: false,
content: () => h(GetWaterMaskRectangle, { width: dialogWidth, height: dialogHeight }),
style: `width : ${dialogWidth}px; height : ${dialogHeight}px`,
maskClosable: false,
onClose: () => {}
})
}
// 一些选择方式
async function handleSelect(key) {
switch (key) {
case 'compute_frame':
case 'compute_frame': // 计算分镜
await ComputeStoryboard()
break
case 'framing':
case 'framing': // 开始分镜
await Framing()
break
case 'recognizing_setting':
case 'recognizing_setting': // 文案位置设置
await GetCopywritingSetting()
break
case 'mj_reverse':
case 'watermark_position': // 水印位置设置
await GetWatermarkPosition()
break
case 'mj_reverse': // MJ反推
await ImageReversePrompt(BookType.MJ_REVERSE)
break
case 'sd_reverse':
case 'sd_reverse': // SD反推
await ImageReversePrompt(BookType.SD_REVERSE)
break
case 'remove_reverse':
@@ -570,12 +599,17 @@ export default defineComponent({
})
}
async function ActionFrame() {
message.info('请使用下面菜单中的按钮进行对应的操作')
}
return {
RetuenMain,
AutoAction,
ImportWordAndSrtFunc,
reverseManageStore,
softwareStore,
ActionFrame,
switchLogger,
ComputeStoryboard,
OpenSettingDialog,
@@ -606,7 +640,7 @@ export default defineComponent({
],
copywritingOptions: [
{
label: '提取文案置',
label: '提取文案置',
key: 'recognizing_setting'
},
{
@@ -616,8 +650,8 @@ export default defineComponent({
],
watermarkOptions: [
{
label: '去除水印置',
key: 'watermark_setting'
label: '选择水印置',
key: 'watermark_position'
},
{
label: '停止去除',
+20 -18
View File
@@ -52,14 +52,14 @@ import {
DuplicateOutline,
GridOutline,
RadioOutline,
BookOutline
BookOutline,
} from '@vicons/ionicons5'
import CheckMachineId from '../Components/CheckMachineId.vue'
import axios from 'axios'
import { DEFINE_STRING } from '../../../../define/define_string'
import ShowMessage from './ShowMessage.vue'
import { MD5 } from 'crypto-js'
import InputDialogContent from '../Original/Components/InputDialogContent.vue'
import APIIcon from '../APIService/APIIcon.vue'
export default defineComponent({
components: {
@@ -71,7 +71,8 @@ export default defineComponent({
NIcon,
ShowMessage,
NSwitch,
NButton
NButton,
APIIcon
},
setup() {
let collapsed = ref(false)
@@ -89,6 +90,7 @@ export default defineComponent({
if (option.key == 'setting') return h(NIcon, null, { default: () => h(SettingsOutline) })
if (option.key == 'gptCopywriting') return h(NIcon, null, { default: () => h(BookOutline) })
if (option.key == 'book_management') return h(NIcon, null, { default: () => h(GridOutline) })
if (option.key == 'lai_api') return h(NIcon, null, { default: () => h(APIIcon) })
if (option.key == 'backward_matrix')
return h(NIcon, null, { default: () => h(DuplicateOutline) })
if (option.key == 'TTS_Services') return h(NIcon, null, { default: () => h(RadioOutline) })
@@ -452,21 +454,21 @@ export default defineComponent({
}
]
},
// {
// label: () =>
// h(
// RouterLink,
// {
// to: {
// name: 'test_options'
// }
// },
// {
// default: () => '测试操作'
// }
// ),
// key: 'test_options'
// },
{
label: () =>
h(
RouterLink,
{
to: {
name: 'lai_api'
}
},
{
default: () => 'API服务'
}
),
key: 'lai_api'
},
{
label: () =>
h(
@@ -3,16 +3,26 @@
<div style="width: 500px; position: relative">
<n-form ref="formRef" label-placement="top" :model="characterData" :rules="rules">
<n-form-item label="人物名称" path="label">
<n-input style="width: 400px;" v-model:value="characterData.label" placeholder="请输入人物名称" />
<n-input
style="width: 400px"
v-model:value="characterData.label"
placeholder="请输入人物名称"
/>
<n-checkbox style="margin-left: 20px" v-model:checked="characterData.isShow"
>显示</n-checkbox
>
<n-image
style="position: absolute; left: 520px; top: 0"
width="120"
height="120"
:src="png_base64 ? png_base64 : characterData.show_image"
/>
<div
style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column"
>
<n-image
width="120"
height="120"
:src="png_base64 ? png_base64 : characterData.show_image"
/>
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="人物别名(可多个)">
<n-dynamic-tags type="success" v-model:value="alias_tags" />
@@ -36,7 +46,7 @@
style="margin-top: 10px"
@click="GenerateCharacterImage"
:loading="imageLoading"
>生成图片</n-button
>SD生成图片</n-button
>
</div>
</n-form-item>
@@ -188,7 +198,6 @@ export default defineComponent({
await window.mj.SaveTagPropertyData(
[JSON.stringify(characterData.value), 'character_tags'],
(value) => {
console.log(value)
if (value.code == 0) {
message.error(value.message)
@@ -280,7 +289,6 @@ export default defineComponent({
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
@@ -298,8 +306,40 @@ export default defineComponent({
})
}
/**
* 选择本地图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
png_base64.value = null
characterData.value.show_image = value.value
}
)
}
return {
characterData,
SelecImage,
alias_tags,
SaveCharacterTag,
TranslatePrompt,
@@ -3,12 +3,12 @@
<n-form label-placement="top" ref="formRef" :rules="rules" :model="styleData">
<n-form-item label="风格名称" path="label">
<n-input v-model:value="styleData.label" placeholder="请输入风格名称" />
<n-image
style="position: absolute; left: 520px; top: 0"
width="120"
height="120"
:src="png_base64 ? png_base64 : styleData.show_image"
/>
<div style="position: absolute; left: 520px; top: 0; display: flex; flex-direction: column">
<n-image width="120" height="120" :src="png_base64 ? png_base64 : styleData.show_image" />
<n-button color="#e18a3b" style="margin-top: 5px" size="tiny" @click="SelecImage"
>本地上传图片</n-button
>
</div>
</n-form-item>
<n-form-item label="风格提示词描述(中文)">
<n-input
@@ -29,7 +29,7 @@
style="margin-top: 10px"
@click="GenerateStyleImage"
:loading="imageLoading"
>生成图片</n-button
>SD生成图片</n-button
>
</template>
使用SD出图模式生成风格图片提示词为1gril加上风格提示词
@@ -149,6 +149,9 @@ export default defineComponent({
return
}
styleData.value['type'] = 'style_main'
styleData.value['show_image'] = styleData.value.show_image
? styleData.value.show_image.split('?t')[0]
: null
// 直接保存
// 开始保存
await window.mj.SaveTagPropertyData(
@@ -244,6 +247,7 @@ export default defineComponent({
}
imageLoading.value = true
await window.sd.txt2img(JSON.stringify([d]), async (value) => {
debugger
if (value.code == 0) {
message.error(value.message)
imageLoading.value = false
@@ -251,7 +255,8 @@ export default defineComponent({
}
if (value.data && value.data.length > 0) {
let d = value.data[0]
styleData.value.show_image = d.image_path.replaceAll('\\', '/')
styleData.value.show_image =
d.image_path.replaceAll('\\', '/') + '?t=' + new Date().getTime()
png_base64.value = 'data:image/png;base64,' + d.base64
console.log(styleData.value.show_image)
imageLoading.value = false
@@ -261,8 +266,52 @@ export default defineComponent({
})
}
/**
* 选择本地的图片应用到风格图片
*/
async function SelecImage() {
await window.api.SelectFile(
[
'jpeg',
'jpg',
'png',
'gif',
'bmp',
'tiff',
'tif',
'webp',
'svg',
'raw',
'cr2',
'nef',
'arw'
],
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 读取文件并转换为Base64
fetch(value.value)
.then((response) => response.blob())
.then((blob) => {
const reader = new FileReader()
reader.onloadend = () => {
png_base64.value = reader.result
}
reader.readAsDataURL(blob)
})
// 更新显示的图片
png_base64.value = null
styleData.value.show_image = value.value + '?t=' + new Date().getTime()
}
)
}
return {
styleData,
SelecImage,
SaveStyleTag,
loading,
imageLoading,
@@ -83,6 +83,7 @@
fit="cover"
width="120"
height="120"
lazy
/>
<n-button
text
@@ -760,7 +760,7 @@ export default defineComponent({
}
// 筛选下面被锁定的数据
tempData = tempData.filter((item) => item.imageLock == false)
tempData = tempData.filter((item) => item.imageLock == null || item.imageLock == false)
if (tempData.length <= 0) {
message.error('下面的数据都被锁定,无法生图,请检查')
@@ -220,10 +220,10 @@ export default defineComponent({
* 生成所有的图片
*/
async function GenerateImageAll() {
debugger
let tmpData = cloneDeep(toRaw(data.value))
tmpData = tmpData.filter((item) => {
// return item.outImagePath == null || item.outImagePath == ''
return !item.imageLoak
return item.imageLock == null || item.imageLock == false
})
if (tmpData.length == 0) {
@@ -280,12 +280,24 @@ export default defineComponent({
}
}
// 重置GPT提示词
async function ResetGPTPrompt() {
for (let i = 0; i < data.value.length; i++) {
data.value[i].gpt_prompt = ''
}
}
// 图片数据重置
async function ResetImage() {
// 循环data删除数据
for (let i = 0; i < data.value.length; i++) {
data.value[i].outImagePath = null
data.value[i].subImagePath = []
data.value[i].imageLock = false
// 判断是不是有mj_message,有的话删除
if (data.value[i].mj_message) {
data.value[i].mj_message = undefined
}
}
}
@@ -333,6 +345,9 @@ export default defineComponent({
await ResetPrompt()
} else if (value == 'resetImage') {
await ResetImage()
} else if (value == 'resetGPTPrompt') {
// 重置GPT提示词
await ResetGPTPrompt()
}
}
@@ -340,6 +355,7 @@ export default defineComponent({
async function resetALLData() {
await ResetPrompt()
await ResetImage()
await ResetGPTPrompt()
}
// 导入提示词
@@ -408,6 +424,7 @@ export default defineComponent({
GptButtonOptions: [{ label: '停止推理任务', key: 'stopGptPrompt' }],
GenerateImageOptions: [{ label: '停止生成图片任务', key: 'stopGenerateImage' }],
ResetDataOptions: [
{ label: '重置GPT提示词', key: 'resetGPTPrompt' },
{ label: '重置提示词', key: 'resetPrompt' },
{ label: '重置图片', key: 'resetImage' }
],
@@ -0,0 +1,531 @@
<template>
<div style="margin-top: 30px">
<div
style="
margin-top: 10px;
display: flex;
justify-content: center;
align-items: center;
position: relative;
"
id="cnvas_ss"
>
<canvas ref="imageCanvas" />
<canvas
ref="tempImageCanvas"
@mousemove="draw"
@mouseup="stopDrawing"
@mousedown="startDrawing"
style="position: absolute; top: 1; left: 0"
/>
</div>
<div style="margin-top: 10px; display: flex; justify-content: center; align-items: center">
<n-button style="margin-left: 10px" @click="undo" type="success">回撤</n-button>
<n-button style="margin-left: 10px" @click="Check" type="success">查看去水印效果</n-button>
<n-button style="margin-left: 10px" @click="SaveMask" type="success">保存蒙板位置</n-button>
</div>
</div>
</template>
<script>
import { defineComponent, ref, onMounted, h, watch, toRaw } from 'vue'
import {
NButton,
useMessage,
NInput,
NTag,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress,
useDialog
} from 'naive-ui'
import ImageFileSelect from './ImageFileSelect.vue'
import ProgressDialog from '../Components/ProgressDialog.vue'
import { DEFINE_STRING } from '../../../../define/define_string'
import { cloneDeep, isEmpty } from 'lodash'
import { useReverseManageStore } from '../../../../stores/reverseManage'
export default defineComponent({
components: {
NButton,
NInput,
NTag,
ImageFileSelect,
NImage,
NCheckbox,
NSlider,
NPopover,
NProgress
},
props: ['width', 'height'],
setup(props) {
let message = useMessage()
let dialog = useDialog()
const imageCanvas = ref(null)
let tempImageCanvas = ref(null)
let imageContext = ref(null)
let reverseManageStore = useReverseManageStore()
let height = ref(props.height)
let width = ref(props.width)
let rectPos = ref(null)
let drawing = ref(false)
let moving = ref(false)
let startPos = ref(null)
let offset = ref({ x: 0, y: 0 })
let isDrawing = false
let radius = ref(20) // 圆的半径
let canvasHistory = [] // 用于保存画布的历史记录(存放位置信息)
let step = 0
let mask_setting = ref({
isRemote: true,
localUrl: null,
mask_path: null
})
let progressDialogRef = ref(null)
let total = ref(0)
let current = ref(0)
let dp
let image = ref(null)
let initImage = null
onMounted(async () => {
if (imageCanvas.value) {
const ctx = imageCanvas.value.getContext('2d', { willReadFrequently: true })
if (ctx) {
imageContext.value = ctx
}
}
let div = document.getElementById('cnvas_ss')
div.style.height = height.value - 120 + 'px'
await window.api.GetDefineConfigJsonByProperty(
JSON.stringify(['img_base', 'mask_setting', false, null]),
(value) => {
if (value.code == 0) {
message.error(value.message)
return
}
// 判断是不是有数据
if (value.data) {
mask_setting.value = Object.assign(mask_setting.value, value.data)
}
}
)
window.api.setEventListen([DEFINE_STRING.IMG.BATCH_PROCESS_IMAGE_RESULT], (value) => {
if (value.code == 0) {
message.error(value.message)
return
}
total.value = value.data.total
current.value = value.data.current
if (progressDialogRef.value == null) {
return
}
progressDialogRef.value?.modifyTotal(total.value)
progressDialogRef.value?.modifyCurrent(current.value)
if (value.data.total == value.data.current) {
// 销毁进度组件
setTimeout(() => {
if (dp) {
dp.destroy()
}
}, 1000)
}
})
// 加载图片
await loadFile(reverseManageStore.selectBookTaskDetail[0].oldImage)
})
let hasImage = false
/**
* 获取图片的高度并且计算适应的宽高
* @param image
*/
function getCurrentWidthHeight(image) {
let image_width = 512
let image_height = 512
// 设置图片的大小
if (image) {
image_width = image.width
image_height = image.height
}
// 判断图片大小,获取当前的传入的window,判断当前的宽度和高度是不是超出了
// let min_size = Math.min(width.value,height.value);
let image_height_scale = (height.value - 120) / image_height
let image_width_scale = (width.value - 120) / image_width
let scale = Math.min(image_height_scale, image_width_scale)
return [image_width * scale, image_height * scale]
}
function getImage(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => {
const img = new Image()
img.onload = () => resolve(img)
img.onerror = reject
img.src = reader.result
}
reader.onerror = (error) => reject(error)
reader.readAsDataURL(file)
})
}
async function loadFile(fileUrl) {
try {
const response = await fetch(fileUrl)
const blob = await response.blob()
const file = new File([blob], 'file.jpg', { type: blob.type })
const img = await getImage(file)
image.value = img
modifyImage(img)
} catch (error) {
console.error('Error loading file:', error)
}
}
/**
* 选择图片初始化
* @param image_d
*/
function modifyImage(image_d) {
let [image_width, image_height] = getCurrentWidthHeight(image_d)
imageContext.value.canvas.width = image_width
imageContext.value.canvas.height = image_height
imageContext.value.drawImage(image_d, 0, 0, image_width, image_height)
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
hasImage = true
let w = (width.value - image_width - 56) / 2
tempImageCanvas.value.style.left = w + 'px'
tempImageCanvas.value.width = imageCanvas.value.width
tempImageCanvas.value.height = imageCanvas.value.height
tempImageCanvas.value.style.zIndex = 100
if (!initImage) {
initImage = imageCanvas.value.toDataURL('image/png')
}
}
/**
* canvas绘制矩形
* @param rect
*/
const drawRectangle = (canvas, rect, clear = true) => {
const ctx = canvas.value.getContext('2d')
if (clear) {
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height) // 清除之前的绘制
}
ctx.strokeStyle = 'red' // 设置矩形颜色
ctx.lineWidth = 2 // 设置线宽
ctx.strokeRect(rect.startX, rect.startY, rect.width, rect.height)
}
const clearCanvas = (canvas) => {
const c = canvas.value
const context = c.getContext('2d')
context.clearRect(0, 0, c.width, c.height)
}
/**
* 获取当前鼠标在canvas中的相对位置
* @param e
*/
function getCanvasRelativePosition(e) {
const rect = tempImageCanvas.value.getBoundingClientRect()
return {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
/**
* 停止画
*/
function stopDrawing(e) {
// 获取矩形
if (drawing.value) {
const pos = getCanvasRelativePosition(e)
rectPos.value = {
startX: Math.min(startPos.value.x, pos.x),
startY: Math.min(startPos.value.y, pos.y),
width: Math.abs(startPos.value.x - pos.x),
height: Math.abs(startPos.value.y - pos.y),
imageWidth: tempImageCanvas.value.width,
imageHeight: tempImageCanvas.value.height
}
drawRectangle(tempImageCanvas, rectPos.value)
// 将当前的矩形画到imageCanvas上
drawRectangle(imageCanvas, rectPos.value, false)
isDrawing = false
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
canvasHistory.push(rectPos.value)
console.log(canvasHistory)
step++
startPos.value = null
drawing.value = false
}
if (moving.value) {
moving.value = false
// 将当前的矩形画到imageCanvas上
drawRectangle(imageCanvas, rectPos.value, false)
// canvasHistory.push(imageCanvas.value.toDataURL('image/png'))
canvasHistory.push(rectPos.value)
}
clearCanvas(tempImageCanvas)
}
/**
* 填充颜色
* @param e
*/
function draw(e) {
if (drawing.value && startPos.value) {
const currentPos = getCanvasRelativePosition(e)
const rect = {
startX: Math.min(startPos.value.x, currentPos.x),
startY: Math.min(startPos.value.y, currentPos.y),
width: Math.abs(startPos.value.x - currentPos.x),
height: Math.abs(startPos.value.y - currentPos.y)
}
drawRectangle(tempImageCanvas, rect)
} else if (moving.value && rectPos.value) {
const currentPos = getCanvasRelativePosition(e)
const newRectPos = {
startX: currentPos.x - offset.value.x,
startY: currentPos.y - offset.value.y,
width: rectPos.value.width,
height: rectPos.value.height,
imageWidth: tempImageCanvas.value.width,
imageHeight: tempImageCanvas.value.height
}
rectPos.value = newRectPos
drawRectangle(tempImageCanvas, newRectPos)
}
}
/**
* 开始话
* @param event
*/
function startDrawing(e) {
// 判断是移动还是绘制
const pos = getCanvasRelativePosition(e)
if (
rectPos.value &&
pos.x >= rectPos.value.startX &&
pos.x <= rectPos.value.startX + rectPos.value.width &&
pos.y >= rectPos.value.startY &&
pos.y <= rectPos.value.startY + rectPos.value.height
) {
moving.value = true
offset.value = { x: pos.x - rectPos.value.startX, y: pos.y - rectPos.value.startY }
} else {
startPos.value = pos
drawing.value = true
}
}
/**
* 回撤
*/
async function undo() {
debugger
if (step <= 0) {
// 如果没有可以撤销的步骤,就清空画布
imageContext.value.clearRect(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
canvasHistory = []
} else {
step--
clearCanvas(imageCanvas)
modifyImage(image.value)
canvasHistory.pop()
// 重新绘制矩形
for (let i = 0; i < canvasHistory.length; i++) {
const element = canvasHistory[i]
drawRectangle(imageCanvas, element, false)
}
}
}
/**
* 获取当前的蒙板
*/
function getCanvasMaskBase64() {
// 处理最后的图片
const imageData = imageContext.value.getImageData(
0,
0,
imageContext.value.canvas.width,
imageContext.value.canvas.height
)
const newImageData = imageContext.value.createImageData(imageData.width, imageData.height)
for (let i = 0; i < imageData.data.length; i += 4) {
if (
imageData.data[i] === 255 &&
imageData.data[i + 1] === 255 &&
imageData.data[i + 2] === 0
) {
// 如果这个像素点的颜色是 rgba(255, 255, 0, 255),就将它设置为白色
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 255
newImageData.data[i + 3] = 255
} else {
// 否则,就将它设置为黑色
newImageData.data[i] = newImageData.data[i + 1] = newImageData.data[i + 2] = 0
newImageData.data[i + 3] = 255
}
}
// // 创建一个新的 Canvas 对象
const newCanvas = document.createElement('canvas')
newCanvas.width = imageData.width
newCanvas.height = imageData.height
const newContext = newCanvas.getContext('2d')
// 将新的 ImageData 对象绘制到新的 Canvas 对象上
newContext.putImageData(newImageData, 0, 0)
// 将新的 Canvas 对象转换为一个 base64 编码的图片 URL
const base64 = newCanvas.toDataURL('image/png')
return base64
}
/**
* 查看效果
*/
async function Check() {
debugger
// 判断当前是不是有蒙板
if (canvasHistory.length == 0) {
message.error('请先选择去水印的区域')
return
}
let res = await window.img.ProcessImageCheck(
initImage,
cloneDeep(canvasHistory),
reverseManageStore.selectBook.id
)
const arrayBuffer = res.data
const blob = new Blob([arrayBuffer], { type: 'image/png' })
const reader = new FileReader()
reader.onloadend = function () {
let img = new Image()
img.src = reader.result
img.onload = function () {
imageContext.value.drawImage(img, 0, 0)
canvasHistory.push({
startX: 0,
startY: 0,
width: 0,
height: 0,
imageWidth: tempImageCanvas.value.width,
imageHeight: tempImageCanvas.value.height
})
step++
}
}
reader.readAsDataURL(blob)
}
async function SaveMask() {
debugger
// 判断当前是不是又蒙板
if (canvasHistory.length <= 1) {
message.error('请先选择去水印的区域')
return
}
let maskPos = canvasHistory.filter((item) => item.width > 0 && item.height > 0)
if (maskPos.length == 0) {
message.error('请先选择去水印的区域,或者选择的水印区域无效')
return
}
// 开始保存
let res = await window.db.UpdateBookData(reverseManageStore.selectBook.id, {
watermarkPosition: JSON.stringify(maskPos)
})
if (res.code == 0) {
message.error(res.message)
return
}
message.success('水印位置保存成功')
}
/**
*查看蒙板
*/
async function ViewMaskImage() {
window.system.OpenFile(mask_setting.value.mask_path)
}
function OpenTeach(type) {
switch (type) {
case 'lama':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#QOIRdJQvEouJB7xv0Xqcx3zInyb'
)
break
case 'iopaint':
window.api.OpenUrl(
'https://rvgyir5wk1c.feishu.cn/docx/RZYCdG7ZpoKsIzxBEzccNEIFn8f#MLoUdn5cfo6NJTxL0xTcUGyLn43'
)
default:
break
}
}
return {
modifyImage,
imageCanvas,
tempImageCanvas,
height,
width,
stopDrawing,
startDrawing,
startPos,
draw,
undo,
Check,
offset,
SaveMask,
mask_setting,
ViewMaskImage,
radius,
progressDialogRef,
total,
current,
OpenTeach,
reverseManageStore,
image
}
}
})
</script>
<style>
.url_class {
color: #e18a3b;
}
.url_class:hover {
color: brown;
cursor: pointer;
}
</style>
@@ -43,6 +43,7 @@ export default defineComponent({
}
async function selectImage(value) {
debugger
let img = await getBase64(value.file.file)
props.modifyImage(img)
}
+8
View File
@@ -6,6 +6,9 @@ const app = createApp(App)
import { createPinia } from 'pinia'
const pinia = createPinia()
// 注册 webview 为自定义元素
app.config.compilerOptions.isCustomElement = (tag) => tag === 'webview'
const routes = [
{
path: '/',
@@ -101,6 +104,11 @@ const routes = [
name: 'test_options',
component: () => import('./components/VideoSubtitle/VideoCanvas.vue')
},
{
path: '/lai_api',
name: 'lai_api',
component: () => import('./components/APIService/ApiService.vue')
},
{
path: '/TTS_Services',
name: 'TTS_Services',