前段时间,项目在做个人信息设置,其中有一项是设置用户头像信息,需要将用户选择的头像按照用户需要进行剪切,同时保存为大(120*120)、中(75*75)、小(35*35)三种格式的图像,分别显示到不同的位置。
需求很简单,就是这么easy,这个其中重点就是用户可以自己对选择的头像进行截取,最终选择了基于jquery的imgareaselect.js。既然插件都有了,那就开工吧!
第一步:
前端下载必须的js插件,后台使用java自带imageio包处理,不需要其他jar包。
jquery.imgareaselect-0.9.10.zip
jquery.js
第二步:
新建静态页面index.html (页面有点丑哈。。。)
<html lang="en">
<head>
<meta charset="UTF-8">
<title>jquery.imgareaselect图像区域剪切</title>
<meta id="i18n_pagename" content="index-common">
<meta name="viewport" content="width=device-width">
<meta name="keywords" content="" />
<meta name="description" content=""/>
<link rel="stylesheet" href="css/imgareaselect-default.css">
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<div>
<input type="file" style="display: none" id="upImg" onchange="changeImg(this)">
<label for="upImg" id="preview">
<img id="imghead" src="images/normal.png" width="198" height="198" alt="头像">
</label>
</div>
<div class="boxFooter">
<input type="hidden" name="x1" value="0">
<input type="hidden" name="y1" value="0">
<input type="hidden" name="x2" value="100">
<input type="hidden" name="y2" value="100">
<button name="confirm" id="subPhoto" >确 定</button>
<div id="imgmsg"></div>
</div>
<script src="js/jquery.js"></script>
<!-- 加载js文件 -->
<script src="js/jquery.imgareaselect.min.js"></script>
<script src="js/image.js"></script>
</body>
</html>
说明:该页面是模拟前端截取图像,并将起始位置坐标和图片base64编码发送给后端,后端进行处理。
第三步:
新建前端处理js文件image.js
$(document).ready(function () {
//提交图片剪切信息到后台
$("#subPhoto").click(function(){
var x1 = $("input[name='x1']").val();
var y1 = $("input[name='y1']").val();
var x2 = $("input[name='x2']").val();
var y2 = $("input[name='y2']").val();
var img64 = $("#imghead").attr("src");
alert(x1+":"+y1+":"+x2+":"+y2);
var url = "";
var param = {
'x1': x1,
'y1': y1,
'x2': x2,
'y2': y2,
'image': img64
}
$.post(url,param,function(data){
alert(data);
});
})
});
//点击图像区域选择图片
function changeImg(obj){
//图片选择处理
var file = obj;
var MAXWIDTH = 198;
var MAXHEIGHT = 198;
var MAXSIZE = 2048*1024;
var div = document.getElementById('preview');
if (file.files && file.files[0]){
if (file.files[0].size > MAXSIZE) {
alert("more than " + (MAXSIZE/1024/1024) + "M");
return false;
};
div.innerHTML ='<img id=imghead>';
var img = document.getElementById('imghead');
img.onload = function(){
var rect = clacImgZoomParam(MAXWIDTH, MAXHEIGHT, img.offsetWidth, img.offsetHeight);
img.width = rect.width;
img.height = rect.height;
img.style.marginTop = rect.top+'px';
}
var reader = new FileReader();
reader.onload = function(evt){
img.src = evt.target.result;
}
reader.readAsDataURL(file.files[0]);
}
//图片剪切区域处理
$('#imghead').imgAreaSelect({
x1:0,
y1:0,
x2:100,
y2:100,
aspectRatio: '1:1', //比例
handles: true,
onSelectChange: function(img, selection){//图片剪切区域变化时触发
$("#imgmsg").html("x1:"+selection.x1+", y1:"+selection.y1+", x2:"+selection.x2+", y2:"+selection.y2);
},
onSelectEnd: function (img, selection) {//图片剪切区域结束时触发
$('input[name="x1"]').val(selection.x1);
$('input[name="y1"]').val(selection.y1);
$('input[name="x2"]').val(selection.x2);
$('input[name="y2"]').val(selection.y2);
}
});
}
//设置图片显示区域为固定大小,方便后台按统一比例截取图片
function clacImgZoomParam( maxWidth, maxHeight, width, height ){
var param = {top:0, left:0, width:width, height:height};
if( width>maxWidth || height>maxHeight ){
rateWidth = width / maxWidth;
rateHeight = height / maxHeight;
if( rateWidth > rateHeight ){
param.width = maxWidth;
param.height = Math.round(height / rateWidth);
}else {
param.width = Math.round(width / rateHeight);
param.height = maxHeight;
}
}
param.left = Math.round((maxWidth - param.width) / 2);
param.top = Math.round((maxHeight - param.height) / 2);
return param;
}
说明:该js文件不难,大家都应该能看懂,调用imgAreaSelect.js的地方在$(‘#imghead’).imgAreaSelect({…})里面,这块也不难看懂,在 onSelectChange: function(img, selection){})这个地方图片剪切区域变化时触发,我们可以做一些其他的处理,比如预览截取部分的图像等等。clacImgZoomParam()方法主要是用于对用户选择的图像进行限定固定高度和宽度,方便后台按照统一的比例计算截取的坐标位置。
第四步:
后台接收并处理图片,后台是基于SpringMVC的,不熟悉的可以自学下下。
UserController.java
package com.gochina.tc.api;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.gochina.tc.po.ResultPo;
import com.gochina.tc.service.UserService;
/** * 用户信息处理 * @author hwy * */
@Controller
@RequestMapping(value = "/user")
public class UserController {
@Autowired
private UserService userService;
@RequestMapping(value = "/uploadImage",method = RequestMethod.POST)
public ResultPo uploadUserImage(
@RequestParam(value = "image",required = true) String base64Code,
@RequestParam(value = "x1",required = true) int x1,
@RequestParam(value = "y1",required = true) int y1,
@RequestParam(value = "x2",required = true) int x2,
@RequestParam(value = "y2",required = true) int y2){
String result = userService.uploadUserImage(base64Code, x1, y1, x2, y2);
if(result.equals("")){
return new ResultPo(false, "上传用户图像失败");
}else{
return new ResultPo("success", result);
}
}
}
UserService.java
package com.gochina.tc.service;
/** * 用户信息处理service * @author hwy * */
public interface UserService {
/** * 上传并处理用户图片 */
public String uploadUserImage(String base64Code,int x1,int y1,int x2,int y2);
}
UserServiceImpl.java
package com.gochina.tc.service.impl;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import org.springframework.stereotype.Service;
import com.gochina.tc.util.ImageUtil;
/** * 用户信息处理serviceImpl * @author hwy * */
@Service
public class UserServiceImpl implements UserService {
/** * 用户上传图片处理 */
public String uploadUserImage(String base64Code,int x1,int y1,int x2,int y2){
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
String dateStr = sdf.format(new Date());
String path = "D:/user_image/" + dateStr + "/";
String url = "http://127.0.0.1:8080/user_image/" + dateStr + "/";
File f = new File(path);
if(!f.exists()){
f.mkdirs();
}
String fileName = UUID.randomUUID().toString().replaceAll("-", "")+".jpg";
String temp_fileName = path + "t_" + fileName;
String b_fileName = path + "b_" + fileName;
String m_fileName = path + "m_" + fileName;
String s_fileName = path + "s_" + fileName;
String result = "";
try{
//创建原始文件(先强制修改为jpg格式)
boolean isCreate = ImageUtil.base64ToImage(base64Code, path + fileName);
if(isCreate){
float scale = ImageUtil.getScaleCutImage(path + fileName);//比例
int width = (int) ((x2-x1)*scale);
int height = width;
int start_x = (int) (x1*scale);
int start_y = (int) (y1*scale);
//剪切图片
ImageUtil.cutImage(path + fileName, temp_fileName, start_x, start_y, width, height);
//剪切以后的图片压缩到固定大小的图片
ImageUtil.reduceImageByWidthHeight(temp_fileName, b_fileName, 200, 200);//200*200大图
ImageUtil.reduceImageByWidthHeight(temp_fileName, m_fileName, 120, 120);//120*120中图
ImageUtil.reduceImageByWidthHeight(temp_fileName, s_fileName, 70, 70);//70*70小图
result = url + fileName;
}
}catch(Exception e){
e.printStackTrace();
}
return result;
}
}
说明:该文件处理流程大致为,1、创建原始文件,将base64编码转换成图片,并强制修改为jpg格式。2、获取图片的压缩比例,并计算剪切图片的长宽和起始坐标。3、按照要求剪切图片。4、将剪切以后的图片压缩到固定大小的图片。
ImageUtil.java
package com.gochina.tc.util;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import sun.misc.BASE64Decoder;
import com.sun.image.codec.jpeg.JPEGCodec;
import com.sun.image.codec.jpeg.JPEGEncodeParam;
import com.sun.image.codec.jpeg.JPEGImageEncoder;
/** * 上传的图片进行截取,压缩处理 * @author hwy * */
public class ImageUtil {
/** * 长高等比例缩小图片 * @param srcImagePath 读取图片路径 * @param toImagePath 写入图片路径 * @param ratio 缩小比例 * @throws IOException */
public static void reduceImageByRatio(String srcImagePath,String toImagePath,float ratio) throws IOException{
FileOutputStream out = null;
try{
//读入文件
File file = new File(srcImagePath);
// 构造Image对象
BufferedImage src = javax.imageio.ImageIO.read(file);
int width = src.getWidth();
int height = src.getHeight();
// 缩小边长
BufferedImage tag = new BufferedImage((int)(width / ratio), (int)(height / ratio), BufferedImage.TYPE_INT_RGB);
// 绘制 缩小后的图片
tag.getGraphics().drawImage(src, 0, 0, (int)(width / ratio), (int)(height / ratio), null);
out = new FileOutputStream(toImagePath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(tag);
param.setQuality(0.75f, true);// 默认0.75
encoder.setJPEGEncodeParam(param);
encoder.encode(tag);
}catch(Exception e){
e.printStackTrace();
}finally{
if(out != null){
out.close();
}
out = null;
System.gc();
}
}
/** * 缩小图片到固定长高 * @param srcImagePath 读取图片路径 * @param toImagePath 写入图片路径 * @param width 缩小后图片宽度 * @param height 缩小后图片长度 * @throws IOException */
public static void reduceImageByWidthHeight(String srcImagePath, String toImagePath, int width, int height) throws IOException{
FileOutputStream out = null;
try{
//读入文件
File file = new File(srcImagePath);
// 构造Image对象
BufferedImage src = javax.imageio.ImageIO.read(file);
// 缩小边长
BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
// 绘制缩小后的图片
tag.getGraphics().drawImage(src, 0, 0, width, height, null);
out = new FileOutputStream(toImagePath);
JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
JPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(tag);
param.setQuality(1f, true);// 默认0.75
encoder.setJPEGEncodeParam(param);
encoder.encode(tag);
}catch(Exception e){
e.printStackTrace();
}finally{
if(out != null){
out.close();
}
out = null;
System.gc();
}
}
/** * 剪切图片 * @param srcpath * @param subpath * @param x * @param y * @param width * @param height * @throws IOException */
public static void cutImage(String srcpath,String subpath,int x,int y,int width,int height) throws IOException {
FileInputStream is = null;
ImageInputStream iis = null;
try {
// 读取图片文件
is = new FileInputStream(srcpath);
Iterator<ImageReader> it = ImageIO.getImageReadersByFormatName("jpg");
ImageReader reader = it.next();
// 获取图片流
iis = ImageIO.createImageInputStream(is);
reader.setInput(iis, true);
ImageReadParam param = reader.getDefaultReadParam();
// 图片裁剪区域。Rectangle 指定了坐标空间中的一个区域,通过 Rectangle 对象的左上顶点的坐标(x,y)、宽度和高度可以定义这个区域。
Rectangle rect = new Rectangle(x, y, width, height);
// 提供一个 BufferedImage,将其用作解码像素数据的目标。
param.setSourceRegion(rect);
BufferedImage bi = reader.read(0, param);
// 保存新图片
ImageIO.write(bi, "jpg", new File(subpath));
}finally {
if(is != null){
is.close();
}
if(iis != null){
iis.close();
}
}
}
/** * 获取图片缩放尺寸 * @param srcImagePath * @return * @throws IOException */
public static float getScaleCutImage(String srcImagePath) throws IOException{
//读入文件
File file = new File(srcImagePath);
// 构造Image对象
BufferedImage src = javax.imageio.ImageIO.read(file);
int width = src.getWidth();
int height = src.getHeight();
float scale = 0f;
if(width >= height){
scale = (float)width/200;
}else if(width < height){
scale = (float)height/200;
}
return scale;
}
/** * @Descriptionmap 对字节数组字符串进行Base64解码并生成图片 * @param base64 图片Base64数据 * @param path 图片路径 * @return */
public static boolean base64ToImage(String base64, String path) {
// 图像数据为空
if(base64 == null){
return false;
}
if(base64.indexOf("base64") != -1){
base64 = base64.substring(base64.indexOf("base64")+7, base64.length());
}
// Base64解码
BASE64Decoder decoder = new BASE64Decoder();
try{
byte[] bytes = decoder.decodeBuffer(base64);
for(int i=0;i<bytes.length;i++){
// 调整异常数据
if(bytes[i] < 0){
bytes[i] += 256;
}
}
// 生成jpeg图片
OutputStream out = new FileOutputStream(path);
out.write(bytes);
out.flush();
out.close();
//非jpg格式的图片强转为jpg
if(base64.indexOf("image/jpeg") == -1){
imageToJPG(path, path);
}
return true;
}catch(Exception e){
e.printStackTrace();
return false;
}
}
/** * 将图片格式转成jpg的支持( GIF->JPG GIF->PNG PNG->GIF(X) PNG->JPG) * @param src1 * @param result * @throws IOException */
public static void imageToJPG(String src1,String result) throws IOException{
File f = new File(src1);
f.canRead();
BufferedImage src = ImageIO.read(f);
ImageIO.write(src, "jpg", new File(result));
}
}
说明:这个工具类注释写的比较详细,也不难看懂。有几个地方需要注意下:
1、imageToJPG()方法,将图片格式转成jpg的暂时只支持( GIF->JPG 、GIF->PNG 、PNG->GIF(X)、 PNG->JPG)这几种形式。
2、base64ToImage()方法,对字节数组字符串进行Base64解码并生成图片,里面前端传过来的数据串会带有类似data:image/png;base64,这段标示,我们需要将这部分截取掉才可以生成图片。
3、getScaleCutImage()方法,计算scale的时候,基数为200,这个要跟前端的图片显示区域保持一致,不然截取的图片就不正确了。
4、reduceImageByRatio()方法,长高等比例缩小图片中, param.setQuality(0.75f, true); 设置图片的质量,这个默认图像质量是0.75,如果想高质量保存,就设置为接近1即可,当然图片容量大小就会跟着变化了。
好了,大功即将告成!先来看下页面的效果如何吧!
当然这个效果比较low,不是很好看,仅仅是个demo而已,再来贴个项目线上的效果图吧,这个不low哦!