implemented D.O.'s VRS, GGA and GGS images.

This commit is contained in:
morkt 2018-01-16 11:53:25 +04:00
parent c270574f52
commit aa93dabd2d
8 changed files with 665 additions and 20 deletions

View File

@ -123,6 +123,11 @@
<Compile Include="elf\ArcAi5DAT.cs" />
<Compile Include="elf\ImageRMT.cs" />
<Compile Include="HuffmanCompression.cs" />
<Compile Include="Ikura\ArcTAN.cs" />
<Compile Include="Ikura\ImageGGA.cs" />
<Compile Include="Ikura\ImageGGS.cs" />
<Compile Include="Ikura\ImageTAN.cs" />
<Compile Include="Ikura\ImageVRS.cs" />
<Compile Include="Lambda\ArcLAX.cs" />
<Compile Include="Lambda\ImageCLS.cs" />
<Compile Include="Maika\ArcBK.cs" />

View File

@ -45,7 +45,7 @@ namespace GameRes.Formats.Ikura
public DrsOpener ()
{
Extensions = Enumerable.Empty<string>(); // DRS archives have no extensions
Extensions = new string[] { "", "dat", "snr" };
}
public override ArcFile TryOpen (ArcView file)
@ -56,7 +56,7 @@ namespace GameRes.Formats.Ikura
if (dir_size < 0x20 || 0 != (dir_size & 0xf) || dir_size + 2 >= file.MaxOffset)
return null;
byte first = file.View.ReadByte (2);
if (0 == first)
if (first <= 0x20)
return null;
file.View.Reserve (0, (uint)dir_size + 2);
int dir_offset = 2;
@ -64,25 +64,19 @@ namespace GameRes.Formats.Ikura
uint next_offset = file.View.ReadUInt32 (dir_offset+12);
if (next_offset > file.MaxOffset || next_offset < dir_size+2)
return null;
var encoding = Encodings.cp932.WithFatalFallback();
byte[] name_raw = new byte[12];
int count = dir_size / 0x10 - 1;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
file.View.Read (dir_offset, name_raw, 0, 12);
int name_length = name_raw.Length;
while (name_length > 0 && 0 == name_raw[name_length-1])
--name_length;
if (0 == name_length)
var name = file.View.ReadString (dir_offset, 12);
if (string.IsNullOrEmpty (name))
return null;
uint offset = next_offset;
dir_offset += 0x10;
next_offset = file.View.ReadUInt32 (dir_offset+12);
if (next_offset > file.MaxOffset || next_offset < offset)
return null;
string name = encoding.GetString (name_raw, 0, name_length).ToLowerInvariant();
var entry = FormatCatalog.Instance.Create<Entry> (name);
entry.Offset = offset;
entry.Size = next_offset - offset;
@ -138,21 +132,16 @@ namespace GameRes.Formats.Ikura
uint index_size = file.View.ReadUInt32 (12);
if (index_size > file.MaxOffset)
return null;
var encoding = Encodings.cp932.WithFatalFallback();
byte[] name_raw = new byte[12];
long dir_offset = 0x20;
var dir = new List<Entry> (count);
bool has_scripts = false;
for (int i = 0; i < count; ++i)
{
file.View.Read (dir_offset, name_raw, 0, 12);
int name_length = name_raw.Length;
while (name_length > 0 && 0 == name_raw[name_length-1])
--name_length;
if (0 == name_length)
var name = file.View.ReadString (dir_offset, 12);
if (string.IsNullOrEmpty (name))
return null;
string name = encoding.GetString (name_raw, 0, name_length).ToLowerInvariant();
name = name.ToLowerInvariant();
Entry entry;
if (name.EndsWith (".isf") || name.EndsWith (".snr"))
{

128
ArcFormats/Ikura/ArcTAN.cs Normal file
View File

@ -0,0 +1,128 @@
//! \file ArcTAN.cs
//! \date 2018 Jan 16
//! \brief D.O. animation resource format.
//
// Copyright (C) 2018 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.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
namespace GameRes.Formats.Ikura
{
internal class TanEntry : Entry
{
public int Index;
}
internal class TanArchive : ArcFile
{
public readonly TanMetaData Info;
public TanArchive (ArcView arc, ArchiveFormat impl, ICollection<Entry> dir, TanMetaData info)
: base (arc, impl, dir)
{
Info = info;
}
}
[Export(typeof(ArchiveFormat))]
public class TanOpener : ArchiveFormat
{
public override string Tag { get { return "TAN/DO"; } }
public override string Description { get { return "D.O. animation resource"; } }
public override uint Signature { get { return 0; } }
public override bool IsHierarchic { get { return false; } }
public override bool CanWrite { get { return false; } }
public override ArcFile TryOpen (ArcView file)
{
if (!file.Name.HasExtension (".tan"))
return null;
int count = file.View.ReadInt16 (0);
if (!IsSaneCount (count))
return null;
uint index_pos = 2 + (uint)count * 4;
var info = new TanMetaData {
Width = file.View.ReadUInt16 (index_pos),
Height = file.View.ReadUInt16 (index_pos+2),
DataOffset = index_pos + 4,
};
index_pos += 0x404;
count = file.View.ReadInt16 (index_pos);
if (!IsSaneCount (count))
return null;
index_pos += 2;
var base_name = Path.GetFileNameWithoutExtension (file.Name);
var base_offset = index_pos + 4 * count;
var dir = new List<Entry> (count);
for (int i = 0; i < count; ++i)
{
var entry = new TanEntry {
Name = string.Format ("{0}#{1:D2}", base_name, i),
Type = "image",
Offset = base_offset + file.View.ReadUInt32 (index_pos),
Index = i,
};
dir.Add (entry);
index_pos += 4;
}
for (int i = 1; i < count; ++i)
{
dir[i-1].Size = (uint)(dir[i].Offset - dir[i-1].Offset);
if (!dir[i-1].CheckPlacement (file.MaxOffset))
return null;
}
dir[dir.Count-1].Size = (uint)(file.MaxOffset - dir[dir.Count-1].Offset);
return new TanArchive (file, this, dir, info);
}
public override IImageDecoder OpenImage (ArcFile arc, Entry entry)
{
var tarc = (TanArchive)arc;
var tent = (TanEntry)entry;
var input = arc.File.CreateStream();
return new TanFrameDecoder (input, tarc.Info, tent.Index);
}
}
internal class TanFrameDecoder : BinaryImageDecoder
{
int m_frame;
TanReader m_reader;
public TanFrameDecoder (IBinaryStream input, TanMetaData info, int frame)
: base (input, info)
{
m_frame = frame;
m_reader = new TanReader (m_input, info);
}
protected override ImageData GetImageData ()
{
var pixels = m_reader.UnpackFrame (m_frame);
return ImageData.Create (Info, m_reader.Format, m_reader.Palette, pixels);
}
}
}

View File

@ -460,13 +460,13 @@ namespace GameRes.Formats.Ikura
}
[Export(typeof(ImageFormat))]
public class GgaFormat : ImageFormat
public class Gga0Format : ImageFormat
{
public override string Tag { get { return "GG2"; } }
public override string Description { get { return "IKURA GDL image format"; } }
public override uint Signature { get { return 0x30414747u; } } // 'GGA0'
public GgaFormat ()
public Gga0Format ()
{
Extensions = new string[] { "gg1", "gg2", "gg3", "gg0" };
}

View File

@ -0,0 +1,81 @@
//! \file ImageGGA.cs
//! \date 2018 Jan 16
//! \brief D.O. compressed image format.
//
// Copyright (C) 2018 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using GameRes.Compression;
namespace GameRes.Formats.Ikura
{
[Export(typeof(ImageFormat))]
public class GgaFormat : ImageFormat
{
public override string Tag { get { return "GGA"; } }
public override string Description { get { return "D.O. image format"; } }
public override uint Signature { get { return 0; } }
class GgaMetaData : ImageMetaData
{
public int UnpackedSize;
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".gga"))
return null;
int x = file.ReadInt16();
int y = file.ReadInt16();
uint w = file.ReadUInt16();
uint h = file.ReadUInt16();
int unpacked_size = file.ReadInt32();
if (0 == w || 0 == h || w > 0x7FFF || h > 0x7FFF || x < 0 || y < 0
|| 3 * w * h != unpacked_size)
return null;
return new GgaMetaData {
Width = w, Height = h, OffsetX = x, OffsetY = y, BPP = 24,
UnpackedSize = unpacked_size,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var meta = (GgaMetaData)info;
file.Position = 12;
var pixels = new byte[meta.UnpackedSize];
using (var input = new LzssStream (file.AsStream, LzssMode.Decompress, true))
{
if (pixels.Length != input.Read (pixels, 0, pixels.Length))
throw new InvalidFormatException();
}
return ImageData.Create (info, PixelFormats.Bgr24, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GgaFormat.Write not implemented");
}
}
}

View File

@ -0,0 +1,140 @@
//! \file ImageGGS.cs
//! \date 2018 Jan 16
//! \brief D.O. compressed image format.
//
// Copyright (C) 2018 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
namespace GameRes.Formats.Ikura
{
[Export(typeof(ImageFormat))]
public class GgsFormat : ImageFormat
{
public override string Tag { get { return "GGS"; } }
public override string Description { get { return "D.O. image format"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".ggs"))
return null;
int x = file.ReadInt16();
int y = file.ReadInt16();
uint w = file.ReadUInt16();
uint h = file.ReadUInt16();
if (0 == w || 0 == h || w > 0x4000 || h > 0x4000 || x < 0 || y < 0)
return null;
return new ImageMetaData { Width = w, Height = h, OffsetX = x, OffsetY = y, BPP = 24 };
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new GgsReader (file, info);
var pixels = reader.Unpack();
return ImageData.Create (info, reader.Format, null, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("GgsFormat.Write not implemented");
}
}
internal class GgsReader
{
IBinaryStream m_input;
byte[] m_output;
public PixelFormat Format { get { return PixelFormats.Bgr24; } }
public GgsReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_output = new byte[3 * info.Width * info.Height];
}
public byte[] Unpack ()
{
m_input.Position = 8;
for (int channel = 0; channel < 3; ++channel)
{
int dst = channel;
while (dst < m_output.Length)
{
int count;
byte ctl = m_input.ReadUInt8();
if (0 == ctl--)
{
count = m_input.ReadUInt8();
byte v = m_input.ReadUInt8();
while (count --> 0)
{
m_output[dst] = v;
dst += 3;
}
}
else if (0 == ctl--)
{
count = m_input.ReadUInt8();
int offset = m_input.ReadUInt8();
while (count --> 0)
{
m_output[dst] = m_output[dst-offset];
dst += 3;
}
}
else if (0 == ctl--)
{
count = m_input.ReadUInt8();
int offset = m_input.ReadUInt16();
while (count --> 0)
{
m_output[dst] = m_output[dst-offset];
dst += 3;
}
}
else if (0 == ctl--)
{
dst += m_input.ReadUInt8();
}
else if (0 == ctl)
{
dst += m_input.ReadUInt16();
}
else
{
count = ctl;
while (count --> 0)
{
m_output[dst] = m_input.ReadUInt8();
dst += 3;
}
}
}
}
return m_output;
}
}
}

View File

@ -0,0 +1,170 @@
//! \file ImageTAN.cs
//! \date 2018 Jan 16
//! \brief D.O. animation resource format.
//
// Copyright (C) 2018 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
namespace GameRes.Formats.Ikura
{
internal class TanMetaData : ImageMetaData
{
public uint DataOffset;
}
[Export(typeof(ImageFormat))]
public class TanFormat : ImageFormat
{
public override string Tag { get { return "TAN"; } }
public override string Description { get { return "D.O. animation resource"; } }
public override uint Signature { get { return 0; } }
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
if (!file.Name.HasExtension (".tan"))
return null;
int count = file.ReadUInt16();
if (0 == count)
return null;
file.Position = 2 + count * 4;
uint w = file.ReadUInt16();
uint h = file.ReadUInt16();
if (0 == w || 0 == h)
return null;
return new TanMetaData {
Width = w, Height = h, BPP = 8,
DataOffset = (uint)file.Position,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new TanReader (file, (TanMetaData)info);
var pixels = reader.UnpackFrame (0);
return ImageData.Create (info, reader.Format, reader.Palette, pixels);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("TanFormat.Write not implemented");
}
}
internal class TanReader
{
IBinaryStream m_input;
byte[] m_output;
TanMetaData m_info;
public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; }
public TanReader (IBinaryStream input, TanMetaData info)
{
m_input = input;
m_output = new byte[info.Width * info.Height];
m_info = info;
Format = 8 == m_info.BPP ? PixelFormats.Indexed8 : PixelFormats.Bgr24;
}
public byte[] UnpackFrame (int frame)
{
m_input.Position = m_info.DataOffset;
if (8 == m_info.BPP)
Palette = ImageFormat.ReadPalette (m_input.AsStream);
int count = m_input.ReadUInt16();
if (frame >= count)
throw new InvalidFormatException ("Not enough frames in TAN file.");
long base_pos = m_input.Position + 4 * count;
var frame_table = new uint[count];
for (int i = 0; i < count; ++i)
frame_table[i] = m_input.ReadUInt32();
Action Unpack;
if (8 == m_info.BPP)
Unpack = Unpack8bpp;
else
Unpack = Unpack24bpp;
for (int i = 0; i <= frame; ++i)
{
m_input.Position = base_pos + frame_table[i];
Unpack();
}
return m_output;
}
void Unpack8bpp ()
{
int dst = 0;
while (dst < m_output.Length)
{
int count;
byte ctl = m_input.ReadUInt8();
if (0 == ctl--)
{
count = m_input.ReadUInt8();
byte v = m_input.ReadUInt8();
while (count --> 0)
m_output[dst++] = v;
}
else if (0 == ctl--)
{
count = m_input.ReadUInt8();
int offset = m_input.ReadUInt8();
Binary.CopyOverlapped (m_output, dst-offset, dst, count);
dst += count;
}
else if (0 == ctl--)
{
count = m_input.ReadUInt8();
int offset = m_input.ReadUInt16();
Binary.CopyOverlapped (m_output, dst-offset, dst, count);
dst += count;
}
else if (0 == ctl--)
{
dst += m_input.ReadUInt8();
}
else if (0 == ctl)
{
dst += m_input.ReadUInt16();
}
else
{
count = ctl;
m_input.Read (m_output, dst, count);
dst += count;
}
}
}
void Unpack24bpp ()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,132 @@
//! \file ImageVRS.cs
//! \date 2018 Jan 16
//! \brief D.O. image format.
//
// Copyright (C) 2018 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.ComponentModel.Composition;
using System.IO;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
namespace GameRes.Formats.Ikura
{
[Export(typeof(ImageFormat))]
public class DoFormat : ImageFormat
{
public override string Tag { get { return "VRS/DO"; } }
public override string Description { get { return "D.O. image format"; } }
public override uint Signature { get { return 0x4F44; } } // 'DO'
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (8);
return new ImageMetaData {
Width = header.ToUInt16 (4),
Height = header.ToUInt16 (6),
BPP = 8,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new DoReader (file, info);
reader.Unpack();
return ImageData.Create (info, reader.Format, reader.Palette, reader.Data);
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("DoFormat.Write not implemented");
}
}
internal class DoReader
{
IBinaryStream m_input;
byte[] m_output;
public PixelFormat Format { get; private set; }
public BitmapPalette Palette { get; private set; }
public byte[] Data { get { return m_output; } }
public DoReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_output = new byte[info.Width * info.Height];
Format = PixelFormats.Indexed8;
}
public void Unpack ()
{
m_input.Position = 12;
Palette = ReadPalette();
int dst = 0;
while (dst < m_output.Length)
{
int count;
byte ctl = m_input.ReadUInt8();
if (0 == (ctl & 0xC0))
{
count = ctl & 0x3F;
if (0 == count)
count = m_input.ReadUInt8() + 0x40;
m_input.Read (m_output, dst, count);
}
else if (0 == (ctl & 0x80))
{
count = ctl & 0x3F;
if (0 == count)
count = m_input.ReadUInt8() + 0x40;
++count;
Binary.CopyOverlapped (m_output, dst-1, dst, count);
}
else
{
int offset = (m_input.ReadUInt8() | (ctl & 0xF) << 8) + 1;
count = (ctl >> 4) & 7;
if (0 == count)
count = m_input.ReadUInt8() + 8;
count += 2;
Binary.CopyOverlapped (m_output, dst-offset, dst, count);
}
dst += count;
}
}
BitmapPalette ReadPalette ()
{
var palette_data = m_input.ReadBytes (0x300);
if (palette_data.Length != 0x300)
throw new EndOfStreamException();
int src = 0;
var color_map = new Color[0x100];
for (int i = 0; i < 0x100; ++i)
{
color_map[i] = Color.FromRgb (palette_data[src+1], palette_data[src+2], palette_data[src]);
src += 3;
}
return new BitmapPalette (color_map);
}
}
}