TIM2.cs 19 KB

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