业务场景
1、用户未设置头像时,根据一张大图来随机获取图上的的一个区域来作为用户的默认头像
解决思路
思路1:将大图平均分割成小图,然后保存起来,提供程序读取对应的地址。
思路2:动态读取一张网络图片,根据所需的位置裁剪写入http的返回体中。
参考文档
1、javax.imageio.ImageIO;
2、java.awt.image.BufferedImage;
程序代码
1、图片裁剪的工具类
package tool;
import dto.http.HttpByteArray;
import org.springframework.util.StringUtils;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.List;
/**
* Created by yuyu on 2018/3/28.
* 裁剪图片相关函数
* 根据 java.awt.image BufferedImage.getSubimage(int x, int y, int w, int h)
*/
public class CropPictureBuilder {
/**
* 将图片裁剪成等分,保存成文件
*
* 判断传入名称的图片长度是否符合裁剪
* 根据裁剪的大小对图片进行裁剪,不合适的部分不裁剪
*
* 返回裁剪成功的图片个数
* @param fileName 需要裁剪的文件
* @param width 裁剪后每张图片的框都
* @param height 裁剪后每张图片的高度
* @return
* @throws Exception
*/
public static Integer cutToFile(String fileName, Integer width, Integer height) throws Exception {
FileInputStream fileInput = null;
Integer back=0;
try {
//检验传入的拆分大小合法
if (width==null||height==null||width<=0||height<=0){
throw new Exception("拆分图片异常,传入裁剪尺寸必须大于零!");
}
//基础数据
String format = CropPictureBuilder.getFormatName(fileName);//图片类型
Integer imageWidth;//图片宽度
Integer imageHeight;//图片高度
Integer imageCutX = 0;//图片开始裁剪的x轴
Integer imageCutY = 0;//图片开始裁剪的Y轴
BufferedImage subImage;
File subFile;
File imageFile = new File(fileName);
//判断文件是否存在
if (!imageFile.exists()){
throw new Exception("拆分图片异常,图片文件不存在!");
}
fileInput = new FileInputStream(imageFile);
BufferedImage sourceImg = ImageIO.read(fileInput);
//获取图片信息
imageWidth = sourceImg.getWidth();
imageHeight = sourceImg.getHeight();
//判断传入的长度是否满足裁剪
while (imageCutX + width <= imageWidth && imageCutY + height <= imageHeight) {
//裁剪图片
subImage = sourceImg.getSubimage(imageCutX, imageCutY, width, height);
subFile = FileNameBuilder.getFileByName(fileName +"-"+ back +"-x-" + imageCutX +
"-y-" + imageCutY + "." + format);
//写入文件
ImageIO.write(subImage, format, subFile);
back++;
//偏移下一个位置
imageCutX += width;
//判断下一个要裁剪的,是否跳转下一行
if (imageCutX + width > imageWidth && imageCutY + height <= imageHeight) {
imageCutX = 0;
imageCutY += height;
}
}
} catch (Exception e) {
throw e;
} finally {
if (fileInput != null) {
fileInput.close();
}
}
return back;
}
/**
* 根据传进来信息来获取对应的图片,将图片写到http返回体中
*
* @param id 图片的位置[1,length]
* @param width 裁剪图片的宽
* @param height 裁剪图片的高度
* @param pictureUrl 网络图片的地址
* @param response http返回体
*/
public static void cutToResponse(Integer id,Integer width, Integer height
,String pictureUrl,HttpServletResponse response) throws Exception{
try{
//获取数据
HttpByteArray data = GetUrlData.getByte(pictureUrl);
BufferedImage image = ImageIO.read(new ByteArrayInputStream(data.getData()));
//判断是否图片
if (image==null){
throw new Exception("目标图片获取失败!-pictureUrl-"+pictureUrl);
}
//获取图片信息
Integer pictureWidth=image.getWidth();
Integer pictureHeight=image.getHeight();
Integer pictureX=0;
Integer pictureY=0;
Integer index=0;
//判断传入的长度是否满足裁剪
while (pictureX + width <= pictureWidth && pictureY + height <= pictureHeight) {
index++;
//判断是否是获取到的目标位置
if (index.equals(id)){
BufferedImage subImage = image.getSubimage(pictureX, pictureY, width, height);
//写入文件
ImageIO.write(subImage, "jpg", response.getOutputStream());
return;
}
//偏移下一个位置
pictureX += width;
//判断下一个要裁剪的,是否跳转下一行
if (pictureX + width > pictureWidth && pictureY + height <= pictureHeight) {
pictureX = 0;
pictureY += height;
}
}
//没有匹配到对应图片的话抛出异常
throw new Exception("目标图片获取失败!-pictureWidth-"+pictureWidth
+"-pictureHeight-"+pictureHeight+"cutWidth"+width
+"cutHeight"+height+"-index-"+index+"-id-"+id);
}catch (Exception e){
throw e;
}
}
/**
* 获取文件的后缀名
*
* @param fileName 一个有后缀名的文件名(.*)
* @return
* @throws Exception
*/
private static String getFormatName(String fileName) throws Exception {
try {
//判断文件名是否合法
if (StringUtils.isEmpty(fileName)) {
throw new Exception("获取文件后缀名传入,文件名为空!");
}
//寻找文件后缀名
String regex = "\\.[a-zA-Z]+";
String replace = "\\.";
//只获取最后一个后缀名
List<String> find = RegexFinder.getAllToListByReplaceEmpty(regex, fileName, replace);
//判断是否匹配后缀名
if (find == null || find.size() == 0) {
throw new Exception("获取文件后缀名失败!文件没有后缀名-fileName-" + fileName);
} else {
//文件以最后一个后缀名有效
return find.get(find.size() - 1);
}
} catch (Exception e) {
throw e;
}
}
}
2、正则辅助
/**
* 获取正则匹配全部可能,到list数据,替换字符中得数据
* @param regex
* @param info
* @param replace
* @return
*/
public static List<String> getAllToListByReplaceEmpty(String regex,String info
, String replace) {
//数据安全校验
if(StringUtils.isEmpty(regex)||StringUtils.isEmpty(info)){
return null;
}
List<String> back=new ArrayList<String>();
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(info);
while (matcher.find()){
back.add(matcher.group().replaceAll(replace,""));
}
return back;
}
3、网络数据获取
/**
* 根据传入的url将结果以byte[]的数据返回
* @param url 需要请求的url
* @return
* @throws Exception
*/
public static HttpByteArray getByte(String url)throws Exception{
//当传入的url返回不为空的时候,读取数据
InputStream input;
if(StringUtils.isNotBlank(url)){
try{
URLConnection connection=GetUrlData.getConnection(url,null);
//获取请求回来的信息
//获取头信息
String type= connection.getHeaderField("Content-Type");
input = connection.getInputStream();
//解决inputStream.available(),读取图片不完整问题
//此方法在内部缓冲输入,因此不需要使用BufferedInputStream。
byte[] data = IOUtils.toByteArray(input);
//关闭读取流
input.close();
//拼接返回数据获取
return new HttpByteArray(type,data);
}catch(Exception e){
throw new Exception("读取Url数据失败:"+url,e);
}
}
return null;
}
/**
* 根据传入的数据获取URLConnection对象
* @param url
* @param mapArgs
* @return
* @throws Exception
*/
public static URLConnection getConnection(String url, Map<String,String> mapArgs)throws Exception{
//设置请求的头信息
URL urlInfo = new URL(url);
URLConnection connection = urlInfo.openConnection();
//设置传入的头信息
if (mapArgs!=null){
for(String key:mapArgs.keySet()){
connection.addRequestProperty(key,mapArgs.get(key));
}
}
//设置默认头信息
connection.addRequestProperty("Host", urlInfo.getHost());
connection.addRequestProperty("Connection", "keep-alive");
connection.addRequestProperty("Cache-Control", "max-age=0");
connection.addRequestProperty("Upgrade-Insecure-Requests", "1");
//表示用户不愿意目标站点追踪用户个人信息。
connection.addRequestProperty("DNT", "1");
//强制要求缓存服务器在返回缓存的版本之前将请求提交到源头服务器进行验证。
connection.addRequestProperty("Pragma", "no-cache");
connection.addRequestProperty("Accept", "*/*");
connection.addRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36");
connection.addRequestProperty("Referer", "http://"+urlInfo.getHost());
connection.connect();
return connection;
}
4、网络数据封装HttpByteArray
public class HttpByteArray {
private String contentType;//http返回类型
private byte[] data;//http返回字节数组
public HttpByteArray(String contentType, byte[] data) {
this.contentType = contentType;
this.data = data;
}
//get,set略
}
测试代码
思路1测试:将图片分割成等分保存成文件
package tool;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Created by yuyu on 2018/3/28.
* 测试裁剪图片
*/
public class CropPictureBuilderTest {
@Test
public void testCut()throws Exception {
System.out.println(CropPictureBuilder.cutToFile("E:/vs/picture.jpg",40,40));
}
}
测试结果截图:
思路2测试:将获取图片写成接口形式
package web;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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 tool.CropPictureBuilder;
import javax.servlet.http.HttpServletResponse;
/**
* Created by yuyu on 2018/3/29.
* 用户相关
*/
@Controller
@RequestMapping("/User")
public class UserController {
private final Logger logger= LoggerFactory.getLogger(this.getClass());
/**
* 根据传入的参数获取特定位置用户头像
*
* 将目标的用户图片读入图片流,然后裁剪对应的图片,写入http返回中
*/
@RequestMapping(value="/{id}/{width}/{height}/Picture",
method = RequestMethod.GET)
public void getUserImage(@PathVariable("id") Integer id
,@PathVariable("width") Integer width
,@PathVariable("height") Integer height
, HttpServletResponse response){
try{
String Url="http://www.dobeone.com/user.jpg";
//设置浏览器缓存
response.setHeader("Cache-Control", "max-age=31536000");
CropPictureBuilder.cutToResponse(id,width,height,Url,response);
}catch (Exception e){
response.setStatus(404);
e.printStackTrace();
}
}
}
测试结果截图:
总结
1、获取部分图片的时候,以上程序读取图片的顺序时时按行级优先,以及从上至下的
2、生成用户图片链接地址的时候,记得不要超过图片的读取范围