JavaScript实现手机拍摄图片的旋转、压缩

1,497 阅读4分钟

现在的手机拍摄的照片大小基本都在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)
        }
    }
    
    
}