image.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. import { downloadFile, base64ToPathFn } from '../utils/util'
  2. /**
  3. * 绘制图片
  4. */
  5. export default class DrawImage {
  6. constructor(params) {
  7. const { Context, commonUtilMethods, commonDrawMethods } = params
  8. this.Context = Context
  9. this.commonUtilMethods = commonUtilMethods
  10. this.commonDrawMethods = commonDrawMethods
  11. }
  12. /**
  13. * 获取绘制参数
  14. * @param { Object } params 参数
  15. * @param { Boolean } conversion 是否需要转换单位
  16. */
  17. getDrawParams(params = {}, conversion = true) {
  18. const globalDataParams = this.commonUtilMethods.getGlobalDataDrawParams(params)
  19. // 绘制图片的参数
  20. const {
  21. src = '', mode = 'aspectFill', triangle = {}, isCompressImage = false,
  22. quality = 100, drawSrc = false, drawModeImage = false, drawImageInfo = false
  23. } = params
  24. const imageParams = {
  25. ...globalDataParams,
  26. src,
  27. mode,
  28. drawType: params.drawType || 'default',
  29. // 绘制三角形图片时三角形的内容
  30. // triangle.type 三角形的类型 right: 直角三角形 isosceles: 等腰三角形 custom: 自定义三角形(不支持旋转)
  31. // triangle.coordinate 自定义三角形时传递的坐标
  32. // triangle.direction 三角形顶点朝向
  33. triangle,
  34. // 是否压缩图片
  35. isCompressImage,
  36. // 压缩图片时图片的质量只对jpg类型的图片生效
  37. quality,
  38. drawSrc,
  39. drawModeImage,
  40. drawImageInfo,
  41. drawImage: true
  42. }
  43. return conversion ? this.commonUtilMethods.conversionUnit(imageParams) : imageParams
  44. }
  45. /**
  46. * 绘制图片
  47. * @param { Object } params 矩形参数
  48. * @param { Boolean } conversion 是否需要转换单位
  49. */
  50. draw(params = {}, conversion = true) {
  51. const { Context, commonUtilMethods, commonDrawMethods } = this
  52. const { canvasWidth, is2d } = commonUtilMethods
  53. let {
  54. w, src, windowAlign, drawModeImage, offsetRight, x, ...otherParams
  55. } = this.getDrawParams(params, conversion)
  56. return new Promise(async resolve => {
  57. try {
  58. if (!/\S/.test(src)) {
  59. return resolve({
  60. success: false,
  61. message: '图片路径为空'
  62. })
  63. }
  64. if (!is2d) {
  65. // 不是2d需要先下载本地
  66. let srcRes = await getImageSrc(src)
  67. if (!srcRes.success) {
  68. return resolve(srcRes)
  69. }
  70. src = srcRes.src
  71. }
  72. if (windowAlign !== 'none') {
  73. x = commonDrawMethods.computedCenterX(canvasWidth, w, windowAlign, offsetRight)
  74. }
  75. let imageInfo = drawModeImage
  76. if (!imageInfo) {
  77. imageInfo = await getImageInfo(src)
  78. if (!imageInfo.success) {
  79. // uni.hideLoading()
  80. return resolve(imageInfo)
  81. }
  82. }
  83. const img = this.getImage(src)
  84. const drawImageParams = {...otherParams, x, imageInfo, img, w, src, windowAlign, drawModeImage, offsetRight}
  85. if (is2d) {
  86. img.onload = async () => {
  87. return resolve(await this.drawImageByType(drawImageParams))
  88. }
  89. } else {
  90. return resolve(await this.drawImageByType(drawImageParams))
  91. }
  92. } catch (e) {
  93. return resolve({
  94. success: false,
  95. msg: '绘制图片出错' + e
  96. })
  97. }
  98. })
  99. }
  100. /**
  101. * 绘制参数
  102. * @param { Object } params
  103. * @returns
  104. */
  105. drawImageByType(params) {
  106. let {
  107. imageInfo, r, x, y, w, h, rotate,
  108. borderWidth, borderColor, color, alpha, borderType, triangle,
  109. mode, drawType, img, shadow, drawSrc, drawModeImage
  110. } = params
  111. const { Context, commonDrawMethods, commonUtilMethods } = this
  112. const { unit, pxToRpx } = commonUtilMethods
  113. return new Promise(async resolve => {
  114. if (h === 0) {
  115. h = w
  116. }
  117. let modeImage = drawModeImage
  118. if (!modeImage) {
  119. modeImage = getModeImage(Number(imageInfo.width), Number(imageInfo.height), x, y, w, h, mode)
  120. // this.conversionUnit(await getImageInfo(src))
  121. }
  122. const { dx, dy, dw, dh, sw, sh, sx, sy } = modeImage
  123. Context.save()
  124. Context.beginPath()
  125. if (drawType !== 'triangle') {
  126. commonDrawMethods.setRotate(x, y, w, h, rotate)
  127. }
  128. const drawEnd = async () => {
  129. Context.clip()
  130. commonDrawMethods.setAlpha(alpha)
  131. await this.drawImageContent(mode, drawType, img, dx, dy, dw, dh, sx, sy, sw, sh)
  132. Context.restore()
  133. }
  134. if (drawType == 'default') {
  135. // commonDrawMethods.drawRect.draw else if (drawType == 'arc')({
  136. // x,
  137. // y,
  138. // w,
  139. // h,
  140. // alpha,
  141. // color,
  142. // drawImage: true
  143. // })
  144. commonDrawMethods.setAlpha(alpha)
  145. await this.drawImageContent(mode, drawType, img, dx, dy, dw, dh, sx, sy, sw, sh, x, y, w, h)
  146. Context.clip()
  147. Context.restore()
  148. } else if (drawType === 'arc') {
  149. commonDrawMethods.drawParams.drawArc.draw({
  150. x,
  151. y,
  152. r: w / 2,
  153. borderWidth,
  154. borderColor,
  155. color,
  156. drawImage: true,
  157. shadow
  158. }, false)
  159. drawEnd()
  160. } else if (drawType === 'rect') {
  161. if (mode === 'widthFix') {
  162. h = sh
  163. } else if (mode === 'heightFix') {
  164. w = sw
  165. }
  166. commonDrawMethods.drawParams.drawRect.draw({
  167. x,
  168. y,
  169. w,
  170. h,
  171. alpha,
  172. borderWidth,
  173. borderColor,
  174. borderType,
  175. r,
  176. color,
  177. drawImage: true,
  178. shadow
  179. }, false)
  180. drawEnd()
  181. } else if (drawType === 'triangle') {
  182. // 绘制三角形图片
  183. let type = triangle.type || 'isosceles'
  184. let coordinate = triangle.coordinate || []
  185. let direction = triangle.direction || 'top'
  186. if (type !== 'custom') {
  187. commonDrawMethods.setTriangleRotate(x, y, w, h, rotate, type)
  188. }
  189. commonDrawMethods.drawParams.drawTriangle.draw({
  190. x,
  191. y,
  192. w,
  193. h,
  194. alpha,
  195. borderWidth,
  196. borderColor,
  197. color,
  198. coordinate,
  199. direction,
  200. drawType: type,
  201. drawImage: true,
  202. }, false)
  203. drawEnd()
  204. }
  205. commonDrawMethods.setAlpha(1)
  206. return resolve({
  207. success: true,
  208. w: unit === 'rpx' ? pxToRpx(w) : w,
  209. h: unit === 'rpx' ? pxToRpx(h) : h
  210. })
  211. })
  212. }
  213. /**
  214. * 绘制图片内容
  215. * @returns
  216. */
  217. drawImageContent(mode, drawType, img, dx, dy, dw, dh, sx, sy, sw, sh, x, y, w, h) {
  218. const { Context } = this
  219. return new Promise(async resolve => {
  220. if (drawType === 'default') {
  221. await Context.drawImage(img, x, y, w, h)
  222. } else if (mode !== 'default') {
  223. await Context.drawImage(img, dx, dy, dw, dh, sx, sy, sw, sh)
  224. } else {
  225. await Context.drawImage(img, dx, dy, dw, dh)
  226. }
  227. resolve(true)
  228. })
  229. }
  230. /**
  231. * 获取绘制图片的内容
  232. * @param { String } src 资源目录
  233. */
  234. getImage(src) {
  235. const { commonUtilMethods } = this
  236. const { is2d, canvas } = commonUtilMethods
  237. if (is2d) {
  238. const img = canvas.createImage()
  239. // const imageData = canvas.createImageData()
  240. img.src = src
  241. return img
  242. } else {
  243. return src
  244. }
  245. }
  246. }
  247. /**
  248. * 获取使用模式的图片信息
  249. * @param { String | Number } oWidth 原图宽度
  250. * @param { String | Number } oHeight 原图高度
  251. * @param { String | Number } x x轴位置
  252. * @param { String | Number } y y轴位置
  253. * @param { String | Number } width 宽度
  254. * @param { String | Number } height 高度
  255. * @param { String } mode 模式
  256. * aspectFit 保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
  257. * aspectFill 保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
  258. * widthFix 宽度不变,高度自动变化,保持原图宽高比不变
  259. * heightFix 高度不变,宽度自动变化,保持原图宽高比不变
  260. */
  261. export const getModeImage = (oWidth, oHeight, x, y, width, height, mode) => {
  262. if (mode === 'aspectFit') {
  263. return getAspectFitModelInfo(oWidth, oHeight, x, y, width, height)
  264. }
  265. if (mode === 'aspectFill') {
  266. return getAspectFillModelInfo(oWidth, oHeight, x, y, width, height)
  267. }
  268. if (mode === 'widthFix') {
  269. return getWidthFixModelInfo(oWidth, oHeight, x, y, width, height)
  270. }
  271. if (mode === 'heightFix') {
  272. return getHeightFixModelInfo(oWidth, oHeight, x, y, width, height)
  273. }
  274. if (mode === 'default') {
  275. return {
  276. dw: width,
  277. dh: height,
  278. dx: x,
  279. dy: y
  280. }
  281. }
  282. return getAspectFillModelInfo(oWidth, oHeight, x, y, width, height)
  283. }
  284. // aspectFit 保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,可以完整地将图片显示出来。
  285. function getAspectFitModelInfo(oWidth, oHeight, x, y, width, height) {
  286. let aspect = oHeight / oWidth
  287. let sw = width
  288. let sh = aspect * sw
  289. if (aspect >= 1) {
  290. aspect = oWidth / oHeight
  291. sh = height
  292. sw = aspect * sh
  293. }
  294. return {
  295. sw,
  296. sh,
  297. sx: x + ((width - sw) / 2),
  298. sy: y + ((height - sh) / 2),
  299. dw: oWidth,
  300. dh: oHeight,
  301. dx: 0,
  302. dy: 0
  303. }
  304. }
  305. // 保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片通常只在水平或垂直方向是完整的,另一个方向将会发生截取。
  306. function getAspectFillModelInfo(oWidth, oHeight, x, y, width, height) {
  307. // 高比宽大 宽是短边
  308. let aspect = oHeight / oWidth
  309. let sw = width
  310. let sh = aspect * sw
  311. let dx = 0
  312. let dy = (oHeight - (height * (oHeight / sh))) / 2
  313. if (aspect < 1) {
  314. // 高比宽小 高是短边
  315. aspect = oWidth / oHeight
  316. sh = height
  317. sw = aspect * sh
  318. dy = 0
  319. dx = (oWidth - (width * (oWidth / sw))) / 2
  320. }
  321. return {
  322. sw,
  323. sh,
  324. sx: x,
  325. sy: y,
  326. dw: oWidth,
  327. dh: oHeight,
  328. dx,
  329. dy
  330. }
  331. }
  332. // 宽度不变,高度自动变化,保持原图宽高比不变
  333. function getWidthFixModelInfo(oWidth, oHeight, x, y, width, height) {
  334. let aspect = oHeight / oWidth
  335. let sw = width
  336. let sh = sw * aspect
  337. let dx = 0
  338. let dy = 0
  339. return {
  340. sw,
  341. sh,
  342. sx: x,
  343. sy: y,
  344. dw: oWidth,
  345. dh: oHeight,
  346. dx,
  347. dy
  348. }
  349. }
  350. // 高度不变,宽度自动变化,保持原图宽高比不变
  351. function getHeightFixModelInfo(oWidth, oHeight, x, y, width, height) {
  352. let aspect = oWidth / oHeight
  353. let sh = height
  354. let sw = sh * aspect
  355. let dx = 0
  356. let dy = 0
  357. return {
  358. sw,
  359. sh,
  360. sx: x,
  361. sy: y,
  362. dw: oWidth,
  363. dh: oHeight,
  364. dx,
  365. dy
  366. }
  367. }
  368. /**
  369. * 获取图片信息
  370. * @param { String } src 图片地址
  371. */
  372. export const getImageInfo = src => {
  373. return new Promise(resolve =>{
  374. uni.getImageInfo({
  375. src,
  376. success: res => {
  377. resolve({
  378. success: true,
  379. ...res
  380. })
  381. },
  382. fail: e => {
  383. resolve({
  384. success: false,
  385. msg: e
  386. })
  387. }
  388. })
  389. })
  390. }
  391. /**
  392. * 获取图片src路径
  393. * @param { String } src 图片地址
  394. * @returns
  395. */
  396. export function getImageSrc(src) {
  397. return new Promise(async resolve => {
  398. src = await base64ToPathFn(src)
  399. // #ifndef MP-TOUTIAO
  400. if (src.includes('http') && !src.includes('resource')) {
  401. const downlaod = await downloadFile(src)
  402. if (!downlaod.success) {
  403. return resolve({
  404. success: false,
  405. msg: `图片路径为:${src}的文件下载失败`
  406. })
  407. }
  408. src = downlaod.data.tempFilePath
  409. }
  410. // #endif
  411. return resolve({
  412. success: true,
  413. src
  414. })
  415. })
  416. }