GARbro-mirror/ArcFormats/ImagePSD.cs

236 lines
8.6 KiB
C#

//! \file ImagePSD.cs
//! \date Tue Nov 17 18:22:36 2015
//! \brief Adobe Photoshop file format implementation.
//
// Copyright (C) 2015 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 GameRes.Utility;
using System;
using System.ComponentModel.Composition;
using System.IO;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace GameRes.Formats.Adobe
{
internal class PsdMetaData : ImageMetaData
{
public int Channels;
public int Mode;
}
[Export(typeof(ImageFormat))]
public class PsdFormat : ImageFormat
{
public override string Tag { get { return "PSD"; } }
public override string Description { get { return "Adobe Photoshop image format"; } }
public override uint Signature { get { return 0x53504238; } } // '8BPS'
public override ImageMetaData ReadMetaData (IBinaryStream stream)
{
var header = stream.ReadHeader (26).ToArray();
int version = BigEndian.ToInt16 (header, 4);
if (1 != version)
return null;
int channels = BigEndian.ToInt16 (header, 0x0C);
if (channels < 1 || channels > 56)
return null;
uint height = BigEndian.ToUInt32 (header, 0x0E);
uint width = BigEndian.ToUInt32 (header, 0x12);
if (width < 1 || width > 30000 || height < 1 || height > 30000)
return null;
int bpc = BigEndian.ToInt16 (header, 0x16);
return new PsdMetaData
{
Width = width,
Height = height,
Channels = channels,
BPP = channels * bpc,
Mode = BigEndian.ToInt16 (header, 0x18),
};
}
public override ImageData Read (IBinaryStream stream, ImageMetaData info)
{
var meta = (PsdMetaData)info;
using (var reader = new PsdReader (stream, meta))
{
reader.Unpack();
return ImageData.Create (info, reader.Format, reader.Palette, reader.Data);
}
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("PsdFormat.Write not implemented");
}
}
internal class PsdReader : IDisposable
{
IBinaryStream m_input;
PsdMetaData m_info;
byte[] m_output;
int m_channel_size;
public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; }
public byte[] Data { get { return m_output; } }
public PsdReader (IBinaryStream input, PsdMetaData info)
{
m_info = info;
m_input = input;
int bpc = m_info.BPP / m_info.Channels;
switch (m_info.Mode)
{
case 3:
if (8 != bpc)
throw new NotSupportedException();
if (3 == m_info.Channels)
Format = PixelFormats.Bgr24;
else if (4 == m_info.Channels)
Format = PixelFormats.Bgra32;
else if (m_info.Channels > 4)
Format = PixelFormats.Bgr32;
else
throw new NotSupportedException();
break;
// case 2: Format = PixelFormats.Indexed8; break;
case 1:
if (8 == bpc)
Format = PixelFormats.Gray8;
else if (16 == bpc)
Format = PixelFormats.Gray16;
else
throw new NotSupportedException();
break;
case 0: Format = PixelFormats.BlackWhite; break;
default:
throw new NotImplementedException ("Not supported PSD color mode");
}
m_channel_size = (int)m_info.Height * (int)m_info.Width * bpc / 8;
}
public void Unpack ()
{
m_input.Position = 0x1A;
int color_data_length = Binary.BigEndian (m_input.ReadInt32());
long next_pos = m_input.Position + color_data_length;
if (0 != color_data_length)
{
if (8 == m_info.BPP)
ReadPalette (color_data_length);
m_input.Position = next_pos;
}
next_pos += 4 + Binary.BigEndian (m_input.ReadInt32());
m_input.Position = next_pos; // skip Image Resources
next_pos += 4 + Binary.BigEndian (m_input.ReadInt32());
m_input.Position = next_pos; // skip Layer and Mask Information
int compression = Binary.BigEndian (m_input.ReadInt16());
int remaining = checked((int)(m_input.Length - m_input.Position));
byte[] pixels;
if (0 == compression)
pixels = m_input.ReadBytes (remaining);
else if (1 == compression)
pixels = UnpackRLE();
else
throw new NotSupportedException ("PSD files with ZIP compression not supported");
if (1 == m_info.Channels)
{
m_output = pixels;
return;
}
int channels = Math.Min (4, m_info.Channels);
m_output = new byte[channels * m_channel_size];
int src = 0;
for (int ch = 0; ch < channels; ++ch)
{
int dst = ChannelMap[ch];
for (int i = 0; i < m_channel_size; ++i)
{
m_output[dst] = pixels[src++];
dst += channels;
}
}
}
static readonly byte[] ChannelMap = { 2, 1, 0, 3 };
void ReadPalette (int palette_size)
{
int colors = Math.Min (0x100, palette_size/3);
Palette = ImageFormat.ReadPalette (m_input.AsStream, colors, PaletteFormat.Rgb);
}
byte[] UnpackRLE ()
{
var scanlines = new int[m_info.Channels, (int)m_info.Height];
for (int ch = 0; ch < m_info.Channels; ++ch)
{
for (uint row = 0; row < m_info.Height; ++row)
scanlines[ch,row] = Binary.BigEndian (m_input.ReadInt16());
}
var pixels = new byte[m_info.Channels * m_channel_size];
int dst = 0;
for (int ch = 0; ch < m_info.Channels; ++ch)
{
for (uint row = 0; row < m_info.Height; ++row)
{
int line_count = scanlines[ch,row];
int n = 0;
while (n < line_count)
{
int count = m_input.ReadInt8();
++n;
if (count >= 0)
{
++count;
m_input.Read (pixels, dst, count);
dst += count;
n += count;
}
else if (count > -128)
{
count = 1 - count;
byte color = m_input.ReadUInt8();
++n;
for (int i = 0; i < count; ++i)
pixels[dst++] = color;
}
}
}
}
return pixels;
}
#region IDisposable Members
public void Dispose ()
{
}
#endregion
}
}