You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Java中生成无重压缩的JPEG-in-TIFF格式文件

实现JPEG-in-TIFF封装(不改动原始JPEG数据)

要在Java中实现将JPEG直接封装为TIFF格式(避免解压缩/重压缩导致的质量损失),核心是完整保留原始JPEG字节数据,并构造符合TIFF规范的文件结构与必要标签。以下提供两种可行方案:

方案一:使用Apache Commons Imaging库(简单高效)

Apache Commons Imaging(原Sanselan)提供了封装JPEG到TIFF的便捷API,无需手动处理TIFF底层结构。

1. 添加依赖

Maven项目中引入依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-imaging</artifactId>
    <version>1.0-alpha3</version>
</dependency>

2. 代码实现

import org.apache.commons.imaging.ImageFormats;
import org.apache.commons.imaging.ImageInfo;
import org.apache.commons.imaging.Imaging;
import org.apache.commons.imaging.formats.tiff.TiffOutputSet;
import org.apache.commons.imaging.formats.tiff.constants.TiffTagConstants;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteOrder;

public class JpegToTiff {
    public static void main(String[] args) throws IOException {
        File inputJpeg = new File("input.jpg");
        File outputTiff = new File("output.tif");

        // 读取JPEG元信息(宽高、颜色类型,不解压缩图像)
        ImageInfo jpegInfo = Imaging.getImageInfo(inputJpeg);
        int width = jpegInfo.getWidth();
        int height = jpegInfo.getHeight();
        
        // 确定光度解释标签值(RGB为2,灰度为1)
        int photometricType = jpegInfo.getColorType() == ImageInfo.ColorType.RGB 
            ? TiffTagConstants.PHOTOMETRIC_INTERPRETATION_RGB 
            : TiffTagConstants.PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO;

        // 读取完整原始JPEG字节
        byte[] jpegRawBytes = Imaging.readAllBytes(inputJpeg);

        // 构造TIFF输出配置
        TiffOutputSet tiffConfig = new TiffOutputSet(ByteOrder.LITTLE_ENDIAN);
        var rootDir = tiffConfig.addRootDirectory();
        
        // 添加TIFF必填标签
        rootDir.add(TiffTagConstants.TAG_IMAGE_WIDTH, width);
        rootDir.add(TiffTagConstants.TAG_IMAGE_LENGTH, height);
        rootDir.add(TiffTagConstants.TAG_COMPRESSION, TiffTagConstants.COMPRESSION_JPEG);
        rootDir.add(TiffTagConstants.TAG_PHOTOMETRIC_INTERPRETATION, photometricType);

        // 写入TIFF文件
        try (FileOutputStream fos = new FileOutputStream(outputTiff)) {
            Imaging.writeImage(jpegRawBytes, fos, ImageFormats.TIFF, tiffConfig.getMetadata());
        }

        System.out.println("JPEG-in-TIFF文件已生成:" + outputTiff.getAbsolutePath());
    }
}

方案二:手动构造TIFF结构(理解底层原理)

如果不想依赖第三方库,可以手动构造TIFF文件头、IFD(图像文件目录)和标签,直接写入原始JPEG数据。

代码实现

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Iterator;

public class ManualJpegToTiff {
    public static void main(String[] args) throws IOException {
        File inputJpeg = new File("input.jpg");
        File outputTiff = new File("output_manual.tif");

        // 1. 获取JPEG宽高(不解压缩)
        int width, height;
        try (ImageInputStream iis = ImageIO.createImageInputStream(inputJpeg)) {
            Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName("jpeg");
            if (!readers.hasNext()) throw new IOException("无可用JPEG阅读器");
            
            ImageReader reader = readers.next();
            reader.setInput(iis);
            width = reader.getWidth(0);
            height = reader.getHeight(0);
            reader.dispose();
        }

        // 2. 读取原始JPEG字节
        byte[] jpegRawBytes = java.nio.file.Files.readAllBytes(inputJpeg.toPath());
        int jpegByteLength = jpegRawBytes.length;

        // 3. 构造TIFF文件头(小端字节序)
        ByteBuffer tiffHeader = ByteBuffer.allocate(8);
        tiffHeader.order(ByteOrder.LITTLE_ENDIAN);
        tiffHeader.put((byte) 'I').put((byte) 'I'); // 小端标识
        tiffHeader.putShort((short) 42); // TIFF魔数
        tiffHeader.putInt(8); // 第一个IFD的偏移位置

        // 4. 构造IFD0(图像文件目录)
        int tagCount = 6; // 6个必填标签
        ByteBuffer ifdBuffer = ByteBuffer.allocate(2 + tagCount * 12 + 4);
        ifdBuffer.order(ByteOrder.LITTLE_ENDIAN);
        ifdBuffer.putShort((short) tagCount); // 标签数量

        // 标签1:图像宽度(256)
        writeTiffTag(ifdBuffer, 256, 3, 1, width);
        // 标签2:图像高度(257)
        writeTiffTag(ifdBuffer, 257, 3, 1, height);
        // 标签3:压缩方式(259,值6表示JPEG)
        writeTiffTag(ifdBuffer, 259, 2, 1, 6);
        // 标签4:光度解释(262,RGB为2,灰度为1)
        writeTiffTag(ifdBuffer, 262, 2, 1, 2);
        // 标签5:条带偏移(273,指向JPEG数据的起始位置)
        int stripOffset = 8 + ifdBuffer.capacity();
        writeTiffTag(ifdBuffer, 273, 3, 1, stripOffset);
        // 标签6:条带字节数(279,JPEG数据长度)
        writeTiffTag(ifdBuffer, 279, 3, 1, jpegByteLength);

        ifdBuffer.putInt(0); // 下一个IFD偏移为0(表示最后一个)

        // 5. 写入所有数据到文件
        try (FileOutputStream fos = new FileOutputStream(outputTiff)) {
            fos.write(tiffHeader.array());
            fos.write(ifdBuffer.array());
            fos.write(jpegRawBytes);
        }

        System.out.println("手动构造的JPEG-in-TIFF文件已生成:" + outputTiff.getAbsolutePath());
    }

    // 辅助方法:写入TIFF标签(标签ID、类型、计数、值)
    private static void writeTiffTag(ByteBuffer buffer, int tagId, int type, int count, long value) {
        buffer.putShort((short) tagId);
        buffer.putShort((short) type);
        buffer.putInt(count);
        
        switch (type) {
            case 2: // 短整数(2字节)
                buffer.putShort((short) value);
                buffer.putShort((short) 0); // 填充到4字节
                break;
            case 3: // 长整数(4字节)
                buffer.putInt((int) value);
                break;
        }
    }
}

关键注意事项

  • 光度解释标签:如果处理灰度JPEG,需将标签值从2改为1(WhiteIsZero),否则部分阅读器会显示异常。
  • 字节序:TIFF支持小端(II)和大端(MM),小端是更通用的选择。
  • 验证文件有效性:可使用ImageMagick命令identify -verbose output.tif检查,确认压缩方式为JPEG,且图像数据大小与原JPEG一致。

内容的提问来源于stack exchange,提问作者Doc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 12:40:56