Initial commit
This commit is contained in:
417
image/codecs/smc.cpp
Normal file
417
image/codecs/smc.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
// Based off ffmpeg's SMC decoder
|
||||
|
||||
#include "image/codecs/smc.h"
|
||||
#include "common/stream.h"
|
||||
#include "common/textconsole.h"
|
||||
|
||||
namespace Image {
|
||||
|
||||
#define GET_BLOCK_COUNT() \
|
||||
(opcode & 0x10) ? (1 + stream.readByte()) : 1 + (opcode & 0x0F);
|
||||
|
||||
#define ADVANCE_BLOCK() \
|
||||
{ \
|
||||
pixelPtr += 4; \
|
||||
if (pixelPtr >= _surface->w) { \
|
||||
pixelPtr = 0; \
|
||||
rowPtr += _surface->w * 4; \
|
||||
} \
|
||||
totalBlocks--; \
|
||||
if (totalBlocks < 0) { \
|
||||
warning("block counter just went negative (this should not happen)"); \
|
||||
return _surface; \
|
||||
} \
|
||||
}
|
||||
|
||||
SMCDecoder::SMCDecoder(uint16 width, uint16 height) {
|
||||
_surface = new Graphics::Surface();
|
||||
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
||||
}
|
||||
|
||||
SMCDecoder::~SMCDecoder() {
|
||||
_surface->free();
|
||||
delete _surface;
|
||||
}
|
||||
|
||||
const Graphics::Surface *SMCDecoder::decodeFrame(Common::SeekableReadStream &stream) {
|
||||
byte *pixels = (byte *)_surface->getPixels();
|
||||
|
||||
uint32 numBlocks = 0;
|
||||
uint32 colorFlags = 0;
|
||||
uint32 colorFlagsA = 0;
|
||||
uint32 colorFlagsB = 0;
|
||||
|
||||
const uint16 rowInc = _surface->w - 4;
|
||||
int32 rowPtr = 0;
|
||||
int32 pixelPtr = 0;
|
||||
uint32 blockPtr = 0;
|
||||
uint32 prevBlockPtr = 0;
|
||||
uint32 prevBlockPtr1 = 0, prevBlockPtr2 = 0;
|
||||
byte prevBlockFlag = false;
|
||||
uint32 pixel = 0;
|
||||
|
||||
uint32 colorPairIndex = 0;
|
||||
uint32 colorQuadIndex = 0;
|
||||
uint32 colorOctetIndex = 0;
|
||||
uint32 colorTableIndex = 0; // indices to color pair, quad, or octet tables
|
||||
|
||||
int32 chunkSize = stream.readUint32BE() & 0x00FFFFFF;
|
||||
if (chunkSize != stream.size())
|
||||
warning("MOV chunk size != SMC chunk size (%d != %d); ignoring SMC chunk size", chunkSize, (int)stream.size());
|
||||
|
||||
int32 totalBlocks = ((_surface->w + 3) / 4) * ((_surface->h + 3) / 4);
|
||||
|
||||
uint32 pixelSize = _surface->w * _surface->h;
|
||||
|
||||
// traverse through the blocks
|
||||
while (totalBlocks != 0) {
|
||||
// sanity checks
|
||||
|
||||
// make sure stream ptr hasn't gone out of bounds
|
||||
if (stream.pos() > stream.size()) {
|
||||
warning("SMC decoder just went out of bounds (stream ptr = %d, chunk size = %d)", (int)stream.pos(), (int)stream.size());
|
||||
return _surface;
|
||||
}
|
||||
|
||||
// make sure the row pointer hasn't gone wild
|
||||
if (rowPtr >= _surface->w * _surface->h) {
|
||||
warning("SMC decoder just went out of bounds (row ptr = %d, size = %d)", rowPtr, _surface->w * _surface->h);
|
||||
return _surface;
|
||||
}
|
||||
|
||||
byte opcode = stream.readByte();
|
||||
|
||||
switch (opcode & 0xF0) {
|
||||
// skip n blocks
|
||||
case 0x00:
|
||||
case 0x10:
|
||||
numBlocks = GET_BLOCK_COUNT();
|
||||
while (numBlocks--) {
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// repeat last block n times
|
||||
case 0x20:
|
||||
case 0x30:
|
||||
numBlocks = GET_BLOCK_COUNT();
|
||||
|
||||
// sanity check
|
||||
if (rowPtr == 0 && pixelPtr == 0) {
|
||||
warning("encountered repeat block opcode (%02X) but no blocks rendered yet", opcode & 0xF0);
|
||||
break;
|
||||
}
|
||||
|
||||
// figure out where the previous block started
|
||||
if (pixelPtr == 0)
|
||||
prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4;
|
||||
else
|
||||
prevBlockPtr1 = rowPtr + pixelPtr - 4;
|
||||
|
||||
while (numBlocks--) {
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
prevBlockPtr = prevBlockPtr1;
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = pixels[prevBlockPtr];
|
||||
blockPtr++, prevBlockPtr++;
|
||||
}
|
||||
blockPtr += rowInc;
|
||||
prevBlockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// repeat previous pair of blocks n times
|
||||
case 0x40:
|
||||
case 0x50:
|
||||
numBlocks = GET_BLOCK_COUNT();
|
||||
numBlocks *= 2;
|
||||
|
||||
// sanity check
|
||||
if (rowPtr == 0 && pixelPtr < 2 * 4) {
|
||||
warning("encountered repeat block opcode (%02X) but not enough blocks rendered yet", opcode & 0xF0);
|
||||
break;
|
||||
}
|
||||
|
||||
// figure out where the previous 2 blocks started
|
||||
if (pixelPtr == 0)
|
||||
prevBlockPtr1 = (rowPtr - _surface->w * 4) + _surface->w - 4 * 2;
|
||||
else if (pixelPtr == 4)
|
||||
prevBlockPtr1 = (rowPtr - _surface->w * 4) + rowInc;
|
||||
else
|
||||
prevBlockPtr1 = rowPtr + pixelPtr - 4 * 2;
|
||||
|
||||
if (pixelPtr == 0)
|
||||
prevBlockPtr2 = (rowPtr - _surface->w * 4) + rowInc;
|
||||
else
|
||||
prevBlockPtr2 = rowPtr + pixelPtr - 4;
|
||||
|
||||
prevBlockFlag = 0;
|
||||
while (numBlocks--) {
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
|
||||
if (prevBlockFlag)
|
||||
prevBlockPtr = prevBlockPtr2;
|
||||
else
|
||||
prevBlockPtr = prevBlockPtr1;
|
||||
|
||||
prevBlockFlag = !prevBlockFlag;
|
||||
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = pixels[prevBlockPtr];
|
||||
blockPtr++, prevBlockPtr++;
|
||||
}
|
||||
|
||||
blockPtr += rowInc;
|
||||
prevBlockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// 1-color block encoding
|
||||
case 0x60:
|
||||
case 0x70:
|
||||
numBlocks = GET_BLOCK_COUNT();
|
||||
pixel = stream.readByte();
|
||||
|
||||
while (numBlocks--) {
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = pixel;
|
||||
blockPtr++;
|
||||
}
|
||||
|
||||
blockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// 2-color block encoding
|
||||
case 0x80:
|
||||
case 0x90:
|
||||
numBlocks = (opcode & 0x0F) + 1;
|
||||
|
||||
// figure out which color pair to use to paint the 2-color block
|
||||
if ((opcode & 0xF0) == 0x80) {
|
||||
// fetch the next 2 colors from bytestream and store in next
|
||||
// available entry in the color pair table
|
||||
for (byte i = 0; i < CPAIR; i++) {
|
||||
pixel = stream.readByte();
|
||||
colorTableIndex = CPAIR * colorPairIndex + i;
|
||||
_colorPairs[colorTableIndex] = pixel;
|
||||
}
|
||||
|
||||
// this is the base index to use for this block
|
||||
colorTableIndex = CPAIR * colorPairIndex;
|
||||
colorPairIndex++;
|
||||
|
||||
// wraparound
|
||||
if (colorPairIndex == COLORS_PER_TABLE)
|
||||
colorPairIndex = 0;
|
||||
} else
|
||||
colorTableIndex = CPAIR * stream.readByte();
|
||||
|
||||
while (numBlocks--) {
|
||||
colorFlags = stream.readUint16BE();
|
||||
uint16 flagMask = 0x8000;
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
if (colorFlags & flagMask)
|
||||
pixel = colorTableIndex + 1;
|
||||
else
|
||||
pixel = colorTableIndex;
|
||||
|
||||
flagMask >>= 1;
|
||||
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = _colorPairs[pixel];
|
||||
|
||||
blockPtr++;
|
||||
}
|
||||
|
||||
blockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// 4-color block encoding
|
||||
case 0xA0:
|
||||
case 0xB0:
|
||||
numBlocks = (opcode & 0x0F) + 1;
|
||||
|
||||
// figure out which color quad to use to paint the 4-color block
|
||||
if ((opcode & 0xF0) == 0xA0) {
|
||||
// fetch the next 4 colors from bytestream and store in next
|
||||
// available entry in the color quad table
|
||||
for (byte i = 0; i < CQUAD; i++) {
|
||||
pixel = stream.readByte();
|
||||
colorTableIndex = CQUAD * colorQuadIndex + i;
|
||||
_colorQuads[colorTableIndex] = pixel;
|
||||
}
|
||||
|
||||
// this is the base index to use for this block
|
||||
colorTableIndex = CQUAD * colorQuadIndex;
|
||||
colorQuadIndex++;
|
||||
|
||||
// wraparound
|
||||
if (colorQuadIndex == COLORS_PER_TABLE)
|
||||
colorQuadIndex = 0;
|
||||
} else
|
||||
colorTableIndex = CQUAD * stream.readByte();
|
||||
|
||||
while (numBlocks--) {
|
||||
colorFlags = stream.readUint32BE();
|
||||
|
||||
// flag mask actually acts as a bit shift count here
|
||||
byte flagMask = 30;
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x03);
|
||||
flagMask -= 2;
|
||||
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = _colorQuads[pixel];
|
||||
blockPtr++;
|
||||
}
|
||||
blockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// 8-color block encoding
|
||||
case 0xC0:
|
||||
case 0xD0:
|
||||
numBlocks = (opcode & 0x0F) + 1;
|
||||
|
||||
// figure out which color octet to use to paint the 8-color block
|
||||
if ((opcode & 0xF0) == 0xC0) {
|
||||
// fetch the next 8 colors from bytestream and store in next
|
||||
// available entry in the color octet table
|
||||
for (byte i = 0; i < COCTET; i++) {
|
||||
pixel = stream.readByte();
|
||||
colorTableIndex = COCTET * colorOctetIndex + i;
|
||||
_colorOctets[colorTableIndex] = pixel;
|
||||
}
|
||||
|
||||
// this is the base index to use for this block
|
||||
colorTableIndex = COCTET * colorOctetIndex;
|
||||
colorOctetIndex++;
|
||||
|
||||
// wraparound
|
||||
if (colorOctetIndex == COLORS_PER_TABLE)
|
||||
colorOctetIndex = 0;
|
||||
} else
|
||||
colorTableIndex = COCTET * stream.readByte();
|
||||
|
||||
while (numBlocks--) {
|
||||
/*
|
||||
For this input of 6 hex bytes:
|
||||
01 23 45 67 89 AB
|
||||
Mangle it to this output:
|
||||
flags_a = xx012456, flags_b = xx89A37B
|
||||
*/
|
||||
|
||||
// build the color flags
|
||||
byte flagData[6];
|
||||
stream.read(flagData, 6);
|
||||
|
||||
colorFlagsA = ((READ_BE_UINT16(flagData) & 0xFFF0) << 8) | (READ_BE_UINT16(flagData + 2) >> 4);
|
||||
colorFlagsB = ((READ_BE_UINT16(flagData + 4) & 0xFFF0) << 8) | ((flagData[1] & 0xF) << 8) |
|
||||
((flagData[3] & 0xF) << 4) | (flagData[5] & 0xf);
|
||||
|
||||
colorFlags = colorFlagsA;
|
||||
|
||||
// flag mask actually acts as a bit shift count here
|
||||
byte flagMask = 21;
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
// reload flags at third row (iteration y == 2)
|
||||
if (y == 2) {
|
||||
colorFlags = colorFlagsB;
|
||||
flagMask = 21;
|
||||
}
|
||||
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
pixel = colorTableIndex + ((colorFlags >> flagMask) & 0x07);
|
||||
flagMask -= 3;
|
||||
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = _colorOctets[pixel];
|
||||
|
||||
blockPtr++;
|
||||
}
|
||||
|
||||
blockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
// 16-color block encoding (every pixel is a different color)
|
||||
case 0xE0:
|
||||
numBlocks = (opcode & 0x0F) + 1;
|
||||
|
||||
while (numBlocks--) {
|
||||
blockPtr = rowPtr + pixelPtr;
|
||||
for (byte y = 0; y < 4; y++) {
|
||||
for (byte x = 0; x < 4; x++) {
|
||||
byte b = stream.readByte();
|
||||
if (blockPtr < pixelSize)
|
||||
pixels[blockPtr] = b;
|
||||
blockPtr++;
|
||||
}
|
||||
|
||||
blockPtr += rowInc;
|
||||
}
|
||||
ADVANCE_BLOCK();
|
||||
}
|
||||
break;
|
||||
|
||||
case 0xF0:
|
||||
warning("0xF0 opcode seen in SMC chunk (contact the developers)");
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return _surface;
|
||||
}
|
||||
|
||||
} // End of namespace Image
|
||||
Reference in New Issue
Block a user