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后生成目标裁剪图片保存到服务器,并返回给页面显示。
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>