[Mono-list] TIFF Images

Jonathan Gilbert 2a5gjx302 at sneakemail.com
Sun Aug 27 00:55:58 EDT 2006


At 11:38 AM 21/08/2006 -0700, you wrote:
>Anyone know of a C# TIFF image reader and writer?  Any that can read/write
>TIFF Tag information.

Perhaps you will find the attached useful. :-)

Jonathan Gilbert
-------------- next part --------------
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Text;

namespace ProfileTool
{
  public enum TIFFColourSpace
  {
    RGB,
    CMYK,
    Lab,
  }

  public class TIFFImageInfo
  {
    public TIFFColourSpace ColourSpace;
    public int BitsPerComponent;
    public Bitmap BitmapData;
  }

  public class TIFF
  {
    public static TIFFImageInfo LoadTIFF(string filename)
    {
      FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);

      try
      {
        return LoadTIFF(file);
      }
      finally
      {
        file.Close();
      }
    }

    public static TIFFImageInfo LoadTIFF(Stream seekable_stream)
    {
      return LoadTIFF(seekable_stream, 0);
    }

    struct TIFFHeader
    {
      public bool IntelByteOrder;
      public int IFDOffset;
    }

    enum TIFFTag : ushort
    {
      NewSubfileType              = 0x00FE,
      SubfileType                 = 0x00FF,
      ImageWidth                  = 0x0100,
      ImageLength                 = 0x0101,
      BitsPerSample               = 0x0102,
      Compression                 = 0x0103,
      PhotometricInterpretation   = 0x0106,
      Threshholding               = 0x0107,
      CellWidth                   = 0x0108,
      CellLength                  = 0x0109,
      FillOrder                   = 0x010A,
      DocumentName                = 0x010D,
      ImageDescription            = 0x010E,
      Make                        = 0x010F,
      Model                       = 0x0110,
      StripOffsets                = 0x0111,
      Orientation                 = 0x0112,
      SamplesPerPixel             = 0x0115,
      RowsPerStrip                = 0x0116,
      StripByteCounts             = 0x0117,
      MinSampleValue              = 0x0118,
      MaxSampleValue              = 0x0119,
      XResolution                 = 0x011A,
      YResolution                 = 0x011B,
      PlanarConfiguration         = 0x011C,
      PageName                    = 0x011D,
      XPosition                   = 0x011E,
      YPosition                   = 0x011F,
      FreeOffsets                 = 0x0120,
      FreeByteCounts              = 0x0121,
      GrayResponseUnit            = 0x0122,
      GrayResponseCurve           = 0x0123,
      T4Options                   = 0x0124,
      T6Options                   = 0x0125,
      ResolutionUnit              = 0x0128,
      PageNumber                  = 0x0129,
      TransferFunction            = 0x012D,
      Software                    = 0x0131,
      DateTime                    = 0x0132,
      Artist                      = 0x013B,
      HostComputer                = 0x013C,
      Predictor                   = 0x013D,
      WhitePoint                  = 0x013E,
      PrimaryChromaticities       = 0x013F,
      ColorMap                    = 0x0140,
      HalftoneHints               = 0x0141,
      TileWidth                   = 0x0142,
      TileLength                  = 0x0143,
      TileOffsets                 = 0x0144,
      TileByteCounts              = 0x0145,
      InkSet                      = 0x014C,
      InkNames                    = 0x014D,
      NumberOfInks                = 0x014E,
      DotRange                    = 0x0150,
      TargetPrinter               = 0x0151,
      ExtraSamples                = 0x0152,
      SampleFormat                = 0x0153,
      SMinSampleValue             = 0x0154,
      SMaxSampleValue             = 0x0155,
      TransferRange               = 0x0156,
      JPEGProc                    = 0x0200,
      JPEGInterchangeFormat       = 0x0201,
      JPEGInterchangeFormatLength = 0x0202,
      JPEGRestartInterval         = 0x0203,
      JPEGLosslessPredictors      = 0x0205,
      JPEGPointTransforms         = 0x0206,
      JPEGQTables                 = 0x0207,
      JPEGDCTables                = 0x0208,
      JPEGACTables                = 0x0209,
      YCbCrCoefficients           = 0x0211,
      YCbCrSubSampling            = 0x0212,
      YCbCrPositioning            = 0x0213,
      ReferenceBlackWhite         = 0x0214,
      Copyright                   = 0x8298,
    }

    enum TIFFType : ushort
    {
      Byte            = 1,
      ASCII           = 2,
      Short           = 3,
      Long            = 4,
      Rational        = 5,
      SignedByte      = 6,
      Undefined       = 7,
      SignedShort     = 8,
      SignedLong      = 9,
      SignedRational  = 10,
      Float           = 11,
      Double          = 12,
    }

    enum TIFFPhotometricInterpretation : uint
    {
      Bilevel_WhiteIsZero = 0,
      Bilevel_BlackIsZero = 1,
      RGB                 = 2,
      RGB_Palette         = 3,
      TransparencyMask    = 4,
      CMYK                = 5,
      YCbCr               = 6,
      CIELab              = 8,
    }

    enum TIFFCompression : uint
    {
      Uncompressed  =     1,
      CCITT_1D      =     2,
      Group3Fax     =     3,
      Group4Fax     =     4,
      LZW           =     5,
      JPEG          =     6,
      PackBits      = 32773,
    }

    static int bytes_for_type(TIFFType type)
    {
      switch (type)
      {
        case TIFFType.Byte:
        case TIFFType.ASCII:
        case TIFFType.SignedByte:
        case TIFFType.Undefined:      return 1;

        case TIFFType.Short:
        case TIFFType.SignedShort:    return 2;

        case TIFFType.Long:
        case TIFFType.SignedLong:
        case TIFFType.Float:          return 4;

        case TIFFType.Rational:
        case TIFFType.SignedRational:
        case TIFFType.Double:         return 8;

        default: return 0;
      }
    }

    struct Rational
    {
      public uint Numerator, Denominator;
    }

    struct SignedRational
    {
      public int Numerator, Denominator;
    }

    class TIFFIFDEntry
    {
      public TIFFTag Tag;
      public TIFFType Type;
      public int Count;
      public uint FirstValueOffset;

      public void ReadFrom(BinaryReader reader, int base_offset)
      {
        Tag = (TIFFTag)reader.ReadUInt16();
        Type = (TIFFType)reader.ReadUInt16();
        Count = reader.ReadInt32();

        int num_bytes = Count * bytes_for_type(Type);

        FirstValueOffset = reader.ReadUInt32();
        if (num_bytes <= 4)
          FirstValueOffset = (uint)(reader.BaseStream.Position - base_offset - 4);
      }

      public object GetData(BinaryReader reader, int base_offset)
      {
        reader.BaseStream.Seek(base_offset + FirstValueOffset, SeekOrigin.Begin);

        switch (Type)
        {
          case TIFFType.Byte:
          case TIFFType.Undefined:
          {
            byte[] ret = reader.ReadBytes(Count);
            return ret;
          }
          case TIFFType.ASCII:
          {
            byte[] bytes = reader.ReadBytes(Count);
            return Encoding.ASCII.GetString(bytes);
          }
          case TIFFType.Short:
          {
            ushort[] ret = new ushort[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadUInt16();
            return ret;
          }
          case TIFFType.Long:
          {
            uint[] ret = new uint[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadUInt32();
            return ret;
          }
          case TIFFType.Rational:
          {
            Rational[] ret = new Rational[Count];
            for (int i=0; i < Count; i++)
            {
              ret[i].Numerator = reader.ReadUInt32();
              ret[i].Denominator = reader.ReadUInt32();
            }
            return ret;
          }
          case TIFFType.SignedByte:
          {
            sbyte[] ret = new sbyte[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadSByte();
            return ret;
          }
          case TIFFType.SignedShort:
          {
            short[] ret = new short[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadInt16();
            return ret;
          }
          case TIFFType.SignedLong:
          {
            int[] ret = new int[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadInt32();
            return ret;
          }
          case TIFFType.SignedRational:
          {
            SignedRational[] ret = new SignedRational[Count];
            for (int i=0; i < Count; i++)
            {
              ret[i].Numerator = reader.ReadInt32();
              ret[i].Denominator = reader.ReadInt32();
            }
            return ret;
          }
          case TIFFType.Float:
          {
            float[] ret = new float[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadSingle();
            return ret;
          }
          case TIFFType.Double:
          {
            double[] ret = new double[Count];
            for (int i=0; i < Count; i++)
              ret[i] = reader.ReadDouble();
            return ret;
          }
          default:
            return null;
        }
      }

      public uint GetGenericScalar(BinaryReader reader, int base_offset)
      {
        object data = GetData(reader, base_offset);

        if (data is int[])
          return (uint)((int[])data)[0];
        if (data is short[])
          return (uint)((short[])data)[0];
        if (data is byte[])
          return ((byte[])data)[0];
        if (data is uint[])
          return ((uint[])data)[0];
        if (data is ushort[])
          return ((ushort[])data)[0];
        if (data is sbyte[])
          return (uint)((sbyte[])data)[0];

        if (data is Rational[])
        {
          Rational r = ((Rational[])data)[0];

          return (uint)(r.Numerator / r.Denominator);
        }
        if (data is SignedRational[])
        {
          SignedRational r = ((SignedRational[])data)[0];

          return (uint)(r.Numerator / r.Denominator);
        }

        if (data is float[])
          return (uint)((float[])data)[0];
        if (data is double[])
          return (uint)((double[])data)[0];

        if (data is string)
          try
          {
            return uint.Parse(data.ToString());
          }
          catch (FormatException)
          {
            return 0;
          }

        throw new OutOfMemoryException(_("The data could not be interpreted."));
      }

      public uint[] GetGenericArray(BinaryReader reader, int base_offset)
      {
        object data = GetData(reader, base_offset);

        if (data is Array)
        {
          Array data_array = (Array)data;

          uint[] ret = new uint[data_array.Length];

          for (int i=0; i < ret.Length; i++)
            ret[i] = Convert.ToUInt32(data_array.GetValue(i));

          return ret;
        }
        else
          throw new OutOfMemoryException(_("The data could not be interpreted."));
      }
    }

    class TIFFIFD
    {
      public TIFFIFDEntry[] Entry;
      public uint NextIFDOffset;

      public void ReadFrom(BinaryReader reader, int base_offset)
      {
        int count = reader.ReadUInt16();

        Entry = new TIFFIFDEntry[count];

        for (int i=0; i < count; i++)
        {
          Entry[i] = new TIFFIFDEntry();
          Entry[i].ReadFrom(reader, base_offset);
        }

        NextIFDOffset = reader.ReadUInt32();
      }

      public bool ContainsTag(TIFFTag tag)
      {
        if (FindTag(tag) != null)
          return true;
        else
          return false;
      }

      public TIFFIFDEntry FindTag(TIFFTag tag)
      {
        for (int i=0; i < Entry.Length; i++)
          if (Entry[i].Tag == tag)
            return Entry[i];
        return null;
      }

      public object GetTagData(TIFFTag tag, BinaryReader reader, int base_offset)
      {
        TIFFIFDEntry entry = FindTag(tag);

        if (entry == null)
          return null;
        else
          return entry.GetData(reader, base_offset);
      }
    }

    class BitReader
    {
      public readonly Stream BaseStream;

      public BitReader(Stream to_wrap)
      {
        BaseStream = to_wrap;
      }

      int bits_left = 0, bits;

      public bool ReadBit()
      {
        if (bits_left == 0)
        {
          bits = BaseStream.ReadByte();
          bits_left = 8;
        }

        bits <<= 1;
        bits_left--;

        return (0 != (bits & 0x100));
      }

      public void SyncToNextByte()
      {
        while (bits_left > 0)
          ReadBit();
      }
    }

    class BitWriter
    {
      public readonly Stream BaseStream;

      public BitWriter(Stream to_wrap)
      {
        BaseStream = to_wrap;
      }

      int bits_left = 8, bits;

      public void WriteBit(bool bit)
      {
        bits = (bits << 1) | (bit ? 1 : 0);
        bits_left--;

        if (bits_left == 0)
        {
          BaseStream.WriteByte((byte)bits);
          bits_left = 8;
          bits = 0;
        }
      }

      public void SyncToNextByte()
      {
        while (bits_left != 8)
          WriteBit(false);
      }

      public void Seek(int position)
      {
        if (bits_left < 8)
          BaseStream.WriteByte((byte)bits);

        bits_left = 8;
        bits = 0;

        BaseStream.Seek(position, SeekOrigin.Begin);
      }
    }

    class TIFFHuffman
    {
      enum CodeType
      {
        White,
        Black,
        Both,
      }

      struct Code
      {
        public int Value, Length;
        public CodeType Type;

        public Code(int value, int length, CodeType type)
        {
          Value = value;
          Length = length;
          Type = type;
        }
      }

      static Code[][] codes_by_length = new Code[][]
        {
          new Code[0], // length 0 does not exist
          new Code[0], // length 1 does not exist
          new Code[]   // length 2
          {
            new Code(3, 2, CodeType.Black), // 11
            new Code(1, 3, CodeType.Black), // 10
          },
          new Code[]   // length 3
          {
            new Code(2, 1, CodeType.Black), // 010
            new Code(6, 4, CodeType.Black), // 011
          },
          new Code[]   // length 4
          {
            new Code(14, 2, CodeType.White), // 0111
            new Code(1, 3, CodeType.White),  // 1000
            new Code(13, 4, CodeType.White), // 1011
            new Code(3, 5, CodeType.White),  // 1100
            new Code(7, 6, CodeType.White),  // 1110
            new Code(15, 7, CodeType.White), // 1111
            new Code(12, 5, CodeType.Black),  // 0011
            new Code(4, 6, CodeType.Black),   // 0010
          },
          new Code[]   // length 5
          {
            new Code(25, 8, CodeType.White),  // 10011
            new Code(5, 9, CodeType.White),   // 10100
            new Code(28, 10, CodeType.White), // 00111
            new Code(2, 11, CodeType.White),  // 01000
            new Code(27, 64, CodeType.White), // 11011
            new Code(9, 128, CodeType.White), // 10010
            new Code(24, 7, CodeType.Black),  // 00011
          },
          new Code[]   // length 6
          {
            new Code(56, 1, CodeType.White),   // 000111
            new Code(4, 12, CodeType.White),   // 001000
            new Code(48, 13, CodeType.White),  // 000011
            new Code(11, 14, CodeType.White),  // 110100
            new Code(43, 15, CodeType.White),  // 110101
            new Code(21, 16, CodeType.White),  // 101010
            new Code(53, 17, CodeType.White),  // 101011
            new Code(58, 192, CodeType.White), // 010111
            new Code(6, 1664, CodeType.White), // 011000
            new Code(40, 8, CodeType.Black),   // 000101
            new Code(8, 9, CodeType.Black),    // 000100
          },
          new Code[]   // length 7
          {
            new Code(114, 18, CodeType.White),  // 0100111
            new Code(24, 19, CodeType.White),   // 0001100
            new Code(8, 20, CodeType.White),    // 0001000
            new Code(116, 21, CodeType.White),  // 0010111
            new Code(96, 22, CodeType.White),   // 0000011
            new Code(16, 23, CodeType.White),   // 0000100
            new Code(10, 24, CodeType.White),   // 0101000
            new Code(106, 25, CodeType.White),  // 0101011
            new Code(100, 26, CodeType.White),  // 0010011
            new Code(18, 27, CodeType.White),   // 0100100
            new Code(12, 28, CodeType.White),   // 0011000
            new Code(118, 256, CodeType.White), // 0110111
            new Code(16, 10, CodeType.Black),   // 0000100
            new Code(80, 11, CodeType.Black),   // 0000101
            new Code(112, 12, CodeType.Black),  // 0000111
          },
          new Code[]   // length 8
          {
            new Code(172, 0, CodeType.White),   // 00110101
            new Code(64, 29, CodeType.White),   // 00000010
            new Code(192, 30, CodeType.White),  // 00000011
            new Code(88, 31, CodeType.White),   // 00011010
            new Code(216, 32, CodeType.White),  // 00011011
            new Code(72, 33, CodeType.White),   // 00010010
            new Code(200, 34, CodeType.White),  // 00010011
            new Code(40, 35, CodeType.White),   // 00010100
            new Code(168, 36, CodeType.White),  // 00010101
            new Code(104, 37, CodeType.White),  // 00010110
            new Code(232, 38, CodeType.White),  // 00010111
            new Code(20, 39, CodeType.White),   // 00101000
            new Code(148, 40, CodeType.White),  // 00101001
            new Code(84, 41, CodeType.White),   // 00101010
            new Code(212, 42, CodeType.White),  // 00101011
            new Code(52, 43, CodeType.White),   // 00101100
            new Code(180, 44, CodeType.White),  // 00101101
            new Code(32, 45, CodeType.White),   // 00000100
            new Code(160, 46, CodeType.White),  // 00000101
            new Code(80, 47, CodeType.White),   // 00001010
            new Code(208, 48, CodeType.White),  // 00001011
            new Code(74, 49, CodeType.White),   // 01010010
            new Code(202, 50, CodeType.White),  // 01010011
            new Code(42, 51, CodeType.White),   // 01010100
            new Code(170, 52, CodeType.White),  // 01010101
            new Code(36, 53, CodeType.White),   // 00100100
            new Code(164, 54, CodeType.White),  // 00100101
            new Code(26, 55, CodeType.White),   // 01011000
            new Code(154, 56, CodeType.White),  // 01011001
            new Code(90, 57, CodeType.White),   // 01011010
            new Code(218, 58, CodeType.White),  // 01011011
            new Code(82, 59, CodeType.White),   // 01001010
            new Code(210, 60, CodeType.White),  // 01001011
            new Code(76, 61, CodeType.White),   // 00110010
            new Code(204, 62, CodeType.White),  // 00110011
            new Code(44, 63, CodeType.White),   // 00110100
            new Code(108, 320, CodeType.White), // 00110110
            new Code(236, 384, CodeType.White), // 00110111
            new Code(38, 448, CodeType.White),  // 01100100
            new Code(166, 512, CodeType.White), // 01100101
            new Code(22, 576, CodeType.White),  // 01101000
            new Code(230, 640, CodeType.White), // 01100111
            new Code(32, 13, CodeType.Black),   // 00000100
            new Code(224, 14, CodeType.Black),  // 00000111
          },
          new Code[]   // length 9
          {
            new Code(102, 704, CodeType.White),  // 011001100
            new Code(358, 768, CodeType.White),  // 011001101
            new Code(150, 832, CodeType.White),  // 011010010
            new Code(406, 896, CodeType.White),  // 011010011
            new Code(86, 960, CodeType.White),   // 011010100
            new Code(342, 1024, CodeType.White), // 011010101
            new Code(214, 1088, CodeType.White), // 011010110
            new Code(470, 1152, CodeType.White), // 011010111
            new Code(54, 1216, CodeType.White),  // 011011000
            new Code(310, 1280, CodeType.White), // 011011001
            new Code(182, 1344, CodeType.White), // 011011010
            new Code(438, 1408, CodeType.White), // 011011011
            new Code(50, 1472, CodeType.White),  // 010011000
            new Code(306, 1536, CodeType.White), // 010011001
            new Code(178, 1600, CodeType.White), // 010011010
            new Code(434, 1728, CodeType.White), // 010011011
            new Code(48, 15, CodeType.Black),    // 000011000
          },
          new Code[]   // length 10
          {
            new Code(944, 0, CodeType.Black),  // 0000110111
            new Code(928, 16, CodeType.Black), // 0000010111
            new Code(96, 17, CodeType.Black),  // 0000011000
            new Code(64, 18, CodeType.Black),  // 0000001000
            new Code(960, 64, CodeType.Black), // 0000001111
          },
          new Code[]   // length 11
          {
            new Code(1840, 19, CodeType.Black),  // 00001100111
            new Code(176, 20, CodeType.Black),   // 00001101000
            new Code(432, 21, CodeType.Black),   // 00001101100
            new Code(1888, 22, CodeType.Black),  // 00000110111
            new Code(160, 23, CodeType.Black),   // 00000101000
            new Code(1856, 24, CodeType.Black),  // 00000010111
            new Code(192, 25, CodeType.Black),   // 00000011000
            new Code(0, -1, CodeType.Black),     // 00000000000
            new Code(128, 1792, CodeType.Both),  // 00000001000
            new Code(384, 1856, CodeType.Both),  // 00000001100
            new Code(1408, 1920, CodeType.Both), // 00000001101
          },
          new Code[]   // length 12
          {
            new Code(2048, -1, CodeType.White),  // 000000000001
            new Code(1328, 26, CodeType.Black),  // 000011001010
            new Code(3376, 27, CodeType.Black),  // 000011001011
            new Code(816, 28, CodeType.Black),   // 000011001100
            new Code(2864, 29, CodeType.Black),  // 000011001101
            new Code(352, 30, CodeType.Black),   // 000001101000
            new Code(2400, 31, CodeType.Black),  // 000001101001
            new Code(1376, 32, CodeType.Black),  // 000001101010
            new Code(3424, 33, CodeType.Black),  // 000001101011
            new Code(1200, 34, CodeType.Black),  // 000011010010
            new Code(3248, 35, CodeType.Black),  // 000011010011
            new Code(688, 36, CodeType.Black),   // 000011010100
            new Code(2736, 37, CodeType.Black),  // 000011010101
            new Code(1712, 38, CodeType.Black),  // 000011010110
            new Code(3760, 39, CodeType.Black),  // 000011010111
            new Code(864, 40, CodeType.Black),   // 000001101100
            new Code(2912, 41, CodeType.Black),  // 000001101101
            new Code(1456, 42, CodeType.Black),  // 000011011010
            new Code(3504, 43, CodeType.Black),  // 000011011011
            new Code(672, 44, CodeType.Black),   // 000001010100
            new Code(2720, 45, CodeType.Black),  // 000001010101
            new Code(1696, 46, CodeType.Black),  // 000001010110
            new Code(3744, 47, CodeType.Black),  // 000001010111
            new Code(608, 48, CodeType.Black),   // 000001100100
            new Code(2656, 49, CodeType.Black),  // 000001100101
            new Code(1184, 50, CodeType.Black),  // 000001010010
            new Code(3232, 51, CodeType.Black),  // 000001010011
            new Code(576, 52, CodeType.Black),   // 000000100100
            new Code(3776, 53, CodeType.Black),  // 000000110111
            new Code(448, 54, CodeType.Black),   // 000000111000
            new Code(3648, 55, CodeType.Black),  // 000000100111
            new Code(320, 56, CodeType.Black),   // 000000101000
            new Code(416, 57, CodeType.Black),   // 000001011000
            new Code(2464, 58, CodeType.Black),  // 000001011001
            new Code(3392, 59, CodeType.Black),  // 000000101011
            new Code(832, 60, CodeType.Black),   // 000000101100
            new Code(1440, 61, CodeType.Black),  // 000001011010
            new Code(1632, 62, CodeType.Black),  // 000001100110
            new Code(3680, 63, CodeType.Black),  // 000001100111
            new Code(304, 128, CodeType.Black),  // 000011001000
            new Code(2352, 192, CodeType.Black), // 000011001001
            new Code(3488, 256, CodeType.Black), // 000001011011
            new Code(3264, 320, CodeType.Black), // 000000110011
            new Code(704, 384, CodeType.Black),  // 000000110100
            new Code(2752, 448, CodeType.Black), // 000000110101
            new Code(1152, 1984, CodeType.Both), // 000000010010
            new Code(3200, 2048, CodeType.Both), // 000000010011
            new Code(640, 2112, CodeType.Both),  // 000000010100
            new Code(2688, 2176, CodeType.Both), // 000000010101
            new Code(1664, 2240, CodeType.Both), // 000000010110
            new Code(3712, 2304, CodeType.Both), // 000000010111
            new Code(896, 2368, CodeType.Both),  // 000000011100
            new Code(2944, 2432, CodeType.Both), // 000000011101
            new Code(1920, 2496, CodeType.Both), // 000000011110
            new Code(3968, 2560, CodeType.Both), // 000000011111
          },
          new Code[]   // length 13
          {
            new Code(1728, 512, CodeType.Black),  // 0000001101100
            new Code(5824, 576, CodeType.Black),  // 0000001101101
            new Code(2624, 640, CodeType.Black),  // 0000001001010
            new Code(6720, 704, CodeType.Black),  // 0000001001011
            new Code(1600, 768, CodeType.Black),  // 0000001001100
            new Code(5696, 832, CodeType.Black),  // 0000001001101
            new Code(2496, 896, CodeType.Black),  // 0000001110010
            new Code(6592, 960, CodeType.Black),  // 0000001110011
            new Code(1472, 1024, CodeType.Black), // 0000001110100
            new Code(5568, 1088, CodeType.Black), // 0000001110101
            new Code(3520, 1152, CodeType.Black), // 0000001110110
            new Code(7616, 1216, CodeType.Black), // 0000001110111
            new Code(2368, 1280, CodeType.Black), // 0000001010010
            new Code(6464, 1344, CodeType.Black), // 0000001010011
            new Code(1344, 1408, CodeType.Black), // 0000001010100
            new Code(5440, 1472, CodeType.Black), // 0000001010101
            new Code(2880, 1536, CodeType.Black), // 0000001011010
            new Code(6976, 1600, CodeType.Black), // 0000001011011
            new Code(1216, 1664, CodeType.Black), // 0000001100100
            new Code(5312, 1728, CodeType.Black), // 0000001100101
          }
        };

      class Tree
      {
        public Tree Bit0, Bit1;
        public int Length;

        public int GetLength(BitReader stream)
        {
          if ((Bit0 == null) && (Bit1 == null))
            return Length;

          bool bit = stream.ReadBit();

          Tree child = (bit == false) ? Bit0 : Bit1;

          if (child == null)
            throw new OutOfMemoryException(_("Invalid Huffman code"));

          return child.GetLength(stream);
        }

        public void Insert(int bits, int num_bits_remaining, int length)
        {
          if (num_bits_remaining == 0)
          {
            if ((Bit0 != null) || (Bit1 != null))
              throw new OutOfMemoryException(_("Huffman code table corrupt"));

            Length = length;

            return;
          }

          bool bit = (0 != (bits & 1));

          bits >>= 1;
          num_bits_remaining--;

          if (bit == false)
          {
            if (Bit0 == null)
              Bit0 = new Tree();

            Bit0.Insert(bits, num_bits_remaining, length);
          }
          else
          {
            if (Bit1 == null)
              Bit1 = new Tree();

            Bit1.Insert(bits, num_bits_remaining, length);
          }
        }
      }

      static Tree white_tree, black_tree;

      static void build_trees()
      {
        white_tree = new Tree();
        black_tree = new Tree();

        for (int length=0; length < codes_by_length.Length; length++)
        {
          Code[] codes = codes_by_length[length];

          for (int i=0; i < codes.Length; i++)
          {
            Code code = codes[i];

            if ((code.Type == CodeType.White) || (code.Type == CodeType.Both))
              white_tree.Insert(code.Value, length, code.Length);
            if ((code.Type == CodeType.Black) || (code.Type == CodeType.Both))
              black_tree.Insert(code.Value, length, code.Length);
          }
        }
      }

      static TIFFHuffman()
      {
        build_trees();
      }

      public static byte[] Decode(byte[] input, int width, int rows, int stride)
      {
        byte[] output = new byte[rows * stride];

        using (MemoryStream input_stream = new MemoryStream(input),
                 output_stream = new MemoryStream(output))
        {
          BitReader reader = new BitReader(input_stream);
          BitWriter writer = new BitWriter(output_stream);

          for (int y=0; y < rows; y++)
          {
            int o = y * stride;
            int code = 0;

            int x=0;

            while (x < width)
            {
              code = white_tree.GetLength(reader);

              if (code < 0) // end-of-line
                break;

              for (int i=0; i < code; i++)
              {
                if (x < width)
                  writer.WriteBit(false);
                x++;
              }

              if (x >= width)
              {
                if (code >= 64)
                {
                  code = white_tree.GetLength(reader);

                  if (code != 0)
                    throw new OutOfMemoryException(_("Invalid Huffman data"));
                }
                break;
              }

              if (code >= 64)
                continue;

              do
              {
                code = black_tree.GetLength(reader);

                if (code < 0) // end-of-line
                  break;

                for (int i=0; i < code; i++)
                {
                  if (x < width)
                    writer.WriteBit(true);
                  x++;
                }
              }
              while (code >= 64);
            }

            reader.SyncToNextByte();
            writer.SyncToNextByte();
          }
        }

        return output;
      }
    }

    class TIFFPackBits
    {
      public static byte[] Decode(byte[] input, int width, int rows, int stride)
      {
        byte[] output = new byte[rows * stride];

        int i = 0;

        for (int y=0; y < rows; y++)
        {
          int o = y * stride;

          int run = 0;
          int run_value = 0;

          int x=0;

          while (x < stride)
          {
            if (i >= input.Length)
              return output;

            sbyte n = unchecked((sbyte)input[i++]);

            if (n >= 0)
            {
              run = 1 + n;
              run_value = -1;
            }
            else if (n > -128)
            {
              run = 1 - n;
              run_value = input[i++];
            }
            else
              continue;

            if (run_value < 0)
            {
              if (run + x > stride)
                Array.Copy(input, i, output, o, stride - x);
              else
                Array.Copy(input, i, output, o, run);

              i += run;
            }
            else
            {
              if (run + x > stride)
                run = stride - x;

              for (int q=0; q < run; q++)
                output[o + q] = unchecked((byte)run_value);
            }

            x += run;
            o += run;
          }
        }

        return output;
      }
    }
  
    public static unsafe TIFFImageInfo LoadTIFF(Stream seekable_stream, int offset)
    {
      seekable_stream.Seek(offset, SeekOrigin.Begin);

      BinaryReader reader = new BinaryReader(seekable_stream, Encoding.ASCII);

      TIFFHeader header = new TIFFHeader();

      ushort byte_order = reader.ReadUInt16();

      if (byte_order == 0x4949)
        header.IntelByteOrder = true;
      else
        header.IntelByteOrder = false;

      if (!header.IntelByteOrder)
      {
        seekable_stream.Seek(offset + 2, SeekOrigin.Begin);

        reader = new MSBBinaryReader(seekable_stream, Encoding.ASCII);
      }

      ushort meaning_of_life = reader.ReadUInt16();

      if (meaning_of_life != 42)
        throw new OutOfMemoryException(_("Invalid file format (the meaning of life is not 42)"));

      header.IFDOffset = reader.ReadInt32();

      int ifd_position = offset + header.IFDOffset;

      reader.BaseStream.Seek(ifd_position, SeekOrigin.Begin);

      TIFFIFD image_file_directory = new TIFFIFD();
      image_file_directory.ReadFrom(reader, offset);

      if ((!image_file_directory.ContainsTag(TIFFTag.ImageWidth))
       || (!image_file_directory.ContainsTag(TIFFTag.ImageLength)))
        throw new OutOfMemoryException(_("Invalid file format (image dimensions are missing)"));

      if (!image_file_directory.ContainsTag(TIFFTag.PhotometricInterpretation))
        throw new OutOfMemoryException(_("Invalid file format (there is no photometric interpretation)"));

      if ((!image_file_directory.ContainsTag(TIFFTag.StripOffsets))
       || (!image_file_directory.ContainsTag(TIFFTag.StripByteCounts)))
        throw new OutOfMemoryException(_("Invalid file format (strip information is missing)"));

      int width = (int)
        image_file_directory.FindTag(TIFFTag.ImageWidth).GetGenericScalar(reader, offset);

      int height = (int)
        image_file_directory.FindTag(TIFFTag.ImageLength).GetGenericScalar(reader, offset);

      int rows_per_strip, samples_per_pixel, extra_samples, fill_order, orientation;
      uint[] bits_per_sample;
      
      if (image_file_directory.ContainsTag(TIFFTag.RowsPerStrip))
        rows_per_strip = (int)
          image_file_directory.FindTag(TIFFTag.RowsPerStrip).GetGenericScalar(reader, offset);
      else
        rows_per_strip = int.MaxValue;

      if (image_file_directory.ContainsTag(TIFFTag.BitsPerSample))
        bits_per_sample =
          image_file_directory.FindTag(TIFFTag.BitsPerSample).GetGenericArray(reader, offset);
      else
      {
        bits_per_sample = new uint[1];
        bits_per_sample[0] = 1;
      }

      if (image_file_directory.ContainsTag(TIFFTag.SamplesPerPixel))
        samples_per_pixel = (int)
          image_file_directory.FindTag(TIFFTag.SamplesPerPixel).GetGenericScalar(reader, offset);
      else
        samples_per_pixel = 1;

      if (image_file_directory.ContainsTag(TIFFTag.ExtraSamples))
        extra_samples = (int)
          image_file_directory.FindTag(TIFFTag.ExtraSamples).GetGenericScalar(reader, offset);
      else
        extra_samples = 0;

      if (image_file_directory.ContainsTag(TIFFTag.FillOrder))
        fill_order = (int)
          image_file_directory.FindTag(TIFFTag.FillOrder).GetGenericScalar(reader, offset);
      else
        fill_order = 1;

      if (image_file_directory.ContainsTag(TIFFTag.Orientation))
        orientation = (int)
          image_file_directory.FindTag(TIFFTag.Orientation).GetGenericScalar(reader, offset);
      else
        orientation = 1;

      uint[] strip_offsets =
        image_file_directory.FindTag(TIFFTag.StripOffsets).GetGenericArray(reader, offset);

      uint[] strip_byte_counts =
        image_file_directory.FindTag(TIFFTag.StripByteCounts).GetGenericArray(reader, offset);

      TIFFPhotometricInterpretation photometric_interpretation = (TIFFPhotometricInterpretation)
        image_file_directory.FindTag(TIFFTag.PhotometricInterpretation).GetGenericScalar(reader, offset);

      TIFFCompression compression = (TIFFCompression)
        image_file_directory.FindTag(TIFFTag.Compression).GetGenericScalar(reader, offset);

      uint[] colour_map = null;

      if (image_file_directory.ContainsTag(TIFFTag.ColorMap))
        colour_map =
          image_file_directory.FindTag(TIFFTag.ColorMap).GetGenericArray(reader, offset);

      int num_strips = (height + rows_per_strip - 1) / rows_per_strip;

      if (fill_order != 1)
        throw new OutOfMemoryException(_("Unsupported fill order"));

      if (orientation != 1)
        throw new OutOfMemoryException(_("Unsupported orientation"));

      if ((strip_offsets.Length != num_strips) || (strip_byte_counts.Length != num_strips))
        throw new OutOfMemoryException(_("Invalid file format (strip data is invalid)"));

      if (bits_per_sample.Length < samples_per_pixel)
      {
        uint[] new_bits_per_sample = new uint[samples_per_pixel];

        Array.Copy(bits_per_sample, 0, new_bits_per_sample, 0, bits_per_sample.Length);
        for (int i=bits_per_sample.Length; i < new_bits_per_sample.Length; i++)
          new_bits_per_sample[i] = bits_per_sample[0];

        bits_per_sample = new_bits_per_sample;
      }

      if (bits_per_sample.Length != samples_per_pixel)
        throw new OutOfMemoryException(_("Invalid file format (incorrect number of samples described in BitsPerSample)"));

      if (((photometric_interpretation == TIFFPhotometricInterpretation.Bilevel_BlackIsZero)
        || (photometric_interpretation == TIFFPhotometricInterpretation.Bilevel_WhiteIsZero))
       && (samples_per_pixel - extra_samples != 1))
        throw new OutOfMemoryException(_("Invalid file format (bilevel image has more than one sample per pixel)"));

      if (photometric_interpretation == TIFFPhotometricInterpretation.RGB_Palette)
      {
        if (samples_per_pixel != 1)
          throw new OutOfMemoryException(_("Invalid file format (palettized image has more than one sample per pixel)"));

        int expected_colour_map_length = (1 << (int)bits_per_sample[0]) * 3;

        if (colour_map.Length != expected_colour_map_length)
          throw new OutOfMemoryException(_("Invalid file format (palette does not contain the correct number of entries)"));
      }

      if (((photometric_interpretation == TIFFPhotometricInterpretation.RGB)
        || (photometric_interpretation == TIFFPhotometricInterpretation.YCbCr))
       && (samples_per_pixel - extra_samples != 3))
        throw new OutOfMemoryException(_("Invalid file format (image does not have 3 components)"));

      if ((photometric_interpretation == TIFFPhotometricInterpretation.CMYK)
       && (samples_per_pixel - extra_samples != 4))
        throw new OutOfMemoryException(_("Invalid file format (CMYK image does not have 4 components)"));

      int strip_index = 0;

      Bitmap bmp;

      bool sixteen_bits = false;

      for (int i=0; i < samples_per_pixel; i++)
        if (bits_per_sample[i] > 8)
        {
          sixteen_bits = true;
          break;
        }

      int pixel_bytes = (sixteen_bits ? 8 : 4);

      if (sixteen_bits)
        bmp = new Bitmap(width, height, PixelFormat.Format64bppArgb);
      else
        if (photometric_interpretation == TIFFPhotometricInterpretation.CMYK)
          bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        else
          bmp = new Bitmap(width, height, PixelFormat.Format32bppRgb);

      BitmapData bmp_data = null;

      int pixel_bits = 0;
      for (int i=0; i < samples_per_pixel; i++)
        pixel_bits += (int)bits_per_sample[i];

      int stride_bits = (int)(width * pixel_bits);

      int stride_bytes = (stride_bits + 7) >> 3;

      try
      {
        bmp_data = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bmp.PixelFormat);

        if (bmp_data == null)
          throw new OutOfMemoryException(_("Unable to lock bitmap data"));

        byte *bmp_data_ptr = (byte *)bmp_data.Scan0.ToPointer();

        int bmp_stride = bmp_data.Stride;
        int bmp_fixup = bmp_stride - bmp_data.Width * (sixteen_bits ? 8 : 4);

        for (long y=0; y < height; y += rows_per_strip, strip_index++)
        {
          int strip_position = (int)(offset + strip_offsets[strip_index]);
          int rows_this_strip = rows_per_strip;
          long strip_end = y + rows_per_strip;
          if (strip_end > height)
          {
            strip_end = height;
            rows_this_strip = (int)(strip_end - y);
          }

          int bmp_data_offset = (int)(y * bmp_stride);

          reader.BaseStream.Seek(strip_position, SeekOrigin.Begin);

          byte[] bytes = reader.ReadBytes((int)strip_byte_counts[strip_index]);

          switch (compression)
          {
            case TIFFCompression.Uncompressed:
              break;
            case TIFFCompression.CCITT_1D:
              bytes = TIFFHuffman.Decode(bytes, width, rows_this_strip, stride_bytes);
              break;
            case TIFFCompression.PackBits:
              bytes = TIFFPackBits.Decode(bytes, width, rows_this_strip, stride_bytes);
              break;
            default:
              throw new OutOfMemoryException(_("Unsupported compression method"));
          }

          switch (photometric_interpretation)
          {
            case TIFFPhotometricInterpretation.Bilevel_BlackIsZero:
            case TIFFPhotometricInterpretation.Bilevel_WhiteIsZero:
            case TIFFPhotometricInterpretation.TransparencyMask:
            {
              using (MemoryStream stream = new MemoryStream(bytes))
              {
                BitReader bits = new BitReader(stream);

                for (long yy=y; yy < strip_end; yy++)
                {
                  for (int xx=0; xx < width; xx++)
                  {
                    byte bit = 0;

                    if ((bits_per_sample[0] == 8) && (extra_samples == 0))
                      bit = (byte)(0xFF ^ stream.ReadByte());
                    else
                      unchecked
                      {
                        for (int i=0; i < bits_per_sample[0]; i++)
                          bit = (byte)((bit << 1) | (bits.ReadBit() ? 0 : 1));

                        switch (bits_per_sample[0])
                        {
                          case 1:
                            bit = (byte)(bit * 255);
                            break;
                          case 4:
                            bit = (byte)(bit * 17);
                            break;
                        }

                        for (int i=1; i < samples_per_pixel; i++) // skip any extra samples
                          for (int j=0; j < bits_per_sample[i]; j++)
                            bits.ReadBit();
                      }

                    if (photometric_interpretation == TIFFPhotometricInterpretation.Bilevel_BlackIsZero)
                      bit ^= 0xFF;

                    if (sixteen_bits)
                      for (int i=0; i < 8; i++)
                        bmp_data_ptr[bmp_data_offset++] = bit;
                    else
                      for (int i=0; i < 4; i++)
                        bmp_data_ptr[bmp_data_offset++] = bit;
                  }

                  bits.SyncToNextByte();

                  bmp_data_offset += bmp_fixup;
                }
              }
              break;
            }
            case TIFFPhotometricInterpretation.RGB_Palette:
            {
              using (MemoryStream stream = new MemoryStream(bytes))
              {
                BitReader bits = new BitReader(stream);

                for (long yy=y; yy < strip_end; yy++)
                {
                  for (int xx=0; xx < width; xx++)
                  {
                    int idx = 0;

                    unchecked
                    {
                      if ((bits_per_sample[0] == 8) && (extra_samples == 0))
                        idx = stream.ReadByte();
                      else
                      {
                        for (int i=0; i < bits_per_sample[0]; i++)
                          idx = ((idx << 1) | (bits.ReadBit() ? 1 : 0));

                        for (int i=1; i < samples_per_pixel; i++) // skip any extra samples
                          for (int j=0; j < bits_per_sample[i]; j++)
                            bits.ReadBit();
                      }

                      ushort r = (ushort)colour_map[idx + (0 << (int)bits_per_sample[0])];
                      ushort g = (ushort)colour_map[idx + (1 << (int)bits_per_sample[0])];
                      ushort b = (ushort)colour_map[idx + (2 << (int)bits_per_sample[0])];

                      if (sixteen_bits)
                      {
                        bmp_data_ptr[bmp_data_offset++] = (byte)(b & 0xFF);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(b >> 8);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(g & 0xFF);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(g >> 8);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(r & 0xFF);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(r >> 8);
                        bmp_data_ptr[bmp_data_offset++] = 0;
                        bmp_data_ptr[bmp_data_offset++] = 0;
                      }
                      else
                      {
                        bmp_data_ptr[bmp_data_offset++] = (byte)(b >> 8);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(g >> 8);
                        bmp_data_ptr[bmp_data_offset++] = (byte)(r >> 8);
                        bmp_data_ptr[bmp_data_offset++] = 0;
                      }
                    }
                  }

                  bits.SyncToNextByte();

                  bmp_data_offset += bmp_fixup;
                }
              }
              break;
            }
            case TIFFPhotometricInterpretation.RGB:
            case TIFFPhotometricInterpretation.YCbCr:
            case TIFFPhotometricInterpretation.CIELab:
            {
              using (MemoryStream stream = new MemoryStream(bytes))
              {
                BitReader bits = new BitReader(stream);

                for (long yy=y; yy < strip_end; yy++)
                {
                  for (int xx=0; xx < width; xx++)
                  {
                    unchecked
                    {
                      for (int i=0; i < 3; i++)
                      {
                        int chan = 0;

                        if (bits_per_sample[i] < 16)
                        {
                          for (int j=0; j < bits_per_sample[i]; j++)
                            chan = ((chan << 1) | (bits.ReadBit() ? 1 : 0));

                          for (int j=(int)bits_per_sample[i]; j < 16; j += (int)bits_per_sample[i])
                            chan = (chan << (int)bits_per_sample[i]) | chan;

                          if ((16 % bits_per_sample[i]) != 0)
                          {
                            chan >>= (int)(bits_per_sample[i] - (16u % bits_per_sample[i]) - 1);
                            chan = (chan + 1) >> 1;
                          }
                        }
                        else
                        {
                          for (int j=0; j < 16; j++)
                            chan = ((chan << 1) | (bits.ReadBit() ? 1 : 0));

                          for (int j=16; j < bits_per_sample[i]; j++)
                            bits.ReadBit(); // discard sample noise
                        }

                        if ((bits_per_sample[i] > 8) && (header.IntelByteOrder == true))
                          chan = (ushort)((chan >> 8) | (chan << 8));

                        if ((i > 0) && (photometric_interpretation == TIFFPhotometricInterpretation.CIELab))
                          chan = (ushort)(chan + 0x8000); // bias the 'a' and 'b' channels

                        if (sixteen_bits)
                        {
                          bmp_data_ptr[bmp_data_offset + (2 - i) * 2] = (byte)(chan & 0xFF);
                          bmp_data_ptr[bmp_data_offset + (2 - i) * 2 + 1] = (byte)(chan >> 8);
                        }
                        else
                          bmp_data_ptr[bmp_data_offset + (2 - i)] = (byte)(chan >> 8);
                      }

                      for (int i=3; i < samples_per_pixel; i++) // skip any extra samples
                        for (int j=0; j < bits_per_sample[i]; j++)
                          bits.ReadBit();

                      if (sixteen_bits)
                      {
                        bmp_data_offset += 6;
                        bmp_data_ptr[bmp_data_offset++] = 0;
                      }
                      else
                        bmp_data_offset += 3;
                      bmp_data_ptr[bmp_data_offset++] = 0;
                    }
                  }

                  bits.SyncToNextByte();

                  bmp_data_offset += bmp_fixup;
                }
              }
              break;
            }
            case TIFFPhotometricInterpretation.CMYK:
            {
              using (MemoryStream stream = new MemoryStream(bytes))
              {
                BitReader bits = new BitReader(stream);

                for (long yy=y; yy < strip_end; yy++)
                {
                  for (int xx=0; xx < width; xx++)
                  {
                    unchecked
                    {
                      for (int i=0; i < 4; i++)
                      {
                        int chan = 0;

                        if (bits_per_sample[i] < 16)
                        {
                          for (int j=0; j < bits_per_sample[i]; j++)
                            chan = ((chan << 1) | (bits.ReadBit() ? 1 : 0));

                          for (int j=(int)bits_per_sample[i]; j < 16; j += (int)bits_per_sample[i])
                            chan = (chan << (int)bits_per_sample[i]) | chan;

                          if ((16 % bits_per_sample[i]) != 0)
                          {
                            chan >>= (int)(bits_per_sample[i] - (16u % bits_per_sample[i]) - 1);
                            chan = (chan + 1) >> 1;
                          }
                        }
                        else
                        {
                          for (int j=0; j < 16; j++)
                            chan = ((chan << 1) | (bits.ReadBit() ? 1 : 0));

                          for (int j=16; j < bits_per_sample[i]; j++)
                            bits.ReadBit(); // discard sample noise
                        }

                        if ((bits_per_sample[i] > 8) && (header.IntelByteOrder == true))
                          chan = (ushort)((chan >> 8) | (chan << 8));

                        if (sixteen_bits)
                        {
                          bmp_data_ptr[bmp_data_offset + i * 2] = (byte)(chan & 0xFF);
                          bmp_data_ptr[bmp_data_offset + i * 2 + 1] = (byte)(chan >> 8);
                        }
                        else
                          bmp_data_ptr[bmp_data_offset + i] = (byte)(chan >> 8);
                      }

                      for (int i=4; i < samples_per_pixel; i++) // skip any extra samples
                        for (int j=0; j < bits_per_sample[i]; j++)
                          bits.ReadBit();
                    }
                  }

                  bits.SyncToNextByte();

                  bmp_data_offset += bmp_fixup;
                }
              }
              break;
            }
            default:
              throw new OutOfMemoryException(_("Unknown photometric interpretation."));
          }
        }
      }
      finally
      {
        if (bmp_data != null)
          bmp.UnlockBits(bmp_data);
      }

      TIFFImageInfo ret = new TIFFImageInfo();

      ret.BitmapData = bmp;
      ret.BitsPerComponent = sixteen_bits ? 16 : 8;
      if (photometric_interpretation == TIFFPhotometricInterpretation.CMYK)
        ret.ColourSpace = TIFFColourSpace.CMYK;
      else if (photometric_interpretation == TIFFPhotometricInterpretation.CIELab)
        ret.ColourSpace = TIFFColourSpace.Lab;
      else
        ret.ColourSpace = TIFFColourSpace.RGB;

      return ret;
    }

    public static void SaveTIFF(Bitmap bmp, string filename, TIFFColourSpace colour_space)
    {
      FileStream file = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read);

      try
      {
        SaveTIFF(bmp, file, colour_space);
      }
      finally
      {
        file.Close();
      }
    }

    public static void SaveTIFF(Bitmap bmp, Stream seekable_stream, TIFFColourSpace colour_space)
    {
      SaveTIFF(bmp, seekable_stream, 0, colour_space);
    }

    static int bits_per_sample_for_pixel_format(PixelFormat format, int component)
    {
      switch (format)
      {
        case PixelFormat.Format1bppIndexed:
          return 1;
        case PixelFormat.Format4bppIndexed:
          return 4;
        case PixelFormat.Format16bppRgb555:
        case PixelFormat.Format16bppRgb565:
          if (component == 2)
            return 6;
          else
            return 5;
        case PixelFormat.Format8bppIndexed:
          return 8;
        case PixelFormat.Format16bppGrayScale:
          return 16;
        case PixelFormat.Format24bppRgb:
        case PixelFormat.Format32bppRgb:
        case PixelFormat.Format32bppArgb:
        case PixelFormat.Format32bppPArgb:
          return 8;
        case PixelFormat.Format48bppRgb:
        case PixelFormat.Format64bppArgb:
        case PixelFormat.Format64bppPArgb:
          return 16;
        default:
          return 0;
      }
    }

    static int bits_per_sample_for_pixel_format(PixelFormat format)
    {
      return bits_per_sample_for_pixel_format(format, 1);
    }

    static int samples_per_pixel_for_pixel_format(PixelFormat format, TIFFColourSpace colour_space)
    {
      switch (format)
      {
        case PixelFormat.Format1bppIndexed:
        case PixelFormat.Format4bppIndexed:
        case PixelFormat.Format8bppIndexed:
        case PixelFormat.Format16bppGrayScale:
          return 1;
        case PixelFormat.Format16bppRgb555:
        case PixelFormat.Format16bppRgb565:
        case PixelFormat.Format24bppRgb:
        case PixelFormat.Format32bppRgb:
        case PixelFormat.Format48bppRgb:
        case PixelFormat.Format32bppArgb:
        case PixelFormat.Format32bppPArgb:
        case PixelFormat.Format64bppArgb:
        case PixelFormat.Format64bppPArgb:
          if (colour_space == TIFFColourSpace.CMYK)
            return 4;
          else
            return 3;
        default:
          return 0;
      }
    }

    static int bits_per_pixel_for_pixel_format(PixelFormat format)
    {
      switch (format)
      {
        case PixelFormat.Format1bppIndexed:
          return 1;
        case PixelFormat.Format4bppIndexed:
          return 4;
        case PixelFormat.Format8bppIndexed:
          return 8;
        case PixelFormat.Format16bppGrayScale:
        case PixelFormat.Format16bppRgb555:
        case PixelFormat.Format16bppRgb565:
          return 16;
        case PixelFormat.Format24bppRgb:
          return 24;
        case PixelFormat.Format32bppRgb:
        case PixelFormat.Format32bppArgb:
        case PixelFormat.Format32bppPArgb:
          return 32;
        case PixelFormat.Format48bppRgb:
          return 48;
        case PixelFormat.Format64bppArgb:
        case PixelFormat.Format64bppPArgb:
          return 64;
        default:
          return 0;
      }
    }

    const int MaximumStripSize = 8200; // as per the spec recommendation of "about 8kb"

    public unsafe static void SaveTIFF(Bitmap bmp, Stream seekable_stream, int offset, TIFFColourSpace colour_space)
    {
      seekable_stream.Seek(offset, SeekOrigin.Begin);

      BinaryWriter writer = new BinaryWriter(seekable_stream);

      int tiff_pixel_bits = 0;
      for (int i=1; i <= samples_per_pixel_for_pixel_format(bmp.PixelFormat, colour_space); i++)
        tiff_pixel_bits += bits_per_sample_for_pixel_format(bmp.PixelFormat, i);

      int tiff_stride_bits = bmp.Width * tiff_pixel_bits;
      int tiff_stride_bytes = (tiff_stride_bits + 7) >> 3;
      int tiff_rows_per_strip = MaximumStripSize / tiff_stride_bytes;

      if (tiff_rows_per_strip == 0)
        tiff_rows_per_strip = 1;

      int num_strips = (bmp.Height + tiff_rows_per_strip - 1) / tiff_rows_per_strip;

      int strip_size = tiff_rows_per_strip * tiff_stride_bytes;

      int IFD_offset = 8;
      int IFD_size = 2 + 11 * 12 + 4;
      int XRes_offset = IFD_offset + IFD_size;
      int XRes_size = 8;
      int YRes_offset = XRes_offset + XRes_size;
      int YRes_size = 8;
      
      int[] strip_offsets = new int[num_strips];

      int strip_offset_table_offset = YRes_offset + YRes_size;
      int strip_offset_table_size = num_strips * 4;
      if (strip_offset_table_size <= 4)
        strip_offset_table_size = 0;

      int strip_size_table_offset = strip_offset_table_offset + strip_offset_table_size;
      int strip_size_table_size = num_strips * 4;
      if (strip_size_table_size <= 4)
        strip_size_table_size = 0;
      
      for (int i=0; i < num_strips; i++)
        if (i == 0)
          strip_offsets[i] = strip_size_table_offset + strip_size_table_size;
        else
          strip_offsets[i] = strip_offsets[i - 1] + strip_size;

      // the header
      writer.Write((ushort)0x4949);
      writer.Write((ushort)42);
      writer.Write((uint)8);

      // the IFD
      writer.Write((ushort)11);
      // ImageWidth
      writer.Write((ushort)TIFFTag.ImageWidth);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      writer.Write((uint)bmp.Width);
      // ImageLength
      writer.Write((ushort)TIFFTag.ImageLength);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      writer.Write((uint)bmp.Height);
      // BitsPerSample
      writer.Write((ushort)TIFFTag.BitsPerSample);
      writer.Write((ushort)TIFFType.Byte);
      writer.Write((uint)samples_per_pixel_for_pixel_format(bmp.PixelFormat, colour_space));
      for (int i=1; i <= 4; i++)
        writer.Write((byte)bits_per_sample_for_pixel_format(bmp.PixelFormat, i));
      // Compression
      writer.Write((ushort)TIFFTag.Compression);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      writer.Write((uint)TIFFCompression.Uncompressed);
      // PhotometricInterpretation
      writer.Write((ushort)TIFFTag.PhotometricInterpretation);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      switch (colour_space)
      {
        case TIFFColourSpace.Lab:
          writer.Write((uint)TIFFPhotometricInterpretation.CIELab);
          break;
        case TIFFColourSpace.CMYK:
          writer.Write((uint)TIFFPhotometricInterpretation.CMYK);
          break;
        case TIFFColourSpace.RGB:
        default:
          writer.Write((uint)TIFFPhotometricInterpretation.RGB);
          break;
      }
      // StripOffsets
      writer.Write((ushort)TIFFTag.StripOffsets);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)num_strips);
      if (strip_offset_table_size == 0)
        writer.Write((uint)strip_offsets[0]);
      else
        writer.Write((uint)strip_offset_table_offset);
      // SamplesPerPixel
      writer.Write((ushort)TIFFTag.SamplesPerPixel);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      writer.Write((uint)samples_per_pixel_for_pixel_format(bmp.PixelFormat, colour_space));
      // RowsPerStrip
      writer.Write((ushort)TIFFTag.RowsPerStrip);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)1);
      writer.Write((uint)tiff_rows_per_strip);
      // StripByteCounts
      writer.Write((ushort)TIFFTag.StripByteCounts);
      writer.Write((ushort)TIFFType.Long);
      writer.Write((uint)num_strips);
      if (strip_size_table_size == 0)
        writer.Write((uint)(bmp.Height * tiff_stride_bytes));
      else
        writer.Write((uint)strip_size_table_offset);
      // XResolution
      writer.Write((ushort)TIFFTag.XResolution);
      writer.Write((ushort)TIFFType.Rational);
      writer.Write((uint)1);
      writer.Write((uint)XRes_offset);
      // YResolution
      writer.Write((ushort)TIFFTag.YResolution);
      writer.Write((ushort)TIFFType.Rational);
      writer.Write((uint)1);
      writer.Write((uint)YRes_offset);
      // next IFD offset
      writer.Write((uint)0);

      // X resolution
      writer.Write((uint)72);
      writer.Write((uint)1);
      // Y resolution
      writer.Write((uint)72);
      writer.Write((uint)1);
      // strip offsets
      for (int i=0; i < num_strips; i++)
        writer.Write(strip_offsets[i]);
      // strip sizes
      for (int i=0; i < num_strips; i++)
        if (i + 1 < num_strips)
          writer.Write(strip_size);
        else
          writer.Write((uint)((((bmp.Height - 1) % tiff_rows_per_strip) + 1) * tiff_stride_bytes));
      // strip data
      byte[] stride_buf = new byte[tiff_stride_bytes];
      BitmapData data = null;

      try
      {
        data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);

        byte *data_ptr = (byte *)data.Scan0.ToPointer();

        int data_stride = (bits_per_pixel_for_pixel_format(data.PixelFormat) * data.Width + 7) >> 3;
        int pixel_bytes = bits_per_pixel_for_pixel_format(data.PixelFormat) >> 3;

        if ((bits_per_pixel_for_pixel_format(data.PixelFormat) <= 8) || (colour_space == TIFFColourSpace.CMYK))
          for (int y=0; y < bmp.Height; y++)
          {
            int o = y * data.Stride;

            for (int p=0; (p < data_stride) && (p < stride_buf.Length); p++)
              stride_buf[p] = data_ptr[o + p];

            writer.Write(stride_buf);
          }
        else if (colour_space == TIFFColourSpace.Lab)
        {
          if (bits_per_sample_for_pixel_format(data.PixelFormat) == 16)
            for (int y=0; y < bmp.Height; y++)
            {
              int o = y * data.Stride;

              for (int x=0, p=0; x < bmp.Width; x++, p += pixel_bytes)
                for (int i=0; i < 6; i++)
                {
                  int j = i ^ 4 ^ ((i & 2) << 1); // {0,1,2,3,4,5} -> {4,5,2,3,0,1}

                  if ((i == 3) || (i == 5))
                    stride_buf[x * 6 + j] = (byte)(0x80 ^ data_ptr[o + p + i]); // unbias
                  else
                    stride_buf[x * 6 + j] = data_ptr[o + p + i];
                }

              writer.Write(stride_buf);
            }
          else
            for (int y=0; y < bmp.Height; y++)
            {
              int o = y * data.Stride;

              for (int x=0, p=0; x < bmp.Width; x++, p += pixel_bytes)
              {
                stride_buf[x * 3 + 0] = data_ptr[o + p + 2];
                stride_buf[x * 3 + 1] = (byte)(0x80 ^ data_ptr[o + p + 1]);
                stride_buf[x * 3 + 2] = (byte)(0x80 ^ data_ptr[o + p + 0]);
              }

              writer.Write(stride_buf);
            }        
        }
        else if (bits_per_sample_for_pixel_format(data.PixelFormat) == 16)
          for (int y=0; y < bmp.Height; y++)
          {
            int o = y * data.Stride;

            for (int x=0, p=0; x < bmp.Width; x++, p += pixel_bytes)
              for (int i=0; i < 6; i++)
              {
                int j = i ^ 4 ^ ((i & 2) << 1); // {0,1,2,3,4,5} -> {4,5,2,3,0,1}

                stride_buf[x * 6 + j] = data_ptr[o + p + i];
              }

            writer.Write(stride_buf);
          }
        else
          for (int y=0; y < bmp.Height; y++)
          {
            int o = y * data.Stride;

            for (int x=0, p=0; x < bmp.Width; x++, p += pixel_bytes)
              for (int i=0; i < 3; i++)
                stride_buf[x * 3 + i] = data_ptr[o + p + 2 - i];

            writer.Write(stride_buf);
          }
      }
      finally
      {
        if (data != null)
          bmp.UnlockBits(data);
      }
    }

    static string _(string key)
    {
      return Language.Item[key];
    }
  }

  class MSBBinaryReader : BinaryReader
  {
    public MSBBinaryReader(Stream input)
      : base(input)
    {
    }

    public MSBBinaryReader(Stream input, Encoding encoding)
      : base(input, encoding)
    {
    }

    public override decimal ReadDecimal()
    {
      int[] bits = new int[4];

      bits[3] = ReadInt32();
      bits[2] = ReadInt32();
      bits[1] = ReadInt32();
      bits[0] = ReadInt32();

      return new decimal(bits);
    }

    public override double ReadDouble()
    {
      double[] item = new double[1];

      for (int i=0; i < 8; i++)
        Buffer.SetByte(item, 7 - i, ReadByte());

      return item[0];
    }

    public override short ReadInt16()
    {
      return unchecked((short)ReadUInt16());
    }

    public override int ReadInt32()
    {
      return unchecked((int)ReadUInt32());
    }

    public override long ReadInt64()
    {
      return unchecked((int)ReadUInt64());
    }

    public override float ReadSingle()
    {
      float[] item = new float[1];

      for (int i=0; i < 4; i++)
        Buffer.SetByte(item, 3 - i, ReadByte());

      return item[0];
    }

    public override ushort ReadUInt16()
    {
      ushort left = ReadByte();
      byte right = ReadByte();

      return unchecked((ushort)((left << 8) | right));
    }

    public override uint ReadUInt32()
    {
      uint left = ReadUInt16();
      ushort right = ReadUInt16();

      return unchecked((uint)((left << 16) | right));
    }

    public override ulong ReadUInt64()
    {
      ulong left = ReadUInt32();
      uint right = ReadUInt32();

      return unchecked((left << 32) | right);
    }
  }
}


More information about the Mono-list mailing list