xoreos  0.0.5
ncgr.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 
26 /* Based heavily on the NCGR reader found in the NDS file viewer
27  * and editor Tinke by pleoNeX (<https://github.com/pleonex/tinke>),
28  * which is licensed under the terms of the GPLv3.
29  *
30  * Tinke in turn is based on the NCGR documentation by lowlines
31  * (<http://llref.emutalk.net/docs/?file=xml/ncgr.xml>).
32  *
33  * The original copyright note in Tinke reads as follows:
34  *
35  * Copyright (C) 2011 pleoNeX
36  *
37  * This program is free software: you can redistribute it and/or modify
38  * it under the terms of the GNU General Public License as published by
39  * the Free Software Foundation, either version 3 of the License, or
40  * (at your option) any later version.
41  *
42  * This program is distributed in the hope that it will be useful,
43  * but WITHOUT ANY WARRANTY; without even the implied warranty of
44  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
45  * GNU General Public License for more details.
46  *
47  * You should have received a copy of the GNU General Public License
48  * along with this program. If not, see <http://www.gnu.org/licenses/>.
49  */
50 
51 #include "src/common/util.h"
52 #include "src/common/strutil.h"
53 #include "src/common/readstream.h"
54 #include "src/common/error.h"
55 
58 
59 static const uint32 kNCGRID = MKTAG('N', 'C', 'G', 'R');
60 static const uint32 kCHARID = MKTAG('C', 'H', 'A', 'R');
61 
62 namespace Graphics {
63 
64 NCGR::NCGRFile::NCGRFile() : ncgr(0), image(0), width(0), height(0) {
65 }
66 
68  delete ncgr;
69  delete image;
70 }
71 
72 
73 NCGR::NCGR(const std::vector<Common::SeekableReadStream *> &ncgrs, uint32 width, uint32 height,
75 
76  try {
77  load(ncgrs, width, height, nclr);
78  } catch (Common::Exception &e) {
79  e.add("Failed reading NCGR files");
80  throw;
81  }
82 }
83 
85  std::vector<Common::SeekableReadStream *> ncgrs;
86  ncgrs.push_back(&ncgr);
87 
88  try {
89  load(ncgrs, 1, 1, nclr);
90  } catch (Common::Exception &e) {
91  e.add("Failed reading NCGR file");
92  throw;
93  }
94 }
95 
96 void NCGR::load(const std::vector<Common::SeekableReadStream *> &ncgrs, uint32 width, uint32 height,
98 
99  if ((width * height) != ncgrs.size())
100  throw Common::Exception("%u NCGRs won't fill a grid of %ux%u", (uint)ncgrs.size(), width, height);
101 
102  ReadContext ctx;
103 
104  ctx.width = width;
105  ctx.height = height;
106 
107  ctx.pal.reset(NCLR::load(nclr));
108 
109  ctx.ncgrs.resize(ncgrs.size());
110 
111  for (size_t i = 0; i < ncgrs.size(); i++) {
112  if (!ncgrs[i])
113  continue;
114 
115  ctx.ncgrs[i].ncgr = open(*ncgrs[i]);
116 
117  load(ctx.ncgrs[i]);
118  }
119 
120  draw(ctx);
121 }
122 
124 }
125 
126 void NCGR::load(NCGRFile &ctx) {
127  readHeader(ctx);
128  readChar (ctx);
129 }
130 
132  const uint32 tag = ctx.ncgr->readUint32();
133  if (tag != kNCGRID)
134  throw Common::Exception("Invalid NCGR file (%s)", Common::debugTag(tag).c_str());
135 
136  const uint16 bom = ctx.ncgr->readUint16();
137  if (bom != 0xFEFF)
138  throw Common::Exception("Invalid BOM: %u", bom);
139 
140  const uint8 versionMinor = ctx.ncgr->readByte();
141  const uint8 versionMajor = ctx.ncgr->readByte();
142  if ((versionMajor != 1) || (versionMinor != 1))
143  throw Common::Exception("Unsupported version %u.%u", versionMajor, versionMinor);
144 
145  const uint32 fileSize = ctx.ncgr->readUint32();
146  if (fileSize > ctx.ncgr->size())
147  throw Common::Exception("Size too large (%u > %u)", fileSize, (uint)ctx.ncgr->size());
148 
149  const uint16 headerSize = ctx.ncgr->readUint16();
150  if (headerSize != 16)
151  throw Common::Exception("Invalid header size (%u)", headerSize);
152 
153  const uint16 sectionCount = ctx.ncgr->readUint16();
154  if ((sectionCount != 1) && (sectionCount != 2))
155  throw Common::Exception("Invalid number of sections (%u)", sectionCount);
156 
157  ctx.offsetCHAR = headerSize;
158 }
159 
161  /* The CHAR section contains the actual graphics data. */
162 
163  ctx.ncgr->seek(ctx.offsetCHAR);
164 
165  const uint32 tag = ctx.ncgr->readUint32();
166  if (tag != kCHARID)
167  throw Common::Exception("Invalid CHAR section (%s)", Common::debugTag(tag).c_str());
168 
169  ctx.ncgr->skip(4);
170 
171  // Width and height in tiles (8x8 pixels)
172  ctx.height = ctx.ncgr->readUint16() * 8;
173  ctx.width = ctx.ncgr->readUint16() * 8;
174 
175  /* TODO: Something weird is going on when width and height are 0xFFFF.
176  *
177  * Fiddling with the data of one of those images, they're somehow
178  * double-tiled? E.g. cbt_victory.ncgr is obviously an image with
179  * dimensions 128x32 (no idea where to get that information from,
180  * though), but it needs to be drawn as 64x64, and then the lower
181  * 64x32 half needs to be moved to the left of the upper half.
182  * Bad ASCII drawing for visualization:
183  * ______
184  * |ORY!|
185  * |VICT|
186  * ''''''
187  */
188 
189  if ((ctx.width >= 0x8000) || (ctx.height >= 0x8000))
190  throw Common::Exception("Unsupported image dimensions");
191 
192  // depthValue == 3 means 4 bit graphics. We don't need to support them.
193  const uint32 depthValue = ctx.ncgr->readUint32();
194  if (depthValue != 4)
195  throw Common::Exception("Unsupported image depth %u", depthValue);
196 
197  ctx.depth = 8;
198 
199  ctx.ncgr->skip(4); // Unknown
200 
201  /* tiled == 0xFF apparently means the graphics are non-tiled.
202  * Would certainly make things easier, but none of the files
203  * are in this format, it seems. */
204  const uint8 tiled = ctx.ncgr->readByte();
205 
206  /* part == 0xFF apparently means that the image is portioned somehow?
207  * None of the Sonic files have this flag set, though. */
208  const uint8 part = ctx.ncgr->readByte();
209 
210  if ((tiled != 0) || (part != 0))
211  throw Common::Exception("Unsupported layout 0x%02X 0x%02X", tiled, part);
212 
213  ctx.ncgr->skip(2); // Unknown
214 
215  const uint32 dataSize = ctx.ncgr->readUint32();
216  const uint32 dataOffset = ctx.ncgr->readUint32() + 24;
217 
218  if ((dataOffset >= ctx.ncgr->size()) || ((ctx.ncgr->size() - dataOffset) < dataSize))
219  throw Common::Exception("Invalid data offset (%u, %u, %u)",
220  dataOffset, dataSize, (uint)ctx.ncgr->size());
221 
222  ctx.image = new Common::SeekableSubReadStream(ctx.ncgr, dataOffset, dataOffset + dataSize);
223 }
224 
225 void NCGR::calculateGrid(ReadContext &ctx, uint32 &imageWidth, uint32 &imageHeight) {
226  imageWidth = imageHeight = 0;
227 
228  // Go through the whole image and get the max height of a row of NCGR
229  for (uint32 y = 0; y < ctx.height; y++) {
230  uint32 rowHeight = 0;
231  uint32 rowWidth = 0;
232 
233  for (uint32 x = 0; x < ctx.width; x++) {
234  NCGRFile &ncgr = ctx.ncgrs[y * ctx.width + x];
235  ncgr.offsetX = rowWidth;
236  ncgr.offsetY = imageHeight;
237 
238  rowHeight = MAX(rowHeight, ncgr.height);
239  rowWidth += ncgr.width;
240  }
241 
242  imageHeight += rowHeight;
243  imageWidth = MAX(imageWidth, rowWidth);
244  }
245 }
246 
248  uint32 imageWidth, imageHeight;
249  calculateGrid(ctx, imageWidth, imageHeight);
250 
251  if ((imageWidth >= 0x8000) || (imageHeight >= 0x8000))
252  throw Common::Exception("Unsupported full image dimensions");
253 
257 
258  _mipMaps.push_back(new MipMap);
259 
260  _mipMaps.back()->width = imageWidth;
261  _mipMaps.back()->height = imageHeight;
262  _mipMaps.back()->size = imageWidth * imageHeight * 4;
263 
264  _mipMaps.back()->data.reset(new byte[_mipMaps.back()->size]);
265  byte *data = _mipMaps.back()->data.get();
266 
267  const bool is0Transp = (ctx.pal[0] == 0xF8) && (ctx.pal[1] == 0x00) && (ctx.pal[2] == 0xF8);
268 
269  // Fill with palette entry 0. Some NCGR cells might be empty, or smaller
270  for (uint32 i = 0; i < (imageWidth * imageHeight); i++) {
271  data[i * 4 + 0] = ctx.pal[0];
272  data[i * 4 + 1] = ctx.pal[1];
273  data[i * 4 + 2] = ctx.pal[2];
274  data[i * 4 + 3] = is0Transp ? 0x00 : 0xFF;
275  }
276 
277  /* The actual image data is stored in a "tiled" fashion, so we need to unswizzle
278  * this manually. Moreover, we ourselves stitch together several NCGR files into
279  * one image. */
280 
281  const uint32 tileWidth = 8;
282  const uint32 tileHeight = 8;
283 
284  for (std::vector<NCGRFile>::iterator n = ctx.ncgrs.begin(); n != ctx.ncgrs.end(); ++n) {
285  if (!n->image)
286  continue;
287 
288  n->image->seek(0);
289 
290  // Position of this NCGR within the big image
291  const uint32 imagePos = n->offsetX + n->offsetY * imageWidth;
292 
293  // Number of "tiles" in this image's rows/columns
294  const uint32 tilesX = n->width / tileWidth;
295  const uint32 tilesY = n->height / tileHeight;
296 
297  // Go over all tiles
298  for (uint32 yT = 0; yT < tilesY; yT++) {
299  for (uint32 xT = 0; xT < tilesX; xT++) {
300 
301  // Position of the tile within the NCGR
302  const uint32 tilePos = xT * tileWidth + yT * tileHeight * imageWidth;
303 
304  // Go over all pixels in the tile
305  for (uint32 y = 0; y < tileHeight; y++) {
306  for (uint32 x = 0; x < tileWidth; x++) {
307 
308  // Position of the pixel within the tile
309  const uint32 pos = imagePos + tilePos + x + y * imageWidth;
310  const uint8 pixel = n->image->readByte();
311 
312  if (pos > (imageWidth * imageHeight))
313  continue;
314 
315  data[pos * 4 + 0] = ctx.pal[pixel * 3 + 0];
316  data[pos * 4 + 1] = ctx.pal[pixel * 3 + 1];
317  data[pos * 4 + 2] = ctx.pal[pixel * 3 + 2];
318  data[pos * 4 + 3] = ((pixel == 0) && is0Transp) ? 0x00 : 0xFF;
319 
320  }
321  }
322 
323  }
324  }
325 
326  }
327 }
328 
329 } // End of namespace Graphics
Loading Nitro CoLoR palette files.
#define MKTAG(a0, a1, a2, a3)
A wrapper macro used around four character constants, like &#39;DATA&#39;, to ensure portability.
Definition: endianness.h:140
uint32 offsetX
X offset in pixels into the final image.
Definition: ncgr.h:77
void add(const char *s,...) GCC_PRINTF(2
Definition: error.cpp:58
uint32 width
Width of the NCGR grid, in NCGR.
Definition: ncgr.h:85
Common::ScopedArray< const byte > pal
Definition: ncgr.h:88
void reset(PointerType o=0)
Resets the pointer with the new value.
Definition: scopedptr.h:87
void calculateGrid(ReadContext &ctx, uint32 &imageWidth, uint32 &imageHeight)
Definition: ncgr.cpp:225
uint8_t uint8
Definition: types.h:200
static const uint32 kCHARID
Definition: ncgr.cpp:60
static const byte * load(Common::SeekableReadStream &nclr)
Definition: nclr.cpp:65
PixelDataType _dataType
Definition: decoder.h:124
Utility templates and functions for working with strings and streams.
Exception that provides a stack of explanations.
Definition: error.h:36
size_t size() const
Obtains the total size of the stream, measured in bytes.
Definition: readstream.cpp:144
void load(const std::vector< Common::SeekableReadStream *> &ncgrs, uint32 width, uint32 height, Common::SeekableReadStream &nclr)
Definition: ncgr.cpp:96
static const uint32 kNCGRID
Definition: ncgr.cpp:59
Basic exceptions to throw.
Common::SeekableSubReadStreamEndian * ncgr
Definition: ncgr.h:67
size_t seek(ptrdiff_t offset, Origin whence=kOriginBegin)
Sets the stream position indicator for the stream.
Definition: readstream.cpp:148
uint16_t uint16
Definition: types.h:202
uint32 width
Width in pixels of this NCGR.
Definition: ncgr.h:73
PixelFormat _format
Definition: decoder.h:122
Utility templates and functions.
Common::SeekableReadStream * image
Definition: ncgr.h:68
uint32 offsetY
Y offset in pixels into the final image.
Definition: ncgr.h:78
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
void readHeader(NCGRFile &ctx)
Definition: ncgr.cpp:131
StackException Exception
Definition: error.h:59
Basic reading stream interfaces.
NCGR(Common::SeekableReadStream &ncgr, Common::SeekableReadStream &nclr)
Definition: ncgr.cpp:84
uint8 depth
Color depth in bits.
Definition: ncgr.h:75
uint32 offsetCHAR
Offset to the CHAR section within the NCGR file.
Definition: ncgr.h:71
void readChar(NCGRFile &ctx)
Definition: ncgr.cpp:160
uint32 height
Height of the NCGR grid, in NCGR.
Definition: ncgr.h:86
uint32_t uint32
Definition: types.h:204
UString debugTag(uint32 tag, bool trim)
Create an elaborate string from an integer tag, for debugging purposes.
Definition: strutil.cpp:117
Nitro Character Graphic Resource, a Nintendo DS image format.
SeekableSubReadStream provides access to a SeekableReadStream restricted to the range [begin...
Definition: readstream.h:359
T MAX(T a, T b)
Definition: util.h:71
uint32 height
Height in pixels of this NCGR.
Definition: ncgr.h:74
static Common::SeekableSubReadStreamEndian * open(Common::SeekableReadStream &stream)
Treat this stream as a Nitro file and return an endian&#39;d stream according to its BOM.
Definition: nitrofile.cpp:52
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
std::vector< NCGRFile > ncgrs
Definition: ncgr.h:90
uint8 byte
Definition: types.h:209
unsigned int uint
Definition: types.h:211
void draw(ReadContext &ctx)
Definition: ncgr.cpp:247