mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 21:59:17 +00:00
460 lines
12 KiB
C++
460 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 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 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
|