//----------------------------------------------------------------------------- // // ImageLib Sources // Copyright (C) 2000-2017 by Denton Woods // Last modified: 03/07/2009 // // Filename: src-IL/src/il_pnm.cpp // // Description: Reads/writes to/from pbm/pgm/ppm formats (enough slashes? =) // //----------------------------------------------------------------------------- #include "il_internal.h" #ifndef IL_NO_PNM #include "il_pnm.h" #include // for maximum values #include #include #include "il_bits.h" using namespace std; // According to the ppm specs, it's 70, but PSP // likes to output longer lines. #define MAX_BUFFER 180 //@TODO: Get rid of these globals static ILbyte LineBuffer[MAX_BUFFER]; static ILbyte SmallBuff[MAX_BUFFER]; // Can't read direct bits from a lump yet ILboolean IsLump = IL_FALSE; //! Checks if the file specified in FileName is a valid .pnm file. ILboolean ilIsValidPnm(ILconst_string FileName) { ILHANDLE PnmFile; ILboolean bPnm = IL_FALSE; if ( !iCheckExtension(FileName, IL_TEXT("pbm")) && !iCheckExtension(FileName, IL_TEXT("pgm")) && !iCheckExtension(FileName, IL_TEXT("ppm")) && !iCheckExtension(FileName, IL_TEXT("pnm")) && !iCheckExtension(FileName, IL_TEXT("pam"))) { ilSetError(IL_INVALID_EXTENSION); return bPnm; } PnmFile = iopenr(FileName); if (PnmFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bPnm; } bPnm = ilIsValidPnmF(PnmFile); icloser(PnmFile); return bPnm; } //! Checks if the ILHANDLE contains a valid .pnm file at the current position. ILboolean ilIsValidPnmF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iIsValidPnm(); iseek(FirstPos, IL_SEEK_SET); return bRet; } //! Checks if Lump is a valid .pnm lump. ILboolean ilIsValidPnmL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iIsValidPnm(); } // Internal function to get the header and check it. ILboolean iIsValidPnm() { char Head[2]; ILint Read; Read = iread(Head, 1, 2); iseek(-Read, IL_SEEK_CUR); // Go ahead and restore to previous state if (Read != 2) return IL_FALSE; return iCheckPnm(Head); } // Internal function used to check if the HEADER is a valid .pnm header. ILboolean iCheckPnm(char Header[2]) { if (Header[0] != 'P') return IL_FALSE; switch (Header[1]) { case '1': case '2': case '3': case '4': case '5': case '6': case '7': return IL_TRUE; } return IL_FALSE; } // Reads a file ILboolean ilLoadPnm(ILconst_string FileName) { ILHANDLE PnmFile; ILboolean bPnm = IL_FALSE; PnmFile = iopenr(FileName); if (PnmFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bPnm; } bPnm = ilLoadPnmF(PnmFile); icloser(PnmFile); return bPnm; } // Reads an already-opened file ILboolean ilLoadPnmF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iLoadPnmInternal(); iseek(FirstPos, IL_SEEK_SET); return bRet; } // Reads from a memory "lump" ILboolean ilLoadPnmL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iLoadPnmInternal(); } // Load either a pgm or a ppm ILboolean iLoadPnmInternal() { ILimage *PmImage = NULL; PPMINFO Info; // ILuint LineInc = 0, SmallInc = 0; Info.Type = 0; if (iCurImage == NULL) { ilSetError(IL_ILLEGAL_OPERATION); return IL_FALSE; } // Find out what type of pgm/ppm this is if (iGetWord(IL_FALSE) == IL_FALSE) return IL_FALSE; if (SmallBuff[0] != 'P') { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } switch (SmallBuff[1]) { case '1': Info.Type = IL_PBM_ASCII; break; case '2': Info.Type = IL_PGM_ASCII; break; case '3': Info.Type = IL_PPM_ASCII; break; case '4': Info.Type = IL_PBM_BINARY; if (IsLump) { ilSetError(IL_FORMAT_NOT_SUPPORTED); return IL_FALSE; } break; case '5': Info.Type = IL_PGM_BINARY; break; case '6': Info.Type = IL_PPM_BINARY; break; case '7': //Info.Type = IL_PAM; return ilReadPam(); default: ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } // Retrieve the width and height if (iGetWord(IL_FALSE) == IL_FALSE) return IL_FALSE; Info.Width = atoi((const char*)SmallBuff); if (Info.Width == 0) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (iGetWord(IL_FALSE) == IL_FALSE) return IL_FALSE; Info.Height = atoi((const char*)SmallBuff); if (Info.Height == 0) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } // Retrieve the maximum colour component value if (Info.Type != IL_PBM_ASCII && Info.Type != IL_PBM_BINARY) { if (iGetWord(IL_TRUE) == IL_FALSE) return IL_FALSE; if ((Info.MaxColour = atoi((const char*)SmallBuff)) == 0) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } } else { Info.MaxColour = 1; } if (Info.Type == IL_PBM_ASCII || Info.Type == IL_PBM_BINARY || Info.Type == IL_PGM_ASCII || Info.Type == IL_PGM_BINARY) { if (Info.Type == IL_PGM_ASCII) { Info.Bpp = Info.MaxColour < 256 ? 1 : 2; } else { Info.Bpp = 1; } } else { Info.Bpp = 3; } switch (Info.Type) { case IL_PBM_ASCII: case IL_PGM_ASCII: case IL_PPM_ASCII: PmImage = ilReadAsciiPpm(&Info); break; case IL_PBM_BINARY: PmImage = ilReadBitPbm(&Info); break; case IL_PGM_BINARY: case IL_PPM_BINARY: PmImage = ilReadBinaryPpm(&Info); break; /*case IL_PAM: PmImage = ilReadPam(&Info);*/ default: return IL_FALSE; } if (PmImage == NULL) { iCurImage->Format = ilGetFormatBpp(iCurImage->Bpp); ilSetError(IL_FILE_READ_ERROR); return IL_FALSE; } // Is this conversion needed? Just 0's and 1's shows up as all black if (Info.Type == IL_PBM_ASCII) { PbmMaximize(PmImage); } if (Info.MaxColour > 255) PmImage->Type = IL_UNSIGNED_SHORT; PmImage->Origin = IL_ORIGIN_UPPER_LEFT; if (Info.Type == IL_PBM_ASCII || Info.Type == IL_PBM_BINARY || Info.Type == IL_PGM_ASCII || Info.Type == IL_PGM_BINARY) PmImage->Format = IL_LUMINANCE; else PmImage->Format = IL_RGB; PmImage->Origin = IL_ORIGIN_UPPER_LEFT; if (PmImage == NULL) return IL_FALSE; return ilFixImage(); } ILimage *ilReadAsciiPpm(PPMINFO *Info) { ILint LineInc = 0, SmallInc = 0, DataInc = 0, Size; // ILint BytesRead = 0; if (Info->MaxColour > 255) Info->Bpp *= 2; Size = Info->Width * Info->Height * Info->Bpp; if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) { return IL_FALSE; } iCurImage->Origin = IL_ORIGIN_UPPER_LEFT; if (Info->MaxColour > 255) iCurImage->Type = IL_UNSIGNED_SHORT; while (DataInc < Size) { // && !feof(File)) { LineInc = 0; if (iFgets((char *)LineBuffer, MAX_BUFFER) == NULL) { //ilSetError(IL_ILLEGAL_FILE_VALUE); //return NULL; //return iCurImage; break; } if (LineBuffer[0] == '#') { // Comment continue; } while ((LineBuffer[LineInc] != NUL) && (LineBuffer[LineInc] != '\n')) { SmallInc = 0; while (!isalnum(LineBuffer[LineInc])) { // Skip any whitespace LineInc++; } while (isalnum(LineBuffer[LineInc])) { SmallBuff[SmallInc] = LineBuffer[LineInc]; SmallInc++; LineInc++; } SmallBuff[SmallInc] = NUL; iCurImage->Data[DataInc] = atoi((const char*)SmallBuff); // Convert from string to colour // PSP likes to put whitespace at the end of lines...figures. =/ while (!isalnum(LineBuffer[LineInc]) && LineBuffer[LineInc] != NUL) { // Skip any whitespace LineInc++; } // We should set some kind of state flag that enables this //Image->Data[DataInc] *= (ILubyte)(255 / Info->MaxColour); // Scales to 0-255 if (Info->MaxColour > 255) DataInc++; DataInc++; } } // If we read less than what we should have... if (DataInc < Size) { //ilCloseImage(iCurImage); //ilSetCurImage(NULL); ilSetError(IL_ILLEGAL_FILE_VALUE); return NULL; } return iCurImage; } ILimage *ilReadBinaryPpm(PPMINFO *Info) { ILuint Size; Size = Info->Width * Info->Height * Info->Bpp; if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) { return IL_FALSE; } iCurImage->Origin = IL_ORIGIN_UPPER_LEFT; /* 4/3/2007 Dario Meloni Here it seems we have eaten too much bytes and it is needed to fix the starting point works well on various images No more need of this workaround. fixed iGetWord iseek(0,IL_SEEK_END); ILuint size = itell(); iseek(size-Size,IL_SEEK_SET); */ if (iread(iCurImage->Data, 1, Size) != Size) { ilCloseImage(iCurImage); return NULL; } return iCurImage; } ILimage *ilReadBitPbm(PPMINFO *Info) { ILuint m, j, x, CurrByte; if (!ilTexImage(Info->Width, Info->Height, 1, (ILubyte)(Info->Bpp), 0, IL_UNSIGNED_BYTE, NULL)) { return IL_FALSE; } iCurImage->Origin = IL_ORIGIN_UPPER_LEFT; x = 0; for (j = 0; j < iCurImage->SizeOfData;) { CurrByte = igetc(); for (m = 128; m > 0 && x < Info->Width; m >>= 1, ++x, ++j) { iCurImage->Data[j] = (CurrByte & m)?255:0; } if (x == Info->Width) x = 0; } return iCurImage; } ILboolean ilReadPam() { PPMINFO Info; string TempStr; ILuint Size; memset(&Info, 0, sizeof(PPMINFO)); while (!ieof()) { if (iGetWord(IL_FALSE) == IL_FALSE) return IL_FALSE; if (!strncmp((const char*)SmallBuff, "ENDHDR", 6)) break; TempStr = (char*)SmallBuff; // Save the first identifier of the line if (iGetWord(IL_FALSE) == IL_FALSE) return IL_FALSE; if (!strncmp(TempStr.c_str(), "WIDTH", 5)) Info.Width = atoi((const char*)SmallBuff); else if (!strncmp(TempStr.c_str(), "HEIGHT", 6)) Info.Height = atoi((const char*)SmallBuff); else if (!strncmp(TempStr.c_str(), "DEPTH", 5)) Info.Depth = atoi((const char*)SmallBuff); else if (!strncmp(TempStr.c_str(), "MAXVAL", 6)) Info.MaxColour = atoi((const char*)SmallBuff); else if (!strncmp(TempStr.c_str(), "TUPLTYPE", 8)) { string tmp = (char*)SmallBuff; Info.TuplType = DecodeTupleType(tmp); } } //@TODO: Handle other max colors and tuple types if (Info.Width == 0 || Info.Height == 0 || Info.Depth == 0 || Info.Depth > 4 || Info.TuplType == 0) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } switch (Info.MaxColour) { case 255: Info.Type = IL_UNSIGNED_BYTE; Size = 1; break; case 65535: Info.Type = IL_UNSIGNED_SHORT; Size = 2; break; default: ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } switch (Info.TuplType) { case PAM_GRAY: if (Info.Depth != 1) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (!ilTexImage(Info.Width, Info.Height, 1, Info.Depth, 0, Info.Type, NULL)) return IL_FALSE; Size *= Info.Width * Info.Height * Info.Depth; iCurImage->Format = IL_LUMINANCE; break; case PAM_GRAY_ALPHA: if (Info.Depth != 2) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (!ilTexImage(Info.Width, Info.Height, 1, Info.Depth, 0, Info.Type, NULL)) return IL_FALSE; Size *= Info.Width * Info.Height * Info.Depth; iCurImage->Format = IL_LUMINANCE_ALPHA; break; case PAM_RGB: if (Info.Depth != 3) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (!ilTexImage(Info.Width, Info.Height, 1, Info.Depth, 0, Info.Type, NULL)) return IL_FALSE; Size *= Info.Width * Info.Height * Info.Depth; iCurImage->Format = IL_RGB; break; case PAM_RGB_ALPHA: if (Info.Depth != 4) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (!ilTexImage(Info.Width, Info.Height, 1, Info.Depth, 0, Info.Type, NULL)) return IL_FALSE; Size *= Info.Width * Info.Height * Info.Depth; iCurImage->Format = IL_RGBA; break; default: //@TODO: Handle black and white case ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } iCurImage->Origin = IL_ORIGIN_UPPER_LEFT; if (iread(iCurImage->Data, 1, Size) != Size) { ilCloseImage(iCurImage); return IL_FALSE; } return ilFixImage(); } ILint DecodeTupleType(string &TupleStr) { if (TupleStr == "BLACKANDWHITE") return PAM_BW; else if (TupleStr == "GRAYSCALE") return PAM_GRAY; else if (TupleStr == "RGB") return PAM_RGB; else if (TupleStr == "BLACKANDWHITE_ALPHA") return PAM_BW_ALPHA; else if (TupleStr == "GRAYSCALE_ALPHA") return PAM_GRAY_ALPHA; else if (TupleStr == "RGB_ALPHA") return PAM_RGB_ALPHA; return 0; } ILboolean iGetWord(ILboolean final) { ILint WordPos = 0; ILint Current = 0; ILboolean Started = IL_FALSE; ILboolean Looping = IL_TRUE; if (ieof()) return IL_FALSE; while (Looping) { while ((Current = igetc()) != IL_EOF && Current != '\n' && Current != '#' && Current != ' ') { if (WordPos >= MAX_BUFFER) // We have hit the maximum line length. return IL_FALSE; if (!isalnum(Current) && Current != '_') { if (Started) { Looping = IL_FALSE; break; } continue; } if (Looping) SmallBuff[WordPos++] = Current; } if (Current == IL_EOF) return IL_FALSE; SmallBuff[WordPos] = 0; // 08-17-2008 - was NULL, changed to avoid warning if (final == IL_TRUE) break; if (!Looping) break; if (Current == '#') { // '#' is a comment...read until end of line while ((Current = igetc()) != IL_EOF && Current != '\n'); } // Get rid of any erroneous spaces while ((Current = igetc()) != IL_EOF) { if (Current != ' ') break; } iseek(-1, IL_SEEK_CUR); if (WordPos > 0) break; } if (Current == -1 || WordPos == 0) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } return IL_TRUE; } ILstring FName = NULL; //! Writes a Pnm file ILboolean ilSavePnm(const ILstring FileName) { ILHANDLE PnmFile; ILuint PnmSize; if (ilGetBoolean(IL_FILE_MODE) == IL_FALSE) { if (iFileExists(FileName)) { ilSetError(IL_FILE_ALREADY_EXISTS); return IL_FALSE; } } PnmFile = iopenw(FileName); if (PnmFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return IL_FALSE; } PnmSize = ilSavePnmF(PnmFile); iclosew(PnmFile); if (PnmSize == 0) return IL_FALSE; return IL_TRUE; } //! Writes a Pnm to an already-opened file ILuint ilSavePnmF(ILHANDLE File) { ILuint Pos; iSetOutputFile(File); Pos = itellw(); if (iSavePnmInternal() == IL_FALSE) return 0; // Error occurred return itellw() - Pos; // Return the number of bytes written. } //! Writes a Pnm to a memory "lump" ILuint ilSavePnmL(void *Lump, ILuint Size) { ILuint Pos; FName = NULL; iSetOutputLump(Lump, Size); Pos = itellw(); if (iSavePnmInternal() == IL_FALSE) return 0; // Error occurred return itellw() - Pos; // Return the number of bytes written. } // Internal function used to save the Pnm. ILboolean iSavePnmInternal() { ILuint Bpp, MaxVal = UCHAR_MAX, i = 0, j, k; ILenum Type = 0; ILuint LinePos = 0; // Cannot exceed 70 for pnm's! ILboolean Binary; ILimage *TempImage; ILubyte *TempData; if (iCurImage == NULL) { ilSetError(IL_ILLEGAL_OPERATION); return IL_FALSE; } if (iCheckExtension(FName, IL_TEXT("pbm"))) Type = IL_PBM_ASCII; else if (iCheckExtension(FName, IL_TEXT("pgm"))) Type = IL_PGM_ASCII; else if (iCheckExtension(FName, IL_TEXT("ppm"))) Type = IL_PPM_ASCII; else Type = IL_PPM_ASCII; /*if (!Type) { ilSetError(IL_INVALID_EXTENSION); return IL_FALSE; }*/ if (iGetHint(IL_COMPRESSION_HINT) == IL_USE_COMPRESSION) { Type += 3; Binary = IL_TRUE; } else { Binary = IL_FALSE; } if (iCurImage->Type == IL_UNSIGNED_BYTE) { MaxVal = UCHAR_MAX; } else if (iCurImage->Type == IL_UNSIGNED_SHORT) { MaxVal = USHRT_MAX; } else { ilSetError(IL_FORMAT_NOT_SUPPORTED); return IL_FALSE; } if (MaxVal > UCHAR_MAX && Type >= IL_PBM_BINARY) { // binary cannot be higher than 255 ilSetError(IL_FORMAT_NOT_SUPPORTED); return IL_FALSE; } switch (Type) { case IL_PBM_ASCII: Bpp = 1; ilprintf("P1\n"); TempImage = iConvertImage(iCurImage, IL_LUMINANCE, IL_UNSIGNED_BYTE); break; //case IL_PBM_BINARY: // Don't want to mess with saving bits just yet... //Bpp = 1; //ilprintf("P4\n"); //break; case IL_PBM_BINARY: ilSetError(IL_FORMAT_NOT_SUPPORTED); return IL_FALSE; case IL_PGM_ASCII: Bpp = 1; ilprintf("P2\n"); TempImage = iConvertImage(iCurImage, IL_COLOUR_INDEX, IL_UNSIGNED_BYTE); break; case IL_PGM_BINARY: Bpp = 1; ilprintf("P5\n"); TempImage = iConvertImage(iCurImage, IL_COLOUR_INDEX, IL_UNSIGNED_BYTE); break; case IL_PPM_ASCII: Bpp = 3; ilprintf("P3\n"); TempImage = iConvertImage(iCurImage, IL_RGB, IL_UNSIGNED_BYTE); break; case IL_PPM_BINARY: Bpp = 3; ilprintf("P6\n"); TempImage = iConvertImage(iCurImage, IL_RGB, IL_UNSIGNED_BYTE); break; default: ilSetError(IL_INTERNAL_ERROR); return IL_FALSE; } if (TempImage == NULL) return IL_FALSE; if (Bpp != TempImage->Bpp) { ilSetError(IL_INVALID_CONVERSION); return IL_FALSE; } if (TempImage->Origin != IL_ORIGIN_UPPER_LEFT) { TempData = iGetFlipped(TempImage); if (TempData == NULL) { ilCloseImage(TempImage); return IL_FALSE; } } else { TempData = TempImage->Data; } ilprintf("%d %d\n", TempImage->Width, TempImage->Height); if (Type != IL_PBM_BINARY && Type != IL_PBM_ASCII) // not needed for .pbm's (only 0 and 1) ilprintf("%d\n", MaxVal); while (i < TempImage->SizeOfPlane) { for (j = 0; j < Bpp; j++) { if (Binary) { if (Type == IL_PBM_BINARY) { iputc((ILubyte)(TempData[i] > 127 ? 1 : 0)); } else { iputc(TempData[i]); } } else { if (TempImage->Type == IL_UNSIGNED_BYTE) k = TempData[i]; else // IL_UNSIGNED_SHORT k = *((ILushort*)TempData + i); if (Type == IL_PBM_ASCII) { LinePos += ilprintf("%d ", TempData[i] > 127 ? 1 : 0); } else { LinePos += ilprintf("%d ", TempData[i]); } } if (TempImage->Type == IL_UNSIGNED_SHORT) i++; i++; } if (LinePos > 65) { // Just a good number =] ilprintf("\n"); LinePos = 0; } } if (TempImage->Origin != IL_ORIGIN_UPPER_LEFT) ifree(TempData); ilCloseImage(TempImage); return IL_TRUE; } // Converts a .pbm to something viewable. void PbmMaximize(ILimage *Image) { ILuint i = 0; for (i = 0; i < Image->SizeOfPlane; i++) if (Image->Data[i] == 1) Image->Data[i] = 0xFF; return; } #endif//IL_NO_PNM