JavaScript實現手機拍攝圖片的旋轉、壓縮

水果哥發表於2018-12-19

現在的手機拍攝的照片大小基本都在5M~10M之間。對於大圖片的上傳,不僅慢,而且對使用者體驗有嚴重的影響。如果我們對圖片清晰度的要求不是很高,可以通過前端的壓縮可以達到兩個目的:1.節省流量。 2.提高使用者體驗。

1.把系統中的圖片呈現在瀏覽器中

html程式碼:
<input id="J_takepic" accept="image/*" type="file">
<img id="J_showpic" src="" alt="show-picture">
<canvas id="J_canvas"></canvas>
js程式碼:
let takePic = document.querySelecotr('#J_takepic')
let showPic = document.querySelecotr('#J_showpic')
複製程式碼

獲取圖片: 目前獲取input圖片的方法主要有兩種:

(1)FileReader (2)createObjectURL

if(tackPic && showPic) {
    takePic.onchange = function(event) {
     let files = event.target.files
     let file = ''
     if (files && files.lenght > 0) {
         file = files[0]
         try {
            let URL = window.URL || window.webkitURL
            let imgURL = URL.createObjectURL(file)
            showPic.src = imgURL
            showPic.onload = function () {
                URL.revokeObjectURL(imgURL)
            }
         }
         catch (e) {
             try {
                 let fileReader = new FileReader()
                 fileReader.onload = function (event) {
                     showPic.src = event.target.result
                 }
                 fileReader.readAsDataURL(file)
             }
             catch (e) {
                 console.error('Neither createObjectURL or FileReader are supported')
             }
         }
     }
    }
}
複製程式碼

2.獲取圖片旋轉度

並不是所有的手機拍攝的圖片在img標籤中都可以正常展示,在測試不同手機的過程中你會驚訝的發現有些圖片竟然被旋轉了90度。作為一名程式猿,這種問題怎麼能忍。 正常情況下,手機拍攝的照片都會攜帶地址、旋轉角度、大小等資訊,通過一定的方法都可以獲取到。這裡我們通過EXIF來獲取圖片旋轉角度。

let Orientation = 1
EXIF.getData(file, function() {  
    Orientation = EXIF.getTag(this, 'Orientation');
})
Orientation的值分別為:1(無旋轉)6(旋轉90度)3(旋轉180度)8(旋轉-90度)
複製程式碼

3.旋轉並壓縮圖片

旋轉圖片的實現基於canvas的rotate()方法。旋轉的中心點預設在canvas的(0,0)點。

利用canvas.toDataURL()進行圖片壓縮,得到圖片的data uri的值。

function rotateAndCompress (image, Orientation) {
    let imgWidthOrigin = image.width
    let imgHeightOrigin = image.height
    // 壓縮圖片
    let ratio = imgWidthOrigin / imgHeightOrigin
    // 假設壓縮後的圖片的寬度為500px
    let canvasWidth = 500
    let canvasHeight = Math.ceil(500 / ratio)
    // 旋轉並壓縮
    let canvas = document.getElementById('canvas')
    let ctx = canvas.getContext('2d')
    canvas.width = canvasWidth
    canvas.height = canvasHeight
    if (Orientation && Orientation !== 1) {
        switch (Orientation) {
            case 6:
             canvas.width = canvasHeight
             canvas.height = canvasWidth
             ctx.rotate(90 * Math.PI / 180)
             ctx.drawImage(image, 0, -canvasHeight, canvasWidth, canvasHeight)
             break
            case 3:
             ctx.rotate(Math.PI)
             ctx.drawImage(image, -canvasWidth, -canvasHeight, canvasWidth,canvasHeight)
             break
            case 8:
            // 旋轉-90度相當於旋轉了270度
             canvas.width = canvasHeight
             canvas.height = canvasWidth
             ctx.rotate(270 * Math.PI / 180)
             ctx.drawImage(image, -canvasWidth, 0, canvasWidth, canvasHeight)
             break
        }
    } esle {
        ctx.drawImage(image, 0, 0, canvasWidth, canvasHeight)
    }
}

複製程式碼

到了這裡,我們基本完成了對圖片的旋轉與壓縮。通常我們的寫法如下:

let Orientation = 1
EXIF.getData(file, function() {  
    Orientation = EXIF.getTag(this, 'Orientation');
})
rotateAndCompress(Orientation) // 立即呼叫旋轉和壓縮的方法
複製程式碼

在自己的手機上測試之後,發現圖片正常的進行了旋轉,檢視壓縮後的圖片,圖片從6.8M壓縮到了1.1M,壓縮效果顯著。心裡美滋滋...

換了臺手機再次測試,竟然發現圖片沒有旋轉的情況依舊沒有改變。what?明明不是已經獲取到圖片的旋轉角度了麼,為什麼有的手機可以正常旋轉,有的手機卻依舊存在問題呢?debug...

調式之後竟然發現Orientation竟然沒有獲取到,原來是回撥函式EXIF.getTag()還沒有返回結果就已經執行了rotateAndCompress()方法(高效能的手機執行速度確實快)。最終的解決方案如下:利用promise解決了非同步回撥的問題,等待獲取到Orientation之後再執行rotateAndCompress()

let promise = new Promise((resolve,reject) => {
  EXIF.getData(file, function() {  
    Orientation = EXIF.getTag(this, 'Orientation');
    resolve(Orientation)
  })
})
promise.then((Orientation) => {
    rotateAndCompress(Orientation)
})
複製程式碼

問題順利解決,繼續心中美滋滋...

4.把canvas畫布轉換成img影象,目前常用的方法有兩種 (1)toDataURL(2)toBolb


showPic.src = canvas.toDataURL(mimeType, qualityArgument)

showPic.src = canvas.toBolb(callback,mineType, qualityArgument)
複製程式碼

區別: (1)toDataURL:是把圖片轉換成base64格式資訊,純字元的圖片表示法。mimeType表示匯出的base64圖片型別預設是png,即'image/png',也可以為 'image/jpeg'或webp等格式。qualityArgument表示匯出圖片的質量,只有匯出圖片為jpg和webp時才有效果,預設是0.92. (2)toBlob:是把canvas轉換成Blob檔案(二進位制檔案),通常用於檔案上傳。 XMLHttpRequest 2.0的家臣們對這些進行了詳細的講解。

function dataURItoBlob (base64Data) {
    //去掉url的頭,並轉換為byte
    let bytes = window.atob(base64Data.split(',')[1])
    // 處理異常,將ascii碼小於0的轉換為大於0
    let ab = new ArrayBuffer(bytes.length)
    // 生成檢視(直接針對記憶體):8位無符號整數,長度1個位元組
    let ia = new Uint8Array(ab)
    for (let i = 0; i < bytes.length; i++) {
        ia[i] = bytes.charCodeAt(i)
    }
    return new Blob([ab], {
        type: 'image/jpg'
    })
}
複製程式碼

5.使用FormaData將生成的blob檔案上傳

利用FormData對像,我們可以通過js來模擬一系列表單控制元件。FormData的最大優點是可以非同步上傳二進位制檔案。

let formData = new FormData()
formData.append('photo', blob, imageName)

$.ajax({
type: 'post',
url: "xxx/file/upload",
data: formData,
processData: false,
traditional: true,
contentType: false,
}).success(function (res) {
    console.log(res);
}).error(function () {
    console.log("upload fail");
})

複製程式碼

注: EXIF獲取圖片旋轉度程式碼

EXIF.getData = function(img, callback) {
    if (((self.Image && img instanceof self.Image) || (self.HTMLImageElement && img instanceof self.HTMLImageElement)) && !img.complete)
    return false
    
    if (!imageHasData(img)) {
        getImageData(img, callback)
    } else {
        if (callback) {
            callback.call(img)
        }
    }
    return true
}
EXIF.getTag = function (img, tag) {
    if (!imageHasData(img)) return
    return img.exifdata[tag]
}

function imageHasData(img) {
    return !!(img.exifdata)
}

function getImageData(img, callback) {
    function handleBinaryFile(binFile) {
        var data = findEXIFinJPEG(binFile)
        img.exifdata = data || {}
        var iptcdata = iptcdata || {}
        if (EXIF.isXmpEnabled) {
            var xmpdata = findXMPinJPEG(bindFile)
            img.xmpdata = xmpdata || {}
        }
        if (callback) {
            callback.call(img)
        }
    }
    
    
}
複製程式碼

相關文章