GARbro-mirror/Legacy/Nekotaro/ImageNCG.cs
morkt f90e2e851c updated legacy formats.
(BIZ): moved to Adviz folder.
(GIZ, GIZ2, BIZ2, PR1): new PC-98 image formats.
(DATA, BGM, SED): MyHarvest resource formats.
(HTF): compressed image format.
(PAK): Mina resource archives.
(GCmp): read palette from external resource.
(NCG): Nekotaro image format.
(BND, TCZ, TSZ): Ponytail Soft PC-98 formats.
(NOR): Sophia resource archive.
(SDA, PLA): Squadra D resource archives.
(UCA): added checks to avoid false positives.
2023-10-11 19:03:45 +04:00

294 lines
11 KiB
C#

//! \file ImageNCG.cs
//! \date 2023 Oct 10
//! \brief Nekotaro Game System image format (PC-98).
//
// Copyright (C) 2023 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;
namespace GameRes.Formats.Nekotaro
{
[Export(typeof(ImageFormat))]
public class NcgFormat : ImageFormat
{
public override string Tag => "NCG";
public override string Description => "Nekotaro Game System image format";
public override uint Signature => 0;
public NcgFormat ()
{
Signatures = new[] { 0xC8500000u, 0u };
}
public override ImageMetaData ReadMetaData (IBinaryStream file)
{
var header = file.ReadHeader (4);
int left = header[0] << 3;
int top = header[1] << 1;
int width = header[2] << 3;
int height = header[3] << 1;
int right = left + width;
int bottom = top + height;
if (right > 640 || bottom > 400 || 0 == width || 0 == height)
return null;
return new ImageMetaData {
Width = (uint)width,
Height = (uint)height,
OffsetX = left,
OffsetY = top,
BPP = 4,
};
}
public override ImageData Read (IBinaryStream file, ImageMetaData info)
{
var reader = new NcgReader (file, info);
return reader.Unpack();
}
public override void Write (Stream file, ImageData image)
{
throw new System.NotImplementedException ("NcgFormat.Write not implemented");
}
}
internal class NcgReader
{
IBinaryStream m_input;
ImageMetaData m_info;
public NcgReader (IBinaryStream input, ImageMetaData info)
{
m_input = input;
m_info = info;
}
public ImageData Unpack ()
{
m_input.Position = 4;
var palette = ReadPalette();
int width = m_info.iWidth;
int height = m_info.iHeight;
int output_stride = width;
var pixels = new byte[output_stride * height];
int quart_width = width / 4;
int half_height = height / 2;
var blockmap = new bool[quart_width * half_height];
var bits1 = new byte[8];
var bits2 = new byte[8];
byte ctl;
int dst, pblk;
do
{
for (int i = 0; i < 8; ++i)
bits1[i] = bits2[i] = 0;
for (int shift = 0; shift < 4; ++shift)
{
byte bit = (byte)(1 << shift);
FillBits (bits1, bit);
FillBits (bits2, bit);
}
for (;;)
{
ctl = m_input.ReadUInt8();
if (0xFF == ctl || 0x7F == ctl)
break;
int pos = (ctl & 0x3F) << 8 | m_input.ReadUInt8();
int x = (pos % 80) << 3;
int y = (pos / 80) << 1;
dst = width * y + x;
pblk = x / 4 + quart_width * (y / 2);
switch (ctl >> 6)
{
case 0:
{
int w_count = m_input.ReadUInt8();
int h_count = m_input.ReadUInt8();
int gap = quart_width - 2 * w_count;
while (h_count --> 0)
{
for (int i = 0; i < w_count; ++i)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk++] = true;
blockmap[pblk++] = true;
}
pblk += gap;
dst += 2 * width - 8 * w_count;
}
break;
}
case 1:
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk++] = true;
blockmap[pblk++] = true;
}
break;
}
case 2:
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk ] = true;
blockmap[pblk+1] = true;
dst += 2 * width - 8;
pblk += quart_width;
}
break;
}
case 3:
{
for (int j = 0; j < 8; ++j)
{
pixels[dst+width] = bits2[j];
pixels[dst++] = bits1[j];
}
blockmap[pblk ] = true;
blockmap[pblk+1] = true;
break;
}
}
}
}
while (ctl != 0xFF);
do
{
for (int i = 0; i < 8; ++i)
bits1[i] = 0;
for (int shift = 0; shift < 4; ++shift)
FillBits (bits1, (byte)(1 << shift));
for (;;)
{
ctl = m_input.ReadUInt8();
if (0xFF == ctl || 0xFE == ctl)
break;
int pos = (ctl & 0x7F) << 8 | m_input.ReadUInt8();
dst = 4 * (pos % 160) + width * 2 * (pos / 160);
pblk = (pos % 160) + quart_width * (pos / 160);
if ((ctl & 0x80) == 0)
{
int count = m_input.ReadUInt8();
while (count --> 0)
{
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
blockmap[pblk] = true;
pblk += quart_width;
dst += 2 * width - 4;
}
}
else
{
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
blockmap[pblk] = true;
}
}
}
while (ctl != 0xFF);
dst = 0;
pblk = 0;
for (int y = 0; y < half_height; ++y)
{
for (int x = 0; x < quart_width; ++x)
{
if (blockmap[pblk++])
{
dst += 4;
}
else
{
for (int i = 0; i < 8; ++i)
bits1[i] = 0;
for (int shift = 0; shift < 4; ++shift)
FillBits (bits1, (byte)(1 << shift));
for (int j = 0; j < 4; ++j)
{
pixels[dst+width] = bits1[j+4];
pixels[dst++] = bits1[j];
}
}
}
dst += width;
}
return ImageData.Create (m_info, PixelFormats.Indexed8, palette, pixels, output_stride);
}
void FillBits (byte[] bits, byte bit)
{
sbyte s = m_input.ReadInt8();
for (int i = 0; i < 8; ++i)
{
if (s < 0)
bits[i] |= bit;
s <<= 1;
}
}
static readonly string PaletteKey = "NEKOTARO";
BitmapPalette ReadPalette ()
{
int k = 0;
var colors = new Color[16];
for (int c = 0; c < 16; ++c)
{
int g = m_input.ReadUInt8();
int r = m_input.ReadUInt8();
int b = m_input.ReadUInt8();
b = (~b - PaletteKey[k++ & 7]) & 0xFF;
r = (~r - PaletteKey[k++ & 7]) & 0xFF;
g = (~g - PaletteKey[k++ & 7]) & 0xFF;
colors[c] = Color.FromRgb ((byte)(r * 0x11), (byte)(g * 0x11), (byte)(b * 0x11));
}
return new BitmapPalette (colors);
}
}
}