mirror of
https://github.com/crskycode/GARbro.git
synced 2024-12-23 19:34:15 +08:00
364 lines
13 KiB
C#
364 lines
13 KiB
C#
//! \file ImageTIFF.cs
|
|
//! \date Mon Jul 07 06:39:45 2014
|
|
//! \brief TIFF image implementation.
|
|
//
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.ComponentModel.Composition;
|
|
using System.Windows.Media.Imaging;
|
|
using GameRes.Utility;
|
|
|
|
namespace GameRes
|
|
{
|
|
#if DEBUG
|
|
// FIXME
|
|
// .Net TIFF encoder messes up transparency,
|
|
// therefore this class is disabled in release build
|
|
[Export(typeof(ImageFormat))]
|
|
#endif
|
|
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 TifFormat ()
|
|
{
|
|
Extensions = new string[] { "tif", "tiff" };
|
|
Signatures = new uint[] { 0x002a4949, 0x2a004d4d };
|
|
}
|
|
|
|
public override ImageData Read (Stream file, ImageMetaData info)
|
|
{
|
|
var decoder = new TiffBitmapDecoder (file,
|
|
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));
|
|
encoder.Save (file);
|
|
}
|
|
|
|
private delegate uint UInt32Reader();
|
|
private delegate ushort UInt16Reader();
|
|
|
|
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 (Stream stream)
|
|
{
|
|
using (var file = new Parser (stream))
|
|
return file.ReadMetaData();
|
|
}
|
|
|
|
public class Parser : IDisposable
|
|
{
|
|
private BinaryReader 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 (Stream file)
|
|
{
|
|
m_file = new ArcView.Reader (file);
|
|
uint signature = m_file.ReadUInt32();
|
|
m_is_bigendian = 0x2a004d4d == 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.BaseStream.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.BaseStream.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.BaseStream.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.BaseStream.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.BaseStream.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.BaseStream.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
|
|
bool disposed = false;
|
|
|
|
public void Dispose ()
|
|
{
|
|
Dispose (true);
|
|
GC.SuppressFinalize (this);
|
|
}
|
|
|
|
protected virtual void Dispose (bool disposing)
|
|
{
|
|
if (!disposed)
|
|
{
|
|
if (disposing)
|
|
{
|
|
m_file.Dispose();
|
|
}
|
|
m_file = null;
|
|
disposed = true;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|
|
}
|