当前位置: 首页 > article >正文

Java 中压缩图片并应用 EXIF 旋转信息

如何在 Java 中压缩图片并应用 EXIF 旋转信息

在图像处理中,特别是当你需要处理从相机或手机获取的照片时,图像的方向是一个常见问题。许多相机和手机在拍摄照片时会存储图像的方向信息,通常会保存在图像的 EXIF 元数据 中。Windows 和其他图像查看器会根据这些 EXIF 数据自动调整图像的显示方向,但 Java 默认并不会应用这些旋转信息。因此,在进行图像压缩时,必须确保图像的方向与 Windows 等系统中的预览一致。

在本文中,我们将探讨如何在 Java 中处理图像压缩,同时自动应用 EXIF 中的旋转信息,使得压缩后的图像方向正确。

关键步骤

  1. 读取 EXIF 数据:提取图片的 EXIF 元数据中的 Orientation 标签。
  2. 根据 EXIF 方向旋转图像:如果需要,旋转图像至正确的方向。
  3. 压缩图像:在旋转后的图像上执行压缩操作,确保图像尺寸符合需求。
  4. 保存压缩图像:保存压缩后的图像到目标文件。

依赖库

为了读取 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();
        }
    }
}

代码解析

  1. 读取 EXIF 数据
    我们通过 metadata-extractor 库来读取图像的 EXIF 元数据,特别是 Orientation 标签。Orientation 标签会告诉我们图像的正确方向。常见的 Orientation 值有:

    • 1:无旋转
    • 3:旋转 180°
    • 6:旋转 90° 顺时针
    • 8:旋转 270° 顺时针
  2. 旋转图像
    根据 EXIF 中的 Orientation 信息,我们用 rotateImageBasedOnOrientation 方法来旋转图像。如果 Orientation 为 6 或 8,表示图像需要旋转 90° 或 270°,如果是 3,表示需要旋转 180°。

  3. 压缩 JPG 图像
    使用 ImageWriter 将旋转后的图像压缩并保存,压缩质量通过 setCompressionQuality 方法控制。设置为 0.3f 表示较高的压缩比。

  4. 处理 PNG 图像
    对于 PNG 图像,我们直接保存,不进行旋转,也不改变其尺寸。

总结

在这篇文章中,我们探讨了如何在 Java 中处理图像压缩,同时确保图像应用 EXIF 中的旋转信息。这对于来自相机或手机的照片尤为重要,因为这些设备通常会存储旋转信息,Windows 等操作系统会根据该信息自动旋转图像。而 Java 默认不会应用这些旋转信息,因此我们需要在处理图像时手动调整方向。

通过使用 metadata-extractor 库,我们能够读取 EXIF 信息并在压缩图像时应用正确的方向。这样,我们就能确保压缩后的图像在各个平台上的显示一致,避免了由于旋转方向问题导致的显示错误。


http://www.kler.cn/a/453209.html

相关文章:

  • HTMLCSS:超炫丝滑的卡片水波纹效果
  • 16 循环语句——for循环
  • YOLOv11 引入高效的可变形卷积网络 DCNv4 | 重新思考用于视觉应用的动态和稀疏算子
  • 网络安全公司150强
  • 【Linux】Linux开发利器:make与Makefile自动化构建详解
  • iClient3D for Cesium在Vue中快速实现场景卷帘
  • .NET能做什么?全面解析.NET的应用领域
  • MPLS小实验:利用LDP动态建立LSP
  • c# 线程 AutoResetEvent 的Set()函数多次调用
  • JavaWeb 开发基础入门
  • VIVO C++开发面试题及参考答案
  • 穷举vs暴搜vs深搜vs回溯vs剪枝系列一>电话号码的字母组合
  • 一文大白话讲清楚javascript单点登录
  • Vue.js 高级组件开发:设计模式与实践
  • Huggingface下载模型的几种方式
  • 文件解析漏洞中间件(iis和Apache)
  • 01-linux基础命令
  • Android 13 非 Launcher 应用开机启动:通过监听开机广播实现
  • linux下搭建lamp环境(dvwa)
  • Qt 应用程序转换为服务
  • MySQL基础-事务
  • 代码随想录算法【Day2】
  • Docker Run使用方法及参数详细说明
  • 【mysql】id主键列乱了之后,重新排序(可根据日期顺序)
  • 4.5 数据表的外连接
  • 【c++笔试强训】(第四十五篇)