JPEG压缩的TIFF比原始JPEG大得多

我正在尝试使用FreeImage.Net和C#将JPEG转换为使用JPEG压缩的TIFF 。 这很好,但是,对于低质量的JPGES,TIFF文件比原始文件大很多。 我假设TIFF大小不依赖于原始的JPEG质量,因为输出图像总是大致相同。

例如(转换屏幕截图):

2065kb JPEG (quality: 100%) --> 1282kb TIFF 379kb JPEG (quality: 50%) --> 1200kb TIFF 

这种规模的增加对我们公司来说是不可接受的,因为我们的客户和我们正在处理大量文件。

有趣的是,当我用GIMP转换图像时,我得到了相同的结果。 现在我想知道: 这是根据TIFF标准还是特别的FreeImage / GIMP? (我认为两者都使用libtiff.dll )。

我想还有另一种方法,因为我们公司有一台扫描仪可以生成图像为JPEG压缩的TIFF,尺寸更小。 有没有人知道另一个库 (无论是否免费)可以更有效地处理这种转换,或者在FreeImage中实现这一点?

更新:

我看了一下TIFF 6.0规范 ,分析了我们的扫描仪生成的文件,并且能够编写一个将JPEG包装到一个非常简单的TIFF容器中的函数(也可以使用多个JPEG合并到多页TIFF中)。

对于那些对TIFF有所了解的人:我根据图像/页面的数量生成了一个新的TIFF文件(带有一个或多个IFD),并将现有JPEG图像的原始数据写入单个条带(每个IFD),使用以下字段/条目:

 NewSubfileType = 0 ImageWidth = //(width of the original JPEG) ImageLength = //(height of the original JPEG) BitsPerSample = {8, 8, 8} //(count: 3) Compression = 7 //(JPEG) PhotometricInterpretation = 6 //(YCbCr) StripOffsets = //(offset of raw JPEG data, count: 1) SamplesPerPixel = 3 RowsPerStrip = //(height of the original JPEG) StripByteCounts = //(length of raw JPEG data, count: 1) XResolution = //(horizontal resolution of original JPEG data) YResolution = //(vertical resolution of original JPEG data) PlanarConfiguration = 1 (chunky) ResolutionUnit = 2 //(Inch) 

(为了获得原始图像的信息,我使用了FreeImage,但任何其他图像库都应该可以工作。)

我知道可能还有一些我还不知道的陷阱。 它可能不适用于任何JPEG文件。 另外,我不确定为什么我必须使用PhotometricInterpretation = 6PlanarConfiguration = 1或其他一些字段的值。 但是,它的工作原理。

我想我的其他库的问题是,它们产生了一个全新的JPEG,质量始终相同(因为你只能将TIFF压缩设置为JPEG但不指定任何其他选项)并将其包装到TIFF容器中。

我现在也知道,TIFF中的JPEG压缩并不是最好的选择(它有损,不常见且很少支持,除了JPEG压缩的TIFF,不比普通的JPEG文件好)。 但是,我们的客户要求。 让我们看看以上是否是一个合适的解决方案,或者我是否设法找到其他的。

JPEG压缩不受支持。 如果库确实包含此压缩类型,则设置(如JPEG质量)通常是固定的,并且原始图像很可能是重新压缩的。 但是,我找到了一种方法将原始JPEG包装在一个简单的TIFF容器中( – >请参阅原始问题中的更新 )。

请记住:这可能不适用于任何JPEG文件! 例如,FreeImage无法读取包装的渐进式 JPEG。

这是我使用的C#-Code:

 using System; using System.Collections.Generic; using System.IO; using FreeImageAPI; namespace Tiff { static class TiffConverter { ///  /// Wraps a list of JPEG images into a simple multi-page TIFF container. /// (Might not work with all JPEG formats.) ///  /// The JPEG-image to convert ///  public static byte[] WrapJpegs(List jpegs) { if (jpegs == null || jpegs.Count == 0 || jpegs.FindIndex(b => b.Length == 0) > -1) throw new ArgumentNullException("Image data must not be null or empty"); MemoryStream tiffData = new MemoryStream(); BinaryWriter writer = new BinaryWriter(tiffData); uint offset = 8; // size of header, offset to IFD ushort entryCount = 14; // entries per IFD #region IFH - Image file header // magic number if (BitConverter.IsLittleEndian) writer.Write(0x002A4949); else writer.Write(0x4D4D002A); // offset to (first) IFD writer.Write(offset); #endregion IFH #region IFD Image file directory // write image file directories for each jpeg for (int i = 0; offset > 0; i++) { // get data from jpeg with FreeImage FreeImageBitmap jpegImage; try { jpegImage = new FreeImageBitmap(new MemoryStream(jpegs[i])); } catch (Exception ex) { throw new Exception("Could not load image data at index " + i, ex); } if (jpegImage.ImageFormat != FREE_IMAGE_FORMAT.FIF_JPEG) throw new ArgumentException("Image data at index " + i + " is not in JPEG format"); // dta to write in tags uint width = (uint)jpegImage.Width; uint length = (uint)jpegImage.Height; uint xres = (uint)jpegImage.HorizontalResolution; uint yres = (uint)jpegImage.VerticalResolution; // count of entries: writer.Write(entryCount); offset += 6 + 12 * (uint)entryCount; // add lengths of entries, entry-count and next-ifd-offset // TIFF-fields / IFD-entrys: // {TAG, TYPE (3 = short, 4 = long, 5 = rational), COUNT, VALUE/OFFSET} uint[,] fields = new uint[,] { {254, 4, 1, 0}, // NewSubfileType {256, 4, 1, width}, // ImageWidth {257, 4, 1, length}, // ImageLength {258, 3, 3, offset}, // BitsPerSample {259, 3, 1, 7}, // Compression (new JPEG) {262, 3, 1, 6}, //PhotometricInterpretation (YCbCr) {273, 4, 1, offset + 22}, // StripOffsets (offset IFH + entries + values of BitsPerSample & YResolution & XResolution) {277, 3, 1, 3}, // SamplesPerPixel {278, 4, 1, length}, // RowsPerStrip {279, 4, 1, (uint)jpegs[i].LongLength}, // StripByteCounts {282, 5, 1, offset + 6}, // XResolution (offset IFH + entries + values of BitsPerSample) {283, 5, 1, offset + 14}, // YResolution (offset IFH + entries + values of BitsPerSample & YResolution) {284, 3, 1, 1}, // PlanarConfiguration (chunky) {296, 3, 1, 2} // ResolutionUnit }; // write fields for (int f = 0; f < fields.GetLength(0); f++) { writer.Write((ushort)fields[f, 0]); writer.Write((ushort)fields[f, 1]); writer.Write(fields[f, 2]); writer.Write(fields[f, 3]); } // offset of next IFD if (i == jpegs.Count - 1) offset = 0; else offset += 22 + (uint)jpegs[i].LongLength; // add values (of fields) length and jpeg length writer.Write(offset); #region values of fields // BitsPerSample writer.Write((ushort)8); writer.Write((ushort)8); writer.Write((ushort)8); // XResolution writer.Write(xres); writer.Write(1); // YResolution writer.Write(yres); writer.Write(1); #endregion values of fields // actual image data writer.Write(jpegs[i]); } #endregion IFD writer.Close(); return tiffData.ToArray(); } } }