Java 利用jquery库cropper完成图片裁剪功能

功能描述:点击用户头像,弹出一个图片裁剪的modal,提交后,java后端保存裁剪后的图片,然后返回该图片url给前端显示。Cropper官网及下载

Cropper依赖于jquery、bootstrap。

1.Java核心图片裁剪工具类 ImageCut.java

package com.jykj.demo.util;

import java.io.*;

import java.awt.*;

import java.awt.image.*;

import java.awt.Graphics;

import java.awt.color.ColorSpace;

import javax.imageio.ImageIO;

public class ImageCut {

/**

* 图像切割(改) *

* @param srcImageFile 源图像地址

* @param dirImageFile 新图像地址

* @param x 目标切片起点x坐标

* @param y 目标切片起点y坐标

* @param destWidth 目标切片宽度

* @param destHeight 目标切片高度

*/

public static void abscut(String srcImageFile,String dirImageFile, int x, int y, int destWidth,

int destHeight) {

try {

Image img;

ImageFilter cropFilter;

// 读取源图像

BufferedImage bi = ImageIO.read(new File(srcImageFile));

int srcWidth = bi.getWidth(); // 源图宽度

int srcHeight = bi.getHeight(); // 源图高度

if (srcWidth >= destWidth && srcHeight >= destHeight) {

Image image = bi.getScaledInstance(srcWidth, srcHeight,

Image.SCALE_DEFAULT);

// 改进的想法:是否可用多线程加快切割速度

// 四个参数分别为图像起点坐标和宽高

// 即: CropImageFilter(int x,int y,int width,int height)

cropFilter = new CropImageFilter(x, y, destWidth, destHeight);

img = Toolkit.getDefaultToolkit().createImage(

new FilteredImageSource(image.getSource(), cropFilter));

BufferedImage tag = new BufferedImage(destWidth, destHeight,

BufferedImage.TYPE_INT_RGB);

Graphics g = tag.getGraphics();

g.drawImage(img, 0, 0, null); // 绘制缩小后的图

g.dispose();

// 输出为文件

ImageIO.write(tag, "JPEG", new File(dirImageFile));

}

} catch (Exception e) {

e.printStackTrace();

}

}

public static void cut(InputStream is,File dirImageFile, int x, int y, int destWidth,

int destHeight) throws IOException {

cut(ImageIO.read(is),dirImageFile,x,y,destWidth,destHeight);

}

public static void cut(BufferedImage bi,File dirImageFile, int x, int y, int destWidth,

int destHeight) {

try {

Image img;

ImageFilter cropFilter;

// 读取源图像

int srcWidth = bi.getWidth(); // 源图宽度

int srcHeight = bi.getHeight(); // 源图高度

if (srcWidth >= destWidth && srcHeight >= destHeight) {

Image image = bi.getScaledInstance(srcWidth, srcHeight,

Image.SCALE_DEFAULT);

// 改进的想法:是否可用多线程加快切割速度

// 四个参数分别为图像起点坐标和宽高

// 即: CropImageFilter(int x,int y,int width,int height)

cropFilter = new CropImageFilter(x, y, destWidth, destHeight);

img = Toolkit.getDefaultToolkit().createImage(

new FilteredImageSource(image.getSource(), cropFilter));

BufferedImage tag = new BufferedImage(destWidth, destHeight,

BufferedImage.TYPE_INT_RGB);

Graphics g = tag.getGraphics();

g.drawImage(img, 0, 0, null); // 绘制缩小后的图

g.dispose();

// 输出为文件

ImageIO.write(tag, "JPEG", dirImageFile);

}

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 缩放图像

*

* @param srcImageFile 源图像文件地址

* @param result 缩放后的图像地址

* @param scale 缩放比例

* @param flag 缩放选择:true 放大; false 缩小;

*/

public static void scale(String srcImageFile, String result, int scale,

boolean flag) {

try {

BufferedImage src = ImageIO.read(new File(srcImageFile)); // 读入文件

int width = src.getWidth(); // 得到源图宽

int height = src.getHeight(); // 得到源图长

if (flag) {

// 放大

width = width * scale;

height = height * scale;

} else {

// 缩小

width = width / scale;

height = height / scale;

}

Image image = src.getScaledInstance(width, height,Image.SCALE_DEFAULT);

BufferedImage tag = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);

Graphics g = tag.getGraphics();

g.drawImage(image, 0, 0, null); // 绘制缩小后的图

g.dispose();

ImageIO.write(tag, "JPEG", new File(result));// 输出到文件流

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 重新生成按指定宽度和高度的图像

* @param srcImageFile 源图像文件地址

* @param result 新的图像地址

* @param _width 设置新的图像宽度

* @param _height 设置新的图像高度

*/

public static void scale(String srcImageFile, String result, int _width,int _height) {

scale(srcImageFile,result,_width,_height,0,0);

}

public static void scale(String srcImageFile, String result, int _width,int _height,int x,int y) {

try {

BufferedImage src = ImageIO.read(new File(srcImageFile)); // 读入文件

int width = src.getWidth(); // 得到源图宽

int height = src.getHeight(); // 得到源图长

if (width > _width) {

width = _width;

}

if (height > _height) {

height = _height;

}

Image image = src.getScaledInstance(width, height,Image.SCALE_DEFAULT);

BufferedImage tag = new BufferedImage(width, height,BufferedImage.TYPE_INT_RGB);

Graphics g = tag.getGraphics();

g.drawImage(image, x, y, null); // 绘制缩小后的图

g.dispose();

ImageIO.write(tag, "JPEG", new File(result));// 输出到文件流

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 图像类型转换 GIF->JPG GIF->PNG PNG->JPG PNG->GIF(X)

*/

public static void convert(String source, String result) {

try {

File f = new File(source);

f.canRead();

f.canWrite();

BufferedImage src = ImageIO.read(f);

ImageIO.write(src, "JPG", new File(result));

} catch (Exception e) {

e.printStackTrace();

}

}

/**

* 彩色转为黑白

*

* @param source

* @param result

*/

public static void gray(String source, String result) {

try {

BufferedImage src = ImageIO.read(new File(source));

ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_GRAY);

ColorConvertOp op = new ColorConvertOp(cs, null);

src = op.filter(src, null);

ImageIO.write(src, "JPEG", new File(result));

} catch (IOException e) {

e.printStackTrace();

}

}

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187

2. Java后端图片裁剪处理(Controller)

//图片裁剪功能

@RequestMapping(value = "/imgCut.do",method = RequestMethod.POST, produces="text/html;charset=utf-8")

@ResponseBody

public String profile_imgCut(MultipartFile avatar_file,String avatar_src,String avatar_data, HttpServletRequest request, Model model) {

String dir = ConfigInfo.resources_upload_path;

String path = request.getSession().getServletContext().getRealPath(dir);

String name = avatar_file.getOriginalFilename();

//判断文件的MIMEtype

String type = avatar_file.getContentType();

if(type==null || !type.toLowerCase().startsWith("image/")) return JSON.toJSONString(new Result(false,"不支持的文件类型,仅支持图片!"));

System.out.println("file type:"+type);

String fileName = new Date().getTime()+""+new Random().nextInt(10000)+"_"+name.substring(name.lastIndexOf('.'));

System.out.println("文件路径:"+path+":"+fileName);

JSONObject joData = (JSONObject) JSONObject.parse(avatar_data);

// 用户经过剪辑后的图片的大小

float x = joData.getFloatValue("x");

float y = joData.getFloatValue("y");

float w = joData.getFloatValue("width");

float h = joData.getFloatValue("height");

//开始上传

File targetFile = new File(path, fileName);

//保存

try {

if(!targetFile.exists()){

targetFile.mkdirs();

InputStream is = avatar_file.getInputStream();

ImageCut.cut(is, targetFile, (int)x,(int)y,(int)w,(int)h);

is.close();

}

} catch (Exception e) {

e.printStackTrace();

return JSON.toJSONString(new Result(false,"上传失败,出现异常:"+e.getMessage()));

}

return JSON.toJSONString(new Result(true,"上传成功!",request.getSession().getServletContext().getContextPath()+dir+fileName));

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

3. 前端form提交(test.html)

<!DOCTYPE html>

<html>

<head>

<meta charset="utf-8">

<meta http-equiv="X-UA-Compatible" content="IE=edge">

<title>TEST</title>

<!-- Tell the browser to be responsive to screen width -->

<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">

<link rel="stylesheet" href="${request.contextPath}/resources/bootstrap/css/bootstrap.min.css">

<link rel="stylesheet" href="${request.contextPath}/resources/cropper/cropper.min.css"/>

<link rel="stylesheet" href="${request.contextPath}/resources/cropper/crop-avatar/css/main.css"/>

</head>

<body>

<div class="container" id="crop-avatar">

<!-- Current avatar -->

<div class="avatar-view" title="Change the avatar">

<img src="${request.contextPath}/resources/cropper/crop-avatar/img/picture.jpg" alt="Avatar">

</div>

<!-- Cropping modal -->

<div class="modal fade" id="avatar-modal" aria-hidden="true" aria-labelledby="avatar-modal-label" role="dialog" tabindex="-1">

<div class="modal-dialog modal-lg">

<div class="modal-content">

<form class="avatar-form" action="imgCut.do" enctype="multipart/form-data" method="post" accept="image/*">

<div class="modal-header">

<button type="button" class="close" data-dismiss="modal">×</button>

<h4 class="modal-title" id="avatar-modal-label">Change Avatar</h4>

</div>

<div class="modal-body">

<div class="avatar-body">

<!-- Upload image and data -->

<div class="avatar-upload">

<input type="hidden" class="avatar-src" name="avatar_src">

<input type="hidden" class="avatar-data" name="avatar_data">

<label for="avatarInput" class="btn btn-primary">选择图片</label>

<input type="file" class="avatar-input" id="avatarInput" name="avatar_file" style="display: none;" accept="image/*">

</div>

<!-- Crop and preview -->

<div class="row">

<div class="col-md-9">

<div class="avatar-wrapper"></div>

</div>

<div class="col-md-3">

<div class="avatar-preview preview-lg"></div>

<div class="avatar-preview preview-md"></div>

<div class="avatar-preview preview-sm"></div>

</div>

</div>

<div class="row avatar-btns">

<div class="col-md-9">

<div class="btn-group">

<button type="button" class="btn btn-primary" data-method="rotate" data-option="-90" title="Rotate -90 degrees">Rotate Left</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="-15">-15deg</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="-30">-30deg</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="-45">-45deg</button>

</div>

<div class="btn-group">

<button type="button" class="btn btn-primary" data-method="rotate" data-option="90" title="Rotate 90 degrees">Rotate Right</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="15">15deg</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="30">30deg</button>

<button type="button" class="btn btn-primary" data-method="rotate" data-option="45">45deg</button>

</div>

</div>

<div class="col-md-3">

<button type="submit" class="btn btn-primary btn-block avatar-save">Done</button>

</div>

</div>

</div>

</div>

<!-- <div class="modal-footer">

<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>

</div> -->

</form>

</div>

</div>

</div><!-- /.modal -->

<!-- Loading state -->

<div class="loading" aria-label="Loading" role="img" tabindex="-1"></div>

</div>

<!-- jQuery -->

<script src="${request.contextPath}/resources/plugins/jQuery/jquery-2.2.4.min.js"></script>

<script src="${request.contextPath}/resources/bootstrap/js/bootstrap.min.js"></script>

<script src="${request.contextPath}/resources/cropper/cropper.min.js"></script>

<script src="${request.contextPath}/resources/cropper/crop-avatar/js/main.js"></script>

</body>

</html>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

注意:从Cropper官网上下载下来后,在examples目录下有一个示例crop-avatar,将其中的main.css以及main.js复制到你的项目中,Cropper是依赖于jquery以及bootstrap的,所以也要把相应的css和js引进来。

4. 页面展示效果

点击头像,弹出modal。点击Done提交form后生成目标裁剪图片保存到服务器,并返回给页面显示。

Java 利用jquery库cropper完成图片裁剪功能

5.配置文件上传

由于上面图片裁剪涉及到图片上传,所以需要配置Spring支持文件上传功能。

5.1 依赖的jar

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->

<dependency>

<groupId>commons-fileupload</groupId>

<artifactId>commons-fileupload</artifactId>

<version>1.3.1</version>

</dependency>

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

5.2 在Spring配置文件中添加文件上传支持

<!-- 支持上传文件 -->

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

  • 1
  • 2

6. 说明

Cropper 使用的是html5的一个新特性(FormData)来提交表单,在其main.js文件中有这样一行代码:

var data = new FormData(this.$avatarForm[0]);

  • 1

这本质上也就是对表单进行了序列化。Cropper同时也支持旋转,只是在后台处理旋转会比较麻烦点。

前端form提交的数据有 :avatar_file(源文件),avatar_data(裁剪参数JSON),如下通过FormData序列化后所提交的form的数据,后台控制器参数名只要对上就可以了:

------WebKitFormBoundaryx52DDecBJ0KiNHRY

Content-Disposition: form-data; name="avatar_src"

------WebKitFormBoundaryx52DDecBJ0KiNHRY

Content-Disposition: form-data; name="avatar_data"

{"x":76.49999999999999,"y":22.000000000000007,"height":176.00000000000003,"width":176.00000000000003,"rotate":0}

------WebKitFormBoundaryx52DDecBJ0KiNHRY

Content-Disposition: form-data; name="avatar_file"; filename="3.jpg"

Content-Type: image/jpeg

------WebKitFormBoundaryx52DDecBJ0KiNHRY--

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

另外Result是自定义的类,很简单:

public class Result {

private boolean success;

private String info;

private Object data;//也可用来标识错误代码

... 此处省略get和set方法

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

用到的JSON,当然可以用其他的JSON的jar包:

<dependency>

<groupId>com.alibaba</groupId>

<artifactId>fastjson</artifactId>

<version>1.2.12</version>

</dependency>