870 lines
26 KiB
JavaScript
870 lines
26 KiB
JavaScript
|
import Pen, { penCache, clearPenCache } from './lib/pen';
|
|||
|
import Downloader from './lib/downloader';
|
|||
|
import WxCanvas from './lib/wx-canvas';
|
|||
|
|
|||
|
const util = require('./lib/util');
|
|||
|
const calc = require('./lib/calc');
|
|||
|
|
|||
|
const downloader = new Downloader();
|
|||
|
|
|||
|
// 最大尝试的绘制次数
|
|||
|
const MAX_PAINT_COUNT = 5;
|
|||
|
const ACTION_DEFAULT_SIZE = 24;
|
|||
|
const ACTION_OFFSET = '2rpx';
|
|||
|
Component({
|
|||
|
canvasWidthInPx: 0,
|
|||
|
canvasHeightInPx: 0,
|
|||
|
canvasNode: null,
|
|||
|
paintCount: 0,
|
|||
|
currentPalette: {},
|
|||
|
outterDisabled: false,
|
|||
|
isDisabled: false,
|
|||
|
needClear: false,
|
|||
|
/**
|
|||
|
* 组件的属性列表
|
|||
|
*/
|
|||
|
properties: {
|
|||
|
use2D: {
|
|||
|
type: Boolean,
|
|||
|
},
|
|||
|
customStyle: {
|
|||
|
type: String,
|
|||
|
},
|
|||
|
// 运行自定义选择框和删除缩放按钮
|
|||
|
customActionStyle: {
|
|||
|
type: Object,
|
|||
|
},
|
|||
|
palette: {
|
|||
|
type: Object,
|
|||
|
observer: function (newVal, oldVal) {
|
|||
|
if (this.isNeedRefresh(newVal, oldVal)) {
|
|||
|
this.paintCount = 0;
|
|||
|
clearPenCache();
|
|||
|
this.startPaint();
|
|||
|
}
|
|||
|
},
|
|||
|
},
|
|||
|
dancePalette: {
|
|||
|
type: Object,
|
|||
|
observer: function (newVal, oldVal) {
|
|||
|
if (!this.isEmpty(newVal) && !this.properties.use2D) {
|
|||
|
clearPenCache();
|
|||
|
this.initDancePalette(newVal);
|
|||
|
}
|
|||
|
},
|
|||
|
},
|
|||
|
// 缩放比,会在传入的 palette 中统一乘以该缩放比
|
|||
|
scaleRatio: {
|
|||
|
type: Number,
|
|||
|
value: 1,
|
|||
|
},
|
|||
|
widthPixels: {
|
|||
|
type: Number,
|
|||
|
value: 0,
|
|||
|
},
|
|||
|
// 启用脏检查,默认 false
|
|||
|
dirty: {
|
|||
|
type: Boolean,
|
|||
|
value: false,
|
|||
|
},
|
|||
|
LRU: {
|
|||
|
type: Boolean,
|
|||
|
value: false,
|
|||
|
},
|
|||
|
action: {
|
|||
|
type: Object,
|
|||
|
observer: function (newVal, oldVal) {
|
|||
|
if (newVal && !this.isEmpty(newVal) && !this.properties.use2D) {
|
|||
|
this.doAction(newVal, null, false, true);
|
|||
|
}
|
|||
|
},
|
|||
|
},
|
|||
|
disableAction: {
|
|||
|
type: Boolean,
|
|||
|
observer: function (isDisabled) {
|
|||
|
this.outterDisabled = isDisabled;
|
|||
|
this.isDisabled = isDisabled;
|
|||
|
},
|
|||
|
},
|
|||
|
clearActionBox: {
|
|||
|
type: Boolean,
|
|||
|
observer: function (needClear) {
|
|||
|
if (needClear && !this.needClear) {
|
|||
|
if (this.frontContext) {
|
|||
|
setTimeout(() => {
|
|||
|
this.frontContext.draw();
|
|||
|
}, 100);
|
|||
|
this.touchedView = {};
|
|||
|
this.prevFindedIndex = this.findedIndex;
|
|||
|
this.findedIndex = -1;
|
|||
|
}
|
|||
|
}
|
|||
|
this.needClear = needClear;
|
|||
|
},
|
|||
|
},
|
|||
|
},
|
|||
|
|
|||
|
data: {
|
|||
|
picURL: '',
|
|||
|
showCanvas: true,
|
|||
|
painterStyle: '',
|
|||
|
},
|
|||
|
|
|||
|
methods: {
|
|||
|
/**
|
|||
|
* 判断一个 object 是否为 空
|
|||
|
* @param {object} object
|
|||
|
*/
|
|||
|
isEmpty(object) {
|
|||
|
for (const i in object) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
},
|
|||
|
|
|||
|
isNeedRefresh(newVal, oldVal) {
|
|||
|
if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
},
|
|||
|
|
|||
|
getBox(rect, type) {
|
|||
|
const boxArea = {
|
|||
|
type: 'rect',
|
|||
|
css: {
|
|||
|
height: `${rect.bottom - rect.top}px`,
|
|||
|
width: `${rect.right - rect.left}px`,
|
|||
|
left: `${rect.left}px`,
|
|||
|
top: `${rect.top}px`,
|
|||
|
borderWidth: '4rpx',
|
|||
|
borderColor: '#1A7AF8',
|
|||
|
color: 'transparent',
|
|||
|
},
|
|||
|
};
|
|||
|
if (type === 'text') {
|
|||
|
boxArea.css = Object.assign({}, boxArea.css, {
|
|||
|
borderStyle: 'dashed',
|
|||
|
});
|
|||
|
}
|
|||
|
if (this.properties.customActionStyle && this.properties.customActionStyle.border) {
|
|||
|
boxArea.css = Object.assign({}, boxArea.css, this.properties.customActionStyle.border);
|
|||
|
}
|
|||
|
Object.assign(boxArea, {
|
|||
|
id: 'box',
|
|||
|
});
|
|||
|
return boxArea;
|
|||
|
},
|
|||
|
|
|||
|
getScaleIcon(rect, type) {
|
|||
|
let scaleArea = {};
|
|||
|
const { customActionStyle } = this.properties;
|
|||
|
if (customActionStyle && customActionStyle.scale) {
|
|||
|
scaleArea = {
|
|||
|
type: 'image',
|
|||
|
url: type === 'text' ? customActionStyle.scale.textIcon : customActionStyle.scale.imageIcon,
|
|||
|
css: {
|
|||
|
height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
},
|
|||
|
};
|
|||
|
} else {
|
|||
|
scaleArea = {
|
|||
|
type: 'rect',
|
|||
|
css: {
|
|||
|
height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
color: '#0000ff',
|
|||
|
},
|
|||
|
};
|
|||
|
}
|
|||
|
scaleArea.css = Object.assign({}, scaleArea.css, {
|
|||
|
align: 'center',
|
|||
|
left: `${rect.right + ACTION_OFFSET.toPx()}px`,
|
|||
|
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`,
|
|||
|
});
|
|||
|
Object.assign(scaleArea, {
|
|||
|
id: 'scale',
|
|||
|
});
|
|||
|
return scaleArea;
|
|||
|
},
|
|||
|
|
|||
|
getDeleteIcon(rect) {
|
|||
|
let deleteArea = {};
|
|||
|
const { customActionStyle } = this.properties;
|
|||
|
if (customActionStyle && customActionStyle.scale) {
|
|||
|
deleteArea = {
|
|||
|
type: 'image',
|
|||
|
url: customActionStyle.delete.icon,
|
|||
|
css: {
|
|||
|
height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
},
|
|||
|
};
|
|||
|
} else {
|
|||
|
deleteArea = {
|
|||
|
type: 'rect',
|
|||
|
css: {
|
|||
|
height: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
width: `${2 * ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`,
|
|||
|
color: '#0000ff',
|
|||
|
},
|
|||
|
};
|
|||
|
}
|
|||
|
deleteArea.css = Object.assign({}, deleteArea.css, {
|
|||
|
align: 'center',
|
|||
|
left: `${rect.left - ACTION_OFFSET.toPx()}px`,
|
|||
|
top: `${rect.top - ACTION_OFFSET.toPx() - deleteArea.css.height.toPx() / 2}px`,
|
|||
|
});
|
|||
|
Object.assign(deleteArea, {
|
|||
|
id: 'delete',
|
|||
|
});
|
|||
|
return deleteArea;
|
|||
|
},
|
|||
|
|
|||
|
doAction(action, callback, isMoving, overwrite) {
|
|||
|
if (this.properties.use2D) {
|
|||
|
return;
|
|||
|
}
|
|||
|
let newVal = null;
|
|||
|
if (action) {
|
|||
|
newVal = action.view;
|
|||
|
}
|
|||
|
if (newVal && newVal.id && this.touchedView.id !== newVal.id) {
|
|||
|
// 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作
|
|||
|
const { views } = this.currentPalette;
|
|||
|
for (let i = 0; i < views.length; i++) {
|
|||
|
if (views[i].id === newVal.id) {
|
|||
|
// 跨层回撤,需要重新构建三层关系
|
|||
|
this.touchedView = views[i];
|
|||
|
this.findedIndex = i;
|
|||
|
this.sliceLayers();
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const doView = this.touchedView;
|
|||
|
|
|||
|
if (!doView || this.isEmpty(doView)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
if (newVal && newVal.css) {
|
|||
|
if (overwrite) {
|
|||
|
doView.css = newVal.css;
|
|||
|
} else if (Array.isArray(doView.css) && Array.isArray(newVal.css)) {
|
|||
|
doView.css = Object.assign({}, ...doView.css, ...newVal.css);
|
|||
|
} else if (Array.isArray(doView.css)) {
|
|||
|
doView.css = Object.assign({}, ...doView.css, newVal.css);
|
|||
|
} else if (Array.isArray(newVal.css)) {
|
|||
|
doView.css = Object.assign({}, doView.css, ...newVal.css);
|
|||
|
} else {
|
|||
|
doView.css = Object.assign({}, doView.css, newVal.css);
|
|||
|
}
|
|||
|
}
|
|||
|
if (newVal && newVal.rect) {
|
|||
|
doView.rect = newVal.rect;
|
|||
|
}
|
|||
|
if (newVal && newVal.url && doView.url && newVal.url !== doView.url) {
|
|||
|
downloader
|
|||
|
.download(newVal.url, this.properties.LRU)
|
|||
|
.then(path => {
|
|||
|
if (newVal.url.startsWith('https')) {
|
|||
|
doView.originUrl = newVal.url;
|
|||
|
}
|
|||
|
doView.url = path;
|
|||
|
wx.getImageInfo({
|
|||
|
src: path,
|
|||
|
success: res => {
|
|||
|
doView.sHeight = res.height;
|
|||
|
doView.sWidth = res.width;
|
|||
|
this.reDraw(doView, callback, isMoving);
|
|||
|
},
|
|||
|
fail: () => {
|
|||
|
this.reDraw(doView, callback, isMoving);
|
|||
|
},
|
|||
|
});
|
|||
|
})
|
|||
|
.catch(error => {
|
|||
|
// 未下载成功,直接绘制
|
|||
|
console.error(error);
|
|||
|
this.reDraw(doView, callback, isMoving);
|
|||
|
});
|
|||
|
} else {
|
|||
|
newVal && newVal.text && doView.text && newVal.text !== doView.text && (doView.text = newVal.text);
|
|||
|
newVal &&
|
|||
|
newVal.content &&
|
|||
|
doView.content &&
|
|||
|
newVal.content !== doView.content &&
|
|||
|
(doView.content = newVal.content);
|
|||
|
this.reDraw(doView, callback, isMoving);
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
reDraw(doView, callback, isMoving) {
|
|||
|
const draw = {
|
|||
|
width: this.currentPalette.width,
|
|||
|
height: this.currentPalette.height,
|
|||
|
views: this.isEmpty(doView) ? [] : [doView],
|
|||
|
};
|
|||
|
const pen = new Pen(this.globalContext, draw);
|
|||
|
|
|||
|
pen.paint(callbackInfo => {
|
|||
|
callback && callback(callbackInfo);
|
|||
|
this.triggerEvent('viewUpdate', {
|
|||
|
view: this.touchedView,
|
|||
|
});
|
|||
|
});
|
|||
|
|
|||
|
const { rect, css, type } = doView;
|
|||
|
|
|||
|
this.block = {
|
|||
|
width: this.currentPalette.width,
|
|||
|
height: this.currentPalette.height,
|
|||
|
views: this.isEmpty(doView) ? [] : [this.getBox(rect, doView.type)],
|
|||
|
};
|
|||
|
if (css && css.scalable) {
|
|||
|
this.block.views.push(this.getScaleIcon(rect, type));
|
|||
|
}
|
|||
|
if (css && css.deletable) {
|
|||
|
this.block.views.push(this.getDeleteIcon(rect));
|
|||
|
}
|
|||
|
const topBlock = new Pen(this.frontContext, this.block);
|
|||
|
topBlock.paint();
|
|||
|
},
|
|||
|
|
|||
|
isInView(x, y, rect) {
|
|||
|
return x > rect.left && y > rect.top && x < rect.right && y < rect.bottom;
|
|||
|
},
|
|||
|
|
|||
|
isInDelete(x, y) {
|
|||
|
for (const view of this.block.views) {
|
|||
|
if (view.id === 'delete') {
|
|||
|
return x > view.rect.left && y > view.rect.top && x < view.rect.right && y < view.rect.bottom;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
isInScale(x, y) {
|
|||
|
for (const view of this.block.views) {
|
|||
|
if (view.id === 'scale') {
|
|||
|
return x > view.rect.left && y > view.rect.top && x < view.rect.right && y < view.rect.bottom;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
},
|
|||
|
|
|||
|
touchedView: {},
|
|||
|
findedIndex: -1,
|
|||
|
onClick() {
|
|||
|
const x = this.startX;
|
|||
|
const y = this.startY;
|
|||
|
const totalLayerCount = this.currentPalette.views.length;
|
|||
|
let canBeTouched = [];
|
|||
|
let isDelete = false;
|
|||
|
let deleteIndex = -1;
|
|||
|
for (let i = totalLayerCount - 1; i >= 0; i--) {
|
|||
|
const view = this.currentPalette.views[i];
|
|||
|
const { rect } = view;
|
|||
|
if (this.touchedView && this.touchedView.id && this.touchedView.id === view.id && this.isInDelete(x, y, rect)) {
|
|||
|
canBeTouched.length = 0;
|
|||
|
deleteIndex = i;
|
|||
|
isDelete = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
if (this.isInView(x, y, rect)) {
|
|||
|
canBeTouched.push({
|
|||
|
view,
|
|||
|
index: i,
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
this.touchedView = {};
|
|||
|
if (canBeTouched.length === 0) {
|
|||
|
this.findedIndex = -1;
|
|||
|
} else {
|
|||
|
let i = 0;
|
|||
|
const touchAble = canBeTouched.filter(item => Boolean(item.view.id));
|
|||
|
if (touchAble.length === 0) {
|
|||
|
this.findedIndex = canBeTouched[0].index;
|
|||
|
} else {
|
|||
|
for (i = 0; i < touchAble.length; i++) {
|
|||
|
if (this.findedIndex === touchAble[i].index) {
|
|||
|
i++;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (i === touchAble.length) {
|
|||
|
i = 0;
|
|||
|
}
|
|||
|
this.touchedView = touchAble[i].view;
|
|||
|
this.findedIndex = touchAble[i].index;
|
|||
|
this.triggerEvent('viewClicked', {
|
|||
|
view: this.touchedView,
|
|||
|
});
|
|||
|
}
|
|||
|
}
|
|||
|
if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) {
|
|||
|
// 证明点击了背景 或无法移动的view
|
|||
|
this.frontContext.draw();
|
|||
|
if (isDelete) {
|
|||
|
this.triggerEvent('touchEnd', {
|
|||
|
view: this.currentPalette.views[deleteIndex],
|
|||
|
index: deleteIndex,
|
|||
|
type: 'delete',
|
|||
|
});
|
|||
|
this.doAction();
|
|||
|
} else if (this.findedIndex < 0) {
|
|||
|
this.triggerEvent('viewClicked', {});
|
|||
|
}
|
|||
|
this.findedIndex = -1;
|
|||
|
this.prevFindedIndex = -1;
|
|||
|
} else if (this.touchedView && this.touchedView.id) {
|
|||
|
this.sliceLayers();
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
sliceLayers() {
|
|||
|
const bottomLayers = this.currentPalette.views.slice(0, this.findedIndex);
|
|||
|
const topLayers = this.currentPalette.views.slice(this.findedIndex + 1);
|
|||
|
const bottomDraw = {
|
|||
|
width: this.currentPalette.width,
|
|||
|
height: this.currentPalette.height,
|
|||
|
background: this.currentPalette.background,
|
|||
|
views: bottomLayers,
|
|||
|
};
|
|||
|
const topDraw = {
|
|||
|
width: this.currentPalette.width,
|
|||
|
height: this.currentPalette.height,
|
|||
|
views: topLayers,
|
|||
|
};
|
|||
|
if (this.prevFindedIndex < this.findedIndex) {
|
|||
|
new Pen(this.bottomContext, bottomDraw).paint();
|
|||
|
this.doAction();
|
|||
|
new Pen(this.topContext, topDraw).paint();
|
|||
|
} else {
|
|||
|
new Pen(this.topContext, topDraw).paint();
|
|||
|
this.doAction();
|
|||
|
new Pen(this.bottomContext, bottomDraw).paint();
|
|||
|
}
|
|||
|
this.prevFindedIndex = this.findedIndex;
|
|||
|
},
|
|||
|
|
|||
|
startX: 0,
|
|||
|
startY: 0,
|
|||
|
startH: 0,
|
|||
|
startW: 0,
|
|||
|
isScale: false,
|
|||
|
startTimeStamp: 0,
|
|||
|
onTouchStart(event) {
|
|||
|
if (this.isDisabled) {
|
|||
|
return;
|
|||
|
}
|
|||
|
const { x, y } = event.touches[0];
|
|||
|
this.startX = x;
|
|||
|
this.startY = y;
|
|||
|
this.startTimeStamp = new Date().getTime();
|
|||
|
if (this.touchedView && !this.isEmpty(this.touchedView)) {
|
|||
|
const { rect } = this.touchedView;
|
|||
|
if (this.isInScale(x, y, rect)) {
|
|||
|
this.isScale = true;
|
|||
|
this.startH = rect.bottom - rect.top;
|
|||
|
this.startW = rect.right - rect.left;
|
|||
|
} else {
|
|||
|
this.isScale = false;
|
|||
|
}
|
|||
|
} else {
|
|||
|
this.isScale = false;
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
onTouchEnd(e) {
|
|||
|
if (this.isDisabled) {
|
|||
|
return;
|
|||
|
}
|
|||
|
const current = new Date().getTime();
|
|||
|
if (current - this.startTimeStamp <= 500 && !this.hasMove) {
|
|||
|
!this.isScale && this.onClick(e);
|
|||
|
} else if (this.touchedView && !this.isEmpty(this.touchedView)) {
|
|||
|
this.triggerEvent('touchEnd', {
|
|||
|
view: this.touchedView,
|
|||
|
});
|
|||
|
}
|
|||
|
this.hasMove = false;
|
|||
|
},
|
|||
|
|
|||
|
onTouchCancel(e) {
|
|||
|
if (this.isDisabled) {
|
|||
|
return;
|
|||
|
}
|
|||
|
this.onTouchEnd(e);
|
|||
|
},
|
|||
|
|
|||
|
hasMove: false,
|
|||
|
onTouchMove(event) {
|
|||
|
if (this.isDisabled) {
|
|||
|
return;
|
|||
|
}
|
|||
|
this.hasMove = true;
|
|||
|
if (!this.touchedView || (this.touchedView && !this.touchedView.id)) {
|
|||
|
return;
|
|||
|
}
|
|||
|
const { x, y } = event.touches[0];
|
|||
|
const offsetX = x - this.startX;
|
|||
|
const offsetY = y - this.startY;
|
|||
|
const { rect, type } = this.touchedView;
|
|||
|
let css = {};
|
|||
|
if (this.isScale) {
|
|||
|
clearPenCache(this.touchedView.id);
|
|||
|
const newW = this.startW + offsetX > 1 ? this.startW + offsetX : 1;
|
|||
|
if (this.touchedView.css && this.touchedView.css.minWidth) {
|
|||
|
if (newW < this.touchedView.css.minWidth.toPx()) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
if (this.touchedView.rect && this.touchedView.rect.minWidth) {
|
|||
|
if (newW < this.touchedView.rect.minWidth) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
const newH = this.startH + offsetY > 1 ? this.startH + offsetY : 1;
|
|||
|
css = {
|
|||
|
width: `${newW}px`,
|
|||
|
};
|
|||
|
if (type !== 'text') {
|
|||
|
if (type === 'image') {
|
|||
|
css.height = `${(newW * this.startH) / this.startW}px`;
|
|||
|
} else {
|
|||
|
css.height = `${newH}px`;
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
this.startX = x;
|
|||
|
this.startY = y;
|
|||
|
css = {
|
|||
|
left: `${rect.x + offsetX}px`,
|
|||
|
top: `${rect.y + offsetY}px`,
|
|||
|
right: undefined,
|
|||
|
bottom: undefined,
|
|||
|
};
|
|||
|
}
|
|||
|
this.doAction(
|
|||
|
{
|
|||
|
view: {
|
|||
|
css,
|
|||
|
},
|
|||
|
},
|
|||
|
null,
|
|||
|
!this.isScale,
|
|||
|
);
|
|||
|
},
|
|||
|
|
|||
|
initScreenK() {
|
|||
|
if (!(getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth)) {
|
|||
|
try {
|
|||
|
getApp().systemInfo = wx.getSystemInfoSync();
|
|||
|
} catch (e) {
|
|||
|
console.error(`Painter get system info failed, ${JSON.stringify(e)}`);
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
this.screenK = 0.5;
|
|||
|
if (getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth) {
|
|||
|
this.screenK = getApp().systemInfo.screenWidth / 750;
|
|||
|
}
|
|||
|
setStringPrototype(this.screenK, this.properties.scaleRatio);
|
|||
|
},
|
|||
|
|
|||
|
initDancePalette() {
|
|||
|
if (this.properties.use2D) {
|
|||
|
return;
|
|||
|
}
|
|||
|
this.isDisabled = true;
|
|||
|
this.initScreenK();
|
|||
|
this.downloadImages(this.properties.dancePalette).then(async palette => {
|
|||
|
this.currentPalette = palette;
|
|||
|
const { width, height } = palette;
|
|||
|
|
|||
|
if (!width || !height) {
|
|||
|
console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
|
|||
|
return;
|
|||
|
}
|
|||
|
this.setData({
|
|||
|
painterStyle: `width:${width.toPx()}px;height:${height.toPx()}px;`,
|
|||
|
});
|
|||
|
this.frontContext || (this.frontContext = await this.getCanvasContext(this.properties.use2D, 'front'));
|
|||
|
this.bottomContext || (this.bottomContext = await this.getCanvasContext(this.properties.use2D, 'bottom'));
|
|||
|
this.topContext || (this.topContext = await this.getCanvasContext(this.properties.use2D, 'top'));
|
|||
|
this.globalContext || (this.globalContext = await this.getCanvasContext(this.properties.use2D, 'k-canvas'));
|
|||
|
new Pen(this.bottomContext, palette, this.properties.use2D).paint(() => {
|
|||
|
this.isDisabled = false;
|
|||
|
this.isDisabled = this.outterDisabled;
|
|||
|
this.triggerEvent('didShow');
|
|||
|
});
|
|||
|
this.globalContext.draw();
|
|||
|
this.frontContext.draw();
|
|||
|
this.topContext.draw();
|
|||
|
});
|
|||
|
this.touchedView = {};
|
|||
|
},
|
|||
|
|
|||
|
startPaint() {
|
|||
|
this.initScreenK();
|
|||
|
const { width, height } = this.properties.palette;
|
|||
|
|
|||
|
if (!width || !height) {
|
|||
|
console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
let needScale = false;
|
|||
|
// 生成图片时,根据设置的像素值重新绘制
|
|||
|
if (width.toPx() !== this.canvasWidthInPx) {
|
|||
|
this.canvasWidthInPx = width.toPx();
|
|||
|
needScale = this.properties.use2D;
|
|||
|
}
|
|||
|
if (this.properties.widthPixels) {
|
|||
|
setStringPrototype(this.screenK, this.properties.widthPixels / this.canvasWidthInPx);
|
|||
|
this.canvasWidthInPx = this.properties.widthPixels;
|
|||
|
}
|
|||
|
|
|||
|
if (this.canvasHeightInPx !== height.toPx()) {
|
|||
|
this.canvasHeightInPx = height.toPx();
|
|||
|
needScale = needScale || this.properties.use2D;
|
|||
|
}
|
|||
|
this.setData(
|
|||
|
{
|
|||
|
photoStyle: `width:${this.canvasWidthInPx}px;height:${this.canvasHeightInPx}px;`,
|
|||
|
},
|
|||
|
function () {
|
|||
|
this.downloadImages(this.properties.palette).then(async palette => {
|
|||
|
if (!this.photoContext) {
|
|||
|
this.photoContext = await this.getCanvasContext(this.properties.use2D, 'photo');
|
|||
|
}
|
|||
|
if (needScale) {
|
|||
|
const scale = getApp().systemInfo.pixelRatio;
|
|||
|
this.photoContext.width = this.canvasWidthInPx * scale;
|
|||
|
this.photoContext.height = this.canvasHeightInPx * scale;
|
|||
|
this.photoContext.scale(scale, scale);
|
|||
|
}
|
|||
|
new Pen(this.photoContext, palette).paint(() => {
|
|||
|
this.saveImgToLocal();
|
|||
|
});
|
|||
|
setStringPrototype(this.screenK, this.properties.scaleRatio);
|
|||
|
});
|
|||
|
},
|
|||
|
);
|
|||
|
},
|
|||
|
|
|||
|
downloadImages(palette) {
|
|||
|
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, this.properties.LRU).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++;
|
|||
|
/* eslint-disable no-loop-func */
|
|||
|
downloader.download(view.url, this.properties.LRU).then(
|
|||
|
path => {
|
|||
|
view.originUrl = view.url;
|
|||
|
view.url = path;
|
|||
|
wx.getImageInfo({
|
|||
|
src: path,
|
|||
|
success: res => {
|
|||
|
// 获得一下图片信息,供后续裁减使用
|
|||
|
view.sWidth = res.width;
|
|||
|
view.sHeight = res.height;
|
|||
|
},
|
|||
|
fail: error => {
|
|||
|
// 如果图片坏了,则直接置空,防止坑爹的 canvas 画崩溃了
|
|||
|
console.warn(`getImageInfo ${view.originUrl} failed, ${JSON.stringify(error)}`);
|
|||
|
view.url = '';
|
|||
|
},
|
|||
|
complete: () => {
|
|||
|
completeCount++;
|
|||
|
if (preCount === completeCount) {
|
|||
|
resolve(paletteCopy);
|
|||
|
}
|
|||
|
},
|
|||
|
});
|
|||
|
},
|
|||
|
() => {
|
|||
|
completeCount++;
|
|||
|
if (preCount === completeCount) {
|
|||
|
resolve(paletteCopy);
|
|||
|
}
|
|||
|
},
|
|||
|
);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
if (preCount === 0) {
|
|||
|
resolve(paletteCopy);
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
saveImgToLocal() {
|
|||
|
const that = this;
|
|||
|
const optionsOf2d = {
|
|||
|
canvas: that.canvasNode,
|
|||
|
}
|
|||
|
const optionsOfOld = {
|
|||
|
canvasId: 'photo',
|
|||
|
destWidth: that.canvasWidthInPx,
|
|||
|
destHeight: that.canvasHeightInPx,
|
|||
|
}
|
|||
|
setTimeout(() => {
|
|||
|
wx.canvasToTempFilePath(
|
|||
|
{
|
|||
|
...(that.properties.use2D ? optionsOf2d : optionsOfOld),
|
|||
|
success: function (res) {
|
|||
|
that.getImageInfo(res.tempFilePath);
|
|||
|
},
|
|||
|
fail: function (error) {
|
|||
|
console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`);
|
|||
|
that.triggerEvent('imgErr', {
|
|||
|
error: error,
|
|||
|
});
|
|||
|
},
|
|||
|
},
|
|||
|
this,
|
|||
|
);
|
|||
|
}, 300);
|
|||
|
},
|
|||
|
|
|||
|
getCanvasContext(use2D, id) {
|
|||
|
const that = this;
|
|||
|
return new Promise(resolve => {
|
|||
|
if (use2D) {
|
|||
|
const query = wx.createSelectorQuery().in(that);
|
|||
|
const selectId = `#${id}`;
|
|||
|
query
|
|||
|
.select(selectId)
|
|||
|
.fields({ node: true, size: true })
|
|||
|
.exec(res => {
|
|||
|
that.canvasNode = res[0].node;
|
|||
|
const ctx = that.canvasNode.getContext('2d');
|
|||
|
const wxCanvas = new WxCanvas('2d', ctx, id, true, that.canvasNode);
|
|||
|
resolve(wxCanvas);
|
|||
|
});
|
|||
|
} else {
|
|||
|
const temp = wx.createCanvasContext(id, that);
|
|||
|
resolve(new WxCanvas('mina', temp, id, true));
|
|||
|
}
|
|||
|
});
|
|||
|
},
|
|||
|
|
|||
|
getImageInfo(filePath) {
|
|||
|
const that = this;
|
|||
|
wx.getImageInfo({
|
|||
|
src: filePath,
|
|||
|
success: infoRes => {
|
|||
|
if (that.paintCount > MAX_PAINT_COUNT) {
|
|||
|
const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`;
|
|||
|
console.error(error);
|
|||
|
that.triggerEvent('imgErr', {
|
|||
|
error: error,
|
|||
|
});
|
|||
|
return;
|
|||
|
}
|
|||
|
// 比例相符时才证明绘制成功,否则进行强制重绘制
|
|||
|
if (
|
|||
|
Math.abs(
|
|||
|
(infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) /
|
|||
|
(infoRes.height * that.canvasHeightInPx),
|
|||
|
) < 0.01
|
|||
|
) {
|
|||
|
that.triggerEvent('imgOK', {
|
|||
|
path: filePath,
|
|||
|
});
|
|||
|
} else {
|
|||
|
that.startPaint();
|
|||
|
}
|
|||
|
that.paintCount++;
|
|||
|
},
|
|||
|
fail: error => {
|
|||
|
console.error(`getImageInfo failed, ${JSON.stringify(error)}`);
|
|||
|
that.triggerEvent('imgErr', {
|
|||
|
error: error,
|
|||
|
});
|
|||
|
},
|
|||
|
});
|
|||
|
},
|
|||
|
},
|
|||
|
});
|
|||
|
|
|||
|
function setStringPrototype(screenK, scale) {
|
|||
|
/* eslint-disable no-extend-native */
|
|||
|
/**
|
|||
|
* string 到对应的 px
|
|||
|
* @param {Number} baseSize 当设置了 % 号时,设置的基准值
|
|||
|
*/
|
|||
|
String.prototype.toPx = function toPx(_, baseSize) {
|
|||
|
if (this === '0') {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
const REG = /-?[0-9]+(\.[0-9]+)?(rpx|px|%)/;
|
|||
|
|
|||
|
const parsePx = origin => {
|
|||
|
const results = new RegExp(REG).exec(origin);
|
|||
|
if (!origin || !results) {
|
|||
|
console.error(`The size: ${origin} is illegal`);
|
|||
|
return 0;
|
|||
|
}
|
|||
|
const unit = results[2];
|
|||
|
const value = parseFloat(origin);
|
|||
|
|
|||
|
let res = 0;
|
|||
|
if (unit === 'rpx') {
|
|||
|
res = Math.round(value * (screenK || 0.5) * (scale || 1));
|
|||
|
} else if (unit === 'px') {
|
|||
|
res = Math.round(value * (scale || 1));
|
|||
|
} else if (unit === '%') {
|
|||
|
res = Math.round((value * baseSize) / 100);
|
|||
|
}
|
|||
|
return res;
|
|||
|
};
|
|||
|
const formula = /^calc\((.+)\)$/.exec(this);
|
|||
|
if (formula && formula[1]) {
|
|||
|
// 进行 calc 计算
|
|||
|
const afterOne = formula[1].replace(/([^\s\(\+\-\*\/]+)\.(left|right|bottom|top|width|height)/g, word => {
|
|||
|
const [id, attr] = word.split('.');
|
|||
|
return penCache.viewRect[id][attr];
|
|||
|
});
|
|||
|
const afterTwo = afterOne.replace(new RegExp(REG, 'g'), parsePx);
|
|||
|
return calc(afterTwo);
|
|||
|
} else {
|
|||
|
return parsePx(this);
|
|||
|
}
|
|||
|
};
|
|||
|
}
|