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