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 [128]; |
64 | |
65 | /* Structured PCX header */ |
66 | typedef struct PCXHeader ; |
67 | struct { |
68 | unsigned ; |
69 | unsigned ; |
70 | unsigned ; |
71 | unsigned ; |
72 | unsigned ; |
73 | unsigned ; |
74 | unsigned ; |
75 | unsigned ; |
76 | unsigned ; |
77 | unsigned ; |
78 | unsigned ; |
79 | unsigned ; |
80 | unsigned ; |
81 | unsigned ; |
82 | unsigned ; |
83 | |
84 | /* Calculated data */ |
85 | unsigned ; |
86 | unsigned ; |
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* (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 (PCXHeader* H) |
110 | /* Free a PCX header structure */ |
111 | { |
112 | xfree (H); |
113 | } |
114 | |
115 | |
116 | |
117 | static PCXHeader* (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 (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 | |