如何在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
相关产品推荐
相关产品推荐

