import { Canvas } from '@tarojs/components' import Taro from '@tarojs/taro' import { forwardRef, useImperativeHandle, useRef, useState } from 'react' import Downloader from './lib/downloader' import Pen from './lib/pen' import { equal, toPx } from './lib/util' /** * * 入参 palette 格式 * const imgDraw = { width: '1124rpx', height: '1578rpx', background: getCDNSource('/user/inviteCodePopup.png'), views: [ { type: 'image', url: 'https://qiniu-image.qtshe.com/1560248372315_467.jpg', css: { top: '32rpx', left: '30rpx', right: '32rpx', width: '688rpx', height: '420rpx', borderRadius: '16rpx', }, }, { type: 'text', text: '青团子', css: { top: '532rpx', fontSize: '28rpx', left: '375rpx', align: 'center', color: '#3c3c3c', }, }, ], } * */ const downloader = new Downloader() let SystemInfo = Taro.getSystemInfoSync() // 最大尝试的绘制次数 const MAX_PAINT_COUNT = 5 interface PropsType { customStyle?: React.CSSProperties painterStyle?: React.CSSProperties dirty?: boolean palette?: any // 参考上方 onImgErr: (result) => void onImgOK: (result) => void } // 绘画 const Painter = (props: PropsType, ref) => { const { customStyle, painterStyle: _painterStyle, dirty, palette, onImgErr, onImgOK } = props const [painterStyle, setPainterStyle] = useState(_painterStyle) const canvasWidthInPx = useRef(0) const canvasHeightInPx = useRef(0) const paintCount = useRef(0) const [, setForceUpdate] = useState({}) const canvasNode = useRef(null) const ctx = useRef(null) /** * 判断一个 object 是否为 空 * @param {object} object */ const isEmpty = (object) => { for (const i in object) { if (i) { return false } } return true } const isNeedRefresh = (newVal, oldVal) => { if (!newVal || isEmpty(newVal) || (dirty && equal(newVal, oldVal))) { return false } return true } const downloadImages = () => { return new Promise((resolve, reject) => { let preCount = 0 let completeCount = 0 const paletteCopy = JSON.parse(JSON.stringify(palette)) if (paletteCopy.background) { preCount++ downloader.download(paletteCopy.background).then((path) => { paletteCopy.background = path completeCount++ if (preCount === completeCount) { resolve(paletteCopy) } }, () => { completeCount++ if (preCount === completeCount) { resolve(paletteCopy) } }) } if (paletteCopy.views) { for (const view of paletteCopy.views) { if (view && view.type === 'image' && view.url) { preCount++ console.log('url', view.url) /* eslint-disable no-loop-func */ downloader.download(view.url).then((path) => { console.log('path', path) view.url = path Taro.getImageInfo({ src: view.url, success: (res) => { // 获得一下图片信息,供后续裁减使用 view.sWidth = res.width view.sHeight = res.height }, fail: (error) => { console.log(`imgDownloadErr failed, ${JSON.stringify(error)}`) onImgErr({ error }) }, complete: () => { completeCount++ if (preCount === completeCount) { resolve(paletteCopy) } }, }) }, () => { completeCount++ if (preCount === completeCount) { resolve(paletteCopy) } }) } } } if (preCount === 0) { resolve(paletteCopy) } }) } // 初始化 canvas const initCanvas = async() => { return new Promise<{ canvas: Taro.Canvas; ctx: Taro.RenderingContext }>((resolve, reject) => { Taro.nextTick(() => { const query = Taro.createSelectorQuery() query.select('#canvas').node(({ node: canvas }: { node: Taro.Canvas }) => { console.log('canvas==>', canvas) const context = canvas.getContext('2d') console.log('ctx', context) canvasNode.current = canvas ctx.current = context console.log('canvas', canvas) setForceUpdate({}) resolve({ canvas: canvasNode.current, ctx: ctx.current }) }).exec() }) }) } // 开始绘制 const _startPaint = () => { if (isEmpty(palette)) { return } console.log('startPaint', palette) if (!(SystemInfo && SystemInfo.screenWidth)) { try { SystemInfo = Taro.getSystemInfoSync() } catch (e) { const error = `Painter get system info failed, ${JSON.stringify(e)}` // that.triggerEvent('imgErr', { error }) onImgErr({ error }) console.log(error) return } } downloadImages().then((palette: any) => { const { width, height } = palette canvasWidthInPx.current = toPx(width) canvasHeightInPx.current = toPx(height) if (!width || !height) { console.log(`You should set width and height correctly for painter, width: ${width}, height: ${height}`) return } console.log('palette', palette, width, height) setPainterStyle({ width: `${width}`, height: `${height}`, }) // 初始化canvas initCanvas().then(({ ctx, canvas }) => { // const ctx = Taro.createCanvasContext('k-canvas', this) const pen = new Pen(ctx, canvas, palette) pen.paint((canvas) => { // eslint-disable-next-line @typescript-eslint/no-use-before-define saveImgToLocal(canvas) }) }) }) } const getImageInfo = (filePath: string) => { Taro.getImageInfo({ src: filePath, success: (infoRes) => { if (paintCount.current > MAX_PAINT_COUNT) { const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times` console.log(error) onImgErr({ error }) return } // 比例相符时才证明绘制成功,否则进行强制重绘制 if (Math.abs((infoRes.width * canvasHeightInPx.current - canvasWidthInPx.current * infoRes.height) / (infoRes.height * canvasHeightInPx.current)) < 0.01) { onImgOK({ path: filePath }) } else { console.log('infoRes filePath', infoRes, filePath, canvasHeightInPx.current, canvasWidthInPx.current) _startPaint() } paintCount.current++ }, fail: (error) => { console.log(`getImageInfo failed, ${JSON.stringify(error)}`) onImgErr({ error }) }, }) } const saveImgToLocal = (canvas) => { setTimeout(() => { Taro.canvasToTempFilePath({ canvas, fileType: 'png', success(res) { console.log('tempFilePath', res.tempFilePath, canvas) getImageInfo(res.tempFilePath) }, fail(error) { console.log(`canvasToTempFilePath failed, ${JSON.stringify(error)}`) // that.triggerEvent('imgErr', { error }) onImgErr({ error }) }, }, this) }, 300) } // 暴露 useImperativeHandle( ref, () => { return { startPaint: () => { paintCount.current = 0 _startPaint() }, } }, [paintCount.current], ) return } export default forwardRef(Painter)