Java 中压缩图片并应用 EXIF 旋转信息
如何在 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 数据,旋转图像,并在压缩时确保图像方向正确。
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 信息并在压缩图像时应用正确的方向。这样,我们就能确保压缩后的图像在各个平台上的显示一致,避免了由于旋转方向问题导致的显示错误。