1/*****************************************************************************/
2/* */
3/* pcx.c */
4/* */
5/* Read PCX files */
6/* */
7/* */
8/* */
9/* (C) 2012, Ullrich von Bassewitz */
10/* Roemerstrasse 52 */
11/* D-70794 Filderstadt */
12/* EMail: uz@cc65.org */
13/* */
14/* */
15/* This software is provided 'as-is', without any expressed or implied */
16/* warranty. In no event will the authors be held liable for any damages */
17/* arising from the use of this software. */
18/* */
19/* Permission is granted to anyone to use this software for any purpose, */
20/* including commercial applications, and to alter it and redistribute it */
21/* freely, subject to the following restrictions: */
22/* */
23/* 1. The origin of this software must not be misrepresented; you must not */
24/* claim that you wrote the original software. If you use this software */
25/* in a product, an acknowledgment in the product documentation would be */
26/* appreciated but is not required. */
27/* 2. Altered source versions must be plainly marked as such, and must not */
28/* be misrepresented as being the original software. */
29/* 3. This notice may not be removed or altered from any source */
30/* distribution. */
31/* */
32/*****************************************************************************/
33
34
35
36#include <errno.h>
37#include <stdio.h>
38#include <string.h>
39
40/* common */
41#include "print.h"
42#include "xmalloc.h"
43
44/* sp65 */
45#include "attr.h"
46#include "error.h"
47#include "fileio.h"
48#include "pcx.h"
49
50
51
52/*****************************************************************************/
53/* Macros */
54/*****************************************************************************/
55
56
57
58/* Some PCX constants */
59#define PCX_MAGIC_ID 0x0A
60#define PCX_MAX_PLANES 4
61
62/* A raw PCX header is just a block of bytes */
63typedef unsigned char RawPCXHeader[128];
64
65/* Structured PCX header */
66typedef struct PCXHeader PCXHeader;
67struct PCXHeader {
68 unsigned Id;
69 unsigned FileVersion;
70 unsigned Compressed;
71 unsigned BPP;
72 unsigned XMin;
73 unsigned YMin;
74 unsigned XMax;
75 unsigned YMax;
76 unsigned XDPI;
77 unsigned YDPI;
78 unsigned Planes;
79 unsigned BytesPerPlane;
80 unsigned PalInfo;
81 unsigned ScreenWidth;
82 unsigned ScreenHeight;
83
84 /* Calculated data */
85 unsigned Width;
86 unsigned Height;
87};
88
89/* Read a little endian word from a byte array at offset O */
90#define WORD(H, O) ((H)[O] | ((H)[O+1] << 8))
91
92
93
94/*****************************************************************************/
95/* Code */
96/*****************************************************************************/
97
98
99
100static PCXHeader* NewPCXHeader (void)
101/* Allocate a new PCX header and return it */
102{
103 /* No initialization here */
104 return xmalloc (sizeof (PCXHeader));
105}
106
107
108
109static void FreePCXHeader (PCXHeader* H)
110/* Free a PCX header structure */
111{
112 xfree (H);
113}
114
115
116
117static PCXHeader* ReadPCXHeader (FILE* F, const char* Name)
118/* Read a structured PCX header from the given file and return it */
119{
120 RawPCXHeader H;
121
122 /* Allocate a new PCXHeader structure */
123 PCXHeader* P = NewPCXHeader ();
124
125 /* Read the raw header */
126 ReadData (F, H, sizeof (H));
127
128 /* Convert the data into structured form */
129 P->Id = H[0];
130 P->FileVersion = H[1];
131 P->Compressed = H[2];
132 P->BPP = H[3];
133 P->XMin = WORD (H, 4);
134 P->YMin = WORD (H, 6);
135 P->XMax = WORD (H, 8);
136 P->YMax = WORD (H, 10);
137 P->XDPI = WORD (H, 12);
138 P->YDPI = WORD (H, 14);
139 P->Planes = H[65];
140 P->BytesPerPlane = WORD (H, 66);
141 P->PalInfo = WORD (H, 68);
142 P->ScreenWidth = WORD (H, 70);
143 P->ScreenHeight = WORD (H, 72);
144 P->Width = P->XMax - P->XMin + 1;
145 P->Height = P->YMax - P->YMin + 1;
146
147 /* Check the header data */
148 if (P->Id != PCX_MAGIC_ID || P->FileVersion == 1 || P->FileVersion > 5) {
149 Error ("'%s' is not a PCX file", Name);
150 }
151 if (P->Compressed > 1) {
152 Error ("Unsupported compression (%d) in PCX file '%s'",
153 P->Compressed, Name);
154 }
155 /* We support:
156 ** - one plane with either 1 or 8 bits per pixel
157 ** - three planes with 8 bits per pixel
158 ** - four planes with 8 bits per pixel (does this exist?)
159 */
160 if (!((P->BPP == 1 && P->Planes == 1) ||
161 (P->BPP == 8 && (P->Planes == 1 || P->Planes == 3 || P->Planes == 4)))) {
162 /* We could support others, but currently we don't */
163 Error ("Unsupported PCX format: %u planes, %u bpp in PCX file '%s'",
164 P->Planes, P->BPP, Name);
165 }
166 if (P->PalInfo != 1 && P->PalInfo != 2) {
167 Error ("Unsupported palette info (%u) in PCX file '%s'",
168 P->PalInfo, Name);
169 }
170 if (!ValidBitmapSize (P->Width, P->Height)) {
171 Error ("PCX file '%s' has an unsupported size (w=%u, h=%d)",
172 Name, P->Width, P->Height);
173 }
174
175 /* Return the structured header */
176 return P;
177}
178
179
180
181static void DumpPCXHeader (const PCXHeader* P, const char* Name)
182/* Dump the header of the PCX file in readable form to stdout */
183{
184 printf ("File name: %s\n", Name);
185 printf ("PCX Version: ");
186 switch (P->FileVersion) {
187 case 0: puts ("2.5"); break;
188 case 2: puts ("2.8 with palette"); break;
189 case 3: puts ("2.8 without palette"); break;
190 case 4: puts ("PCX for Windows without palette"); break;
191 case 5: puts ("3.0"); break;
192 }
193 printf ("Image type: %s\n", P->PalInfo? "color" : "grayscale");
194 printf ("Compression: %s\n", P->Compressed? "RLE" : "None");
195 printf ("Structure: %u planes of %u bits\n", P->Planes, P->BPP);
196 printf ("Bounding box: [%u/%u - %u/%u]\n", P->XMin, P->YMin, P->XMax, P->YMax);
197 printf ("Resolution: %u/%u DPI\n", P->XDPI, P->YDPI);
198 printf ("Screen size: %u/%u\n", P->ScreenWidth, P->ScreenHeight);
199 printf ("Bytes per plane: %u\n", P->BytesPerPlane);
200}
201
202
203
204static void ReadPlane (FILE* F, PCXHeader* P, unsigned char* L)
205/* Read one (possibly compressed) plane from the file */
206{
207 if (P->Compressed) {
208
209 /* Uncompress RLE data */
210 unsigned Remaining = P->Width;
211 while (Remaining) {
212
213 unsigned char C;
214
215 /* Read the next byte */
216 unsigned char B = Read8 (F);
217
218 /* Check for a run length */
219 if ((B & 0xC0) == 0xC0) {
220 C = (B & 0x3F); /* Count */
221 B = Read8 (F); /* Value */
222 } else {
223 C = 1;
224 }
225
226 /* Write the data to the buffer */
227 if (C > Remaining) {
228 C = Remaining;
229 }
230 memset (L, B, C);
231
232 /* Bump counters */
233 L += C;
234 Remaining -= C;
235
236 }
237 } else {
238
239 /* Just read one line */
240 ReadData (F, L, P->Width);
241
242 }
243}
244
245
246
247Bitmap* ReadPCXFile (const Collection* A)
248/* Read a bitmap from a PCX file */
249{
250 PCXHeader* P;
251 Bitmap* B;
252 unsigned char* L;
253 Pixel* Px;
254 unsigned MaxIdx = 0;
255 unsigned X, Y;
256
257
258 /* Get the file name */
259 const char* Name = NeedAttrVal (A, "name", "read pcx file");
260
261 /* Open the file */
262 FILE* F = fopen (Name, "rb");
263 if (F == 0) {
264 Error ("Cannot open PCX file '%s': %s", Name, strerror (errno));
265 }
266
267 /* Read the PCX header */
268 P = ReadPCXHeader (F, Name);
269
270 /* Dump the header if requested */
271 if (Verbosity > 0) {
272 DumpPCXHeader (P, Name);
273 }
274
275 /* Create the bitmap */
276 B = NewBitmap (P->Width, P->Height);
277
278 /* Copy the name */
279 SB_CopyStr (&B->Name, Name);
280
281 /* Allocate memory for the scan line */
282 L = xmalloc (P->Width);
283
284 /* Read the pixel data */
285 Px = B->Data;
286 if (P->Planes == 1) {
287
288 /* This is either monochrome or indexed */
289 if (P->BPP == 1) {
290 /* Monochrome */
291 for (Y = 0, Px = B->Data; Y < P->Height; ++Y) {
292
293 unsigned I;
294 unsigned char Mask;
295
296 /* Read the plane */
297 ReadPlane (F, P, L);
298
299 /* Create pixels */
300 for (X = 0, I = 0, Mask = 0x01; X < P->Width; ++Px) {
301 Px->Index = (L[I] & Mask) != 0;
302 if (Mask == 0x80) {
303 Mask = 0x01;
304 ++I;
305 } else {
306 Mask <<= 1;
307 }
308 }
309
310 }
311 } else {
312 /* One plane with 8bpp is indexed */
313 for (Y = 0, Px = B->Data; Y < P->Height; ++Y) {
314
315 /* Read the plane */
316 ReadPlane (F, P, L);
317
318 /* Create pixels */
319 for (X = 0; X < P->Width; ++X, ++Px) {
320 if (L[X] > MaxIdx) {
321 MaxIdx = L[X];
322 }
323 Px->Index = L[X];
324 }
325 }
326 }
327
328 /* One plane means we have a palette which is either part of the header
329 ** or follows.
330 */
331 if (P->PalInfo == 0) {
332
333 /* Create the monochrome palette */
334 B->Pal = NewMonochromePalette ();
335
336 } else {
337
338 unsigned Count;
339 unsigned I;
340 unsigned char Palette[256][3];
341 unsigned long EndPos;
342
343 /* Determine the current file position */
344 unsigned long CurPos = FileGetPos (F);
345
346 /* Seek to the end of the file */
347 (void) fseek (F, 0, SEEK_END);
348
349 /* Get this position */
350 EndPos = FileGetPos (F);
351
352 /* There's a palette if the old location is 769 bytes from the end */
353 if (EndPos - CurPos == sizeof (Palette) + 1) {
354
355 /* Seek back */
356 FileSetPos (F, CurPos);
357
358 /* Check for palette marker */
359 if (Read8 (F) != 0x0C) {
360 Error ("Invalid palette marker in PCX file '%s'", Name);
361 }
362
363 } else if (EndPos == CurPos) {
364
365 /* The palette is in the header */
366 FileSetPos (F, 16);
367
368 /* Check the maximum index for safety */
369 if (MaxIdx > 15) {
370 Error ("PCX file '%s' contains more than 16 indexed colors "
371 "but no extra palette", Name);
372 }
373
374 } else {
375 Error ("Error in PCX file '%s': %lu bytes at end of pixel data",
376 Name, EndPos - CurPos);
377 }
378
379 /* Read the palette. We will just read what we need. */
380 Count = MaxIdx + 1;
381 ReadData (F, Palette, Count * sizeof (Palette[0]));
382
383 /* Create the palette from the data */
384 B->Pal = NewPalette (Count);
385 for (I = 0; I < Count; ++I) {
386 B->Pal->Entries[I].R = Palette[I][0];
387 B->Pal->Entries[I].G = Palette[I][1];
388 B->Pal->Entries[I].B = Palette[I][2];
389 B->Pal->Entries[I].A = 0;
390 }
391
392 }
393
394 } else {
395
396 /* 3 or 4 planes are RGB or RGBA (don't know if this exists) */
397 for (Y = 0, Px = B->Data; Y < P->Height; ++Y) {
398
399 /* Read the R plane and move the data */
400 ReadPlane (F, P, L);
401 for (X = 0; X < P->Width; ++X, ++Px) {
402 Px->C.R = L[X];
403 }
404
405 /* Read the G plane and move the data */
406 ReadPlane (F, P, L);
407 for (X = 0; X < P->Width; ++X, ++Px) {
408 Px->C.G = L[X];
409 }
410
411 /* Read the B plane and move the data */
412 ReadPlane (F, P, L);
413 for (X = 0; X < P->Width; ++X, ++Px) {
414 Px->C.B = L[X];
415 }
416
417 /* Either read the A plane or clear it */
418 if (P->Planes == 4) {
419 ReadPlane (F, P, L);
420 for (X = 0; X < P->Width; ++X, ++Px) {
421 Px->C.A = L[X];
422 }
423 } else {
424 for (X = 0; X < P->Width; ++X, ++Px) {
425 Px->C.A = 0;
426 }
427 }
428 }
429 }
430
431 /* Close the file */
432 fclose (F);
433
434 /* Free memory for the scan line */
435 xfree (L);
436
437 /* Free the PCX header */
438 FreePCXHeader (P);
439
440 /* Return the bitmap */
441 return B;
442}
443