I am trying to convert JPEGs to TIFFs with JPEG-compression using FreeImage.Net and C#. This works fine, however, for low-quality JPGES the TIFF-files are a lot larger than the original. I assume the TIFF-size does not depend on the original JPEG-Quality, because the output-images were always about the same size.
For example (converting a screenshot):
2065kb JPEG (quality: 100%) --> 1282kb TIFF
379kb JPEG (quality: 50%) --> 1200kb TIFF
This increase in size is not acceptable for our company, because our clients and we are dealing with quite a lot of documents.
Interestingly enough, I get about the same results when I convert the images with GIMP. Now I am wondering: is this according to the TIFF-standard or special to FreeImage/GIMP? (I think both use libtiff.dll).
I suppose there is another way, because we have a scanner in our company that produces images as JPEG-compressed TIFFs with a much smaller size. Does anyone know about another library (either free or not) that can handle this conversion more effectively, or achieve this in FreeImage?
UPDATE:
I took a look at the TIFF 6.0 specification, analyzed the files our scanner produced and was able to write a function that wraps a JPEG into a very simple TIFF container (works also with multiple JPEGs which are merged to a multi-page TIFF).
For those who know a little about TIFF: I produced a new TIFF file (with one or more IFDs, according to the number of images/pages) and wrote the raw data of an existing JPEG image into a single strip (per IFD), using the following fields/entries:
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)
(To obtain the information of the original image, I used FreeImage, but any other image library should work as well.)
I know there could be some pitfalls I don't know about yet. It might not work with any JPEG-file. Also, I'm not sure why I had to use PhotometricInterpretation = 6
and PlanarConfiguration = 1
or some of the other fields' values. However, it works.
I suppose my problem with the other libraries was, that they produced a completely new JPEG with always the same quality (since you can only set TIFF-compression to JPEG but not specify any further options) and wrapped that into a TIFF container.
I know too now, JPEG compression in a TIFF is not exactly the best choice (it's lossy, uncommon and rarely supported, besides a JPEG-compressed TIFF not any better then a regular JPEG file). However, our clients demand that. Let's see if the above is going to be a suitable solution, or if I manage to find sth else.
JPEG-compression is not supported very well. If a library does include this compression type, the settings (like JPEG-quality) are often fixed and the original image is most likely re-compressed. I found a way, however, to wrap the original JPEG in a simple TIFF container (--> see the update in my original question).
Remember: This might not work with any JPEG file! For instance, FreeImage was not able to read a wrapped progressive JPEG.
This is the C#-Code I used:
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();
}
}
}
来源:https://stackoverflow.com/questions/14811496/tiff-with-jpeg-compression-much-larger-than-original-jpeg