如何在 Java 中压缩图片并应用 EXIF 旋转信息
在图像处理中,特别是当你需要处理从相机或手机获取的照片时,图像的方向是一个常见问题。许多相机和手机在拍摄照片时会存储图像的方向信息,通常会保存在图像的 EXIF 元数据 中。Windows 和其他图像查看器会根据这些 EXIF 数据自动调整图像的显示方向,但 Java 默认并不会应用这些旋转信息。因此,在进行图像压缩时,必须确保图像的方向与 Windows 等系统中的预览一致。
在本文中,我们将探讨如何在 Java 中处理图像压缩,同时自动应用 EXIF 中的旋转信息,使得压缩后的图像方向正确。
关键步骤
- 读取 EXIF 数据:提取图片的 EXIF 元数据中的
Orientation
标签。 - 根据 EXIF 方向旋转图像:如果需要,旋转图像至正确的方向。
- 压缩图像:在旋转后的图像上执行压缩操作,确保图像尺寸符合需求。
- 保存压缩图像:保存压缩后的图像到目标文件。
依赖库
为了读取 EXIF 元数据,我们使用了 metadata-extractor 库。这个库能够帮助我们从图片中提取 EXIF 信息,尤其是 Orientation
标签。
Maven 依赖
如果你使用 Maven,可以在 pom.xml
文件中添加以下依赖:
<dependency><groupId>com.drewnoakes</groupId><artifactId>metadata-extractor</artifactId><version>2.16.0</version>
</dependency>
示例代码
以下是一个 Java 程序,它展示了如何读取图像的 EXIF 数据,旋转图像,并在压缩时确保图像方向正确。
java">import com.drewnoakes.metadata.exif.ExifDirectoryBase;
import com.drewnoakes.metadata.metadata.Directory;
import com.drewnoakes.metadata.metadata.Metadata;
import com.drewnoakes.metadata.extractor.ImageMetadataReader;import javax.imageio.*;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;public class ImageCompressorWithOrientation {public static void main(String[] args) throws Exception {File inputImage = new File("path_to_your_input_image.jpg");File outputImage = new File("path_to_your_output_image.jpg");compressImage(inputImage, outputImage);}public static void compressImage(File inputImage, File outputImage) throws Exception {BufferedImage image = ImageIO.read(inputImage);// 获取文件扩展名String extension = getFileExtension(inputImage);if ("jpg".equalsIgnoreCase(extension) || "jpeg".equalsIgnoreCase(extension)) {// 读取 EXIF 信息并根据 Orientation 旋转图像int orientation = getExifOrientation(inputImage);image = rotateImageBasedOnOrientation(image, orientation);// 压缩 JPG 文件compressJpgImage(image, outputImage);} else if ("png".equalsIgnoreCase(extension)) {// 对 PNG 文件进行无损压缩保存,不改变尺寸ImageIO.write(image, "png", outputImage);}}// 获取文件扩展名private static String getFileExtension(File file) {String filename = file.getName();int dotIndex = filename.lastIndexOf(".");if (dotIndex > 0) {return filename.substring(dotIndex + 1);}return "";}// 获取 EXIF 中的 Orientation 信息public static int getExifOrientation(File imageFile) throws Exception {Metadata metadata = ImageMetadataReader.readMetadata(imageFile);for (Directory directory : metadata.getDirectories()) {if (directory.containsTag(ExifDirectoryBase.TAG_ORIENTATION)) {return directory.getInt(ExifDirectoryBase.TAG_ORIENTATION);}}return 1; // 默认值,如果没有 Orientation 信息,认为是正向}// 根据 EXIF Orientation 信息旋转图像public static BufferedImage rotateImageBasedOnOrientation(BufferedImage image, int orientation) {BufferedImage rotatedImage = image;switch (orientation) {case 3:rotatedImage = rotateImage(image, 180);break;case 6:rotatedImage = rotateImage(image, 90);break;case 8:rotatedImage = rotateImage(image, 270);break;default:// 如果 Orientation 为 1,表示无需旋转break;}return rotatedImage;}// 旋转图像的方法public static BufferedImage rotateImage(BufferedImage img, int angle) {double radians = Math.toRadians(angle);int width = img.getWidth();int height = img.getHeight();int newWidth = (int) Math.abs(width * Math.cos(radians)) + (int) Math.abs(height * Math.sin(radians));int newHeight = (int) Math.abs(height * Math.cos(radians)) + (int) Math.abs(width * Math.sin(radians));BufferedImage rotatedImg = new BufferedImage(newWidth, newHeight, img.getType());Graphics2D g = rotatedImg.createGraphics();g.translate((newWidth - width) / 2, (newHeight - height) / 2);g.rotate(radians, width / 2, height / 2);g.drawImage(img, 0, 0, null);g.dispose();return rotatedImg;}// 压缩 JPG 图像public static void compressJpgImage(BufferedImage image, File outputImage) throws IOException {ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();try (ImageOutputStream ios = ImageIO.createImageOutputStream(outputImage)) {writer.setOutput(ios);ImageWriteParam param = writer.getDefaultWriteParam();if (param.canWriteCompressed()) {param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);param.setCompressionQuality(0.3f); // 压缩质量,0.0 最小质量,1.0 最高质量}// 将压缩图像写入文件writer.write(null, new javax.imageio.IIOImage(image, null, null), param);} finally {writer.dispose();}}
}
代码解析
-
读取 EXIF 数据:
我们通过metadata-extractor
库来读取图像的 EXIF 元数据,特别是Orientation
标签。Orientation
标签会告诉我们图像的正确方向。常见的Orientation
值有:1
:无旋转3
:旋转 180°6
:旋转 90° 顺时针8
:旋转 270° 顺时针
-
旋转图像:
根据 EXIF 中的Orientation
信息,我们用rotateImageBasedOnOrientation
方法来旋转图像。如果Orientation
为 6 或 8,表示图像需要旋转 90° 或 270°,如果是 3,表示需要旋转 180°。 -
压缩 JPG 图像:
使用ImageWriter
将旋转后的图像压缩并保存,压缩质量通过setCompressionQuality
方法控制。设置为 0.3f 表示较高的压缩比。 -
处理 PNG 图像:
对于 PNG 图像,我们直接保存,不进行旋转,也不改变其尺寸。
总结
在这篇文章中,我们探讨了如何在 Java 中处理图像压缩,同时确保图像应用 EXIF 中的旋转信息。这对于来自相机或手机的照片尤为重要,因为这些设备通常会存储旋转信息,Windows 等操作系统会根据该信息自动旋转图像。而 Java 默认不会应用这些旋转信息,因此我们需要在处理图像时手动调整方向。
通过使用 metadata-extractor
库,我们能够读取 EXIF 信息并在压缩图像时应用正确的方向。这样,我们就能确保压缩后的图像在各个平台上的显示一致,避免了由于旋转方向问题导致的显示错误。