uni-canvas-image 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "uni-canvas-image",
3
+ "version": "1.0.0",
4
+ "description": "租房项目使用uniapp canvas合并图片",
5
+ "main": "src/index.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 1"
9
+ },
10
+ "author": "",
11
+ "license": "ISC"
12
+ }
@@ -0,0 +1,137 @@
1
+ export const directionEnum = { NORTH: '北', EAST: '东', SOUTH: '南', WEST: '西', NORTH_EAST: '东北', SOUTH_EAST: '东南', SOUTH_WEST: '西南', NORTH_WEST: '西北' }
2
+
3
+ export async function getImageInfo(src, targetRatio) {
4
+ return new Promise(async (resolve, reject) => {
5
+ if (src && src.endsWith('.mp4')) src = src.replace('.mp4', '.png')
6
+ if (src) {
7
+ const imageInfo = await uni.getImageInfo({ src })
8
+ const newImageInfo = {
9
+ ...imageInfo,
10
+ ...getMaxSizeByRatio(imageInfo.width, imageInfo.height, targetRatio),
11
+ type: 'image',
12
+ }
13
+ resolve(newImageInfo)
14
+ } else {
15
+ resolve({
16
+ type: 'rect2',
17
+ color: '#f2f2f2',
18
+ })
19
+ }
20
+ })
21
+ }
22
+
23
+ function getMaxSizeByRatio(width, height, targetRatio = 5 / 4) {
24
+ // const targetRatio = 5 / 4 // 1.25
25
+ const originalRatio = width / height
26
+ let sWidth, sHeight
27
+ let sx = 0
28
+ let sy = 0
29
+
30
+ if (originalRatio > targetRatio) {
31
+ // 原始图片更宽,以高度为基准
32
+ sHeight = height
33
+ sWidth = Math.round(sHeight * targetRatio)
34
+ sx = (width - sWidth) / 2
35
+ } else {
36
+ // 原始图片更高或比例相同,以宽度为基准
37
+ sWidth = width
38
+ sHeight = Math.round(sWidth / targetRatio)
39
+ sy = (height - sHeight) / 2
40
+ }
41
+
42
+ return { sx, sy, sWidth, sHeight }
43
+ }
44
+
45
+ export async function base64ToBlob(base64Data) {
46
+ const response = await fetch(base64Data)
47
+ return await response.blob()
48
+ }
49
+
50
+ export async function drawImageToCanvas(arr, canvasId = 'canvas-1', api) {
51
+ return new Promise((resolve, reject) => {
52
+ const ctx = uni.createCanvasContext(canvasId)
53
+ arr.forEach((item) => {
54
+ if (item.type === 'image') {
55
+ const { path, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight } = item
56
+ ctx.drawImage(path, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)
57
+ } else if (item.type === 'text') {
58
+ const { text, x = 10, y = 20, fontSize = 16, color = '#000' } = item
59
+ ctx.setFontSize(fontSize)
60
+ ctx.setFillStyle(color)
61
+ ctx.setTextBaseline('top')
62
+ ctx.fillText(text, x, y)
63
+ } else if (item.type === 'rect') {
64
+ const { x, y, width, height, borderRadius, color } = item
65
+ ctx.fillStyle = color // 填充
66
+ roundedRectPath(ctx, x, y, width, height, borderRadius) // x, y, width, height, radius
67
+ ctx.fill()
68
+ } else if (item.type === 'rect2') {
69
+ console.log(item, 'item')
70
+ const { dx: x, dy: y, dWidth: width, dHeight: height, color } = item
71
+ ctx.setFillStyle(color) // 填充
72
+ ctx.fillRect(x, y, width, height) // x, y
73
+ }
74
+ })
75
+ ctx.draw(false, () => {
76
+ uni.canvasToTempFilePath({
77
+ canvasId,
78
+ fileType: 'jpg',
79
+ quality: 0.1,
80
+ destWidth: 1500,
81
+ destHeight: 1200,
82
+ success: async (tempRes) => {
83
+ ctx.clearRect(0, 0, 375, 300)
84
+ let filePath
85
+ // #ifdef MP-WEIXIN
86
+ filePath = tempRes.tempFilePath
87
+ // #endif
88
+ // #ifdef H5
89
+ const blob = await base64ToBlob(tempRes.tempFilePath)
90
+ filePath = URL.createObjectURL(blob)
91
+ // #endif
92
+ if (!api) return resolve(filePath)
93
+ else {
94
+ const { result } = await api('/third-party/open/upload/uploadFile', filePath, {}, 'file')
95
+ resolve(result)
96
+ // #ifdef MP-WEIXIN
97
+ uni.getFileSystemManager().unlink({ filePath })
98
+ // #endif
99
+ // #ifdef H5
100
+ URL.revokeObjectURL(filePath)
101
+ // #endif
102
+ }
103
+ },
104
+ fail: (err) => {
105
+ console.error('生成临时文件失败:', err)
106
+ },
107
+ })
108
+ })
109
+ })
110
+ }
111
+
112
+ function roundedRectPath(ctx, x, y, width, height, radius) {
113
+ // 防止半径超过宽高一半
114
+ radius = Math.min(radius, width / 2, height / 2)
115
+ ctx.beginPath()
116
+ ctx.moveTo(x + radius, y) // 左上开始,略过圆角
117
+ ctx.lineTo(x + width - radius, y) // 上边线
118
+ ctx.quadraticCurveTo(x + width, y, x + width, y + radius) // 右上圆角
119
+ ctx.lineTo(x + width, y + height - radius) // 右边线
120
+ ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height) // 右下圆角
121
+ ctx.lineTo(x + radius, y + height) // 下边线
122
+ ctx.quadraticCurveTo(x, y + height, x, y + height - radius) // 左下圆角
123
+ ctx.lineTo(x, y + radius) // 左边线
124
+ ctx.quadraticCurveTo(x, y, x + radius, y) // 左上圆角
125
+ ctx.closePath()
126
+ }
127
+
128
+ export function handleParagraph(text, lineLength = 25, line = 2) {
129
+ if (!text) return []
130
+ const textArray = []
131
+ for (let i = 0; i < line; i++) {
132
+ let str = text.slice(i * lineLength, (i + 1) * lineLength)
133
+ if (i === line - 1 && text.length > (i + 1) * lineLength) str = str.slice(0, -1) + '...'
134
+ if (str.trim()) textArray.push(str)
135
+ }
136
+ return textArray
137
+ }
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as houseImage } from './util/houseImage'
2
+ export { default as noteImage } from './util/noteImage'
3
+ export { default as coverImage } from './util/coverImage'
@@ -0,0 +1,74 @@
1
+ import { getMaxSizeByRatio, drawImageToCanvas } from '../common/index'
2
+
3
+ const canvasSize = {
4
+ w: 375,
5
+ h: 300,
6
+ }
7
+ const imgSize = {
8
+ w: 0,
9
+ h: 0,
10
+ }
11
+
12
+ const coverImage = (params = {}) => {
13
+ const { data, canvasId, api } = params
14
+ return new Promise(async (resolve, reject) => {
15
+ const imgs = data?.filter((i) => i.type === 'image').map((i) => i.content)
16
+ if (imgs.length === 0) return resolve('')
17
+ else if (imgs.length < 4) return resolve(imgs[0]) //小于4 为1张不用拼图
18
+ const newImageInfos = await handleImgInfo(imgs)
19
+ const drawImgs = await calculateOperation(newImageInfos)
20
+ const newDrawImgs = drawImgs.map((i) => {
21
+ return {
22
+ ...i,
23
+ dx: i.x,
24
+ dy: i.y,
25
+ dWidth: imgSize.w,
26
+ dHeight: imgSize.h,
27
+ type: 'image',
28
+ }
29
+ })
30
+ const url = await drawImageToCanvas(newDrawImgs, canvasId, api)
31
+ resolve(url)
32
+ })
33
+ }
34
+
35
+ async function handleImgInfo(imgs) {
36
+ return new Promise(async (resolve, reject) => {
37
+ const imageInfos = await Promise.all(imgs.map((src) => uni.getImageInfo({ src })))
38
+ const newImageInfos = imageInfos.map((i) => {
39
+ return {
40
+ ...i,
41
+ ...getMaxSizeByRatio(i.width, i.height),
42
+ }
43
+ })
44
+ resolve(newImageInfos)
45
+ })
46
+ }
47
+ function calculateOperation(newImageInfos) {
48
+ let drawImgs = []
49
+ let count
50
+ const length = newImageInfos.length
51
+ if (length === 4 || length < 9) {
52
+ imgSize.w = canvasSize.w / 2
53
+ imgSize.h = canvasSize.h / 2
54
+ count = 4
55
+ } else if (length >= 9) {
56
+ imgSize.w = canvasSize.w / 3
57
+ imgSize.h = canvasSize.h / 3
58
+ count = 9
59
+ }
60
+ drawImgs = newImageInfos.slice(0, count)
61
+ drawImgs = drawImgs.map((i, index) => {
62
+ if (count === 4) {
63
+ i.x = index % 2 === 0 ? 0 : canvasSize.w / 2
64
+ i.y = index < 2 ? 0 : canvasSize.h / 2
65
+ } else if (count === 9) {
66
+ i.x = index % 3 === 0 ? 0 : index % 3 === 1 ? canvasSize.w / 3 : (canvasSize.w / 3) * 2
67
+ i.y = index < 3 ? 0 : index < 6 ? canvasSize.h / 3 : (canvasSize.h / 3) * 2
68
+ }
69
+ return i
70
+ })
71
+ return drawImgs
72
+ }
73
+
74
+ export default coverImage
@@ -0,0 +1,144 @@
1
+ import { directionEnum, getImageInfo, drawImageToCanvas, handleParagraph } from '../common/index'
2
+ const categoryEnum = [
3
+ { text: '单价', x: 10, y: 246, fontSize: 14, color: '#9B9B9B' },
4
+ { text: '(元/m²/天)', x: 34, y: 248, fontSize: 10, color: '#9B9B9B' },
5
+ { text: '面积', x: 160, y: 246, fontSize: 14, color: '#9B9B9B' },
6
+ { text: '(m²)', x: 184, y: 248, fontSize: 10, color: '#9B9B9B' },
7
+ { text: '朝向', x: 310, y: 246, fontSize: 14, color: '#9B9B9B' },
8
+ { text: '¥', x: 10, y: 276, fontSize: 10, color: '#FF703B' },
9
+ { name: '单价', key: 'rentDayCeilPrice', x: 20, y: 270, fontSize: 18, color: '#FF703B' },
10
+ { name: '面积', key: 'houseArea', x: 160, y: 270, fontSize: 18, color: '#FF703B' },
11
+ { name: '朝向', key: 'direction', x: 310, y: 270, fontSize: 18, color: '#FF703B' },
12
+ ]
13
+ /**
14
+ *
15
+ * @param {Object Array} data 房源数据数组
16
+ * @returns {String} 处理后的图片URL
17
+ */
18
+ async function houseImage(params = {}) {
19
+ const { data, canvasId, api } = params
20
+ if (!data) return reject('数据不能为空')
21
+ const items = Array.isArray(data) ? data : [data]
22
+ return items.length > 1
23
+ ? await handleItems(items, canvasId, api) // 多个房源
24
+ : await handleItem(items[0], canvasId, api) // 单个房源
25
+ }
26
+ //单个房源
27
+ async function handleItem(obj, canvasId, api) {
28
+ return new Promise(async (resolve, reject) => {
29
+ const imgInfo = await getImageInfo(obj.imageUrl, 750 / 340)
30
+ const newImgInfo = {
31
+ ...imgInfo,
32
+ dx: 0,
33
+ dy: 0,
34
+ dWidth: 375,
35
+ dHeight: 170,
36
+ }
37
+ const paragraphInfos = handleParagraph(obj.title)
38
+ const titleInfos = paragraphInfos.map((text, index) => {
39
+ return {
40
+ type: 'text',
41
+ text,
42
+ x: 10,
43
+ y: 180 + index * 20,
44
+ fontSize: 14,
45
+ color: '#000',
46
+ }
47
+ })
48
+ const tagsInfos = hanleTagsInfo(obj.tags, 14, titleInfos.length > 1 ? 224 : 216)
49
+ const categoryInfos = handleCategoryInfo(obj, categoryEnum)
50
+ const newObj = [newImgInfo, ...titleInfos, ...tagsInfos, ...categoryInfos]
51
+ const url = await drawImageToCanvas(newObj, canvasId, api)
52
+ resolve(url)
53
+ })
54
+ }
55
+ function handleItems(arr, canvasId, api) {
56
+ return new Promise(async (resolve, reject) => {
57
+ const newObj = []
58
+ const newArr = arr.slice(0, 2)
59
+ console.log('🚀 ~ handleItems ~ newArr:', newArr)
60
+ for (let i = 0; i < newArr.length; i++) {
61
+ const obj = newArr[i]
62
+ // 图片
63
+ const imgInfo = await getImageInfo(obj.imageUrl, 1 / 1)
64
+ const newImgInfo = {
65
+ ...imgInfo,
66
+ dx: 0,
67
+ dy: i * 152,
68
+ dWidth: 148,
69
+ dHeight: 148,
70
+ }
71
+ // 标题
72
+ const paragraphInfos = handleParagraph(obj.title, 14)
73
+ const titleInfos = paragraphInfos.map((text, index) => {
74
+ return {
75
+ type: 'text',
76
+ text,
77
+ x: 160,
78
+ y: 10 + i * 152 + index * 20,
79
+ fontSize: 14,
80
+ color: '#000',
81
+ }
82
+ })
83
+ // 标签
84
+ const tagsInfos = hanleTagsInfo(obj.tags, 160, 60 + i * 152)
85
+ // 分类信息
86
+ const category = [
87
+ { text: '单价', x: 160, y: 90 + i * 152, fontSize: 14, color: '#9B9B9B' },
88
+ { text: '(元/m²/天)', x: 184, y: 92 + i * 152, fontSize: 10, color: '#9B9B9B' },
89
+ { text: '面积', x: 290, y: 90 + i * 152, fontSize: 14, color: '#9B9B9B' },
90
+ { text: '(m²)', x: 314, y: 92 + i * 152, fontSize: 10, color: '#9B9B9B' },
91
+ { text: '¥', x: 160, y: 124 + i * 152, fontSize: 10, color: '#FF703B' },
92
+ { name: '单价', key: 'rentDayCeilPrice', x: 170, y: 118 + i * 152, fontSize: 18, color: '#FF703B' },
93
+ { name: '面积', key: 'houseArea', x: 290, y: 118 + i * 152, fontSize: 18, color: '#FF703B' },
94
+ ]
95
+ const categoryInfos = handleCategoryInfo(obj, category)
96
+ newObj.push(newImgInfo, ...titleInfos, ...tagsInfos, ...categoryInfos)
97
+ }
98
+ const url = await drawImageToCanvas(newObj, canvasId, api)
99
+ resolve(url)
100
+ })
101
+ }
102
+
103
+ function hanleTagsInfo(tags, x = 14, y = 224) {
104
+ if (!tags || tags.length === 0) return []
105
+ const tagsInfos = tags.map((tag, index) => {
106
+ return {
107
+ type: 'text',
108
+ text: tag,
109
+ // 前面标签的长度和间距计算x坐标
110
+ x: x + tags.slice(0, index).reduce((acc, cur) => acc + cur.length, 0) * 12 + index * 18, // 根据
111
+ y,
112
+ fontSize: 12,
113
+ color: '#377DF3',
114
+ }
115
+ })
116
+ //tga背景颜色
117
+ const rects = tagsInfos.map((tag, index) => {
118
+ return {
119
+ type: 'rect',
120
+ x: tag.x - 4,
121
+ y: tag.y - 6,
122
+ width: tag.text.length * 12 + 8,
123
+ height: 20,
124
+ borderRadius: 4,
125
+ color: '#ebf1ff',
126
+ }
127
+ })
128
+ return [...rects, ...tagsInfos]
129
+ }
130
+
131
+ function handleCategoryInfo(obj, categoryEnum) {
132
+ const newArr = categoryEnum.map((item) => {
133
+ item.type = 'text'
134
+ if (item.name) {
135
+ if (item.key === 'direction') {
136
+ item.text = directionEnum[obj?.direction[0]] + (obj?.direction.length > 1 ? '...' : '')
137
+ } else item.text = obj[item.key] || ''
138
+ }
139
+ return item
140
+ })
141
+ return newArr
142
+ }
143
+
144
+ export default houseImage
@@ -0,0 +1,98 @@
1
+ import { getImageInfo, drawImageToCanvas, handleParagraph } from '../common/index'
2
+ /**
3
+ *
4
+ * @param {Object Array} data 笔记数据数组
5
+ * @returns {String} 处理后的图片URL
6
+ */
7
+ async function noteImage(params = {}) {
8
+ const { data, canvasId, api } = params
9
+ if (!data) return reject('数据不能为空')
10
+ const items = Array.isArray(data) ? data : [data]
11
+ return items.length > 1
12
+ ? await handleItems(items, canvasId, api) // 多个房源
13
+ : await handleItem(items[0], canvasId, api) // 单个房源
14
+ }
15
+
16
+ function handleItem(obj, canvasId) {
17
+ return new Promise(async (resolve, reject) => {
18
+ const imgInfo = await getImageInfo(obj.imageUrl, 750 / 340)
19
+ const newImgInfo = {
20
+ ...imgInfo,
21
+ dx: 0,
22
+ dy: 0,
23
+ dWidth: 375,
24
+ dHeight: 170,
25
+ }
26
+ const paragraphInfos = handleParagraph(obj.title)
27
+ const titleInfos = paragraphInfos.map((text, index) => {
28
+ return {
29
+ type: 'text',
30
+ text,
31
+ x: 10,
32
+ y: 180 + index * 20,
33
+ fontSize: 14,
34
+ color: '#000',
35
+ }
36
+ })
37
+ const contentInfos = handleParagraph(obj.content, 31, 3)
38
+ const newContentInfos = contentInfos.map((text, index) => {
39
+ return {
40
+ type: 'text',
41
+ text,
42
+ x: 10,
43
+ y: 230 + index * 18,
44
+ fontSize: 12,
45
+ color: '#9B9B9B',
46
+ }
47
+ })
48
+ const newObj = [newImgInfo, ...titleInfos, ...newContentInfos]
49
+ const url = await drawImageToCanvas(newObj, canvasId, api)
50
+ resolve(url)
51
+ })
52
+ }
53
+ function handleItems(arr, canvasId) {
54
+ return new Promise(async (resolve, reject) => {
55
+ const newObj = []
56
+ const newArr = arr.slice(0, 2)
57
+ for (let i = 0; i < newArr.length; i++) {
58
+ const obj = newArr[i]
59
+ // 图片
60
+ const imgInfo = await getImageInfo(obj.imageUrl, 1 / 1)
61
+ const newImgInfo = {
62
+ ...imgInfo,
63
+ dx: 10,
64
+ dy: i * 140 + 10,
65
+ dWidth: 120,
66
+ dHeight: 120,
67
+ }
68
+ const paragraphInfos = handleParagraph(obj.title, 16)
69
+ const titleInfos = paragraphInfos.map((text, index) => {
70
+ return {
71
+ type: 'text',
72
+ text,
73
+ x: 144,
74
+ y: 10 + i * 140 + index * 20,
75
+ fontSize: 14,
76
+ color: '#000',
77
+ }
78
+ })
79
+ const contentInfos = handleParagraph(obj.content, 24, 4)
80
+ const newContentInfos = contentInfos.map((text, index) => {
81
+ return {
82
+ type: 'text',
83
+ text,
84
+ x: 144,
85
+ y: 60 + i * 140 + index * 18,
86
+ fontSize: 10,
87
+ color: '#9B9B9B',
88
+ }
89
+ })
90
+ newObj.push(newImgInfo, ...titleInfos, ...newContentInfos)
91
+ }
92
+
93
+ const url = await drawImageToCanvas(newObj, canvasId, api)
94
+ resolve(url)
95
+ })
96
+ }
97
+
98
+ export default noteImage