栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > Web开发 > Html/CSS > CSS教程

千万级流量 H5 应用涉及到图片处理技能点

CSS教程 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

千万级流量 H5 应用涉及到图片处理技能点

最近开了一节课,《支持10万人同时在线 Go语言打造高并发web即时聊天(IM)应用》

课程播出后,很多读者问到图片处理相关的东西,如怎么进行前端压缩,和异步上传等,有鉴于此,笔者系统性地整理了图片处理相关技术细节。老规矩,文章末尾给有源代码地址。


从 2017 年开始,我们持续为某企业支撑多场 HTML5 晒单赢红包活动,活动规则(套路)如下:

  1. 用户在商超里面购买产品 P,获得小票 T;

  2. 用户关注公众号,从菜单进入小票上传页面内,用手机拍摄小票,并上传;

  3. 上传成功后,后端通过图像识别,分辨小票内容是否包含产品 P,借此判断用户是否有抽奖的机会;

  4. 小票上传成功后,将在列表页面 L 中按照上传先后顺序显示。

整个活动持续运维 2 年多,整个过程遇到了各种奇葩问题,举例如下:

  1. 因为有些手机像素太高,拍照图片达到 2M 左右,上传太慢,上传出错;

  2. 部分手机上传后相片旋转了 90 度;

  3. 列表页面加载速度越来越慢,甚至卡顿;

  4. 图片铺满了硬盘空间,导致应用日志写入失败,系统报错;

  5. 用户反应页面打开慢,白屏;

  6. 多用户同时上传图片,有用户上传失败;

  7. 有用户反应手机无牌照弹窗;

  8. 经过分析反应页面打开慢的大部分是北方用户。

  9. ...... 本场 chat 的目的也就在此,希望从如何解决这些问题出发,举一反三,触类旁通,最后达到系统化地梳理图片应用常用方法和技巧的目的。

图片异步上传2.1 异步上传的好处

异步上传图片的好处显而易见。首先用户能获得更好的用户体验,异步上传页面不需要要刷新,在上传过程中,应用可以通过进度条,loading 等效果,达到友好提醒的目的。其次,能将图片业务抽象,业务逻辑和图像上传解耦合。

2.2 用插件实现前端异步上传

常用插件 uploadify、webuploader、ajaxfileupload.js、jquery.form.js 等,这里不做重点讨论。

2.3 用 JS+H5 实现图片上传2.3.1 Javascript 代码段
//异步上传核心函数//filedom 通过dom query方法返回的dom如document.getElementByID("test")//onsuccess上传成功回//onerror上传失败回调function uploadfile(filedom,
onsuccess,
onerror,
onprogress ){//使用H5的formdata进行上传//formdata 是H5新增加内容var formdata = new FormData();  
formdata.append(filedom.name,filedom.files[0]);//还可以同时上传参数formdata.append("clientID","客户端的唯一标识"); //在AndroID/IOS 系统中webview都支持XMLHttpRequest//无需考虑IEvar xhr= new XMLHttpRequest();

xhr.upload.onprogress=console.log;//如果定义了进度函数if(typeof onprogress=="function"){
    xhr.upload.onprogress = onprogress;
}//如果定义了上传成功回调if(typeof onsuccess!="function"){
    onsuccess = console.log;
}//如果定义了上传失败回调if(typeof onerror!="function"){
    onerror = console.log;
}//第二个参数是后端服务地址,将做重点说明xhr.open("POST", "/attach/upload");//ajax发送数据xhr.send(formdata);//时间成功回调xhr.onreadystatechange = function(){  //OnReadyStateChange事件
        if(xhr.readyState == 4){  //4为完成
            if(xhr.status == 200){    //200为成功
                onsuccess(JSON.parse(xhr.responseText)) 
            }else{
                onerror(xhr) 
            }
        }
    };
}//上传成功后回调处理function     ajaxuploadsuccess(res){}//上传失败回调处理function     ajaxuploaderror(res){}//上传失败回调处理function      ajaxuploadprogress(ev) {                if(ev.lengthComputable) {                    var percent = 100 * ev.loaded/ev.total;                    console.log({"文件总大小":ev.total,"已上传":ev.loaded,"上传进度":percent+"%"});
                }
            }
2.3.2 Html 代码段
图片压缩3.1 为什么要对图片进行处理3.1.1 可以快速上传增强用户体验

目前手机质量越来越好,相机拍照质量也越来越高,导致直接后果是手机端图片变大,随便一张图 2-3M,那么如果上传这样的图片,用户所需时间将会增加,以 1 秒钟 300kb 计算,一张图所需时间越为 10 秒钟,用户耗费大量的时间在等待当中,以至于失去耐性,这对用户是极大的伤害。如果对图片进行压缩,比如压缩至 300kb,用户所需时间将缩短为 1 秒,可以显著提高用户体验。

3.1.2 可以降低流量

主要有俩个方面,一方面是用户上传的流量,1 张 3M 的和一张 300kb 的,显而易见前者消耗的流量大得多。另一方面是降低用户浏览图片所耗费的流量。

3.1.3 可以降低存储所用空间

经过压缩处理的图片,存储空间大大降低。

3.1.4 可以让应用更流畅

如果我们未对图片的显示效果做处理,当每个图片较大时,浏览器将超负荷渲染图片,该行为直接导致页面加载速度变慢,刷新响应变慢,甚至出现卡顿现象。对图片进行压缩处理后,应用将反应快,更加流畅

3.2 实现前端压缩3.2.1 关于 Html5 操作文件的基本知识3.2.1.1 关于文件对象 File

如下是 console.log 打印出来的 file 对象内容。file 对象描述了文件的基本信息,但是没有文件内容。通过 input 标签可以获得 files 数组,遍历该数组可以获得具体每一个 file 对象。

var files = document.getElementByID("filedomID").files;for(var file in files){    console.log(file);
}

在 Chrome 引擎浏览器中效果

{
lastModified:1511236246031,//最近一次更新时间戳lastModifiedDate:"Tue Nov 21 2017 11:50:46 GMT+0800 (中国标准时间) ",//最近一次更新时间name:"0.首页 – 副本.png",//文件名称size:2552293,//文件大小,单位Bytetype:"image/png",//文件类型webkitRelativePath:""//input上加上webkitdirectory属性时,用户可选择文件夹,此时weblitRelativePath表示文件夹中文件的相对路径}

Firefox下对象信息如下,缺少了 lastModifiedDate 信息

{
lastModified:1511236246031,//最近一次更新时间戳name:"0.首页 – 副本.png",//文件名称size:2552293,//文件大小,单位Bytetype:"image/png",//文件类型webkitRelativePath:""}

IE 下打印信息如下

{constructor: File {...}, lastModifiedDate: "Tue Nov 21 2017 11:50:46 GMT+0800 (中国标准时间) ",//最近一次更新name: "0.首页 – 副本.png", size: 75605, type: "image/jpeg"}

可见无论何种浏览器,size、name、type 属性都会存在。我们将利用 file 的 size 属性做大小判断,name 属性做类型校验。

3.2.1.2 FileReader 对象简介

FileReader 对象提供了操作文件内容的接口

方法名称描述
readAsArrayBuffer(file)按字节读取文件内容,结果用ArrayBuffer对象表示
readAsBinaryString(file)按字节读取文件内容,结果为文件的二进制串
readAsDataURL(file)读取文件内容,结果用data:url的字符串形式表示
readAsText(file,encoding)按字符读取文件内容,结果用encoding编码的字符串形式表示
abort()终止文件读取操作

其中,readAsDataURL 方法可以将文件内容编码成 base64 格式,这点很重要,这意味着我们可以用 base64 编码统一 Canvas 图片压缩接口。

另外,FileReader提供了如下事件机制:

方法名称描述
onabort当读取操作被中止时调用
onerror当读取操作发生错误时调用
onload当读取操作成功完成时调用,一般使用用该方法进行回调
onloadend当读取操作完成时调用,无论成功或失败
onloadstart当读取操作开始时调用
onprogress在读取数据过程中周期性调用,进度回调
我们可以利用onload事件来处理文件内容。onprogress处理进度属性。
回调函数原型如下:
function(e){}

e 参数格式如下,我们可以通过 e.target.result 获得文件内容,如图所示,这是一连串 base64 格式字符串。

3.2.1.3 获取文件内容

由以上可以获得读取文件内容的一般函数:

//file:这是一个文件对象
            //onload :加载成功回调
            //onerror 加载失败回调
            //onprogress :加载进度回调function filetobase64withfilereader(file,onload,onerror,onprogress){//创建对象
  var reader = new FileReader();  //发起请求
  reader.readAsDataURL(file);//发起异步请求
  //配置回调函数
  reader.onload=function(ev){      if(!!onload){
      onload({"code":200,"data":ev.target.result,"msg":""})
      }
  }  if(typeof onerror=="function"){
    reader.onerror = function(ev){
        onerror({"code":400,"data":ev,"msg":"加载文件出错"})
    };  
  }else{
      reader.onerror = console.log;
  }  if(typeof onprogress=="function"){
    reader.onprogress = onprogress;  
  }else{
      reader.onprogress = console.log;
  }
}
3.2.2 利用 Canvas 对图进行压缩3.2.2.1 Canvas 实现图片压缩的原理

Canvas.toDataURL(type, encoderOptions);

利用该方法可以返回 dataUrl 数据,这是一连串经过 base64 编码后的图片内容,这些内容在大部分浏览器中都能直接显示。函数参数说明如下:

  • type 可选图片格式,默认为 image/png,jpg 为 image/jpeg。

  • encoderOptions 可选在指定图片格式为 image/jpeg 或 image/webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他参数会被忽略。

  • 返回值:类似 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby// blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC,其中 image/png 表示 png 图片类型,base64 为固定参数,标识这是 base64 编码。上面所示字符串是一个白色图标。

3.2.2.2 实现 Canvas 压缩图片函数

综上所述,可以直接上代码了:

//压缩成功回调function oncompress(res){ console.log(res)                 document.getElementByID("testimg").src=res.data;
            }//报错回调function onerror(res) {                    console.log("onerror",res)
            }//这个函数的核心思路//首席按调用filereader对象获得文件内容base64格式//然后判断如果不需要压缩就返回base64//否则调研Canvas绘制图片,最后将Canvas上的内容导出为dataUrl格式,即base64格式//所有数据都通过回调函数传递function filetobase64withfilereader(file,onload,onerror,onprogress) {    var reader = new FileReader();    //发起请求
    reader.readAsDataURL(file);//发起异步请求
    //配置回调函数
    if (typeof onload == "function") {
        reader.onload = onload;
    } else {
        reader.onload = console.log;
    }    if (typeof onerror == "function") {
        reader.onerror = onerror;
    } else {
        reader.onerror = console.log;
    }    if (typeof onprogress == "function") {
        reader.onprogress = onprogress;
    } else {
        reader.onprogress = console.log;
    }
}//图片压缩后的最大宽度var CompressMaxWIDth = 400;//图片压缩后的最大高度var CompressMaxHeight = 400;//图片压缩后获取图片质量var CompressQuality=0.92;//如下函数核心逻辑如下//通过先用filereader对象加载file,获得文件内容//在filereader加载完成后的回调函数onload里面//可以将内容填充到一个Image对象中,//在Image加载完成时onload回调函数中,//调用Canvas,的draw方法,将图片内容加载到Canvas上,//同时,可以通过设置Canvas画布大小,实现图片缩放,//最后利用Canvas的toDataUrl方法,获得画布上的图片内容function filetobase64withCanvas(file,onsuccess,onerror){//这个方法只支持image方法console.log(file.type.indexOf("image/"));if(!file || file.type.indexOf("image/")==-1){
    onerror({"code":400,"msg":"不支持改格式"})    return ;
}var reader = new FileReader();//加载图片文件到base64 编码,image可以直接加载啦reader.readAsDataURL(file);//发起异步请求var image = new Image();
reader.onload = function(ev){
image.src = ev.target.result;
};// 缩放图片需要的Canvasreader.onerror = function(ev){
    onerror({"code":400,"data":ev,"msg":"读取文件出错"});
}
image.onerror = function(ev){
    onerror({"code":400,"data":ev,"msg":"展示图片出错"});
}var Canvas=document.createElement("Canvas");var context=Canvas.getContext("2d");//定义image 事件//base64地址图片加载完毕后image.onload=function () {        console.log("image",this);// 图片原始尺寸var originWIDth=this.wIDth;var originHeight=this.height;//期待的目标尺寸var targetWIDth=originWIDth, targetHeight=originHeight;//如果原始尺寸小于压缩的尺寸,说明是放大,不在我们这里处理的范围内。//如果原始尺寸大于压缩的尺寸,说明我们需要压缩。var needcompress =originWIDth>CompressMaxWIDth ||
originHeight>CompressMaxHeight;if(!needcompress){
    onsuccess({        "code":200,        "data":this.src,        "msg":"加载成功"
    })
} else{var orate =originWIDth/originHeight;//假设目标宽度高度都是最大值//则目标尺寸的比列如下var drate = CompressMaxWIDth/CompressMaxHeight;var k = orate/drate;//要想得到等比缩放的压缩效果,//如果k=orate/drate=1说明是等比缩放了不要处理//如果k=orate/drate>1说明当前设置的目标宽高比相比理想比例偏小,//要么增加宽度,要么降低高度,显然不能增加宽度,因此只能降低高度了//如果orate/drate<1说明当前设置的目标宽高比比相比理想比例偏大,//要么降低宽度,要么增加高度,显然不能增加高度,因此只能降低宽度了if (k>1){
     targetWIDth = CompressMaxWIDth;
      targetHeight= Math.round(CompressMaxHeight/k);
} else {
    targetHeight=CompressMaxHeight;
    targetWIDth=Math.round(CompressMaxWIDth* k);
}//Canvas对图片进行缩放Canvas.wIDth=targetWIDth;
Canvas.height=targetHeight;// 清除画布context.clearRect(0, 0, targetWIDth, targetHeight);// 图片压缩
 context.drawImage(image, 0, 0, targetWIDth, targetHeight);// Canvas压缩并上传var dataURL=Canvas.toDataURL(file.type,CompressQuality);//成功回调onsuccess({"code":200,"data":dataURL,"msg":""})
    }
   }
}

测试 Html 代码如下

     }

效果展示如下为压缩后的图片,该图模糊不清。大小 15kb 。

 如下为未压缩的图片,显然该图清晰可见。大小 128kb 。 

3.2.2.3 并非所有图片都适合用 Canvas 方式进行压缩

如下几个细节需要澄清:

  • size 小于 100kb 的图片不适合用 Canvas 进行压缩,经过该方法处理后存储得到的图片将大于 100kb。

  • Canvas 压缩会导致图片失真。如果我们的图片是票据等,不适合用改方法进行压缩。

  • 对 Canvas 方法进行处理图片时,我们应该先行判断,该图片是否适合压缩。

使用 Canvas 进行压缩,伪代码如下:

function  compress(filedom,onsuccess,onerror){    //定义变量
    var file = filedom.files[0];    if(file.size<1024*100){    //使用filereader将文件转成base64,然后传入onsuccess
     filetobase64withfilereader(file,
     onsuccess,
     onerror);
    }else{    //使用Canvas 将文件内容转成base64字符串,然后传入onsuccess
    filetobase64withCanvas(file,onsuccess,onerror);
}
}
3.2.2.4 上传 base64 格式数据注意事项

通过以上我们得到了 base64 格式内容,接下来要做的就是将该内容上传到后端,但是因为 Canvas 或者 FileReader 得到的 base64 格式字符串中存在特殊字符,因此上传前需要做相应转换,否则将会导致后端保存的图片报错。解决这些问题的方法很多,这里推荐一种方法,就是采用 encodeURIComponent(data) 函数对 base64 字符串内容进行预处理。后端接收到后通过类似 urIDecode 的方法进行解密,最后保存为图片文件。 简单代码如下,本代码采用 x-www-form-urlencoded 格式发送,后端 ContentType 请使用相应的方式。

function uploadbase64(url,base64data,onsuccess,onerror){var xhr= new XMLHttpRequest();var data ={}//base64编码预处理data.base64data=base64data;//如果定义了上传成功回调if(typeof onsuccess!="function"){
    onsuccess = console.log;
}//如果定义了上传失败回调if(typeof onerror!="function"){
    onerror = console.log;
}//第二个参数是后端服务地址,将做重点说明xhr.open("POST", url);//ajax发送数据var postdata=[];for(var i in data){
    postdata.push(i+"="+encodeURIComponent(data[i]));
}
postdata.join("&");//注意下面该函数的位置,在xhr.open之后才起作用。xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(postdata.join("&"));//时间成功回调xhr.onreadystatechange = function(){  //OnReadyStateChange事件
        if(xhr.readyState == 4){  //4为完成
            if(xhr.status == 200){    //200为成功
                onsuccess(JSON.parse(xhr.responseText)) 
            }else{
                onerror(xhr) 
            }
        }
    };
}
3.3 在后端代码层实现压缩

正如以上所述,前端压缩虽然能实现压缩,但是导致像素失真,很多关键信息都丢失了。当时我们采用前端压缩,小票信息失真,导致图像识别准确率大大降低。有鉴于此,我们将目光转向了后端压缩技术。

3.3.1 Java 实现后端压缩

Java 后端压缩方法很多, Graphics 类的 drawImage 方法可以实现压缩,也有采用开源包的。这里采用开源包 net.coobird.thumbnailator。核心代码如下:

Thumbnails.
of(sourcefilepath).
scale(rate).
outputQuality(quality).
toFile(destfilepath);

相关参数说明如下:

  • sourcefilepath 源文件图像地址。

  • destfilepath 缩略图地址。

  • rate 图片压缩比列。

  • quality 输出图片质量。

使用该包需要引入依赖项,以 Maven 为例:


      net.coobird
      thumbnailator
      0.4.8

Java测试代码如下

//app.javapackage com.imwinlion.thumb;import java.io.IOException;import net.coobird.thumbnailator.Thumbnails;public class ThumbApplication {    public static voID main(String[] args) throws IOException{

        System.out.println(args.length);        if(args.length!=2){
            System.out.println("jave -jar app.jar src.jpg dst.jsp");
        }else{
            Thumbnails.of(args[0]).scale(0.5f).outputQuality(0.6).toFile(args[1]);
        }
}

命令行参数

其中,rate=0.5、quality=0.6,裁剪后的效果如下:

对比前端上传,显然效果清晰。

3.3.2 Golang 实现后端压缩

Golang 后端压缩需要引入第三方包如下

import (
    "image"
    "os"
    "image/gif"
    "errors"
    "image/jpeg"
    "image/png"
    "github.com/nfnt/resize"
)

其中 github.com/nfnt/resize 是一个第三方包,里面封装了大部分常用的图片操作工具类。

Golang 实现后端压缩核心代码如下

//srcpath:源文件路径,
// dstpath:缩略图路径
//dstMaxW:缩略图最大宽度
//dstMaxH:缩略图最大高度
func thumb(srcpath,dstpath string,dstMaxW,dstMaxH int)(err error) {
    //打开源图
    file, err := os.Open(srcpath)
    defer file.Close()
    if err!=nil{
        return
    }
    //获得图片对象,以及图片格式等
    origin, fmtimg, err := image.Decode(file)
    if err!=nil{
        return
    }
    bounds := origin.Bounds()
    //原始图片宽度
    srcw := bounds.Max.X
    //原始图片高度
    srch := bounds.Max.Y

    //得到原始宽高比和给定参数的宽高比
    k := (srcw / srch) / (dstMaxW / dstMaxH)
    targetW := dstMaxW
    targetH := dstMaxH
    //如果k>1 说明 dstMaxW/dstMaxH 偏小
    //那么 dstmaxh应该缩小(dstMaxw不能增大了)
    //如果k<1 说明 dstMaxW/dstMaxH 偏大
    //那么 dstMaxW 应该缩小(dstMaxH不能增大了)
    if (k > 1) {
        targetH = dstMaxH / k
    } else {
        targetW = dstMaxW / k
    }

    //然后采用图片压缩
    out, _ := os.Create(dstpath)
    defer out.Close()
    rect := image.Rect(0,0, targetW, targetH)
    switch fmtimg {
    case "jpeg": //jpg 格式
        img := origin.(*image.YCbCr)
        subImg := img.SubImage(rect).(*image.YCbCr)
        return jpeg.Encode(out, subImg, &jpeg.Options{Quality:100*targetW/srcw,})
    case "png": //png 格式
        Canvas := resize.Thumbnail(uint(targetW), uint(targetH), origin, resize.Lanczos3)
        switch Canvas.(type) {
        case *image.NRGBA:
            img := Canvas.(*image.NRGBA)
            subImg := img.SubImage(rect).(*image.NRGBA)
            return png.Encode(out, subImg)
        case *image.RGBA:
            img := Canvas.(*image.RGBA)
            subImg := img.SubImage(rect).(*image.RGBA)
            return png.Encode(out, subImg)
        }
    case "gif": //gif 格式
        img := origin.(*image.Paletted)
        subImg := img.SubImage(rect).(*image.Paletted)
        return gif.Encode(out, subImg, &gif.Options{})
    //用户可以添加bmp格式支持,需要安装golang.org/x/image/bmp
    
    default:
        return errors.New("ERROR FORMAT")
    }
    return nil
}

检验代码如下

func  main()  {
    thumb("src.png","dst.png",930,500)
}

压缩得到的效果图,和 Java 效果一致。 需要注意的是,如果需要添加 BMP 支持,需要安装 BMP 操作类。 golang.org/x/image/bmp,因为某些原因,该包不能正常安装,解决方法是在 gopath/src 目录下 新建文件夹 golang.orgx 如下所示  然后执行 clone 操作

>mkdir -p golang.orgx>cd golang.orgx>git clone https://github.com/golang/image.git

事实上很多扩展包都可以通过此方法安装,如 protobuf

>git clone https://github.com/golang/net.git>git clone https://github.com/golang/text.git>git clone https://github.com/golang/protobuf.git
3.3.3 使用 PHP 进行压缩

PHP 图片压缩使用的核心函数

imagecreatefromxxx($filepath)

表示创建一块画布,并从$filepath路径处,载入一副 xxx 类型的图像。常用的文件处理函数如下

imagecreatefromjpeg(filepath)
imagecreatefrompng(filepath)
imagecreatefromwbmp(filepath)
imagecreatefromgif(filepath)

PHP 图片压缩的另俩个核心函数 imagecopyresized 和 imagecopyresampled ,这俩个函数都是用来缩放的,但是都有缺陷,imagecopyresampled 得到的图片偏大,imagecopyresized 得到的图片质量较差。

bool imagecopyresampled ( 
resource $dst_image , 
resource $src_image , 
int $dst_x , 
int $dst_y , 
int $src_x , 
int $src_y , 
int $dst_w , 
int $dst_h ,int $src_w , 
int $src_h 
)
bool imagecopyresized ( 
resource $dst_image , 
resource $src_image , 
int $dst_x , 
int $dst_y , 
int $src_x , 
int $src_y , 
int $dst_w , 
int $dst_h ,int $src_w , 
int $src_h 
)

参数说明如下:

$dst_image:新建的图片。$src_image:需要载入的图片。$dst_x:设定需要载入的图片在新图中的x坐标。$dst_y:设定需要载入的图片在新图中的y坐标。$src_x:设定载入图片要载入的区域x坐标。$src_y:设定载入图片要载入的区域y坐标。$dst_w:设定载入的原图的宽度(在此设置缩放)。$dst_h:设定载入的原图的高度(在此设置缩放)。$src_w:原图要载入的宽度。$src_h:原图要载入的高度

PHP 压缩文件一般流程如下

$filename="src.jpg";//创建一幅图片对象$src_image=imagecreatefromjpeg($filename);//获得图片的原始宽度和高度list($src_w,$src_h)=getimagesize($filename);//设置缩放比例$scale=0.5;//获得压缩后的图片宽度和高度,这个也可以借鉴golang例子自动计算获得。$dst_w=ceil($src_w*$scale);
$dst_h=ceil($src_h*$scale);//创建一块画布$dst_image=imagecreatetruecolor($dst_w, $dst_h);//把源图片画到新创建的画布上imagecopyresampled($dst_image, $src_image, 0, 0, 0, 0, $dst_w, $dst_h, $src_w, $src_h);//设置输出格式header("content-type:image/jpeg");//输出图片imagejpeg($dst_image);//释放内存空间imagedestroy($src_image);
imagedestroy($dst_image);

我们封装函数如下:

//$srcpath:源文件路径,//$dstpath:缩略图路径//$dstMaxW:缩略图最大宽度//$dstMaxH:缩略图最大高度//$method:默认的图片压缩方式.imagecopyresampledfunction thumb($srcpath,$dstpath,$dstMaxW,$dstMaxH,$method="sampled"){    
    list($srcW, $srcH, $type, $attr) = getimagesize($srcpath);
    $imageinfo = array(                     'wIDth'=>$srcW,                     'height'=>$srcH,                     'type'=>image_type_to_extension($type,false),                     'attr'=>$attr
              );
    $imagetype =        $imageinfo['type']; 
    //获得处理函数
    $fun = "imagecreatefrom".$imagetype;    //获得原始图片
    $origin = $fun($srcpath);    //得到原始宽高比和给定参数的宽高比
    $k = ($srcW / $srcH) / ($dstMaxW / $dstMaxH);
    $targetW = $dstMaxW;
    $targetH =  $dstMaxH;    //如果k>1 说明 dstMaxW/dstMaxH 偏小
    //那么 dstmaxh应该缩小(dstMaxw不能增大了)
    //如果k<1 说明 dstMaxW/dstMaxH 偏大
    //那么 dstMaxW 应该缩小(dstMaxH不能增大了)
    if ($k > 1) {
        $targetH = $dstMaxH /$k;
    } else {
        $targetW = $dstMaxW /$k;
    }
    $thump = imagecreatetruecolor($targetW,$targetH);              //将原图复制带图片载体上面,并且按照一定比例压缩,极大的保持了清晰度
    $copyfun =  "imagecopyresampled";    if($method=="resized"){
        $copyfun = "imagecopyresized";
    }
    $copyfun($thump,$origin,0,0,0,0,$targetW,$targetH,$srcW,$srcH);

    $funcs = "image".$imagetype;
    $funcs($thump,$dstpath);
    imagedestroy($origin);
    imagedestroy($thump);
}    
//测试代码如下thumb("./src.png","resized.png",930,500,"resized");
thumb("./src.png","resampled.png",930,500,"resampled");

如下是我我们用 imagecopyresized 得到的效果图,大小 38kb,质量非常模糊。 如下使我们用 imagecopyresampled 得到的效果图,大小为 178kb,原图大小 128kb。 

显然 imagecopyresampled 质量优于 imagecopyresized。 需要注意的是,PHP 图像处理需要开启 GD 库支持。具体操作,如下:

打开 PHP.ini 文件中可以加载 GD 库,可以在 PHP.ini 文件中到如下扩展,

;extension=PHP_gd2.dll

将选项前的分号删除,保存,再重启 Apache 服务器即可。

上面所述都是利用应用层代码实现压缩,实际上我们可以在服务器层面进行压缩,比如 Nginx 服务器,可以扩展图片压缩模块。

3.4 自建图片服务器进行压缩

Nginx 服务器可以扩展图片处理模块,它和需要缩略图机制的应用场景非常契合。该服务器一般与缓存配合使用。

3.4.1 安装模块

编译前请确认您的系统已经安装了libcurl-dev libgd2-dev libpcre-dev 依赖库

#Debian / Ubuntu 系统举例# 如果你没有安装GCC相关环境才需要执行$ sudo apt-get install build-essential m4 autoconf automake make 
$ sudo apt-get install libgd2-noxpm-dev libcurl4-openssl-dev libpcre3-dev#CentOS /RedHat / Fedora举例# 请确保已经安装了gcc automake autoconf m4 #$ sudo yum install gd-devel pcre-devel libcurl-devel 支持Nginx和Tengine,两者选其一# 下载Nginx$ wget http://nginx.org/download/nginx-1.4.0.tar.gz#解压$ tar -zxvf nginx-1.4.0.tar.gz
$ cd nginx-1.4.0#下载 图片压缩模块$ wget https://github.com/oupula/ngx_image_thumb/archive/master.zip#解压$ unzip master.zip#配置$ ./configure --add-module=./nginx-image-master#编译$ make#安装$ sudo make install
3.4.2 设置配置文件

打开 Nginx 配置文件nginx.conf

vim /etc/nginx/nginx.conf

不同的系统该文件路径不一样,请按照自己的系统为准。

location / {   root html;   #添加以下配置
   image on;   image_output on;
}

或者指定目录开启

location /mnt {   root html; 
   image on;   image_output on;
}
3.4.3 参数说明

image on/off:是否开启缩略图功能,默认关闭。 imagebackend on/off:是否开启镜像服务,当开启该功能时,请求目录不存在的图片(判断原图),将自动从镜像服务器地址下载原图。 imagebackendserver:镜像服务器地址。 imageoutput on/off:是否不生成图片而直接处理后输出,默认 off。 imagejpegquality 75:生成 JPEG 图片的质量默认值 75。 imagewater on/off:是否开启水印功能。 imagewatertype 0/1:水印类型 0,图片水印 1,文字水印。 imagewatermin 300 300:图片宽度 300 高度 300 的情况才添加水印。 imagewaterpos 0-9:水印位置默认值9,0为随机位置,1为顶端居左,2为顶端居中,3为顶端居右,4为中部居左,5为中部居中,6为中部居右,7为底端居左,8为底端居中,9为底端居右。 imagewaterfile: 水印文件(jpg/png/gif),绝对路径或者相对路径的水印图片。 imagewatertransparent: 水印透明度,默认 20,越小越透明,0最透明 。 imagewatertext:水印文字 "Power By Vampire"。 imagewaterfontsize:水印大小 默认 5 。 imagewaterfont: 文字水印字体文件路径。 imagewatercolor: 水印文字颜色,默认 #000000 。

3.4.4 调用说明

这里假设你的 Nginx 访问地址为 http://localhost/,并在 Nginx 网站根目录存在一个 test.jpg 的图片。通过访问http://localhost/test.jpg!c300x200.jpg,将会 生成或输出一个 300x200 的缩略图。其中 300 是生成缩略图的宽度,200 是生成缩略图的 高度。一共可以生成四种不同类型的缩略图。支持 jpeg/png/gif (Gif生成后变成静态图片)。

  • C 参数按请求宽高比例从图片高度 10% 处开始截取图片,然后缩放/放大到指定尺寸( 图片缩略图大小等于请求的宽高 )。

  • M 参数按请求宽高比例居中截图图片,然后缩放/放大到指定尺寸( 图片缩略图大小等于请求的宽高 )。

  • T 参数按请求宽高比例按比例缩放/放大到指定尺寸( 图片缩略图大小可能小于请求的宽高 )。

  • W 参数按请求宽高比例缩放/放大到指定尺寸,空白处填充白色背景颜色( 图片缩略图大小等于请求的宽高 )。

3.4.5 调用举例

正如前面所说,调用图片将采用如下所示格式:

http://oopul.vicp.net/12.jpg!c300x300.jpghttp://oopul.vicp.net/12.jpg!t300x300.jpghttp://oopul.vicp.net/12.jpg!m300x300.jpg
3.5 使用云服务进行压缩

提供图片服务的云平台有很多,这里以阿里云 OSS 为例。

3.5.1 关于存储

OSS 提供海量、安全、低成本、高可靠的云存储服务,提供 99.999999999% 的数据可靠性。使用 RESTful API 可以在互联网任何位置存储和访问,容量和处理能力弹性扩展,多种存储类型供选择全面优化存储成本。

3.5.2 关于缩略图

阿里云可以配置图片处理样式,在 OSS 后台 > 相应 Bucket > 图片处理 下新建样式如 thumb256 : 是要使用图片时,只需要按照如下格式即可使用。

域名/sample.jpg?x-oss-process=style/stylename
或者
域名/example.jpg@!panda_style

举个栗子:

http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=style/panda_style或者
http://image-demo.oss-cn-hangzhou.aliyuncs.com/example.jpg@!panda_style
3.6 关于图片旋转问题

在使用上传过程中,发现部分三星手机以及一部分苹果手机出现图片上传后旋转了 90 度的情况,解决思路如下:

Step1: 获得图片旋转角度 a,将 a 传递到后端,代码如下:

//引进Exif.js这个js能得到图片的一些旋转信息