529 lines
15 KiB
Vue
529 lines
15 KiB
Vue
<template>
|
|
<div ref="liquefactionModal"></div>
|
|
<a-modal
|
|
class="liquefaction generalModel"
|
|
v-model:visible="liqufeaction"
|
|
:footer="null"
|
|
:get-container="() => $refs.liquefactionModal"
|
|
width="80%"
|
|
height="90%"
|
|
:maskClosable="false"
|
|
:centered="true"
|
|
:closable="false"
|
|
wrapClassName="#app"
|
|
:keyboard="false"
|
|
>
|
|
<div class="generalModel_btn">
|
|
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
|
|
<svg width="100%" height="100%" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
<circle cx="23" cy="23" r="23" fill="white" fill-opacity="0.3"/>
|
|
<rect x="32.5063" y="12" width="3" height="29" rx="1.5" transform="rotate(45 32.5063 12)" fill="#000"/>
|
|
<rect x="34.6274" y="32.5059" width="3" height="29" rx="1.5" transform="rotate(135 34.6274 32.5059)" fill="#000"/>
|
|
</svg>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<div class="liquefaction_center">
|
|
<div class="liquefaction_canvas_box">
|
|
<canvas id="c"></canvas>
|
|
<div v-show="arrows.show" class="moveDom" :style="arrows.domStyle"></div>
|
|
<div v-show="arrows.show" class="jiantouDom" :style="arrows.jiantouStyle"> </div>
|
|
</div>
|
|
<div class="liquefaction_parameter">
|
|
<div class="liquefaction_parameter_item icon">
|
|
<i class="icon iconfont icon-chehui" @click="historyState('reverse')"></i>
|
|
<i class="icon iconfont icon-fanchehui" @click="historyState('')"></i>
|
|
</div>
|
|
<div class="liquefaction_parameter_item">
|
|
<div class="liquefaction_parameter_item_title">
|
|
<span>{{ $t('exportModel.Size') }}</span>
|
|
<input type="number" v-model="routes" @change="routesChange('routes')">
|
|
</div>
|
|
<div class="liquefaction_parameter_item_input">
|
|
<input type="range" v-model="routes" @change="routesChange('routes')">
|
|
</div>
|
|
</div>
|
|
<div class="liquefaction_parameter_item">
|
|
<div class="liquefaction_parameter_item_title">
|
|
<span>{{ $t('exportModel.density') }}</span>
|
|
<input type="number" v-model="density" @change="routesChange('density')">
|
|
</div>
|
|
<div class="liquefaction_parameter_item_input">
|
|
<input type="range" v-model="density" @change="routesChange('density')">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="liquefaction_parameter_item">
|
|
<!-- <div class="generage_btn started_btn" @click="cancelDsign">{{ $t('Cropper.Cancel')}}</div> -->
|
|
<div class="generage_btn started_btn" @click="submit">{{ $t('Cropper.Finish')}}</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</a-modal>
|
|
</template>
|
|
<script>
|
|
import { defineComponent, ref, reactive, watch, onMounted, nextTick, toRefs, onBeforeUnmount } from "vue";
|
|
import { useI18n } from "vue-i18n";
|
|
import { getMousePosition } from "@/tool/mdEvent";
|
|
export default defineComponent({
|
|
components: {
|
|
},
|
|
emits: ['submitLiquefaction','clearLiquefaction'],
|
|
setup(props,{emit}) {
|
|
// let presentState = ref('paypal');
|
|
let liqufeaction = ref(false);
|
|
let liqufeactionData = reactive({
|
|
routes: 30,//移动框大小
|
|
density: 30,//移动大小
|
|
})
|
|
let arrows = ref({
|
|
show:false,
|
|
domStyle:{
|
|
left:0,
|
|
top:0,
|
|
width:0,
|
|
height:0,
|
|
},
|
|
jiantouStyle:{
|
|
height:'1px',
|
|
transform:`translateY(-100%) rotate(${90}deg)`,
|
|
}
|
|
})
|
|
let records = []//撤回
|
|
let normalCanvasState = []//反撤回
|
|
let canvas
|
|
let canvasImgData = {
|
|
width:0,
|
|
height:0,
|
|
}
|
|
let downX
|
|
let downY
|
|
let context
|
|
let init = async (data)=>{
|
|
records = []
|
|
liqufeaction.value = true
|
|
document.addEventListener("keydown", kyeDown);
|
|
document.addEventListener("keyup", KeyUp);
|
|
await new Promise((resolve, reject) => {
|
|
nextTick(()=>{
|
|
resolve()
|
|
})
|
|
})
|
|
let img = new Image()
|
|
img.setAttribute('crossOrigin', 'Anonymous')
|
|
img.onload = () => {
|
|
canvasImgData.width = img.width
|
|
canvasImgData.height = img.height
|
|
let canvasBox = document.querySelector('.liquefaction .liquefaction_canvas_box')
|
|
canvas = document.getElementById('c')
|
|
let scale = canvasBox.offsetWidth / img.width
|
|
let width = canvasBox.offsetWidth
|
|
let height = img.height * scale
|
|
if(height > canvasBox.offsetHeight){
|
|
height = canvasBox.offsetHeight
|
|
width = img.width / img.height * height
|
|
}
|
|
canvas.width = width
|
|
canvas.height = height
|
|
let optfor = false
|
|
context = canvas.getContext('2d');
|
|
context.drawImage(img, 1, 1, canvas.width, canvas.height);
|
|
let imgData = context.getImageData(0, 0, canvas.width, canvas.height)
|
|
|
|
let canvasDownX
|
|
let canvasDownY
|
|
let distance = 0
|
|
let angle
|
|
let mousedown = (event)=>{
|
|
let e = getMousePosition(event,false)
|
|
down(e)
|
|
}
|
|
let touchdown = (event)=>{
|
|
let e = getMousePosition(event,true)
|
|
down(e)
|
|
}
|
|
let down = (event)=> {
|
|
let canvasPosition = canvas.getBoundingClientRect()
|
|
let maxX = canvasPosition.width + canvasPosition.x
|
|
let minX = canvasPosition.x
|
|
let maxY = canvasPosition.height + canvasPosition.y
|
|
let minY = canvasPosition.y
|
|
if (event.clientX > minX && event.clientX < maxX && event.clientY > minY && event.clientY < maxY) {
|
|
downX = event.clientX
|
|
downY = event.clientY
|
|
canvasDownX = downX - minX
|
|
canvasDownY = downY - minY
|
|
}
|
|
optfor = true
|
|
arrows.value.jiantouStyle.height = distance + 'px'
|
|
arrows.value.jiantouStyle.left = event.clientX - canvasBox.getBoundingClientRect().left + 'px'
|
|
arrows.value.jiantouStyle.top = event.clientY - canvasBox.getBoundingClientRect().top + 'px'
|
|
arrows.value.domStyle.width = liqufeactionData.routes * 2 + 'px'
|
|
arrows.value.domStyle.height = liqufeactionData.routes * 2 + 'px'
|
|
arrows.value.domStyle.left = event.clientX - canvasBox.getBoundingClientRect().left - liqufeactionData.routes + 'px'
|
|
arrows.value.domStyle.top = event.clientY - canvasBox.getBoundingClientRect().top - liqufeactionData.routes + 'px'
|
|
arrows.value.show = true
|
|
let mouseup = ()=>{
|
|
if(!optfor)return
|
|
if(distance != 0){
|
|
let {upDownX, upDownY} = calculateTargetCoordinates(canvasDownX, canvasDownY,distance,angle)
|
|
liquify(imgData, canvasDownX, canvasDownY, upDownX, upDownY, liqufeactionData.routes, liqufeactionData.density)
|
|
}
|
|
distance = 0
|
|
arrows.value.show = false
|
|
const copiedImgData = new ImageData(new Uint8ClampedArray(imgData.data), imgData.width, imgData.height);
|
|
records.push(copiedImgData)
|
|
context.putImageData(copiedImgData, 0, 0);
|
|
optfor = false
|
|
canvasBox.removeEventListener('mousemove', mouseMove);
|
|
canvasBox.removeEventListener('touchmove', touchmove);
|
|
document.removeEventListener('mouseup', mouseup);
|
|
document.removeEventListener('touchend', mouseup);
|
|
}
|
|
canvasBox.addEventListener('mousemove', mouseMove);
|
|
canvasBox.addEventListener('touchmove', touchmove);
|
|
document.addEventListener('mouseup', mouseup);
|
|
document.addEventListener('touchend', mouseup);
|
|
}
|
|
let mouseMove = (event)=>{
|
|
let e = getMousePosition(event,false)
|
|
move(e)
|
|
}
|
|
let touchmove = (event)=>{
|
|
let e = getMousePosition(event,true)
|
|
move(e)
|
|
}
|
|
let move = function (moveEvent) {
|
|
let height = distanceFun(downX , downY , moveEvent.clientX , moveEvent.clientY)
|
|
if(optfor){
|
|
let height = distanceFun(downX , downY , moveEvent.clientX , moveEvent.clientY)
|
|
angle = angleFun(downX , downY , moveEvent.clientX , moveEvent.clientY)
|
|
|
|
if(height < liqufeactionData.routes){
|
|
distance = height
|
|
arrows.value.jiantouStyle.height = height + 'px'
|
|
}
|
|
arrows.value.jiantouStyle.transform = `translateY(-100%) rotate(${angle+90}deg)`
|
|
|
|
}
|
|
}
|
|
// canvas.addEventListener('mousedown', down);
|
|
canvasBox.addEventListener('mousedown', mousedown);
|
|
canvasBox.addEventListener('touchstart', touchdown);
|
|
}
|
|
if(data._element){
|
|
img.src = data._element.src
|
|
}else{
|
|
img.src = data
|
|
}
|
|
|
|
|
|
}
|
|
|
|
let distanceSqr = (x1, y1, x2, y2) => sqr(x1 - x2) + sqr(y1 - y2);
|
|
let sqr = (x) => x * x;;
|
|
let eachCircleDot = (imageData, ox, oy, r, callback)=>{
|
|
var imgWidth = imageData.width,
|
|
imgHeight = imageData.height,
|
|
data = imageData.data,
|
|
left = ox - r,
|
|
right = ox + r,
|
|
top = oy - r,
|
|
bottom = oy + r,
|
|
dotRedOffset, dotGreenOffset, dotBlueOffset, alphaOffset;
|
|
for (var x = left; x < right; x++){
|
|
for (var y = top; y < bottom; y++){
|
|
if (distanceSqr(x, y, ox, oy) <= sqr(r)) {
|
|
|
|
dotRedOffset = y * imgWidth * 4 + x * 4;
|
|
dotGreenOffset = dotRedOffset + 1;
|
|
dotBlueOffset = dotGreenOffset + 1;
|
|
alphaOffset = dotBlueOffset + 1;
|
|
|
|
callback(
|
|
// 当前点的坐标
|
|
{ x: x, y: y },
|
|
// 点的RGBA四个分量对应字节的下标
|
|
{
|
|
r: dotRedOffset,
|
|
g: dotGreenOffset,
|
|
b: dotBlueOffset,
|
|
a: alphaOffset,
|
|
},
|
|
// 传进来的ImageData的data部分
|
|
data
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
let keyDowns = []
|
|
let kyeDown = (event)=>{
|
|
if(keyDowns.indexOf(event.code)>-1){
|
|
}else{
|
|
keyDowns.push(event.code)
|
|
if(keyDowns.indexOf('ControlLeft') > -1 && keyDowns.indexOf('KeyZ') > -1 && keyDowns.indexOf('ShiftLeft') > -1){
|
|
historyState('')
|
|
}else if(keyDowns.indexOf('ControlLeft') > -1 && keyDowns.indexOf('KeyZ') > -1){
|
|
historyState('reverse')
|
|
}
|
|
}
|
|
}
|
|
let KeyUp = (event) =>{
|
|
keyDowns = keyDowns.filter(function(item) {
|
|
return event.code !== item;
|
|
})
|
|
}
|
|
const historyState = (str)=>{
|
|
console.log(str);
|
|
let obj
|
|
if(str == 'reverse' && records.length > 0){//反撤回
|
|
obj = records.pop()
|
|
normalCanvasState.push(obj);
|
|
}else if(str == '' && normalCanvasState.length > 1){
|
|
obj = normalCanvasState.pop()
|
|
records.push(obj);
|
|
}else{
|
|
return
|
|
}
|
|
console.log(obj,context);
|
|
context.putImageData(obj, 0, 0);
|
|
|
|
}
|
|
onBeforeUnmount(()=>{
|
|
document.removeEventListener("keydown", kyeDown);
|
|
document.removeEventListener("keyup", KeyUp);
|
|
})
|
|
let copyImageDataBuff = (imgData)=>{
|
|
var data = imgData.data,
|
|
imgDataBuff = [];
|
|
for (var i in data){
|
|
imgDataBuff[i] = data[i];
|
|
}
|
|
return imgDataBuff;
|
|
}
|
|
let moveDot = (imgData, dataBuff, x, y, srcX, srcY)=> {
|
|
|
|
var imgWidth = imgData.width,
|
|
imgHeight = imgData.height,
|
|
data = imgData.data;
|
|
|
|
// // 进行边界检查,确保不超出图像范围
|
|
x = Math.max(0, Math.min(Math.round(x), imgWidth - 1));
|
|
y = Math.max(0, Math.min(Math.round(y), imgHeight - 1));
|
|
srcX = Math.max(0, Math.min(Math.round(srcX), imgWidth - 1));
|
|
srcY = Math.max(0, Math.min(Math.round(srcY), imgHeight - 1));
|
|
srcX = Math.round(srcX);
|
|
srcY = Math.round(srcY);
|
|
|
|
var targetStartOffset = y * imgWidth * 4 + x * 4,
|
|
srcStartOffset = srcY * imgWidth * 4 + srcX * 4;
|
|
for (var i = 0; i < 4; i++)
|
|
data[targetStartOffset + i] = dataBuff[srcStartOffset + i];
|
|
}
|
|
let liquify = (imgData, cx, cy, mx, my, r, strenth) => {
|
|
var imgDataBuff = copyImageDataBuff(imgData);
|
|
eachCircleDot(imgData, cx, cy, r, function (posi) {
|
|
var tx = posi.x,
|
|
ty = posi.y;
|
|
var u = transFormula(cx, cy, mx, my, tx, ty, r, strenth);
|
|
moveDot(imgData, imgDataBuff, tx, ty, u.x, u.y);
|
|
function transFormula(cx, cy, mx, my, tx, ty, r, strenth) {
|
|
strenth = strenth || 100;
|
|
var relativity = sqr(r) - distanceSqr(tx, ty, cx, cy);
|
|
var distanceMovedSqr = distanceSqr(mx, my, cx, cy);
|
|
var rate = sqr(relativity / (relativity + distanceMovedSqr * (100 / strenth)));
|
|
var ux = tx - rate * (mx - cx),
|
|
uy = ty - rate * (my - cy);
|
|
return { x: ux, y: uy };
|
|
}
|
|
});
|
|
}
|
|
let distanceFun = (x1, y1, x2, y2) => {
|
|
return Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
|
|
}
|
|
let angleFun = (x1, y1, x2, y2) => {
|
|
return Math.atan2(y2 - y1, x2 - x1) * (180 / Math.PI);
|
|
}
|
|
let calculateTargetCoordinates = (x1, y1, distance, angleDegrees) => {
|
|
let angleRad = angleDegrees * (Math.PI / 180);
|
|
let x2 = x1 + distance * Math.cos(angleRad);
|
|
let y2 = y1 + distance * Math.sin(angleRad);
|
|
return { upDownX: x2, upDownY: y2 };
|
|
}
|
|
let cancelDsign = ()=>{
|
|
liqufeaction.value = false
|
|
emit('clearLiquefaction')
|
|
}
|
|
let routesChange = (str)=>{
|
|
liqufeactionData[str] = Math.round(liqufeactionData[str]/10)*10;
|
|
if (liqufeactionData[str] < 10) {
|
|
liqufeactionData[str] = 10; // 设置为最小值
|
|
} else if (liqufeactionData[str] > 100) {
|
|
liqufeactionData[str] = 100; // 设置为最大值
|
|
}
|
|
}
|
|
let submit = ()=>{
|
|
const newCanvas = document.createElement('canvas');
|
|
newCanvas.width = canvasImgData.width;
|
|
newCanvas.height = canvasImgData.height;
|
|
const newCtx = newCanvas.getContext('2d');
|
|
newCtx.drawImage(
|
|
canvas,
|
|
0, 0,
|
|
canvas.width,
|
|
canvas.height,
|
|
0, 0,
|
|
newCanvas.width,
|
|
newCanvas.height
|
|
);
|
|
emit('submitLiquefaction',newCanvas.toDataURL('image/png'))
|
|
cancelDsign()
|
|
}
|
|
return {
|
|
liqufeaction,
|
|
...toRefs(liqufeactionData),
|
|
arrows,
|
|
init,
|
|
historyState,
|
|
cancelDsign,
|
|
routesChange,
|
|
submit,
|
|
};
|
|
},
|
|
data() {
|
|
return {
|
|
};
|
|
},
|
|
mounted() {},
|
|
methods: {
|
|
|
|
},
|
|
});
|
|
</script>
|
|
<style lang="less" scoped>
|
|
.liquefactionModal{
|
|
:deep(.liquefaction){
|
|
.ant-modal-mask{
|
|
background: rgba(0,0,0,.2);
|
|
}
|
|
}
|
|
}
|
|
.liquefaction {
|
|
background: #f9fafb;
|
|
.generalModel_btn{
|
|
.generalModel_closeIcon{
|
|
transform: translate(-100%, 100%);
|
|
}
|
|
}
|
|
.liquefaction_center{
|
|
height: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
.liquefaction_canvas_box{
|
|
background: #fff;
|
|
height: 90%;
|
|
width: 55%;
|
|
text-align: center;
|
|
// overflow-x: auto;
|
|
position: relative;
|
|
// overflow-y: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
overflow: hidden;
|
|
justify-content: center;
|
|
}
|
|
.liquefaction_parameter{
|
|
height: 100%;
|
|
// flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
.liquefaction_parameter_item{
|
|
display: flex;
|
|
flex-direction: column;
|
|
.liquefaction_parameter_item_title{
|
|
display: flex;
|
|
justify-content: space-between;
|
|
input{
|
|
width: 30%;
|
|
text-align: right;
|
|
}
|
|
}
|
|
.liquefaction_parameter_item_input{
|
|
width: 100%;
|
|
input{
|
|
width: 100%;
|
|
}
|
|
}
|
|
&.icon{
|
|
flex-direction: row;
|
|
i{
|
|
width: 4rem;
|
|
height: 4rem;
|
|
display: flex;
|
|
border-radius: .5rem;
|
|
font-size: 2rem;
|
|
cursor: pointer;
|
|
// border: 2px solid #000;
|
|
margin-right: 1rem;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-weight: 600;
|
|
}
|
|
i::last-nth{
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
.liquefaction_parameter_item:last-child{
|
|
margin-top: 20px;
|
|
text-align: center;
|
|
div{
|
|
margin-bottom: 1rem;
|
|
}
|
|
}
|
|
}
|
|
.moveDom {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
box-sizing: border-box;
|
|
border: 1px solid;
|
|
position: absolute;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.jiantouDom {
|
|
position: absolute;
|
|
width: 2px;
|
|
height: 1px;
|
|
background-color: #000;
|
|
transform-origin: center bottom;
|
|
transform: translateY(-100%);
|
|
pointer-events: none;
|
|
}
|
|
|
|
.jiantouDom::before {
|
|
content: "";
|
|
display: block;
|
|
position: absolute;
|
|
width: 10px;
|
|
height: 10px;
|
|
left: 50%;
|
|
transform: translateX(-50%) rotate(45deg);
|
|
border-top: 2px solid #000;
|
|
border-left: 2px solid #000;
|
|
pointer-events: none;
|
|
}
|
|
|
|
}
|
|
}
|
|
</style> |