Worldstone
 All Classes Files Functions Variables Enumerations Enumerator Macros Pages
dcc.cpp
Go to the documentation of this file.
1 
7 #include "dcc.h"
8 #include <BitStream.h>
9 #include <SystemUtils.h>
10 #include <array>
11 #include <assert.h>
12 #include <fmt/format.h>
13 #include "ImageView.h"
14 #include "Palette.h"
15 #include "utils.h"
16 
17 namespace WorldStone
18 {
19 
20 constexpr unsigned DCC::bitsWidthTable[16];
21 // constexpr unsigned DCC::bitsWidthTable[16] = {0, 1, 2, 4, 6, 8, 10, 12,
22 // 14, 16, 20, 24, 26, 28, 30, 32};
23 
24 using readSignedPtrType = int32_t (WorldStone::BitStreamView::*)(void);
25 
26 static constexpr readSignedPtrType readSignedPtrs[16] = {
27  &BitStreamView::readSigned<DCC::bitsWidthTable[0]>,
28  &BitStreamView::readSigned<DCC::bitsWidthTable[1]>,
29  &BitStreamView::readSigned<DCC::bitsWidthTable[2]>,
30  &BitStreamView::readSigned<DCC::bitsWidthTable[3]>,
31  &BitStreamView::readSigned<DCC::bitsWidthTable[4]>,
32  &BitStreamView::readSigned<DCC::bitsWidthTable[5]>,
33  &BitStreamView::readSigned<DCC::bitsWidthTable[6]>,
34  &BitStreamView::readSigned<DCC::bitsWidthTable[7]>,
35  &BitStreamView::readSigned<DCC::bitsWidthTable[8]>,
36  &BitStreamView::readSigned<DCC::bitsWidthTable[9]>,
37  &BitStreamView::readSigned<DCC::bitsWidthTable[10]>,
38  &BitStreamView::readSigned<DCC::bitsWidthTable[11]>,
39  &BitStreamView::readSigned<DCC::bitsWidthTable[12]>,
40  &BitStreamView::readSigned<DCC::bitsWidthTable[13]>,
41  &BitStreamView::readSigned<DCC::bitsWidthTable[14]>,
42  &BitStreamView::readSigned<DCC::bitsWidthTable[15]>};
43 
44 bool DCC::initDecoder(StreamPtr&& streamPtr)
45 {
46  assert(!stream);
47  stream = std::move(streamPtr);
48  if (stream && stream->good()) {
49  return extractHeaderAndOffsets();
50  }
51  return false;
52 }
53 
54 bool DCC::extractHeaderAndOffsets()
55 {
56  // For now assume the stream is the whole file, and starts at offset 0
57  assert(stream->tell() == 0);
58  // DCC header can not encode a bigger size anyway
59  assert(stream->size() < std::numeric_limits<int32_t>::max());
60  stream->readRaw(header.signature);
61  stream->readRaw(header.version);
62  stream->readRaw(header.directions);
63  stream->readRaw(header.framesPerDir); // TODO : ENDIAN
64  stream->readRaw(header.tag); // TODO : ENDIAN
65  stream->readRaw(header.finalDc6Size); // TODO : ENDIAN
66 
67  directionsOffsets.resize(header.directions + 1);
68  directionsOffsets[header.directions] = uint32_t(stream->size());
69  for (uint32_t dir = 0; dir < header.directions; dir++)
70  {
71  stream->readRaw(directionsOffsets[dir]); // TODO : ENDIAN
72  }
73  return stream->good();
74 }
75 
76 size_t DCC::getDirectionSize(uint32_t dirIndex)
77 {
78  return directionsOffsets[dirIndex + 1] - directionsOffsets[dirIndex];
79 }
80 
81 static bool readDirHeader(DCC::DirectionHeader& dirHeader, BitStreamView& bitStream)
82 {
83  dirHeader.outsizeCoded = bitStream.readUnsigned(32);
84  dirHeader.hasRawPixelEncoding = bitStream.readBool();
85  dirHeader.compressEqualCells = bitStream.readBool();
86  dirHeader.variable0Bits = bitStream.readUnsigned8OrLess(4);
87  dirHeader.widthBits = bitStream.readUnsigned8OrLess(4);
88  dirHeader.heightBits = bitStream.readUnsigned8OrLess(4);
89  dirHeader.xOffsetBits = bitStream.readUnsigned8OrLess(4);
90  dirHeader.yOffsetBits = bitStream.readUnsigned8OrLess(4);
91  dirHeader.optionalBytesBits = bitStream.readUnsigned8OrLess(4);
92  dirHeader.codedBytesBits = bitStream.readUnsigned8OrLess(4);
93  return bitStream.good();
94 }
95 
96 static bool readFrameHeaders(uint32_t nbFrames, DCC::Direction& outDir, BitStreamView& bitStream)
97 {
98  constexpr auto bitsWidthTable = DCC::bitsWidthTable;
99  const DCC::DirectionHeader& dirHeader = outDir.header;
100  // Read all frame headers
101  outDir.frameHeaders.resize(nbFrames);
102  for (DCC::FrameHeader& fHdr : outDir.frameHeaders)
103  {
104  // We are using member function pointers here because we would have one indirection
105  // From looking up the size anyway, so we might as well call the template instance directly
106  fHdr.variable0 = bitStream.readUnsigned(bitsWidthTable[dirHeader.variable0Bits]);
107  fHdr.width = bitStream.readUnsigned(bitsWidthTable[dirHeader.widthBits]);
108  fHdr.height = bitStream.readUnsigned(bitsWidthTable[dirHeader.heightBits]);
109  fHdr.xOffset = (bitStream.*readSignedPtrs[dirHeader.xOffsetBits])();
110  fHdr.yOffset = (bitStream.*readSignedPtrs[dirHeader.yOffsetBits])();
111 
112  fHdr.optionalBytes = bitStream.readUnsigned(bitsWidthTable[dirHeader.optionalBytesBits]);
113  fHdr.codedBytes = bitStream.readUnsigned(bitsWidthTable[dirHeader.codedBytesBits]);
114  fHdr.frameBottomUp = bitStream.readBool();
115 
116  assert(fHdr.width < 0x700000);
117  assert(fHdr.height < 0x700000);
118  fHdr.extents.xLower = fHdr.xOffset;
119  fHdr.extents.xUpper = fHdr.xOffset + int32_t(fHdr.width);
120 
121  if (fHdr.frameBottomUp) {
122  assert(false && "Please report the name of the DCC file to the devs!");
123  fHdr.extents.yLower = fHdr.yOffset;
124  // Upper excluded (max==upper-1)
125  fHdr.extents.yUpper = fHdr.yOffset + int32_t(fHdr.height);
126  }
127  else // top-down
128  {
129  fHdr.extents.yLower = fHdr.yOffset - int32_t(fHdr.height) + 1;
130  // Upper excluded (max==upper-1)
131  fHdr.extents.yUpper = fHdr.yOffset + 1;
132  }
133  }
134 
135  // Handle optional data
136  for (DCC::FrameHeader& frameHeader : outDir.frameHeaders)
137  {
138  if (frameHeader.optionalBytes) {
139  assert(false && "Please report the name of the DCC file to the devs!");
140  bitStream.alignToByte();
141  bitStream.skip(frameHeader.optionalBytes * CHAR_BIT);
142  }
143  }
144  return bitStream.good();
145 }
146 namespace
147 { // Do not expose internals
148 // For the pixel buffer the maximum size of a cell is 4
149 constexpr size_t pbCellMaxPixelSize = 4u;
150 
151 struct Cell
152 {
153  size_t width : 4; // 3 bits would be engouh (max value is 5)
154  size_t height : 4; // 3 bits would be engouh (max value is 5)
155 };
156 
157 // Each pixel buffer entry contains 4 pixels codes
158 struct PixelBufferEntry
159 {
160  static constexpr size_t nbValues = 4;
161  uint8_t values[nbValues];
162 };
163 
164 struct FrameData
165 {
166  using CellSize = uint8_t;
167 
168  size_t firstPixelBufferEntry;
169  uint16_t nbCellsX;
170  uint16_t nbCellsY;
171  uint16_t offsetX;
172  uint16_t offsetY;
173 
174  Vector<bool> cellSameAsPrevious;
175  Vector<CellSize> cellWidths;
176  Vector<CellSize> cellHeights;
177 
178  ImageView<uint8_t> imageView;
179  FrameData(const DCC::Direction& dir, const DCC::FrameHeader& frameHeader,
180  IImageProvider<uint8_t>& imgProvider)
181  : firstPixelBufferEntry(0)
182  {
183  offsetX = uint16_t(frameHeader.extents.xLower - dir.extents.xLower);
184  offsetY = uint16_t(frameHeader.extents.yLower - dir.extents.yLower);
185 
186  // width (in # of pixels) in 1st column
187  const uint16_t widthFirstColumn = 4 - (offsetX % 4);
188  const uint16_t frameWidth = uint16_t(frameHeader.extents.width());
189  if ((frameWidth - widthFirstColumn) <= 1)
190  nbCellsX = 1; // if 2nd column is 0 or 1 pixel wide, only use 1 cell
191  else
192  {
193  // so, we have minimum 2 pixels behind 1st column
194  uint16_t tmp = frameWidth - widthFirstColumn - 1; // tmp is minimum 1, can't be 0
195  nbCellsX = 2 + (tmp / 4);
196  if ((tmp % 4) == 0) nbCellsX--;
197  }
198 
199  const uint16_t heightFirstRow = 4 - (offsetY % 4);
200  const uint16_t frameHeight = uint16_t(frameHeader.extents.height());
201  if ((frameHeight - heightFirstRow) <= 1)
202  nbCellsY = 1; // if 2nd row is 0 or 1 pixel high, only use 1 cell
203  else
204  {
205  uint16_t tmp = frameHeight - heightFirstRow - 1;
206  nbCellsY = 2 + (tmp / 4);
207  if ((tmp % 4) == 0) nbCellsY--;
208  }
209 
210  cellSameAsPrevious.resize(size_t(nbCellsX) * size_t(nbCellsY));
211 
212  // Initialize to 4 by default
213  cellWidths.resize(nbCellsX, 4);
214  cellHeights.resize(nbCellsY, 4);
215 
216  if (nbCellsX == 1)
217  cellWidths[0] = CellSize(frameWidth); // Might have merged 2nd column into 1st
218  else
219  {
220  // Treat the special cases (first and last columns/rows)
221  cellWidths[0] = CellSize(widthFirstColumn);
222  // Compute size of the last column
223  const size_t nbColumnsExcludingFirstAndLast = nbCellsX - 2;
224  const size_t widthExcludingFirstAndLastColumns = 4 * nbColumnsExcludingFirstAndLast;
225  cellWidths[nbCellsX - 1] =
226  CellSize(frameWidth - (widthFirstColumn + widthExcludingFirstAndLastColumns));
227  }
228 
229  if (nbCellsY == 1)
230  cellHeights[0] = CellSize(frameHeight); // Might have merged 2nd row into 1st
231  else
232  {
233  cellHeights[0] = CellSize(heightFirstRow);
234  // Compute size of the last row
235  const size_t nbRowsExcludingFirstAndLast = nbCellsY - 2;
236  const size_t heightExcludingFirstAndLastRows = 4 * nbRowsExcludingFirstAndLast;
237  cellHeights[nbCellsY - 1] =
238  CellSize(frameHeight - (heightFirstRow + heightExcludingFirstAndLastRows));
239  }
240 
241  imageView = imgProvider.getNewImage(frameWidth, frameHeight);
242  }
243 };
244 
245 struct DirectionData
246 {
247  const DCC::Direction& dirRef;
248 
249  Vector<uint8_t> codeToPixelValue;
250 
251  BitStreamView equalCellBitStream;
252  BitStreamView pixelMaskBitStream;
253  BitStreamView rawPixelUsageBitStream;
254  BitStreamView rawPixelCodesBitStream;
255  BitStreamView pixelCodesDisplacementBitStream;
256 
257  size_t nbFrames;
258  size_t nbPixelBufferCellsX;
259  size_t nbPixelBufferCellsY;
260 
261  Vector<FrameData> framesData;
262 
263  DirectionData(const DCC::Direction& dir, BitStreamView& bitStream, size_t nbFramesPerDir,
264  IImageProvider<uint8_t>& imgProvider)
265  : dirRef(dir), nbFrames(nbFramesPerDir)
266  {
267  uint32_t equalCellsBitStreamSize = 0;
268  uint32_t pixelMaskBitStreamSize = 0;
269  uint32_t encodingTypeBitsreamSize = 0;
270  uint32_t rawPixelCodesBitStreamSize = 0;
271 
272  if (dir.header.compressEqualCells) {
273  equalCellsBitStreamSize = bitStream.readUnsigned(20);
274  }
275 
276  pixelMaskBitStreamSize = bitStream.readUnsigned(20);
277 
278  if (dir.header.hasRawPixelEncoding) {
279  encodingTypeBitsreamSize = bitStream.readUnsigned(20);
280  rawPixelCodesBitStreamSize = bitStream.readUnsigned(20);
281  }
282 
283  // Tells what code correspond to which pixel value.
284  // For example if the pixel values used are 0, 31 , 42 then
285  // code 0 gives 0
286  // code 1 gives 31
287  // code 2 gives 42
288  codeToPixelValue.reserve(256);
289  for (size_t i = 0; i < 256; i++)
290  {
291  const bool pixelValueUsed = bitStream.readBool();
292  if (pixelValueUsed) codeToPixelValue.push_back(uint8_t(i));
293  }
294 
295  // Prepare the bitstreams
296 
297  assert(!dir.header.compressEqualCells || equalCellsBitStreamSize);
298  equalCellBitStream = bitStream.createSubView(equalCellsBitStreamSize);
299  bitStream.skip(equalCellsBitStreamSize);
300 
301  pixelMaskBitStream = bitStream.createSubView(pixelMaskBitStreamSize);
302  bitStream.skip(pixelMaskBitStreamSize);
303 
304  assert(!dir.header.hasRawPixelEncoding ||
305  (encodingTypeBitsreamSize && rawPixelCodesBitStreamSize));
306  rawPixelUsageBitStream = bitStream.createSubView(encodingTypeBitsreamSize);
307  bitStream.skip(encodingTypeBitsreamSize);
308 
309  rawPixelCodesBitStream = bitStream.createSubView(rawPixelCodesBitStreamSize);
310  bitStream.skip(rawPixelCodesBitStreamSize);
311 
312  // Note : goes until the end of the direction
313  pixelCodesDisplacementBitStream =
314  bitStream.createSubView(bitStream.bufferSizeInBits() - bitStream.tell());
315 
316  const size_t dirWidth = size_t(dir.extents.width());
317  const size_t dirHeight = size_t(dir.extents.height());
318 
319  // Compute the size in cells of the pixel buffer. There are no alignment nor dimensions
320  // requirements for the pixel buffer, but cells are of size 4 at max.
321 
322  // nbPixelBufferCellsX = dirWidth/4 rounded up
323  nbPixelBufferCellsX = 1u + (dirWidth - 1u) / pbCellMaxPixelSize;
324  nbPixelBufferCellsY = 1u + (dirHeight - 1u) / pbCellMaxPixelSize;
325 
326  framesData.reserve(nbFrames);
327 
328  for (size_t frameIndex = 0; frameIndex < nbFrames; ++frameIndex)
329  {
330  framesData.emplace_back(dir, dir.frameHeaders[frameIndex], imgProvider);
331  }
332  }
333 
335  bool isValid() const
336  {
337  for (size_t frameIndex = 0; frameIndex < nbFrames; ++frameIndex)
338  {
339  if (!framesData[frameIndex].imageView.isValid()) return false;
340  }
341  return true;
342  }
343 };
344 
345 using PixelCodesStack = std::array<uint8_t, PixelBufferEntry::nbValues>;
349 int decodePixelCodesStack(DirectionData& data, uint8_t pixelMask, PixelCodesStack& pixelCodesStack)
350 {
351  if (!pixelMask) return 0; // Reuse the previous cell values, but still decode the cell in stage2
352  const uint16_t nbPixelsInMask = Utils::popCount(uint16_t(pixelMask));
353 
354  // Is the cell encoded in the raw stream ?
355  const bool decodeRaw = data.rawPixelUsageBitStream.bufferSizeInBits() > 0 &&
356  data.rawPixelUsageBitStream.readBool();
357 
358  uint8_t lastPixelCode = 0;
359  size_t curPixelIdx = 0;
360 
361  for (curPixelIdx = 0; curPixelIdx < nbPixelsInMask; curPixelIdx++)
362  {
363  uint8_t& curPixelCode = pixelCodesStack[curPixelIdx];
364  if (decodeRaw) {
365  // Read the value of the code directly from rawPixelCodesBitStream
366  curPixelCode = data.rawPixelCodesBitStream.readUnsigned8OrLess(8);
367  }
368  else
369  {
370  // Read the value of the code incrementally from pixelCodesDisplacementBitStream
371  curPixelCode = lastPixelCode;
372  uint8_t pixelDisplacement;
373  do
374  {
375  pixelDisplacement = data.pixelCodesDisplacementBitStream.readUnsigned8OrLess(4);
376  curPixelCode += pixelDisplacement;
377  } while (pixelDisplacement == 0xF);
378  }
379  // Stop decoding if we encounter twice the same pixel code.
380  // It also means that this pixel code is discarded.
381  if (curPixelCode == lastPixelCode) {
382  // Note : We discard the pixel by putting a 0 but it doesn't matter anyway since we only
383  // use nbPixelsDecoded values later when popping the stack
384  curPixelCode = 0;
385  break;
386  }
387  else
388  {
389  lastPixelCode = curPixelCode;
390  }
391  }
392  return int(curPixelIdx);
393 }
394 
395 void decodeFrameStage1(DirectionData& data, FrameData& frameData, Vector<size_t>& pixelBuffer,
396  Vector<PixelBufferEntry>& pbEntries)
397 {
398  // Offset in terms of cells for this frame
399  const size_t frameCellOffsetX = frameData.offsetX / 4;
400  const size_t frameCellOffsetY = frameData.offsetY / 4;
401 
402  // For each cell of this frame (not the same number as the pixel buffer cells ! )
403  for (size_t y = 0; y < frameData.nbCellsY; y++)
404  {
405  const size_t curCellY = frameCellOffsetY + y;
406  for (size_t x = 0; x < frameData.nbCellsX; x++)
407  {
408  const size_t curCellX = frameCellOffsetX + x;
409 
410  const size_t curPbCellIndex = curCellX + curCellY * data.nbPixelBufferCellsX;
411  const size_t curFrameCellIndex = x + y * frameData.nbCellsX;
412 
413  size_t& lastPixelEntryIndexForCell = pixelBuffer[curPbCellIndex];
414 
415  bool sameAsPreviousCell = false; // By default always decode the cell
416  uint8_t pixelMask = 0x0F; // Default pixel mask
417 
418  // Check if this cell is equal to the previous one
419  if (lastPixelEntryIndexForCell < pbEntries.size()) {
420  // Check if we have to reuse the previous values
421  if (data.dirRef.header.compressEqualCells) {
422  // If true, the cell is the same as the previous one or transparent.
423  // Which actually mean the same thing : skip the decoding of this cell
424  sameAsPreviousCell = data.equalCellBitStream.readBool();
425  }
426  if (!sameAsPreviousCell) {
427  pixelMask = data.pixelMaskBitStream.readUnsigned8OrLess(4);
428  }
429  }
430 
431  // Store the fact that we skipped this cell for the 2nd phase
432  frameData.cellSameAsPrevious[curFrameCellIndex] = sameAsPreviousCell;
433 
434  if (!sameAsPreviousCell) {
435  // Pixel buffer entries are encoded as a stack in the stream which means the
436  // last value decoded is actually the 1st value with a bit in the mask.
437  PixelCodesStack pixelCodesStack = {};
438  int nbPixelsDecoded = decodePixelCodesStack(data, pixelMask, pixelCodesStack);
439 
440  PixelBufferEntry previousEntryForCell;
441  if (lastPixelEntryIndexForCell < pbEntries.size()) {
442  previousEntryForCell = pbEntries[lastPixelEntryIndexForCell];
443  }
444  else
445  {
446  // No need for previousEntryForCell if it doesn't exist, as the mask is 0xF
447  assert(pixelMask == 0xF);
448  }
449 
450  // Finalize the decoding of the pixel buffer entry
451  PixelBufferEntry newEntry;
452 
453  int curIndex = nbPixelsDecoded - 1;
454  for (size_t i = 0; i < PixelBufferEntry::nbValues; i++)
455  {
456  // Pop a value if bit set in the mask
457  if (pixelMask & (1u << i)) {
458  uint8_t pixelCode;
459  if (curIndex >= 0) {
460  pixelCode = pixelCodesStack[size_t(curIndex--)];
461  }
462  else
463  {
464  pixelCode = 0;
465  }
466  // Store the actual values instead of the codes
467  newEntry.values[i] = data.codeToPixelValue[pixelCode];
468  }
469  // If not set, use the previous value for this entry
470  else
471  {
472  newEntry.values[i] = previousEntryForCell.values[i];
473  }
474  }
475  // Update the pixel buffer cell information
476  lastPixelEntryIndexForCell = pbEntries.size();
477  // Add the new entry for use in the 2nd stage
478  pbEntries.push_back(newEntry);
479  }
480  }
481  }
482 }
483 
484 void decodeDirectionStage1(DirectionData& data, Vector<PixelBufferEntry>& pbEntries)
485 {
486  // For each cell store a PixelBufferEntry index that points to the last entry for this cell
487  // This will be used to retrieve values from the previous frame
488  constexpr size_t invalidIndex = std::numeric_limits<size_t>::max();
489  const size_t pixelBufferNbCells = data.nbPixelBufferCellsX * data.nbPixelBufferCellsY;
490  Vector<size_t> pixelBuffer(pixelBufferNbCells, invalidIndex);
491 
492  // 1st phase of decoding : fill the pixel buffer
493  // We actually fill a buffer of entries as to avoid storing empty entries
494  for (size_t frameIndex = 0; frameIndex < data.nbFrames; ++frameIndex)
495  {
496  FrameData& frameData = data.framesData[frameIndex];
497  frameData.firstPixelBufferEntry = pbEntries.size();
498  decodeFrameStage1(data, frameData, pixelBuffer, pbEntries);
499  }
500 }
501 
502 void decodeDirectionStage2(DirectionData& data, const Vector<PixelBufferEntry>& pbEntries)
503 {
504  // This is the reason why we need to stages, we don't have the offset of this bitstream
505  BitStreamView& pixelCodeIndices = data.pixelCodesDisplacementBitStream;
506 
507  const size_t pbWidth = size_t(data.dirRef.extents.width());
508  const size_t pbHeight = size_t(data.dirRef.extents.height());
509  const size_t pbStride = pbWidth;
510  const size_t nbPixelBufferCells = data.nbPixelBufferCellsX * data.nbPixelBufferCellsY;
511 
512  Vector<Cell> pixelBufferCells(nbPixelBufferCells, Cell{0xF, 0xF});
513  Vector<uint8_t> pixelBufferColors(pbStride * pbHeight);
514  ImageView<uint8_t> pBuffer{pixelBufferColors.data(), pbWidth, pbHeight, pbStride};
515 
516  // 2nd phase of decoding : Finish using the pixel buffer entries
517  for (size_t frameIndex = 0; frameIndex < data.nbFrames; ++frameIndex)
518  {
519  const FrameData& frameData = data.framesData[frameIndex];
520  size_t pbEntryIndex = frameData.firstPixelBufferEntry;
521 
522  size_t pbCellPosY = frameData.offsetY;
523  for (size_t cellY = 0; cellY < frameData.nbCellsY; cellY++)
524  {
525  size_t pbCellPosX = frameData.offsetX;
526  for (size_t cellX = 0; cellX < frameData.nbCellsX; cellX++)
527  {
528  const size_t frameCellIndex = cellX + cellY * frameData.nbCellsX;
529  Cell frameCell;
530  frameCell.width = frameData.cellWidths[cellX];
531  frameCell.height = frameData.cellHeights[cellY];
532 
533  const size_t pbCellIndex =
534  (pbCellPosX / pbCellMaxPixelSize) +
535  (pbCellPosY / pbCellMaxPixelSize) * data.nbPixelBufferCellsX;
536  Cell& pbCell = pixelBufferCells[pbCellIndex];
537 
538  if (frameData.cellSameAsPrevious[frameCellIndex]) {
539  if ((frameCell.width != pbCell.width) || (frameCell.height != pbCell.height)) {
540  // Clear the cell to 0
541  // If we used the previous frame pixels instead of reusing the same
542  // buffer for all frames we would not need to clear this (it would be
543  // initialized to 0 before writing any value to the frame pixels) But we
544  // would then need to copy values if the size matched
545  pBuffer.fillBytes(pbCellPosX, pbCellPosY, frameCell.width, frameCell.height,
546  0);
547  }
548  else
549  {
550  // Same size, copy from previous cell in the buffer
551  // Nothing to change !
552  }
553  }
554  else
555  {
556  const auto& pixelValues = pbEntries[pbEntryIndex++].values;
557 
558  if (pixelValues[0] == pixelValues[1]) {
559  // This means we only got one pixel code, so fill the cell with it
560  pBuffer.fillBytes(pbCellPosX, pbCellPosY, frameCell.width, frameCell.height,
561  pixelValues[0]);
562  }
563  else
564  {
565  int nbBitsToRead = 0;
566  if (pixelValues[1] == pixelValues[2]) {
567  // Stopped decoding after the 2nd value, only pixelValues[0] and
568  // pixelValues[1] are different, so only 1bit needs to be read to choose
569  // from those values
570  nbBitsToRead = 1;
571  }
572  else // We need 2 bits to index 3-4 values
573  {
574  nbBitsToRead = 2;
575  }
576 
577  // fill FRAME cell with pixels
578  for (size_t y = 0; y < frameCell.height; y++)
579  {
580  for (size_t x = 0; x < frameCell.width; x++)
581  {
582  const uint8_t pixelCodeIndex =
583  pixelCodeIndices.readUnsigned8OrLess(nbBitsToRead);
584  // Note: This actually means that a cell (4x4 block) can use at most
585  // 4 colors, a bit like DXT !
586  const uint8_t pixelValue = pixelValues[pixelCodeIndex];
587 
588  pBuffer(pbCellPosX + x, pbCellPosY + y) = pixelValue;
589  }
590  }
591  }
592  }
593 
594  pbCell = frameCell;
595  pbCellPosX += frameCell.width;
596  }
597  pbCellPosY += frameData.cellHeights[cellY];
598  }
599  // Done decoding this frame, now copy its content.
600  const ImageView<uint8_t> frameImageView = frameData.imageView;
601  const ImageView<uint8_t> pbFrameView = pBuffer.subView(
602  frameData.offsetX, frameData.offsetY, frameImageView.width, frameImageView.height);
603  assert(frameImageView.isValid() && pbFrameView.isValid());
604 
605  pbFrameView.copyTo(frameImageView);
606 
608 #define DEBUG_EXPORT_PPM 0
609 #if DEBUG_EXPORT_PPM
610 #define EXPORT_FULL_SIZE false
612  auto filename = fmt::format("test{}.ppm", frameIndex);
613  Utils::exportToPGM(filename.c_str(), EXPORT_FULL_SIZE ? pBuffer : frameImageView);
614 #endif
615  }
616 }
617 } // anonymous namespace
618 bool DCC::readDirection(Direction& outDir, uint32_t dirIndex, IImageProvider<uint8_t>& imgProvider)
619 {
620  if (dirIndex >= header.directions) return false;
621 
622  const size_t directionEncodedSize = getDirectionSize(dirIndex);
623  Vector<uint8_t> buffer(directionEncodedSize);
624  stream->seek(directionsOffsets[dirIndex], IStream::beg);
625  stream->read(buffer.data(), directionEncodedSize);
626  assert(stream->good());
627  BitStreamView bitStream(buffer.data(), directionEncodedSize * CHAR_BIT);
628 
629  DirectionHeader& dirHeader = outDir.header;
630  if (!readDirHeader(dirHeader, bitStream)) return false;
631 
632  if (!readFrameHeaders(header.framesPerDir, outDir, bitStream)) return false;
633 
634  outDir.computeDirExtents();
635 
636  DirectionData data{outDir, bitStream, header.framesPerDir, imgProvider};
637  if (!data.isValid()) return false;
638 
639  Vector<PixelBufferEntry> pbEntries;
640  {
641  size_t estimatedNbEntries =
642  (header.framesPerDir * data.nbPixelBufferCellsX * data.nbPixelBufferCellsY) / 4;
643  pbEntries.reserve(estimatedNbEntries);
644  }
645 
646  decodeDirectionStage1(data, pbEntries);
647 
648  decodeDirectionStage2(data, pbEntries);
649 
650  // Make sure we fully read the streams
651  assert(data.equalCellBitStream.tell() == data.equalCellBitStream.sizeInBits());
652  assert(data.pixelMaskBitStream.tell() == data.pixelMaskBitStream.sizeInBits());
653  assert(data.rawPixelUsageBitStream.tell() == data.rawPixelUsageBitStream.sizeInBits());
654  assert(data.rawPixelCodesBitStream.tell() == data.rawPixelCodesBitStream.sizeInBits());
655  // This exact stream size is not known, so check if we at least are in the last byte
656  assert(data.pixelCodesDisplacementBitStream.bitPositionInBuffer() + 7_z >=
657  data.pixelCodesDisplacementBitStream.bufferSizeInBits());
658 
659  return bitStream.good();
660 }
661 
662 } // namespace WorldStone
663 
ImageView< uint8_t > imageView
Output buffer image view.
Definition: dcc.cpp:178
uint16_t offsetX
X Offset relative to the whole direction bounding box.
Definition: dcc.cpp:171
static constexpr unsigned bitsWidthTable[16]
An array that maps an encoded 4-bit size to the real size in bits.
Definition: dcc.h:160
bool readDirection(Direction &outDir, uint32_t dirIndex, IImageProvider< uint8_t > &imgProvider)
Decodes a direction of the file into memory.
Definition: dcc.cpp:618
uint32_t framesPerDir
Frames number for each direction.
Definition: dcc.h:57
Vector< uint32_t > directionsOffsets
Offset of each direction header in the file, follows the Header.
Definition: dcc.h:171
uint8_t version
DCC major version, usually 6.
Definition: dcc.h:55
Represents a direction header.
Definition: dcc.h:73
uint16_t offsetY
Y Offset relative to the whole direction bounding box.
Definition: dcc.cpp:172
void computeDirExtents()
Definition: dcc.h:149
uint8_t signature
Magic number for DCC files, must be 0x74.
Definition: dcc.h:54
uint8_t directions
Number of directions in this file, max 32.
Definition: dcc.h:56
Implements image manipulation helpers.
An interface of a class that can provide images views.
Definition: ImageView.h:130
Implementation of a DCC file decoder.
bool initDecoder(StreamPtr &&streamPtr)
Start decoding the stream and preparing data.
Definition: dcc.cpp:44
uint32_t tag
Seems to be always 1 ?
Definition: dcc.h:58
uint32_t finalDc6Size
Size of the decoded image in bytes (outsizeCoded for all directions) + directions*framesPerDir*4 + 24...
Definition: dcc.h:62
Provides access to a variable bitsize values stream.
Definition: BitStream.h:33