線上摳圖網站速摳圖sukoutu.com全面技術解析之canvas應用

aiaito發表於2019-05-22
  • 技術關鍵詞

          Canvas應用,泛洪演算法(Flood Fill),圖片縮放,相對位置等比縮放,判斷一個點是否在一個平面閉合多邊形,nginx代理

  • 業務關鍵詞

         線上摳圖,智慧摳圖,一鍵摳圖,鋼筆摳圖,矩陣摳圖,圖片處理,圖片壓縮,圖片尺寸,圖片格式,圖片透明度,圖片下載

  • 引用元件(前端)

         Jquery,Canvas,Jcrop, Layer,MiniColors

  • 技術解析背景

          發表這篇隨筆,一方面幫助前端的同學認識Canvas的實際應用場景和了解相關技術點,另一方面讓sukoutu.com能夠幫助更多的人去快速高效的完成摳圖。

         作為一名後端研發去開發一套純前端的線上摳圖工具實際上看起來有些疑惑和怪異,或許是因為 14年5月發表的一個隨筆  html5 canvas+js實現ps鋼筆摳圖 的延續。

         發表在知乎上的一篇文章 能讓你知道sukoutu.com上線的原由、目的以及相關功能簡介。

         

  • 速摳圖網站實現原理

          網站幾乎沒有和後端服務做互動。後端用nginx作為server,另外代理了三方房展的圖片訪問(解決前端跨域訪問網路圖片跨域禁止canvas渲染的問題,通過nginx代理訪問規避這個問題)。圖片上傳及圖片操作等均在客戶端瀏覽器上完成(圖片操作流暢的很),因為有Canvas,所以整個工具網站的核心都在於js的互動及canvas的應用。

  • 智慧摳圖(仿PS魔法棒效果)

         這裡參考了一些開源專案和泛洪演算法(Flood Fill)的實現案例。對於一次純色或者背景色差比較大的圖片摳圖,基本都能實現一鍵摳圖的效果,通過點選滑選閉合色區來實現摳圖,但是對於背景色差複雜的圖片就很難實現自己想要的摳圖了。

         演算法參考: 影像分割經典演算法--《泛洪演算法》(Flood Fill)

         實現原理:底層放圖片,頂層放canvas,將圖片按照對等尺寸渲染至到畫布上得到ImageData,滑鼠點選獲取ImageData RGB 色值,用floodfill演算法得到閉合外圍座標,在頂層canvas渲染邊線(ctx.putImageData)

 1  init: function (img) {
 2         this.data.img = img;
 3         this.data.tempCanvas = document.createElement('canvas');
 4         var tempCtx = this.data.tempCanvas.getContext('2d');
 5         //切記,原始影像大小
 6         tempCtx.canvas.width = panel.data.targetImgWidth;
 7         tempCtx.canvas.height = panel.data.targetImgHeight;
 8         tempCtx.drawImage(img, 0, 0, panel.data.targetImgWidth, panel.data.targetImgHeight);
 9         this.data.imgData = tempCtx.getImageData(0, 0, panel.data.targetImgWidth, panel.data.targetImgHeight);
10     },
  • 鋼筆摳圖

         鋼筆摳圖參考 html5 canvas+js實現ps鋼筆摳圖 

         實現原理:底層放圖片,頂層放canvas,滑鼠點選座標存放到array中,canvas根據座標list畫線畫點,canvas的 globalCompositeOperation = "destination-out" 屬性可以實現通過由多個點構成的閉合區間設定成透明色穿透畫布背景色或是背景圖片。

  • 矩陣摳圖

         這裡用的是非常實用的Jcrop外掛,參考Jcrop官網 ,提供了豐富的api,比如等比、移動等實用的功能。

  • 圖片等比縮放(很實用)

          解決圖片載入要等比縮放到畫布中,並且居中顯示。

 1 function imgScale(src, w, h, fun) {
 2     var img = new Image();
 3     img.src = src;
 4     img.onload = function () {
 5         var wi = img.width;
 6         var he = img.height;
 7         var toHe = he * w / wi;
 8         var toWi = 0;
 9         if (toHe > h) {
10             toWi = wi * h / he;
11             toHe = h;
12         } else {
13             toWi = w;
14         }
15         fun(toWi, toHe);
16     };
17 }
  • 相對位置等比縮放

        在實現鋼筆摳圖或矩陣摳圖需要放大,並且鋼筆及矩陣座標list也要等比縮放,需要用到如下演算法來實現。

 1 _scale = function (c, a, f) {
 2     x1 *= c.x;
 3     y1 *= c.y;
 4     x2 *= c.x;
 5     y2 *= c.y;
 6     if (a) {
 7         _values = [1.0000000000000002, -0, -0, 1.0000000000000002];
 8         var b = transform(a.x - translationStart.x, a.y - translationStart.y);
 9         var d = transform(c.x * -b.x, c.y * -b.y);
10         translation.x = d.x + a.x;
11         translation.y = d.y + a.y
12     }
13 };
14 
15 transform = function (b, d, a) {//兩中心x差,兩中心y差
16     var c = new Point(_values[0] * b + _values[1] * d, _values[2] * b + _values[3] * d);
17     return c
18 };
  •  Canvas應用

      圖片壓縮、圖片尺寸調整、圖片背景色、圖片透明度實際均可通過canvas屬性來操作

//透明度 
ctx.globalAlpha = imgCreate.cutObj.alpha;
//背景色
ctx.fillStyle = imgCreate.cutObj.color;
//根據座標list裁剪圖片
var ctx = $("#createCanvas")[0].getContext('2d');
ctx.beginPath();
ctx.moveTo(0, 0);
for (var i = 0; i < proxy.cutObj.pointArray.length; i++) {
    ctx.lineTo(proxy.cutObj.pointArray[i].pointx, proxy.cutObj.pointArray[i].pointy);
}
ctx.lineTo(proxy.cutObj.pointArray[0].pointx, proxy.cutObj.pointArray[0].pointy);
ctx.clip();
ctx.drawImage(proxy.cutObj.imgObj, tempPointArray[0].pointx * -1, tempPointArray[0].pointy * -1, proxy.cutObj.width, proxy.cutObj.height);
//生成及下載圖片相容(ie\谷歌\火狐)
var fileName = proxy.cutObj.name + "." + proxy.cutObj.suffix.replace("e", "");
if (window.navigator.msSaveOrOpenBlob) {
    var imgData = $("#createCanvas")[0].msToBlob();
    var blobObj = new Blob([imgData]);
    window.navigator.msSaveOrOpenBlob(blobObj, fileName);
} else {
    var imgData = $("#createCanvas")[0].toDataURL("image/" + imgCreate.cutObj.suffix, parseFloat(imgCreate.cutObj.quality));
    imgData = imgData.replace("image/" + imgCreate.cutObj.suffix, 'image/octet-stream');
    var a = document.createElement('a');
    var event = new MouseEvent('click');
    a.download = fileName;
    a.href = URL.createObjectURL(dataURIToBlob(imgData));
    a.dispatchEvent(event);
}

 

 

相關文章