JavaScript 图片处理

  • 简介


简介

使用javascript封装了 文件读取、base64格式图片数据转换、图片压缩、图片截取等功能

/* 图片压缩处理 */

/**
 * 文件读取
 *  FileReader 的实例拥有 4 个方法,其中 3 个用以读取文件,另一个用来中断读取。下面的表格列出了这些方法以及他们的参数和功能,需要注意的是 ,无论读取成功或失败,方法并不会返回读取结果,这一结果存储在 result属性中。
 * 方法名	            参数	描述
 * abort	            none	            中断读取
 * readAsBinaryString	file	            将文件读取为二进制码
 * readAsDataURL	    file	            将文件读取为 DataURL, 若是图片的话,读取格式为 base64格式
 * readAsText	        file, [encoding]	将文件读取为文本
 *
 *  FileReader 包含了一套完整的事件模型,用于捕获读取文件时的状态。
 * 事件	描述
 * onabort	中断时触发
 * onerror	出错时触发
 * onload	文件读取成功完成时触发
 * onloadend	读取完成触发,无论成功或失败
 * onloadstart	读取开始时触发
 * onprogress	读取中
 *
 * @param file  文件
 * @param type binary、data、text
 * @param encoding 字符编码方式, type = text 时可选
 * @return {Promise<{size, data}>}
 */
function fileReader(file, type, encoding = 'UTF-8') {
    return new Promise((resolve, reject) => {
        if(!window.FileReader) {
            return reject('FileReader is undefined')
        }else if (Object.prototype.toString.call(file) !== '[object File]'){
            return reject('not object File')
        }
        const reader = new FileReader()
        reader.onloadend = (e) => {
            resolve({
                size: e.total,
                data: e.target.result
            })
        }
        reader.onerror = reject
        if (type === 'binary') {
            reader.readAsBinaryString(file)
        } else if (type === 'data') {
            reader.readAsDataURL(file)
        } else if (type === 'text') {
            reader.readAsText(file, encoding)
        } else {
            reject('not support type')
        }
    })
}

/**
 * 读取文件,返回图片对象, 直接 append 到 document
 * @param file 图片文件
 * @return {Promise<Image>}
 */
function toImage(file) {
    return new Promise((resolve, reject) => {
        if (Object.prototype.toString.call(file) !== '[object File]'){
            return reject('not object File')
        }
        const image = new Image()
        const URL = window.webkitURL || window.URL
        if (URL) {
            const url = URL.createObjectURL(file)
            image.onload = () => {
                resolve(image)
                URL.revokeObjectURL(url)
            }
            image.src = url;
            image.onerror = reject
        } else {
            fileReader(file, 'data').then(res => {
                image.src = res.data
                resolve(image)
            }).catch(reject)
        }
    })
}

/**
 *
 * 利用 drawImage() 方法将 Image 对象绘画在 Canvas 对象上。
 *
 * drawImage 有三种语法形式:
 *  void ctx.drawImage(image, dx, dy);
 *  void ctx.drawImage(image, dx, dy, dWidth, dHeight);
 *  void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
 * 参数:
 *  image 绘制到上下文的元素;
 *  sx 绘制选择框左上角以 Image 为基准 X 轴坐标;
 *  sy 绘制选择框左上角以 Image 为基准 Y 轴坐标;
 *  sWidth 绘制选择框宽度;
 *  sHeight 绘制选择框宽度;
 *  dx Image 的左上角在目标 canvas 上 X 轴坐标;
 *  dy Image 的左上角在目标 canvas 上 Y 轴坐标;
 *  dWidth Image 在目标 canvas 上绘制的宽度;
 *  dHeight Image 在目标 canvas 上绘制的高度;
 *
 * @param image
 * @return {Promise<unknown>}
 */
function toCanvas(image) {
    return new Promise((resolve, reject) => {
        if (Object.prototype.toString.call(image) !== '[object HTMLImageElement]'){
            return reject('not Object HTMLImageElement')
        }
       try {
           Object.prototype.toString.call(new Image())
           const canvas = document.createElement('canvas');
           canvas.width = image.naturalWidth;
           canvas.height = image.naturalHeight;
           const ctx = canvas.getContext('2d');
           ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
           resolve(canvas)
       }catch (e){
           reject(e)
       }
    })
}

/**
 * 将画布转换成图片格式
 *
 * @param resolve 数据回调函数
 * @param canvas    画布
 * @param quality   精度 在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92,其他参数会被忽略。
 * @param type      图片类型
 * @return {string}
 */
function toDataUrl(resolve, canvas, quality = 1, type = 'image/jpeg') {
    _checkCbCanvas(resolve, canvas)
    resolve(canvas.toDataURL(type, quality));
}

function _checkCbCanvas(resolve, canvas){
    if (Object.prototype.toString.call(resolve) !== '[object Function]') {
        throw 'resolve not a function'

    } else if(Object.prototype.toString.call(canvas) !== '[object HTMLCanvasElement]') {
        throw 'canvas not object HTMLCanvasElement'
    }
}

/**
 * 转换成二进制对象
 * @param resolve 数据回调函数
 * @param canvas
 * @param quality
 * @param type
 */
function toBlob(resolve, canvas, quality = 1, type = 'image/jpeg') {
    _checkCbCanvas(resolve, canvas)
    canvas.toBlob(resolve, type, quality);
}

/**
 * 压缩图片 - 压缩清晰度,不压缩图片尺寸
 * @param image 压缩的图片
 * @param quality  精度
 * @return {Promise<Base64 image>}
 */
function compressImage(image, quality){
    return compressImageRatio(image, 0, quality)
}

/**
 * 压缩图片 - 指定宽度,等比压缩
 * @param image 图片
 * @param width 指定宽度 指定宽度, 等比压缩。 = 0 时表示不要宿
 * @param quality 精度
 * @param toCb 结果转换函数, 默认转换成 base64
 * @return {Promise<unknown>}
 */
function compressImageRatio(image, width, quality = 0.9, toCb = toDataUrl){
    return new Promise((resolve, reject) => {
        if (Object.prototype.toString.call(image) !== '[object HTMLImageElement]'){
            return reject('not Object HTMLImageElement')
        }
        /* 计算图片宽高 */
        let { naturalWidth, naturalHeight } = image
        if (width && width > 0 && width < naturalWidth){
            naturalHeight *= (width / naturalWidth)
            /* 等比计算 */
            naturalWidth = width
        }
        /* 创建画布 */
        const canvas = document.createElement('canvas')
        const context = canvas.getContext('2d')
        /* 设置画布宽高*/
        canvas.width = naturalWidth
        canvas.height = naturalHeight
        /* 图片绘制到画布 */
        context.drawImage(image, 0, 0, canvas.width, canvas.height)
        toCb(resolve, canvas, quality, image.type)
    })
}

/**
 * 图片裁剪
 * @param image 图片
 * @param imgX  裁剪开始 x 坐标
 * @param imgY  裁剪开始 Y 坐标
 * @param width 图标图片宽度
 * @param height 图标图片高度
 * @param toCb   数据格式转换函数
 * @return {Promise<unknown>}
 */
function cutImg(image, imgX, imgY, width, height, toCb = toDataUrl){
    return new Promise((resolve, reject) => {
        if (Object.prototype.toString.call(image) !== '[object HTMLImageElement]'){
            return reject('not Object HTMLImageElement')
        }
        let { naturalWidth, naturalHeight } = image
        width = Math.min(naturalWidth - imgX, width)
        height = Math.min(naturalHeight - imgY, height)
        if (imgX > naturalWidth || imgY > naturalHeight){
            return reject('x or y out of range')
        }
        /* 创建画布 */
        const canvas = document.createElement('canvas')
        const context = canvas.getContext('2d')
        /* 设置画布宽高*/
        canvas.width = width
        canvas.height = height
        const zero = 0
        /* 图片绘制到画布 */
        context.drawImage(image
            /* 图片的xy坐标, 宽高 */
            , imgX, imgY,  width, height
            /* 画布的xy坐标, 宽高 */
            , zero, zero, canvas.width, canvas.height)
        /* void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); */
        toCb(resolve, canvas, 1, image.type)
    })
}

/**
 * 将file格式的图片压缩,然后转换成file对象
 * @param file 文件
 * @param width 指定宽度, 等比压缩。 = 0 时表示不要宿
 * @param quality 精度 默认 0.9
 * @return {Promise<File>}
 */
function imgTo(file, width, quality){
    return new Promise((resolve, reject) => {
        toImage(file).catch(reject).then(img => {
            compressImageRatio(img, width, quality, toBlob).catch(reject).then(data => {
                let hiFile = new File([ data ], file.name, { type: data.type })
                resolve(hiFile)
            })
        })
    })
}

/**
 * 图片压缩成 icon
 * @param file
 * @return {Promise<File>}
 */
function toIconSize(file){
   return imgTo(file, 150, 0.8)
}

/**
 * 图片压缩成 icon
 * @param file
 * @return {Promise<File>}
 */
function toHeadSize(file){
   return imgTo(file, 400, 0.8)
}


export {
      fileReader
    , toImage
    , toCanvas
    , toDataUrl
    , toBlob
    , compressImage
    , compressImageRatio
    , cutImg
    , imgTo
    , toHeadSize
    , toIconSize
}