painter.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838
  1. import Pen from './lib/pen';
  2. import Downloader from './lib/downloader';
  3. const util = require('./lib/util');
  4. const downloader = new Downloader();
  5. // 最大尝试的绘制次数
  6. const MAX_PAINT_COUNT = 5;
  7. const ACTION_DEFAULT_SIZE = 24;
  8. const ACTION_OFFSET = '2rpx';
  9. Component({
  10. canvasWidthInPx: 0,
  11. canvasHeightInPx: 0,
  12. paintCount: 0,
  13. currentPalette: {},
  14. movingCache: {},
  15. outterDisabled: false,
  16. isDisabled: false,
  17. needClear: false,
  18. /**
  19. * 组件的属性列表
  20. */
  21. properties: {
  22. customStyle: {
  23. type: String,
  24. },
  25. // 运行自定义选择框和删除缩放按钮
  26. customActionStyle: {
  27. type: Object,
  28. },
  29. palette: {
  30. type: Object,
  31. observer: function (newVal, oldVal) {
  32. if (this.isNeedRefresh(newVal, oldVal)) {
  33. this.paintCount = 0;
  34. this.startPaint();
  35. }
  36. },
  37. },
  38. dancePalette: {
  39. type: Object,
  40. observer: function (newVal, oldVal) {
  41. if (!this.isEmpty(newVal)) {
  42. this.initDancePalette(newVal);
  43. }
  44. },
  45. },
  46. // 缩放比,会在传入的 palette 中统一乘以该缩放比
  47. scaleRatio: {
  48. type: Number,
  49. value: 1
  50. },
  51. widthPixels: {
  52. type: Number,
  53. value: 0
  54. },
  55. // 启用脏检查,默认 false
  56. dirty: {
  57. type: Boolean,
  58. value: false,
  59. },
  60. LRU: {
  61. type: Boolean,
  62. value: true,
  63. },
  64. action: {
  65. type: Object,
  66. observer: function (newVal, oldVal) {
  67. if (newVal && !this.isEmpty(newVal)) {
  68. this.doAction(newVal, (callbackInfo) => {
  69. this.movingCache = callbackInfo
  70. }, false, true)
  71. }
  72. },
  73. },
  74. disableAction: {
  75. type: Boolean,
  76. observer: function (isDisabled) {
  77. this.outterDisabled = isDisabled
  78. this.isDisabled = isDisabled
  79. }
  80. },
  81. clearActionBox: {
  82. type: Boolean,
  83. observer: function (needClear) {
  84. if (needClear && !this.needClear) {
  85. if (this.frontContext) {
  86. setTimeout(() => {
  87. this.frontContext.draw();
  88. }, 100);
  89. this.touchedView = {};
  90. this.prevFindedIndex = this.findedIndex
  91. this.findedIndex = -1;
  92. }
  93. }
  94. this.needClear = needClear
  95. }
  96. },
  97. },
  98. data: {
  99. picURL: '',
  100. showCanvas: true,
  101. painterStyle: '',
  102. },
  103. methods: {
  104. /**
  105. * 判断一个 object 是否为 空
  106. * @param {object} object
  107. */
  108. isEmpty(object) {
  109. for (const i in object) {
  110. return false;
  111. }
  112. return true;
  113. },
  114. isNeedRefresh(newVal, oldVal) {
  115. if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) {
  116. return false;
  117. }
  118. return true;
  119. },
  120. getBox(rect, type) {
  121. const boxArea = {
  122. type: 'rect',
  123. css: {
  124. height: `${rect.bottom - rect.top}px`,
  125. width: `${rect.right - rect.left}px`,
  126. left: `${rect.left}px`,
  127. top: `${rect.top}px`,
  128. borderWidth: '4rpx',
  129. borderColor: '#1A7AF8',
  130. color: 'transparent'
  131. }
  132. }
  133. if (type === 'text') {
  134. boxArea.css = Object.assign({}, boxArea.css, {
  135. borderStyle: 'dashed'
  136. })
  137. }
  138. if (this.properties.customActionStyle && this.properties.customActionStyle.border) {
  139. boxArea.css = Object.assign({}, boxArea.css, this.properties.customActionStyle.border)
  140. }
  141. Object.assign(boxArea, {
  142. id: 'box'
  143. })
  144. return boxArea
  145. },
  146. getScaleIcon(rect, type) {
  147. let scaleArea = {}
  148. const {
  149. customActionStyle
  150. } = this.properties
  151. if (customActionStyle && customActionStyle.scale) {
  152. scaleArea = {
  153. type: 'image',
  154. url: type === 'text' ? customActionStyle.scale.textIcon : customActionStyle.scale.imageIcon,
  155. css: {
  156. height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  157. width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  158. borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  159. }
  160. }
  161. } else {
  162. scaleArea = {
  163. type: 'rect',
  164. css: {
  165. height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  166. width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  167. borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  168. color: '#0000ff',
  169. }
  170. }
  171. }
  172. scaleArea.css = Object.assign({}, scaleArea.css, {
  173. align: 'center',
  174. left: `${rect.right + ACTION_OFFSET.toPx()}px`,
  175. top: type === 'text' ? `${rect.top - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px` : `${rect.bottom - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px`
  176. })
  177. Object.assign(scaleArea, {
  178. id: 'scale'
  179. })
  180. return scaleArea
  181. },
  182. getDeleteIcon(rect) {
  183. let deleteArea = {}
  184. const {
  185. customActionStyle
  186. } = this.properties
  187. if (customActionStyle && customActionStyle.scale) {
  188. deleteArea = {
  189. type: 'image',
  190. url: customActionStyle.delete.icon,
  191. css: {
  192. height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  193. width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  194. borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  195. }
  196. }
  197. } else {
  198. deleteArea = {
  199. type: 'rect',
  200. css: {
  201. height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  202. width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
  203. borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
  204. color: '#0000ff',
  205. }
  206. }
  207. }
  208. deleteArea.css = Object.assign({}, deleteArea.css, {
  209. align: 'center',
  210. left: `${rect.left - ACTION_OFFSET.toPx()}px`,
  211. top: `${rect.top - ACTION_OFFSET.toPx() - deleteArea.css.height.toPx() / 2}px`
  212. })
  213. Object.assign(deleteArea, {
  214. id: 'delete'
  215. })
  216. return deleteArea
  217. },
  218. doAction(action, callback, isMoving, overwrite) {
  219. let newVal = null
  220. if (action) {
  221. newVal = action.view
  222. }
  223. if (newVal && newVal.id && this.touchedView.id !== newVal.id) {
  224. // 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作
  225. const {
  226. views
  227. } = this.currentPalette;
  228. for (let i = 0; i < views.length; i++) {
  229. if (views[i].id === newVal.id) {
  230. // 跨层回撤,需要重新构建三层关系
  231. this.touchedView = views[i];
  232. this.findedIndex = i;
  233. this.sliceLayers();
  234. break
  235. }
  236. }
  237. }
  238. const doView = this.touchedView
  239. if (!doView || this.isEmpty(doView)) {
  240. return
  241. }
  242. if (newVal && newVal.css) {
  243. if (overwrite) {
  244. doView.css = newVal.css
  245. } else if (Array.isArray(doView.css) && Array.isArray(newVal.css)) {
  246. doView.css = Object.assign({}, ...doView.css, ...newVal.css)
  247. } else if (Array.isArray(doView.css)) {
  248. doView.css = Object.assign({}, ...doView.css, newVal.css)
  249. } else if (Array.isArray(newVal.css)) {
  250. doView.css = Object.assign({}, doView.css, ...newVal.css)
  251. } else {
  252. doView.css = Object.assign({}, doView.css, newVal.css)
  253. }
  254. }
  255. if (newVal && newVal.rect) {
  256. doView.rect = newVal.rect;
  257. }
  258. if (newVal && newVal.url && doView.url && newVal.url !== doView.url) {
  259. downloader.download(newVal.url, this.properties.LRU).then((path) => {
  260. if (newVal.url.startsWith('https')) {
  261. doView.originUrl = newVal.url
  262. }
  263. doView.url = path;
  264. wx.getImageInfo({
  265. src: path,
  266. success: (res) => {
  267. doView.sHeight = res.height
  268. doView.sWidth = res.width
  269. this.reDraw(doView, callback, isMoving)
  270. },
  271. fail: () => {
  272. this.reDraw(doView, callback, isMoving)
  273. }
  274. })
  275. }).catch((error) => {
  276. // 未下载成功,直接绘制
  277. console.error(error)
  278. this.reDraw(doView, callback, isMoving)
  279. })
  280. } else {
  281. (newVal && newVal.text && doView.text && newVal.text !== doView.text) && (doView.text = newVal.text);
  282. (newVal && newVal.content && doView.content && newVal.content !== doView.content) && (doView.content = newVal.content);
  283. this.reDraw(doView, callback, isMoving)
  284. }
  285. },
  286. reDraw(doView, callback, isMoving) {
  287. const draw = {
  288. width: this.currentPalette.width,
  289. height: this.currentPalette.height,
  290. views: this.isEmpty(doView) ? [] : [doView]
  291. }
  292. const pen = new Pen(this.globalContext, draw);
  293. if (isMoving && doView.type === 'text') {
  294. pen.paint((callbackInfo) => {
  295. callback && callback(callbackInfo);
  296. this.triggerEvent('viewUpdate', {
  297. view: this.touchedView
  298. });
  299. }, true, this.movingCache);
  300. } else {
  301. // 某些机型(华为 P20)非移动和缩放场景下,只绘制一遍会偶然性图片绘制失败
  302. if (!isMoving && !this.isScale) {
  303. pen.paint()
  304. }
  305. pen.paint((callbackInfo) => {
  306. callback && callback(callbackInfo);
  307. this.triggerEvent('viewUpdate', {
  308. view: this.touchedView
  309. });
  310. })
  311. }
  312. const {
  313. rect,
  314. css,
  315. type
  316. } = doView
  317. this.block = {
  318. width: this.currentPalette.width,
  319. height: this.currentPalette.height,
  320. views: this.isEmpty(doView) ? [] : [this.getBox(rect, doView.type)]
  321. }
  322. if (css && css.scalable) {
  323. this.block.views.push(this.getScaleIcon(rect, type))
  324. }
  325. if (css && css.deletable) {
  326. this.block.views.push(this.getDeleteIcon(rect))
  327. }
  328. const topBlock = new Pen(this.frontContext, this.block)
  329. topBlock.paint();
  330. },
  331. isInView(x, y, rect) {
  332. return (x > rect.left &&
  333. y > rect.top &&
  334. x < rect.right &&
  335. y < rect.bottom
  336. )
  337. },
  338. isInDelete(x, y) {
  339. for (const view of this.block.views) {
  340. if (view.id === 'delete') {
  341. return (x > view.rect.left &&
  342. y > view.rect.top &&
  343. x < view.rect.right &&
  344. y < view.rect.bottom)
  345. }
  346. }
  347. return false
  348. },
  349. isInScale(x, y) {
  350. for (const view of this.block.views) {
  351. if (view.id === 'scale') {
  352. return (x > view.rect.left &&
  353. y > view.rect.top &&
  354. x < view.rect.right &&
  355. y < view.rect.bottom)
  356. }
  357. }
  358. return false
  359. },
  360. touchedView: {},
  361. findedIndex: -1,
  362. onClick() {
  363. const x = this.startX
  364. const y = this.startY
  365. const totalLayerCount = this.currentPalette.views.length
  366. let canBeTouched = []
  367. let isDelete = false
  368. let deleteIndex = -1
  369. for (let i = totalLayerCount - 1; i >= 0; i--) {
  370. const view = this.currentPalette.views[i]
  371. const {
  372. rect
  373. } = view
  374. if (this.touchedView && this.touchedView.id && this.touchedView.id === view.id && this.isInDelete(x, y, rect)) {
  375. canBeTouched.length = 0
  376. deleteIndex = i
  377. isDelete = true
  378. break
  379. }
  380. if (this.isInView(x, y, rect)) {
  381. canBeTouched.push({
  382. view,
  383. index: i
  384. })
  385. }
  386. }
  387. this.touchedView = {}
  388. if (canBeTouched.length === 0) {
  389. this.findedIndex = -1
  390. } else {
  391. let i = 0
  392. const touchAble = canBeTouched.filter(item => Boolean(item.view.id))
  393. if (touchAble.length === 0) {
  394. this.findedIndex = canBeTouched[0].index
  395. } else {
  396. for (i = 0; i < touchAble.length; i++) {
  397. if (this.findedIndex === touchAble[i].index) {
  398. i++
  399. break
  400. }
  401. }
  402. if (i === touchAble.length) {
  403. i = 0
  404. }
  405. this.touchedView = touchAble[i].view
  406. this.findedIndex = touchAble[i].index
  407. this.triggerEvent('viewClicked', {
  408. view: this.touchedView
  409. })
  410. }
  411. }
  412. if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) {
  413. // 证明点击了背景 或无法移动的view
  414. this.frontContext.draw();
  415. if (isDelete) {
  416. this.triggerEvent('touchEnd', {
  417. view: this.currentPalette.views[deleteIndex],
  418. index: deleteIndex,
  419. type: 'delete'
  420. })
  421. this.doAction()
  422. } else if (this.findedIndex < 0) {
  423. this.triggerEvent('viewClicked', {})
  424. }
  425. this.findedIndex = -1
  426. this.prevFindedIndex = -1
  427. } else if (this.touchedView && this.touchedView.id) {
  428. this.sliceLayers();
  429. }
  430. },
  431. sliceLayers() {
  432. const bottomLayers = this.currentPalette.views.slice(0, this.findedIndex)
  433. const topLayers = this.currentPalette.views.slice(this.findedIndex + 1)
  434. const bottomDraw = {
  435. width: this.currentPalette.width,
  436. height: this.currentPalette.height,
  437. background: this.currentPalette.background,
  438. views: bottomLayers
  439. }
  440. const topDraw = {
  441. width: this.currentPalette.width,
  442. height: this.currentPalette.height,
  443. views: topLayers
  444. }
  445. if (this.prevFindedIndex < this.findedIndex) {
  446. new Pen(this.bottomContext, bottomDraw).paint();
  447. this.doAction(null, (callbackInfo) => {
  448. this.movingCache = callbackInfo
  449. })
  450. new Pen(this.topContext, topDraw).paint();
  451. } else {
  452. new Pen(this.topContext, topDraw).paint();
  453. this.doAction(null, (callbackInfo) => {
  454. this.movingCache = callbackInfo
  455. })
  456. new Pen(this.bottomContext, bottomDraw).paint();
  457. }
  458. this.prevFindedIndex = this.findedIndex
  459. },
  460. startX: 0,
  461. startY: 0,
  462. startH: 0,
  463. startW: 0,
  464. isScale: false,
  465. startTimeStamp: 0,
  466. onTouchStart(event) {
  467. if (this.isDisabled) {
  468. return
  469. }
  470. const {
  471. x,
  472. y
  473. } = event.touches[0]
  474. this.startX = x
  475. this.startY = y
  476. this.startTimeStamp = new Date().getTime()
  477. if (this.touchedView && !this.isEmpty(this.touchedView)) {
  478. const {
  479. rect
  480. } = this.touchedView
  481. if (this.isInScale(x, y, rect)) {
  482. this.isScale = true
  483. this.movingCache = {}
  484. this.startH = rect.bottom - rect.top
  485. this.startW = rect.right - rect.left
  486. } else {
  487. this.isScale = false
  488. }
  489. } else {
  490. this.isScale = false
  491. }
  492. },
  493. onTouchEnd(e) {
  494. if (this.isDisabled) {
  495. return
  496. }
  497. const current = new Date().getTime()
  498. if ((current - this.startTimeStamp) <= 500 && !this.hasMove) {
  499. !this.isScale && this.onClick(e)
  500. } else if (this.touchedView && !this.isEmpty(this.touchedView)) {
  501. this.triggerEvent('touchEnd', {
  502. view: this.touchedView,
  503. })
  504. }
  505. this.hasMove = false
  506. },
  507. onTouchCancel(e) {
  508. if (this.isDisabled) {
  509. return
  510. }
  511. this.onTouchEnd(e)
  512. },
  513. hasMove: false,
  514. onTouchMove(event) {
  515. if (this.isDisabled) {
  516. return
  517. }
  518. this.hasMove = true
  519. if (!this.touchedView || (this.touchedView && !this.touchedView.id)) {
  520. return
  521. }
  522. const {
  523. x,
  524. y
  525. } = event.touches[0]
  526. const offsetX = x - this.startX
  527. const offsetY = y - this.startY
  528. const {
  529. rect,
  530. type
  531. } = this.touchedView
  532. let css = {}
  533. if (this.isScale) {
  534. const newW = this.startW + offsetX > 1 ? this.startW + offsetX : 1
  535. if (this.touchedView.css && this.touchedView.css.minWidth) {
  536. if (newW < this.touchedView.css.minWidth.toPx()) {
  537. return
  538. }
  539. }
  540. if (this.touchedView.rect && this.touchedView.rect.minWidth) {
  541. if (newW < this.touchedView.rect.minWidth) {
  542. return
  543. }
  544. }
  545. const newH = this.startH + offsetY > 1 ? this.startH + offsetY : 1
  546. css = {
  547. width: `${newW}px`,
  548. }
  549. if (type !== 'text') {
  550. if (type === 'image') {
  551. css.height = `${(newW) * this.startH / this.startW }px`
  552. } else {
  553. css.height = `${newH}px`
  554. }
  555. }
  556. } else {
  557. this.startX = x
  558. this.startY = y
  559. css = {
  560. left: `${rect.x + offsetX}px`,
  561. top: `${rect.y + offsetY}px`,
  562. right: undefined,
  563. bottom: undefined
  564. }
  565. }
  566. this.doAction({
  567. view: {
  568. css
  569. }
  570. }, (callbackInfo) => {
  571. if (this.isScale) {
  572. this.movingCache = callbackInfo
  573. }
  574. }, !this.isScale)
  575. },
  576. initScreenK() {
  577. if (!(getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth)) {
  578. try {
  579. getApp().systemInfo = wx.getSystemInfoSync();
  580. } catch (e) {
  581. console.error(`Painter get system info failed, ${JSON.stringify(e)}`);
  582. return;
  583. }
  584. }
  585. this.screenK = 0.5;
  586. if (getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth) {
  587. this.screenK = getApp().systemInfo.screenWidth / 750;
  588. }
  589. setStringPrototype(this.screenK, this.properties.scaleRatio);
  590. },
  591. initDancePalette() {
  592. this.isDisabled = true;
  593. this.initScreenK();
  594. this.downloadImages(this.properties.dancePalette).then((palette) => {
  595. this.currentPalette = palette
  596. const {
  597. width,
  598. height
  599. } = palette;
  600. if (!width || !height) {
  601. console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
  602. return;
  603. }
  604. this.setData({
  605. painterStyle: `width:${width.toPx()}px;height:${height.toPx()}px;`,
  606. });
  607. this.frontContext || (this.frontContext = wx.createCanvasContext('front', this));
  608. this.bottomContext || (this.bottomContext = wx.createCanvasContext('bottom', this));
  609. this.topContext || (this.topContext = wx.createCanvasContext('top', this));
  610. this.globalContext || (this.globalContext = wx.createCanvasContext('k-canvas', this));
  611. new Pen(this.bottomContext, palette).paint(() => {
  612. this.isDisabled = false;
  613. this.isDisabled = this.outterDisabled;
  614. this.triggerEvent('didShow');
  615. });
  616. this.globalContext.draw();
  617. this.frontContext.draw();
  618. this.topContext.draw();
  619. });
  620. this.touchedView = {};
  621. },
  622. startPaint() {
  623. this.initScreenK();
  624. this.downloadImages(this.properties.palette).then((palette) => {
  625. const {
  626. width,
  627. height
  628. } = palette;
  629. if (!width || !height) {
  630. console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
  631. return;
  632. }
  633. // 生成图片时,根据设置的像素值重新绘制
  634. this.canvasWidthInPx = width.toPx();
  635. if (this.properties.widthPixels) {
  636. setStringPrototype(this.screenK, this.properties.widthPixels / this.canvasWidthInPx)
  637. this.canvasWidthInPx = this.properties.widthPixels
  638. }
  639. this.canvasHeightInPx = height.toPx();
  640. this.setData({
  641. photoStyle: `width:${this.canvasWidthInPx}px;height:${this.canvasHeightInPx}px;`,
  642. });
  643. this.photoContext || (this.photoContext = wx.createCanvasContext('photo', this));
  644. new Pen(this.photoContext, palette).paint(() => {
  645. this.saveImgToLocal();
  646. });
  647. setStringPrototype(this.screenK, this.properties.scaleRatio);
  648. });
  649. },
  650. downloadImages(palette) {
  651. return new Promise((resolve, reject) => {
  652. let preCount = 0;
  653. let completeCount = 0;
  654. const paletteCopy = JSON.parse(JSON.stringify(palette));
  655. if (paletteCopy.background) {
  656. preCount++;
  657. downloader.download(paletteCopy.background, this.properties.LRU).then((path) => {
  658. paletteCopy.background = path;
  659. completeCount++;
  660. if (preCount === completeCount) {
  661. resolve(paletteCopy);
  662. }
  663. }, () => {
  664. completeCount++;
  665. if (preCount === completeCount) {
  666. resolve(paletteCopy);
  667. }
  668. });
  669. }
  670. if (paletteCopy.views) {
  671. for (const view of paletteCopy.views) {
  672. if (view && view.type === 'image' && view.url) {
  673. preCount++;
  674. /* eslint-disable no-loop-func */
  675. downloader.download(view.url, this.properties.LRU).then((path) => {
  676. view.originUrl = view.url;
  677. view.url = path;
  678. wx.getImageInfo({
  679. src: path,
  680. success: (res) => {
  681. // 获得一下图片信息,供后续裁减使用
  682. view.sWidth = res.width;
  683. view.sHeight = res.height;
  684. },
  685. fail: (error) => {
  686. // 如果图片坏了,则直接置空,防止坑爹的 canvas 画崩溃了
  687. view.url = "";
  688. console.error(`getImageInfo ${view.url} failed, ${JSON.stringify(error)}`);
  689. },
  690. complete: () => {
  691. completeCount++;
  692. if (preCount === completeCount) {
  693. resolve(paletteCopy);
  694. }
  695. },
  696. });
  697. }, () => {
  698. completeCount++;
  699. if (preCount === completeCount) {
  700. resolve(paletteCopy);
  701. }
  702. });
  703. }
  704. }
  705. }
  706. if (preCount === 0) {
  707. resolve(paletteCopy);
  708. }
  709. });
  710. },
  711. saveImgToLocal() {
  712. const that = this;
  713. setTimeout(() => {
  714. wx.canvasToTempFilePath({
  715. canvasId: 'photo',
  716. destWidth: that.canvasWidthInPx,
  717. destHeight: that.canvasHeightInPx,
  718. success: function (res) {
  719. that.getImageInfo(res.tempFilePath);
  720. },
  721. fail: function (error) {
  722. console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`);
  723. that.triggerEvent('imgErr', {
  724. error: error
  725. });
  726. },
  727. }, this);
  728. }, 300);
  729. },
  730. getImageInfo(filePath) {
  731. const that = this;
  732. wx.getImageInfo({
  733. src: filePath,
  734. success: (infoRes) => {
  735. if (that.paintCount > MAX_PAINT_COUNT) {
  736. const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`;
  737. console.error(error);
  738. that.triggerEvent('imgErr', {
  739. error: error
  740. });
  741. return;
  742. }
  743. // 比例相符时才证明绘制成功,否则进行强制重绘制
  744. if (Math.abs((infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) / (infoRes.height * that.canvasHeightInPx)) < 0.01) {
  745. that.triggerEvent('imgOK', {
  746. path: filePath
  747. });
  748. } else {
  749. that.startPaint();
  750. }
  751. that.paintCount++;
  752. },
  753. fail: (error) => {
  754. console.error(`getImageInfo failed, ${JSON.stringify(error)}`);
  755. that.triggerEvent('imgErr', {
  756. error: error
  757. });
  758. },
  759. });
  760. },
  761. },
  762. });
  763. function setStringPrototype(screenK, scale) {
  764. /* eslint-disable no-extend-native */
  765. /**
  766. * 是否支持负数
  767. * @param {Boolean} minus 是否支持负数
  768. * @param {Number} baseSize 当设置了 % 号时,设置的基准值
  769. */
  770. String.prototype.toPx = function toPx(minus, baseSize) {
  771. if (this === '0') {
  772. return 0
  773. }
  774. let reg;
  775. if (minus) {
  776. reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g;
  777. } else {
  778. reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g;
  779. }
  780. const results = reg.exec(this);
  781. if (!this || !results) {
  782. console.error(`The size: ${this} is illegal`);
  783. return 0;
  784. }
  785. const unit = results[2];
  786. const value = parseFloat(this);
  787. let res = 0;
  788. if (unit === 'rpx') {
  789. res = Math.round(value * (screenK || 0.5) * (scale || 1));
  790. } else if (unit === 'px') {
  791. res = Math.round(value * (scale || 1));
  792. } else if (unit === '%') {
  793. res = Math.round(value * baseSize / 100);
  794. }
  795. return res;
  796. };
  797. }