用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 图像裁剪插件有Jcrop和CropBox,前者是将原图和需要裁剪的参数(裁剪的各点坐标,旋转角度等)传到后台,然后由后台完成实际的裁剪和后续操作。
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'});
                },

下面贴出主要代码:
视图层

index.html
实现头像裁剪,上传,预览,
这里利用了jQuery.ajax()函数,不懂得去复习下吧
官网
我的博客里也有简要介绍:http://www.cnblogs.com/xzwblog/p/6915213.html#_label5
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=" width: 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="width: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="width: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="width: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的请求与回应。
如下图所示,当我们点击了提交按钮
。。。。。详情请参考:
https://www.cnblogs.com/xzwblog/p/6912320.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值