mirror of
https://github.com/scummvm/scummvm.git
synced 2026-05-21 05:40:43 +00:00
1162174448
Looks like the URL for this specification of the PCX format is becoming unstable on this site, lately -- let's just use a stable URL at web.archive.org instead. Also, when referencing the FFmpeg PCX decoder, link to an FFmpeg release tag that should be close to what was checked back when this was committed.
203 lines
5.4 KiB
C++
203 lines
5.4 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:
|
|
* https://web.archive.org/web/20151028182714/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/tags/n0.11.1:/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
|
|
_surface->create(width, height, Graphics::PixelFormat::createFormatRGB24());
|
|
dst = (byte *)_surface->getPixels();
|
|
_palette.clear();
|
|
|
|
for (y = 0; y < height; y++) {
|
|
decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
|
|
|
|
for (x = 0; x < width; x++) {
|
|
*dst++ = scanLine[x];
|
|
*dst++ = scanLine[x + bytesPerLine];
|
|
*dst++ = scanLine[x + (bytesPerLine << 1)];
|
|
}
|
|
}
|
|
} 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
|