FtexImagePlugin.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """
  2. A Pillow loader for .ftc and .ftu files (FTEX)
  3. Jerome Leclanche <jerome@leclan.ch>
  4. The contents of this file are hereby released in the public domain (CC0)
  5. Full text of the CC0 license:
  6. https://creativecommons.org/publicdomain/zero/1.0/
  7. Independence War 2: Edge Of Chaos - Texture File Format - 16 October 2001
  8. The textures used for 3D objects in Independence War 2: Edge Of Chaos are in a
  9. packed custom format called FTEX. This file format uses file extensions FTC
  10. and FTU.
  11. * FTC files are compressed textures (using standard texture compression).
  12. * FTU files are not compressed.
  13. Texture File Format
  14. The FTC and FTU texture files both use the same format. This
  15. has the following structure:
  16. {header}
  17. {format_directory}
  18. {data}
  19. Where:
  20. {header} = {
  21. u32:magic,
  22. u32:version,
  23. u32:width,
  24. u32:height,
  25. u32:mipmap_count,
  26. u32:format_count
  27. }
  28. * The "magic" number is "FTEX".
  29. * "width" and "height" are the dimensions of the texture.
  30. * "mipmap_count" is the number of mipmaps in the texture.
  31. * "format_count" is the number of texture formats (different versions of the
  32. same texture) in this file.
  33. {format_directory} = format_count * { u32:format, u32:where }
  34. The format value is 0 for DXT1 compressed textures and 1 for 24-bit RGB
  35. uncompressed textures.
  36. The texture data for a format starts at the position "where" in the file.
  37. Each set of texture data in the file has the following structure:
  38. {data} = format_count * { u32:mipmap_size, mipmap_size * { u8 } }
  39. * "mipmap_size" is the number of bytes in that mip level. For compressed
  40. textures this is the size of the texture data compressed with DXT1. For 24 bit
  41. uncompressed textures, this is 3 * width * height. Following this are the image
  42. bytes for that mipmap level.
  43. Note: All data is stored in little-Endian (Intel) byte order.
  44. """
  45. import struct
  46. from io import BytesIO
  47. from . import Image, ImageFile
  48. MAGIC = b"FTEX"
  49. FORMAT_DXT1 = 0
  50. FORMAT_UNCOMPRESSED = 1
  51. class FtexImageFile(ImageFile.ImageFile):
  52. format = "FTEX"
  53. format_description = "Texture File Format (IW2:EOC)"
  54. def _open(self):
  55. struct.unpack("<I", self.fp.read(4)) # magic
  56. struct.unpack("<i", self.fp.read(4)) # version
  57. self._size = struct.unpack("<2i", self.fp.read(8))
  58. mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
  59. self.mode = "RGB"
  60. # Only support single-format files.
  61. # I don't know of any multi-format file.
  62. assert format_count == 1
  63. format, where = struct.unpack("<2i", self.fp.read(8))
  64. self.fp.seek(where)
  65. (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
  66. data = self.fp.read(mipmap_size)
  67. if format == FORMAT_DXT1:
  68. self.mode = "RGBA"
  69. self.tile = [("bcn", (0, 0) + self.size, 0, (1))]
  70. elif format == FORMAT_UNCOMPRESSED:
  71. self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
  72. else:
  73. raise ValueError(f"Invalid texture compression format: {repr(format)}")
  74. self.fp.close()
  75. self.fp = BytesIO(data)
  76. def load_seek(self, pos):
  77. pass
  78. def _validate(prefix):
  79. return prefix[:4] == MAGIC
  80. Image.register_open(FtexImageFile.format, FtexImageFile, _validate)
  81. Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])