ruoyi-geek-App/uni_modules/uview-plus/components/u-cropper/u-cropper.vue
2025-11-25 03:31:46 +08:00

1229 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="u-cropper">
<!-- <image :src="imgSrc.imgSrc" @click="select" :style="[ imgStyle ]" class="my-avatar"></image> -->
<canvas :canvas-id="'avatar-canvas-' + instanceId" :id="'avatar-canvas-' + instanceId" class="my-canvas"
:style="{top: styleTop, height: cvsStyleHeight}" disable-scroll="false"></canvas>
<canvas :canvas-id="'oper-canvas-' + instanceId" :id="'oper-canvas-' + instanceId" class="oper-canvas"
:style="{top: styleTop, height: cvsStyleHeight}"
disable-scroll="false" @touchstart="start" @touchmove="move" @touchend="end"></canvas>
<canvas :canvas-id="'prv-canvas-' + instanceId" :id="'prv-canvas-' + instanceId" class="prv-canvas"
disable-scroll="false" @touchstart="hideImg" :style="{ height: cvsStyleHeight, top: prvTop }"></canvas>
<view class="oper-wrapper" :style="{display: styleDisplay}">
<view class="oper">
<view class="btn-wrapper" v-if="showOper">
<view @click="select" hover-class="hover" :style="{width: btnWidth}">
<text>{{ t("up.common.re-select") }}</text>
</view>
<view @click="close" hover-class="hover" :style="{width: btnWidth}">
<text>{{ t("up.common.close") }}</text>
</view>
<view @click="rotate" hover-class="hover" :style="{width: btnWidth, display: btnDsp}">
<text>{{ t("up.common.rotate") }}</text>
</view>
<view @click="preview" hover-class="hover" :style="{width: btnWidth}">
<text>{{ t("up.common.preview") }}</text>
</view>
<view @click="confirm" hover-class="hover" :style="{width: btnWidth}">
<text>{{ t("up.common.confirm") }}</text>
</view>
</view>
<view class="clr-wrapper" v-else>
<slider class="my-slider" @change="colorChange"
block-size="25" value="0" min="-100" max="100" activeColor="red"
backgroundColor="green" block-color="grey" show-value></slider>
<view @click="prvUpload" hover-class="hover" :style="{width: btnWidth}">
<text>{{ t("up.common.confirm") }}</text>
</view>
</view>
</view>
</view>
<view @click="chooseImage(0, {})" v-if="styleDisplay == 'none'">
<slot>
</slot>
</view>
</view>
</template>
<script>
import { t } from '../../libs/i18n'
const tabHeight = 50;
export default {
name: "u-cropper",
data() {
return {
// 添加实例ID用于区分不同实例
instanceId: Date.now() + '-' + Math.random().toString(36).substr(2, 9),
cvsStyleHeight: '0px',
styleDisplay: 'none',
styleTop: '-10000px',
prvTop: '-10000px',
imgStyle: {},
selStyle: {},
showOper: true,
imgSrc: {
imgSrc: ''
},
btnWidth: '19%',
btnDsp: 'flex',
// 裁剪区域宽度,用于设置选择区域的宽度
arWidth: '',
// 裁剪区域高度,用于设置选择区域的高度
arHeight: '',
// 导出图片宽度,用于设置最终导出图片的宽度
expWidth: '',
// 导出图片高度,用于设置最终导出图片的高度
expHeight: '',
// 是否允许调整裁剪框大小
letChangeSize: false,
};
},
watch: {
avatarSrc() {
this.imgSrc.imgSrc = this.avatarSrc;
}
},
emits: ['avtinit', 'confirm'],
props:{
minScale: '',
maxScale: '',
canScale: true,
canRotate: true,
lockWidth: '',
lockHeight: '',
stretch: '',
lock: '',
noTab: true,
inner: false,
quality: '',
index: '',
canChangeSize: false,
areaWidth: '300rpx',
// 裁剪区域高度,用于设置选择区域的高度
areaHeight: '300rpx',
// 导出图片宽度,用于设置最终导出图片的宽度
exportWidth: '260rpx',
// 导出图片高度,用于设置最终导出图片的高度
exportHeight: '260rpx',
},
created() {
this.ctxCanvas = uni.createCanvasContext('avatar-canvas-' + this.instanceId, this);
this.ctxCanvasOper = uni.createCanvasContext('oper-canvas-' + this.instanceId, this);
this.ctxCanvasPrv = uni.createCanvasContext('prv-canvas-' + this.instanceId, this);
this.qlty = parseInt(this.quality) || 0.9;
this.imgSrc.imgSrc = this.imageSrc;
this.letRotate = (this.canRotate === false || this.inner === true) ? 0 : 1;
this.letScale = this.canScale === false ? 0 : 1;
// 是否允许调整裁剪框大小false表示不允许其他值表示允许
this.letChangeSize = this.canChangeSize;
this.isin = this.inner === true ? 1 : 0;
this.indx = this.index || undefined;
this.mnScale = this.minScale || 0.3;
this.mxScale = this.maxScale || 4;
this.noBar = this.noTab === true ? 1 : 0;
this.stc = this.stretch;
this.lck = this.lock;
if(this.isin) {
this.btnWidth = '24%';
this.btnDsp = 'none';
} else {
this.btnWidth = '19%';
this.btnDsp = 'flex';
}
if(this.noBar) {
this.moreHeight = 0;
this.windowResize();
} else {
uni.showTabBar({
complete:(res) => {
this.moreHeight = (res.errMsg === 'showTabBar:ok') ? 50 : 0;
this.windowResize();
}
});
}
},
methods: {
t,
windowResize() {
let sysInfo = uni.getSystemInfoSync();
this.platform = sysInfo.platform;
this.pixelRatio = sysInfo.pixelRatio;
this.windowWidth = sysInfo.windowWidth;
// #ifdef H5
this.drawTop = sysInfo.windowTop;
this.windowHeight = sysInfo.windowHeight + sysInfo.windowBottom;
this.cvsStyleHeight = this.windowHeight - tabHeight + 'px';
// #endif
// #ifdef APP-PLUS
if(this.platform === 'android') {
this.windowHeight = sysInfo.screenHeight + sysInfo.statusBarHeight;
this.cvsStyleHeight = this.windowHeight - tabHeight + 'px';
} else {
this.windowHeight = sysInfo.windowHeight + this.moreHeight;
this.cvsStyleHeight = this.windowHeight - tabHeight + 6 + 'px';
}
// #endif
// #ifdef MP
this.windowHeight = sysInfo.windowHeight + this.moreHeight;
this.cvsStyleHeight = this.windowHeight - tabHeight - 2 + 'px';
// #endif
this.pxRatio = this.windowWidth/750;
let style = this.avatarStyle;
if(style && style !== true && (style=style.trim()) ) {
style = style.split(';');
let obj = {};
for( let v of style ) {
if(!v) continue;
v = v.trim().split(':');
if(v[1].indexOf('rpx') >= 0) {
let arr = v[1].trim().split(' ');
for( let k in arr ) {
if(!arr[k]) continue;
if(arr[k].indexOf('rpx') >= 0) {
arr[k] = parseFloat(arr[k]) * this.pxRatio + 'px';
}
}
v[1] = arr.join(' ');
}
obj[v[0].trim()] = v[1].trim();
}
this.imgStyle = obj;
}
this.expWidth && (this.expWidth = this.expWidth.indexOf('rpx') >= 0 ? parseInt(this.expWidth)*this.pxRatio : parseInt(this.expWidth));
this.expHeight && (this.expHeight = this.expHeight.indexOf('rpx') >= 0 ? parseInt(this.expHeight)*this.pxRatio : parseInt(this.expHeight));
if(this.styleDisplay === 'flex') {
this.drawInit(true);
}
this.hideImg();
},
select() {
if(this.fSelecting) return;
this.fSelecting = true;
setTimeout(()=>{ this.fSelecting = false; }, 500);
uni.chooseImage({
count: 1,
sizeType: ['original', 'compressed'],
sourceType: ['album', 'camera'],
success: (r)=>{
uni.showLoading({ mask: true });
let path = this.imgPath = r.tempFilePaths[0];
uni.getImageInfo({
src: path,
success: r => {
this.imgWidth = r.width;
this.imgHeight = r.height;
this.path = path;
if( !this.hasSel ) {
let style = this.selStyle || {};
if( this.arWidth && this.arHeight ) {
let areaWidth = this.arWidth.indexOf('rpx') >= 0 ? parseInt(this.arWidth) * this.pxRatio: parseInt(this.arWidth),
areaHeight = this.arHeight.indexOf('rpx') >= 0 ? parseInt(this.arHeight) * this.pxRatio: parseInt(this.arHeight);
style.width = areaWidth + 'px';
style.height = areaHeight + 'px';
style.top = (this.windowHeight - areaHeight - tabHeight)/2 + 'px';
style.left = (this.windowWidth - areaWidth)/2 + 'px';
} else {
uni.showModal({
title: t("up.cropper.emptyWidhtOrHeight"),
showCancel: false
})
return;
}
this.selStyle = style;
}
if( this.noBar ) {
this.drawInit(true);
} else {
uni.hideTabBar({
complete: () => {
this.drawInit(true);
}
});
}
},
fail: ()=>{
uni.showToast({
title: "error3",
duration: 2000,
})
},
complete() {
uni.hideLoading();
}
});
}
})
},
confirm() {
if(this.fUploading) return;
this.fUploading = true;
setTimeout(()=>{ this.fUploading = false; }, 1000)
let style = this.selStyle,
x = parseInt(style.left),
y = parseInt(style.top),
width = parseInt(style.width),
height = parseInt(style.height),
expWidth = this.expWidth || width,
expHeight = this.expHeight || height;
// #ifdef H5
// x *= this.pixelRatio;
// y *= this.pixelRatio;
expWidth = width;
expHeight = height;
// #endif
uni.showLoading({ mask: true });
this.styleDisplay = 'none';
this.styleTop = '-10000px';
this.hasSel = false;
this.hideImg();
uni.canvasToTempFilePath({
x: x,
y: y,
width: width,
height: height,
destWidth: expWidth,
destHeight: expHeight,
canvasId: 'avatar-canvas-' + this.instanceId,
fileType: 'png',
quality: this.qlty,
success: (r)=>{
r = r.tempFilePath;
// #ifdef H5
this.btop(r).then((r)=> {
if(this.expWidth && this.expHeight) {
let ctxCanvas = this.ctxCanvas;
expWidth = this.expWidth,
expHeight = this.expHeight;
ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
ctxCanvas.draw(false,()=>{
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: expWidth,
height: expHeight,
destWidth: expWidth,
destHeight: expHeight,
canvasId: 'avatar-canvas-' + this.instanceId,
fileType: 'png',
quality: this.qlty,
success: (r)=>{
r = r.tempFilePath;
this.btop(r).then((r)=> {
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
});
},
fail: ()=>{
uni.showToast({
title: "error0",
duration: 2000,
})
}
});
});
} else {
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
}
})
// #endif
// #ifndef H5
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
// #endif
},
fail: (res)=>{
uni.showToast({
title: "error1",
duration: 2000,
})
},
complete: () => {
uni.hideLoading();
this.noBar || uni.showTabBar();
}
}, this);
},
// 用户点击"预览"模式下的"确认"按钮时被调用,用于将预览的裁剪结果上传
prvUpload() {
if(this.fPrvUploading) return;
this.fPrvUploading = true;
setTimeout(()=>{ this.fPrvUploading = false; }, 1000)
let style = this.selStyle,
destWidth = parseInt(style.width),
destHeight = parseInt(style.height),
prvX = this.prvX,
prvY = this.prvY,
prvWidth = this.prvWidth,
prvHeight = this.prvHeight,
expWidth = this.expWidth || prvWidth,
expHeight = this.expHeight || prvHeight;
// #ifdef H5
// prvX *= this.pixelRatio;
// prvY *= this.pixelRatio;
expWidth = prvWidth;
expHeight = prvHeight;
// #endif
uni.showLoading({ mask: true });
this.styleDisplay = 'none';
this.styleTop = '-10000px';
this.hasSel = false;
this.hideImg();
uni.canvasToTempFilePath({
x: prvX,
y: prvY,
width: prvWidth,
height: prvHeight,
destWidth: expWidth,
destHeight: expHeight,
canvasId: 'prv-canvas-' + this.instanceId,
fileType: 'png',
quality: this.qlty,
success: (r)=>{
r = r.tempFilePath;
// #ifdef H5
this.btop(r).then((r)=> {
if(this.expWidth && this.expHeight) {
let ctxCanvas = this.ctxCanvas;
expWidth = this.expWidth,
expHeight = this.expHeight;
ctxCanvas.drawImage(r, 0, 0, expWidth, expHeight);
ctxCanvas.draw(false, ()=>{
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: expWidth,
height: expHeight,
destWidth: expWidth,
destHeight: expHeight,
canvasId: 'avatar-canvas-' + this.instanceId,
fileType: 'png',
quality: this.qlty,
success: (r)=>{
r = r.tempFilePath;
this.btop(r).then((r)=> {
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
});
},
fail: ()=>{
uni.showToast({
title: "error0",
duration: 2000,
})
}
});
});
} else {
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
}
})
// #endif
// #ifndef H5
this.$emit("confirm", {avatar: this.imgSrc, path: r, index: this.indx, data: this.rtn});
// #endif
},
fail: ()=>{
uni.showToast({
title: "error_prv",
duration: 2000,
})
},
complete: () => {
uni.hideLoading();
this.noBar || uni.showTabBar();
}
}, this);
},
drawInit(ini=false) {
let allWidth = this.windowWidth,
allHeight = this.windowHeight,
imgWidth = this.imgWidth,
imgHeight = this.imgHeight,
imgRadio = imgWidth/imgHeight,
useWidth = allWidth - 40,
useHeight = allHeight - tabHeight - 80,
pixelRatio = this.pixelRatio,
selWidth = parseInt(this.selStyle.width),
selHeight = parseInt(this.selStyle.height);
this.fixWidth = 0;
this.fixHeight = 0;
this.lckWidth = 0;
this.lckHeight = 0;
switch(this.stc) {
case 'x': this.fixWidth = 1; break;
case 'y': this.fixHeight = 1; break;
case 'long': if(imgRadio > 1) this.fixWidth = 1; else this.fixHeight = 1; break;
case 'short': if(imgRadio > 1) this.fixHeight = 1; else this.fixWidth = 1; break;
case 'longSel': if(selWidth > selHeight) this.fixWidth = 1; else this.fixHeight = 1; break;
case 'shortSel': if(selWidth > selHeight) this.fixHeight = 1; else this.fixWidth = 1; break;
}
// lck 用于控制裁剪框的宽度和高度锁定行为
// 'x': 锁定宽度,不允许水平方向调整
// 'y': 锁定高度,不允许垂直方向调整
// 'long': 根据图片长边锁定,如果图片横向较长则锁定宽度,否则锁定高度
// 'short': 根据图片短边锁定,如果图片横向较长则锁定高度,否则锁定宽度
// 'longSel': 根据选择框的长边锁定,如果选择框宽度大于高度则锁定宽度,否则锁定高度
// 'shortSel': 根据选择框的短边锁定,如果选择框宽度大于高度则锁定高度,否则锁定宽度
switch(this.lck) {
case 'x': this.lckWidth = 1; break;
case 'y': this.lckHeight = 1; break;
case 'long': if(imgRadio > 1) this.lckWidth = 1; else this.lckHeight = 1; break;
case 'short': if(imgRadio > 1) this.lckHeight = 1; else this.lckWidth = 1; break;
case 'longSel': if(selWidth > selHeight) this.lckWidth = 1; else this.lckHeight = 1; break;
case 'shortSel': if(selWidth > selHeight) this.lckHeight = 1; else this.lckWidth = 1; break;
}
if( this.fixWidth ) {
useWidth = selWidth;
useHeight = useWidth/imgRadio;
} else if( this.fixHeight ) {
useHeight = selHeight;
useWidth = useHeight*imgRadio;
} else if( imgRadio < 1 ) {
if( imgHeight < useHeight ) {
useWidth = imgWidth;
useHeight = imgHeight;
} else {
useHeight = useHeight;
useWidth = useHeight*imgRadio;
}
} else {
if( imgWidth < useWidth ) {
useWidth = imgWidth;
useHeight = imgHeight;
} else {
useWidth = useWidth;
useHeight = useWidth/imgRadio;
}
}
if( this.isin ) {
this.scaleWidth = 0;
this.scaleHeight = 0;
if(useWidth < selWidth) {
useWidth = selWidth;
useHeight = useWidth/imgRadio;
this.lckHeight = 0;
}
if(useHeight < selHeight) {
useHeight = selHeight;
useWidth = useHeight*imgRadio;
this.lckWidth = 0;
}
}
this.scaleSize = 1;
this.rotateDeg = 0;
this.posWidth = (allWidth-useWidth)/2;
this.posHeight = (allHeight-useHeight-tabHeight)/2;
this.useWidth = useWidth;
this.useHeight = useHeight;
let style = this.selStyle,
left = parseInt(style.left),
top = parseInt(style.top),
width = parseInt(style.width),
height = parseInt(style.height),
canvas = this.canvas,
canvasOper = this.canvasOper,
ctxCanvas = this.ctxCanvas,
ctxCanvasOper = this.ctxCanvasOper;
ctxCanvasOper.setLineWidth(3);
ctxCanvasOper.setStrokeStyle('grey');
ctxCanvasOper.setGlobalAlpha(0.4);
ctxCanvasOper.setFillStyle('black');
ctxCanvasOper.strokeRect( left, top, width, height );
ctxCanvasOper.fillRect(0, 0, this.windowWidth, top);
ctxCanvasOper.fillRect(0, top, left, height);
ctxCanvasOper.fillRect(0, top+height, this.windowWidth, this.windowHeight-height-top-tabHeight);
ctxCanvasOper.fillRect(left+width, top,this.windowWidth-width-left, height);
ctxCanvasOper.setStrokeStyle('red');
ctxCanvasOper.moveTo(left+20, top);ctxCanvasOper.lineTo(left, top);ctxCanvasOper.lineTo(left, top+20);
ctxCanvasOper.moveTo(left+width-20, top);ctxCanvasOper.lineTo(left+width, top);ctxCanvasOper.lineTo(left+width, top+20);
ctxCanvasOper.moveTo(left+20, top+height);ctxCanvasOper.lineTo(left, top+height);ctxCanvasOper.lineTo(left, top+height-20);
ctxCanvasOper.moveTo(left+width-20, top+height);ctxCanvasOper.lineTo(left+width, top+height);ctxCanvasOper.lineTo(left+width, top+height-20);
// 绘制控制点(四个角)
const controlPointSize = 10;
ctxCanvasOper.setFillStyle('white');
ctxCanvasOper.setStrokeStyle('grey');
ctxCanvasOper.setLineWidth(1);
// 左上角
ctxCanvasOper.fillRect(left - controlPointSize/2, top - controlPointSize/2, controlPointSize, controlPointSize);
ctxCanvasOper.strokeRect(left - controlPointSize/2, top - controlPointSize/2, controlPointSize, controlPointSize);
// 右上角
ctxCanvasOper.fillRect(left + width - controlPointSize/2, top - controlPointSize/2, controlPointSize, controlPointSize);
ctxCanvasOper.strokeRect(left + width - controlPointSize/2, top - controlPointSize/2, controlPointSize, controlPointSize);
// 左下角
ctxCanvasOper.fillRect(left - controlPointSize/2, top + height - controlPointSize/2, controlPointSize, controlPointSize);
ctxCanvasOper.strokeRect(left - controlPointSize/2, top + height - controlPointSize/2, controlPointSize, controlPointSize);
// 右下角
ctxCanvasOper.fillRect(left + width - controlPointSize/2, top + height - controlPointSize/2, controlPointSize, controlPointSize);
ctxCanvasOper.strokeRect(left + width - controlPointSize/2, top + height - controlPointSize/2, controlPointSize, controlPointSize);
ctxCanvasOper.stroke();
ctxCanvasOper.draw(false, ()=>{
if( ini ) {
this.styleDisplay = 'flex';
// #ifdef H5
this.styleTop = this.drawTop + 'px';
// #endif
// #ifndef H5
this.styleTop = '0';
// #endif
ctxCanvas.setFillStyle('black');
this.drawImage();
}
});
this.$emit("avtinit");
},
drawImage() {
let tm_now = Date.now();
if(tm_now - this.drawTm < 20) return;
this.drawTm = tm_now;
let ctxCanvas = this.ctxCanvas;
ctxCanvas.fillRect(0, 0, this.windowWidth, this.windowHeight-tabHeight);
ctxCanvas.translate(this.posWidth+this.useWidth/2, this.posHeight+this.useHeight/2);
ctxCanvas.scale(this.scaleSize, this.scaleSize);
ctxCanvas.rotate(this.rotateDeg * Math.PI/180);
ctxCanvas.drawImage(this.imgPath, -this.useWidth/2, -this.useHeight/2, this.useWidth, this.useHeight);
ctxCanvas.draw(false);
},
hideImg() {
this.prvImg = '';
this.prvTop = '-10000px';
this.showOper = true;
this.prvImgData = null;
this.target = null;
},
close() {
this.styleDisplay = 'none';
this.styleTop = '-10000px';
this.hasSel = false;
this.hideImg();
this.noBar || uni.showTabBar();
},
preview() {
if(this.fPreviewing) return;
this.fPreviewing = true;
setTimeout(()=>{ this.fPreviewing = false; }, 1000);
let style = this.selStyle,
x = parseInt(style.left),
y = parseInt(style.top),
width = parseInt(style.width),
height = parseInt(style.height);
uni.showLoading({ mask: true });
// console.log('size', x, y, width, height)
uni.canvasToTempFilePath({
x: x,
y: y,
width: width,
height: height,
canvasId: 'avatar-canvas-' + this.instanceId,
fileType: 'png',
quality: this.qlty,
success: (r)=>{
// console.log(r)
this.prvImgTmp = r = r.tempFilePath;
let ctxCanvasPrv = this.ctxCanvasPrv,
prvX = this.windowWidth,
prvY = parseInt(this.cvsStyleHeight),
prvWidth = parseInt(this.selStyle.width),
prvHeight = parseInt(this.selStyle.height),
useWidth = prvX - 40,
useHeight = prvY - 80,
radio = useWidth/prvWidth,
rHeight = prvHeight*radio;
if(rHeight < useHeight) {
prvWidth = useWidth;
prvHeight = rHeight;
} else {
radio = useHeight/prvHeight;
prvWidth *= radio;
prvHeight = useHeight;
}
ctxCanvasPrv.setFillStyle('black');
ctxCanvasPrv.fillRect(0, 0, prvX, prvY);
ctxCanvasPrv.fillRect(x, y, width, height);
this.prvX = prvX = (prvX-prvWidth)/2;
this.prvY = prvY = (prvY-prvHeight)/2;
this.prvWidth = prvWidth;
this.prvHeight = prvHeight;
ctxCanvasPrv.drawImage(r, prvX, prvY, prvWidth, prvHeight);
ctxCanvasPrv.draw(false, () => {
// #ifdef H5
this.btop(this.prvImgTmp).then((r)=> {
this.showOper = false;
this.prvTop = this.drawTop + 'px';
})
// #endif
// #ifndef H5
if( this.platform != 'android' ) {
this.showOper = false;
}
this.prvTop = '0';
// #endif
});
},
fail: ()=>{
uni.showToast({
title: "error2",
duration: 2000,
})
},
complete: () => {
uni.hideLoading();
}
}, this);
},
chooseImage(index=undefined, params=undefined, data=undefined) {
if(params) {
console.log(params)
let areaWidth = params.areaWidth || this.areaWidth,
areaHeight = params.areaHeight || this.areaHeight,
expWidth = params.exportWidth || this.exportWidth,
expHeight = params.exportHeight || this.exportHeight,
quality = params.quality,
canRotate = params.canRotate,
canScale = params.canScale,
canChangeSize = params.canChangeSize,
minScale = params.minScale,
maxScale = params.maxScale,
stretch = params.stretch,
inner = params.inner,
lock = params.lock;
console.log('areaWidth', this.areaWidth)
expWidth && (this.expWidth = expWidth.indexOf('rpx') >= 0 ? parseInt(expWidth)*this.pxRatio : parseInt(expWidth));
expHeight && (this.expHeight = expHeight.indexOf('rpx') >= 0 ? parseInt(expHeight)*this.pxRatio : parseInt(expHeight));
this.letRotate = canRotate === false ? 0 : 1;
this.letScale = canScale === false ? 0 : 1;
// 设置是否允许调整裁剪框大小
this.letChangeSize = canChangeSize || false;
this.qlty = parseInt(quality) || 0.9;
this.mnScale = minScale || 0.3;
this.mxScale = maxScale || 4;
this.stc = stretch;
this.isin = inner === true ? 1 : 0;
this.lck = lock;
if(this.isin) {
this.btnWidth = '24%';
this.btnDsp = 'none';
} else {
this.btnWidth = '19%';
this.btnDsp = 'flex';
}
if( areaWidth && areaHeight) {
areaWidth = areaWidth.indexOf('rpx') >= 0 ? parseInt(areaWidth) * this.pxRatio: parseInt(areaWidth);
areaHeight = areaHeight.indexOf('rpx') >= 0 ? parseInt(areaHeight) * this.pxRatio: parseInt(areaHeight);
this.selStyle.width = areaWidth + 'px';
this.selStyle.height = areaHeight + 'px';
this.selStyle.top = (this.windowHeight - areaHeight - tabHeight)/2 + 'px';
this.selStyle.left = (this.windowWidth - areaWidth)/2 + 'px';
// console.log(this.selStyle);
this.hasSel = true;
}
}
this.rtn = data;
this.indx = index;
this.select();
},
rotate() {
// #ifdef APP-PLUS
if(this.platform === 'android') {
if(this.fRotateing) return;
this.fRotateing = true;
setTimeout(()=>{ this.fRotateing = false; }, 500);
}
// #endif
// if(this.letRotate) {
this.rotateDeg += 90 - this.rotateDeg%90;
this.drawImage();
// }
},
start(e) {
let touches = e.touches,
touch0 = touches[0],
touch1 = touches[1];
this.touch0 = touch0;
this.touch1 = touch1;
if( touch1 ) {
let x = touch1.x - touch0.x,
y = touch1.y - touch0.y;
this.fgDistance = Math.sqrt(x * x + y * y);
} else {
// 只有在允许调整大小时才检查控制点
if (this.letChangeSize) {
// 检查是否点击在控制点上
const controlPointSize = 20;
const x = touch0.x;
const y = touch0.y;
const style = this.selStyle;
const left = parseInt(style.left);
const top = parseInt(style.top);
const width = parseInt(style.width);
const height = parseInt(style.height);
// 检查四个控制点
if (Math.abs(x - left) < controlPointSize && Math.abs(y - top) < controlPointSize) {
this.resizeHandle = 'top-left';
} else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - top) < controlPointSize) {
this.resizeHandle = 'top-right';
} else if (Math.abs(x - left) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
this.resizeHandle = 'bottom-left';
} else if (Math.abs(x - (left + width)) < controlPointSize && Math.abs(y - (top + height)) < controlPointSize) {
this.resizeHandle = 'bottom-right';
} else {
this.resizeHandle = null;
}
} else {
this.resizeHandle = null;
}
}
},
move(e) {
let touches = e.touches,
touch0 = touches[0],
touch1 = touches[1];
if( touch1 ) {
let x = touch1.x - touch0.x,
y = touch1.y - touch0.y,
fgDistance = Math.sqrt(x * x + y * y),
scaleSize = 0.005 * (fgDistance - this.fgDistance),
beScaleSize = this.scaleSize + scaleSize;
do {
if( !this.letScale ) break;
if( beScaleSize < this.mnScale) break;
if( beScaleSize > this.mxScale) break;
if(this.isin) {
let imgWidth = this.useWidth*beScaleSize,
imgHeight = this.useHeight*beScaleSize,
rx0 = this.posWidth+this.useWidth/2,
ry0 = this.posHeight+this.useHeight/2,
l = rx0-imgWidth/2, t = ry0-imgHeight/2,
r = l+imgWidth, b = t+imgHeight,
left = parseInt(this.selStyle.left),
top = parseInt(this.selStyle.top),
width = parseInt(this.selStyle.width),
height = parseInt(this.selStyle.height);
if(left < l || left+width > r || top < t || top+height > b) break;
this.scaleWidth = (this.useWidth-imgWidth)/2;
this.scaleHeight = (this.useHeight-imgHeight)/2;
}
this.scaleSize = beScaleSize;
} while(0);
this.fgDistance = fgDistance;
if(touch1.x !== touch0.x && this.letRotate) {
x = (this.touch1.y - this.touch0.y)/(this.touch1.x - this.touch0.x);
y = (touch1.y - touch0.y)/(touch1.x - touch0.x);
this.rotateDeg += Math.atan((y-x)/(1+x*y)) * 180/Math.PI;
this.touch0 = touch0;
this.touch1 = touch1;
}
this.drawImage();
} else if( this.touch0 ) {
// 只有在允许调整大小时才处理裁剪框大小调整
if (this.resizeHandle && this.letChangeSize) {
const style = {...this.selStyle};
const left = parseInt(style.left);
const top = parseInt(style.top);
const width = parseInt(style.width);
const height = parseInt(style.height);
const minWidth = 50;
const minHeight = 50;
switch (this.resizeHandle) {
case 'top-left':
style.left = touch0.x + 'px';
style.top = touch0.y + 'px';
style.width = (left + width - touch0.x) + 'px';
style.height = (top + height - touch0.y) + 'px';
break;
case 'top-right':
style.top = touch0.y + 'px';
style.width = (touch0.x - left) + 'px';
style.height = (top + height - touch0.y) + 'px';
break;
case 'bottom-left':
style.left = touch0.x + 'px';
style.width = (left + width - touch0.x) + 'px';
style.height = (touch0.y - top) + 'px';
break;
case 'bottom-right':
style.width = (touch0.x - left) + 'px';
style.height = (touch0.y - top) + 'px';
break;
}
// 确保最小尺寸
if (parseInt(style.width) >= minWidth && parseInt(style.height) >= minHeight) {
// 确保裁剪框不超出屏幕边界
if (parseInt(style.left) >= 0 &&
parseInt(style.top) >= 0 &&
(parseInt(style.left) + parseInt(style.width)) <= this.windowWidth &&
(parseInt(style.top) + parseInt(style.height)) <= (this.windowHeight - tabHeight)) {
this.selStyle = style;
// 重新绘制操作层
this.drawInit();
}
}
} else {
// 原有的移动图片逻辑
let x = touch0.x - this.touch0.x,
y = touch0.y - this.touch0.y,
beX = this.posWidth + x,
beY = this.posHeight + y;
if(this.isin) {
let imgWidth = this.useWidth*this.scaleSize,
imgHeight = this.useHeight*this.scaleSize,
rx0 = beX+this.useWidth/2,
ry0 = beY+this.useHeight/2,
l = rx0-imgWidth/2, t = ry0-imgHeight/2,
r = l+imgWidth, b = t+imgHeight,
left = parseInt(this.selStyle.left),
top = parseInt(this.selStyle.top),
width = parseInt(this.selStyle.width),
height = parseInt(this.selStyle.height);
if(!this.lckWidth && Math.abs(x) < 100) {
if(left >= l && left+width <= r) {
this.posWidth = beX;
} else if(left < l){
this.posWidth = left - this.scaleWidth;
} else if(left+width > r) {
this.posWidth = left-(imgWidth-width) - this.scaleWidth;
}
}
if(!this.lckHeight && Math.abs(y) < 100) {
if(top >= t && top+height <= b) {
this.posHeight = beY;
} else if(top < t) {
this.posHeight = top - this.scaleHeight;
} else if(top+height > b) {
this.posHeight = top-(imgHeight-height) - this.scaleHeight;
}
}
} else {
if( Math.abs(x) < 100 && !this.lckWidth) this.posWidth = beX;
if( Math.abs(y) < 100 && !this.lckHeight) this.posHeight = beY;
}
this.touch0 = touch0;
this.drawImage();
}
}
},
end(e) {
let touches = e.touches,
touch0 = touches && touches[0],
touch1 = touches && touches[1];
if(touch0) {
this.touch0 = touch0;
} else {
this.touch0 = null;
this.touch1 = null;
this.resizeHandle = null; // 重置调整手柄
}
},
getImgData() {
return new Promise((resolve, reject)=>{
let prvX = this.prvX,
prvY = this.prvY,
prvWidth = this.prvWidth,
prvHeight = this.prvHeight;
// #ifdef APP-PLUS||H5
prvX *= this.pixelRatio;
prvY *= this.pixelRatio;
prvWidth *= this.pixelRatio;
prvHeight *= this.pixelRatio;
// #endif
uni.canvasGetImageData({
canvasId: 'prv-canvas-' + this.instanceId,
x: prvX,
y: prvY,
width: prvWidth,
height: prvHeight,
success(res) {
resolve(res.data);
},
fail(err) {
reject(err);
}
}, this);
});
},
async colorChange(e) {
let tm_now = Date.now();
if(tm_now - this.prvTm < 100) return;
this.prvTm = tm_now;
uni.showLoading({ mask: true });
if( !this.prvImgData ) {
if( !(this.prvImgData = await this.getImgData().catch((res)=>{
uni.showToast({
title: "error_read",
duration: 2000,
})
}))) return;
this.target = new Uint8ClampedArray(this.prvImgData.length);
}
let data = this.prvImgData,
target = this.target,
i = e.detail.value,
r,g,b,a,h,s,l,d,p,q,t,min,max,hK,tR,tG,tB;
if( i === 0 ) {
target = data;
} else {
i = (i+100)/200;
if( i < 0.005 ) i = 0;
if( i > 0.995 ) i = 1;
for( let n = data.length-1; n >= 0; n-=4 ) {
r = data[n-3] / 255;
g = data[n-2] / 255;
b = data[n-1] / 255;
max = Math.max(r,g,b);
min = Math.min(r,g,b);
d = max-min;
if ( max === min ){
h = 0 ;
}else if( max === r && g>=b ){
h = 60*( (g-b)/d ) ;
}else if( max === r && g<b ){
h = 60*( (g-b)/d ) + 360 ;
}else if( max === g ){
h = 60*( (b-r)/d ) + 120 ;
}else if( max === b ){
h = 60*( (r-g)/d ) + 240 ;
}
l = (max+min)/2 ;
if ( l===0 || max===min ){
s = 0 ;
}else if( 0<l && l<=0.5 ){
s = d/(2*l) ;
}else if( l>0.5 ){
s = d/(2-2*l) ;
}
data[n] && (a = data[n]);
if ( i < 0.5 ){
s = s*i/0.5 ;
}else if ( i > 0.5 ){
s = 2*s + 2*i - (s*i/0.5) - 1 ;
}
if ( s === 0 ){
r = g = b = Math.round( l*255 ) ;
}else{
if ( l<0.5 ){
q = l * ( 1 + s ) ;
}else if( l>=0.5 ){
q = l + s - ( l * s ) ;
}
p = 2*l-q ;
hK = h/360 ;
tR = hK + 1/3 ;
tG = hK ;
tB = hK - 1/3 ;
let correctRGB = (t)=>{
if( t<0 ){
return t + 1.0 ;
}
if( t>1 ){
return t - 1.0 ;
}
return t ;
} ;
let createRGB = (t)=>{
if ( t<(1/6) ){
return p+((q-p)*6*t) ;
}else if( t>=(1/6) && t<(1/2) ){
return q ;
}else if( t>=(1/2) && t<(2/3) ){
return p+((q-p)*6*((2/3)-t)) ;
}
return p ;
} ;
r = tR = Math.round( createRGB( correctRGB( tR ) )*255 ) ;
g = tG = Math.round( createRGB( correctRGB( tG ) )*255 ) ;
b = tB = Math.round( createRGB( correctRGB( tB ) )*255 ) ;
}
a && ( target[n] = a ) ;
target[n-3] = r;
target[n-2] = g;
target[n-1] = b;
}
}
let prvX = this.prvX,
prvY = this.prvY,
prvWidth = this.prvWidth,
prvHeight = this.prvHeight;
this.ctxCanvasPrv.setFillStyle('black');
this.ctxCanvasPrv.fillRect(prvX, prvY, prvWidth, prvHeight);
this.ctxCanvasPrv.draw(true);
// #ifdef APP-PLUS||H5
prvX *= this.pixelRatio;
prvY *= this.pixelRatio;
prvWidth *= this.pixelRatio;
prvHeight *= this.pixelRatio;
// #endif
uni.canvasPutImageData({
canvasId: 'prv-canvas-' + this.instanceId,
x: prvX,
y: prvY,
width: prvWidth,
height: prvHeight,
data: target,
fail() {
uni.showToast({
title: 'error_put',
duration: 2000
})
},
complete() {
uni.hideLoading();
}
}, this);
},
btop(base64) {
return new Promise(function(resolve, reject) {
var arr = base64.split(','),
mime = arr[0].match(/:(.*?);/)[1],
bstr = atob(arr[1]),
n = bstr.length,
u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([u8arr], { type: mime })));
});
},
}
}
</script>
<style lang="scss" scoped>
.u-cropper {
.my-canvas{
display: flex;
position: fixed !important;
background: #000000;
left: 0;
z-index: 100000;
width: 100%;
}
.my-avatar {
width: 150rpx;
height: 150rpx;
border-radius: 100%;
}
.oper-canvas {
display: flex;
position: fixed !important;
left: 0;
z-index: 100001;
width: 100%;
}
.prv-canvas {
display: flex;
position: fixed !important;
background: #000000;
left: 0;
z-index: 200000;
width: 100%;
}
.oper-wrapper {
height: 50px;
position: fixed !important;
box-sizing: border-box;
border: 1px solid #F1F1F1;
background: #ffffff;
width: 100%;
left: 0;
bottom: 0;
z-index: 100009;
flex-direction: row;
}
.oper {
display: flex;
flex-direction: column;
justify-content: center;
padding: 10rpx 20rpx;
width: 100%;
height: 100%;
box-sizing: border-box;
align-self: center;
}
.btn-wrapper {
display: flex;
flex-direction: row;
/* #ifndef H5 */
flex-grow: 1;
/* #endif */
/* #ifdef H5 */
height: 50px;
/* #endif */
justify-content: space-between;
}
.btn-wrapper view {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #333;
border: 1px solid #f1f1f1;
border-radius: 6%;
}
.hover {
background: #f1f1f1;
border-radius: 6%;
}
.clr-wrapper {
display: flex;
flex-direction: row;
flex-grow: 1;
}
.clr-wrapper view {
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #333;
border: 1px solid #f1f1f1;
border-radius: 6%;
}
.my-slider {
flex-grow: 1;
}
}
</style>