【问题标题】:TIFF with JPEG-compression much larger than original JPEGJPEG 压缩比原始 JPEG 大得多的 TIFF
【发布时间】:2013-01-26 12:23:57
【问题描述】:

我正在尝试使用 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 specification,分析了我们的扫描仪生成的文件,并能够编写一个将 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 图像的插值器。等效的 .NET 属性是 Graphics.InterpolationMode
  • @HansPassant 这听起来很有希望。如果在转换过程中确实进行了插值,这将解释生成的 tiff 的大小大致相等。但是,到目前为止,我还没有找到关闭它的方法。我想问题是,FreeImage(或其他库)将图像加载为像素矩阵,并在将其保存为 tiff 时再次使用 jpeg-compression 压缩它,而不是直接将原始文件嵌入到 tiff-container 中。后者是我想做的。

标签: c# image-processing jpeg tiff freeimage


【解决方案1】:

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

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

这是我使用的 C# 代码:

using System;
using System.Collections.Generic;
using System.IO;
using FreeImageAPI;

namespace Tiff
{
    static class TiffConverter
    {
        /// <summary>
        /// <para>Wraps a list of JPEG images into a simple multi-page TIFF container.</para>
        /// <para>(Might not work with all JPEG formats.)</para>
        /// </summary>
        /// <param name="jpegs">The JPEG-image to convert</param>
        /// <returns></returns>
        public static byte[] WrapJpegs(List<byte[]> 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();
        }
    }
}

【讨论】:

  • 我在我的程序中使用了它,效果很好。你能解释一下为什么 x 和 y 分辨率的值是这样的吗?
  • @geometrikal 在编写 TIFF 标头时,对于超过特定大小(在本例中为 x/y 分辨率)的字段类型,您只需将偏移量写入文件中存储实际值的位置。正如您在代码中进一步看到的那样,xres/yres 的值是在写入标头之后写入的。这有帮助吗?
  • 谢谢。我很难解决为什么我使用此代码创建的 TIFF 无法在 image magick 中打开(但我仍然可以在 python 和其他一些应用程序中读取它),因此试图理解它。原来我编码成 JPEG 的帧之一来自灰度位图。在编码修复之前将位图的颜色空间转换为 BGR。再次感谢这段代码,为我们节省了大量的网络驱动器空间。 :)
  • @geometrikal 不客气。这是高度实验性的。部分基于 tiff 规范,部分只是反复试验。因此,如果还有很多问题需要解决,我并不感到惊讶。
猜你喜欢
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 2015-08-10
  • 2023-03-17
  • 2010-09-22
  • 1970-01-01
相关资源
最近更新 更多