GARbro-mirror/GameRes/ImageTIFF.cs

360 lines
13 KiB
C#

//! \file ImageTIFF.cs
//! \date Mon Jul 07 06:39:45 2014
//! \brief TIFF image implementation.
//
// Copyright (C) 2014-2016 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;
using System.Collections.Generic;
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 = file.ReadHeader (8).ToUInt32 (4);
}
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;
var seen_ifd = new HashSet<uint>();
uint ifd = m_first_ifd;
while (ifd != 0 && parsed != MetaParsed.Complete && !seen_ifd.Contains (ifd))
{
m_file.Position = ifd;
seen_ifd.Add (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;
}
ifd = ReadUInt32();
}
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 ^ !BitConverter.IsLittleEndian)
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 ^ !BitConverter.IsLittleEndian)
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
}
}
}