ImageIO.write缩放编辑图片后方向错误 出现90度旋转问题
问题原因
- 手机/相机拍摄生成的JPEG图片通常携带EXIF元数据,其中
Orientation字段记录了图片的正确显示方向。直接保存原始上传字节时EXIF信息完整,看图工具、浏览器会自动根据该字段校正图片朝向,所以显示正常。 - 当前缩放逻辑通过
ImageIO读取图片时仅加载传感器输出的原始像素矩阵,不会自动根据EXIF方向做旋转校正;重新编码输出图片时也没有保留、校正EXIF方向信息,最终输出的图片就会出现90度旋转的异常。 - 现有
rescale方法存在逻辑顺序错误:先执行了图片绘制、g.dispose()释放资源操作,之后才设置抗锯齿、插值算法等渲染参数,这些参数完全不会生效。 - 额外兼容问题:代码中输出格式指定为jpg,但使用了
TYPE_INT_ARGB带透明通道的图片类型,JPEG不支持透明通道,部分场景下会出现图片红底、色块异常。
修复步骤
- 读取上传图片的EXIF方向值,根据方向值对原始
BufferedImage做对应的旋转、翻转校正,再执行缩放逻辑。 - 调整
Graphics2D的参数设置顺序:所有渲染参数在绘制图片前配置,绘制完成后再释放资源。 - 输出JPEG格式时统一转成无透明通道的RGB类型,避免色块问题。
修复后代码
如果使用Maven管理依赖,先引入轻量EXIF解析依赖:
<dependency> <groupId>com.drewnoakes</groupId> <artifactId>metadata-extractor</artifactId> <version>2.19.0</version> </dependency>
调整后的图片压缩工具代码:
import com.drew.imaging.ImageMetadataReader; import com.drew.metadata.Metadata; import com.drew.metadata.exif.ExifIFD0Directory; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import javax.imageio.ImageIO; public class CompressImages { // EXIF标准方向值定义 private static final int ORIENTATION_NORMAL = 1; private static final int ORIENTATION_ROTATE_90 = 6; private static final int ORIENTATION_ROTATE_180 = 3; private static final int ORIENTATION_ROTATE_270 = 8; public static byte[] compress(byte[] img) throws IOException { try (InputStream inputStream = new ByteArrayInputStream(img)) { // 读取原始图片像素 BufferedImage originalImage = ImageIO.read(inputStream); // 读取EXIF方向标记 int orientation = getExifOrientation(img); // 按方向校正图片 BufferedImage correctedImage = rotateByExifOrientation(originalImage, orientation); // 执行缩放 return rescale(correctedImage).toByteArray(); } } private static int getExifOrientation(byte[] img) { try (InputStream is = new ByteArrayInputStream(img)) { Metadata metadata = ImageMetadataReader.readMetadata(is); ExifIFD0Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); if (directory != null && directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { return directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); } } catch (Exception e) { // EXIF读取失败默认按正常方向处理 } return ORIENTATION_NORMAL; } private static BufferedImage rotateByExifOrientation(BufferedImage image, int orientation) { int width = image.getWidth(); int height = image.getHeight(); AffineTransform transform = new AffineTransform(); int newWidth = width; int newHeight = height; switch (orientation) { case ORIENTATION_ROTATE_90: transform.rotate(Math.PI / 2, width / 2.0, height / 2.0); transform.translate(0, height - width); newWidth = height; newHeight = width; break; case ORIENTATION_ROTATE_180: transform.rotate(Math.PI, width / 2.0, height / 2.0); transform.translate(-width, -height); break; case ORIENTATION_ROTATE_270: transform.rotate(Math.PI * 3 / 2, width / 2.0, height / 2.0); transform.translate(width - height, 0); newWidth = height; newHeight = width; break; default: return image; } // JPG不支持透明通道,统一使用RGB类型 BufferedImage rotatedImage = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g = rotatedImage.createGraphics(); g.setTransform(transform); g.drawImage(image, 0, 0, null); g.dispose(); return rotatedImage; } private static ByteArrayOutputStream rescale(BufferedImage bi) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); int originalWidth = bi.getWidth(); int originalHeight = bi.getHeight(); // 缩放为原尺寸的1/4 int targetWidth = originalWidth / 4; int targetHeight = originalHeight / 4; BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); Graphics2D g = resizedImage.createGraphics(); // 所有渲染参数在绘制前设置 g.setComposite(AlphaComposite.Src); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 参数配置完成后再执行绘制 g.drawImage(bi, 0, 0, targetWidth, targetHeight, null); g.dispose(); ImageIO.write(resizedImage, "jpg", baos); return baos; } }
如果不想引入第三方依赖,也可以通过JDK自带的
ImageIO接口解析图片元数据获取Orientation字段,只是代码更繁琐,metadata-extractor是目前EXIF兼容性最高的轻量实现,Jar包体积仅300KB左右。
内容的提问来源于stack exchange,提问作者mobiw
相关产品推荐
相关产品推荐

