最近在做聊天APP项目,通过手机上传照片,直接通过流的形式存储没问题,但是调用压缩功能后,发现压缩后的图片被旋转了如下图
而打开原图却是正常的:
在网络上找了好多资料,一直梳理的不是太清楚,最终结合几个文章总结如何处理这个问题
首先,是因为有的手机,自带了一个图片旋转属性:
本人项目是通过 :MultipartFile上传文件
因此本人先将文件转成临时file:
private File transferToFile(MultipartFile multipartFile) { //选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用MultipartFile.transferto()方法 。 File file = null; try { String originalFilename = multipartFile.getOriginalFilename(); String[] filename = originalFilename.split("\\."); file=File.createTempFile(filename[0], filename[1]); multipartFile.transferTo(file); //这里我把删除临时文件去掉了,为了后面坐旋转用 //file.deleteOnExit(); } catch (IOException e) { e.printStackTrace(); } return file; }
然后获取图片的属性,检查是否有旋转属性,有则旋转:
注意:这里旋转的是临时图片,不是已经上传好的原图,临时图片最后要删除,保留压缩图片
/** * 纠正图片旋转 * * @param srcFile */ public static void correctImg(File srcFile ) { FileOutputStream fos = null; try { //旋转角度 int angle = 0; // 原始图片缓存 BufferedImage srcImg = ImageIO.read(srcFile); int imgWidth = srcImg.getHeight(); // 原始高度 int imgHeight = srcImg.getWidth(); try { Metadata metadata = ImageMetadataReader.readMetadata(srcFile); for (Directory directory: metadata.getDirectories()){ for (Tag tag : directory.getTags()){ //检查确定是否有Orientation 属性,并看是否有Rotate 90 值 System.out.println("getDirectoryName======>"+tag.getDirectoryName()+"========getDescription=====>"+tag.getDescription()); if(tag.getTagName().equals("Orientation") ){ //有90 复制90度 if(tag.getDescription().indexOf("Rotate 90") > -1){ angle = 90; } //270度,都需要旋转 if(tag.getDescription().indexOf("Rotate 270")>0){ angle = 270; } } } if (directory.hasErrors()) { for (String error : directory.getErrors()) { System.err.format("ERROR: %s", error); } } } } catch (Exception e) { System.out.println(e.toString()); } // 中心点位置 double centerWidth = ((double) imgWidth) / 2; double centerHeight = ((double) imgHeight) / 2; // 图片缓存 BufferedImage targetImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB); // 旋转对应角度 Graphics2D g = targetImg.createGraphics(); g.rotate(Math.toRadians(angle), centerWidth, centerHeight); g.drawImage(srcImg, (imgWidth - srcImg.getWidth()) / 2, (imgHeight - srcImg.getHeight()) / 2, null); g.rotate(Math.toRadians(-angle), centerWidth, centerHeight); g.dispose(); // 输出图片 fos = new FileOutputStream(srcFile); ImageIO.write(targetImg, "jpg", fos); System.out.println("图片旋转==>完成!"); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } } }
然后调用压缩图片:
/** * 根据指定大小和指定精度压缩图片 * * @param srcPath 源图片地址 * @param desPath 目标图片地址 * @param desFileSize 指定图片大小,单位kb(压缩到多大以内) * @param accuracy 精度,递归压缩的比率,建议小于0.9 * @param desMaxWidth 目标最大宽度 * @param desMaxHeight 目标最大高度 * @return 目标文件路径 */ public static String compressPicForScale(String srcPath, String desPath, long desFileSize, double accuracy, int desMaxWidth, int desMaxHeight) { if (!new File(srcPath).exists()) { return null; } try { File srcFile = new File(srcPath); long srcFileSize = srcFile.length(); System.out.println("源图片:" + srcPath + ",大小:" + srcFileSize / 1024 + "kb"); //获取图片信息 BufferedImage bim = ImageIO.read(srcFile); int srcWidth = bim.getWidth(); int srcHeight = bim.getHeight(); //先转换成jpg Thumbnails.Builder<File> builder = Thumbnails.of(srcFile).outputFormat("jpg"); // 指定大小(宽或高超出会才会被缩放) if (srcWidth > desMaxWidth || srcHeight > desMaxHeight) { builder.size(desMaxWidth, desMaxHeight); } else { //宽高均小,指定原大小 builder.size(srcWidth, srcHeight); } // 写入到内存 ByteArrayOutputStream bos = new ByteArrayOutputStream(); //字节输出流(写入到内存) builder.toOutputStream(bos); // 获取偏转角度 // int angle = getAngle(srcFile); // System.out.println("目标图片偏转角度111:" + angle); // 递归压缩,直到目标文件大小小于desFileSize byte[] bytes = compressPicCycle(bos.toByteArray(), desFileSize, accuracy); // 输出到文件 File desFile = new File(desPath); FileOutputStream fos = new FileOutputStream(desFile); fos.write(bytes); fos.close(); // //调用旋转检查 // correctImg(desPath); System.out.println("目标图片:" + desPath + ",大小" + desFile.length() / 1024 + "kb"); System.out.println("图片压缩完成!"); } catch (Exception e) { e.printStackTrace(); return null; } return desPath; }
最后删除临时图片:
//删除临时 f.deleteOnExit();
这样就实现了图片的旋转判断,并更正,再压缩保存
其中引入的pom:
<!-- 图片压缩 --> <dependency> <groupId>net.coobird</groupId> <artifactId>thumbnailator</artifactId> <version>0.4.8</version> </dependency> <dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> <version>2.7.0</version> </dependency> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.1</version> </dependency> <dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> <version>2.18.0</version> </dependency>
以下是全代码,入需要保存,自己增加上传文件保存内容
package com.xb.chat.utils; import com.drew.imaging.ImageMetadataReader; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.Tag; import net.coobird.thumbnailator.Thumbnails; import org.springframework.web.multipart.MultipartFile; import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.math.BigDecimal; public class PicYS { private void picUplod(MultipartFile uploadFile){ //文件路径 String upLoadPath = "/data/img/"; //图片名自定义 String ysfileName = "_yasuo.jpg"; //因为有的手机图片是带旋转属性,所以,可以用临时文件方法,先把图片上传,并调整正确位置,再进行压缩,压缩完毕后再删除临时文件的图 File f = this.transferToFile(uploadFile); try { //旋转 this.correctImg(f); //压缩图片 this.compressPicForScale(f.getCanonicalPath(), upLoadPath + ysfileName, 100, 0.8, 200, 200); // 图片小于1000kb //删除临时 f.deleteOnExit(); } catch (Exception e) { System.out.println(e.toString()); } } private File transferToFile(MultipartFile multipartFile) { // 选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。 File file = null; try { String originalFilename = multipartFile.getOriginalFilename(); String[] filename = originalFilename.split("\\."); file=File.createTempFile(filename[0], filename[1]); multipartFile.transferTo(file); //file.deleteOnExit(); } catch (IOException e) { e.printStackTrace(); } return file; } /** * 纠正图片旋转 * * @param srcFile */ public static void correctImg(File srcFile ) { FileOutputStream fos = null; try { //旋转角度 int angle = 0; // 原始图片缓存 BufferedImage srcImg = ImageIO.read(srcFile); int imgWidth = srcImg.getHeight(); // 原始高度 int imgHeight = srcImg.getWidth(); try { Metadata metadata = ImageMetadataReader.readMetadata(srcFile); for (Directory directory: metadata.getDirectories()){ for (Tag tag : directory.getTags()){ //检查确定是否有Orientation 属性,并看是否有Rotate 90 值 System.out.println("getDirectoryName======>"+tag.getDirectoryName()+"========getDescription=====>"+tag.getDescription()); if(tag.getTagName().equals("Orientation") ){ //有90 复制90度 if(tag.getDescription().indexOf("Rotate 90") > -1){ angle = 90; } //270度,都需要旋转 if(tag.getDescription().indexOf("Rotate 270")>0){ angle = 270; } } } if (directory.hasErrors()) { for (String error : directory.getErrors()) { System.err.format("ERROR: %s", error); } } } } catch (Exception e) { System.out.println(e.toString()); } // 中心点位置 double centerWidth = ((double) imgWidth) / 2; double centerHeight = ((double) imgHeight) / 2; // 图片缓存 BufferedImage targetImg = new BufferedImage(imgWidth, imgHeight, BufferedImage.TYPE_INT_RGB); // 旋转对应角度 Graphics2D g = targetImg.createGraphics(); g.rotate(Math.toRadians(angle), centerWidth, centerHeight); g.drawImage(srcImg, (imgWidth - srcImg.getWidth()) / 2, (imgHeight - srcImg.getHeight()) / 2, null); g.rotate(Math.toRadians(-angle), centerWidth, centerHeight); g.dispose(); // 输出图片 fos = new FileOutputStream(srcFile); ImageIO.write(targetImg, "jpg", fos); System.out.println("图片旋转==>完成!"); } catch (Exception e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } } } } /** * 根据指定大小和指定精度压缩图片 * * @param srcPath 源图片地址 * @param desPath 目标图片地址 * @param desFileSize 指定图片大小,单位kb(压缩到多大以内) * @param accuracy 精度,递归压缩的比率,建议小于0.9 * @param desMaxWidth 目标最大宽度 * @param desMaxHeight 目标最大高度 * @return 目标文件路径 */ public static String compressPicForScale(String srcPath, String desPath, long desFileSize, double accuracy, int desMaxWidth, int desMaxHeight) { if (!new File(srcPath).exists()) { return null; } try { File srcFile = new File(srcPath); long srcFileSize = srcFile.length(); System.out.println("源图片:" + srcPath + ",大小:" + srcFileSize / 1024 + "kb"); //获取图片信息 BufferedImage bim = ImageIO.read(srcFile); int srcWidth = bim.getWidth(); int srcHeight = bim.getHeight(); //先转换成jpg Thumbnails.Builder<File> builder = Thumbnails.of(srcFile).outputFormat("jpg"); // 指定大小(宽或高超出会才会被缩放) if (srcWidth > desMaxWidth || srcHeight > desMaxHeight) { builder.size(desMaxWidth, desMaxHeight); } else { //宽高均小,指定原大小 builder.size(srcWidth, srcHeight); } // 写入到内存 ByteArrayOutputStream bos = new ByteArrayOutputStream(); //字节输出流(写入到内存) builder.toOutputStream(bos); // 获取偏转角度 // int angle = getAngle(srcFile); // System.out.println("目标图片偏转角度111:" + angle); // 递归压缩,直到目标文件大小小于desFileSize byte[] bytes = compressPicCycle(bos.toByteArray(), desFileSize, accuracy); // 输出到文件 File desFile = new File(desPath); FileOutputStream fos = new FileOutputStream(desFile); fos.write(bytes); fos.close(); // //调用旋转检查 // correctImg(desPath); System.out.println("目标图片:" + desPath + ",大小" + desFile.length() / 1024 + "kb"); System.out.println("图片压缩完成!"); } catch (Exception e) { e.printStackTrace(); return null; } return desPath; } private static byte[] compressPicCycle(byte[] bytes, long desFileSize, double accuracy) throws IOException { // File srcFileJPG = new File(desPath); long srcFileSizeJPG = bytes.length; // 2、判断大小,如果小于500kb,不压缩;如果大于等于500kb,压缩 if (srcFileSizeJPG <= desFileSize * 1024) { return bytes; } // 计算宽高 BufferedImage bim = ImageIO.read(new ByteArrayInputStream(bytes)); int srcWidth = bim.getWidth(); int srcHeight = bim.getHeight(); int desWidth = new BigDecimal(srcWidth).multiply(new BigDecimal(accuracy)).intValue(); int desHeight = new BigDecimal(srcHeight).multiply(new BigDecimal(accuracy)).intValue(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); //字节输出流(写入到内存) Thumbnails.of(new ByteArrayInputStream(bytes)).size(desWidth, desHeight).outputQuality(accuracy).toOutputStream(bos); return compressPicCycle(bos.toByteArray(), desFileSize, accuracy); } }