1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  */
22 
23 #include "image/pcx.h"
24 
25 #include "common/stream.h"
26 #include "common/textconsole.h"
27 #include "graphics/pixelformat.h"
28 #include "graphics/surface.h"
29 
30 /**
31  * Based on the PCX specs:
32  * http://www.fileformat.info/format/pcx/spec/a10e75307b3a4cc49c3bbe6db4c41fa2/view.htm
33  * and the PCX decoder of FFmpeg (libavcodec/pcx.c):
34  * http://git.videolan.org/?p=ffmpeg.git;a=blob;f=libavcodec/pcx.c
35  */
36 
37 namespace Image {
38 
PCXDecoder()39 PCXDecoder::PCXDecoder() {
40 	_surface = 0;
41 	_palette = 0;
42 	_paletteColorCount = 0;
43 }
44 
~PCXDecoder()45 PCXDecoder::~PCXDecoder() {
46 	destroy();
47 }
48 
destroy()49 void PCXDecoder::destroy() {
50 	if (_surface) {
51 		_surface->free();
52 		delete _surface;
53 		_surface = 0;
54 	}
55 
56 	delete[] _palette;
57 	_palette = 0;
58 	_paletteColorCount = 0;
59 }
60 
loadStream(Common::SeekableReadStream & stream)61 bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) {
62 	destroy();
63 
64 	if (stream.readByte() != 0x0a)	// ZSoft PCX
65 		return false;
66 
67 	byte version = stream.readByte();	// 0 - 5
68 	if (version > 5)
69 		return false;
70 
71 	bool compressed = stream.readByte(); // encoding, 1 = run length encoding
72 	byte bitsPerPixel = stream.readByte();	// 1, 2, 4 or 8
73 
74 	// Window
75 	uint16 xMin = stream.readUint16LE();
76 	uint16 yMin = stream.readUint16LE();
77 	uint16 xMax = stream.readUint16LE();
78 	uint16 yMax = stream.readUint16LE();
79 
80 	uint16 width  = xMax - xMin + 1;
81 	uint16 height = yMax - yMin + 1;
82 
83 	if (xMax < xMin || yMax < yMin) {
84 		warning("Invalid PCX image dimensions");
85 		return false;
86 	}
87 
88 	stream.skip(4);	// HDpi, VDpi
89 
90 	// Read the EGA palette (colormap)
91 	_palette = new byte[16 * 3];
92 	for (uint16 i = 0; i < 16; i++) {
93 		_palette[i * 3 + 0] = stream.readByte();
94 		_palette[i * 3 + 1] = stream.readByte();
95 		_palette[i * 3 + 2] = stream.readByte();
96 	}
97 
98 	if (stream.readByte() != 0)	// reserved, should be set to 0
99 		return false;
100 
101 	byte nPlanes = stream.readByte();
102 	uint16 bytesPerLine = stream.readUint16LE();
103 	uint16 bytesPerscanLine = nPlanes * bytesPerLine;
104 
105 	if (bytesPerscanLine < width * bitsPerPixel * nPlanes / 8) {
106 		warning("PCX data is corrupted");
107 		return false;
108 	}
109 
110 	stream.skip(60);	// PaletteInfo, HscreenSize, VscreenSize, Filler
111 
112 	_surface = new Graphics::Surface();
113 
114 	byte *scanLine = new byte[bytesPerscanLine];
115 	byte *dst;
116 	int x, y;
117 
118 	if (nPlanes == 3 && bitsPerPixel == 8) {	// 24bpp
119 		Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0);
120 		_surface->create(width, height, format);
121 		dst = (byte *)_surface->getPixels();
122 		_paletteColorCount = 0;
123 
124 		for (y = 0; y < height; y++) {
125 			decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
126 
127 			for (x = 0; x < width; x++) {
128 				byte b = scanLine[x];
129 				byte g = scanLine[x +  bytesPerLine];
130 				byte r = scanLine[x + (bytesPerLine << 1)];
131 				uint32 color = format.RGBToColor(r, g, b);
132 
133 				*((uint32 *)dst) = color;
134 				dst += format.bytesPerPixel;
135 			}
136 		}
137 	} else if (nPlanes == 1 && bitsPerPixel == 8) {	// 8bpp indexed
138 		_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
139 		dst = (byte *)_surface->getPixels();
140 		_paletteColorCount = 16;
141 
142 		for (y = 0; y < height; y++, dst += _surface->pitch) {
143 			decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
144 			memcpy(dst, scanLine, width);
145 		}
146 
147 		if (version == 5) {
148 			if (stream.readByte() != 12) {
149 				warning("Expected a palette after the PCX image data");
150 				delete[] scanLine;
151 				return false;
152 			}
153 
154 			// Read the VGA palette
155 			delete[] _palette;
156 			_palette = new byte[256 * 3];
157 			for (uint16 i = 0; i < 256; i++) {
158 				_palette[i * 3 + 0] = stream.readByte();
159 				_palette[i * 3 + 1] = stream.readByte();
160 				_palette[i * 3 + 2] = stream.readByte();
161 			}
162 
163 			_paletteColorCount = 256;
164 		}
165 	} else if ((nPlanes == 2 || nPlanes == 3 || nPlanes == 4) && bitsPerPixel == 1) {	// planar, 4, 8 or 16 colors
166 		_surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
167 		dst = (byte *)_surface->getPixels();
168 		_paletteColorCount = 16;
169 
170 		for (y = 0; y < height; y++, dst += _surface->pitch) {
171 			decodeRLE(stream, scanLine, bytesPerscanLine, compressed);
172 
173 			for (x = 0; x < width; x++) {
174 				int m = 0x80 >> (x & 7), v = 0;
175 				for (int i = nPlanes - 1; i >= 0; i--) {
176 					v <<= 1;
177 					v  += (scanLine[i * bytesPerLine + (x >> 3)] & m) == 0 ? 0 : 1;
178 				}
179 				dst[x] = v;
180 			}
181 		}
182 	} else {
183 		// Known unsupported case: 1 plane and bpp < 8 (1, 2 or 4)
184 		warning("Invalid PCX file (%d planes, %d bpp)", nPlanes, bitsPerPixel);
185 		delete[] scanLine;
186 		return false;
187 	}
188 
189 	delete[] scanLine;
190 
191 	return true;
192 }
193 
decodeRLE(Common::SeekableReadStream & stream,byte * dst,uint32 bytesPerscanLine,bool compressed)194 void PCXDecoder::decodeRLE(Common::SeekableReadStream &stream, byte *dst, uint32 bytesPerscanLine, bool compressed) {
195 	uint32 i = 0;
196 	byte run, value;
197 
198 	if (compressed) {
199 		while (i < bytesPerscanLine) {
200 			run = 1;
201 			value = stream.readByte();
202 			if (value >= 0xc0) {
203 				run = value & 0x3f;
204 				value = stream.readByte();
205 			}
206 			while (i < bytesPerscanLine && run--)
207 				dst[i++] = value;
208 		}
209 	} else {
210 		stream.read(dst, bytesPerscanLine);
211 	}
212 }
213 
214 } // End of namespace Image
215