mirror of
https://github.com/crskycode/GARbro.git
synced 2026-06-06 05:38:48 +08:00
Add PS1 TIM image support
This commit is contained in:
173
ArcFormats/Ps1/ImageTIM.cs
Normal file
173
ArcFormats/Ps1/ImageTIM.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
//! \file ImageTIM.cs
|
||||
//! \date 2026 Jan 05
|
||||
//! \brief Standard PlayStation (PS1) image format.
|
||||
//
|
||||
// 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;
|
||||
|
||||
namespace GameRes.Formats.Sony
|
||||
{
|
||||
internal class TimMetaData : ImageMetaData
|
||||
{
|
||||
public uint BppMode;
|
||||
public bool HasClut;
|
||||
public uint ClutOffset;
|
||||
public uint ImageOffset;
|
||||
}
|
||||
|
||||
[Export(typeof(ImageFormat))]
|
||||
public class TimFormat : ImageFormat
|
||||
{
|
||||
public override string Tag { get { return "TIM"; } }
|
||||
public override string Description { get { return "PlayStation image format"; } }
|
||||
public override uint Signature { get { return 0x00000010; } } // Magic: 10 00 00 00
|
||||
|
||||
public override ImageMetaData ReadMetaData(IBinaryStream stream)
|
||||
{
|
||||
var header = stream.ReadHeader(8);
|
||||
if (header[0] != 0x10 || header[1] != 0)
|
||||
return null;
|
||||
|
||||
ushort flag = header.ToUInt16(4);
|
||||
uint mode = (uint)(flag & 3);
|
||||
bool hasClut = (flag & 8) != 0;
|
||||
|
||||
uint currentOffset = 8;
|
||||
uint clutOffset = 0;
|
||||
|
||||
if (hasClut)
|
||||
{
|
||||
clutOffset = currentOffset;
|
||||
stream.Position = currentOffset;
|
||||
uint clutSize = stream.ReadUInt32();
|
||||
currentOffset += clutSize;
|
||||
}
|
||||
|
||||
uint imageOffset = currentOffset;
|
||||
if (imageOffset + 12 > stream.Length) return null;
|
||||
|
||||
// Image Header: BlockSize(4), VramX(2), VramY(2), WordWidth(2), Height(2)
|
||||
stream.Position = imageOffset + 8;
|
||||
ushort wordWidth = stream.ReadUInt16();
|
||||
ushort height = stream.ReadUInt16();
|
||||
|
||||
int bpp;
|
||||
int width;
|
||||
switch (mode)
|
||||
{
|
||||
case 0: bpp = 4; width = wordWidth * 4; break;
|
||||
case 1: bpp = 8; width = wordWidth * 2; break;
|
||||
case 2: bpp = 16; width = wordWidth; break;
|
||||
case 3: bpp = 24; width = (wordWidth * 2) / 3; break;
|
||||
default: return null;
|
||||
}
|
||||
|
||||
return new TimMetaData
|
||||
{
|
||||
Width = (uint)width,
|
||||
Height = height,
|
||||
BPP = bpp,
|
||||
BppMode = mode,
|
||||
HasClut = hasClut,
|
||||
ClutOffset = clutOffset,
|
||||
ImageOffset = imageOffset
|
||||
};
|
||||
}
|
||||
|
||||
public override ImageData Read(IBinaryStream stream, ImageMetaData info)
|
||||
{
|
||||
var meta = (TimMetaData)info;
|
||||
BitmapPalette palette = null;
|
||||
|
||||
if (meta.HasClut)
|
||||
{
|
||||
// CLUT Header: BlockSize(4), X(2), Y(2), Width(2), Height(2)
|
||||
// Width/Height are at offsets 8 and 10 within the CLUT block.
|
||||
stream.Position = meta.ClutOffset + 8;
|
||||
ushort colorsCount = stream.ReadUInt16();
|
||||
ushort rows = stream.ReadUInt16();
|
||||
int totalColors = colorsCount * rows;
|
||||
|
||||
// Color data starts at offset 12
|
||||
var colorData = stream.ReadBytes(totalColors * 2);
|
||||
var colors = new Color[totalColors];
|
||||
for (int i = 0; i < totalColors; i++)
|
||||
{
|
||||
ushort c = BitConverter.ToUInt16(colorData, i * 2);
|
||||
colors[i] = ConvertBgr1555(c);
|
||||
}
|
||||
palette = new BitmapPalette(colors);
|
||||
}
|
||||
|
||||
// Image data starts at ImageOffset + 12 (skipping the 12-byte header)
|
||||
stream.Position = meta.ImageOffset + 12;
|
||||
int pixelDataSize = (int)(meta.Width * meta.Height * meta.BPP / 8);
|
||||
if (meta.BPP == 4) pixelDataSize = (int)(meta.Width * meta.Height / 2);
|
||||
|
||||
var pixels = stream.ReadBytes(pixelDataSize);
|
||||
PixelFormat format;
|
||||
|
||||
switch (meta.BppMode)
|
||||
{
|
||||
case 0: format = PixelFormats.Indexed4; break;
|
||||
case 1: format = PixelFormats.Indexed8; break;
|
||||
case 2: format = PixelFormats.Bgr555; break;
|
||||
case 3: format = PixelFormats.Bgr24; break;
|
||||
default: throw new NotSupportedException("Unsupported TIM mode");
|
||||
}
|
||||
|
||||
// PS1 4bpp nibbles are stored in reverse order compared to Windows standard
|
||||
if (meta.BppMode == 0)
|
||||
{
|
||||
for (int i = 0; i < pixels.Length; i++)
|
||||
{
|
||||
byte b = pixels[i];
|
||||
pixels[i] = (byte)((b >> 4) | (b << 4));
|
||||
}
|
||||
}
|
||||
|
||||
return ImageData.Create(info, format, palette, pixels);
|
||||
}
|
||||
|
||||
public override void Write(Stream file, ImageData image)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private Color ConvertBgr1555(ushort c)
|
||||
{
|
||||
// PS1 BGR1555: Bit 15=STP, 14-10=B, 9-5=G, 4-0=R
|
||||
byte r = (byte)((c & 0x1F) << 3);
|
||||
byte g = (byte)(((c >> 5) & 0x1F) << 3);
|
||||
byte b = (byte)(((c >> 10) & 0x1F) << 3);
|
||||
|
||||
// Transparency logic: 0,0,0 is transparent unless STP bit is set.
|
||||
byte a = (c == 0) ? (byte)0 : (byte)255;
|
||||
if ((c & 0x8000) != 0) a = 255;
|
||||
|
||||
return Color.FromArgb(a, r, g, b);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user