/** * 在照片左下角叠加多行水印文字 * @param {string} imagePath - 原始图片路径 * @param {string|string[]} lines - 水印文字,字符串或字符串数组 * @param {object} [canvasCtx] - APP端传入 { canvasId, proxy, setSize } * @returns {Promise} 带水印的图片路径,失败时返回原图 */ export function addWatermark(imagePath, lines, canvasCtx) { if (typeof lines === 'string') { lines = [lines] } return new Promise((resolve) => { // 超时保护:10秒 const timer = setTimeout(() => { console.warn('[watermark] timeout') resolve(imagePath) }, 10000) _doWatermark(imagePath, lines, canvasCtx) .then((r) => { clearTimeout(timer); resolve(r || imagePath) }) .catch((e) => { clearTimeout(timer); console.warn('[watermark]', e); resolve(imagePath) }) }) } function _doWatermark(imagePath, lines, canvasCtx) { // #ifdef H5 return _h5(imagePath, lines) // #endif // #ifdef APP-PLUS return _app(imagePath, lines, canvasCtx) // #endif // eslint-disable-next-line no-unreachable return Promise.resolve(imagePath) } // ========== H5 ========== // #ifdef H5 function _h5(imagePath, lines) { return new Promise((resolve, reject) => { const img = new Image() img.crossOrigin = 'anonymous' img.onload = () => { try { const w = img.naturalWidth, h = img.naturalHeight const c = document.createElement('canvas') c.width = w; c.height = h const ctx = c.getContext('2d') ctx.drawImage(img, 0, 0, w, h) _stamp(ctx, w, h, lines, false) resolve(c.toDataURL('image/jpeg', 0.9)) } catch (e) { reject(e) } } img.onerror = reject img.src = imagePath }) } // #endif // ========== APP ========== // #ifdef APP-PLUS function _app(imagePath, lines, cc) { if (!cc || !cc.canvasId || !cc.proxy) { return Promise.reject(new Error('missing canvasCtx')) } return new Promise((resolve, reject) => { uni.getImageInfo({ src: imagePath, success(info) { try { // 限制 canvas 宽度 ≤ 1200,避免内存问题 let dw = info.width, dh = info.height if (dw > 1200) { const r = 1200 / dw; dw = 1200; dh = Math.round(info.height * r) } // 更新页面 canvas 尺寸 if (typeof cc.setSize === 'function') cc.setSize(dw, dh) // 等 DOM 刷新(200ms 足够 APP webview 更新) setTimeout(() => { try { const ctx = uni.createCanvasContext(cc.canvasId, cc.proxy) ctx.drawImage(imagePath, 0, 0, dw, dh) _stamp(ctx, dw, dh, lines, true) let done = false const exp = () => { if (done) return done = true uni.canvasToTempFilePath({ canvasId: cc.canvasId, x: 0, y: 0, width: dw, height: dh, destWidth: info.width, destHeight: info.height, fileType: 'jpg', quality: 0.9, success: (r) => resolve(r.tempFilePath), fail: reject }, cc.proxy) } ctx.draw(false, () => setTimeout(exp, 200)) // 兜底 setTimeout(exp, 1200) } catch (e) { reject(e) } }, 200) } catch (e) { reject(e) } }, fail: reject }) }) } // #endif /** * 在 canvas context 上绘制水印条 * @param {boolean} isUni - true 用 uni canvas API,false 用 H5 canvas API */ function _stamp(ctx, w, h, lines, isUni) { const fs = Math.max(Math.floor(w * 0.03), 14) const pad = Math.floor(fs * 0.8) const lh = fs + pad const bgH = lh * lines.length + pad if (isUni) { ctx.setFillStyle('rgba(0,0,0,0.4)') ctx.fillRect(0, h - bgH, w, bgH) ctx.setFillStyle('#ffffff') ctx.setFontSize(fs) lines.forEach((t, i) => { // uni canvas 的 fillText y 是文字顶部基线 ctx.fillText(t, pad, h - bgH + pad + lh * i) }) } else { ctx.fillStyle = 'rgba(0,0,0,0.4)' ctx.fillRect(0, h - bgH, w, bgH) ctx.fillStyle = '#ffffff' ctx.font = `${fs}px sans-serif` ctx.textBaseline = 'middle' lines.forEach((t, i) => { ctx.fillText(t, pad, h - bgH + pad / 2 + lh * i + fs / 2) }) } }