implemented optional 'tags' section of TLG images.

This commit is contained in:
morkt 2016-05-20 04:09:15 +04:00
parent ecfd324f3b
commit d2ff05fd8f

View File

@ -12,8 +12,10 @@ using System;
using System.IO;
using System.ComponentModel.Composition;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using GameRes.Utility;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace GameRes.Formats.KiriKiri
{
@ -75,14 +77,31 @@ namespace GameRes.Formats.KiriKiri
public override ImageData Read (Stream file, ImageMetaData info)
{
var meta = info as TlgMetaData;
if (null == meta)
throw new System.ArgumentException ("TlgFormat.Read should be supplied with TlgMetaData", "info");
file.Seek (meta.DataOffset, SeekOrigin.Begin);
if (6 == meta.Version)
return ReadV6 (file, meta);
else
return ReadV5 (file, meta);
var meta = (TlgMetaData)info;
file.Position = meta.DataOffset;
using (var src = new ArcView.Reader (file))
{
var image = ReadTlg (src, meta);
int tail_size = (int)Math.Min (file.Length - file.Position, 512);
if (tail_size > 8)
{
var tail = src.ReadBytes (tail_size);
try
{
var blended_image = ApplyTags (image, meta, tail);
if (null != blended_image)
return blended_image;
}
catch (Exception X)
{
Trace.WriteLine (X.Message, "[TlgFormat.Read]");
}
}
PixelFormat format = 32 == meta.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
return ImageData.Create (meta, format, null, image, (int)meta.Width * 4);
}
}
public override void Write (Stream file, ImageData image)
@ -90,6 +109,96 @@ namespace GameRes.Formats.KiriKiri
throw new NotImplementedException ("TlgFormat.Write not implemented");
}
byte[] ReadTlg (BinaryReader src, TlgMetaData info)
{
if (6 == info.Version)
return ReadV6 (src, info);
else
return ReadV5 (src, info);
}
ImageData ApplyTags (byte[] image, TlgMetaData meta, byte[] tail)
{
int i = tail.Length - 8;
while (i >= 0)
{
if ('s' == tail[i+3] && 'g' == tail[i+2] && 'a' == tail[i+1] && 't' == tail[i])
break;
--i;
}
if (i < 0)
return null;
var tags = new TagsParser (tail, i+4);
if (!tags.Parse())
return null;
var base_name = tags.GetString (1);
meta.OffsetX = tags.GetInt (2) & 0xFFFF;
meta.OffsetY = tags.GetInt (3) & 0xFFFF;
if (string.IsNullOrEmpty (base_name))
return null;
base_name = VFS.CombinePath (VFS.GetDirectoryName (meta.FileName), base_name);
if (base_name == meta.FileName)
return null;
TlgMetaData base_info;
byte[] base_image;
using (var base_file = VFS.OpenSeekableStream (base_name))
using (var base_src = new BinaryReader (base_file))
{
base_info = ReadMetaData (base_file) as TlgMetaData;
if (null == base_info)
return null;
base_info.FileName = base_name;
base_file.Position = base_info.DataOffset;
base_image = ReadTlg (base_src, base_info);
}
var pixels = BlendImage (base_image, base_info, image, meta);
PixelFormat format = 32 == base_info.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
return ImageData.Create (base_info, format, null, pixels, (int)base_info.Width*4);
}
byte[] BlendImage (byte[] base_image, ImageMetaData base_info, byte[] overlay, ImageMetaData overlay_info)
{
int dst_stride = (int)base_info.Width * 4;
int src_stride = (int)overlay_info.Width * 4;
int dst = overlay_info.OffsetY * dst_stride + overlay_info.OffsetX * 4;
int src = 0;
int gap = dst_stride - src_stride;
for (uint y = 0; y < overlay_info.Height; ++y)
{
for (uint x = 0; x < overlay_info.Width; ++x)
{
byte src_alpha = overlay[src+3];
if (src_alpha != 0)
{
if (0xFF == src_alpha || 0 == base_image[dst+3])
{
base_image[dst] = overlay[src];
base_image[dst+1] = overlay[src+1];
base_image[dst+2] = overlay[src+2];
base_image[dst+3] = src_alpha;
}
else
{
// FIXME this blending algorithm is oversimplified.
base_image[dst+0] = (byte)((overlay[src+0] * src_alpha
+ base_image[dst+0] * (0xFF - src_alpha)) / 0xFF);
base_image[dst+1] = (byte)((overlay[src+1] * src_alpha
+ base_image[dst+1] * (0xFF - src_alpha)) / 0xFF);
base_image[dst+2] = (byte)((overlay[src+2] * src_alpha
+ base_image[dst+2] * (0xFF - src_alpha)) / 0xFF);
base_image[dst+3] = (byte)Math.Max (src_alpha, base_image[dst+3]);
}
}
dst += 4;
src += 4;
}
dst += gap;
}
return base_image;
}
const int TVP_TLG6_H_BLOCK_SIZE = 8;
const int TVP_TLG6_W_BLOCK_SIZE = 8;
@ -97,9 +206,7 @@ namespace GameRes.Formats.KiriKiri
const int TVP_TLG6_LeadingZeroTable_BITS = 12;
const int TVP_TLG6_LeadingZeroTable_SIZE = (1<<TVP_TLG6_LeadingZeroTable_BITS);
ImageData ReadV6 (Stream stream, TlgMetaData info)
{
using (var src = new ArcView.Reader (stream))
byte[] ReadV6 (BinaryReader src, TlgMetaData info)
{
int width = (int)info.Width;
int height = (int)info.Height;
@ -239,27 +346,15 @@ namespace GameRes.Formats.KiriKiri
}
prevline = image_bits;
prevline_index = curline;
// Array.Copy (image_bits, curline, prevline, 0, width);
}
}
unsafe
{
fixed (void* data = image_bits)
{
int stride = width * 4;
PixelFormat format = 32 == info.BPP ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
var bitmap = BitmapSource.Create(width, height, ImageData.DefaultDpiX, ImageData.DefaultDpiY,
format, null, (IntPtr) data, height * stride, stride);
bitmap.Freeze();
return new ImageData(bitmap, info);
}
}
}
var pixels = new byte[height * stride];
Buffer.BlockCopy (image_bits, 0, pixels, 0, pixels.Length);
return pixels;
}
ImageData ReadV5 (Stream stream, TlgMetaData info)
{
using (var src = new ArcView.Reader (stream))
byte[] ReadV5 (BinaryReader src, TlgMetaData info)
{
int width = (int)info.Width;
int height = (int)info.Height;
@ -368,9 +463,7 @@ namespace GameRes.Formats.KiriKiri
prevline = current_org;
}
}
PixelFormat format = 4 == colors ? PixelFormats.Bgra32 : PixelFormats.Bgr32;
return ImageData.Create (info, format, null, image_bits, stride);
}
return image_bits;
}
void TVPTLG5ComposeColors3To4 (byte[] outp, int outp_index, int upper,
@ -486,35 +579,6 @@ namespace GameRes.Formats.KiriKiri
return tvp_packed_bytes_add ((((a&b) + (((a^b) & 0xfefefefe) >> 1)) + ((a^b)&0x01010101)), v);
}
/*
#define TVP_TLG6_DO_CHROMA_DECODE(N, R, G, B) case (N<<1): \
TVP_TLG6_DO_CHROMA_DECODE_PROTO(R, G, B, IA, {inbuf_index+=step;}) break; \
case (N<<1)+1: \
TVP_TLG6_DO_CHROMA_DECODE_PROTO2(R, G, B, IA, {inbuf_index+=step;}) break;
#define TVP_TLG6_DO_CHROMA_DECODE_PROTO(B, G, R, A, POST_INCREMENT) do \
{ \
uint u = prevline[prevline_index]; \
p = tvp_med(p, u, up, \
(0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \
up = u; \
curline[curline_index] = p; \
curline_index++; \
prevline_index++; \
POST_INCREMENT \
} while(--w);
#define TVP_TLG6_DO_CHROMA_DECODE_PROTO2(B, G, R, A, POST_INCREMENT) do \
{ \
uint u = *prevline; \
p = avg(p, u, up, \
(0xff0000 & ((R)<<16)) + (0xff00 & ((G)<<8)) + (0xff & (B)) + ((A) << 24) ); \
up = u; \
*curline = p; \
curline ++; \
prevline ++; \
POST_INCREMENT \
} while(--w);
*/
delegate uint tvp_decoder (uint a, uint b, uint c, uint v);
void TVPTLG6DecodeLineGeneric (uint[] prevline, int prevline_index,
@ -554,21 +618,15 @@ namespace GameRes.Formats.KiriKiri
if (step == -1) inbuf_index += ww-1;
if (0 != (i & 1)) inbuf_index += oddskip * ww;
// byte IA = (byte)(inbuf[inbuf_index]>>24);
// byte IR = (byte)(inbuf[inbuf_index]>>16);
// byte IG = (byte)(inbuf[inbuf_index]>>8 );
// byte IB = (byte)(inbuf[inbuf_index] );
tvp_decoder decoder;
switch (filtertypes[filtertypes_index+i])
{
// TVP_TLG6_DO_CHROMA_DECODE( 0, IB, IG, IR);
case 0:
decoder = (a, b, c, v) => tvp_med (a, b, c, v);
break;
case 1:
decoder = (a, b, c, v) => tvp_avg (a, b, c, v);
break;
// TVP_TLG6_DO_CHROMA_DECODE( 1, IB+IG, IG, IR+IG);
case 2:
decoder = (a, b, c, v) => tvp_med (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000))));
@ -577,7 +635,6 @@ namespace GameRes.Formats.KiriKiri
decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+((v>>8)&0xff))<<16)) + (((v>>8)&0xff)<<8) + (0xff & ((v&0xff)+((v>>8)&0xff))) + ((v&0xff000000))));
break;
// TVP_TLG6_DO_CHROMA_DECODE( 2, IB, IG+IB, IR+IB+IG);
case 4:
decoder = (a, b, c, v) => tvp_med (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000))));
@ -586,7 +643,6 @@ namespace GameRes.Formats.KiriKiri
decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+(v&0xff))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000))));
break;
// TVP_TLG6_DO_CHROMA_DECODE( 3, IB+IR+IG, IG+IR, IR);
case 6:
decoder = (a, b, c, v) => tvp_med (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>16)&0xff)+((v>>8)&0xff))) + ((v&0xff000000))));
@ -683,7 +739,6 @@ namespace GameRes.Formats.KiriKiri
decoder = (a, b, c, v) => tvp_avg (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+(v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v>>16)&0xff))<<8)) + (0xff & ((v&0xff)+((v>>8)&0xff)+((v>>16)&0xff))) + ((v&0xff000000))));
break;
// TVP_TLG6_DO_CHROMA_DECODE(15, IB, IG+(IB<<1), IR+(IB<<1));
case 30:
decoder = (a, b, c, v) => tvp_med (a, b, c, (uint)
((0xff0000 & ((((v>>16)&0xff)+((v&0xff)<<1))<<16)) + (0xff00 & ((((v>>8)&0xff)+((v&0xff)<<1))<<8)) + (0xff & ((v&0xff))) + ((v&0xff000000))));
@ -726,7 +781,6 @@ namespace GameRes.Formats.KiriKiri
static TVP_Tables ()
{
// TVPInitDitherTable();
TVPTLG6InitLeadingZeroTable();
TVPTLG6InitGolombTable();
}
@ -1000,4 +1054,82 @@ namespace GameRes.Formats.KiriKiri
}
}
}
internal class TagsParser
{
byte[] m_tags;
Dictionary<int, Tuple<int, int>> m_map = new Dictionary<int, Tuple<int, int>>();
int m_offset;
public TagsParser (byte[] tags, int offset)
{
m_tags = tags;
m_offset = offset;
}
public bool Parse ()
{
int length = LittleEndian.ToInt32 (m_tags, m_offset);
m_offset += 4;
if (length <= 0 || length > m_tags.Length - m_offset)
return false;
while (m_offset < m_tags.Length)
{
int key_len = ParseInt();
if (key_len < 0)
return false;
int key;
switch (key_len)
{
case 1:
key = m_tags[m_offset];
break;
case 2:
key = LittleEndian.ToUInt16 (m_tags, m_offset);
break;
case 4:
key = LittleEndian.ToInt32 (m_tags, m_offset);
break;
default:
return false;
}
m_offset += key_len + 1;
int value_len = ParseInt();
if (value_len < 0)
return false;
m_map[key] = Tuple.Create (m_offset, value_len);
m_offset += value_len + 1;
}
return m_map.Count > 0;
}
int ParseInt ()
{
int colon = Array.IndexOf (m_tags, (byte)':', m_offset);
if (-1 == colon)
return -1;
var len_str = Encoding.ASCII.GetString (m_tags, m_offset, colon-m_offset);
m_offset = colon + 1;
return Int32.Parse (len_str);
}
public int GetInt (int key)
{
var val = m_map[key];
switch (val.Item2)
{
case 0: return 0;
case 1: return m_tags[val.Item1];
case 2: return LittleEndian.ToUInt16 (m_tags, val.Item1);
case 4: return LittleEndian.ToInt32 (m_tags, val.Item1);
default: throw new InvalidFormatException();
}
}
public string GetString (int key)
{
var val = m_map[key];
return Encodings.cp932.GetString (m_tags, val.Item1, val.Item2);
}
}
}