Commit bc180f71 authored by 朱国瑞's avatar 朱国瑞

裁剪组件支持裁剪图片

parent 3aa0bb5e
......@@ -21,22 +21,31 @@ const props = defineProps({
})
watch(
() => [props.cropArea, props.visible],
() => [props.cropArea, props.visible, props.url],
(newVal, oldVal) => {
console.log('cropArea', newVal[0])
if (!props.visible) {
showCropBox.value = false
// handleInitCropArea()
}
if (props.url) {
// 判断URL是视频还是图片
const type = utils.getFileType(props.url)
sourceMaterialType.value = type
title.value = type === 'video' ? '裁剪视频' : '裁剪图片'
}
}
)
const emit = defineEmits(['update:visible', 'crop'])
const isVisible = ref(props.visible)
const loginFormRef = ref<FormInstance>()
const title = ref('裁剪视频')
const sourceMaterialType = ref<Wm.SourceMaterialType>('video')
const isMobile = ref(utils.checkIsMobile())
const cropBoxRef = ref<any>()
const backgroundVideoRef = ref<HTMLVideoElement>()
const backgroundImageRef = ref<HTMLImageElement>()
const croppedCanvasRef = ref<HTMLCanvasElement>()
const rectData = ref({
left: 0,
......@@ -63,13 +72,31 @@ const onCancel = () => {
console.log('cancel')
handleClose()
}
const onConfirm = async () => {
const getSourceMaterialInfo = () => {
if (sourceMaterialType.value === 'video') {
const video = backgroundVideoRef.value
return {
width: video?.videoWidth || 0,
height: video?.videoHeight || 0,
clientWidth: video?.clientWidth || 0,
clientHeight: video?.clientHeight || 0
}
} else {
const image = backgroundImageRef.value
return {
width: image?.naturalWidth || 0,
height: image?.naturalHeight || 0,
clientWidth: image?.clientWidth || 0,
clientHeight: image?.clientHeight || 0
}
}
}
const onConfirm = async () => {
const sourceMaterialInfo = getSourceMaterialInfo()
// 计算裁剪框在视频中的位置
const scaleX = video!.videoWidth / video!.clientWidth
const scaleY = video!.videoHeight / video!.clientHeight
const scaleX = sourceMaterialInfo!.width / sourceMaterialInfo!.clientWidth
const scaleY = sourceMaterialInfo!.height / sourceMaterialInfo!.clientHeight
const cropData = {
x: Math.round(rectData.value.left * scaleX),
y: Math.round(rectData.value.top * scaleY),
......@@ -86,17 +113,17 @@ const onConfirm = async () => {
if (cropData.y < 0) {
cropData.y = 0
}
if (cropData.width > video!.videoWidth) {
cropData.width = video!.videoWidth
if (cropData.width > sourceMaterialInfo!.width) {
cropData.width = sourceMaterialInfo!.width
}
if (cropData.x + cropData.width > video!.videoWidth) {
cropData.width = video!.videoWidth - cropData.x
if (cropData.x + cropData.width > sourceMaterialInfo!.width) {
cropData.width = sourceMaterialInfo!.width - cropData.x
}
if (cropData.height > video!.videoHeight) {
cropData.height = video!.videoHeight
if (cropData.height > sourceMaterialInfo!.height) {
cropData.height = sourceMaterialInfo!.height
}
if (cropData.y + cropData.height > video!.videoHeight) {
cropData.height = video!.videoHeight - cropData.y
if (cropData.y + cropData.height > sourceMaterialInfo!.height) {
cropData.height = sourceMaterialInfo!.height - cropData.y
}
console.log(rectData.value, scaleX, scaleY)
......@@ -223,6 +250,46 @@ const handleInitCropArea = () => {
}
}
}
const onImageLoaded = (val: any) => {
console.log('onImageLoaded', val)
const img = val.target
const imageWidth = img.naturalWidth
const imageHeight = img.naturalHeight
cropPreviewBox.value = {
width: img.width,
height: img.height
}
showCropBox.value = true
const cropArea: any = toRaw(props.cropArea)
if (cropArea.length > 0) {
const scaleX = cropPreviewBox.value.width / imageWidth
const scaleY = cropPreviewBox.value.height / imageHeight
console.log('cropArea', cropArea, scaleX, scaleY)
const left = Math.round(cropArea[0][0] * scaleX)
const top = Math.round(cropArea[0][1] * scaleY)
const width = Math.round((cropArea[1][0] - cropArea[0][0]) * scaleX)
const height = Math.round((cropArea[1][1] - cropArea[0][1]) * scaleY)
console.log('init cropArea', left, top, width, height)
rectData.value = {
left,
top,
width,
height
}
} else {
// 全部显示
rectData.value = {
left: 0,
top: 0,
width: imageWidth,
height: imageHeight
}
}
}
</script>
<template>
<el-dialog
......@@ -249,8 +316,15 @@ const handleInitCropArea = () => {
playsinline
class="background-video"
@loadeddata="onVideoLoaded"
v-if="isVisible"
v-if="isVisible && sourceMaterialType === 'video'"
></video>
<img
ref="backgroundImageRef"
:src="url"
class="background-image"
@load="onImageLoaded"
v-if="isVisible && sourceMaterialType === 'image'"
/>
<VueDragResize
ref="cropBoxRef"
:w="rectData.width"
......@@ -300,6 +374,13 @@ const handleInitCropArea = () => {
object-fit: cover;
}
.background-image {
width: 100%;
height: 100%;
object-fit: cover;
-webkit-user-drag: none; /* 禁止图片拖动 */
}
.crop-box {
border-color: var(--el-color-primary-dark-2);
position: absolute;
......
......@@ -143,4 +143,19 @@ export default class utils {
return false
}
}
static getFileType = (url: string) => {
const videoExtensions = ['mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv'];
const imageExtensions = ['jpg', 'jpeg', 'png', 'gif', 'bmp', 'webp'];
const extension = (url.split('.').pop() || '').toLowerCase();
if (videoExtensions.includes(extension)) {
return 'video';
} else if (imageExtensions.includes(extension)) {
return 'image';
} else {
return 'unknown';
}
}
}
......@@ -424,7 +424,7 @@ const onDownloadVideo = async () => {
size="small"
@click.stop="showCropVideoDialog('show_image_or_video')"
>
裁剪视频画面
裁剪图片或视频画面
</el-button>
<template #tip>
<div class="el-upload__tip" style="color: #0000ff; background-color: #e6f7ff">
......
declare namespace Wm {
interface ImgSize {
"width": string,
"height": string,
width: string
height: string
}
interface PromptHistory {
"role": string,
"content": string,
role: string
content: string
}
interface PicText {
"text": string,
"color": string,
"bg_color": string,
"font_size": number,
"position": number,
text: string
color: string
bg_color: string
font_size: number
position: number
}
interface ScriptsItem {
"编号": string,
"场景描述": string,
"场景关键词": string,
"场景关键词英文": string,
"角色": string,
"角色关键词": string,
"角色关键词英文": string,
"画面描述词": string,
"本镜配图": string,
"local_image_path": string,
"info": string,
"roles": String[],
"info2": string,
编号: string
场景描述: string
场景关键词: string
场景关键词英文: string
角色: string
角色关键词: string
角色关键词英文: string
画面描述词: string
本镜配图: string
local_image_path: string
info: string
roles: String[]
info2: string
}
interface GenVideo {
"task_id": string,
"task_info": GenVideoItem[],
task_id: string
task_info: GenVideoItem[]
}
interface GenVideoItem {
"idx": string,
"text": string,
"img_path": string,
idx: string
text: string
img_path: string
}
interface RolesItem {
"角色": string,
"角色关键词": string,
"角色关键词英文": string,
"属性": string,
角色: string
角色关键词: string
角色关键词英文: string
属性: string
}
interface UploadResult {
"code": int,
"data": [
code: int
data: [
{
"id": int,
"key": string,
"path": string,
"url": string
id: int
key: string
path: string
url: string
}
],
"message": string
]
message: string
}
interface Coordinate {
"x": int,
"y": int,
x: int
y: int
}
interface LoginParams {
......@@ -83,4 +83,6 @@ declare namespace Wm {
password: string
username: string
}
type SourceMaterialType = 'video' | 'image' | 'unknown'
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment