• 如何用CropBox实现头像裁剪并与java后台交互


    如何用CropBox实现头像裁剪并与java后台交互

    参考网站:https://developer.mozilla.org/zh-CN/docs/Web/API/Blob
    参考:
    http://blog.csdn.net/u013160024/article/details/51849732
    http://www.cnblogs.com/shinefon-2-2/p/5901330.html
    http://www.cnblogs.com/hhhyaaon/p/5928152.html

    主流的前端jQuery 图像裁剪插件有JcropCropBox,前者是将原图和需要裁剪的参数(裁剪的各点坐标,旋转角度等)传到后台,然后由后台完成实际的裁剪和后续操作。
    CropBox实现功能相对较少,但操作更简单,它的原理是:
    将裁减后的图片通过base64编码,然后转化为blob格式发送到服务器,服务器完成解码即可,官网介绍可以看github上的说明和Demo
    核心js函数只有两个:
    getDataURL 将裁剪后的图片简单以base64编码后的结果,用于实时预览,当然也可以将它直接传到服务器,然后解码为png格式
    getBlob 上传图片为Blob格式

    首先贴出两个函数的源码:

     getDataURL: function ()
                    {
                        var width = this.thumbBox.width(),
                            height = this.thumbBox.height(),
                            canvas = document.createElement("canvas"),
                            dim = el.css('background-position').split(' '),
                            size = el.css('background-size').split(' '),
                            dx = parseInt(dim[0]) - el.width()/2 + width/2,
                            dy = parseInt(dim[1]) - el.height()/2 + height/2,
                            dw = parseInt(size[0]),
                            dh = parseInt(size[1]),
                            sh = parseInt(this.image.height),
                            sw = parseInt(this.image.width);
    
                        canvas.width = width;
                        canvas.height = height;
                        var context = canvas.getContext("2d");
                        context.drawImage(this.image, 0, 0, sw, sh, dx, dy, dw, dh);
                        var imageData = canvas.toDataURL('image/png');
                        return imageData;
                    },
                    getBlob: function()
                    {
                        var imageData = this.getDataURL();
                        var b64 = imageData.replace('data:image/png;base64,','');
                        var binary = atob(b64);
                        var array = [];
                        for (var i = 0; i < binary.length; i++) {
                            array.push(binary.charCodeAt(i));
                        }
                        return  new Blob([new Uint8Array(array)], {type: 'image/png'});
                    },
    

    1. Data URIs方式

    主要利用了HTMLCanvasElement.toDataURL()方法,HTMLCanvasElement.toDataURL() 方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。
    语法:

    canvas.toDataURL(type, encoderOptions);
    

    参数:

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

    返回值:
    包含 data URI 的DOMString。

    比如:

    <canvas id="canvas" width="5" height="5"></canvas>
    
    var canvas = document.getElementById("canvas");
    var dataURL = canvas.toDataURL();
    console.log(dataURL);
    // "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNby
    // blAAAADElEQVQImWNgoBMAAABpAAFEI8ARAAAAAElFTkSuQmCC"
    

    1.1 Data URIs

    1.1.1 什么是URI

    统一资源标识符(Uniform Resource Identifier,或URI)是一个用于标识某一互联网资源名称的字符串。Web上可用的每种资源 -HTML文档、图像、视频片段、程序等,都由一个统一资源标识符(Uniform Resource Identifier, 简称"URI")进行标识。

    而URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。

    1.1.2 怎么用

     Data URIs的数据格式很简单,通过RFC 2397的定义,一般格式是:

      data:[mime type][;charset=][;base64],
    

      说明:

    • data - 指代URI协议
    • mime type - 代表数据类型,如png图片则为image/png,若不说明,默认为text/plain(上面的默认值为那个函数的默认值,这里是这种数据格式的默认值)
    • charset - 如果不使用base64,则使用charset指定的字符类
    • encoded data - 对应的编码信息

    1.1.3 优缺点

    优点:
    1.减少HTTP请求数,没有了TCP连接消耗和同一域名下浏览器的并发数限制,这里说的方式是下图这种将编码后的图片放到标签的url或者src中。

    网页上的图片资源如果采用http形式的url的话都会额外发送一次请求,网页发送的http请求次数越多,会造成页面加载速度越慢。而采用Base64格式的编码,将图片转化为字符串后,图片文件会随着html元素一并加载,这样就可以减少http请求的次数,对于网页优化是一种比较好的手段。

    2.对于小文件会降低带宽。虽然编码后数据量会增加,但是却减少了http头,当http头的数据量大于文件编码的增量,那么就会降低带宽。
    3.采用Base64编码的图片是随着页面一起加载的,不会造成跨域请求的问题,也不会造成清理图片缓存的问题。

    缺点:
    1.无法被重复利用,同一个文档应用多次同一个内容,则需要重复多次,数据量大量增加,增加了下载时间。
    2.不支持数据压缩,base64编码大小会增加1/3左右,而urlencode后数据量会增加更多。
    3.当我们将一个只有几KB的图片转化为Base64格式编码,生成的字符串往往会大于几KB,如果将其写在一个css文件中,这样一个css文件的大小会剧增,造成代码可读性差不说,还会造成请求传输的数据量递增。
    4.如果我们将Base64编码的图片存入数据库中,会造成数据库数据量的增大,这样的效果还不如将图片存至图片服务器,而只在数据库中存入url字段。
    5.不利于安全软件的过滤,同时也存在一定的安全隐患。

    由于我们会将图片传输到服务器,为减小传输数据量,采用了Blob。

    2. Blob(二进制大对象)方式

    HTMLCanvasElement.toBlob() 方法创造Blob对象,用以展示canvas上的图片;这个图片文件可以被缓存或保存到本地,由用户代理端自行决定。如不特别指明,图片的类型默认为 image/png,分辨率为96dpi。
    语法:

    void canvas.toBlob(callback, type, encoderOptions);
    

    参数:

    • callback
      回调函数,可获得一个单独的Blob对象参数。
    • type 可选
      DOMString类型,指定图片格式,默认格式为image/png。
    • encoderOptions 可选
      Number类型,值在0与1之间,当请求图片格式为image/jpeg或者image/webp时用来指定图片展示质量。

    比如将canvas图像转换为文件,当一个内容画到canvas上时,我们可以将它生成任何一个格式支持的图片文件。比如,下面的代码段获得了id为“canvas”的<canvas>元素中的图像,复制成一个PNG图,在文档中加入一个新的<img>元素,这个<img>元素的源图就是使用canvas创建的那个图像:

    var canvas = document.getElementById("canvas");
    
    canvas.toBlob(function(blob) {
      var newImg = document.createElement("img"),
          url = URL.createObjectURL(blob);
    
      newImg.onload = function() {
        // no longer need to read the blob so it's revoked(撤销,删除)
        URL.revokeObjectURL(url);
      };
    
      newImg.src = url;
      document.body.appendChild(newImg);
    });
    

    注意,我们这里创建的是PNG图片;如果在toBlob()传入第二个参数,就可以指定图片格式。例如,生成JPEG格式的图片:

    canvas.toBlob(function(blob){...}, "image/jpeg", 0.95); // JPEG at 95% quality
    

    2.1 new Blob([new Uint8Array(array)], {type: 'image/png'})

    当然CropBox并没有使用toBlob方法,而是直接利用js中的Blob对象类型的构造方法。
    语法:

    var aBlob = new Blob( array, options );
    

    参数:

    • array 是一个由ArrayBuffer, ArrayBufferView, Blob, DOMString 等对象构成的 Array ,或者其他类似对象的混合体,它将会被放进 Blob.
    • options 是一个可选的Blob熟悉字典,它可能会指定如下两种属性:
      type,默认值为 "",它代表了将会被放入到blob中的数组内容的MIME类型。
      endings,默认值为"transparent",它代表包含行结束符 的字符串如何被输出。 它是以下两个值中的一个: "native",代表行结束符会被更改为适合宿主操作系统文件系统的惯例,或者 "transparent", 代表会保持blob中保存的结束符不变
      比如:
    var aFileParts = ['<a id="a"><b id="b">hey!</b></a>']; // an array consisting of a single DOMString
    var oMyBlob = new Blob(aFileParts, {type : 'text/html'}); // the blob
    

    用处:

    1. 大文件分割 (slice() 方法):
      slice() 方法接受三个参数,起始偏移量,结束偏移量,还有可选的 mime 类型,然后轮循向后台提交各文件片段,即可实现文件的分片上传。

    3 Base64编码

    Base64编码本质上是一种将二进制数据转成文本数据的方案。对于非二进制数据,是先将其转换成二进制形式,然后每连续6比特(2的6次方=64)计算其十进制值,根据该值在大小为64的码表中找到对应的字符,最终得到一个文本字符串。

    Base64编码的作用:

    • 由于某些系统中只能使用ASCII字符。Base64用来将非ASCII字符的数据转换成ASCII字符。
      比如我们的电子邮件系统,一般是使用SMTP(简单邮件传输协议)将邮件从客户端发往服务器端,邮件客户端使用POP3(邮局协议,第3版本)或IMAP(交互邮件访问协议)从服务器端获取邮件。

    SMTP协议一开始是基于纯ASCII文本的,对于二进制文件(比如邮件附件中的图像、声音等)的处理并不好,因为标准ASCII编码最高位不是数据位,会把二进制文件的最高位作为不可见字符,可能传输的过程中当做校验位处理掉了,从而导致传输错误。Base64可以将非ASCII字符的数据转换成ASCII字符。

    标准ASCII码的最高位是奇偶校验位,比如奇校验规定:正确的代码一个字节中1的个数必须是奇数,若非奇数,则在最高位b7添1;偶校验规定:正确的代码一个字节中1的个数必须是偶数,若非偶数,则在最高位b7添

    • HTML内嵌Base64编码图片
      前端在实现页面时,对于一些简单图片,通常会选择将图片内容直接内嵌在页面中,避免不必要的外部资源加载和Http请求,比如Data URIs,允许使用Base64对图片或其他文件的二进制数据进行编码,将其作为文本字符串嵌入网页中。以百度搜索首页为例,其中语音搜索的图标是个背景图片,其内容以 Data URLs 形式直接写在css中,这个css内容又直接嵌在HTML页面中,如下图所示:

    • 很多场景下的数据传输要求数据只能由简单通用的字符组成,比如HTTP协议要求请求的首行和请求头都必须是ASCII编码。Base-64编码将用户输入或二进制数据,打包成一种安全格式,将其作为HTTP首部字段的值发送出去,而无须担心其中包含会破坏HTTP分析程序的冒号、换行符或二进制值。

    原理

    base64其实不是安全领域下的加密解密算法。虽然有时候经常看到所谓的base64加密解密。其实base64只能算是一个编码算法,对数据内容进行编码来适合传输。虽然base64编码过后原文也变成不能看到的字符格式,但也仅此而已。
    它的算法是:每3个字节(每字节8bit),转换为4个6bit的字节(一个字节应该是8bit,所以前2位补0)。例如:

    xxxxxxxx yyyyyyyy xxxxyyyy这里转换前的3个字节,然后,每6位分到一个字节中:

    xxxxxx xxyyyy yyyyxx xxyyyy

    然后高位补0

    00xxxxxx 00xxyyyy 00yyyyxx 00xxyyyy

    其中xy是二进制的0和1,然后再按base64码表进行替换(base64,基本的64个码,=号不在其内),base64编码后的字符串只包含字母A-Z,a-z,数字0-9,还有+/这2个特殊字符。

    也就是说,转换后的字符串理论上将要比原来的长1/3。因此Base64所造成数据冗余不是很严重,Base64是当今比较流行的编码方法,因为它编起来速度快而且简单

    举个例子,有三个字节的原始数据:aaaaaabb bbbbccccc ccdddddd(这里每个字母表示一个bit位)
      那么编码之后会变成:      00aaaaaa 00bbbbbb 00cccccc 00dddddd

    所以可以看出base64编码简单,虽然编码后不是明文,看不出原文,但是解码也很简单
    原文的字节不够的地方可以用全0来补足,转换时Base64编码用=号来代替。

    4. 实现

    上面的知识点纯属复制粘贴,复习一下知识点,下面开始实现代码(源码)。
    项目的目录结构:

    采用Spring+SpringMVC+MyBatis+MySQL+Maven+CropBox实现,主要功能:

    • 图片上传,裁剪
    • 预览
    • 后台保存到数据库和文件夹
    • 缩放输出

      下面为从后台数据库或者文件夹取出的图片,然后byte[]格式传到前台, 前一个头像正常大小,后一个缩放到100*100像素

      下面贴出主要代码:

    视图层

    cropbox用法:
    github上的说明和Demo

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>CropBox头像裁剪,上传,回显</title>
    <link rel="stylesheet" href="/css/style.css" type="text/css" />
    </head>
    <body>
    <script type="text/javascript" src="/js/jquery-1.11.1.min.js"></script>
    <script type="text/javascript" src="/js/cropbox.js"></script>
    <div class="container">
      <div class="imageBox">
        <div class="thumbBox"></div>
        <div class="spinner" style="display: none">Loading...</div>
      </div>
        <div class="action">
          <!-- <input type="file" id="file" style="  200px">-->
          <div class="new-contentarea tc"><a href="javascript:void(0)" class="upload-img">
            <label for="upload-file">上传图像</label>
          </a>
            <input type="file" class="" name="upload-file" id="upload-file"/>
          </div>
          <input type="button" id="btnCrop" class="Btnsty_peyton" value="裁切">
          <input type="button" id="btnZoomIn" class="Btnsty_peyton" value="+">
          <input type="button" id="btnZoomOut" class="Btnsty_peyton" value="-">
          <input type="button" id="blobSubmit" class="Btnsty_peyton" value="提交">
        </div>
        <div class="cropped"></div>
    </div>
    <script type="text/javascript">
    $(window).load(function() {
    	var options =
    	{
    		thumbBox: '.thumbBox',
    		spinner: '.spinner',
    		imgSrc: 'images/avatar.png'
    	}
    	var cropper = $('.imageBox').cropbox(options);
    	$('#upload-file').on('change', function(){
    		var reader = new FileReader();
    		reader.onload = function(e) {
    			options.imgSrc = e.target.result;
    			cropper = $('.imageBox').cropbox(options);
    		}
    		reader.readAsDataURL(this.files[0]);
    		this.files = [];
    	})
        $('#blobSubmit').on('click', function(){
            var img = cropper.getBlob();
            var formdata = new FormData();
            formdata.append("imagefile", img);
            $.ajax({
                url:"/file/updateHeadPicture.action",
                data: formdata,
                type:"post",
                //默认值: true。默认情况下,通过data选项传递进来的数据,如果是一个对象(技术上讲只要不是字符串),
                // 都会处理转化成一个查询字符串,以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。
                processData: false,
                contentType: false,
                success: function(oResult) {
                    if(oResult.success==1){
                        window.location.href="/image";
                    }else{
                        alert(oResult.message);
                    }
                }
            })
        })
    	$('#btnCrop').on('click', function(){
    		var img = cropper.getDataURL();
    		$('.cropped').html('');
    		$('.cropped').append('<img src="'+img+'" align="absmiddle" style="64px;margin-top:4px;border-radius:64px;box-shadow:0px 0px 12px #7E7E7E;" ><p>64px*64px</p>');
    		$('.cropped').append('<img src="'+img+'" align="absmiddle" style="128px;margin-top:4px;border-radius:128px;box-shadow:0px 0px 12px #7E7E7E;"><p>128px*128px</p>');
    		$('.cropped').append('<img src="'+img+'" align="absmiddle" style="180px;margin-top:4px;border-radius:180px;box-shadow:0px 0px 12px #7E7E7E;"><p>180px*180px</p>');
    	})
    	$('#btnZoomIn').on('click', function(){
    		cropper.zoomIn();
    	})
    	$('#btnZoomOut').on('click', function(){
    		cropper.zoomOut();
    	})
    });
    </script>
    </div>
    </body>
    </html>
    
    • image.html
      从后台获得byte[]格式的字节流,然后展示经过后台处理后的效果:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>头像展示</title>
    </head>
    <body>
    <div id="forAppend" class="demo"></div>
    <img src="/image/xie" alt=""/>
    ##单位是像素,并且是按照长和宽中较小的值来确定等比例缩放的比例
    <img src="/image/xie/300/100" alt=""/>
    </body>
    
    </html>
    

    控制层

    package com.cropbox.demo.uploadHead.controller;
    
    import com.alibaba.fastjson.JSON;
    import com.cropbox.demo.uploadHead.mapper.UserMapper;
    import com.cropbox.demo.uploadHead.model.UploadPictureResponse;
    import com.cropbox.demo.uploadHead.model.User;
    import com.cropbox.demo.uploadHead.service.UploadService;
    import com.cropbox.demo.uploadHead.utils.ImageUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.servlet.http.HttpServletResponse;
    import java.io.*;
    
    /**
     * getBlob:上传图片为Blob格式,并保存到mysql的blob字段中,
     * 其实File继承了Blob,所以表单中的图片处理方式与之类似,
     * 实现头像的裁剪,保存到服务器,并在需要时回显到客户端
     *
     * @author xie
     * @version 1.0
     * @Date 2017/5/26
     */
    @Controller
    public class FileController {
        @Autowired
        UploadService uploadService;
    
        @Autowired
        UserMapper userMapper;
    
        @Autowired
        ImageUtils imageUtils;
    
        /**
         * 主页
         * @return
         */
        @RequestMapping(path = {"/"}, method = {RequestMethod.GET, RequestMethod.POST})
        public String index() {
            return "index";
        }
    
        /**
         * 实现图片上传
         * @param file
         * @param response
         */
        @RequestMapping(path = {"/file/updateHeadPicture.action"}, method = {RequestMethod.GET, RequestMethod.POST})
        public void index(@RequestParam("imagefile") MultipartFile file, HttpServletResponse response) {
            try {
                UploadPictureResponse uploadPictureResponse = uploadService.updateHeadPicture(file);
                     /*
                     设置编码格式,返回结果json结果,注意其中的对象转化为json字符串格式为:
                     {"message":"上传图片成功!","success":1,"url":"C:\\home\\myblog\\pic\\2f1b63bc4b654a27a7e0c1b1a0fb9270.png"}
                     所以前端可以直接读取success,message等信息
                     */
                response.setContentType( "application/json;charset=UTF-8");
                response.getWriter().write( JSON.toJSONString(uploadPictureResponse));
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    
        @RequestMapping(path= {"/image"}, method = {RequestMethod.GET, RequestMethod.POST})
        public String index1() {
            return "image";
        }
    
        /**
         * 按照用户名查找头像
         * @param username
         * @param response
         */
        @RequestMapping(path = {"/image/{username}"}, method = {RequestMethod.GET, RequestMethod.POST})
        public void index1(@PathVariable("username") String username, HttpServletResponse response) {
            User user = userMapper.selectByUsername(username);
            try {
                //写到输出流
                response.setContentType("image/png");
                response.setCharacterEncoding("UTF-8");
                //BufferedOutputStream 是缓冲输出流,默认新建字节数组大小为8192的“缓冲输出流”
                OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
                outputStream.write(user.getHead());
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 按照用户名查找头像,并提供缩放功能
         * @param username 用户名
         * @param width 要求图片的宽度
         * @param height 要求图片的高度
         * @param response
         */
        @RequestMapping(path = "/image/{username}/{width}/{height}")
        public void getPhotoById(@PathVariable("username") String username, @PathVariable("width") int width,
                                 @PathVariable("height") int height, HttpServletResponse response) {
            User user = userMapper.selectByUsername(username);
            byte[] data = user.getHead();
            try {
                if (width > 0 && height > 0) {
                    data = imageUtils.scaleImage(data, width, height);
                }
                response.setContentType("image/png");
                response.setCharacterEncoding("UTF-8");
                OutputStream outputStream = new BufferedOutputStream(response.getOutputStream());
                outputStream.write(data);
                outputStream.flush();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    

    service层

    package com.cropbox.demo.uploadHead.service;
    
    import com.cropbox.demo.uploadHead.mapper.UserMapper;
    import com.cropbox.demo.uploadHead.model.UploadPictureResponse;
    import com.cropbox.demo.uploadHead.utils.ImageUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.util.FileCopyUtils;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.file.Files;
    import java.nio.file.StandardCopyOption;
    import java.util.UUID;
    
    /**
     * 类的详细说明
     *
     * @author xie
     * @version 1.0
     * @Date 2017/5/27
     */
    @Service
    public class UploadService {
    
        @Autowired
        ImageUtils imageUtils;
    
        @Autowired
        UserMapper userMapper;
    
        /**
         * 上传的头像统一都转换为了png格式,故不进行是否允许类型判断
         * @param file
         * @return
         * @throws IOException
         */
        public UploadPictureResponse updateHeadPicture(MultipartFile file) throws IOException {
          UploadPictureResponse uploadPictureResponse = new UploadPictureResponse();
            try {
                InputStream is = file.getInputStream();
                byte[] bytes = FileCopyUtils.copyToByteArray(is);
                //更新数据库中的blob格式的head字段,返回1表示更新成功,返回0表示失败
                int success = userMapper.updateHead(1,bytes);
                //上面已经将输入流中的数据全部读完,故重新初始化
                is = file.getInputStream();
                //同时将图片保存到C:\home\myblog\pic\ 路径下,这里保存到文件夹只是演示作用,请根据需求决定将图片保存到数据库还是服务器文件夹
                String fileName = UUID.randomUUID().toString().replaceAll("-", "") + ".png" ;
                Files.copy(is, new File(imageUtils.getPictureDir() + fileName).toPath(),
                        StandardCopyOption.REPLACE_EXISTING);
                uploadPictureResponse.setSuccess(success);
                uploadPictureResponse.setMessage("上传图片成功!");
                uploadPictureResponse.setUrl(imageUtils.getPictureDir() + fileName);
                is.close();
                return uploadPictureResponse;
            } catch (Exception e) {
                // 请求失败时打印的异常的信息
                uploadPictureResponse.setSuccess(0);
                uploadPictureResponse.setMessage("服务器异常!");
                return uploadPictureResponse;
            }
        }
    
    }
    
    

    图片处理工具类

    package com.cropbox.demo.uploadHead.utils;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    
    import javax.imageio.ImageIO;
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    
    /**
     * 图片处理服务
     *
     * @author xie
     * @version 1.0
     * @Date 2017/5/27
     */
    @Service
    public class ImageUtils {
    
        /** 头像图片的放置路径*/
        @Value("${headPath.home}")
        private String PictureDir;
    
        /** 允许的图片类型头像图片,这里分别使用属性占位符和SpEL表达式,可以实现更复杂的功能,运行时计算值*/
        @Value("${pictureLimit.suffix}")
        private String PictureFileSuffix;
    
        /**
        判断上传图片格式是否被允许
         */
        public boolean isFileAllowed(String fileSuffix) {
            for (String suffix : PictureFileSuffix.split(",")) {
                if (suffix.equals(fileSuffix)) {
                    return true;
                }
            }
            return false;
        }
    
        /**
         * 获得图片存储路径
         * @return
         */
        public String getPictureDir(){
            return PictureDir;
        }
    
        /**
         * 获得系统允许上传图片后缀
         * @return
         */
        public String getPictureFileSuffix(){
            return PictureFileSuffix;
        }
    
        /**
         * 等比例缩放图片,按照长和宽中较小的数来确定缩放比例,所有单位为像素,
         * 在传输中,图片是不能直接传的,因此需要把图片变为字节数组,然后传输比较方便;只需要一般输出流的write方法即可;而字节数组变成BufferedImage能够还原图像;
         *
         * @param data 图片的byte[]格式
         * @param width 缩放后的宽度
         * @param height 缩放后的高度
         * @return 图片缩放后的byte[]格式
         * @throws IOException
         */
        public byte[] scaleImage(byte[] data, int width, int height) throws IOException {
            ////从特定文件载入
            BufferedImage oldImage = ImageIO.read(new ByteArrayInputStream(data));
            int imageOldWidth = oldImage.getWidth();
            int imageOldHeight = oldImage.getHeight();
            double scale_x = (double) width / imageOldWidth;
            double scale_y = (double) height / imageOldHeight;
            double scale_xy = Math.min(scale_x, scale_y);
            int imageNewWidth = (int) (imageOldWidth * scale_xy);
            int imageNewHeight = (int) (imageOldHeight * scale_xy);
    
            //创建一个不带透明色的BufferedImage对象
            BufferedImage newImage = new BufferedImage(imageNewWidth, imageNewHeight, BufferedImage.TYPE_INT_RGB);
    
            /*BufferedImage与Image之间的相互转换,其中
            * oldImage.getScaledInstance(imageNewWidth, imageNewHeight, BufferedImage.SCALE_SMOOTH)表示缩放图像
            * BufferedImage.SCALE_SMOOTH表示压缩图片所用的算法,本算法生成缩略图片的平滑度的优先级比速度高,生成的图片质量比较好,但速度慢
            *
             */
            newImage.getGraphics().drawImage(oldImage.getScaledInstance(imageNewWidth, imageNewHeight, BufferedImage.SCALE_SMOOTH), 0, 0, null);
    
            /*
            释放绘图上下文所占的系统资源
             */
            newImage.getGraphics().dispose();
            ByteArrayOutputStream outPutStream = new ByteArrayOutputStream();
    
            /*BufferedImage  ---->byte[],
            参数newImage表示获得的BufferedImage;
            参数format表示图片的格式,比如“gif”等;
            参数out表示输出流,如果要转成Byte数组,则输出流为ByteArrayOutputStream即可;
            执行完后,只需要toByteArray()就能得到byte[];
            */
            ImageIO.write(newImage, "jpg", outPutStream);
            oldImage.flush();
    
            outPutStream.flush();
            outPutStream.close();
            return outPutStream.toByteArray();
        }
    }
    

    分析

    利用Fiddler我们简要分析一下http的请求与回应。
    如下图所示,当我们点击了提交按钮

    http各字段的含义:http://www.cnblogs.com/xzwblog/p/6917960.html

    其中:
    Content-Length: 78174表示传输的二进制图片的大小,单位字节
    Content-Disposition: form-data; name="imagefile"; filename="blob"作为对下载文件的一个标识字段。
    在请求时,form-data表示上传表单数据,name="imagefile"表示表单参数的名字, filename="blob"表示文件名
    在回应时,Content-Disposition有两种属性,inline 和 attachment。 inline :将文件内容直接显示在页面, attachment:弹出对话框让用户下载。
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryrmcDIMhUIGDLILq1表示利用表单提交数据,boundary是为了区分POST的内容的区段用的,只要在内容中遇到了此值,就表示下面要开始一个新的区段了,每个区段的内容相对独立。如果遇到的是此值后面连着两个减号,则表示全部内容到此结束。每个段也分为段头和段体两部分,用空行隔开,每段都有自己的类型和相关信息。比如下面的请求,第一区段是text1的值,它的名称是“text1”,值为“hehe”。第二段是文件内容,段首里表明了此文件域的名称“file1”和此文件在用户磁盘上的位置,后面就是文件的内容。

    POST /form.asp HTTP/1.1
    Accept: */*
    Referer: http://localhost:8080/form.asp
    Accept-Language: zh-cn
    Content-Type: multipart/form-data; boundary=---------------------------7d62bf2f9066c
    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; InfoPath.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)
    Host: localhost:8080
    Content-Length: 337
    Connection: Keep-Alive
    Cache-Control: no-cache
    Cookie: key=haha; ASPSESSIONIDCSQCRTBS=LOIPGIMBLMNOGCOBOMPJBOKP
    -----------------------------7d62bf2f9066c
    Content-Disposition: form-data; name="text1"
    
    hehe
    -----------------------------7d62bf2f9066c
    Content-Disposition: form-data; name="file1"; filename="H:Documents and SettingsAdministrator桌面haha.txt"
    Content-Type: text/plain
    
    ABCDEFG
    -----------------------------7d62bf2f9066c--
    

    {"message":"上传图片成功!","success":1,"url":"C:\\home\\myblog\\pic\\3973974dd2bb41b49fe064dedc0dcae9.png"}表示回应的json字符串,即项目中的JSON.toJSONString(uploadPictureResponse)部分,将uploadPictureResponse实体对象转化为json字符串。

    源码

  • 相关阅读:
    zabbix4.2.5常见问题指南
    postgres常用运维sql
    centos7部署inotify与rsync实现实时数据同步
    postgres主从基于流复制
    postgres高可用学习篇三:haproxy+keepalived实现postgres负载均衡
    postgres高可用学习篇二:通过pgbouncer连接池工具来管理postgres连接
    postgres高可用学习篇一:如何通过patroni如何管理3个postgres节点
    centos7安装yum安装pip
    nginx1.15.10配置使用非https访问返回403
    zabbix4.2.5自定义告警模板
  • 原文地址:https://www.cnblogs.com/xzwblog/p/6912320.html
Copyright © 2020-2023  润新知