Ver Fonte

完成拍照水印功能

yuanmingze há 8 meses atrás
pai
commit
8a8f5a9e22

+ 35 - 85
src/pages/index/components/ImagePreviewOverlay.vue

@@ -17,24 +17,19 @@
     <!-- 隐藏 canvas -->
     <canvas
       canvas-id="watermarkCanvas"
-      :style="{
-        width: canvasWidth + 'px',
-        height: canvasHeight + 'px',
-        position: 'fixed',
-        left: '-9999px',
-      }"
+      :style="`width: ${canvasWidth}px; height: ${canvasHeight}px; position: fixed; left: -9999px; top: 0`"
     />
   </view>
 </template>
 
 <script setup lang="ts">
-import { ref, watch } from 'vue'
+import { nextTick, ref, watch } from 'vue'
 import { getCurrentInstance } from 'vue'
 
-import dayjs from 'dayjs'
-
 import { useSaveToAlbum } from '@/composables/photo/useSaveToAlbum'
 
+import { generateWatermark } from '@/utils/generateWatermark'
+
 const visible = defineModel<boolean>('visible', { default: false })
 
 const props = defineProps<{
@@ -51,8 +46,6 @@ const safeTop = system.safeAreaInsets?.top ?? 0
 const safeBottom = system.safeAreaInsets?.bottom ?? 0
 
 const watermarkSrc = ref('')
-const canvasWidth = ref(0)
-const canvasHeight = ref(0)
 
 const close = () => {
   visible.value = false
@@ -65,85 +58,42 @@ const download = () => {
   saveToAlbum(watermarkSrc.value)
 }
 
-/**
- * 核心:生成水印图
- */
-
 const instance = getCurrentInstance()
-const generateWatermark = (fileUrl: string, address: string) => {
-  console.log('fileUrl', fileUrl)
-  console.log('address', address)
-
-  return new Promise<string>((resolve, reject) => {
-    uni.getImageInfo({
-      src: fileUrl,
-      success(res) {
-        canvasWidth.value = res.width
-        canvasHeight.value = res.height
-
-        const ctx = uni.createCanvasContext('watermarkCanvas', instance)
-
-        // 原图
-        ctx.drawImage(fileUrl, 0, 0, res.width, res.height)
-
-        const scale = res.width / 375
-        const padding = 12 * scale
-        const fontSize = 12 * scale
-        const lineHeight = 18 * scale
-
-        const textList = [address, dayjs().format('YYYY/MM/DD HH:mm:ss')]
-
-        ctx.setFontSize(fontSize)
-        const textWidth = Math.max(...textList.map((t) => ctx.measureText(t).width))
-
-        const boxWidth = textWidth + padding * 2
-        const boxHeight = lineHeight * textList.length + padding
-
-        const x = padding
-        const y = res.height - boxHeight - padding
-
-        ctx.setFillStyle('rgba(0,0,0,0.45)')
-        ctx.fillRect(x, y, boxWidth, boxHeight)
-
-        ctx.setFillStyle('#fff')
-        textList.forEach((text, i) => {
-          ctx.fillText(text, x + padding, y + padding + lineHeight * (i + 1) - 4)
-        })
-
-        ctx.draw(false, () => {
-          uni.canvasToTempFilePath(
-            {
-              canvasId: 'watermarkCanvas',
-              fileType: 'jpg',
-              success(r) {
-                resolve(r.tempFilePath)
-              },
-              fail: reject,
-            },
-            instance
-          )
-        })
-      },
-      fail: reject,
-    })
-  })
-}
 
-/**
- * src 或 address 变化时,重新生成水印
- */
+const canvasWidth = ref(10)
+const canvasHeight = ref(10)
+
 watch(
-  () => [props.src, props.address],
-  async ([src, address]) => {
-    if (!src || !address) return
+  () => visible.value,
+  async (val) => {
+    if (!val) return
+    if (!props.src || !props.address) return
+
+    await new Promise<void>((resolve, reject) => {
+      uni.getImageInfo({
+        src: props.src,
+        success(res) {
+          canvasWidth.value = res.width
+          canvasHeight.value = res.height
+          resolve()
+        },
+        fail: reject,
+      })
+    })
+
+    // 等 canvas 真正挂载完成
+    await nextTick()
     try {
-      watermarkSrc.value = await generateWatermark(src, address)
-      console.log('watermarkSrc.value', watermarkSrc.value)
-    } catch (error) {
-      console.log('err', error)
+      watermarkSrc.value = await generateWatermark({
+        imageUrl: props.src,
+        address: props.address,
+        canvasId: 'watermarkCanvas',
+        instance,
+      })
+    } catch (err) {
+      console.error('generateWatermark error', err)
     }
-  },
-  { immediate: true }
+  }
 )
 watch(visible, (val) => {
   if (val) {

+ 1 - 3
src/pages/index/components/WorkbenchTaskSection.vue

@@ -56,7 +56,6 @@ const imageUrl = ref('')
 const address = ref('')
 const takePhotoFlow = async (): Promise<void> => {
   const locationResult = await getLocation()
-  console.log('locationResult', locationResult)
 
   if (!locationResult?.address) {
     return
@@ -64,9 +63,8 @@ const takePhotoFlow = async (): Promise<void> => {
   address.value = locationResult.address
   try {
     const imageSrc = await takePhoto()
-
-    previewVisible.value = true
     imageUrl.value = imageSrc
+    previewVisible.value = true
   } catch {
     // 用户取消 / 拍照失败 / 格式不支持
   }

BIN
src/static/images/common/watermarksLogo.png


+ 124 - 0
src/utils/generateWatermark.ts

@@ -0,0 +1,124 @@
+import dayjs from 'dayjs'
+import type { ComponentInternalInstance } from 'vue'
+
+interface GenerateWatermarkOptions {
+  imageUrl: string
+  address: string
+  canvasId: string
+  instance: ComponentInternalInstance | null
+}
+
+/**
+ * 生成带水印的图片(稳定版)
+ */
+export function generateWatermark({
+  imageUrl,
+  address,
+  canvasId,
+  instance,
+}: GenerateWatermarkOptions): Promise<string> {
+  return new Promise((resolve, reject) => {
+    if (!imageUrl || !address || !canvasId || !instance) {
+      reject(new Error('generateWatermark: missing params'))
+      return
+    }
+
+    uni.getImageInfo({
+      src: imageUrl,
+      success(res) {
+        const width = res.width
+        const height = res.height
+        const ctx = uni.createCanvasContext(canvasId, instance)
+
+        /* ========= 原图 ========= */
+        // 使用本地路径绘制,避免远程地址绘制失败
+        ctx.drawImage(res.path, 0, 0, width, height)
+
+        /* ========= 设计稿比例 ========= */
+        const designWidth = 375
+        const scale = width / designWidth
+
+        const radius = 10 * scale
+        const padding = 8 * scale
+        const iconSize = 10 * scale
+        const textSpacing = 4 * scale
+        const lineHeight = 14 * scale
+        const bottomSpacing = 14 * scale
+        const leftSpacing = 14 * scale
+        const fontSize = 10 * scale
+
+        ctx.setFontSize(fontSize)
+
+        /* ========= 文本换行 ========= */
+        const maxTextWidth = width - (iconSize + textSpacing + padding * 3 + leftSpacing * 2)
+
+        const textArr: string[] = []
+        let temp = ''
+
+        for (const char of address) {
+          if (ctx.measureText(temp + char).width > maxTextWidth) {
+            textArr.push(temp)
+            temp = char
+          } else {
+            temp += char
+          }
+        }
+        if (temp) textArr.push(temp)
+
+        const time = dayjs().format('YYYY/MM/DD HH:mm:ss')
+        textArr.push(time)
+
+        /* ========= 背景尺寸 ========= */
+        const textWidth = Math.max(...textArr.map((t) => ctx.measureText(t).width))
+        const boxWidth = iconSize + textWidth + textSpacing * 3 + padding
+        const boxHeight = lineHeight * textArr.length + padding
+
+        const boxX = leftSpacing
+        const boxY = height - boxHeight - bottomSpacing
+
+        /* ========= 圆角背景 ========= */
+        ctx.setFillStyle('rgba(0,0,0,0.43)')
+        ctx.beginPath()
+        ctx.moveTo(boxX + radius, boxY)
+        ctx.lineTo(boxX + boxWidth - radius, boxY)
+        ctx.arc(boxX + boxWidth - radius, boxY + radius, radius, -Math.PI / 2, 0)
+        ctx.lineTo(boxX + boxWidth, boxY + boxHeight - radius)
+        ctx.arc(boxX + boxWidth - radius, boxY + boxHeight - radius, radius, 0, Math.PI / 2)
+        ctx.lineTo(boxX + radius, boxY + boxHeight)
+        ctx.arc(boxX + radius, boxY + boxHeight - radius, radius, Math.PI / 2, Math.PI)
+        ctx.lineTo(boxX, boxY + radius)
+        ctx.arc(boxX + radius, boxY + radius, radius, Math.PI, -Math.PI / 2)
+        ctx.closePath()
+        ctx.fill()
+
+        /* ========= icon ========= */
+        const iconPath = '/static/images/common/watermarksLogo.png'
+        ctx.drawImage(iconPath, boxX + padding, boxY + padding, iconSize, iconSize)
+
+        /* ========= 文本 ========= */
+        ctx.setFillStyle('#fff')
+        ctx.setFontSize(fontSize)
+        textArr.forEach((text, i) => {
+          ctx.fillText(text, boxX + iconSize + textSpacing + padding, boxY + lineHeight * (i + 1))
+        })
+
+        ctx.draw(false, () => {
+          uni.canvasToTempFilePath(
+            {
+              canvasId,
+              fileType: 'jpg',
+              destWidth: width,
+              destHeight: height,
+              success(r) {
+                resolve(r.tempFilePath)
+              },
+              fail: reject,
+            },
+            instance
+          )
+        })
+      },
+      fail: reject,
+    })
+  })
+}

+ 0 - 1
src/utils/image.ts

@@ -46,4 +46,3 @@ function isUnsupportedFormat(path: string): boolean {
 }
 
 
-