mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
1413a8318c
The RGB planes were flipped and TESTBED image decoder tests revealed this bug. AGS is the only engine that allows for 24bpp PCX image, but an example game is currently unknown.
208 lines
5.5 KiB
C++
208 lines
5.5 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/>.
|
|
*
|
|
*/
|
|
|
|
#include "image/pcx.h"
|
|
|
|
#include "common/stream.h"
|
|
#include "common/textconsole.h"
|
|
#include "graphics/pixelformat.h"
|
|
#include "graphics/surface.h"
|
|
|
|
/**
|
|
* Based on the PCX specs:
|
|
* http://www.fileformat.info/format/pcx/spec/a10e75307b3a4cc49c3bbe6db4c41fa2/view.htm
|
|
* and the PCX decoder of FFmpeg (libavcodec/pcx.c):
|
|
* https://git.ffmpeg.org/gitweb/ffmpeg.git/blob/HEAD:/libavcodec/pcx.c
|
|
*/
|
|
|
|
namespace Image {
|
|
|
|
PCXDecoder::PCXDecoder(): _surface(nullptr), _palette(0) {
|
|
}
|
|
|
|
PCXDecoder::~PCXDecoder() {
|
|
destroy();
|
|
}
|
|
|
|
void PCXDecoder::destroy() {
|
|
if (_surface) {
|
|
_surface->free();
|
|
delete _surface;
|
|
_surface = nullptr;
|
|
}
|
|
|
|
_palette.clear();
|
|
}
|
|
|
|
bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) {
|
|
destroy();
|
|
|
|
if (stream.readByte() != 0x0a) // ZSoft PCX
|
|
return false;
|
|
|
|
byte version = stream.readByte(); // 0 - 5
|
|
if (version > 5)
|
|
return false;
|
|
|
|
bool compressed = stream.readByte(); // encoding, 1 = run length encoding
|
|
byte bitsPerPixel = stream.readByte(); // 1, 2, 4 or 8
|
|
|
|
// Window
|
|
uint16 xMin = stream.readUint16LE();
|
|
uint16 yMin = stream.readUint16LE();
|
|
uint16 xMax = stream.readUint16LE();
|
|
uint16 yMax = stream.readUint16LE();
|
|
|
|
uint16 width = xMax - xMin + 1;
|
|
uint16 height = yMax - yMin + 1;
|
|
|
|
if (xMax < xMin || yMax < yMin) {
|
|
warning("Invalid PCX image dimensions");
|
|
return false;
|
|
}
|
|
|
|
stream.skip(4); // HDpi, VDpi
|
|
|
|
// Read the EGA palette (colormap)
|
|
_palette.resize(16, false);
|
|
for (uint16 i = 0; i < 16; i++) {
|
|
byte r = stream.readByte();
|
|
byte g = stream.readByte();
|
|
byte b = stream.readByte();
|
|
_palette.set(i, r, g, b);
|
|
}
|
|
|
|
if (stream.readByte() != 0) // reserved, should be set to 0
|
|
return false;
|
|
|
|
byte nPlanes = stream.readByte();
|
|
uint16 bytesPerLine = stream.readUint16LE();
|
|
uint16 bytesPerscanLine = nPlanes * bytesPerLine;
|
|
|
|
if (bytesPerscanLine < width * bitsPerPixel * nPlanes / 8) {
|
|
warning("PCX data is corrupted");
|
|
return false;
|
|
}
|
|
|
|
stream.skip(60); // PaletteInfo, HscreenSize, VscreenSize, Filler
|
|
|
|
_surface = new Graphics::Surface();
|
|
|
|
byte *scanLine = new byte[bytesPerscanLine];
|
|
byte *dst;
|
|
int x, y;
|
|
|
|
if (nPlanes == 3 && bitsPerPixel == 8) { // 24bpp
|
|
Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
|
|
_surface->create(width, height, format);
|
|
dst = (byte *)_surface->getPixels();
|
|
_palette.clear();
|
|
|
|
for (y = 0; y < height; y++) {
|
|
decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
|
|
|
|
for (x = 0; x < width; x++) {
|
|
byte r = scanLine[x];
|
|
byte g = scanLine[x + bytesPerLine];
|
|
byte b = scanLine[x + (bytesPerLine << 1)];
|
|
uint32 color = format.RGBToColor(r, g, b);
|
|
|
|
*((uint32 *)dst) = color;
|
|
dst += format.bytesPerPixel;
|
|
}
|
|
}
|
|
} else if (nPlanes == 1 && bitsPerPixel == 8) { // 8bpp indexed
|
|
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
dst = (byte *)_surface->getPixels();
|
|
_palette.resize(16, true);
|
|
|
|
for (y = 0; y < height; y++, dst += _surface->pitch) {
|
|
decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
|
|
memcpy(dst, scanLine, width);
|
|
}
|
|
|
|
if (version == 5) {
|
|
if (stream.readByte() != 12) {
|
|
warning("Expected a palette after the PCX image data");
|
|
delete[] scanLine;
|
|
return false;
|
|
}
|
|
|
|
// Read the VGA palette
|
|
_palette.resize(256, false);
|
|
for (uint16 i = 0; i < 256; i++) {
|
|
byte r = stream.readByte();
|
|
byte g = stream.readByte();
|
|
byte b = stream.readByte();
|
|
_palette.set(i, r, g, b);
|
|
}
|
|
}
|
|
} else if ((nPlanes == 2 || nPlanes == 3 || nPlanes == 4) && bitsPerPixel == 1) { // planar, 4, 8 or 16 colors
|
|
_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
|
|
dst = (byte *)_surface->getPixels();
|
|
_palette.resize(16, true);
|
|
|
|
for (y = 0; y < height; y++, dst += _surface->pitch) {
|
|
decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
|
|
|
|
for (x = 0; x < width; x++) {
|
|
int m = 0x80 >> (x & 7), v = 0;
|
|
for (int i = nPlanes - 1; i >= 0; i--) {
|
|
v <<= 1;
|
|
v += (scanLine[i * bytesPerLine + (x >> 3)] & m) == 0 ? 0 : 1;
|
|
}
|
|
dst[x] = v;
|
|
}
|
|
}
|
|
} else {
|
|
// Known unsupported case: 1 plane and bpp < 8 (1, 2 or 4)
|
|
warning("Invalid PCX file (%d planes, %d bpp)", nPlanes, bitsPerPixel);
|
|
delete[] scanLine;
|
|
return false;
|
|
}
|
|
|
|
delete[] scanLine;
|
|
|
|
return true;
|
|
}
|
|
|
|
void PCXDecoder::decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerscanLine, bool compressed) {
|
|
uint32 i = 0;
|
|
byte run, value;
|
|
|
|
if (compressed) {
|
|
while (i < bytesPerscanLine) {
|
|
run = 1;
|
|
value = stream.readByte();
|
|
if (value >= 0xc0) {
|
|
run = value & 0x3f;
|
|
value = stream.readByte();
|
|
}
|
|
while (i < bytesPerscanLine && run--)
|
|
dst[i++] = value;
|
|
}
|
|
} else {
|
|
stream.read(dst, bytesPerscanLine);
|
|
}
|
|
}
|
|
|
|
} // End of namespace Image
|