import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'
import Canvas, { Image as CanvasImage } from 'react-native-canvas'
import RNFS from 'react-native-fs'
import { Dimensions } from 'react-native'
import { useUserStore } from '@/store'
import { getAddressByLocation } from '@/utils/location'
import dayjs from 'dayjs'
import { formatError, sectcutArr } from '@/utils'
export type WatermarkArgs = {
url?: string
handleCustomDraw?: (ctx: any) => void
maskText?: string[]
width?: number
height?: number
uri?: string
}
export interface WatermarkRefType {
addWatermark: (args: WatermarkArgs) => Promise<string>
removeTask: (uri?: string) => void
}
type DrawImageParams = {
args: WatermarkArgs
resolve: (url: string) => void
reject: (error?: any) => void
}
export const getCommonMask = async () => {
const address = await getAddressByLocation()
const userInfo = useUserStore.getState().userInfo
return [`${userInfo.nickName} ${dayjs().format('YYYY-MM-DD HH:mm')}`, address]
}
const scale = Dimensions.get('window').scale
const WatermarkRoot = forwardRef<WatermarkRefType>((_, ref) => {
const canvasRef = useRef<Canvas | null>(null)
const taskQueue = useRef<DrawImageParams[]>([])
const addWatermark = async (args: WatermarkArgs) => {
return new Promise<string>((resolve, reject) => {
if (!canvasRef.current) {
console.error('Canvas not initialized')
return reject(new Error('Canvas not initialized'))
}
const task = { args, resolve, reject }
if (!taskQueue.current.find((t) => t.args.url === args.url)) {
taskQueue.current.push(task)
if (taskQueue.current.length === 1) {
drawImage(task)
} else {
handleEventTracking({
tip: 'canvas绘制任务被挂起--Watermark/index.tsx--55',
data: {
taskQueue: taskQueue.current,
},
})
}
}
})
}
const processNextTask = () => {
taskQueue.current.shift()
if (taskQueue.current.length > 0) {
drawImage(taskQueue.current[0])
}
}
const drawImage = ({ args, resolve, reject }: DrawImageParams) => {
const canvas = canvasRef.current
if (!canvas) return reject(new Error('Canvas instance not available'))
const { handleCustomDraw, maskText = [], width = 1080, height = 1440 } = args
const imageWidth = width / scale
const imageHeight = height / scale
canvas.width = imageWidth
canvas.height = imageHeight
const ctx = canvas.getContext('2d')
const image = new CanvasImage(canvas)
image.crossOrigin = 'Anonymous'
image.addEventListener('load', async () => {
try {
ctx.drawImage(image, 0, 0, imageWidth, imageHeight)
ctx.fillStyle = '#fff'
const size = Math.floor(imageWidth / 34)
ctx.font = `${size}px PingFang SC`
const commonText = await getCommonMask()
const handlerMaskText = sectcutArr([...maskText, ...commonText])
const bgHeight = handlerMaskText.length * 1.6 * size
const maskHeight = imageHeight - bgHeight
handlerMaskText.forEach((item, index) => {
ctx.fillText(item, size, maskHeight + (index + 1) * 1.4 * size)
})
if (handleCustomDraw) handleCustomDraw(ctx)
const dataURL = await canvas.toDataURL('image/jpeg', 0.8)
const url = await base64ToFile(dataURL)
handleEventTracking({
tip: '图片水印绘制成功--Watermark/index.tsx--96',
data: {
taskQueue: taskQueue.current.length,
url,
},
})
resolve(url)
} catch (error) {
reject(error)
handleEventTracking({
tip: '捕获图片水印绘制失败--Watermark/index.tsx--129',
data: {
errorObj: formatError(error),
taskQueue: taskQueue.current,
},
})
} finally {
processNextTask()
}
})
image.addEventListener('error', (error: any) => {
handleEventTracking({
tip: '绘制水印时加载图片失败--Watermark/index.tsx--77',
data: { errorObj: formatError(error), base64: image.src },
})
reject(error)
processNextTask()
})
image.src = `data:image/png;base64,${args.url}`
}
const base64ToFile = async (base64: string) => {
const imageData = base64.split('data:image/jpeg;base64,')[1]
const imagePath = `${RNFS.TemporaryDirectoryPath}/${Date.now()}.jpg`
await RNFS.writeFile(imagePath, imageData, 'base64')
return `file://${imagePath}`
}
const removeTask = (taskUri?: string) => {
const flag = taskQueue.current.find((task) => task.args.uri === taskUri)
if (flag) {
taskQueue.current = taskQueue.current.filter((task) => task.args.uri !== taskUri)
if (taskQueue.current.length > 0) {
drawImage(taskQueue.current[0])
}
}
}
useImperativeHandle(ref, () => ({ addWatermark, removeTask }), [])
return <Canvas ref={canvasRef} style={{ position: 'absolute', left: -9999, top: -9999 }} />
})
export default WatermarkRoot