using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.IO;
using System.Linq;
namespace OpenVIII
{
///
/// Playstation TIM Texture format.
///
///
///
///
///
///
/// upgraded TIM class, because that first one is a trash
public class TIM2 : Texture_Base
{
#region Fields
///
/// Bits per pixel
///
protected sbyte bpp = -1;
///
/// Raw Data buffer
///
protected byte[] buffer;
///
/// Image has a CLUT
///
protected bool CLP;
protected bool ignorealpha = false;
///
/// Texture Data
///
protected Texture texture;
///
/// Start of Image Data
///
protected uint textureDataPointer;
protected bool throwexc = true;
///
/// Start of Tim Data
///
protected uint timOffset;
protected bool trimExcess = false;
#endregion Fields
#region Constructors
///
/// Initialize TIM class
///
/// Raw Data buffer
/// Start of Tim Data
public TIM2(byte[] buffer, uint offset = 0, bool noExc = false)
{
throwexc = !noExc;
_Init(buffer, offset);
}
/// Initialize TIM class BinaryReader
/// pointing to the file data Start of Tim Data
public TIM2(BinaryReader br, uint offset = 0, bool noExec = false)
{
throwexc = !noExec;
_Init(br, offset);
}
protected TIM2()
{
}
#endregion Constructors
#region Enums
///
/// BPP indicator
/// 4 BPP is default
/// If 8 and 16 are set then it's 24
/// CLP should always be set for 4 and 8
///
[Flags]
public enum Bppflag : byte
{
///
/// 4 BPP
/// This is 0 so it will show as unset.
///
_4bpp = 0b0,
///
/// 8 BPP
/// if _8bpp and _16bpp are set then it's 24 bit
///
_8bpp = 0b1,
///
/// 16 BPP
/// if _8bpp and _16bpp are set then it's 24 bit
///
_16bpp = 0b10,
///
/// 24 BPP
/// Both flags must be set for this to be right
///
_24bpp = _8bpp | _16bpp,
///
/// Color Lookup table Present
///
CLP = 0b1000,
}
#endregion Enums
#region Properties
///
/// Gets Bits per pixel
///
public override byte GetBytesPerPixel => (byte)bpp;
///
/// Number of clut color palettes
///
public override int GetClutCount => texture.NumOfCluts;
///
/// Gets size of clut data as in TIM file
///
public override int GetClutSize => texture.clutdataSize;
///
/// Gets number of colours per palette
///
public override int GetColorsCountPerPalette => texture.NumOfColours;
///
/// Height
///
public override int GetHeight => texture.Height;
///
/// Gets origin texture coordinate X for VRAM buffer
///
public override int GetOrigX => texture.ImageOrgX;
///
/// Gets origin texture coordinate Y for VRAM buffer
///
public override int GetOrigY => texture.ImageOrgY;
///
/// Width
///
public override int GetWidth => texture.Width;
public bool IgnoreAlpha { get => ignorealpha; set => ignorealpha = value; }
public bool NOT_TIM { get; protected set; }
#endregion Properties
#region Methods
public bool Assert(bool a)
{
if (!a)
{
NOT_TIM = true;
if (throwexc)
{
throw new InvalidDataException($"Invalid TIM File");
}
//else
// Debug.Assert(a);
}
return !a;
}
public override void ForceSetClutColors(ushort newNumOfColours) => texture.NumOfColours = newNumOfColours;
public override void ForceSetClutCount(ushort newClut) => texture.NumOfCluts = newClut;
public override Color[] GetClutColors(ushort clut)
{
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
return GetClutColors(br, clut);
}
}
///
/// Gets Color[] palette from TIM image data
///
/// clut index
///
public Color[] GetPalette(ushort clut = 0)
{
Color[] colors;
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
colors = GetClutColors(br, clut);
return colors;
}
///
/// Create Texture from Tim image data.
///
/// Active clut data
///
/// If true skip size check useful for files with more than just Tim
///
/// Texture2D
public override Texture2D GetTexture(ushort? clut = null)
{
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
return GetTexture(br, clut == null || !CLP ? null : GetClutColors(br, clut.Value));
}
}
public override Texture2D GetTexture()
{
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
return GetTexture(br, !CLP ? null : GetClutColors(br, 0));
}
}
public override Texture2D GetTexture(Color[] colors = null)
{
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
if (Assert(CLP) || Assert(colors.Length == texture.NumOfColours))
return null;
return GetTexture(br, colors);
}
}
///
/// Initialize TIM class
///
/// BinaryReader pointing to the file data
/// Start of Tim Data
public void Init(BinaryReader br, uint offset)
{
br.BaseStream.Seek(offset, SeekOrigin.Begin);
if (Assert(br.ReadByte() == 0x10) || //tag
Assert(br.ReadByte() == 0)) // version
return;
br.BaseStream.Seek(2, SeekOrigin.Current);
Bppflag b = (Bppflag)br.ReadByte();
timOffset = offset;
if ((b & (Bppflag._24bpp)) >= (Bppflag._24bpp)) bpp = 24;
else if ((b & Bppflag._16bpp) != 0) bpp = 16;
else if ((b & Bppflag._8bpp) != 0) bpp = 8;
else bpp = 4;
CLP = (b & Bppflag.CLP) != 0;
if (Assert(((bpp == 4 || bpp == 8) && CLP) || ((bpp == 16 || bpp == 24) && !CLP)))
return;
ReadParameters(br);
}
public override void Load(byte[] buffer, uint offset = 0) => _Init(buffer, offset);
///
/// Writes the Tim file to the hard drive.
///
/// Path where you want file to be saved.
public override void Save(string path)
{
using (BinaryWriter bw = new BinaryWriter(File.Create(path)))
{
if (trimExcess)
bw.Write(buffer);
else
bw.Write(buffer.Skip((int)timOffset).Take((int)(texture.ImageDataSize + textureDataPointer)).ToArray());
}
}
public override void SaveCLUT(string path)
{
if (CLP)
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
using (Texture2D CLUT = new Texture2D(Memory.graphics.GraphicsDevice, texture.NumOfColours, texture.NumOfCluts))
{
for (ushort i = 0; i < texture.NumOfCluts; i++)
{
CLUT.SetData(0, new Rectangle(0, i, texture.NumOfColours, 1), GetClutColors(br, i), 0, texture.NumOfColours);
}
using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
CLUT.SaveAsPng(fs, texture.NumOfColours, texture.NumOfCluts);
}
}
}
protected void _Init(byte[] buffer, uint offset = 0)
{
this.buffer = buffer;
using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
{
Init(br, offset);
}
}
protected void _Init(BinaryReader br, uint offset)
{
trimExcess = true;
br.BaseStream.Seek(offset, SeekOrigin.Begin);
buffer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
using (BinaryReader br2 = new BinaryReader(new MemoryStream(buffer)))
{
Init(br2, 0);
}
}
///
/// Output 32 bit Color data for image.
///
/// Binaryreader pointing to memorystream of data.
/// Color[] palette
///
/// If true skip size check useful for files with more than just Tim
///
/// Color[]
///
/// This allows null palette but it doesn't seem to handle the palette being null
///
protected TextureBuffer CreateImageBuffer(BinaryReader br, Color[] palette = null)
{
br.BaseStream.Seek(textureDataPointer, SeekOrigin.Begin);
TextureBuffer buffer = new TextureBuffer(texture.Width, texture.Height); //ARGB
if (Assert((buffer.Length / bpp) <= br.BaseStream.Length - br.BaseStream.Position)) //make sure the buffer is large enough
return null;
if (bpp == 8)
{
for (int i = 0; i < buffer.Length; i++)
{
byte colorkey = br.ReadByte();
if (colorkey < texture.NumOfColours)
buffer[i] = palette[colorkey]; //colorkey
//else
// buffer[i] = Color.TransparentBlack; // trying something out of oridinary.
}
}
else if (bpp == 4)
{
for (int i = 0; i < buffer.Length; i++)
{
byte colorkey = br.ReadByte();
buffer[i] = palette[colorkey & 0xf];
buffer[++i] = palette[colorkey >> 4];
}
}
else if (bpp == 16) //copied from overture
{
for (int i = 0; i < buffer.Length && br.BaseStream.Position + 2 < br.BaseStream.Length; i++)
buffer[i] = ABGR1555toRGBA32bit(br.ReadUInt16(), ignorealpha);
}
else if (bpp == 24) //could be wrong. // assuming it is BGR
{
for (int i = 0; i < buffer.Length && br.BaseStream.Position + 2 < br.BaseStream.Length; i++)
{
byte[] pixel = br.ReadBytes(3);
buffer[i] = new Color
{
R = pixel[2],
G = pixel[1],
B = pixel[0],
A = 0xFF
};
}
}
else
throw new Exception($"TIM_v2::CreateImageBuffer::TIM unsupported bits per pixel = {bpp}");
//Then in bs debug where ReadTexture store for all cluts
//data and then create Texture2D from there. (array of e.g. 15 texture2D)
return buffer;
}
///
/// Get clut color palette
///
/// Binaryreader pointing to memorystream of data.
/// Active clut data
/// Color[]
protected Color[] GetClutColors(BinaryReader br, ushort clut)
{
if (clut >= texture.NumOfCluts)
throw new Exception($"TIM_v2::GetClutColors::given clut {clut} is >= texture number of cluts {texture.NumOfCluts}");
if (CLP)
{
Color[] colorPixels = new Color[texture.NumOfColours];
br.BaseStream.Seek(timOffset + 20 + (texture.NumOfColours * 2 * clut), SeekOrigin.Begin);
for (int i = 0; i < texture.NumOfColours; i++)
colorPixels[i] = ABGR1555toRGBA32bit(br.ReadUInt16(), ignorealpha);
return colorPixels;
}
else throw new Exception($"TIM that has {bpp} bpp mode and has no clut data!");
}
protected Texture2D GetTexture(BinaryReader br, Color[] colors) => CreateImageBuffer(br, colors).GetTexture();
///
/// Populate Texture structure
///
/// Binaryreader pointing to memorystream of data.
/// bits per pixel
protected void ReadParameters(BinaryReader br)
{
texture = new Texture();
texture.Read(br, (byte)bpp, CLP, !throwexc);
if (Assert(!texture.NOT_TIM)) return;
textureDataPointer = (uint)br.BaseStream.Position;
if (trimExcess)
buffer = buffer.Skip((int)timOffset).Take((int)(texture.ImageDataSize + textureDataPointer - timOffset)).ToArray();
}
#endregion Methods
#region Structs
protected struct Texture
{
#region Fields
public byte[] ClutData;
public int clutdataSize;
///
/// length, in bytes, of the entire CLUT block (including the header)
///
public uint clutSize;
public ushort Height;
public int ImageDataSize;
public ushort ImageOrgX;
public ushort ImageOrgY;
public uint ImageSize;
public ushort NumOfCluts;
public ushort NumOfColours;
public ushort PaletteX;
public ushort PaletteY;
public bool throwexc;
public ushort Width;
#endregion Fields
#region Properties
public bool NOT_TIM { get; private set; }
#endregion Properties
#region Methods
public bool Assert(bool a)
{
if (!a)
{
NOT_TIM = true;
if (throwexc)
{
throw new InvalidDataException($"Invalid TIM File");
}
//else
// Debug.Assert(a);
}
return !a;
}
///
/// Populate Texture structure
///
/// Binaryreader pointing to memorystream of data.
/// bits per pixel
public void Read(BinaryReader br, byte _bpp, bool clp, bool noExec = false)
{
throwexc = !noExec;
br.BaseStream.Seek(3, SeekOrigin.Current);
if (clp)
{
//long start = br.BaseStream.Position;
clutSize = br.ReadUInt32();
PaletteX = br.ReadUInt16();
PaletteY = br.ReadUInt16();
NumOfColours = br.ReadUInt16(); //width of clut
NumOfCluts = br.ReadUInt16(); //height of clut
clutdataSize = (int)(clutSize - 12);//(NumOfColours * NumOfCluts*2);
if (Assert(clutdataSize == NumOfColours * NumOfCluts * 2 || clutdataSize == NumOfColours * NumOfCluts) ||
Assert(PaletteX % 16 == 0) ||
Assert(PaletteY >= 0 && PaletteY <= 511))
return;
if (_bpp == 4 && NumOfColours > 16)
{
// timviewer was overriding the read number of colors per pixel to 16
// and this makes sense because you cannot read more than 16 colors with only a 4 bit value.
// though this might break stuff.
NumOfColours = 16;
NumOfCluts = checked((ushort)(clutdataSize / (NumOfColours * 2)));
}
Assert(_bpp == 8 && NumOfColours <= 256 || _bpp != 8);
ClutData = br.ReadBytes(clutdataSize);
//br.BaseStream.Seek(start+clutSize, SeekOrigin.Begin);
}
//wmsetus uses 4BPP, but sets 256 colours, but actually is 16, but num of clut is 2* 256/16 WTF?
ImageSize = br.ReadUInt32(); // image size + header in bytes
ImageOrgX = br.ReadUInt16();
ImageOrgY = br.ReadUInt16();
Width = br.ReadUInt16();
Height = br.ReadUInt16();
ImageDataSize = (int)(ImageSize - 12);//(NumOfColours * NumOfCluts*2);
Assert(ImageDataSize == Width * Height * 2);
// Pixel data is stored in uint16 spots. So you use this to convert that to the
// correct color / CLUT colorkey If 32 bit existed it'd be 1:2 24 bit is 1:1.5 16 bit
// is 1:1 8 bit is 2:1 4 bit is 4:1
if (_bpp == 4)
Width = (ushort)(Width * 4);
if (_bpp == 8)
Width = (ushort)(Width * 2);
//if (_bpp == 16)
// Width = Width;
if (_bpp == 24)
{
Width = (ushort)(Width / 1.5);
// The below i'm unsure if any of this effects TIM files. So I commented them out.
// http://www.elisanet.fi/6581/PSX/doc/Playstation_Hardware.pdf
// http://www.elisanet.fi/6581/PSX/doc/psx.pdf
//24 bit color mode has different restrictions. I guess you bybass the psx gpu to draw these
//Assert(Width >= 8 && Height >= 2);// && // not sure if the multiple of 8 for width is right.
}
//Assert(Width <= 256 && Height <= 256 && Width > 0 && Height > 0); // sprite max size 256x256 and min size 1x1
//Assert(Width % 8 == 0 && Height % 8 == 0); // maybe texture must be multiple of 8.
}
#endregion Methods
}
#endregion Structs
}
}