//! \file ImageTIFF.cs //! \date Mon Jul 07 06:39:45 2014 //! \brief TIFF image implementation. // // Copyright (C) 2014 by morkt // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to // deal in the Software without restriction, including without limitation the // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // using System; using System.IO; using System.Text; using System.ComponentModel.Composition; using System.Windows.Media.Imaging; using GameRes.Utility; namespace GameRes { [Export(typeof(ImageFormat))] public class TifFormat : ImageFormat { public override string Tag { get { return "TIFF"; } } public override string Description { get { return "Tagged Image File Format"; } } public override uint Signature { get { return 0; } } public override bool CanWrite { get { return true; } } public TifFormat () { Extensions = new string[] { "tif", "tiff" }; Signatures = new uint[] { 0x002a4949, 0x2a004d4d }; } public override ImageData Read (IBinaryStream file, ImageMetaData info) { var decoder = new TiffBitmapDecoder (file.AsStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); var frame = decoder.Frames[0]; frame.Freeze(); return new ImageData (frame, info); } public override void Write (Stream file, ImageData image) { var encoder = new TiffBitmapEncoder(); encoder.Compression = TiffCompressOption.Zip; encoder.Frames.Add (BitmapFrame.Create (image.Bitmap, null, null, null)); encoder.Save (file); } enum TIFF { ImageWidth = 0x100, ImageHeight = 0x101, BitsPerSample = 0x102, Compression = 0x103, SamplesPerPixel = 0x115, XResolution = 0x11a, YResolution = 0x11b, XPosition = 0x11e, YPosition = 0x11f, } enum TagType { Byte = 1, Ascii = 2, Short = 3, Long = 4, Rational = 5, SByte = 6, Undefined = 7, SShort = 8, SLong = 9, SRational = 10, Float = 11, Double = 12, LastKnown = Double, } enum MetaParsed { None = 0, Width = 1, Height = 2, BPP = 4, PosX = 8, PosY = 16, Sufficient = Width|Height|BPP, Complete = Sufficient|PosX|PosY, } public override ImageMetaData ReadMetaData (IBinaryStream stream) { using (var file = new Parser (stream)) return file.ReadMetaData(); } internal sealed class Parser : IDisposable { private IBinaryStream m_file; private readonly bool m_is_bigendian; private readonly uint m_first_ifd; private readonly uint[] m_type_size = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 }; private delegate ushort UInt16Reader (); private delegate uint UInt32Reader (); private delegate ulong UInt64Reader (); UInt16Reader ReadUInt16; UInt32Reader ReadUInt32; UInt64Reader ReadUInt64; public Parser (IBinaryStream file) { m_file = file; m_is_bigendian = 0x2a004d4d == m_file.Signature; if (m_is_bigendian) { ReadUInt16 = () => Binary.BigEndian (m_file.ReadUInt16()); ReadUInt32 = () => Binary.BigEndian (m_file.ReadUInt32()); ReadUInt64 = () => Binary.BigEndian (m_file.ReadUInt64()); } else { ReadUInt16 = () => m_file.ReadUInt16(); ReadUInt32 = () => m_file.ReadUInt32(); ReadUInt64 = () => m_file.ReadUInt64(); } m_first_ifd = ReadUInt32(); } public long FindLastIFD () { uint ifd = m_first_ifd; for (;;) { m_file.Position = ifd; uint tag_count = ReadUInt16(); ifd += 2 + tag_count*12; uint ifd_next = ReadUInt32(); if (0 == ifd_next) break; if (ifd_next == ifd || ifd_next >= m_file.Length) return -1; ifd = ifd_next; } return ifd; } public ImageMetaData ReadMetaData () { MetaParsed parsed = MetaParsed.None; int width = 0, height = 0, bpp = 0, pos_x = 0, pos_y = 0; uint ifd = m_first_ifd; while (ifd != 0 && parsed != MetaParsed.Complete) { m_file.Position = ifd; uint tag_count = ReadUInt16(); ifd += 2; for (uint i = 0; i < tag_count && parsed != MetaParsed.Complete; ++i) { ushort tag = ReadUInt16(); TagType type = (TagType)ReadUInt16(); uint count = ReadUInt32(); if (0 != count && 0 != type && type <= TagType.LastKnown) { switch ((TIFF)tag) { case TIFF.ImageWidth: if (1 == count) if (ReadOffsetValue (type, out width)) parsed |= MetaParsed.Width; break; case TIFF.ImageHeight: if (1 == count) if (ReadOffsetValue (type, out height)) parsed |= MetaParsed.Height; break; case TIFF.XPosition: if (1 == count) if (ReadOffsetValue (type, out pos_x)) parsed |= MetaParsed.PosX; break; case TIFF.YPosition: if (1 == count) if (ReadOffsetValue (type, out pos_y)) parsed |= MetaParsed.PosY; break; case TIFF.BitsPerSample: if (count * GetTypeSize (type) > 4) { var bpp_offset = ReadUInt32(); m_file.Position = bpp_offset; } bpp = 0; for (uint b = 0; b < count; ++b) { int plane = 0; ReadValue (type, out plane); bpp += plane; } parsed |= MetaParsed.BPP; break; default: break; } } ifd += 12; m_file.Position = ifd; } uint ifd_next = ReadUInt32(); if (ifd_next == ifd) break; ifd = ifd_next; } if (MetaParsed.Sufficient == (parsed & MetaParsed.Sufficient)) return new ImageMetaData() { Width = (uint)width, Height = (uint)height, OffsetX = pos_x, OffsetY = pos_y, BPP = bpp, }; else return null; } uint GetTypeSize (TagType type) { if ((int)type < m_type_size.Length) return m_type_size[(int)type]; else return 0; } bool ReadOffsetValue (TagType type, out int value) { if (GetTypeSize (type) > 4) m_file.Position = ReadUInt32(); return ReadValue (type, out value); } bool ReadValue (TagType type, out int value) { switch (type) { case TagType.Undefined: case TagType.SByte: case TagType.Byte: value = m_file.ReadByte(); break; default: case TagType.Ascii: value = 0; return false; case TagType.SShort: case TagType.Short: value = ReadUInt16(); break; case TagType.SLong: case TagType.Long: value = (int)ReadUInt32(); break; case TagType.Rational: return ReadRational (out value); case TagType.SRational: return ReadSRational (out value); case TagType.Float: return ReadFloat (out value); case TagType.Double: return ReadDouble (out value); } return true; } bool ReadRational (out int value) { uint numer = ReadUInt32(); uint denom = ReadUInt32(); if (1 == denom) value = (int)numer; else if (0 == denom) { value = 0; return false; } else value = (int)((double)numer / denom); return true; } bool ReadSRational (out int value) { int numer = (int)ReadUInt32(); int denom = (int)ReadUInt32(); if (1 == denom) value = numer; else if (0 == denom) { value = 0; return false; } else value = (int)((double)numer / denom); return true; } bool ReadFloat (out int value) { var convert_buffer = new byte[4]; if (4 != m_file.Read (convert_buffer, 0, 4)) { value = 0; return false; } if (m_is_bigendian) Array.Reverse (convert_buffer); value = (int)BitConverter.ToSingle (convert_buffer, 0); return true; } bool ReadDouble (out int value) { var convert_buffer = new byte[8]; if (8 != m_file.Read (convert_buffer, 0, 8)) { value = 0; return false; } if (m_is_bigendian) Array.Reverse (convert_buffer); long bits = BitConverter.ToInt64 (convert_buffer, 0); value = (int)BitConverter.Int64BitsToDouble (bits); return true; } #region IDisposable Members public void Dispose () { GC.SuppressFinalize (this); } #endregion } } }