scummvm/image/tga.cpp

461 lines
12 KiB
C++

/* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
/* Based on code from xoreos https://github.com/DrMcCoy/xoreos/
* relicensed under GPLv2+ with permission from DrMcCoy and clone2727
*/
#include "image/tga.h"
#include "common/util.h"
#include "common/algorithm.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "common/error.h"
namespace Image {
TGADecoder::TGADecoder() {
_colorMapSize = 0;
_colorMapOrigin = 0;
_colorMapLength = 0;
_colorMapEntryLength = 0;
_colorMap = NULL;
}
TGADecoder::~TGADecoder() {
destroy();
}
void TGADecoder::destroy() {
_surface.free();
delete[] _colorMap;
}
bool TGADecoder::loadStream(Common::SeekableReadStream &tga) {
destroy();
byte imageType, pixelDepth;
bool success;
success = readHeader(tga, imageType, pixelDepth);
if (success) {
switch (imageType) {
case TYPE_BW:
case TYPE_TRUECOLOR:
success = readData(tga, imageType, pixelDepth);
break;
case TYPE_RLE_BW:
case TYPE_RLE_TRUECOLOR:
case TYPE_RLE_CMAP:
success = readDataRLE(tga, imageType, pixelDepth);
break;
case TYPE_CMAP:
success = readDataColorMapped(tga, imageType, pixelDepth);
break;
default:
success = false;
break;
}
}
if (tga.err() || !success) {
warning("Failed reading TGA-file");
return false;
}
return success;
}
bool TGADecoder::readHeader(Common::SeekableReadStream &tga, byte &imageType, byte &pixelDepth) {
if (!tga.seek(0)) {
warning("Failed reading TGA-file");
return false;
}
// TGAs have an optional "id" string in the header
uint32 idLength = tga.readByte();
// Number of colors in the color map / palette
int hasColorMap = tga.readByte();
// Image type. See header for numeric constants
imageType = tga.readByte();
switch (imageType) {
case TYPE_CMAP:
case TYPE_TRUECOLOR:
case TYPE_BW:
case TYPE_RLE_CMAP:
case TYPE_RLE_TRUECOLOR:
case TYPE_RLE_BW:
break;
default:
warning("Unsupported image type: %d", imageType);
return false;
}
// Color map specifications
if (hasColorMap == 0) {
tga.skip(5);
} else {
_colorMapOrigin = tga.readUint16LE();
_colorMapLength = tga.readUint16LE();
_colorMapEntryLength = tga.readByte();
}
// Origin-defintions
tga.skip(2 + 2);
// Image dimensions
_surface.w = tga.readUint16LE();
_surface.h = tga.readUint16LE();
// Bits per pixel
pixelDepth = tga.readByte();
_surface.format.bytesPerPixel = pixelDepth / 8;
// Image descriptor
byte imgDesc = tga.readByte();
int attributeBits = imgDesc & 0x0F;
assert((imgDesc & 0x10) == 0);
_originTop = (imgDesc & 0x20);
// Interleaving is not handled at this point
//int interleave = (imgDesc & 0xC);
if (imageType == TYPE_CMAP || imageType == TYPE_RLE_CMAP) {
if (pixelDepth == 8) {
_format = Graphics::PixelFormat::createFormatCLUT8();
} else {
warning("Unsupported index-depth: %d", pixelDepth);
return false;
}
} else if (imageType == TYPE_TRUECOLOR || imageType == TYPE_RLE_TRUECOLOR) {
if (pixelDepth == 24) {
_format = Graphics::PixelFormat(3, 8, 8, 8, 0, 16, 8, 0, 0);
} else if (pixelDepth == 32) {
// HACK: According to the spec, attributeBits should determine the amount
// of alpha-bits, however, as the game files that use this decoder seems
// to ignore that fact, we force the amount to 8 for 32bpp files for now.
_format = Graphics::PixelFormat(4, 8, 8, 8, /* attributeBits */ 8, 16, 8, 0, 24);
} else if (pixelDepth == 16) {
// 16bpp TGA is ARGB1555
_format = Graphics::PixelFormat(2, 5, 5, 5, attributeBits, 10, 5, 0, 15);
} else {
warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
return false;
}
} else if (imageType == TYPE_BW || imageType == TYPE_RLE_BW) {
if (pixelDepth == 8) {
_format = Graphics::PixelFormat(4, 8, 8, 8, 0, 16, 8, 0, 0);
} else {
warning("Unsupported pixel depth: %d, %d", imageType, pixelDepth);
return false;
}
} else {
warning("Unsupported image type: %d", imageType);
return false;
}
// Skip the id string
tga.skip(idLength);
if (hasColorMap) {
return readColorMap(tga, imageType, pixelDepth);
}
return true;
}
bool TGADecoder::readColorMap(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
_colorMap = new byte[3 * _colorMapLength];
for (int i = 0; i < _colorMapLength * 3; i += 3) {
byte r, g, b;
if (_colorMapEntryLength == 32) {
byte a;
Graphics::PixelFormat format(4, 8, 8, 8, 0, 16, 8, 0, 24);
uint32 color = tga.readUint32LE();
format.colorToARGB(color, a, r, g, b);
} else if (_colorMapEntryLength == 24) {
r = tga.readByte();
g = tga.readByte();
b = tga.readByte();
} else if (_colorMapEntryLength == 16) {
byte a;
Graphics::PixelFormat format(2, 5, 5, 5, 0, 10, 5, 0, 15);
uint16 color = tga.readUint16LE();
format.colorToARGB(color, a, r, g, b);
} else {
warning("Unsupported image type: %d", imageType);
r = g = b = 0;
}
#ifdef SCUMM_LITTLE_ENDIAN
_colorMap[i] = r;
_colorMap[i + 1] = g;
_colorMap[i + 2] = b;
#else
_colorMap[i] = b;
_colorMap[i + 1] = g;
_colorMap[i + 2] = r;
#endif
}
return true;
}
// Additional information found from http://paulbourke.net/dataformats/tga/
// With some details from the link referenced in the header.
bool TGADecoder::readData(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
// TrueColor
if (imageType == TYPE_TRUECOLOR) {
_surface.create(_surface.w, _surface.h, _format);
if (pixelDepth == 16) {
for (int i = 0; i < _surface.h; i++) {
uint16 *dst;
if (!_originTop) {
dst = (uint16 *)_surface.getBasePtr(0, _surface.h - i - 1);
} else {
dst = (uint16 *)_surface.getBasePtr(0, i);
}
for (int j = 0; j < _surface.w; j++) {
*dst++ = tga.readUint16LE();
}
}
} else if (pixelDepth == 32) {
for (int i = 0; i < _surface.h; i++) {
uint32 *dst;
if (!_originTop) {
dst = (uint32 *)_surface.getBasePtr(0, _surface.h - i - 1);
} else {
dst = (uint32 *)_surface.getBasePtr(0, i);
}
for (int j = 0; j < _surface.w; j++) {
*dst++ = tga.readUint32LE();
}
}
} else if (pixelDepth == 24) {
for (int i = 0; i < _surface.h; i++) {
byte *dst;
if (!_originTop) {
dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
} else {
dst = (byte *)_surface.getBasePtr(0, i);
}
for (int j = 0; j < _surface.w; j++) {
byte r = tga.readByte();
byte g = tga.readByte();
byte b = tga.readByte();
#ifdef SCUMM_LITTLE_ENDIAN
*dst++ = r;
*dst++ = g;
*dst++ = b;
#else
*dst++ = b;
*dst++ = g;
*dst++ = r;
#endif
}
}
}
// Black/White
} else if (imageType == TYPE_BW) {
_surface.create(_surface.w, _surface.h, _format);
byte *data = (byte *)_surface.getPixels();
uint32 count = _surface.w * _surface.h;
while (count-- > 0) {
byte g = tga.readByte();
*data++ = g;
*data++ = g;
*data++ = g;
*data++ = g;
}
}
return true;
}
bool TGADecoder::readDataColorMapped(Common::SeekableReadStream &tga, byte imageType, byte indexDepth) {
// Color-mapped
if (imageType == TYPE_CMAP) {
_surface.create(_surface.w, _surface.h, _format);
if (indexDepth == 8) {
for (int i = 0; i < _surface.h; i++) {
byte *dst;
if (!_originTop) {
dst = (byte *)_surface.getBasePtr(0, _surface.h - i - 1);
} else {
dst = (byte *)_surface.getBasePtr(0, i);
}
for (int j = 0; j < _surface.w; j++) {
byte index = tga.readByte();
*dst++ = index;
}
}
} else if (indexDepth == 16) {
warning("16 bit indexes not supported");
return false;
}
} else {
return false;
}
return true;
}
bool TGADecoder::readDataRLE(Common::SeekableReadStream &tga, byte imageType, byte pixelDepth) {
// RLE-TrueColor / RLE-Black/White
if (imageType == TYPE_RLE_TRUECOLOR || imageType == TYPE_RLE_BW || imageType == TYPE_RLE_CMAP) {
_surface.create(_surface.w, _surface.h, _format);
uint32 count = _surface.w * _surface.h;
byte *data = (byte *)_surface.getPixels();
while (count > 0) {
uint32 header = tga.readByte();
byte type = (header & 0x80) >> 7;
uint32 rleCount = (header & 0x7F) + 1;
// RLE-packet
if (type == 1) {
if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
uint32 color = tga.readUint32LE();
while (rleCount-- > 0) {
*((uint32 *)data) = color;
data += 4;
count--;
}
} else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
byte r = tga.readByte();
byte g = tga.readByte();
byte b = tga.readByte();
while (rleCount-- > 0) {
#ifdef SCUMM_LITTLE_ENDIAN
*data++ = r;
*data++ = g;
*data++ = b;
#else
*data++ = b;
*data++ = g;
*data++ = r;
#endif
count--;
}
} else if (pixelDepth == 16 && imageType == TYPE_RLE_TRUECOLOR) {
const uint16 rgb = tga.readUint16LE();
while (rleCount-- > 0) {
*((uint16 *)data) = rgb;
data += 2;
count--;
}
} else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
byte color = tga.readByte();
while (rleCount-- > 0) {
*data++ = color;
*data++ = color;
*data++ = color;
*data++ = color;
count--;
}
} else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
byte index = tga.readByte();
while (rleCount-- > 0) {
*data++ = index;
count--;
}
} else {
warning("Unhandled pixel-depth for image-type 10");
return false;
}
// Raw-packet
} else if (type == 0) {
if (pixelDepth == 32 && imageType == TYPE_RLE_TRUECOLOR) {
while (rleCount-- > 0) {
uint32 color = tga.readUint32LE();
*((uint32 *)data) = color;
data += 4;
count--;
}
} else if (pixelDepth == 24 && imageType == TYPE_RLE_TRUECOLOR) {
while (rleCount-- > 0) {
byte r = tga.readByte();
byte g = tga.readByte();
byte b = tga.readByte();
#ifdef SCUMM_LITTLE_ENDIAN
*data++ = r;
*data++ = g;
*data++ = b;
#else
*data++ = b;
*data++ = g;
*data++ = r;
#endif
count--;
}
} else if (pixelDepth == 16 && imageType == TYPE_RLE_TRUECOLOR) {
while (rleCount-- > 0) {
*((uint16 *)data) = tga.readUint16LE();
data += 2;
count--;
}
} else if (pixelDepth == 8 && imageType == TYPE_RLE_BW) {
while (rleCount-- > 0) {
byte color = tga.readByte();
*data++ = color;
*data++ = color;
*data++ = color;
*data++ = color;
count--;
}
} else if (pixelDepth == 8 && imageType == TYPE_RLE_CMAP) {
while (rleCount-- > 0) {
byte index = tga.readByte();
*data++ = index;
count--;
}
} else {
warning("Unhandled pixel-depth for image-type 10");
return false;
}
} else {
warning("Unknown header for RLE-packet %d", type);
return false;
}
}
} else {
return false;
}
// If it's a bottom origin image, we need to vertically flip the image
if (!_originTop) {
byte *tempLine = new byte[_surface.pitch];
byte *line1 = (byte *)_surface.getBasePtr(0, 0);
byte *line2 = (byte *)_surface.getBasePtr(0, _surface.h - 1);
for (int y = 0; y < (_surface.h / 2); ++y, line1 += _surface.pitch, line2 -= _surface.pitch) {
Common::copy(line1, line1 + _surface.pitch, tempLine);
Common::copy(line2, line2 + _surface.pitch, line1);
Common::copy(tempLine, tempLine + _surface.pitch, line2);
}
delete[] tempLine;
}
return true;
}
} // End of namespace Image