FtexImagePlugin.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  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 enum import IntEnum
  47. from io import BytesIO
  48. from . import Image, ImageFile
  49. MAGIC = b"FTEX"
  50. class Format(IntEnum):
  51. DXT1 = 0
  52. UNCOMPRESSED = 1
  53. class FtexImageFile(ImageFile.ImageFile):
  54. format = "FTEX"
  55. format_description = "Texture File Format (IW2:EOC)"
  56. def _open(self):
  57. if not _accept(self.fp.read(4)):
  58. msg = "not an FTEX file"
  59. raise SyntaxError(msg)
  60. struct.unpack("<i", self.fp.read(4)) # version
  61. self._size = struct.unpack("<2i", self.fp.read(8))
  62. mipmap_count, format_count = struct.unpack("<2i", self.fp.read(8))
  63. self._mode = "RGB"
  64. # Only support single-format files.
  65. # I don't know of any multi-format file.
  66. assert format_count == 1
  67. format, where = struct.unpack("<2i", self.fp.read(8))
  68. self.fp.seek(where)
  69. (mipmap_size,) = struct.unpack("<i", self.fp.read(4))
  70. data = self.fp.read(mipmap_size)
  71. if format == Format.DXT1:
  72. self._mode = "RGBA"
  73. self.tile = [("bcn", (0, 0) + self.size, 0, 1)]
  74. elif format == Format.UNCOMPRESSED:
  75. self.tile = [("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
  76. else:
  77. msg = f"Invalid texture compression format: {repr(format)}"
  78. raise ValueError(msg)
  79. self.fp.close()
  80. self.fp = BytesIO(data)
  81. def load_seek(self, pos):
  82. pass
  83. def _accept(prefix):
  84. return prefix[:4] == MAGIC
  85. Image.register_open(FtexImageFile.format, FtexImageFile, _accept)
  86. Image.register_extensions(FtexImageFile.format, [".ftc", ".ftu"])