2022-12-09 13:44:43 +08:00

269 lines
7.7 KiB
TypeScript

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<any>(null)
const ctx = useRef<any>(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 <Canvas style={{ ...customStyle, ...painterStyle }} id="canvas" type="2d" />
}
export default forwardRef(Painter)