このコンポーネントの機能には次のものが含まれます。
画像のトリミング (トリミング ボックスをドラッグして、トリミング ボックスのサイズを変更します);
画像モザイク(描画モザイク、クリアモザイク)
画像プレビュー、画像修復(元の画像に戻す、処理済みの画像に戻す);
画像のアップロード (署名の取得、画像のアップロード)。
2. コアロジック2.1 画像のトリミング
キャンバス (左上) に対するトリミング ボックス (長方形) の位置と、トリミング ボックスの高さと幅を取得します。キャンバスの対応する位置にある画像オブジェクト(ImageData)を取得(getImageData)します。キャンバスキャンバスをクリアします。 (putImageData)で取得した絵オブジェクト(ImageData)をキャンバスcanvasの対応する位置に描画します。プレビュー画像を生成します。
2.2 画像モザイク
モザイク描画とは、マウスのストローク軌跡(ブラシ幅)を中心とした領域を別の色に再描画することです。一般的な結果は、周囲の色が似たものになります。
色の選択方法:
1) たとえば、マウスが通過した点の座標 (x, y) がある場合、左上隅の座標 (x, y)、幅 30 ピクセル、高さ 30 ピクセルの長方形を定義します。長方形の幅と高さを 5 で割ります (5 つの部分に分割され、n 個の部分にカスタマイズできます)。そのため、6 ピクセルの小さなグリッドが 25 個あります。それぞれの小さなグリッドの幅と高さは 6 ピクセルです。
2) 次に、小さなグリッドをランダムに取得し、この小さなグリッドの画像オブジェクト (ImageData) を取得 (getImageData) し、その上の特定のピクセル (幅 1px、高さ 1px) のカラー color (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. 完全なコードは次のとおりです。
<テンプレート> <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 left-up @mousedown.stop=startResize($event,0)></div> <div class=canvas-minBox up @mousedown.stop=startResize($event,1)></div> <div class=canvas-minBox right-up @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 down @mousedown.stop=startResize($event,5)></div> <div class=canvas-minBox left-down @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> <!-- キャンバスをバックアップ --> <canvas class=canvas-copy ref=canvasCopy :width=canvasWidth :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 type=range> <span class=size-num>{{maSize}}</span> </div> --> </div> </div></template><script>import axios from axios;import md5 from js-md5 ; ../../axios/config から要求をインポート; デフォルトをエクスポート { props: [imgUrl], data() { return { raiseFX: , 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: , // Canvas Context CanvasCopy: , // キャンバスをコピー ctxCopy: , // キャンバス コンテキストをコピー UploadOption: { // 画像アップロード パラメータ パス: , ポリシー: , 署名: , ユーザー名: } }; }, mount() { this.clipEle = 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, 800, 600); img.src = this.imgUrl + '?time=' + new Date().valueOf(); }, //プレビューはトリミング ボックスの位置 (左上の座標) を計算します。clipPosition() { this.isDisabled = true; this.backBtn = true; this.isMa = false; this.isMaClear = false; //キャンバス位置 var CanvasPx = this.canvas.offsetLeft, CanvasPy = this.canvas.offsetTop; if (this.isDrop) { // クロップボックスの位置 var ClipPx = 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(画像データ, 位置X, 位置Y); this.clipEle.style.display = none; this.canvasCopy.style.display = none; }, // プレビュー前の状態に戻ります。 = false ; this.isDrop = false; this.ctx.putImageData(this.canvasDataSession, 0, 0); block; }, // 元の画像 sourceImg() { this.isDisabled = false; this.isMa = 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(); }, //署名を取得 getSignature() { // Canvas 画像 Base64 を File オブジェクトに変換 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); this.movePrev = [e.pageX, e.pageY]; $(document).mousemove(this.moveDiv); document.addEventListener(mouseup, this.stopMove) }, stopMove(e) { $(document).off(mouseup, 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({ left: 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 = parseFloat(targetDiv.css(left))、oy = parseFloat(targetDiv.css(top)); // マウスの位置を取得し、要素の初期オフセットと比較します。 var chaX = e.pageX - offsetArr.left、chaY = e.pageY - offsetArr.top; this.resizeFX) { case 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; case 1: //移動距離が幅または高さに近い場合は変更されません if (chaY >= eleSHeight - 10) { return; // 位置の差を取得し、幅を設定します最初に高さ、次に位置を設定 // 元の幅と高さ + ((me) *-1), 元の位置 + (me) targetDiv.css({ height: eleSHeight + chaY * -1 + px, top: oy +チャY+ピクセル}); Break; case 2: //移動距離が幅または高さに近い場合、変更は行われません if (chaX <= 10 || chaY >= eleSHeight - 10) { return;差分 (me), まず幅と高さを設定し、位置を設定します // 元の高さ + ((me) *-1), 元の幅 + ((me)), 元の位置 + (me) targetDiv.css({ width :chaX + ピクセル、 height: eleSHeight + chaY * -1 + px, top: oy + chaY + px }); case 3: //移動距離が幅または高さに近い場合、変更は行われません (chaX <= 10) ) { return; } // 位置の差 (me) を取得し、最初に幅と高さを設定し、次に位置を設定します // 元の幅と高さ + ((me) *-1)、元の位置 + (me) targetDiv .css({幅:chaX + px }); Break; case 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) *-1), 元の位置 + (me) targetDiv.css({ height: chaY + px }); //If Break;移動距離が幅または高さに近い場合、何も変更されません (chaX. >= eleSWidth - 10 || chaY <= 10) { return; } // 位置の差 (me) を取得し、最初に幅と高さを設定し、次に位置を設定します // 元の幅と高さ + ((me) * -1)、元の位置 + (me) targetDiv.css({ width: eleSWidth + chaX * -1 + px、height: chaY + px、left: ox + chaX + px }); 7: //移動距離が幅または高さに近い場合は変更されません if (chaX >= eleSWidth - 10) { return; // 位置の差(me)を取得し、最初に幅と高さを設定します。 、次に位置を設定します // 元の幅と高さ + ((me) *-1), 元の位置 + (me) targetDiv.css({ width: eleSWidth + chaX * -1 + px, left: ox + chaX +ピクセル });ブレーク; デフォルト: ブレーク; } }, // クリッピング ClipReady() { this.isMa = false; this.isDrop = true; .btnIndex = 1; this.isMa = true; this.isMaClear = false;消しゴムpaintRectClearReady() { this.btnIndex = 2; this.isDrop = 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++){ // 小さな正方形のランダムな色を取得します。これは、小さな正方形のランダムな位置から取得される変数の色です = 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< num;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; カラー[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 d = obj.data; 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) }, // キャンバスを閉じる CanvasClose(type) , url) { this.$emit(isShowImgChange, type, url) } }};</script><styleスコープ>.canvas-clip { 位置: 固定; 下: 0; z-index: 9010; }.canvas-mainBox { 位置: 絶対幅: 400ピクセル; 高さ: 300ピクセル; 左マージン: -200ピクセル; 1px ソリッド #FFF; 移動; z-index: 9009;}.canvas-minBox { 位置: 絶対; 幅: 8px; }.left-up { 上: -4px; : -4px; カーソル: nw-resize;}.up { 上: -4px; マージン左: -4px; n-resize;}.right-up { 上: -4px; 右: -4px; }.right { 上: 50%; 右: -4px; -resize;}.right-down { 下: -4px; 右: -4px; カーソル: se-resize;}.down { 下: -4px;マージン左: -4px; カーソル: s-resize;}.left-down { 下: -4px; 左: -4px; }.left { トップ: 50%; ; 左: -4px; カーソル: w-resize;}.canvas-btns { 位置: 固定; 上: 30px; z-index: 9003;}.canvas-btns ボタン { 表示: インラインブロック; カーソル: ポインター; 幅: 30 ピクセル; 行の高さ: #fff; font-size: 15px;}.canvas-btns button.active { 背景: rgb(32, 230, 32);}.canvas-btns button.close { 背景: rgb(230, 72, 32);}.canvas-copy { 位置: 絶対; 左: 50%; margin-left: -400px; z-index: 9007;}.canvas-mosatic { 位置: 絶対上: 50%; 50%; マージン上部: -300px; z インデックス: 9009;}.canvas-area { 位置: 上部: 50%; マージン上部: -300px;マージン左: -400ピクセル: 9008;}.ペイントサイズ{ マージントップ: 20ピクセル; 13px; 色: #FFF; 高さ: 30px; テキスト整列: 右; }.paint-size .size-num{表示: インラインブロック; 幅: 15px;}.hoverClear{ カーソル: url('./paint.png'),auto;}.hoverPaint{カーソル: url('./paint.png'),auto;}</style>
4. レンダリングは次のとおりです。
要約する上記は、HTML5 キャンバスに基づいてトリミング機能とモザイク機能、およびクラウドアップロード機能を実現するためにエディターが紹介したものです。ご質問があれば、エディターが私にメッセージを残してください。までに返信してください。また、VeVb武道サイトを応援してくださった皆様、誠にありがとうございました!
この記事が役立つと思われる場合は、転載していただいてかまいませんので、出典を明記してください。ありがとうございます。