]> git.sur5r.net Git - cc65/blob - src/sp65/pcx.c
Remove the bitmap type since it is not really needed and complicate things.
[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 "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 */
63 typedef unsigned char           RawPCXHeader[128];
64
65 /* Structured PCX header */
66 typedef struct PCXHeader PCXHeader;
67 struct 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
100 static 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
109 static void FreePCXHeader (PCXHeader* H)
110 /* Free a PCX header structure */
111 {
112     xfree (H);
113 }
114
115
116
117 static 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
181 static 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
204 static 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
247 Bitmap* 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
444
445