用canvas切图展示及标记在原图片中的位置

1.切图

需要后端返回原图片地址及对应四个角的位置数组

TypeScript 复制代码
const getImageList = async (photos: string[]) => {
  try {
    const res = await getAiImage(photos)

    if (res.data && res.data.length) {
      // 处理结果
      const results: AIResultItem[] = []

      for (const item of res.data) {
        if (item.Num && item.Url && item.location) {
          const chars = []
          const resultItem: AIResultItem = {
            Num: item.Num,
            Url: item.Url,
            location: item.location,
            chars: chars
          }
          // 尝试裁剪图片
          try {
            const croppedUrl = await cropImage(item.Url, item.location)
            resultItem.croppedImageUrl = croppedUrl
          } catch (error) {
            console.warn('图片裁剪失败:', error)
            // 如果裁剪失败,使用原图
            resultItem.croppedImageUrl = item.Url.startsWith('http')
              ? item.Url
              : `${MINO}${item.Url}`
          }

          results.push(resultItem)
        }
      }

      currentImgList.value = results
      console.log('处理后的结果列表:', currentImgList.value)
    } else {
      showFailToast('未识别到')
      currentImgList.value = []
    }
  } catch (error) {
    console.error('AI识别失败:', error)
    showFailToast('识别失败,请重试')
  } finally {
    isidentify.value = false
  }
}

// 裁剪图片
const cropImage = (imageUrl: string, location: location): Promise<string> => {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.crossOrigin = 'anonymous'

    img.onload = () => {
      try {
        const rect = calculatePlateRect(location)
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        if (!ctx) {
          reject(new Error('Failed to get canvas context'))
          return
        }

        // 设置canvas尺寸
        canvas.width = rect.width
        canvas.height = rect.height

        // 绘制裁剪区域
        ctx.drawImage(img, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height)

        // 转换为DataURL
        const croppedUrl = canvas.toDataURL('image/jpeg', 0.9)
        resolve(croppedUrl)
      } catch (error) {
        console.error('图片裁剪失败:', error)
        reject(error)
      }
    }

    img.onerror = () => {
      reject(new Error('Failed to load image'))
    }

    // 确保图片地址完整
    const fullImageUrl = imageUrl.startsWith('http') ? imageUrl : `${MINO}${imageUrl}`
    img.src = fullImageUrl
  })
}
// 根据location计算矩形区域
const calculatePlateRect = (location: location) => {
  const { leftTop, rightTop, rightBottom, leftBottom } = location

  // 计算矩形的坐标
  const x = Math.min(leftTop.x, rightTop.x, rightBottom.x, leftBottom.x)
  const y = Math.min(leftTop.y, rightTop.y, rightBottom.y, leftBottom.y)
  const width = Math.max(leftTop.x, rightTop.x, rightBottom.x, leftBottom.x) - x
  const height = Math.max(leftTop.y, rightTop.y, rightBottom.y, leftBottom.y) - y

  return { x, y, width, height }
}

2.标记位置

预览原图并标记出截图的位置重新绘制图片展示

TypeScript 复制代码
// 打开预览
const openPreview = async (item: AIResultItem) => {
  console.log('打开预览:', item)

  if (!item || !item.Url) {
    showToast('暂无照片')
    return
  }

  try {
    // 加载原始图片
    const originalImageUrl = item.Url.startsWith('http')
      ? item.Url
      : `${MINO}${item.Url}`

    // 在Canvas上绘制带框的图片
    const canvasImageUrl = await drawPlateLocationOnImage(originalImageUrl, item.plateLocation)

    // 显示预览
    if (canvasImageUrl) {
      showImagePreview([canvasImageUrl])
    } else {
      // 如果绘制失败,显示原始图片
      showImagePreview([originalImageUrl])
    }
  } catch (error) {
    console.error('预览图片失败:', error)
    // 出错时显示原始图片
    showImagePreview([`${MINO}${item.Url}`])
  }
}

// 在图片上绘制截图位置框
const drawPlateLocationOnImage = (
  imageUrl: string,
  plateLocation: PlateLocation
): Promise<string> => {
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.crossOrigin = 'anonymous'

    img.onload = () => {
      try {
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')

        if (!ctx) {
          reject(new Error('Failed to get canvas context'))
          return
        }

        // 设置canvas尺寸与图片相同
        canvas.width = img.width
        canvas.height = img.height

        // 1. 绘制原始图片
        ctx.drawImage(img, 0, 0)

        // 2. 计算位置矩形
        const rect = calculatePlateRect(plateLocation)

        // 3. 绘制位置框
        drawPlateRect(ctx, rect)

        // 4. 转换为DataURL
        const imageWithRectUrl = canvas.toDataURL('image/jpeg', 0.9)
        resolve(imageWithRectUrl)
      } catch (error) {
        console.error('绘制位置框失败:', error)
        reject(error)
      }
    }

    img.onerror = () => {
      reject(new Error('Failed to load image'))
    }

    img.src = imageUrl
  })
}

// 绘制位置矩形框
const drawPlateRect = (
  ctx: CanvasRenderingContext2D,
  rect: { x: number; y: number; width: number; height: number }
) => {
  const { x, y, width, height } = rect

  // 设置绘制样式
  ctx.save()

  // 绘制半透明背景填充
  ctx.fillStyle = 'rgba(255, 0, 0, 0.1)'
  ctx.fillRect(x, y, width, height)

  // 绘制边框
  ctx.lineWidth = 3
  ctx.strokeStyle = '#FF0000'
  ctx.setLineDash([8, 4]) // 虚线边框

  // 绘制圆角矩形
  const borderRadius = 8
  drawRoundedRect(ctx, x, y, width, height, borderRadius)
  ctx.stroke()

  // 绘制四个角的标记
  const cornerSize = 12
  const cornerLineWidth = 3
  ctx.setLineDash([]) // 实线
  ctx.lineWidth = cornerLineWidth
  ctx.strokeStyle = 'FF0000'

  // 左上角
  ctx.beginPath()
  ctx.moveTo(x, y + cornerSize)
  ctx.lineTo(x, y)
  ctx.lineTo(x + cornerSize, y)
  ctx.stroke()

  // 右上角
  ctx.beginPath()
  ctx.moveTo(x + width - cornerSize, y)
  ctx.lineTo(x + width, y)
  ctx.lineTo(x + width, y + cornerSize)
  ctx.stroke()

  // 右下角
  ctx.beginPath()
  ctx.moveTo(x + width, y + height - cornerSize)
  ctx.lineTo(x + width, y + height)
  ctx.lineTo(x + width - cornerSize, y + height)
  ctx.stroke()

  // 左下角
  ctx.beginPath()
  ctx.moveTo(x + cornerSize, y + height)
  ctx.lineTo(x, y + height)
  ctx.lineTo(x, y + height - cornerSize)
  ctx.stroke()

  // 添加位置标签
  ctx.fillStyle = '#47A0FF'
  ctx.font = 'bold 16px Arial'
  ctx.textAlign = 'left'
  ctx.textBaseline = 'top'
  ctx.fillText('', x, y - 25)

  ctx.restore()
}

// 绘制圆角矩形
const drawRoundedRect = (
  ctx: CanvasRenderingContext2D,
  x: number,
  y: number,
  width: number,
  height: number,
  radius: number
) => {
  ctx.beginPath()
  // 左上角
  ctx.moveTo(x + radius, y)
  ctx.arcTo(x + width, y, x + width, y + height, radius)
  // 右上角
  ctx.arcTo(x + width, y + height, x, y + height, radius)
  // 右下角
  ctx.arcTo(x, y + height, x, y, radius)
  // 左下角
  ctx.arcTo(x, y, x + width, y, radius)
  ctx.closePath()
}
相关推荐
巧克力芋泥包5 小时前
前端vue3调取阿里的oss存储
前端
AAA阿giao5 小时前
React Hooks 详解:从 useState 到 useEffect,彻底掌握函数组件的“灵魂”
前端·javascript·react.js
RedHeartWWW5 小时前
Next.js Middleware 极简教程
前端
程序员阿鹏5 小时前
OOM是如何解决的?
java·开发语言·jvm·spring
爱潜水的小L5 小时前
自学嵌入式day37,网络编程
开发语言·网络·php
阿蒙Amon5 小时前
C#每日面试题-类和结构的区别
开发语言·c#
Bin二叉5 小时前
南京大学cpp复习(c10——多态、操作符重载)
开发语言·c++·笔记·学习
用户12039112947265 小时前
从零上手 LangChain:用 JavaScript 打造强大 AI 应用的全流程指南
javascript·langchain·llm
饼饼饼5 小时前
从 0 到 1:前端 CI/CD 实战 ( 第一篇: 云服务器环境搭建)
运维·前端·自动化运维