이 구성요소 기능은 다음과 같습니다.
이미지 자르기(자르기 상자를 끌어서 자르기 상자의 크기 변경)
그림 모자이크(모자이크 그리기, 모자이크 지우기);
이미지 미리보기, 이미지 복원(원본 이미지로 돌아가기, 처리된 이미지로 돌아가기);
이미지 업로드(서명 받기, 이미지 업로드)
2. 핵심 로직2.1 이미지 자르기
캔버스(왼쪽 위)를 기준으로 자르기 상자(직사각형)의 위치와 자르기 상자의 높이 및 너비를 가져옵니다. 캔버스의 해당 위치에 있는 이미지 객체(ImageData)를 가져옵니다(getImageData). 캔버스 캔버스를 지웁니다. (putImageData)로 얻은 그림 객체(ImageData)를 캔버스의 해당 위치에 그립니다. 미리보기 이미지를 생성합니다.
2.2 그림 모자이크
모자이크 그리기는 마우스 스트로크 경로(브러시 폭)를 중심으로 한 영역을 다른 색상으로 다시 그리는 것입니다. 일반적인 결과는 주변 색상이 비슷하다는 것입니다.
색상 선택 방법:
1) 예를 들어, 마우스가 교차하는 점의 좌표(x, y)가 있는 경우 왼쪽 위 모서리(x, y)의 좌표로 너비 30px, 높이 30px의 직사각형을 정의합니다. 직사각형의 너비와 높이를 5로 나누므로(5개 부분으로 나누어 n 부분으로 사용자 정의 가능) 이제 6px의 작은 그리드 25개가 있습니다. 각 작은 그리드의 너비와 높이는 6px입니다.
2) 그런 다음 작은 그리드를 무작위로 얻고 이 작은 그리드의 이미지 객체(ImageData)를 얻은 다음(getImageData) 이에 대한 특정 픽셀(너비 1px, 높이 1px)의 색상 색상(rgba: ImageD)을 무작위로 얻습니다. 이미지 객체 ata.data[0], ImageData.data[1], ImageData.data[2], ImageData.data[3]); 마지막으로 첫 번째 6x6px 작은 그리드의 각 픽셀 색상을 color 로 설정합니다.
3) 나머지 24개의 작은 그리드의 색상은 2단계를 따르세요.
2.3 명확한 모자이크
모자이크를 그리는 것이든 모자이크를 지우는 것이든 문제를 이해해야 하는데, 본질은 그림을 그리는 것입니다. 특정 위치에 모자이크를 그렸습니다. 이를 지우면 현재 위치에 원본 이미지 객체를 다시 그립니다. 청소 효과가 나타납니다. 따라서 원본 이미지와 정확히 동일한 캔버스를 백업해야 하며, 클리어 시에는 백업 캔버스의 해당 위치에 이미지를 얻어서 모자이크 위치에 그려야 합니다.
2.4 사진 미리보기
이미지 미리보기는 자르기 프레임의 영역을 구하고 해당 영역의 이미지 개체를 구하는 것입니다. 그런 다음 캔버스에 그립니다.
2.5 사진을 원본 사진으로 복원
캔버스를 지우고 원본 이미지를 다시 그립니다.
2.6 조작된 그림으로 복원
미리보기는 캔버스 이미지 객체(ImageData)를 저장하고, 캔버스를 지우고, 저장된 이미지 객체를 캔버스에 그리는 것입니다.
2.7 이미지 업로드
(toDataURL) 캔버스 이미지 경로를 가져오고 얻은 base64 이미지를 File 객체로 변환합니다. 업로드합니다.
3. 전체 코드는 다음과 같습니다.
<template> <div class=canvas-clip :loading=loading> <div v-show=isDrop class=canvas-mainBox ref=canvas-mainBox id=canvas-mainBox @mousedown.stop=startMove($event) > <div class=canvas-minBox 왼쪽 위로 @mousedown.stop=startResize($event,0)></div> <div class=canvas-minBox 위로 @mousedown.stop=startResize($event,1)></div> <div class=canvas-minBox 오른쪽 위로 @mousedown.stop=startResize($event,2)></div> <div class=canvas- minBox 오른쪽 @mousedown.stop=startResize($event,3)></div> <div class=canvas-minBox 오른쪽 아래 @mousedown.stop=startResize($event,4)></div> <div class=canvas-minBox 아래로 @mousedown.stop=startResize($event,5)></div> <div class=canvas-minBox 왼쪽 아래 @mousedown.stop=startResize($event,6)></ div div> <div class=canvas-minBox left @mousedown.stop=startResize($event,7)></div> </div> <!-- Canvas--> <canvas class=canvas-area ref=canvas id=canvas :width=canvasWidth :height=canvasHeight @mousedown.stop=startMove($event) :class={hoverPaint:isMa,hoverClear:isMaClear} ></canvas> <!-- 백업 캔버스--> <캔버스 클래스=캔버스-복사 ref=캔버스복사 :너비=캔버스폭 :height=canvasHeight></canvas> <div class=canvas-btns> <button v-if=backBtn @click=clipBack>뒤로</button> <button :class={active:btnIndex==0} @click= sourceImg>원본 이미지</button> <button :class={active:btnIndex==1} @click=paintRectReady :disabled=isDisabled>모자이크</button> <button :class={active:btnIndex==2} @click=paintRectClearReady :disabled=isDisabled>지우개</button> <button :class={active:btnIndex==3 } @click=clipReady :disabled=isDisabled>자르기</button> <button :class={active:btnIndex==4} @click=clipPosition>미리보기</button> <button @click=getSignature>업로드</button> <button class=close @click=canvasClose()>x</button> <!-- <div class=paint-size v-if=isMaClear || isMa> <span>브러시 크기</span> <input :defaultValue=maSize v-model=maSize max=100 min=1 유형=범위> <span class=size-num>{{maSize}}</span> </div> --> </div> </div></template><script>axios에서 axios 가져오기; js-md5에서 md5 가져오기 ;../../axios/config에서 요청 가져오기;기본값 내보내기 { props: [imgUrl], data() { return { resizeFX: , movePrev: , canvasWidth: 800, // 캔버스 너비 canvasHeight: 600, // 캔버스 높이 loading: false, isDrop: false, // 자르기 isMa: false, // 모자이크 maSize: 30, // 모자이크 크기 isMaClear: false, // 모자이크 지우기 backBtn: false, // 반환 버튼 isDisabled: false, // 버튼 비활성화 btnIndex: 0, //현재 버튼 mouseX:'', // 마우스 위치 mouseY:'',clipEle: , // 자르기 상자 요소 canvasDataSession: , // 미리보기 전 캔버스 정보 canvas: , // Canvas ctx: , // 캔버스 컨텍스트 canvasCopy: , // 캔버스 복사 ctxCopy: , // 캔버스 컨텍스트 복사 uploadOption: { // 이미지 업로드 매개변수 경로: , 정책: , 서명: , 사용자 이름: } }; }, Mounted() { this.clipEle = this.$refs[canvas-mainBox]; this.canvas = this.$refs[canvas] this.ctx = this.canvas.getContext(2d); = this.$refs[canvasCopy]; this.ctxCopy = this.canvasCopy.getContext(2d); 메소드: { // 이미지 만들기 draw() { var img = new Image(); img.setAttribute('crossOrigin', 'anonymous') img.onload = () => { this.ctx.drawImage(img, 0, 0, 800 , 600); this.ctxCopy.drawImage(img, 0, 0, 800, 600); img.src = this.imgUrl + '?time=' + new Date().valueOf(); }, //미리보기는 자르기 상자의 위치를 계산합니다(왼쪽 위 좌표).clipPosition() { this.isDisabled = true; this.backBtn = true; this.isMa = false; this.btnIndex = 4; //캔버스 위치 var canvasPx = this.canvas.offsetLeft, canvasPy = this.canvas.offsetTop; if (this.isDrop) { // 자르기 상자 위치 varclipPx = this.clipEle.offsetLeft,clipPy = this.clipEle.offsetTop, x =clipPx - canvasPx, y =clipPy - canvasPy, w = this.clipEle.offsetWidth, h = this.clipEle.offsetHeight, // 미리보기 이미지 중심 위치 positionX = 400 - this.clipEle.offsetWidth / 2, positionY = 300 - this.clipEle.offsetHeight / 2; } else { // 자르기 상자 없음, 전체 이미지 저장 var x = 0, y = 0, w = this.canvas.offsetWidth , h = this.canvas.offsetHeight, // 미리보기 이미지를 중앙에 위치시킵니다. positionX = 0, positionY = 0 } var imageData =; this.ctx.getImageData(x, y, w, h); this.canvasDataSession = this.ctx.getImageData( 0, 0, this.canvasWidth, this.canvasHeight ); this.ctx.clearRect(0, 0, this. canvasWidth, this.canvasHeight); this.ctx.putImageData(imageData, positionX, positionY); this.clipEle.style.display = none; this.canvasCopy.style.display = none; }, // 미리보기 이전 상태로 돌아갑니다.clipBack() { this.btnIndex = -1; isDisabled = false ; this.isDrop = false; this.ctx.putImageData(this.canvasDataSession, 0, 0); block; }, // 원본 이미지 sourceImg() { this.isDisabled = false; this.backBtn = false; this.isMaClear = false; = 새로운 이미지(); this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight); img.setAttribute('crossOrigin', 'anonymous'); img.onload = () => { this.ctx.drawImage(img, 0, 0, this.canvasWidth, this.canvasHeight) }; .imgUrl + '?time=' + new Date().valueOf(); this.canvasCopy.style.display = 블록 }, // 서명 가져오기 getSignature() { // 캔버스 이미지 base64를 파일 객체로 var dataURL = this.canvas.toDataURL(image/jpg), arr = dataURL.split(,), mime = arr[0].match(/:( . *?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); (n--) { u8arr[n] = bstr.charCodeAt(n) } var obj = new Blob([u8arr], { type: mime }), time = new Date().toGMTString(), formData = new FormData(); formData.append(file, obj); // 파일 접미사 가져오기 var suffix = formData.get(file).type.split(/)[1]; req .get(/carsource-api/upyun/sign, { suffix: suffix }) .then(response => { if (response.data.code === 0) { this.uploadOption.path = response.data.data.path; formData.append(policy, response.data.data.policy); formData.append(authorization, response.data.data.signature); this.updateImg(formData) } }) .catch(function(error) {}) }, // updateImg(formData) { axios({ url : http://v0.api.upyun.com/tmp-img, 메소드: POST, 데이터: formData }).then(response => { if (response.data.code == 200) { this.$message.success(이미지가 성공적으로 수정되었습니다) this.canvasClose(upload, response.data.url.slice(4)) } }); 상자 확대/축소 이동 startResize(e, n) { this.resizeFX = n $(document).mousemove(this.resizeDiv); document.addEventListener(mouseup, this.stopResize); stopResize(e) { $(document).off(mousemove, this.resizeDiv); document.removeEventListener(mouseup, this.stopResize) }, startMove(e) this.movePrev = [e.pageX, e.pageY]; $(document).mousemove(this.moveDiv); document.addEventListener(mouseup, this.stopMove) }, stopMove(e) { $(document).off(mousemove, this.moveDiv); this.stopMove) }, moveDiv(e) { // 모자이크 if (this.isMa) { this.paintRect(e); } // 모자이크 지우기 if (this.isMaClear) { this.paintRectClear(e) } // 자르기 if (this.isDrop) { var targetDiv = $(#canvas-mainBox), offsetArr = targetDiv.offset(); var chaX = e.pageX - this.movePrev[0], chaY = e.pageY - this.movePrev[1], ox =parseFloat(targetDiv.css(left)), oy =parseFloat(targetDiv.css(top)); targetDiv.css({왼쪽: ox + chaX + px, top: oy + chaY + px }); this.movePrev = [e.pageX, e.pageY] } }, resizeDiv(e) { e.preventDefault(); e.stopPropagation(); // 페이지에서 크기를 변경해야 하는 요소와의 거리를 가져옵니다. var targetDiv = $(#canvas-mainBox), offsetArr = targetDiv.offset(); = targetDiv.width() , eleSHeight = targetDiv.height(), ox = parsFloat(targetDiv.css(왼쪽)), oy = parseFloat(targetDiv.css(top)); // 마우스 위치를 가져와서 요소의 초기 오프셋과 비교합니다. var chaX = e.pageX - offsetArr.left, chaY = e.pageY - offsetArr.top; this.resizeFX) { 사례 0: //이동 거리가 너비 또는 높이에 가까우면 변경되지 않습니다. if (chaX >= eleSWidth - 10 || chaY >= eleSHeight - 10) { return; } // 위치 차이(me)를 구하고 너비와 높이를 먼저 설정한 후 위치를 설정합니다. // 원래 너비와 높이 + ((me) *-1), 원래 위치 + (me) targetDiv.css({ 너비: eleSWidth + chaX * -1 + px, 높이: eleSHeight + chaY * -1 + px, 왼쪽: ox + chaX + px, 위쪽: oy + chaY + px }); break; 사례 1: //이동 거리가 너비 또는 높이에 가까우면 변경되지 않습니다. if (chaY >= eleSHeight - 10) { return } // 위치 차이(me)를 구하고 너비를 설정합니다. 높이를 먼저 지정한 다음 위치를 설정합니다. // 원래 너비 및 높이 + ((me) *-1), 원래 위치 + (me) targetDiv.css({ height: eleSHeight + chaY * -1 + px, top: oy + 차이 + px }); 사례 2: //이동 거리가 너비 또는 높이에 가까우면 변경되지 않습니다. if (chaX <= 10 || chaY >= eleSHeight - 10) { return; 차이(me), 먼저 너비와 높이를 설정하고, 위치를 설정합니다. // 원래 높이 + ((me) *-1), 원래 너비 + ((me)), 원래 위치 + (me) targetDiv.css({ 너비 : chaX + px, height: eleSHeight + chaY * -1 + px, top: oy + chaY + px }) break; 사례 3: //이동 거리가 너비 또는 높이에 가까우면 변경되지 않습니다. if (chaX <= 10 ) { return; } // 위치 차이(me)를 구하고 먼저 너비와 높이를 설정한 다음 위치를 설정합니다. // 원래 너비와 높이 + ((me) *-1), 원래 위치 + (me) targetDiv .css({ 너비: chaX + px }); break; 사례 4: //이동 거리가 너비 또는 높이에 가까우면 변경되지 않습니다. if (chaX <= 10 || chaY <= 10) { return; 차이(me), 너비와 높이를 먼저 설정한 후 위치를 설정합니다. // 원래 너비와 높이 + ((me) *-1), 원래 위치 + (me) targetDiv.css({ width: chaX + px, height : chaY + px }); break; case 5: //이동 거리가 너비나 높이에 가까우면 변경되지 않습니다. if (chaY <= 10) { return } // 위치 차이(me)를 구하고 너비와 높이를 설정합니다. 먼저 위치를 설정합니다. // 원래 너비와 높이 + ((me) *-1), 원래 위치 + (me) targetDiv.css({ height: chaY + px }): //If 이동 거리가 너비 또는 높이에 가까우면 (chaX가 변경되는 경우 아무런 조치도 취하지 않습니다. >= eleSWidth - 10 || chaY <= 10) { return; } // 위치 차이(me)를 구하고 먼저 너비와 높이를 설정한 다음 위치를 설정합니다. // 원래 너비와 높이 + ((me) * -1), 원래 위치 + (나) targetDiv.css({ 너비: eleSWidth + chaX * -1 + px, 높이: chaY + px, 왼쪽: ox + chaX + px }); 7: //이동 거리가 너비나 높이에 가까울 경우 변경되지 않습니다. if (chaX >= eleSWidth - 10) { return } // 위치 차이(me)를 구하고 너비와 높이를 먼저 설정합니다. , 위치를 설정합니다 // 원래 너비 및 높이 + ((me) *-1), 원래 위치 + (me) targetDiv.css({ width: eleSWidth + chaX * -1 + px, left: ox + chaX + px }); break; 기본값: break; } }, //clipReady() { this.btnIndex = 3; this.isDrop = true; this.isMaClear = false }, // 모자이크 페인트RectReady() btnIndex = 1; this.isMa = true; this.isMaClear = false }, // Eraser PaintRectClearReady() { this.btnIndex = 2; this.isMa = false; this.isMaClear = true; }, // 모자이크 그리기 PaintRect(e) { var offT = this.canvas.offsetTop, / / 상단으로부터의 거리 offL = this.canvas.offsetLeft, // 왼쪽으로부터의 거리 x = e.clientX, y = e.clientY; if(this.mouseX - x > this.maSize/2 || x - this.mouseX > this.maSize/2 || this.mouseY - y > this.maSize/2 || y - this.mouseY > this.maSize/2){ var oImg = this.ctx.getImageData(x - offL ,y - offT,this.maSize,this.maSize); var w = oImg.width; var h = oImg.height; //모자이크 정도는 숫자가 클수록 흐릿해집니다. var num = 6; canvas var stepW = w/ num; var stepH = h/num; //여기에 루프 캔버스의 픽셀이 있습니다. for(var i=0;i<stepH;i++){ for(var j=0;j<stepW; j++){ //작은 사각형의 임의의 색상을 가져옵니다. 이는 작은 사각형의 임의의 위치에서 얻은 var 색상입니다. = this.getXY(oImg,j*num+Math.floor(Math.random()*num),i *num +Math.floor(Math.random()*num)); //여기에 작은 원형 정사각형의 픽셀이 있습니다. for(var k=0;k<num;k++){ for(var l=0; l< 숫자;l++){ //작은 사각형의 색상을 설정합니다. this.setXY(oImg,j*num+l,i*num+k,color) } } } } this.ctx.putImageData(oImg,x - offL ,y - offT) ; this.mouseX = e.clientX this.mouseY = e.clientY } }, getXY(obj,x,y){ var w = obj.width; var h = obj.height; var d = obj.data; color[0] = d[4*(y*w+x)]; w+x)+1]; 색상[2] = d[4*(y*w+x)+2]; 색상[3] = d[4*(y*w+x)+3]; ; setXY(obj,x,y,color){ var w = obj.width; var h = obj.height; var d = obj.data; d[4*(y*w+x)] = color[0]; d[4*(y*w+x)+1] = 색상[1]; d[4*(y*w+x)+2] = 색상[2]; d[4*(y*w+x)+3] = color[3]; }, // 모자이크 지우기 PaintRectClear(e) { var offT = this.canvasCopy.offsetTop, // 상단으로부터의 거리 offL = this.canvasCopy .offsetLeft, // 왼쪽까지의 거리 x = e.clientX, y = e.clientY, // 원본 이미지의 이 위치에서 이미지 데이터를 가져옵니다. imageData = this.ctxCopy.getImageData( x - offL, y - offT, this.maSize, this.maSize ); this.ctx.putImageData(imageData, x - offL, y - offT) }, // 캔버스를 닫습니다. , url) { this.$emit(isShowImgChange, type, url) } }};</script><style 범위>.canvas-clip { 위치: 고정; 위쪽: 0; 오른쪽: 0; 배경: #000;}.canvas-mainBox { 위치: 절대; 400px, 높이: 300px, 상단: 50%, 여백 상단: -150px 1px 솔리드 #FFF; 커서: z-index: 9009;}.canvas-minBox { 위치: 절대; 너비: 8px; 왼쪽 위로 { 위쪽: -4px : -4px; 커서: nw-resize;}.up { 상단: -4px; 왼쪽: 50%; n-resize;}.right-up { 위쪽: -4px; 오른쪽: -4px; 커서: ne-resize;}.right { 위쪽: 50%; 오른쪽: -4px; -resize;}.right-down { 하단: -4px; 오른쪽: -4px; 커서: se-resize;}.down { 하단: -4px; margin-left: -4px; 커서: s-resize;}.left-down { 하단: -4px; 왼쪽: -4px; 커서: sw-resize;}.left { 상단: 50%; ; 왼쪽: -4px; 커서: w-resize;}.canvas-btns { 위치: 오른쪽: 50px; z-index: 9003;}.canvas-btns 버튼 { 디스플레이: inline-blovk; 배경: 녹색; 커서: 포인터: 없음; 높이: 30px; 글꼴 크기: 15px;}.canvas-btns 버튼.활성 { 배경: rgb(32, 230, 32);}.canvas-btns 버튼.close { 배경: rgb(230, 72, 32);}.canvas-copy { 위치: 절대; 왼쪽: 50%; 여백-왼쪽: -400px; z-색인: 9007;}.canvas-mosatic { 위치: 절대: 50%; 50%; 여백 상단: -300px; z-색인: 9009;}.canvas-area { 위치: 절대; 왼쪽: 50%; 여백-왼쪽: -400px; z-색인: 9008;}.paint-size{ 여백-상단: 20px; 13px; 높이: 30px; 텍스트 정렬: 오른쪽;}.paint-size 입력{ 수직 정렬: 중간; 녹색;}.paint-size .size-num{ 디스플레이: 인라인 블록; 너비: 15px;}.hoverClear{ 커서: url('./paint.png'),auto;}.hoverPaint{ 커서: url('./paint.png'),auto;}</style>
4. 렌더링은 다음과 같습니다.
요약위 내용은 사진 자르기 및 모자이크, 클라우드 업로드 기능을 구현하기 위해 HTML5 캔버스를 기반으로 편집자가 소개하는 내용입니다. 궁금한 점이 있으면 메시지를 남겨주세요. 편집자가 시간에 맞춰 답변을 드릴 것입니다. 또한 VeVb 무술 웹사이트를 지원해 주신 모든 분들께 감사드립니다!
이 글이 도움이 되셨다면 재인쇄하셔도 좋고, 출처를 밝혀주시면 감사하겠습니다!