初始化仓库
This commit is contained in:
141
components/lime-painter/components/common/relation.js
Normal file
141
components/lime-painter/components/common/relation.js
Normal file
@ -0,0 +1,141 @@
|
||||
import {base64ToPath} from '../l-painter/utils.js'
|
||||
|
||||
const styles = (v = '') => v.split(';').filter(v => v && !/^[\n\s]+$/.test(v)).map(v => {
|
||||
const item = v.split(':');
|
||||
return {
|
||||
[item[0]
|
||||
.replace(/-([a-z])/g, function() {
|
||||
return arguments[1].toUpperCase()
|
||||
})
|
||||
.replace(/\s+/g, '')
|
||||
]: item?. [1]?.replace(/^\s+/, '')?.replace(/\s+$/, '') || ''
|
||||
}
|
||||
})
|
||||
export function parent(parent) {
|
||||
return {
|
||||
provide() {
|
||||
return {
|
||||
[parent]: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
el: {
|
||||
css: {},
|
||||
views: []
|
||||
},
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
css: {
|
||||
handler(v) {
|
||||
if (this.canvasId) {
|
||||
this.el.css = typeof v == 'object' ? v : v && Object.assign(...styles(v)) || {}
|
||||
this.canvasWidth = this.el.css?.width || this.canvasWidth
|
||||
this.canvasHeight = this.el.css?.height || this.canvasHeight
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
export function children(parent, options = {}) {
|
||||
const indexKey = options.indexKey || 'index'
|
||||
return {
|
||||
inject: {
|
||||
[parent]: {
|
||||
default: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
el: {
|
||||
handler(v, o) {
|
||||
if (JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
src: {
|
||||
handler(v, o) {
|
||||
if (v != o)
|
||||
this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
text: {
|
||||
handler(v, o) {
|
||||
if (v != o) this.bindRelation()
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
css: {
|
||||
handler(v, o) {
|
||||
if (v != o)
|
||||
this.el.css = typeof v == 'object' ? v : v && Object.assign(...styles(v)) || {}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
replace: {
|
||||
handler(v, o) {
|
||||
if (JSON.stringify(v) != JSON.stringify(o))
|
||||
this.bindRelation()
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
},
|
||||
created() {
|
||||
Object.defineProperty(this, 'parent', {
|
||||
get: () => {
|
||||
return this[parent]
|
||||
},
|
||||
})
|
||||
Object.defineProperty(this, 'index', {
|
||||
get: () => {
|
||||
this.bindRelation();
|
||||
return this.parent?.el.views?.indexOf(this.el)
|
||||
},
|
||||
});
|
||||
this.el.type = this.type
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.parent) {
|
||||
this.parent.el.views = this.parent.el.views.filter(
|
||||
(item) => item._uid !== this._uid
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
bindRelation() {
|
||||
if (!this.el._uid) {
|
||||
this.el._uid = this._uid
|
||||
}
|
||||
if (['text', 'qrcode'].includes(this.type)) {
|
||||
this.el.text = this.$slots?.default?. [0]?.text || this.text?.replace(/\\n/g, '\n')
|
||||
}
|
||||
if (this.type == 'text' && this.replace) {
|
||||
this.el.replace = this.replace
|
||||
}
|
||||
if (this.type == 'image') {
|
||||
this.el.src = this.src
|
||||
}
|
||||
// || this.parent.el.views.indexOf(this.el) !== -1
|
||||
if (!this.parent) {
|
||||
return;
|
||||
}
|
||||
|
||||
let views = this.parent.el.views || [];
|
||||
if (views.indexOf(this.el) !== -1) {
|
||||
this.parent.el.views = views.map(v => v._uid == this._uid ? this.el : v)
|
||||
} else {
|
||||
this.parent.el.views = [...views, this.el];
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.bindRelation()
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-image',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
src: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'image',
|
||||
el: {
|
||||
css: {},
|
||||
src: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@ -0,0 +1,27 @@
|
||||
<template>
|
||||
<view></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-qrcode',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
text: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'qrcode',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<text style="opacity: 0;"><slot/></text>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-text',
|
||||
mixins:[children('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
text: [String, Number],
|
||||
replace: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'text',
|
||||
el: {
|
||||
css: {},
|
||||
text: null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<view><slot/></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {parent, children} from '../common/relation';
|
||||
export default {
|
||||
name: 'lime-painter-view',
|
||||
mixins:[children('painter'), parent('painter')],
|
||||
props: {
|
||||
css: [String, Object],
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
type: 'view',
|
||||
el: {
|
||||
css: {},
|
||||
views:[]
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
523
components/lime-painter/components/l-painter/l-painter.vue
Normal file
523
components/lime-painter/components/l-painter/l-painter.vue
Normal file
@ -0,0 +1,523 @@
|
||||
<template>
|
||||
<view v-if="canvasId && size" class="lime-painter" :style="size + customStyle">
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
|
||||
<canvas class="lime-painter__canvas" v-else :canvas-id="canvasId" :style="size" :id="canvasId" :width="boardWidth * dpr" :height="boardHeight * dpr"></canvas>
|
||||
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<web-view
|
||||
:style="size"
|
||||
ref="webview"
|
||||
class="lime-painter__canvas"
|
||||
@pagefinish="onPageFinish"
|
||||
@error="onError"
|
||||
@onPostMessage="onMessage"
|
||||
></web-view>
|
||||
<!-- #endif -->
|
||||
<slot/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
import { toPx, compareVersion, sleep, base64ToPath, pathToBase64, getImageInfo, isBase64 } from './utils';
|
||||
import {parent} from '../common/relation'
|
||||
// #ifndef APP-NVUE
|
||||
import {Painter} from './painter'
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
import painterScript from './nvue'
|
||||
// #endif
|
||||
export default {
|
||||
name: 'lime-painter',
|
||||
mixins:[parent('painter')],
|
||||
props: {
|
||||
board: Object,
|
||||
pathType: {
|
||||
type: String,
|
||||
// default: 'url'
|
||||
// 'base64'、'url'
|
||||
},
|
||||
fileType: {
|
||||
type: String,
|
||||
default: 'png'
|
||||
},
|
||||
quality: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
css: [String, Object],
|
||||
width: [Number, String],
|
||||
height: [Number, String],
|
||||
pixelRatio: Number,
|
||||
customStyle: String,
|
||||
isCanvasToTempFilePath: Boolean,
|
||||
sleep: {
|
||||
type: Number,
|
||||
default: 1000 / 30
|
||||
},
|
||||
beforeDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
afterDelay: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO||MP-ALIPAY
|
||||
type: {
|
||||
type: String,
|
||||
default: '2d'
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: true,
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
use2dCanvas: false,
|
||||
// #endif
|
||||
canvasHeight: 150,
|
||||
canvasWidth: null,
|
||||
isDrawIng: false,
|
||||
isPC: false,
|
||||
inited: false,
|
||||
name: 'view',
|
||||
progress: 0,
|
||||
// #ifdef APP-NVUE
|
||||
tempFilePath: [],
|
||||
// #endif
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
canvasId() {
|
||||
return `l-painter${this._uid}`
|
||||
},
|
||||
size() {
|
||||
if(this.boardWidth && this.boardHeight) {
|
||||
return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
|
||||
}
|
||||
},
|
||||
dpr() {
|
||||
return this.pixelRatio || uni.getSystemInfoSync().pixelRatio;
|
||||
},
|
||||
boardWidth() {
|
||||
const {width = 0, canvasWidth = 0} = this
|
||||
const {width: boardWidth = 0} = this.board?.css || this.board || {}
|
||||
return Math.max(toPx(width || boardWidth), toPx(canvasWidth));
|
||||
},
|
||||
boardHeight() {
|
||||
const {height= 0, canvasHeight =0} = this
|
||||
const {height: boardHeight = 0} = this.board?.css || this.board || {}
|
||||
return Math.max(toPx(height || boardHeight), toPx(canvasHeight));
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
canvasWidth(v) {
|
||||
if(this.el.css && !this.el.css?.width) {
|
||||
this.el.css.width = v
|
||||
}
|
||||
},
|
||||
size(v) {
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.use2dCanvas) {
|
||||
this.inited = false;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.inited = false;
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
// #ifdef MP-WEIXIN || MP-TOUTIAO || MP-ALIPAY
|
||||
created() {
|
||||
const { SDKVersion, version, platform, environment } = uni.getSystemInfoSync();
|
||||
// #ifdef MP-WEIXIN
|
||||
// ios wx7.0.20 createImage bug
|
||||
this.isPC = /windows/i.test(platform)
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.9.2') >= 0 && !((/ios/i.test(platform) && /7.0.20/.test(version)) || /wxwork/i.test(environment)) && !this.isPC;
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '1.78.0') >= 0;
|
||||
// #endif
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.use2dCanvas = this.type === '2d' && compareVersion(SDKVersion, '2.8.0') >= 0;
|
||||
// #endif
|
||||
},
|
||||
// #endif
|
||||
mounted() {
|
||||
// #ifdef APP-NVUE
|
||||
this.webViewInit()
|
||||
// #endif
|
||||
this.$nextTick(() => {
|
||||
setTimeout(() => {
|
||||
if(this.board) {
|
||||
this.$watch('board', this.watchRender, {deep: true,immediate: true});
|
||||
} else if(this.el.views.length) {
|
||||
this.$watch('el', this.watchRender, {deep: true,immediate: true});
|
||||
}
|
||||
},30)
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
async watchRender(val) {
|
||||
this.progress = 0
|
||||
if (JSON.stringify(val) === '{}' || !val) return;
|
||||
clearTimeout(this.rendertimer)
|
||||
this.rendertimer = setTimeout(() => {
|
||||
this.render(val);
|
||||
}, this.beforeDelay)
|
||||
},
|
||||
async setFilePath(path, isEmit) {
|
||||
let filePath = path
|
||||
const {pathType} = this
|
||||
if(pathType == 'base64' && !isBase64(path)) {
|
||||
filePath = await pathToBase64(path)
|
||||
} else if(pathType == 'url' && isBase64(path)) {
|
||||
filePath = await base64ToPath(path)
|
||||
}
|
||||
if(isEmit) {
|
||||
this.$emit('success', filePath);
|
||||
}
|
||||
return filePath
|
||||
},
|
||||
// #ifdef APP-NVUE
|
||||
onError(e) {
|
||||
console.log('onError', e)
|
||||
},
|
||||
// onPagestart() {
|
||||
// console.log('onPagestart')
|
||||
// },
|
||||
// onPageFinish() {
|
||||
// this.$refs.webview.evalJS(`init()`)
|
||||
// },
|
||||
// onReceivedTitle() {
|
||||
// console.log('onReceivedTitle')
|
||||
// },
|
||||
onMessage(e) {
|
||||
const res = e?.detail?.data[0] || null;
|
||||
if (res?.event) {
|
||||
if(res.event == 'inited') {
|
||||
this.inited = true
|
||||
}
|
||||
if(res.event == 'layoutChange') {
|
||||
const data = JSON.parse(res.data)
|
||||
this.canvasWidth = data.width;
|
||||
this.canvasHeight = data.height;
|
||||
}
|
||||
if(res.event == 'progressChange') {
|
||||
this.progress = res.data * 1
|
||||
}
|
||||
if(res.event == 'file') {
|
||||
this.tempFilePath.push(res.data)
|
||||
if(this.tempFilePath.length > 7) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
return
|
||||
}
|
||||
if(res.event == 'success') {
|
||||
if(res.data) {
|
||||
this.tempFilePath.push(res.data)
|
||||
if(this.tempFilePath.length > 8) {
|
||||
this.tempFilePath.shift()
|
||||
}
|
||||
if(this.isCanvasToTempFilePath) {
|
||||
this.setFilePath(this.tempFilePath.join(''), true)
|
||||
}
|
||||
} else {
|
||||
this.$emit('fail')
|
||||
}
|
||||
return
|
||||
}
|
||||
this.$emit(res.event, JSON.parse(res.data));
|
||||
} else if (res?.file) {
|
||||
this.file = res.data;
|
||||
} else {
|
||||
console.error(res);
|
||||
}
|
||||
},
|
||||
async webViewInit() {
|
||||
await sleep(30)
|
||||
// await this.getWebViewInited()
|
||||
const webview = this.$refs.webview;
|
||||
webview.evalJS(painterScript)
|
||||
},
|
||||
getWebViewInited() {
|
||||
if(this.inited) return Promise.resolve(this.inited);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'inited',
|
||||
async val => {if(val) {resolve(val)}},
|
||||
{immediate: true}
|
||||
);
|
||||
})
|
||||
},
|
||||
getTempFilePath() {
|
||||
if(this.tempFilePath.length == 8) return Promise.resolve(this.tempFilePath)
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'tempFilePath',
|
||||
async val => {if(val.length == 8) {resolve(val.join(''))}}
|
||||
);
|
||||
})
|
||||
},
|
||||
getWebViewDone() {
|
||||
if(this.progress == 1) return Promise.resolve(this.progress);
|
||||
return new Promise((resolve) => {
|
||||
this.$watch(
|
||||
'progress',
|
||||
async val => {
|
||||
if(val == 1) {
|
||||
this.$emit('done')
|
||||
resolve(val)
|
||||
}},
|
||||
{immediate: true}
|
||||
);
|
||||
})
|
||||
},
|
||||
async render(args) {
|
||||
const newNode = await this.calcImage(args);
|
||||
await this.getWebViewInited()
|
||||
const webview = this.$refs.webview;
|
||||
webview.evalJS(`source(${JSON.stringify(newNode)})`)
|
||||
if(this.isCanvasToTempFilePath) {
|
||||
await this.getWebViewDone()
|
||||
await sleep(this.afterDelay)
|
||||
const params = {fileType: this.fileType, quality: this.quality}
|
||||
webview.evalJS(`save(${JSON.stringify(params)})`)
|
||||
}
|
||||
},
|
||||
async calcImage(args) {
|
||||
let node = JSON.parse(JSON.stringify(args))
|
||||
const url = node.url || node.src
|
||||
if(node.type === "image" && url && !isBase64(url)) {
|
||||
const suffix = url.match(/\.(\w+)$/)[1]
|
||||
const {width = 0, height = 0, path, naturalSrc} = await getImageInfo(url)
|
||||
const src = await pathToBase64(path)
|
||||
node.src = src.replace(/^data:application[\w\/]+;base64/,'data:image/'+suffix+';base64')
|
||||
} else if(node.views?.length) {
|
||||
for (let i = 0; i < node.views.length; i++) {
|
||||
node.views[i] = await this.calcImage(node.views[i])
|
||||
}
|
||||
}
|
||||
return node
|
||||
},
|
||||
async canvasToTempFilePath(args = {}){
|
||||
this.tempFilePath = []
|
||||
this.$refs.webview.evalJS(`save(${JSON.stringify(args)})`)
|
||||
try{
|
||||
let tempFilePath = await this.getTempFilePath()
|
||||
tempFilePath = await this.setFilePath(tempFilePath)
|
||||
args.success({errMsg: "canvasToTempFilePath:ok", tempFilePath})
|
||||
}catch(e){
|
||||
args.fail({error: e})
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
getParentWeith() {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`.lime-painter`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
this.canvasWidth = Math.ceil(res[0].width)
|
||||
this.canvasHeight = res[0].height
|
||||
})
|
||||
},
|
||||
async update(args, single) {
|
||||
this.painter = null;
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.use2dCanvas) {
|
||||
this.ctx = null;
|
||||
this.inited = false;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
this.inited = false;
|
||||
// #endif
|
||||
this.isDrawIng = false;
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
await sleep(200);
|
||||
return this.render(args, single);
|
||||
},
|
||||
async render(args = {}, single = false) {
|
||||
if (this.isDrawIng) {
|
||||
return this.update(args, single);
|
||||
}
|
||||
this.isDrawIng = true;
|
||||
const isArg = JSON.stringify(args) != '{}';
|
||||
const ctx = await this.getContext();
|
||||
let { use2dCanvas, boardWidth, boardHeight, canvas, afterDelay } = this;
|
||||
if (use2dCanvas && !canvas) {
|
||||
return Promise.reject(new Error('render: fail canvas has not been created'));
|
||||
}
|
||||
this.boundary = {
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: boardWidth,
|
||||
height: boardHeight
|
||||
};
|
||||
// if (!single) {
|
||||
// ctx.clearRect(0, 0, boardWidth, boardHeight);
|
||||
// }
|
||||
if(!this.painter) {
|
||||
this.painter = new Painter({context: ctx, canvas, width: boardWidth, height: boardHeight, pixelRatio: this.dpr}, this)
|
||||
}
|
||||
this.painter.listen('progressChange', (v) => {
|
||||
this.$emit('progress', v)
|
||||
})
|
||||
const {width, height} = await this.painter.source(args)
|
||||
this.canvasHeight = toPx(this.el.css?.height) || height
|
||||
this.canvasWidth = toPx(this.el.css?.width) || width
|
||||
this.boundary.height = this.canvasHeight
|
||||
this.boundary.width = this.canvasWidth
|
||||
await sleep(this.sleep);
|
||||
await this.painter.render()
|
||||
|
||||
await new Promise(resolve => this.$nextTick(resolve));
|
||||
if (!use2dCanvas && !single) {
|
||||
await this.canvasDraw();
|
||||
}
|
||||
if (afterDelay && use2dCanvas) {
|
||||
await sleep(afterDelay);
|
||||
}
|
||||
this.$emit('done');
|
||||
if (this.isCanvasToTempFilePath && !single && this.isDrawIng) {
|
||||
this.canvasToTempFilePath()
|
||||
.then(async res => {
|
||||
this.$emit('success', res.tempFilePath)
|
||||
})
|
||||
.catch(err => {
|
||||
this.$emit('fail', new Error(JSON.stringify(err)));
|
||||
});
|
||||
}
|
||||
this.isDrawIng = false;
|
||||
return Promise.resolve({ ctx, draw: this.painter, node: this.node });
|
||||
},
|
||||
async custom(cb) {
|
||||
const { ctx, draw } = await this.render({}, true);
|
||||
ctx.save();
|
||||
await cb(ctx, draw);
|
||||
ctx.restore();
|
||||
return Promise.resolve(true);
|
||||
},
|
||||
async single(args = {}) {
|
||||
const res = await this.render(args, true);
|
||||
return Promise.resolve(res);
|
||||
},
|
||||
canvasDraw(flag = false) {
|
||||
return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this.afterDelay)));
|
||||
},
|
||||
async getContext() {
|
||||
if (this.ctx && this.inited) {
|
||||
return Promise.resolve(this.ctx);
|
||||
}
|
||||
this.getParentWeith()
|
||||
const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
|
||||
const _getContext = () => {
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.boundingClientRect()
|
||||
.exec(res => {
|
||||
if (res) {
|
||||
const ctx = uni.createCanvasContext(this.canvasId, this);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = false;
|
||||
this.canvas = res;
|
||||
}
|
||||
if(this.isPC) {
|
||||
ctx.scale(1/dpr, 1/dpr);
|
||||
}
|
||||
// #ifdef MP-ALIPAY
|
||||
ctx.scale(dpr, dpr);
|
||||
// #endif
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
// #ifndef MP-WEIXIN
|
||||
return _getContext();
|
||||
// #endif
|
||||
|
||||
if (!use2dCanvas) {
|
||||
return _getContext();
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this)
|
||||
.select(`#${this.canvasId}`)
|
||||
.node()
|
||||
.exec(res => {
|
||||
let { node: canvas } = res[0];
|
||||
if (!canvas) {
|
||||
this.use2dCanvas = false;
|
||||
resolve(this.getContext());
|
||||
}
|
||||
const ctx = canvas.getContext(type);
|
||||
if (!this.inited) {
|
||||
this.inited = true;
|
||||
this.use2dCanvas = true;
|
||||
this.canvas = canvas;
|
||||
}
|
||||
this.ctx = ctx
|
||||
resolve(this.ctx);
|
||||
});
|
||||
});
|
||||
},
|
||||
canvasToTempFilePath(args = {}) {
|
||||
const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
|
||||
return new Promise((resolve, reject) => {
|
||||
let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
|
||||
let destWidth = width * dpr;
|
||||
let destHeight = height * dpr;
|
||||
// #ifdef MP-ALIPAY
|
||||
width = destWidth;
|
||||
height = destHeight;
|
||||
// #endif
|
||||
const success = async (res) => {
|
||||
const tempFilePath = await this.setFilePath(res.tempFilePath)
|
||||
resolve(Object.assign(res, {tempFilePath}))
|
||||
}
|
||||
const copyArgs = Object.assign({
|
||||
x,
|
||||
y,
|
||||
width,
|
||||
height,
|
||||
destWidth,
|
||||
destHeight,
|
||||
canvasId,
|
||||
fileType,
|
||||
quality,
|
||||
success,
|
||||
fail: reject
|
||||
}, args);
|
||||
if (use2dCanvas) {
|
||||
delete copyArgs.canvasId;
|
||||
copyArgs.canvas = this.canvas;
|
||||
}
|
||||
uni.canvasToTempFilePath(copyArgs, this);
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.lime-painter, .lime-painter__canvas {
|
||||
// #ifndef APP-NVUE
|
||||
width: 100%;
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
flex: 1;
|
||||
// #endif
|
||||
}
|
||||
</style>
|
||||
103
components/lime-painter/components/l-painter/nvue.js
Normal file
103
components/lime-painter/components/l-painter/nvue.js
Normal file
@ -0,0 +1,103 @@
|
||||
const painterContent = `
|
||||
var cache = [];
|
||||
var painter = null;
|
||||
var canvas = null;
|
||||
var context = null;
|
||||
var timer = null;
|
||||
var pixelRatio = 1;
|
||||
console.log = function(...args) {
|
||||
postMessage(args);
|
||||
};
|
||||
function stringify(key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
return;
|
||||
}
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
function emit(event, data) {
|
||||
let dataStr = typeof data !== 'object' && data !== null ? data : JSON.stringify(data, stringify);
|
||||
postMessage({
|
||||
event,
|
||||
data: dataStr
|
||||
});
|
||||
cache = [];
|
||||
};
|
||||
function postMessage(data) {
|
||||
uni.postMessage({
|
||||
data
|
||||
});
|
||||
};
|
||||
function init() {
|
||||
canvas = document.querySelector('#lime-painter');
|
||||
context = canvas.getContext('2d');
|
||||
pixelRatio = window.devicePixelRatio;
|
||||
painter = new Painter({
|
||||
id: 'lime-painter',
|
||||
context,
|
||||
canvas,
|
||||
pixelRatio,
|
||||
width: canvas.offsetWidth,
|
||||
height: canvas.offsetHeight
|
||||
});
|
||||
emit('inited', true);
|
||||
painter.listen('progressChange', (v) => {
|
||||
emit('progressChange', v);
|
||||
});
|
||||
};
|
||||
function save(args) {
|
||||
delete args.success;
|
||||
delete args.fail;
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
const path = painter.save(args);
|
||||
if(typeof path == 'string') {
|
||||
const index = Math.ceil(path.length / 8);
|
||||
for (var i = 0; i < 8; i++) {
|
||||
if(i == 7) {
|
||||
emit('success', path.substr(i * index, index));
|
||||
} else {
|
||||
emit('file', path.substr(i * index, index));
|
||||
}
|
||||
};
|
||||
} else {
|
||||
emit('fail', '');
|
||||
};
|
||||
}, 30);
|
||||
};
|
||||
async function source(args) {
|
||||
let res = await painter.source(args);
|
||||
emit('layoutChange', res);
|
||||
await painter.render();
|
||||
};
|
||||
`
|
||||
export default `
|
||||
document.write("<canvas id='lime-painter'>不支持cavnas</canvas>");
|
||||
let meta = document.createElement('meta');
|
||||
meta.name = 'viewport';
|
||||
meta.content = 'width=device-width, initial-scale=1.0';
|
||||
document.head.appendChild(meta);
|
||||
let styleEl = document.createElement('style');
|
||||
styleEl.setAttribute('type', 'text/css');
|
||||
styleEl.textContent='html,body,#lime-painter{padding: 0; margin: 0; width:100%;height:100%}';
|
||||
document.head.appendChild(styleEl);
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.src = "https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js";
|
||||
script.onload = function() {
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.src = "https://cdn.jsdelivr.net/gh/liangei/image@latest/lime-ui/lime-painter/painter.js";
|
||||
script.onload = function() {init()};
|
||||
document.head.appendChild(script);
|
||||
};
|
||||
document.head.appendChild(script);
|
||||
|
||||
var script = document.createElement("script");
|
||||
script.language = "javascript";
|
||||
script.text = "${painterContent}";
|
||||
document.body.appendChild(script);
|
||||
`
|
||||
15
components/lime-painter/components/l-painter/painter.js
Normal file
15
components/lime-painter/components/l-painter/painter.js
Normal file
File diff suppressed because one or more lines are too long
414
components/lime-painter/components/l-painter/utils.js
Normal file
414
components/lime-painter/components/l-painter/utils.js
Normal file
@ -0,0 +1,414 @@
|
||||
const networkReg = /^(http|\/\/)/;
|
||||
export const isBase64 = (path) => /^data:image\/(\w+);base64/.test(path);
|
||||
export function sleep(delay) {
|
||||
return new Promise(resolve => setTimeout(resolve, delay))
|
||||
}
|
||||
const isDev = ['devtools'].includes(uni.getSystemInfoSync().platform)
|
||||
// 缓存图片
|
||||
let cache = {}
|
||||
export function isNumber(value) {
|
||||
return /^-?\d+(\.\d+)?$/.test(value);
|
||||
}
|
||||
export function toPx(value, baseSize, isDecimal = false) {
|
||||
// 如果是数字
|
||||
if (typeof value === 'number') {
|
||||
return value
|
||||
}
|
||||
// 如果是字符串数字
|
||||
if (isNumber(value)) {
|
||||
return value * 1
|
||||
}
|
||||
// 如果有单位
|
||||
if (typeof value === 'string') {
|
||||
const reg = /^-?([0-9]+)?([.]{1}[0-9]+){0,1}(em|rpx|px|%)$/g
|
||||
const results = reg.exec(value);
|
||||
if (!value || !results) {
|
||||
return 0;
|
||||
}
|
||||
const unit = results[3];
|
||||
value = parseFloat(value);
|
||||
let res = 0;
|
||||
if (unit === 'rpx') {
|
||||
res = uni.upx2px(value);
|
||||
} else if (unit === 'px') {
|
||||
res = value * 1;
|
||||
} else if (unit === '%') {
|
||||
res = value * toPx(baseSize) / 100;
|
||||
} else if (unit === 'em') {
|
||||
res =value * toPx(baseSize || 14);
|
||||
}
|
||||
return isDecimal ? res.toFixed(2) * 1 : Math.round(res);
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算版本
|
||||
export function compareVersion(v1, v2) {
|
||||
v1 = v1.split('.')
|
||||
v2 = v2.split('.')
|
||||
const len = Math.max(v1.length, v2.length)
|
||||
while (v1.length < len) {
|
||||
v1.push('0')
|
||||
}
|
||||
while (v2.length < len) {
|
||||
v2.push('0')
|
||||
}
|
||||
for (let i = 0; i < len; i++) {
|
||||
const num1 = parseInt(v1[i], 10)
|
||||
const num2 = parseInt(v2[i], 10)
|
||||
|
||||
if (num1 > num2) {
|
||||
return 1
|
||||
} else if (num1 < num2) {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
// #ifdef MP
|
||||
export const prefix = () => {
|
||||
// #ifdef MP-TOUTIAO
|
||||
return tt
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
return wx
|
||||
// #endif
|
||||
// #ifdef MP-BAIDU
|
||||
return swan
|
||||
// #endif
|
||||
// #ifdef MP-ALIPAY
|
||||
return my
|
||||
// #endif
|
||||
// #ifdef MP-QQ
|
||||
return qq
|
||||
// #endif
|
||||
// #ifdef MP-360
|
||||
return qh
|
||||
// #endif
|
||||
}
|
||||
// #endif
|
||||
|
||||
const base64ToArrayBuffer = (data) => {
|
||||
// #ifndef MP-WEIXIN || APP-PLUS
|
||||
/**
|
||||
* Base64Binary.decode(base64_string);
|
||||
* Base64Binary.decodeArrayBuffer(base64_string);
|
||||
*/
|
||||
const Base64Binary = {
|
||||
_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
|
||||
/* will return a Uint8Array type */
|
||||
decodeArrayBuffer(input) {
|
||||
const bytes = (input.length/4) * 3;
|
||||
const ab = new ArrayBuffer(bytes);
|
||||
this.decode(input, ab);
|
||||
return ab;
|
||||
},
|
||||
removePaddingChars(input) {
|
||||
const lkey = this._keyStr.indexOf(input.charAt(input.length - 1));
|
||||
if(lkey == 64){
|
||||
return input.substring(0,input.length - 1);
|
||||
}
|
||||
return input;
|
||||
},
|
||||
decode(input, arrayBuffer) {
|
||||
//get last chars to see if are valid
|
||||
input = this.removePaddingChars(input);
|
||||
input = this.removePaddingChars(input);
|
||||
|
||||
const bytes = parseInt((input.length / 4) * 3, 10);
|
||||
|
||||
let uarray;
|
||||
let chr1, chr2, chr3;
|
||||
let enc1, enc2, enc3, enc4;
|
||||
let i = 0;
|
||||
let j = 0;
|
||||
|
||||
if (arrayBuffer)
|
||||
uarray = new Uint8Array(arrayBuffer);
|
||||
else
|
||||
uarray = new Uint8Array(bytes);
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
for (i=0; i<bytes; i+=3) {
|
||||
//get the 3 octects in 4 ascii chars
|
||||
enc1 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc2 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc3 = this._keyStr.indexOf(input.charAt(j++));
|
||||
enc4 = this._keyStr.indexOf(input.charAt(j++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
uarray[i] = chr1;
|
||||
if (enc3 != 64) uarray[i+1] = chr2;
|
||||
if (enc4 != 64) uarray[i+2] = chr3;
|
||||
}
|
||||
return uarray;
|
||||
}
|
||||
}
|
||||
return Base64Binary.decodeArrayBuffer(data)
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || APP-PLUS
|
||||
return uni.base64ToArrayBuffer(data)
|
||||
// #endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* base64转路径
|
||||
* @param {Object} base64
|
||||
*/
|
||||
export function base64ToPath(base64) {
|
||||
const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64) || [];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP
|
||||
const fs = uni.getFileSystemManager()
|
||||
//自定义文件名
|
||||
if (!format) {
|
||||
console.error('ERROR_BASE64SRC_PARSE')
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
let pre = prefix()
|
||||
const filePath = `${pre.env.USER_DATA_PATH}/${time}.${format}`
|
||||
//let buffer = base64ToArrayBuffer(bodyData)
|
||||
console.log(filePath)
|
||||
fs.writeFile({
|
||||
filePath,
|
||||
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
// data: buffer,
|
||||
// encoding: 'binary',
|
||||
success() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail(err) {
|
||||
|
||||
console.log(base64,'!!!!!!')
|
||||
console.error('获取base64图片失败', JSON.stringify(err))
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// mime类型
|
||||
let mimeString = base64.split(',')[0].split(':')[1].split(';')[0];
|
||||
//base64 解码
|
||||
let byteString = atob(base64.split(',')[1]);
|
||||
//创建缓冲数组
|
||||
let arrayBuffer = new ArrayBuffer(byteString.length);
|
||||
//创建视图
|
||||
let intArray = new Uint8Array(arrayBuffer);
|
||||
for (let i = 0; i < byteString.length; i++) {
|
||||
intArray[i] = byteString.charCodeAt(i);
|
||||
}
|
||||
resolve(URL.createObjectURL(new Blob([intArray], { type: mimeString })))
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, () => {
|
||||
if (!format) {
|
||||
reject(new Error('ERROR_BASE64SRC_PARSE'))
|
||||
}
|
||||
const time = new Date().getTime();
|
||||
const filePath = `_doc/uniapp_temp/${time}.${format}`
|
||||
bitmap.save(filePath, {},
|
||||
() => {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
},
|
||||
(error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
bitmap.clear()
|
||||
console.error(`${JSON.stringify(error)}`)
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径转base64
|
||||
* @param {Object} string
|
||||
*/
|
||||
export function pathToBase64(path) {
|
||||
if(/^data:/.test(path)) return path
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef H5
|
||||
const _canvas = ()=> {
|
||||
let image = new Image();
|
||||
image.setAttribute("crossOrigin",'Anonymous');
|
||||
image.onload = function() {
|
||||
let canvas = document.createElement('canvas');
|
||||
// 获取图片原始宽高
|
||||
canvas.width = this.naturalWidth;
|
||||
canvas.height = this.naturalHeight;
|
||||
// 将图片插入画布并开始绘制
|
||||
canvas.getContext('2d').drawImage(image, 0, 0);
|
||||
let result = canvas.toDataURL('image/png')
|
||||
resolve(result);
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
image.src = path
|
||||
image.onerror = (error) => {
|
||||
console.error(`urlToBase64 error: ${path}`, JSON.stringify(error))
|
||||
reject(new Error('urlToBase64 error'));
|
||||
};
|
||||
}
|
||||
const _fileReader = (blob) => {
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (e) => {
|
||||
resolve(e.target.result);
|
||||
};
|
||||
fileReader.readAsDataURL(blob);
|
||||
fileReader.onerror = (error) => {
|
||||
console.error('blobToBase64 error:', JSON.stringify(error))
|
||||
reject(new Error('blobToBase64 error'));
|
||||
};
|
||||
}
|
||||
const isFileReader = typeof FileReader === 'function'
|
||||
if(networkReg.test(path) && isFileReader ) {
|
||||
window.URL = window.URL || window.webkitURL;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("get", path, true);
|
||||
xhr.timeout = 2000;
|
||||
xhr.responseType = "blob";
|
||||
xhr.onload = function() {
|
||||
if(this.status == 200) {
|
||||
_fileReader(this.response)
|
||||
} else {
|
||||
_canvas()
|
||||
}
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if(this.status === 0) {
|
||||
console.error('图片跨域了,得后端处理咯')
|
||||
}
|
||||
}
|
||||
xhr.send();
|
||||
} else if(/^blob/.test(path) && isFileReader){
|
||||
_fileReader(path)
|
||||
} else {
|
||||
_canvas()
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
if(uni.canIUse('getFileSystemManager')) {
|
||||
uni.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: (res) => {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('urlToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), (entry) => {
|
||||
entry.file((file) => {
|
||||
const fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = (data) => { resolve(data.target.result)}
|
||||
fileReader.onerror = (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
})
|
||||
}, (error) => {
|
||||
console.error('pathToBase64 error:', JSON.stringify(error))
|
||||
reject(error)
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const getLocalFilePath = (path)=> {
|
||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('file://') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
const localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substr(1)
|
||||
}
|
||||
}
|
||||
return '_www/' + path
|
||||
}
|
||||
// #endif
|
||||
|
||||
export function getImageInfo(img, isH5PathToBase64 = false) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// const base64Reg = /^data:image\/(\w+);base64/
|
||||
const localReg = /^\.|^\/(?=[^\/])/;
|
||||
// #ifdef H5
|
||||
if(networkReg.test(img) && isH5PathToBase64) {
|
||||
img = await pathToBase64(img)
|
||||
}
|
||||
// #endif
|
||||
// #ifndef MP-ALIPAY
|
||||
if(isBase64(img)) {
|
||||
|
||||
if(isDev || !cache[img]) {
|
||||
const imgName = img
|
||||
img = await base64ToPath(img)
|
||||
cache[imgName] = img
|
||||
} else {
|
||||
img = cache[img]
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
if(cache[img] && cache[img].errMsg) {
|
||||
resolve(cache[img])
|
||||
} else {
|
||||
uni.getImageInfo({
|
||||
src: img,
|
||||
success: (image) => {
|
||||
// #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO
|
||||
image.path = localReg.test(img) ? `/${image.path}` : image.path;
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
image.path = image.path.replace(/^\./, window.location.origin)
|
||||
// #endif
|
||||
image.naturalSrc = img
|
||||
if(isDev) {
|
||||
resolve(image)
|
||||
} else {
|
||||
cache[img] = image
|
||||
resolve(cache[img])
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
resolve({path: img})
|
||||
console.error(`getImageInfo:fail ${img} failed ${JSON.stringify(err)}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user