TIM2.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. using Microsoft.Xna.Framework;
  2. using Microsoft.Xna.Framework.Graphics;
  3. using System;
  4. using System.IO;
  5. using System.Linq;
  6. namespace OpenVIII
  7. {
  8. /// <summary>
  9. /// Playstation TIM Texture format.
  10. /// </summary>
  11. /// <see cref="http://www.raphnet.net/electronique/psx_adaptor/Playstation.txt"/>
  12. /// <seealso cref="http://www.psxdev.net/forum/viewtopic.php?t=109"/>
  13. /// <seealso cref="https://mrclick.zophar.net/TilEd/download/timgfx.txt"/>
  14. /// <seealso cref="http://www.elisanet.fi/6581/PSX/doc/Playstation_Hardware.pdf"/>
  15. /// <seealso cref="http://www.elisanet.fi/6581/PSX/doc/psx.pdf"/>
  16. /// <remarks>upgraded TIM class, because that first one is a trash</remarks>
  17. public class TIM2 : Texture_Base
  18. {
  19. #region Fields
  20. /// <summary>
  21. /// Bits per pixel
  22. /// </summary>
  23. protected sbyte bpp = -1;
  24. /// <summary>
  25. /// Raw Data buffer
  26. /// </summary>
  27. protected byte[] buffer;
  28. /// <summary>
  29. /// Image has a CLUT
  30. /// </summary>
  31. protected bool CLP;
  32. protected bool ignorealpha = false;
  33. /// <summary>
  34. /// Texture Data
  35. /// </summary>
  36. protected Texture texture;
  37. /// <summary>
  38. /// Start of Image Data
  39. /// </summary>
  40. protected uint textureDataPointer;
  41. protected bool throwexc = true;
  42. /// <summary>
  43. /// Start of Tim Data
  44. /// </summary>
  45. protected uint timOffset;
  46. protected bool trimExcess = false;
  47. #endregion Fields
  48. #region Constructors
  49. /// <summary>
  50. /// Initialize TIM class
  51. /// </summary>
  52. /// <param name="buffer">Raw Data buffer</param>
  53. /// <param name="offset">Start of Tim Data</param>
  54. public TIM2(byte[] buffer, uint offset = 0, bool noExc = false)
  55. {
  56. throwexc = !noExc;
  57. _Init(buffer, offset);
  58. }
  59. /// <summary> <summary> Initialize TIM class </summary> <param name="br">BinaryReader
  60. /// pointing to the file data</param> <param name="offset">Start of Tim Data</param>
  61. public TIM2(BinaryReader br, uint offset = 0, bool noExec = false)
  62. {
  63. throwexc = !noExec;
  64. _Init(br, offset);
  65. }
  66. protected TIM2()
  67. {
  68. }
  69. #endregion Constructors
  70. #region Enums
  71. /// <summary>
  72. /// BPP indicator
  73. /// <para>4 BPP is default</para>
  74. /// <para>If 8 and 16 are set then it's 24</para>
  75. /// <para>CLP should always be set for 4 and 8</para>
  76. /// </summary>
  77. [Flags]
  78. public enum Bppflag : byte
  79. {
  80. /// <summary>
  81. /// <para>4 BPP</para>
  82. /// <para>This is 0 so it will show as unset.</para>
  83. /// </summary>
  84. _4bpp = 0b0,
  85. /// <summary>
  86. /// <para>8 BPP</para>
  87. /// <para>if _8bpp and _16bpp are set then it's 24 bit</para>
  88. /// </summary>
  89. _8bpp = 0b1,
  90. /// <summary>
  91. /// <para>16 BPP</para>
  92. /// <para>if _8bpp and _16bpp are set then it's 24 bit</para>
  93. /// </summary>
  94. _16bpp = 0b10,
  95. /// <summary>
  96. /// <para>24 BPP</para>
  97. /// <para>Both flags must be set for this to be right</para>
  98. /// </summary>
  99. _24bpp = _8bpp | _16bpp,
  100. /// <summary>
  101. /// Color Lookup table Present
  102. /// </summary>
  103. CLP = 0b1000,
  104. }
  105. #endregion Enums
  106. #region Properties
  107. /// <summary>
  108. /// Gets Bits per pixel
  109. /// </summary>
  110. public override byte GetBytesPerPixel => (byte)bpp;
  111. /// <summary>
  112. /// Number of clut color palettes
  113. /// </summary>
  114. public override int GetClutCount => texture.NumOfCluts;
  115. /// <summary>
  116. /// Gets size of clut data as in TIM file
  117. /// </summary>
  118. public override int GetClutSize => texture.clutdataSize;
  119. /// <summary>
  120. /// Gets number of colours per palette
  121. /// </summary>
  122. public override int GetColorsCountPerPalette => texture.NumOfColours;
  123. /// <summary>
  124. /// Height
  125. /// </summary>
  126. public override int GetHeight => texture.Height;
  127. /// <summary>
  128. /// Gets origin texture coordinate X for VRAM buffer
  129. /// </summary>
  130. public override int GetOrigX => texture.ImageOrgX;
  131. /// <summary>
  132. /// Gets origin texture coordinate Y for VRAM buffer
  133. /// </summary>
  134. public override int GetOrigY => texture.ImageOrgY;
  135. /// <summary>
  136. /// Width
  137. /// </summary>
  138. public override int GetWidth => texture.Width;
  139. public bool IgnoreAlpha { get => ignorealpha; set => ignorealpha = value; }
  140. public bool NOT_TIM { get; protected set; }
  141. #endregion Properties
  142. #region Methods
  143. public bool Assert(bool a)
  144. {
  145. if (!a)
  146. {
  147. NOT_TIM = true;
  148. if (throwexc)
  149. {
  150. throw new InvalidDataException($"Invalid TIM File");
  151. }
  152. //else
  153. // Debug.Assert(a);
  154. }
  155. return !a;
  156. }
  157. public override void ForceSetClutColors(ushort newNumOfColours) => texture.NumOfColours = newNumOfColours;
  158. public override void ForceSetClutCount(ushort newClut) => texture.NumOfCluts = newClut;
  159. public override Color[] GetClutColors(ushort clut)
  160. {
  161. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  162. {
  163. return GetClutColors(br, clut);
  164. }
  165. }
  166. /// <summary>
  167. /// Gets Color[] palette from TIM image data
  168. /// </summary>
  169. /// <param name="clut">clut index</param>
  170. /// <returns></returns>
  171. public Color[] GetPalette(ushort clut = 0)
  172. {
  173. Color[] colors;
  174. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  175. colors = GetClutColors(br, clut);
  176. return colors;
  177. }
  178. /// <summary>
  179. /// Create Texture from Tim image data.
  180. /// </summary>
  181. /// <param name="clut">Active clut data</param>
  182. /// <param name="bIgnoreSize">
  183. /// If true skip size check useful for files with more than just Tim
  184. /// </param>
  185. /// <returns>Texture2D</returns>
  186. public override Texture2D GetTexture(ushort? clut = null)
  187. {
  188. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  189. {
  190. return GetTexture(br, clut == null || !CLP ? null : GetClutColors(br, clut.Value));
  191. }
  192. }
  193. public override Texture2D GetTexture()
  194. {
  195. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  196. {
  197. return GetTexture(br, !CLP ? null : GetClutColors(br, 0));
  198. }
  199. }
  200. public override Texture2D GetTexture(Color[] colors = null)
  201. {
  202. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  203. {
  204. if (Assert(CLP) || Assert(colors.Length == texture.NumOfColours))
  205. return null;
  206. return GetTexture(br, colors);
  207. }
  208. }
  209. /// <summary>
  210. /// Initialize TIM class
  211. /// </summary>
  212. /// <param name="br">BinaryReader pointing to the file data</param>
  213. /// <param name="offset">Start of Tim Data</param>
  214. public void Init(BinaryReader br, uint offset)
  215. {
  216. br.BaseStream.Seek(offset, SeekOrigin.Begin);
  217. if (Assert(br.ReadByte() == 0x10) || //tag
  218. Assert(br.ReadByte() == 0)) // version
  219. return;
  220. br.BaseStream.Seek(2, SeekOrigin.Current);
  221. Bppflag b = (Bppflag)br.ReadByte();
  222. timOffset = offset;
  223. if ((b & (Bppflag._24bpp)) >= (Bppflag._24bpp)) bpp = 24;
  224. else if ((b & Bppflag._16bpp) != 0) bpp = 16;
  225. else if ((b & Bppflag._8bpp) != 0) bpp = 8;
  226. else bpp = 4;
  227. CLP = (b & Bppflag.CLP) != 0;
  228. if (Assert(((bpp == 4 || bpp == 8) && CLP) || ((bpp == 16 || bpp == 24) && !CLP)))
  229. return;
  230. ReadParameters(br);
  231. }
  232. public override void Load(byte[] buffer, uint offset = 0) => _Init(buffer, offset);
  233. /// <summary>
  234. /// Writes the Tim file to the hard drive.
  235. /// </summary>
  236. /// <param name="path">Path where you want file to be saved.</param>
  237. public override void Save(string path)
  238. {
  239. using (BinaryWriter bw = new BinaryWriter(File.Create(path)))
  240. {
  241. if (trimExcess)
  242. bw.Write(buffer);
  243. else
  244. bw.Write(buffer.Skip((int)timOffset).Take((int)(texture.ImageDataSize + textureDataPointer)).ToArray());
  245. }
  246. }
  247. public override void SaveCLUT(string path)
  248. {
  249. if (CLP)
  250. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  251. {
  252. using (Texture2D CLUT = new Texture2D(Memory.graphics.GraphicsDevice, texture.NumOfColours, texture.NumOfCluts))
  253. {
  254. for (ushort i = 0; i < texture.NumOfCluts; i++)
  255. {
  256. CLUT.SetData(0, new Rectangle(0, i, texture.NumOfColours, 1), GetClutColors(br, i), 0, texture.NumOfColours);
  257. }
  258. using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite))
  259. CLUT.SaveAsPng(fs, texture.NumOfColours, texture.NumOfCluts);
  260. }
  261. }
  262. }
  263. protected void _Init(byte[] buffer, uint offset = 0)
  264. {
  265. this.buffer = buffer;
  266. using (BinaryReader br = new BinaryReader(new MemoryStream(buffer)))
  267. {
  268. Init(br, offset);
  269. }
  270. }
  271. protected void _Init(BinaryReader br, uint offset)
  272. {
  273. trimExcess = true;
  274. br.BaseStream.Seek(offset, SeekOrigin.Begin);
  275. buffer = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
  276. using (BinaryReader br2 = new BinaryReader(new MemoryStream(buffer)))
  277. {
  278. Init(br2, 0);
  279. }
  280. }
  281. /// <summary>
  282. /// Output 32 bit Color data for image.
  283. /// </summary>
  284. /// <param name="br">Binaryreader pointing to memorystream of data.</param>
  285. /// <param name="palette">Color[] palette</param>
  286. /// <param name="bIgnoreSize">
  287. /// If true skip size check useful for files with more than just Tim
  288. /// </param>
  289. /// <returns>Color[]</returns>
  290. /// <remarks>
  291. /// This allows null palette but it doesn't seem to handle the palette being null
  292. /// </remarks>
  293. protected TextureBuffer CreateImageBuffer(BinaryReader br, Color[] palette = null)
  294. {
  295. br.BaseStream.Seek(textureDataPointer, SeekOrigin.Begin);
  296. TextureBuffer buffer = new TextureBuffer(texture.Width, texture.Height); //ARGB
  297. if (Assert((buffer.Length / bpp) <= br.BaseStream.Length - br.BaseStream.Position)) //make sure the buffer is large enough
  298. return null;
  299. if (bpp == 8)
  300. {
  301. for (int i = 0; i < buffer.Length; i++)
  302. {
  303. byte colorkey = br.ReadByte();
  304. if (colorkey < texture.NumOfColours)
  305. buffer[i] = palette[colorkey]; //colorkey
  306. //else
  307. // buffer[i] = Color.TransparentBlack; // trying something out of oridinary.
  308. }
  309. }
  310. else if (bpp == 4)
  311. {
  312. for (int i = 0; i < buffer.Length; i++)
  313. {
  314. byte colorkey = br.ReadByte();
  315. buffer[i] = palette[colorkey & 0xf];
  316. buffer[++i] = palette[colorkey >> 4];
  317. }
  318. }
  319. else if (bpp == 16) //copied from overture
  320. {
  321. for (int i = 0; i < buffer.Length && br.BaseStream.Position + 2 < br.BaseStream.Length; i++)
  322. buffer[i] = ABGR1555toRGBA32bit(br.ReadUInt16(), ignorealpha);
  323. }
  324. else if (bpp == 24) //could be wrong. // assuming it is BGR
  325. {
  326. for (int i = 0; i < buffer.Length && br.BaseStream.Position + 2 < br.BaseStream.Length; i++)
  327. {
  328. byte[] pixel = br.ReadBytes(3);
  329. buffer[i] = new Color
  330. {
  331. R = pixel[2],
  332. G = pixel[1],
  333. B = pixel[0],
  334. A = 0xFF
  335. };
  336. }
  337. }
  338. else
  339. throw new Exception($"TIM_v2::CreateImageBuffer::TIM unsupported bits per pixel = {bpp}");
  340. //Then in bs debug where ReadTexture store for all cluts
  341. //data and then create Texture2D from there. (array of e.g. 15 texture2D)
  342. return buffer;
  343. }
  344. /// <summary>
  345. /// Get clut color palette
  346. /// </summary>
  347. /// <param name="br">Binaryreader pointing to memorystream of data.</param>
  348. /// <param name="clut">Active clut data</param>
  349. /// <returns>Color[]</returns>
  350. protected Color[] GetClutColors(BinaryReader br, ushort clut)
  351. {
  352. if (clut >= texture.NumOfCluts)
  353. throw new Exception($"TIM_v2::GetClutColors::given clut {clut} is >= texture number of cluts {texture.NumOfCluts}");
  354. if (CLP)
  355. {
  356. Color[] colorPixels = new Color[texture.NumOfColours];
  357. br.BaseStream.Seek(timOffset + 20 + (texture.NumOfColours * 2 * clut), SeekOrigin.Begin);
  358. for (int i = 0; i < texture.NumOfColours; i++)
  359. colorPixels[i] = ABGR1555toRGBA32bit(br.ReadUInt16(), ignorealpha);
  360. return colorPixels;
  361. }
  362. else throw new Exception($"TIM that has {bpp} bpp mode and has no clut data!");
  363. }
  364. protected Texture2D GetTexture(BinaryReader br, Color[] colors) => CreateImageBuffer(br, colors).GetTexture();
  365. /// <summary>
  366. /// Populate Texture structure
  367. /// </summary>
  368. /// <param name="br">Binaryreader pointing to memorystream of data.</param>
  369. /// <param name="_bpp">bits per pixel</param>
  370. protected void ReadParameters(BinaryReader br)
  371. {
  372. texture = new Texture();
  373. texture.Read(br, (byte)bpp, CLP, !throwexc);
  374. if (Assert(!texture.NOT_TIM)) return;
  375. textureDataPointer = (uint)br.BaseStream.Position;
  376. if (trimExcess)
  377. buffer = buffer.Skip((int)timOffset).Take((int)(texture.ImageDataSize + textureDataPointer - timOffset)).ToArray();
  378. }
  379. #endregion Methods
  380. #region Structs
  381. protected struct Texture
  382. {
  383. #region Fields
  384. public byte[] ClutData;
  385. public int clutdataSize;
  386. /// <summary>
  387. /// length, in bytes, of the entire CLUT block (including the header)
  388. /// </summary>
  389. public uint clutSize;
  390. public ushort Height;
  391. public int ImageDataSize;
  392. public ushort ImageOrgX;
  393. public ushort ImageOrgY;
  394. public uint ImageSize;
  395. public ushort NumOfCluts;
  396. public ushort NumOfColours;
  397. public ushort PaletteX;
  398. public ushort PaletteY;
  399. public bool throwexc;
  400. public ushort Width;
  401. #endregion Fields
  402. #region Properties
  403. public bool NOT_TIM { get; private set; }
  404. #endregion Properties
  405. #region Methods
  406. public bool Assert(bool a)
  407. {
  408. if (!a)
  409. {
  410. NOT_TIM = true;
  411. if (throwexc)
  412. {
  413. throw new InvalidDataException($"Invalid TIM File");
  414. }
  415. //else
  416. // Debug.Assert(a);
  417. }
  418. return !a;
  419. }
  420. /// <summary>
  421. /// Populate Texture structure
  422. /// </summary>
  423. /// <param name="br">Binaryreader pointing to memorystream of data.</param>
  424. /// <param name="_bpp">bits per pixel</param>
  425. public void Read(BinaryReader br, byte _bpp, bool clp, bool noExec = false)
  426. {
  427. throwexc = !noExec;
  428. br.BaseStream.Seek(3, SeekOrigin.Current);
  429. if (clp)
  430. {
  431. //long start = br.BaseStream.Position;
  432. clutSize = br.ReadUInt32();
  433. PaletteX = br.ReadUInt16();
  434. PaletteY = br.ReadUInt16();
  435. NumOfColours = br.ReadUInt16(); //width of clut
  436. NumOfCluts = br.ReadUInt16(); //height of clut
  437. clutdataSize = (int)(clutSize - 12);//(NumOfColours * NumOfCluts*2);
  438. if (Assert(clutdataSize == NumOfColours * NumOfCluts * 2 || clutdataSize == NumOfColours * NumOfCluts) ||
  439. Assert(PaletteX % 16 == 0) ||
  440. Assert(PaletteY >= 0 && PaletteY <= 511))
  441. return;
  442. if (_bpp == 4 && NumOfColours > 16)
  443. {
  444. // timviewer was overriding the read number of colors per pixel to 16
  445. // and this makes sense because you cannot read more than 16 colors with only a 4 bit value.
  446. // though this might break stuff.
  447. NumOfColours = 16;
  448. NumOfCluts = checked((ushort)(clutdataSize / (NumOfColours * 2)));
  449. }
  450. Assert(_bpp == 8 && NumOfColours <= 256 || _bpp != 8);
  451. ClutData = br.ReadBytes(clutdataSize);
  452. //br.BaseStream.Seek(start+clutSize, SeekOrigin.Begin);
  453. }
  454. //wmsetus uses 4BPP, but sets 256 colours, but actually is 16, but num of clut is 2* 256/16 WTF?
  455. ImageSize = br.ReadUInt32(); // image size + header in bytes
  456. ImageOrgX = br.ReadUInt16();
  457. ImageOrgY = br.ReadUInt16();
  458. Width = br.ReadUInt16();
  459. Height = br.ReadUInt16();
  460. ImageDataSize = (int)(ImageSize - 12);//(NumOfColours * NumOfCluts*2);
  461. Assert(ImageDataSize == Width * Height * 2);
  462. // Pixel data is stored in uint16 spots. So you use this to convert that to the
  463. // correct color / CLUT colorkey If 32 bit existed it'd be 1:2 24 bit is 1:1.5 16 bit
  464. // is 1:1 8 bit is 2:1 4 bit is 4:1
  465. if (_bpp == 4)
  466. Width = (ushort)(Width * 4);
  467. if (_bpp == 8)
  468. Width = (ushort)(Width * 2);
  469. //if (_bpp == 16)
  470. // Width = Width;
  471. if (_bpp == 24)
  472. {
  473. Width = (ushort)(Width / 1.5);
  474. // The below i'm unsure if any of this effects TIM files. So I commented them out.
  475. // http://www.elisanet.fi/6581/PSX/doc/Playstation_Hardware.pdf
  476. // http://www.elisanet.fi/6581/PSX/doc/psx.pdf
  477. //24 bit color mode has different restrictions. I guess you bybass the psx gpu to draw these
  478. //Assert(Width >= 8 && Height >= 2);// && // not sure if the multiple of 8 for width is right.
  479. }
  480. //Assert(Width <= 256 && Height <= 256 && Width > 0 && Height > 0); // sprite max size 256x256 and min size 1x1
  481. //Assert(Width % 8 == 0 && Height % 8 == 0); // maybe texture must be multiple of 8.
  482. }
  483. #endregion Methods
  484. }
  485. #endregion Structs
  486. }
  487. }