TIFF с JPEG-сжатием намного больше оригинального JPEG

Я пытаюсь конвертировать JPEG в TIFF с JPEG-сжатием, используя FreeImage.Net и C#. Это прекрасно работает, однако, для JPGES низкого качества TIFF-файлы намного больше оригинальных. Я предполагаю, что размер TIFF не зависит от исходного качества JPEG, потому что выходные изображения всегда были примерно одинакового размера.

Например (преобразование скриншота):

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

Такое увеличение в размерах не приемлемо для нашей компании, потому что мы и наши клиенты имеем дело с большим количеством документов.

Интересно, что я получаю примерно те же результаты, когда конвертирую изображения с помощью GIMP. Теперь мне интересно: это в соответствии со стандартом TIFF или специально для FreeImage / GIMP? (Я думаю, что оба используют libtiff.dll).

Я полагаю, что есть и другой способ, потому что в нашей компании есть сканер, который производит изображения в формате TIFF со сжатием JPEG и гораздо меньшего размера. Кто-нибудь знает о другой библиотеке (бесплатной или нет), которая может более эффективно обрабатывать это преобразование или достичь этого во 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 = 6 а также PlanarConfiguration = 1 или значения некоторых других полей. Тем не менее, это работает.

Я предполагаю, что моя проблема с другими библиотеками состояла в том, что они создали совершенно новый JPEG с всегда одинаковым качеством (поскольку вы можете установить только сжатие TIFF для JPEG, но не указывать дополнительные параметры) и поместили его в контейнер TIFF.

Теперь я знаю, что сжатие JPEG в формате TIFF - не лучший выбор (оно с потерями, редко и редко поддерживается, кроме TIFF со сжатием JPEG, не лучше, чем обычный файл JPEG). Однако наши клиенты требуют этого. Давайте посмотрим, будет ли описанное выше подходящим решением, или мне удастся найти что-то еще.

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();
        }
    }
}
Другие вопросы по тегам