2013-02-03 40 views
45

TL; DR;如何在瀏覽器中通過Javascript壓縮圖像?

有沒有辦法在上傳之前直接在瀏覽器端壓縮圖像(主要是jpeg,png和gif)?我非常確定JavaScript可以做到這一點,但我找不到實現它的方法。


下面是完整的情況我想實現:

  • 用戶去我的網站,並通過input type="file"元素選擇圖像,
  • 這個圖像是通過JavaScript檢索,我們做一些驗證,如正確的文件格式,最大的文件大小等,
  • 如果一切正常,在頁面上顯示圖像預覽,
  • 用戶可以做一些基本的操作,如如將圖像旋轉90°/ -90°,按照預先設定的比例裁剪等,或者用戶可以上傳其他圖像並返回到步驟1,當用戶滿意時,編輯後的圖像將會返回到步驟1,然後返回到步驟1
  • 壓縮和「保存」在本地(不保存到一個文件,但在瀏覽器內存/頁面), -
  • 用戶填寫表單與數據如名稱,年齡等,
  • 用戶點擊「完成」按鈕,然後將含有DATAS +壓縮圖像的形式被髮送到服務器(沒有AJAX),

全過程直到最後的步驟應做客戶端,並且應該對最新Chrome和Firefox兼容,Safari 5+和IE 8+。如果可能的話,只能使用JavaScript(但我確信這是不可能的)。

我現在還沒有編寫任何代碼,但我已經考慮過了。通過File API可以在本地讀取本地文件,可以使用Canvas元素完成圖像預覽和編輯,但是我無法找到一種方法來執行圖像壓縮部分

根據html5please.comcaniuse.com,支持這些瀏覽器是很困難(感謝IE),但可以用填充工具,如FlashCanvasFileReader來完成。

實際上,我們的目標是減少文件大小,所以我將圖像壓縮視爲一種解決方案。但是,我知道上傳的圖像將顯示在我的網站上,每次都在同一個地方,我知道這個顯示區域的尺寸(例如200x400)。所以,我可以調整圖像大小以適應這些尺寸,從而減小文件大小。我不知道這種技術的壓縮比率是多少。

您認爲如何?你有什麼建議可以告訴我嗎?你知道有什麼辦法在JavaScript中壓縮圖像瀏覽器端嗎?感謝您的回覆。

+0

「只有JavaScript應使用**(但我敢肯定,這是不可能的) **「也許你可以停止像1980年那樣不可能的編程,並且開始像2017年那樣可能的編程。 :) – 2017-05-13 19:12:32

回答

85

簡而言之:

  • 閱讀使用與HTML5的FileReader API的文件。readAsArrayBuffer
  • 創建文件數據E Blob和與window.URL.createObjectURL(blob)
  • 得到它的URL創建新的圖像元素,並將其設置的SRC的文件BLOB URL
  • 將圖像發送到畫布上。將畫布大小設置爲所需的輸出大小
  • 通過canvas.toDataURL(「image/jpeg」,0.7)獲取從畫布返回的縮小數據(設置您自己的輸出格式和質量)
  • 附加新的隱藏輸入原來的形式和基本傳輸dataURI圖像作爲普通的文本
  • 在後端,閱讀dataURI,從Base64編碼解碼,並將其保存

來源:code

+0

非常感謝!這是我正在尋找的。你知道這種技術的壓縮比有多好? – pomeh

+0

除了網絡傳輸(你發送Base64編碼的內容,這不是最好的內容),圖像壓縮算法是標準算法之一,其大小取決於你選擇的質量和格式。 – psychowood

+0

準確地更正。 –

3

據我所知,你不能使用畫布壓縮圖像,相反,你可以調整它的大小。使用canvas.toDataURL不會讓您選擇要使用的壓縮比率。你可以看一看你想要的圖像:https://github.com/nfroidure/CanImage/blob/master/chrome/canimage/content/canimage.js

實際上,調整圖像的大小通常足以減小圖像大小,但如果你想進一步研究,就必須使用新引入的方法file.readAsArrayBuffer獲得一個包含圖像數據的緩衝區。

然後,只需使用DataView根據圖像格式規範(http://en.wikipedia.org/wiki/JPEG或)讀取它的內容即可。

這將是很難處理圖像數據壓縮,但它是一個更糟糕的嘗試。另一方面,您可以嘗試刪除PNG標頭或JPEG Exif數據以使圖像更小,這樣做應該更容易。

您必須在另一個緩衝區上創建另一個DataWiew並將其填充過濾後的圖像內容。然後,您只需使用window.btoa將圖像內容編碼爲DataURI。

讓我知道如果你實現類似的東西,將會很有趣地通過代碼。

8

@PsychoWoods的答案很好。我想提供我自己的解決方案。這個Javascript函數獲取圖像數據的URL和寬度,將其縮放到新的寬度,並返回一個新的數據URL。

// Take an image URL, downscale it to the given width, and return a new image URL. 
function downscaleImage(dataUrl, newWidth, imageType, imageArguments) { 
    "use strict"; 
    var image, oldWidth, oldHeight, newHeight, canvas, ctx, newDataUrl; 

    // Provide default values 
    imageType = imageType || "image/jpeg"; 
    imageArguments = imageArguments || 0.7; 

    // Create a temporary image so that we can compute the height of the downscaled image. 
    image = new Image(); 
    image.src = dataUrl; 
    oldWidth = image.width; 
    oldHeight = image.height; 
    newHeight = Math.floor(oldHeight/oldWidth * newWidth) 

    // Create a temporary canvas to draw the downscaled image on. 
    canvas = document.createElement("canvas"); 
    canvas.width = newWidth; 
    canvas.height = newHeight; 

    // Draw the downscaled image on the canvas and return the new data URL. 
    ctx = canvas.getContext("2d"); 
    ctx.drawImage(image, 0, 0, newWidth, newHeight); 
    newDataUrl = canvas.toDataURL(imageType, imageArguments); 
    return newDataUrl; 
} 

此代碼可用於任何有數據URL且想要縮小圖像的數據URL的地方。

+0

拜託,你能給我更多關於這個例子的詳細信息,如何調用函數以及如何返回結果? – nabil

+0

下面是一個示例:http://danielsadventure.info/Html/scaleimage.html請務必閱讀頁面的源代碼以瞭解其工作原理。 –

0

對於JPG圖像壓縮,你可以使用一個叫做JIC (JavaScript圖片壓縮)最好的壓縮技術,這一定會幫助你 - >https://github.com/brunobar79/J-I-C

+1

而不降低質量 –

5

我看到兩件事情從另一個答案丟失:

  • canvas.toBlob(當可用時)比canvas.toDataURL更高性能,也是異步。
  • 文件 - >圖像 - >畫布 - >文件轉換失去EXIF數據;尤其是關於現代手機/平板電腦通常設置的圖像旋轉數據。

下面的腳本涉及兩個點:

// From https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob, needed for Safari: 
if (!HTMLCanvasElement.prototype.toBlob) { 
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 
     value: function(callback, type, quality) { 

      var binStr = atob(this.toDataURL(type, quality).split(',')[1]), 
       len = binStr.length, 
       arr = new Uint8Array(len); 

      for (var i = 0; i < len; i++) { 
       arr[i] = binStr.charCodeAt(i); 
      } 

      callback(new Blob([arr], {type: type || 'image/png'})); 
     } 
    }); 
} 

window.URL = window.URL || window.webkitURL; 

// Modified from https://stackoverflow.com/a/32490603, cc by-sa 3.0 
// -2 = not jpeg, -1 = no data, 1..8 = orientations 
function getExifOrientation(file, callback) { 
    // Suggestion from http://code.flickr.net/2012/06/01/parsing-exif-client-side-using-javascript-2/: 
    if (file.slice) { 
     file = file.slice(0, 131072); 
    } else if (file.webkitSlice) { 
     file = file.webkitSlice(0, 131072); 
    } 

    var reader = new FileReader(); 
    reader.onload = function(e) { 
     var view = new DataView(e.target.result); 
     if (view.getUint16(0, false) != 0xFFD8) { 
      callback(-2); 
      return; 
     } 
     var length = view.byteLength, offset = 2; 
     while (offset < length) { 
      var marker = view.getUint16(offset, false); 
      offset += 2; 
      if (marker == 0xFFE1) { 
       if (view.getUint32(offset += 2, false) != 0x45786966) { 
        callback(-1); 
        return; 
       } 
       var little = view.getUint16(offset += 6, false) == 0x4949; 
       offset += view.getUint32(offset + 4, little); 
       var tags = view.getUint16(offset, little); 
       offset += 2; 
       for (var i = 0; i < tags; i++) 
        if (view.getUint16(offset + (i * 12), little) == 0x0112) { 
         callback(view.getUint16(offset + (i * 12) + 8, little)); 
         return; 
        } 
      } 
      else if ((marker & 0xFF00) != 0xFF00) break; 
      else offset += view.getUint16(offset, false); 
     } 
     callback(-1); 
    }; 
    reader.readAsArrayBuffer(file); 
} 

// Derived from https://stackoverflow.com/a/40867559, cc by-sa 
function imgToCanvasWithOrientation(img, rawWidth, rawHeight, orientation) { 
    var canvas = document.createElement('canvas'); 
    if (orientation > 4) { 
     canvas.width = rawHeight; 
     canvas.height = rawWidth; 
    } else { 
     canvas.width = rawWidth; 
     canvas.height = rawHeight; 
    } 

    if (orientation > 1) { 
     console.log("EXIF orientation = " + orientation + ", rotating picture"); 
    } 

    var ctx = canvas.getContext('2d'); 
    switch (orientation) { 
     case 2: ctx.transform(-1, 0, 0, 1, rawWidth, 0); break; 
     case 3: ctx.transform(-1, 0, 0, -1, rawWidth, rawHeight); break; 
     case 4: ctx.transform(1, 0, 0, -1, 0, rawHeight); break; 
     case 5: ctx.transform(0, 1, 1, 0, 0, 0); break; 
     case 6: ctx.transform(0, 1, -1, 0, rawHeight, 0); break; 
     case 7: ctx.transform(0, -1, -1, 0, rawHeight, rawWidth); break; 
     case 8: ctx.transform(0, -1, 1, 0, 0, rawWidth); break; 
    } 
    ctx.drawImage(img, 0, 0, rawWidth, rawHeight); 
    return canvas; 
} 

function reduceFileSize(file, acceptFileSize, maxWidth, maxHeight, quality, callback) { 
    if (file.size <= acceptFileSize) { 
     callback(file); 
     return; 
    } 
    var img = new Image(); 
    img.onerror = function() { 
     URL.revokeObjectURL(this.src); 
     callback(file); 
    }; 
    img.onload = function() { 
     URL.revokeObjectURL(this.src); 
     getExifOrientation(file, function(orientation) { 
      var w = img.width, h = img.height; 
      var scale = (orientation > 4 ? 
       Math.min(maxHeight/w, maxWidth/h, 1) : 
       Math.min(maxWidth/w, maxHeight/h, 1)); 
      h = Math.round(h * scale); 
      w = Math.round(w * scale); 

      var canvas = imgToCanvasWithOrientation(img, w, h, orientation); 
      canvas.toBlob(function(blob) { 
       console.log("Resized image to " + w + "x" + h + ", " + (blob.size >> 10) + "kB"); 
       callback(blob); 
      }, 'image/jpeg', quality); 
     }); 
    }; 
    img.src = URL.createObjectURL(file); 
} 

用法示例:

inputfile.onchange = function() { 
    // If file size > 500kB, resize such that width <= 1000, quality = 0.9 
    reduceFileSize(this.files[0], 500*1024, 1000, Infinity, 0.9, blob => { 
     let body = new FormData(); 
     body.set('file', blob, blob.name || "file.jpg"); 
     fetch('/upload-image', {method: 'POST', body}).then(...); 
    }); 
}; 
+0

ToBlob爲我做了訣竅,創建一個文件並接收服務器上的$ _FILES數組。謝謝! –