1 //-----------------------------------------------------------------------------
2 //
3 // ImageLib Sources
4 // Copyright (C) 2000-2009 by Denton Woods
5 // Last modified: 03/07/2009
6 //
7 // Filename: src-IL/src/il_pnm.c
8 //
9 // Description: Reads/writes to/from pbm/pgm/ppm formats (enough slashes? =)
10 //
11 //-----------------------------------------------------------------------------
12
13
14
15 #include "il_internal.h"
16 #ifndef IL_NO_PNM
17 #include "il_pnm.h"
18 #include <limits.h> // for maximum values
19 #include <ctype.h>
20 #include "il_manip.h"
21 #include "il_bits.h"
22
23 // According to the ppm specs, it's 70, but PSP
24 // likes to output longer lines.
25 #define MAX_BUFFER 180
26 static ILbyte LineBuffer[MAX_BUFFER];
27 static ILbyte SmallBuff[MAX_BUFFER];
28
29 // Can't read direct bits from a lump yet
30 ILboolean IsLump = IL_FALSE;
31
32
33 //! Checks if the file specified in FileName is a valid .pnm file.
ilIsValidPnm(ILconst_string FileName)34 ILboolean ilIsValidPnm(ILconst_string FileName)
35 {
36 ILHANDLE PnmFile;
37 ILboolean bPnm = IL_FALSE;
38
39 if ( !iCheckExtension(FileName, IL_TEXT("pbm"))
40 && !iCheckExtension(FileName, IL_TEXT("pgm"))
41 && !iCheckExtension(FileName, IL_TEXT("ppm"))
42 && !iCheckExtension(FileName, IL_TEXT("pnm"))) {
43 ilSetError(IL_INVALID_EXTENSION);
44 return bPnm;
45 }
46
47 PnmFile = iopenr(FileName);
48 if (PnmFile == NULL) {
49 ilSetError(IL_COULD_NOT_OPEN_FILE);
50 return bPnm;
51 }
52
53 bPnm = ilIsValidPnmF(PnmFile);
54 icloser(PnmFile);
55
56 return bPnm;
57 }
58
59
60 //! Checks if the ILHANDLE contains a valid .pnm file at the current position.
ilIsValidPnmF(ILHANDLE File)61 ILboolean ilIsValidPnmF(ILHANDLE File)
62 {
63 ILuint FirstPos;
64 ILboolean bRet;
65
66 iSetInputFile(File);
67 FirstPos = itell();
68 bRet = iIsValidPnm();
69 iseek(FirstPos, IL_SEEK_SET);
70
71 return bRet;
72 }
73
74
75 //! Checks if Lump is a valid .pnm lump.
ilIsValidPnmL(const void * Lump,ILuint Size)76 ILboolean ilIsValidPnmL(const void *Lump, ILuint Size)
77 {
78 iSetInputLump(Lump, Size);
79 return iIsValidPnm();
80 }
81
82
83 // Internal function to get the header and check it.
iIsValidPnm()84 ILboolean iIsValidPnm()
85 {
86 char Head[2];
87 ILint Read;
88
89 Read = iread(Head, 1, 2);
90 iseek(-Read, IL_SEEK_CUR); // Go ahead and restore to previous state
91 if (Read != 2)
92 return IL_FALSE;
93
94 return iCheckPnm(Head);
95 }
96
97
98 // Internal function used to check if the HEADER is a valid .pnm header.
iCheckPnm(char Header[2])99 ILboolean iCheckPnm(char Header[2])
100 {
101 if (Header[0] != 'P')
102 return IL_FALSE;
103 switch (Header[1])
104 {
105 case '1':
106 case '2':
107 case '3':
108 case '4':
109 case '5':
110 case '6':
111 return IL_TRUE;
112 }
113
114 return IL_FALSE;
115 }
116
117
118 // Reads a file
ilLoadPnm(ILconst_string FileName)119 ILboolean ilLoadPnm(ILconst_string FileName)
120 {
121 ILHANDLE PnmFile;
122 ILboolean bPnm = IL_FALSE;
123
124 PnmFile = iopenr(FileName);
125 if (PnmFile == NULL) {
126 ilSetError(IL_COULD_NOT_OPEN_FILE);
127 return bPnm;
128 }
129
130 bPnm = ilLoadPnmF(PnmFile);
131 icloser(PnmFile);
132
133 return bPnm;
134 }
135
136
137 // Reads an already-opened file
ilLoadPnmF(ILHANDLE File)138 ILboolean ilLoadPnmF(ILHANDLE File)
139 {
140 ILuint FirstPos;
141 ILboolean bRet;
142
143 iSetInputFile(File);
144 FirstPos = itell();
145 bRet = iLoadPnmInternal();
146 iseek(FirstPos, IL_SEEK_SET);
147
148 return bRet;
149 }
150
151
152 // Reads from a memory "lump"
ilLoadPnmL(const void * Lump,ILuint Size)153 ILboolean ilLoadPnmL(const void *Lump, ILuint Size)
154 {
155 iSetInputLump(Lump, Size);
156 return iLoadPnmInternal();
157 }
158
159
160 // Load either a pgm or a ppm
iLoadPnmInternal()161 ILboolean iLoadPnmInternal()
162 {
163 ILimage *PmImage = NULL;
164 PPMINFO Info;
165 // ILuint LineInc = 0, SmallInc = 0;
166
167 Info.Type = 0;
168
169 if (iCurImage == NULL) {
170 ilSetError(IL_ILLEGAL_OPERATION);
171 return IL_FALSE;
172 }
173
174 // Find out what type of pgm/ppm this is
175 if (iGetWord(IL_FALSE) == IL_FALSE)
176 return IL_FALSE;
177
178 if (SmallBuff[0] != 'P') {
179 ilSetError(IL_INVALID_FILE_HEADER);
180 return IL_FALSE;
181 }
182
183 switch( SmallBuff[1] ) {
184 case '1':
185 Info.Type = IL_PBM_ASCII;
186 break;
187 case '2':
188 Info.Type = IL_PGM_ASCII;
189 break;
190 case '3':
191 Info.Type = IL_PPM_ASCII;
192 break;
193 case '4':
194 Info.Type = IL_PBM_BINARY;
195 if (IsLump) {
196 ilSetError(IL_FORMAT_NOT_SUPPORTED);
197 return IL_FALSE;
198 }
199 break;
200 case '5':
201 Info.Type = IL_PGM_BINARY;
202 break;
203 case '6':
204 Info.Type = IL_PPM_BINARY;
205 break;
206 default:
207 ilSetError(IL_INVALID_FILE_HEADER);
208 return IL_FALSE;
209 }
210
211 // Retrieve the width and height
212 if (iGetWord(IL_FALSE) == IL_FALSE)
213 return IL_FALSE;
214 Info.Width = atoi((const char*)SmallBuff);
215 if (Info.Width == 0) {
216 ilSetError(IL_INVALID_FILE_HEADER);
217 return IL_FALSE;
218 }
219
220 if (iGetWord(IL_FALSE) == IL_FALSE)
221 return IL_FALSE;
222 Info.Height = atoi((const char*)SmallBuff);
223 if (Info.Height == 0) {
224 ilSetError(IL_INVALID_FILE_HEADER);
225 return IL_FALSE;
226 }
227
228 // Retrieve the maximum colour component value
229 if (Info.Type != IL_PBM_ASCII && Info.Type != IL_PBM_BINARY) {
230 if (iGetWord(IL_TRUE) == IL_FALSE)
231 return IL_FALSE;
232 if ((Info.MaxColour = atoi((const char*)SmallBuff)) == 0) {
233 ilSetError(IL_INVALID_FILE_HEADER);
234 return IL_FALSE;
235 }
236 } else {
237 Info.MaxColour = 1;
238 }
239
240 if (Info.Type == IL_PBM_ASCII || Info.Type == IL_PBM_BINARY ||
241 Info.Type == IL_PGM_ASCII || Info.Type == IL_PGM_BINARY) {
242 if (Info.Type == IL_PGM_ASCII) {
243 Info.Bpp = Info.MaxColour < 256 ? 1 : 2;
244 } else {
245 Info.Bpp = 1;
246 }
247 } else {
248 Info.Bpp = 3;
249 }
250
251 switch (Info.Type) {
252 case IL_PBM_ASCII:
253 case IL_PGM_ASCII:
254 case IL_PPM_ASCII:
255 PmImage = ilReadAsciiPpm(&Info);
256 break;
257 case IL_PBM_BINARY:
258 PmImage = ilReadBitPbm(&Info);
259 break;
260 case IL_PGM_BINARY:
261 case IL_PPM_BINARY:
262 PmImage = ilReadBinaryPpm(&Info);
263 break;
264 default:
265 return IL_FALSE;
266 }
267
268 if (PmImage == NULL) {
269 iCurImage->Format = ilGetFormatBpp(iCurImage->Bpp);
270 ilSetError(IL_FILE_READ_ERROR);
271 return IL_FALSE;
272 }
273
274 // Is this conversion needed? Just 0's and 1's shows up as all black
275 if (Info.Type == IL_PBM_ASCII) {
276 PbmMaximize(PmImage);
277 }
278
279 if (Info.MaxColour > 255)
280 PmImage->Type = IL_UNSIGNED_SHORT;
281 PmImage->Origin = IL_ORIGIN_UPPER_LEFT;
282 if (Info.Type == IL_PBM_ASCII || Info.Type == IL_PBM_BINARY ||
283 Info.Type == IL_PGM_ASCII || Info.Type == IL_PGM_BINARY)
284 PmImage->Format = IL_LUMINANCE;
285 else
286 PmImage->Format = IL_RGB;
287 PmImage->Origin = IL_ORIGIN_UPPER_LEFT;
288
289 if (PmImage == NULL)
290 return IL_FALSE;
291 return ilFixImage();
292 }
293
294
295
ilReadAsciiPpm(PPMINFO * Info)296 ILimage *ilReadAsciiPpm(PPMINFO *Info)
297 {
298 ILint LineInc = 0, SmallInc = 0, DataInc = 0, Size;
299 // ILint BytesRead = 0;
300
301 if (Info->MaxColour > 255)
302 Info->Bpp *= 2;
303
304 Size = Info->Width * Info->Height * Info->Bpp;
305
306 if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) {
307 return IL_FALSE;
308 }
309 iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
310 if (Info->MaxColour > 255)
311 iCurImage->Type = IL_UNSIGNED_SHORT;
312
313 while (DataInc < Size) { // && !feof(File)) {
314 LineInc = 0;
315
316 if (iFgets((char *)LineBuffer, MAX_BUFFER) == NULL) {
317 //ilSetError(IL_ILLEGAL_FILE_VALUE);
318 //return NULL;
319 //return iCurImage;
320 break;
321 }
322 if (LineBuffer[0] == '#') { // Comment
323 continue;
324 }
325
326 while ((LineBuffer[LineInc] != NUL) && (LineBuffer[LineInc] != '\n')) {
327
328 SmallInc = 0;
329 while (!isalnum(LineBuffer[LineInc])) { // Skip any whitespace
330 LineInc++;
331 }
332 while (isalnum(LineBuffer[LineInc])) {
333 SmallBuff[SmallInc] = LineBuffer[LineInc];
334 SmallInc++;
335 LineInc++;
336 }
337 SmallBuff[SmallInc] = NUL;
338 iCurImage->Data[DataInc] = atoi((const char*)SmallBuff); // Convert from string to colour
339
340 // PSP likes to put whitespace at the end of lines...figures. =/
341 while (!isalnum(LineBuffer[LineInc]) && LineBuffer[LineInc] != NUL) { // Skip any whitespace
342 LineInc++;
343 }
344
345 // We should set some kind of state flag that enables this
346 //Image->Data[DataInc] *= (ILubyte)(255 / Info->MaxColour); // Scales to 0-255
347 if (Info->MaxColour > 255)
348 DataInc++;
349 DataInc++;
350 }
351 }
352
353 // If we read less than what we should have...
354 if (DataInc < Size) {
355 //ilCloseImage(iCurImage);
356 //ilSetCurImage(NULL);
357 ilSetError(IL_ILLEGAL_FILE_VALUE);
358 return NULL;
359 }
360
361 return iCurImage;
362 }
363
364
ilReadBinaryPpm(PPMINFO * Info)365 ILimage *ilReadBinaryPpm(PPMINFO *Info)
366 {
367 ILuint Size;
368
369 Size = Info->Width * Info->Height * Info->Bpp;
370
371 if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) {
372 return IL_FALSE;
373 }
374 iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
375
376 /* 4/3/2007 Dario Meloni
377 Here it seems we have eaten too much bytes and it is needed to fix
378 the starting point
379 works well on various images
380
381 No more need of this workaround. fixed iGetWord
382 iseek(0,IL_SEEK_END);
383 ILuint size = itell();
384 iseek(size-Size,IL_SEEK_SET);
385 */
386 if (iread(iCurImage->Data, 1, Size ) != Size) {
387 ilCloseImage(iCurImage);
388 return NULL;
389 }
390 return iCurImage;
391 }
392
393
ilReadBitPbm(PPMINFO * Info)394 ILimage *ilReadBitPbm(PPMINFO *Info)
395 {
396 ILuint m, j, x, CurrByte;
397
398 if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) {
399 return IL_FALSE;
400 }
401 iCurImage->Origin = IL_ORIGIN_UPPER_LEFT;
402
403 x = 0;
404 for (j = 0; j < iCurImage->SizeOfData;) {
405 CurrByte = igetc();
406 for (m = 128; m > 0 && x < Info->Width; m >>= 1, ++x, ++j) {
407 iCurImage->Data[j] = (CurrByte & m)?255:0;
408 }
409 if (x == Info->Width)
410 x = 0;
411 }
412
413 return iCurImage;
414 }
415
416
iGetWord(ILboolean final)417 ILboolean iGetWord(ILboolean final)
418 {
419 ILint WordPos = 0;
420 ILint Current = 0;
421 ILboolean Started = IL_FALSE;
422 ILboolean Looping = IL_TRUE;
423
424 if (ieof())
425 return IL_FALSE;
426
427 while (Looping) {
428 while ((Current = igetc()) != IL_EOF && Current != '\n' && Current != '#' && Current != ' ') {
429 if (WordPos >= MAX_BUFFER) // We have hit the maximum line length.
430 return IL_FALSE;
431
432 if (!isalnum(Current)) {
433 if (Started) {
434 Looping = IL_FALSE;
435 break;
436 }
437 continue;
438 }
439
440 if (Looping)
441 SmallBuff[WordPos++] = Current;
442 }
443 if (Current == IL_EOF)
444 return IL_FALSE;
445 SmallBuff[WordPos] = 0; // 08-17-2008 - was NULL, changed to avoid warning
446 if (final == IL_TRUE)
447 break;
448
449 if (!Looping)
450 break;
451
452 if (Current == '#') { // '#' is a comment...read until end of line
453 while ((Current = igetc()) != IL_EOF && Current != '\n');
454 }
455
456 // Get rid of any erroneous spaces
457 while ((Current = igetc()) != IL_EOF) {
458 if (Current != ' ')
459 break;
460 }
461 iseek(-1, IL_SEEK_CUR);
462
463 if (WordPos > 0)
464 break;
465 }
466
467 if (Current == -1 || WordPos == 0) {
468 ilSetError(IL_INVALID_FILE_HEADER);
469 return IL_FALSE;
470 }
471
472 return IL_TRUE;
473 }
474
475
476 ILstring FName = NULL;
477
478 //! Writes a Pnm file
ilSavePnm(const ILstring FileName)479 ILboolean ilSavePnm(const ILstring FileName)
480 {
481 ILHANDLE PnmFile;
482 ILuint PnmSize;
483
484 if (ilGetBoolean(IL_FILE_MODE) == IL_FALSE) {
485 if (iFileExists(FileName)) {
486 ilSetError(IL_FILE_ALREADY_EXISTS);
487 return IL_FALSE;
488 }
489 }
490
491 PnmFile = iopenw(FileName);
492 if (PnmFile == NULL) {
493 ilSetError(IL_COULD_NOT_OPEN_FILE);
494 return IL_FALSE;
495 }
496
497 PnmSize = ilSavePnmF(PnmFile);
498 iclosew(PnmFile);
499
500 if (PnmSize == 0)
501 return IL_FALSE;
502 return IL_TRUE;
503 }
504
505
506 //! Writes a Pnm to an already-opened file
ilSavePnmF(ILHANDLE File)507 ILuint ilSavePnmF(ILHANDLE File)
508 {
509 ILuint Pos;
510 iSetOutputFile(File);
511 Pos = itellw();
512 if (iSavePnmInternal() == IL_FALSE)
513 return 0; // Error occurred
514 return itellw() - Pos; // Return the number of bytes written.
515 }
516
517
518 //! Writes a Pnm to a memory "lump"
ilSavePnmL(void * Lump,ILuint Size)519 ILuint ilSavePnmL(void *Lump, ILuint Size)
520 {
521 ILuint Pos;
522 FName = NULL;
523 iSetOutputLump(Lump, Size);
524 Pos = itellw();
525 if (iSavePnmInternal() == IL_FALSE)
526 return 0; // Error occurred
527 return itellw() - Pos; // Return the number of bytes written.
528 }
529
530
531 // Internal function used to save the Pnm.
iSavePnmInternal()532 ILboolean iSavePnmInternal()
533 {
534 ILuint Bpp, MaxVal = UCHAR_MAX, i = 0, j, k;
535 ILenum Type = 0;
536 ILuint LinePos = 0; // Cannot exceed 70 for pnm's!
537 ILboolean Binary;
538 ILimage *TempImage;
539 ILubyte *TempData;
540
541 if (iCurImage == NULL) {
542 ilSetError(IL_ILLEGAL_OPERATION);
543 return IL_FALSE;
544 }
545
546 if (iCheckExtension(FName, IL_TEXT("pbm")))
547 Type = IL_PBM_ASCII;
548 else if (iCheckExtension(FName, IL_TEXT("pgm")))
549 Type = IL_PGM_ASCII;
550 else if (iCheckExtension(FName, IL_TEXT("ppm")))
551 Type = IL_PPM_ASCII;
552 else
553 Type = IL_PPM_ASCII;
554
555 /*if (!Type) {
556 ilSetError(IL_INVALID_EXTENSION);
557 return IL_FALSE;
558 }*/
559
560 if (iGetHint(IL_COMPRESSION_HINT) == IL_USE_COMPRESSION) {
561 Type += 3;
562 Binary = IL_TRUE;
563 }
564 else {
565 Binary = IL_FALSE;
566 }
567
568 if (iCurImage->Type == IL_UNSIGNED_BYTE) {
569 MaxVal = UCHAR_MAX;
570 }
571 else if (iCurImage->Type == IL_UNSIGNED_SHORT) {
572 MaxVal = USHRT_MAX;
573 }
574 else {
575 ilSetError(IL_FORMAT_NOT_SUPPORTED);
576 return IL_FALSE;
577 }
578 if (MaxVal > UCHAR_MAX && Type >= IL_PBM_BINARY) { // binary cannot be higher than 255
579 ilSetError(IL_FORMAT_NOT_SUPPORTED);
580 return IL_FALSE;
581 }
582
583 switch (Type)
584 {
585 case IL_PBM_ASCII:
586 Bpp = 1;
587 ilprintf("P1\n");
588 TempImage = iConvertImage(iCurImage, IL_LUMINANCE, IL_UNSIGNED_BYTE);
589 break;
590 //case IL_PBM_BINARY: // Don't want to mess with saving bits just yet...
591 //Bpp = 1;
592 //ilprintf("P4\n");
593 //break;
594 case IL_PBM_BINARY:
595 ilSetError(IL_FORMAT_NOT_SUPPORTED);
596 return IL_FALSE;
597 case IL_PGM_ASCII:
598 Bpp = 1;
599 ilprintf("P2\n");
600 TempImage = iConvertImage(iCurImage, IL_COLOUR_INDEX, IL_UNSIGNED_BYTE);
601 break;
602 case IL_PGM_BINARY:
603 Bpp = 1;
604 ilprintf("P5\n");
605 TempImage = iConvertImage(iCurImage, IL_COLOUR_INDEX, IL_UNSIGNED_BYTE);
606 break;
607 case IL_PPM_ASCII:
608 Bpp = 3;
609 ilprintf("P3\n");
610 TempImage = iConvertImage(iCurImage, IL_RGB, IL_UNSIGNED_BYTE);
611 break;
612 case IL_PPM_BINARY:
613 Bpp = 3;
614 ilprintf("P6\n");
615 TempImage = iConvertImage(iCurImage, IL_RGB, IL_UNSIGNED_BYTE);
616 break;
617 default:
618 ilSetError(IL_INTERNAL_ERROR);
619 return IL_FALSE;
620 }
621
622 if (TempImage == NULL)
623 return IL_FALSE;
624
625 if (Bpp != TempImage->Bpp) {
626 ilSetError(IL_INVALID_CONVERSION);
627 return IL_FALSE;
628 }
629
630 if (TempImage->Origin != IL_ORIGIN_UPPER_LEFT) {
631 TempData = iGetFlipped(TempImage);
632 if (TempData == NULL) {
633 ilCloseImage(TempImage);
634 return IL_FALSE;
635 }
636 }
637 else {
638 TempData = TempImage->Data;
639 }
640
641 ilprintf("%d %d\n", TempImage->Width, TempImage->Height);
642 if (Type != IL_PBM_BINARY && Type != IL_PBM_ASCII) // not needed for .pbm's (only 0 and 1)
643 ilprintf("%d\n", MaxVal);
644
645 while (i < TempImage->SizeOfPlane) {
646 for (j = 0; j < Bpp; j++) {
647 if (Binary) {
648 if (Type == IL_PBM_BINARY) {
649 iputc((ILubyte)(TempData[i] > 127 ? 1 : 0));
650 }
651 else {
652 iputc(TempData[i]);
653 }
654 }
655 else {
656 if (TempImage->Type == IL_UNSIGNED_BYTE)
657 k = TempData[i];
658 else // IL_UNSIGNED_SHORT
659 k = *((ILushort*)TempData + i);
660 if (Type == IL_PBM_ASCII) {
661 LinePos += ilprintf("%d ", TempData[i] > 127 ? 1 : 0);
662 }
663 else {
664 LinePos += ilprintf("%d ", TempData[i]);
665 }
666 }
667
668 if (TempImage->Type == IL_UNSIGNED_SHORT)
669 i++;
670 i++;
671 }
672
673 if (LinePos > 65) { // Just a good number =]
674 ilprintf("\n");
675 LinePos = 0;
676 }
677 }
678
679 if (TempImage->Origin != IL_ORIGIN_UPPER_LEFT)
680 ifree(TempData);
681 ilCloseImage(TempImage);
682
683 return IL_TRUE;
684 }
685
686
687 // Converts a .pbm to something viewable.
PbmMaximize(ILimage * Image)688 void PbmMaximize(ILimage *Image)
689 {
690 ILuint i = 0;
691 for (i = 0; i < Image->SizeOfPlane; i++)
692 if (Image->Data[i] == 1)
693 Image->Data[i] = 0xFF;
694 return;
695 }
696
697
698 #endif//IL_NO_PNM
699