xoreos  0.0.5
tpc.cpp
Go to the documentation of this file.
1 /* xoreos - A reimplementation of BioWare's Aurora engine
2  *
3  * xoreos is the legal property of its developers, whose names
4  * can be found in the AUTHORS file distributed with this source
5  * distribution.
6  *
7  * xoreos is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 3
10  * of the License, or (at your option) any later version.
11  *
12  * xoreos is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with xoreos. If not, see <http://www.gnu.org/licenses/>.
19  */
20 
25 #include <cstring>
26 
27 #include "src/common/scopedptr.h"
28 #include "src/common/util.h"
29 #include "src/common/maths.h"
30 #include "src/common/error.h"
32 
35 
36 static const byte kEncodingGray = 0x01;
37 static const byte kEncodingRGB = 0x02;
38 static const byte kEncodingRGBA = 0x04;
39 static const byte kEncodingSwizzledBGRA = 0x0C;
40 
41 namespace Graphics {
42 
44  load(tpc);
45 }
46 
48 }
49 
51  try {
52 
53  byte encoding;
54 
55  readHeader(tpc, encoding);
56  readData (tpc, encoding);
57  readTXI (tpc);
58 
59  fixupCubeMap();
60 
61  } catch (Common::Exception &e) {
62  e.add("Failed reading TPC file");
63  throw;
64  }
65 }
66 
68  // Number of bytes for the pixel data in one full image
69  uint32 dataSize = tpc.readUint32LE();
70 
71  tpc.skip(4); // Some float
72 
73  // Image dimensions
74  uint32 width = tpc.readUint16LE();
75  uint32 height = tpc.readUint16LE();
76 
77  if ((width >= 0x8000) || (height >= 0x8000))
78  throw Common::Exception("Unsupported image dimensions (%ux%u)", width, height);
79 
80  // How's the pixel data encoded?
81  encoding = tpc.readByte();
82 
83  // Number of mip maps in the image
84  byte mipMapCount = tpc.readByte();
85 
86  tpc.skip(114); // Reserved
87 
88  uint32 minDataSize = 0;
89  if (dataSize == 0) {
90  // Uncompressed
91 
92  _compressed = false;
93 
94  if (encoding == kEncodingGray) {
95  // 8bpp grayscale
96 
97  _hasAlpha = false;
101 
102  minDataSize = 1;
103  dataSize = width * height;
104  } else if (encoding == kEncodingRGB) {
105  // RGB, no alpha channel
106 
107  _hasAlpha = false;
111 
112  minDataSize = 3;
113  dataSize = width * height * 3;
114  } else if (encoding == kEncodingRGBA) {
115  // RGBA, alpha channel
116 
117  _hasAlpha = true;
121 
122  minDataSize = 4;
123  dataSize = width * height * 4;
124  } else if (encoding == kEncodingSwizzledBGRA) {
125  // BGRA, alpha channel, texture memory layout is "swizzled"
126 
127  _hasAlpha = true;
131 
132  minDataSize = 4;
133  dataSize = width * height * 4;
134  } else
135  throw Common::Exception("Unknown TPC raw encoding: %d (%d), %dx%d, %d", encoding, dataSize, width, height, mipMapCount);
136 
137  } else if (encoding == kEncodingRGB) {
138  // S3TC DXT1
139 
140  _compressed = true;
141  _hasAlpha = false;
145 
146  minDataSize = 8;
147 
148  checkCubeMap(width, height);
149 
150  } else if (encoding == kEncodingRGBA) {
151  // S3TC DXT5
152 
153  _compressed = true;
154  _hasAlpha = true;
158 
159  minDataSize = 16;
160 
161  checkCubeMap(width, height);
162 
163  } else
164  throw Common::Exception("Unknown TPC encoding: %d (%d)", encoding, dataSize);
165 
166  if (!hasValidDimensions(_formatRaw, width, height))
167  throw Common::Exception("Invalid dimensions (%dx%d) for format %d", width, height, _formatRaw);
168 
169  const size_t fullImageDataSize = getDataSize(_formatRaw, width, height);
170 
171  size_t fullDataSize = tpc.size() - 128;
172  if (fullDataSize < (_layerCount * fullImageDataSize))
173  throw Common::Exception("Image wouldn't fit into data");
174 
175  _mipMaps.reserve(mipMapCount * _layerCount);
176 
177  size_t layerCount;
178  for (layerCount = 0; layerCount < _layerCount; layerCount++) {
179  uint32 layerWidth = width;
180  uint32 layerHeight = height;
181  uint32 layerSize = dataSize;
182 
183  for (size_t i = 0; i < mipMapCount; i++) {
184  Common::ScopedPtr<MipMap> mipMap(new MipMap(this));
185 
186  mipMap->width = MAX<uint32>(layerWidth, 1);
187  mipMap->height = MAX<uint32>(layerHeight, 1);
188 
189  mipMap->size = MAX<uint32>(layerSize, minDataSize);
190 
191  const size_t mipMapDataSize = getDataSize(_formatRaw, mipMap->width, mipMap->height);
192 
193  // Wouldn't fit
194  if ((fullDataSize < mipMap->size) || (mipMap->size < mipMapDataSize))
195  break;
196 
197  fullDataSize -= mipMap->size;
198 
199  _mipMaps.push_back(mipMap.release());
200 
201  layerWidth >>= 1;
202  layerHeight >>= 1;
203  layerSize >>= 2;
204 
205  if ((layerWidth < 1) && (layerHeight < 1))
206  break;
207  }
208  }
209 
210  if ((layerCount != _layerCount) || ((_mipMaps.size() % _layerCount) != 0))
211  throw Common::Exception("Failed to correctly read all texture layers (%u, %u, %u, %u)",
212  (uint) _layerCount, (uint) mipMapCount,
213  (uint) layerCount, (uint) _mipMaps.size());
214 }
215 
216 bool TPC::checkCubeMap(uint32 &width, uint32 &height) {
217  /* Check if this texture is a cube map by looking if height equals to six
218  * times width. This means that there are 6 sides of width * (height / 6)
219  * images in this texture, making it a cube map.
220  *
221  * The individual sides are then stores on after another, together with
222  * their mip maps.
223  *
224  * I.e.
225  * - Side 0, mip map 0
226  * - Side 0, mip map 1
227  * - ...
228  * - Side 1, mip map 0
229  * - Side 1, mip map 1
230  * - ...
231  *
232  * The ordering of the sides should be the usual Direct3D cube map order,
233  * which is the same as the OpenGL cube map order.
234  *
235  * Yes, that's a really hacky way to encode a cube map. But this is how
236  * the original game does it. It works and doesn't clash with other, normal
237  * textures because TPC textures always have power-of-two side lengths,
238  * and therefore (height / width) == 6 isn't true for non-cubemaps.
239  */
240 
241  if ((height == 0) || (width == 0) || ((height / width) != 6))
242  return false;
243 
244  height /= 6;
245 
246  _layerCount = 6;
247  _isCubeMap = true;
248 
249  return true;
250 }
251 
252 void TPC::deSwizzle(byte *dst, const byte *src, uint32 width, uint32 height) {
253  for (uint32 y = 0; y < height; y++) {
254  for (uint32 x = 0; x < width; x++) {
255  const uint32 offset = deSwizzleOffset(x, y, width, height) * 4;
256 
257  *dst++ = src[offset + 0];
258  *dst++ = src[offset + 1];
259  *dst++ = src[offset + 2];
260  *dst++ = src[offset + 3];
261  }
262  }
263 }
264 
266  for (MipMaps::iterator mipMap = _mipMaps.begin(); mipMap != _mipMaps.end(); ++mipMap) {
267 
268  // If the texture width is a power of two, the texture memory layout is "swizzled"
269  const bool widthPOT = ((*mipMap)->width & ((*mipMap)->width - 1)) == 0;
270  const bool swizzled = (encoding == kEncodingSwizzledBGRA) && widthPOT;
271 
272  (*mipMap)->data.reset(new byte[(*mipMap)->size]);
273 
274  if (swizzled) {
275  std::vector<byte> tmp((*mipMap)->size);
276 
277  if (tpc.read(&tmp[0], (*mipMap)->size) != (*mipMap)->size)
279 
280  deSwizzle((*mipMap)->data.get(), &tmp[0], (*mipMap)->width, (*mipMap)->height);
281 
282  } else {
283  if (tpc.read((*mipMap)->data.get(), (*mipMap)->size) != (*mipMap)->size)
285 
286  // Unpacking 8bpp grayscale data into RGB
287  if (encoding == kEncodingGray) {
288  Common::ScopedArray<byte> dataGray((*mipMap)->data.release());
289 
290  (*mipMap)->size = (*mipMap)->width * (*mipMap)->height * 3;
291  (*mipMap)->data.reset(new byte[(*mipMap)->size]);
292 
293  for (int i = 0; i < ((*mipMap)->width * (*mipMap)->height); i++)
294  std::memset((*mipMap)->data.get() + i * 3, dataGray[i], 3);
295  }
296  }
297 
298  }
299 }
300 
302  const size_t txiDataSize = tpc.size() - tpc.pos();
303  if (txiDataSize == 0)
304  return;
305 
307 
308  try {
309  _txi.load(*txiData);
310  } catch (...) {
311  }
312 }
313 
315  /* Do various fixups to the cube maps. This includes rotating and swapping a
316  * few sides around. This is done by the original games as well.
317  */
318 
319  if (!isCubeMap())
320  return;
321 
322  for (size_t j = 0; j < getMipMapCount(); j++) {
323  assert(getLayerCount() > 0);
324 
325  const size_t index0 = 0 * getMipMapCount() + j;
326  assert(index0 < _mipMaps.size());
327 
328  const int32 width = _mipMaps[index0]->width;
329  const int32 height = _mipMaps[index0]->height;
330  const uint32 size = _mipMaps[index0]->size;
331 
332  for (size_t i = 1; i < getLayerCount(); i++) {
333  const size_t index = i * getMipMapCount() + j;
334  assert(index < _mipMaps.size());
335 
336  if ((width != _mipMaps[index]->width ) ||
337  (height != _mipMaps[index]->height) ||
338  (size != _mipMaps[index]->size ))
339  throw Common::Exception("Cube map layer dimensions mismatch");
340  }
341  }
342 
343  // Since we need to rotate the individual cube sides, we need to decompress them all
344  decompress();
345 
346  // Swap the first two sides of the cube maps
347  for (size_t j = 0; j < getMipMapCount(); j++) {
348  const size_t index0 = 0 * getMipMapCount() + j;
349  const size_t index1 = 1 * getMipMapCount() + j;
350  assert((index0 < _mipMaps.size()) && (index1 < _mipMaps.size()));
351 
352  MipMap &mipMap0 = *_mipMaps[index0];
353  MipMap &mipMap1 = *_mipMaps[index1];
354 
355  mipMap0.data.swap(mipMap1.data);
356  }
357 
358  const int bpp = (_formatRaw == kPixelFormatRGB8) ? 3 : ((_formatRaw == kPixelFormatRGBA8) ? 4 : 0);
359  if (bpp == 0)
360  return;
361 
362  // Rotate the cube sides so that they're all oriented correctly
363  for (size_t i = 0; i < getLayerCount(); i++) {
364  for (size_t j = 0; j < getMipMapCount(); j++) {
365  const size_t index = i * getMipMapCount() + j;
366  assert(index < _mipMaps.size());
367 
368  MipMap &mipMap = *_mipMaps[index];
369 
370  static const int rotation[6] = { 1, 3, 0, 2, 2, 0 };
371 
372  rotate90(mipMap.data.get(), mipMap.width, mipMap.height, bpp, rotation[i]);
373  }
374  }
375 
376 }
377 
378 } // End of namespace Graphics
bool checkCubeMap(uint32 &width, uint32 &height)
Definition: tpc.cpp:216
uint16 readUint16LE()
Read an unsigned 16-bit word stored in little endian (LSB first) order from the stream and return it...
Definition: readstream.h:122
void add(const char *s,...) GCC_PRINTF(2
Definition: error.cpp:58
uint32 readUint32LE()
Read an unsigned 32-bit word stored in little endian (LSB first) order from the stream and return it...
Definition: readstream.h:133
void reset(PointerType o=0)
Resets the pointer with the new value.
Definition: scopedptr.h:87
PointerType release()
Returns the plain pointer value and releases ScopedPtr.
Definition: scopedptr.h:103
void fixupCubeMap()
Definition: tpc.cpp:314
MemoryReadStream * readStream(size_t dataSize)
Read the specified amount of data into a new[]&#39;ed buffer which then is wrapped into a MemoryReadStrea...
Definition: readstream.cpp:67
static void deSwizzle(byte *dst, const byte *src, uint32 width, uint32 height)
Definition: tpc.cpp:252
static bool hasValidDimensions(PixelFormatRaw format, int32 width, int32 height)
Are these image dimensions valid for this format?
Definition: util.h:73
Common::ScopedArray< byte > data
The mip map&#39;s data.
Definition: decoder.h:56
bool _isCubeMap
Is this image a cube map? A cube map always needs to have 6 layers!
Definition: decoder.h:129
Mathematical helpers.
Implementing the reading stream interfaces for plain memory blocks.
void swap(ScopedPtrBase< T, Deallocator > &right)
Swap the managed pointers of two ScopedPtrs of the same type.
Definition: scopedptr.h:110
static const byte kEncodingRGBA
Definition: tpc.cpp:38
void load(Common::SeekableReadStream &stream)
Definition: txi.cpp:112
bool isCubeMap() const
Is this image a cube map?
Definition: decoder.cpp:198
PixelDataType _dataType
Definition: decoder.h:124
size_t getLayerCount() const
Return the number of layers contained in the image.
Definition: decoder.cpp:194
Exception that provides a stack of explanations.
Definition: error.h:36
A simple scoped smart pointer template.
void decompress()
Manually decompress the texture image data.
Definition: decoder.cpp:242
void readData(Common::SeekableReadStream &tpc, byte encoding)
Definition: tpc.cpp:265
Basic exceptions to throw.
static uint32 deSwizzleOffset(uint32 x, uint32 y, uint32 width, uint32 height)
De-"swizzle" a texture pixel offset.
Definition: util.h:179
PixelFormat _format
Definition: decoder.h:122
static void rotate90(byte *data, int width, int height, int bpp, int steps)
Rotate a square image in 90° steps, clock-wise.
Definition: util.h:145
TPC(Common::SeekableReadStream &tpc)
Definition: tpc.cpp:43
Utility templates and functions.
size_t _layerCount
Number of layers in this image.
Definition: decoder.h:127
virtual size_t skip(ptrdiff_t offset)
Skip the specified number of bytes, adding that offset to the current position in the stream...
Definition: readstream.h:317
PixelFormatRaw _formatRaw
Definition: decoder.h:123
virtual size_t read(void *dataPtr, size_t dataSize)=0
Read data from the stream.
Image related utility functions.
StackException Exception
Definition: error.h:59
const Exception kReadError("Read error")
Exception when reading from a stream failed.
Definition: error.h:62
A scoped plain pointer, allowing pointer-y access and normal deletion.
Definition: scopedptr.h:120
virtual size_t size() const =0
Obtains the total size of the stream, measured in bytes.
static uint32 getDataSize(PixelFormatRaw format, int32 width, int32 height)
Return the number of bytes necessary to hold an image of these dimensions and in this format...
Definition: util.h:43
virtual size_t pos() const =0
Obtains the current value of the stream position indicator of the stream.
void readTXI(Common::SeekableReadStream &tpc)
Definition: tpc.cpp:301
void load(Common::SeekableReadStream &tpc)
Definition: tpc.cpp:50
uint32_t uint32
Definition: types.h:204
TPC (BioWare&#39;s own texture format) loading.
static const byte kEncodingGray
Definition: tpc.cpp:36
static const byte kEncodingRGB
Definition: tpc.cpp:37
size_t getMipMapCount() const
Return the number of mip maps contained in the image.
Definition: decoder.cpp:188
void readHeader(Common::SeekableReadStream &tpc, byte &encoding)
Definition: tpc.cpp:67
static const byte kEncodingSwizzledBGRA
Definition: tpc.cpp:39
Interface for a seekable & readable data stream.
Definition: readstream.h:265
byte readByte()
Read an unsigned byte from the stream and return it.
Definition: readstream.h:92
uint8 byte
Definition: types.h:209
unsigned int uint
Definition: types.h:211
int32_t int32
Definition: types.h:203