r-canvas.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. export default{
  2. data(){
  3. return{
  4. system_info:{}, //system info
  5. canvas_width:0, //canvas width px
  6. canvas_height:0, //canvas height px
  7. ctx:null, //canvas object
  8. canvas_id:null, //canvas id
  9. hidden:true,//Whether to hide canvas
  10. scale:1,//canvas scale
  11. r_canvas_scale:1,
  12. if_ctx:true
  13. }
  14. },
  15. methods:{
  16. /**
  17. * Draw round rect text
  18. * @param {Object} config
  19. * @param {Number} config.x x坐标
  20. * @param {Number} config.y y坐标
  21. * @param {Number} config.w 宽度
  22. * @param {Number} config.h 高度
  23. * @param {Number} config.radius 圆角弧度
  24. * @param {String} config.fill_color 矩形颜色
  25. */
  26. fillRoundRect(config) {
  27. return new Promise((resolve,reject)=>{
  28. let x = this.compatibilitySize(parseFloat(config.x)*this.scale)
  29. let y = this.compatibilitySize(parseFloat(config.y)*this.scale)
  30. let w = this.compatibilitySize(parseFloat(config.w)*this.scale)
  31. let h = this.compatibilitySize(parseFloat(config.h)*this.scale)
  32. let radius = config.radius?parseFloat(config.radius)*this.scale:10*this.scale
  33. let fill_color = config.fill_color || "black"
  34. // The diameter of the circle must be less than the width and height of the rectangle
  35. if (2 * radius > w || 2 * radius > h) {
  36. reject("The diameter of the circle must be less than the width and height of the rectangle")
  37. return false;
  38. }
  39. this.ctx.save();
  40. this.ctx.translate(x, y);
  41. //
  42. this.drawRoundRectPath({
  43. w: w,
  44. h: h,
  45. radius: radius
  46. });
  47. this.ctx.fillStyle = fill_color
  48. this.ctx.fill();
  49. this.ctx.restore();
  50. resolve()
  51. })
  52. },
  53. /**
  54. * Draws the sides of a rounded rectangle
  55. * @param {Object} config
  56. * @param {Number} config.w 宽度
  57. * @param {Number} config.h 高度
  58. * @param {Number} config.radius 圆角弧度
  59. */
  60. drawRoundRectPath(config) {
  61. this.ctx.beginPath(0);
  62. this.ctx.arc(config.w - config.radius, config.h - config.radius, config.radius, 0, Math.PI / 2);
  63. this.ctx.lineTo(config.radius, config.h);
  64. this.ctx.arc(config.radius, config.h - config.radius, config.radius, Math.PI / 2, Math.PI);
  65. this.ctx.lineTo(0, config.radius);
  66. this.ctx.arc(config.radius, config.radius, config.radius, Math.PI, Math.PI * 3 / 2);
  67. this.ctx.lineTo(config.w - config.radius, 0);
  68. this.ctx.arc(config.w - config.radius, config.radius, config.radius, Math.PI * 3 / 2, Math.PI * 2);
  69. this.ctx.lineTo(config.w, config.h - config.radius);
  70. this.ctx.closePath();
  71. },
  72. /**
  73. * Draw special Text,line wrapping is not supported
  74. * @param {Object} config
  75. * @param {String} config.text 文字
  76. * @param {Number} config.x x坐标
  77. * @param {Number} config.y y坐标
  78. * @param {String} config.font_color 文字颜色
  79. * @param {String} config.font_family 文字字体
  80. * @param {Number} config.font_size 文字大小(px)
  81. */
  82. drawSpecialText(params){
  83. let general = params.general
  84. let list = params.list
  85. return new Promise(async (resolve,reject)=>{
  86. if(!general){
  87. reject("general cannot be empty:101")
  88. return;
  89. }else if(list && list.length>0){
  90. for(let i in list){
  91. if(i != 0){
  92. let font_size = list[i-1].font_size?parseFloat(list[i-1].font_size):20
  93. this.ctx.setFontSize(font_size)
  94. general.x = parseFloat(general.x) + this.ctx.measureText(list[i-1].text).width
  95. }
  96. list[i].x = general.x
  97. list[i].y = general.y + (list[i].margin_top?parseFloat(list[i].margin_top):0)
  98. await this.drawText(list[i])
  99. }
  100. resolve()
  101. }else{
  102. reject("The length of config arr is less than 0")
  103. return;
  104. }
  105. })
  106. },
  107. /**
  108. * array delete empty
  109. * @param {Object} arr
  110. */
  111. arrDeleteEmpty(arr){
  112. let newArr = []
  113. for(let i in arr){
  114. if(arr[i]){
  115. newArr.push(arr[i])
  116. }
  117. }
  118. return newArr
  119. },
  120. /**
  121. * Draw Text,support line
  122. * @param {Object} config
  123. * @param {String} config.text 文字
  124. * @param {Number} config.max_width 文字最大宽度(大于宽度自动换行)
  125. * @param {Number} config.line_height 文字上下行间距
  126. * @param {Number} config.x x坐标
  127. * @param {Number} config.y y坐标
  128. * @param {String} config.font_color 文字颜色
  129. * @param {String} config.font_family 文字字体 默认值:Arial
  130. * @param {String} config.text_align 文字对齐方式(left/center/right)
  131. * @param {Number} config.font_size 文字大小(px)
  132. * @param {Boolean} config.line_through_height 中划线大小
  133. * @param {Boolean} config.line_through_color 中划线颜色
  134. * @param {String} config.font_style 规定文字样式
  135. * @param {String} config.font_variant 规定字体变体
  136. * @param {String} config.font_weight 规定字体粗细
  137. * @param {String} config.line_through_cap 线末端类型
  138. * @param {String} config.line_clamp 最大行数
  139. * @param {String} config.line_clamp_hint 超过line_clamp后,尾部显示的自定义标识 如 ...
  140. * @param {String} config.is_line_break 是否开启换行符换行
  141. *
  142. */
  143. drawText(config,configuration = {}){
  144. configuration['line_num'] = configuration.line_num?configuration.line_num:0
  145. configuration['text_width'] = configuration.text_width?configuration.text_width:0
  146. return new Promise(async (resolve,reject)=>{
  147. if(config.text){
  148. let draw_width = 0,draw_height = 0,draw_x = config.x,draw_y = config.y
  149. let font_size = config.font_size?(parseFloat(config.font_size)*this.scale):(20*this.scale)
  150. let font_color = config.font_color || "#000"
  151. let font_family = config.font_family || "Arial"
  152. let line_height = config.line_height || config.font_size || 20
  153. let text_align = config.text_align || "left"
  154. let font_weight = config.font_weight || "normal"
  155. let font_variant = config.font_variant || "normal"
  156. let font_style = config.font_style || "normal"
  157. let line_clamp_hint = config.line_clamp_hint || '...'
  158. let lineBreakJoinText = ""
  159. let max_width = config.max_width?parseFloat(config.max_width)*this.scale:0
  160. // checkout is line break
  161. if(config.is_line_break){
  162. let splitTextArr = config.text.split(/[\n]/g)
  163. if(splitTextArr && splitTextArr.length > 0){
  164. let newSplitTextArr = this.arrDeleteEmpty(splitTextArr)
  165. if(newSplitTextArr && newSplitTextArr.length > 0){
  166. lineBreakJoinText = newSplitTextArr.slice(1).join("\n")
  167. config.text = newSplitTextArr[0]
  168. }else{
  169. reject("Text cannot be empty:103")
  170. return
  171. }
  172. }else{
  173. reject("Text cannot be empty:102")
  174. return
  175. }
  176. }
  177. this.ctx.setFillStyle(font_color) // color
  178. this.ctx.textAlign = text_align;
  179. this.ctx.font = `${font_style} ${font_variant} ${font_weight} ${parseInt(font_size)}px ${font_family}`
  180. if(configuration.text_width >= this.ctx.measureText(config.text).width){
  181. draw_width = configuration.text_width
  182. }else if(max_width > 0){
  183. draw_width = max_width < this.ctx.measureText(config.text).width ? this.resetCompatibilitySize(max_width) : this.resetCompatibilitySize(this.ctx.measureText(config.text).width)
  184. }else{
  185. draw_width = this.ctx.measureText(config.text).width
  186. }
  187. configuration.text_width = draw_width / this.scale
  188. if( max_width && this.compatibilitySize(this.ctx.measureText(config.text).width) > this.compatibilitySize(max_width)){
  189. let current_text = ""
  190. let text_arr = config.text.split("")
  191. for(let i in text_arr){
  192. if( this.compatibilitySize(this.ctx.measureText(current_text+text_arr[i]).width) > this.compatibilitySize(max_width) ){
  193. // Hyphenation that is greater than the drawable width continues to draw
  194. if(config.line_clamp && parseInt(config.line_clamp) == 1){
  195. // Subtracting the current_text tail width from the line_clamp_hint width
  196. let current_text_arr = current_text.split('')
  197. let json_current_text = ''
  198. while(true){
  199. current_text_arr = current_text_arr.slice(1)
  200. json_current_text = current_text_arr.join('')
  201. if(this.compatibilitySize(this.ctx.measureText(json_current_text).width) <= this.compatibilitySize(this.ctx.measureText(line_clamp_hint).width)){
  202. current_text = current_text.replace(json_current_text,'')
  203. break;
  204. }
  205. }
  206. configuration.line_num += 1
  207. this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
  208. this.ctx.fillText(current_text + line_clamp_hint, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
  209. }else{
  210. configuration.line_num += 1
  211. this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
  212. this.ctx.fillText(current_text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
  213. config.text = text_arr.slice(i).join("")
  214. config.y = config.y + line_height
  215. if(config.line_clamp){
  216. config.line_clamp = parseInt(config.line_clamp) - 1
  217. }
  218. await this.drawText(config,configuration)
  219. }
  220. break;
  221. }else{
  222. current_text = current_text+text_arr[i]
  223. }
  224. }
  225. }else{
  226. if(config.line_through_height){
  227. let x = parseFloat(config.x)*this.scale
  228. let w
  229. let y = parseFloat(config.y)*this.scale - (font_size / 2.6)
  230. if(text_align == "left"){
  231. w = this.ctx.measureText(config.text).width/1.1 + parseFloat(config.x)*this.scale
  232. }else if(text_align == "right"){
  233. w = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width/1.1
  234. }else if(text_align == "center"){
  235. x = parseFloat(config.x)*this.scale - this.ctx.measureText(config.text).width / 1.1 / 2
  236. w = parseFloat(config.x)*this.scale + this.ctx.measureText(config.text).width / 1.1 / 2
  237. }
  238. this.drawLineTo({
  239. x:x,
  240. y:y,
  241. w:w,
  242. h:y,
  243. line_width:config.line_through_height,
  244. line_color:config.line_through_color,
  245. line_cap:config.line_through_cap
  246. })
  247. }
  248. configuration.line_num += 1
  249. this.ctx.setFontSize(parseInt(this.compatibilitySize(font_size))) // font size
  250. this.ctx.fillText(config.text, this.compatibilitySize(parseFloat(config.x)*this.scale), this.compatibilitySize(parseFloat(config.y)*this.scale));
  251. if(config.line_clamp){
  252. config.line_clamp = parseInt(config.line_clamp) - 1
  253. }
  254. }
  255. if(lineBreakJoinText){
  256. await this.drawText({...config,text:lineBreakJoinText,y:config.y + line_height},configuration)
  257. }
  258. draw_height = config.font_size * configuration.line_num
  259. draw_width = configuration.text_width
  260. resolve({draw_width,draw_height,draw_x,draw_y})
  261. }else{
  262. reject("Text cannot be empty:101")
  263. }
  264. })
  265. },
  266. /**
  267. * Draw Line
  268. * @param {Object} config
  269. * @param {Object} config.x x坐标
  270. * @param {Object} config.y y坐标
  271. * @param {Object} config.w 线的宽度
  272. * @param {Object} config.h 线的高度
  273. * @param {Object} config.line_width 线的宽度
  274. * @param {Object} config.line_color 线条颜色
  275. */
  276. drawLineTo(config){
  277. let x = this.compatibilitySize(config.x)
  278. let y = this.compatibilitySize(config.y)
  279. let w = this.compatibilitySize(config.w)
  280. let h = this.compatibilitySize(config.h)
  281. let line_width = config.line_width?parseFloat(config.line_width)*this.scale:1*this.scale
  282. let line_color = config.line_color || "black"
  283. let line_cap = config.line_cap || "butt"
  284. this.ctx.beginPath()
  285. this.ctx.lineCap = line_cap
  286. this.ctx.lineWidth = line_width
  287. this.ctx.strokeStyle = line_color
  288. this.ctx.moveTo(x,y)
  289. this.ctx.lineTo(w,h)
  290. this.ctx.stroke()
  291. },
  292. /**
  293. * Compatibility px
  294. * @param {Object} size
  295. */
  296. compatibilitySize(size) {
  297. let canvasSize = (parseFloat(size) / 750) * this.system_info.windowWidth
  298. canvasSize = parseFloat(canvasSize * 2)
  299. return canvasSize
  300. },
  301. /**
  302. * Restore compatibility px
  303. * @param {Object} size
  304. */
  305. resetCompatibilitySize(size) {
  306. let canvasSize = (parseFloat(size/2)/this.system_info.windowWidth) * 750
  307. return canvasSize
  308. },
  309. /**
  310. * Init canvas
  311. */
  312. init(config){
  313. return new Promise(async (resolve,reject)=>{
  314. if(!config.canvas_id){
  315. reject("Canvas ID cannot be empty, please refer to the usage example")
  316. return;
  317. }
  318. this.hidden = config.hidden
  319. this.canvas_id = config.canvas_id
  320. let system_info = await uni.getSystemInfoSync()
  321. this.system_info = system_info
  322. this.scale = config.scale&&parseFloat(config.scale)>0?parseInt(config.scale):1
  323. this.canvas_width = (config.canvas_width ? this.compatibilitySize(config.canvas_width) : system_info.windowWidth) * this.scale
  324. this.canvas_height = (config.canvas_height ? this.compatibilitySize(config.canvas_height) : system_info.windowHeight) * this.scale,
  325. this.r_canvas_scale = 1/this.scale
  326. this.ctx = uni.createCanvasContext(this.canvas_id,this)
  327. this.setCanvasConfig({
  328. global_alpha:config.global_alpha?parseFloat(config.global_alpha):1,
  329. backgroundColor:config.background_color?config.background_color:"transparent"
  330. })
  331. resolve()
  332. })
  333. },
  334. /**
  335. * clear canvas all path
  336. */
  337. clearCanvas(){
  338. return new Promise(async (resolve,reject)=>{
  339. if(!this.ctx){
  340. reject("canvas is not initialized:101")
  341. return
  342. }else{
  343. this.ctx.clearRect(0,0,parseFloat(this.canvas_width)*this.scale,parseFloat(this.canvas_height)*this.scale)
  344. await this.draw()
  345. resolve()
  346. }
  347. })
  348. },
  349. /**
  350. * Set canvas config
  351. * @param {Object} config
  352. */
  353. setCanvasConfig(config){
  354. this.ctx.globalAlpha = config.global_alpha
  355. this.ctx.fillStyle = config.backgroundColor
  356. this.ctx.fillRect(0, 0, parseFloat(this.canvas_width)*this.scale, parseFloat(this.canvas_height)*this.scale)
  357. },
  358. /**
  359. * set canvas width
  360. * @param {Object} width
  361. */
  362. setCanvasWidth(width){
  363. if(!width){
  364. uni.showToast({
  365. title:'setCanvasWidth:width error',
  366. icon:'none'
  367. })
  368. }
  369. this.canvas_width = this.compatibilitySize(parseFloat(width)) * this.scale
  370. this.ctx.width = this.canvas_width
  371. },
  372. /**
  373. * set canvas height
  374. * @param {Object} height
  375. */
  376. setCanvasHeight(height){
  377. if(!height){
  378. uni.showToast({
  379. title:'setCanvasWidth:height error',
  380. icon:'none'
  381. })
  382. }
  383. this.canvas_height = this.compatibilitySize(parseFloat(height)) * this.scale
  384. this.ctx.height = this.canvas_height
  385. },
  386. /**
  387. * Draw to filepath
  388. */
  389. draw(callback){
  390. return new Promise((resolve,reject)=>{
  391. let finished = false
  392. // attempt to draw to canvas; some platforms may not call the draw callback
  393. try{
  394. this.ctx.draw(false)
  395. }catch(e){
  396. console.error('ctx.draw error', e)
  397. }
  398. // after a short delay, try to export the canvas to a file
  399. const exportTimer = setTimeout(()=>{
  400. if (finished) return
  401. uni.canvasToTempFilePath({
  402. canvasId: this.canvas_id,
  403. quality: 1,
  404. success: (res)=>{
  405. if (finished) return
  406. finished = true
  407. console.log('canvasToTempFilePath success', res)
  408. callback && callback(res)
  409. resolve(res)
  410. },
  411. fail: (err)=>{
  412. if (finished) return
  413. finished = true
  414. console.error('canvasToTempFilePath fail', err)
  415. reject(JSON.stringify(err) || 'Failed to generate poster:101')
  416. }
  417. }, this)
  418. }, 400)
  419. // overall timeout to avoid hanging forever
  420. const overallTimeout = setTimeout(()=>{
  421. if (finished) return
  422. finished = true
  423. clearTimeout(exportTimer)
  424. console.error('canvas export overall timeout')
  425. reject('canvas export timeout')
  426. }, 7000)
  427. })
  428. },
  429. /**
  430. * draw rect
  431. * @param {Number} config.x x坐标
  432. * @param {Number} config.y y坐标
  433. * @param {Number} config.w 图形宽度(px)
  434. * @param {Number} config.h 图形高度(px)
  435. * @param {Number} config.color 图形颜色
  436. * @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
  437. * @param {Number} config.border_width 边框大小
  438. * @param {Number} config.border_color 边框颜色
  439. *
  440. */
  441. drawRect(config){
  442. return new Promise(async (resolve,reject)=>{
  443. if(!config.border_width || config.border_width <=0){
  444. config.border_width = 0
  445. }else{
  446. config.border_width = parseFloat(config.border_width)
  447. }
  448. if(parseFloat(config.border_width) > 0){
  449. let sub_config = JSON.parse(JSON.stringify(config))
  450. sub_config.border_width = 0
  451. sub_config.w = config.w + config.border_width
  452. sub_config.h = config.h + config.border_width
  453. sub_config.color = config.border_color || 'black'
  454. if(sub_config.border_radius){
  455. sub_config.border_radius = parseFloat(sub_config.border_radius) + parseFloat(config.border_width) / 2
  456. }
  457. await this.drawRect(sub_config)
  458. }
  459. let color = config.color || 'white'
  460. config.x = (parseFloat(config.x) + config.border_width / 2)
  461. config.y = (parseFloat(config.y) + config.border_width / 2)
  462. config['color'] = color
  463. this.ctx.fillStyle = color;
  464. if(config.is_radius || config.border_radius){
  465. this.setNativeBorderRadius(config)
  466. this.ctx.fill()
  467. }else{
  468. console.log('config.border_width',config.border_width)
  469. this.ctx.fillRect(this.compatibilitySize(config.x*this.scale),this.compatibilitySize(config.y*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
  470. }
  471. resolve()
  472. })
  473. },
  474. /**
  475. * Draw image
  476. * @param {Object} config
  477. * @param {String} config.url 图片链接
  478. * @param {Number} config.x x坐标
  479. * @param {Number} config.y y坐标
  480. * @param {Number} config.w 图片宽度(px)
  481. * @param {Number} config.h 图片高度(px)
  482. * @param {Number} config.border_width 边大小
  483. * @param {Number} config.border_color 边颜色
  484. * @param {Number} config.is_radius 是否开启圆图(1.1.6及以下版本废弃,请使用border_radius)
  485. * @param {Number} config.border_radius 圆角弧度
  486. */
  487. drawImage(config){
  488. return new Promise(async (resolve,reject)=>{
  489. if(config.url){
  490. let type = 0 // 1、network image 2、native image 3、base64 image
  491. let image_url
  492. let reg = /^https?/ig;
  493. if(reg.test(config.url)){
  494. type = 1
  495. }else{
  496. if((config.url.indexOf("data:image/png;base64") != -1) || config.url.indexOf("data:image/jpeg;base64") != -1 || config.url.indexOf("data:image/gif;base64") != -1){
  497. type = 3
  498. }else{
  499. type = 2
  500. }
  501. }
  502. if(type == 1){
  503. // network image
  504. await this.downLoadNetworkFile(config.url).then(res=>{ // two function
  505. image_url = res
  506. }).catch(err=>{
  507. reject(err)
  508. return;
  509. })
  510. }else if(type == 2){
  511. // native image (local static path or local file)
  512. const imageInfoResult = await uni.getImageInfo({ src: config.url });
  513. let nativeImagePath = null;
  514. try {
  515. // uni.getImageInfo 在不同平台/基础库返回值结构可能不同,优先取常见字段
  516. if (imageInfoResult && typeof imageInfoResult === 'object') {
  517. if (imageInfoResult.path) nativeImagePath = imageInfoResult.path;
  518. else if (imageInfoResult.tempFilePath) nativeImagePath = imageInfoResult.tempFilePath;
  519. }
  520. // 兼容某些平台把结果放数组里的情况
  521. if (!nativeImagePath && Array.isArray(imageInfoResult) && imageInfoResult.length > 1 && imageInfoResult[1] && imageInfoResult[1].path) {
  522. nativeImagePath = imageInfoResult[1].path;
  523. }
  524. if (!nativeImagePath) {
  525. reject('getImageInfo no path:404')
  526. return;
  527. }
  528. } catch (e) {
  529. reject(e + ':500')
  530. return;
  531. }
  532. let base64 = await this.urlToBase64({ url: nativeImagePath });
  533. // #ifdef MP-WEIXIN
  534. await this.base64ToNative({ url: base64 }).then(res => {
  535. image_url = res;
  536. }).catch(err => {
  537. reject(JSON.stringify(err) + ":501");
  538. return;
  539. });
  540. // #endif
  541. // #ifndef MP-WEIXIN
  542. image_url = base64;
  543. // #endif
  544. }else if(type == 3){
  545. // #ifdef MP-WEIXIN
  546. await this.base64ToNative({url:config.url}).then(res=>{
  547. image_url = res
  548. }).catch(err=>{
  549. reject(JSON.stringify(err)+":500")
  550. return;
  551. })
  552. // #endif
  553. // #ifndef MP-WEIXIN
  554. image_url = config.url
  555. // #endif
  556. }else{
  557. reject("Other Type Errors:101")
  558. return
  559. }
  560. if(config.border_width){
  561. let border_radius = 0
  562. if(config.border_radius){
  563. let multiple = config.w / config.border_radius
  564. border_radius = (parseFloat(config.w) + parseFloat(config.border_width)) / multiple
  565. }
  566. // drawRect
  567. await this.drawRect({
  568. x:parseFloat(config.x) - parseFloat(config.border_width)/2,
  569. y:parseFloat(config.y) - parseFloat(config.border_width)/2,
  570. w:parseFloat(config.w) + parseFloat(config.border_width),
  571. h:parseFloat(config.h) + parseFloat(config.border_width),
  572. color:config.border_color,
  573. border_radius:border_radius,
  574. border_width:config.border_width,
  575. is_radius:config.is_radius
  576. })
  577. }
  578. if(config.border_radius){
  579. config.color = config.color?config.color:'rgba(0,0,0,0)'
  580. // 圆角有白边,+0.5的误差
  581. config.w = config.w + 0.3
  582. config.h = config.h + 0.3
  583. this.setNativeBorderRadius(config)
  584. }else if(config.is_radius){
  585. //已废弃 is_radius
  586. this.ctx.setStrokeStyle("rgba(0,0,0,0)")
  587. this.ctx.save()
  588. this.ctx.beginPath()
  589. this.ctx.arc(this.compatibilitySize(parseFloat(config.x)*this.scale+parseFloat(config.w)*this.scale/2), this.compatibilitySize(parseFloat(config.y)*this.scale+parseFloat(config.h)*this.scale/2), this.compatibilitySize(parseFloat(config.w)*this.scale/2), 0, 2 * Math.PI, false)
  590. this.ctx.stroke();
  591. this.ctx.clip()
  592. }
  593. await this.ctx.drawImage(image_url,this.compatibilitySize(parseFloat(config.x)*this.scale),this.compatibilitySize(parseFloat(config.y)*this.scale),this.compatibilitySize(parseFloat(config.w)*this.scale),this.compatibilitySize(parseFloat(config.h)*this.scale))
  594. this.ctx.restore() //Restore previously saved drawing context
  595. resolve()
  596. }else{
  597. let err_msg = "Links cannot be empty:101"
  598. reject(err_msg)
  599. }
  600. })
  601. },
  602. /**
  603. * base64 to native available path
  604. * @param {Object} config
  605. */
  606. base64ToNative(config){
  607. return new Promise((resolve,reject)=>{
  608. let fileName = new Date().getTime()
  609. var filePath = `${wx.env.USER_DATA_PATH}/${fileName}_rCanvas.png`
  610. wx.getFileSystemManager().writeFile({
  611. filePath: filePath,
  612. data: config.url.replace(/^data:\S+\/\S+;base64,/, ''),
  613. encoding: 'base64',
  614. success: function() {
  615. resolve(filePath)
  616. },
  617. fail: function(error) {
  618. reject(error)
  619. }
  620. })
  621. })
  622. },
  623. /**
  624. * native url to base64
  625. * @param {Object} config
  626. */
  627. urlToBase64(config){
  628. return new Promise(async (resolve,reject)=>{
  629. if (typeof window != 'undefined') {
  630. await this.downLoadNetworkFile(config.url).then(res=>{ // two function
  631. resolve(res)
  632. }).catch(err=>{
  633. reject(err)
  634. })
  635. }else if (typeof plus != 'undefined') {
  636. plus.io.resolveLocalFileSystemURL(config.url,(obj)=>{
  637. obj.file((file)=>{
  638. let fileReader = new plus.io.FileReader()
  639. fileReader.onload = (res)=>{
  640. resolve(res.target.result)
  641. }
  642. fileReader.onerror = (err)=>{
  643. reject(err)
  644. }
  645. fileReader.readAsDataURL(file)
  646. }, (err)=>{
  647. reject(err)
  648. })
  649. },(err)=>{
  650. reject(err)
  651. })
  652. }else if(typeof wx != 'undefined'){
  653. wx.getFileSystemManager().readFile({
  654. filePath: config.url,
  655. encoding: 'base64',
  656. success: function(res) {
  657. resolve('data:image/png;base64,' + res.data)
  658. },
  659. fail: function(error) {
  660. reject(error)
  661. }
  662. })
  663. }
  664. })
  665. },
  666. setNativeBorderRadius(config){
  667. let border_radius = config.border_radius?(parseFloat(config.border_radius)*this.scale):(20*this.scale)
  668. if ((parseFloat(config.w)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.w)*this.scale) / 2;
  669. if ((parseFloat(config.h)*this.scale) < 2 * border_radius) border_radius = (parseFloat(config.h)*this.scale) / 2;
  670. this.ctx.beginPath();
  671. this.ctx.moveTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + border_radius), this.compatibilitySize((parseFloat(config.y)*this.scale)));
  672. this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
  673. this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize(border_radius));
  674. this.ctx.arcTo((this.compatibilitySize(parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale) + (parseFloat(config.h)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
  675. this.ctx.arcTo(this.compatibilitySize((parseFloat(config.x)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize((parseFloat(config.x)*this.scale) + (parseFloat(config.w)*this.scale)), this.compatibilitySize((parseFloat(config.y)*this.scale)), this.compatibilitySize(border_radius));
  676. this.ctx.closePath();
  677. this.ctx.strokeStyle = config.color || config.border_color || 'rgba(0,0,0,0)'; // 设置绘制边框的颜色
  678. this.ctx.stroke();
  679. this.ctx.save()
  680. this.ctx.clip();
  681. },
  682. /**
  683. * Download network file
  684. * @param {Object} url : download url
  685. */
  686. downLoadNetworkFile(url){
  687. return new Promise((resolve,reject)=>{
  688. uni.downloadFile({
  689. url,
  690. success:(res)=>{
  691. if(res.statusCode == 200){
  692. resolve(res.tempFilePath)
  693. }else{
  694. reject("Download Image Fail:102")
  695. }
  696. },
  697. fail:(err)=>{
  698. reject("Download Image Fail:101")
  699. }
  700. })
  701. })
  702. },
  703. /**
  704. * Save image to natice
  705. * @param {Object} filePath : native imageUrl
  706. */
  707. saveImage(filePath){
  708. return new Promise((resolve,reject)=>{
  709. if(!filePath){
  710. reject("FilePath cannot be null:101")
  711. return;
  712. }
  713. // #ifdef H5
  714. var createA = document.createElement("a");
  715. createA.download = filePath;
  716. createA.href = filePath;
  717. document.body.appendChild(createA);
  718. createA.click();
  719. createA.remove();
  720. resolve()
  721. // #endif
  722. // #ifndef H5
  723. uni.saveImageToPhotosAlbum({
  724. filePath: filePath,
  725. success:(res)=>{
  726. resolve(res)
  727. },
  728. fail:(err)=>{
  729. reject(err)
  730. }
  731. })
  732. // #endif
  733. })
  734. }
  735. }
  736. }