//----------------------------------------------------------------------------- // // ImageLib Sources // Copyright (C) 2000-2009 by Denton Woods // Last modified: 03/07/2009 // // Filename: src-IL/src/il_psp.c // // Description: Reads a Paint Shop Pro file. // //----------------------------------------------------------------------------- #include "il_internal.h" #include "il_psp.h" #ifndef IL_NO_PSP ILubyte PSPSignature[32] = { 0x50, 0x61, 0x69, 0x6E, 0x74, 0x20, 0x53, 0x68, 0x6F, 0x70, 0x20, 0x50, 0x72, 0x6F, 0x20, 0x49, 0x6D, 0x61, 0x67, 0x65, 0x20, 0x46, 0x69, 0x6C, 0x65, 0x0A, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00 }; ILubyte GenAttHead[4] = { 0x7E, 0x42, 0x4B, 0x00 }; // Make these global, since they contain most of the image information. GENATT_CHUNK AttChunk; PSPHEAD Header; ILuint NumChannels; ILubyte **Channels = NULL; ILubyte *Alpha = NULL; ILpal Pal; //! Checks if the file specified in FileName is a valid Psp file. ILboolean ilIsValidPsp(ILconst_string FileName) { ILHANDLE PspFile; ILboolean bPsp = IL_FALSE; if (!iCheckExtension(FileName, IL_TEXT("psp"))) { ilSetError(IL_INVALID_EXTENSION); return bPsp; } PspFile = iopenr(FileName); if (PspFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bPsp; } bPsp = ilIsValidPspF(PspFile); icloser(PspFile); return bPsp; } //! Checks if the ILHANDLE contains a valid Psp file at the current position. ILboolean ilIsValidPspF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iIsValidPsp(); iseek(FirstPos, IL_SEEK_SET); return bRet; } //! Checks if Lump is a valid Psp lump. ILboolean ilIsValidPspL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iIsValidPsp(); } // Internal function used to get the Psp header from the current file. ILboolean iGetPspHead() { if (iread(Header.FileSig, 1, 32) != 32) return IL_FALSE; Header.MajorVersion = GetLittleUShort(); Header.MinorVersion = GetLittleUShort(); return IL_TRUE; } // Internal function to get the header and check it. ILboolean iIsValidPsp() { if (!iGetPspHead()) return IL_FALSE; iseek(-(ILint)sizeof(PSPHEAD), IL_SEEK_CUR); return iCheckPsp(); } // Internal function used to check if the HEADER is a valid Psp header. ILboolean iCheckPsp() { if (stricmp(Header.FileSig, "Paint Shop Pro Image File\n\x1a")) return IL_FALSE; if (Header.MajorVersion < 3 || Header.MajorVersion > 5) return IL_FALSE; if (Header.MinorVersion != 0) return IL_FALSE; return IL_TRUE; } //! Reads a PSP file ILboolean ilLoadPsp(ILconst_string FileName) { ILHANDLE PSPFile; ILboolean bPsp = IL_FALSE; PSPFile = iopenr(FileName); if (PSPFile == NULL) { ilSetError(IL_COULD_NOT_OPEN_FILE); return bPsp; } bPsp = ilLoadPspF(PSPFile); icloser(PSPFile); return bPsp; } //! Reads an already-opened PSP file ILboolean ilLoadPspF(ILHANDLE File) { ILuint FirstPos; ILboolean bRet; iSetInputFile(File); FirstPos = itell(); bRet = iLoadPspInternal(); iseek(FirstPos, IL_SEEK_SET); return bRet; } //! Reads from a memory "lump" that contains a PSP ILboolean ilLoadPspL(const void *Lump, ILuint Size) { iSetInputLump(Lump, Size); return iLoadPspInternal(); } // Internal function used to load the PSP. ILboolean iLoadPspInternal() { if (iCurImage == NULL) { ilSetError(IL_ILLEGAL_OPERATION); return IL_FALSE; } Channels = NULL; Alpha = NULL; Pal.Palette = NULL; if (!iGetPspHead()) return IL_FALSE; if (!iCheckPsp()) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (!ReadGenAttributes()) return IL_FALSE; if (!ParseChunks()) return IL_FALSE; if (!AssembleImage()) return IL_FALSE; Cleanup(); return ilFixImage(); } ILboolean ReadGenAttributes() { BLOCKHEAD AttHead; ILint Padding; ILuint ChunkLen; if (iread(&AttHead, sizeof(AttHead), 1) != 1) return IL_FALSE; UShort(&AttHead.BlockID); UInt(&AttHead.BlockLen); if (AttHead.HeadID[0] != 0x7E || AttHead.HeadID[1] != 0x42 || AttHead.HeadID[2] != 0x4B || AttHead.HeadID[3] != 0x00) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } if (AttHead.BlockID != PSP_IMAGE_BLOCK) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } ChunkLen = GetLittleUInt(); if (Header.MajorVersion != 3) ChunkLen -= 4; if (iread(&AttChunk, IL_MIN(sizeof(AttChunk), ChunkLen), 1) != 1) return IL_FALSE; // Can have new entries in newer versions of the spec (4.0). Padding = (ChunkLen) - sizeof(AttChunk); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); // @TODO: Anything but 24 not supported yet... if (AttChunk.BitDepth != 24 && AttChunk.BitDepth != 8) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } // @TODO; Add support for compression... if (AttChunk.Compression != PSP_COMP_NONE && AttChunk.Compression != PSP_COMP_RLE) { ilSetError(IL_INVALID_FILE_HEADER); return IL_FALSE; } // @TODO: Check more things in the general attributes chunk here. return IL_TRUE; } ILboolean ParseChunks() { BLOCKHEAD Block; ILuint Pos; do { if (iread(&Block, 1, sizeof(Block)) != sizeof(Block)) { ilGetError(); // Get rid of the erroneous IL_FILE_READ_ERROR. return IL_TRUE; } if (Header.MajorVersion == 3) Block.BlockLen = GetLittleUInt(); else UInt(&Block.BlockLen); if (Block.HeadID[0] != 0x7E || Block.HeadID[1] != 0x42 || Block.HeadID[2] != 0x4B || Block.HeadID[3] != 0x00) { return IL_TRUE; } UShort(&Block.BlockID); UInt(&Block.BlockLen); Pos = itell(); switch (Block.BlockID) { case PSP_LAYER_START_BLOCK: if (!ReadLayerBlock(Block.BlockLen)) return IL_FALSE; break; case PSP_ALPHA_BANK_BLOCK: if (!ReadAlphaBlock(Block.BlockLen)) return IL_FALSE; break; case PSP_COLOR_BLOCK: if (!ReadPalette(Block.BlockLen)) return IL_FALSE; break; // Gets done in the next iseek, so this is now commented out. //default: //iseek(Block.BlockLen, IL_SEEK_CUR); } // Skip to next block just in case we didn't read the entire block. iseek(Pos + Block.BlockLen, IL_SEEK_SET); // @TODO: Do stuff here. } while (1); return IL_TRUE; } ILboolean ReadLayerBlock(ILuint BlockLen) { BLOCKHEAD Block; LAYERINFO_CHUNK LayerInfo; LAYERBITMAP_CHUNK Bitmap; ILuint ChunkSize, Padding, i, j; ILushort NumChars; BlockLen; // Layer sub-block header if (iread(&Block, 1, sizeof(Block)) != sizeof(Block)) return IL_FALSE; if (Header.MajorVersion == 3) Block.BlockLen = GetLittleUInt(); else UInt(&Block.BlockLen); if (Block.HeadID[0] != 0x7E || Block.HeadID[1] != 0x42 || Block.HeadID[2] != 0x4B || Block.HeadID[3] != 0x00) { return IL_FALSE; } if (Block.BlockID != PSP_LAYER_BLOCK) return IL_FALSE; if (Header.MajorVersion == 3) { iseek(256, IL_SEEK_CUR); // We don't care about the name of the layer. iread(&LayerInfo, sizeof(LayerInfo), 1); if (iread(&Bitmap, sizeof(Bitmap), 1) != 1) return IL_FALSE; } else { // Header.MajorVersion >= 4 ChunkSize = GetLittleUInt(); NumChars = GetLittleUShort(); iseek(NumChars, IL_SEEK_CUR); // We don't care about the layer's name. ChunkSize -= (2 + 4 + NumChars); if (iread(&LayerInfo, IL_MIN(sizeof(LayerInfo), ChunkSize), 1) != 1) return IL_FALSE; // Can have new entries in newer versions of the spec (5.0). Padding = (ChunkSize) - sizeof(LayerInfo); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); ChunkSize = GetLittleUInt(); if (iread(&Bitmap, sizeof(Bitmap), 1) != 1) return IL_FALSE; Padding = (ChunkSize - 4) - sizeof(Bitmap); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); } Channels = (ILubyte**)ialloc(sizeof(ILubyte*) * Bitmap.NumChannels); if (Channels == NULL) { return IL_FALSE; } NumChannels = Bitmap.NumChannels; for (i = 0; i < NumChannels; i++) { Channels[i] = GetChannel(); if (Channels[i] == NULL) { for (j = 0; j < i; j++) ifree(Channels[j]); return IL_FALSE; } } return IL_TRUE; } ILboolean ReadAlphaBlock(ILuint BlockLen) { BLOCKHEAD Block; ALPHAINFO_CHUNK AlphaInfo; ALPHA_CHUNK AlphaChunk; ILushort NumAlpha, StringSize; ILuint ChunkSize, Padding; if (Header.MajorVersion == 3) { NumAlpha = GetLittleUShort(); } else { ChunkSize = GetLittleUInt(); NumAlpha = GetLittleUShort(); Padding = (ChunkSize - 4 - 2); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); } // Alpha channel header if (iread(&Block, 1, sizeof(Block)) != sizeof(Block)) return IL_FALSE; if (Header.MajorVersion == 3) Block.BlockLen = GetLittleUInt(); else UInt(&Block.BlockLen); if (Block.HeadID[0] != 0x7E || Block.HeadID[1] != 0x42 || Block.HeadID[2] != 0x4B || Block.HeadID[3] != 0x00) { return IL_FALSE; } if (Block.BlockID != PSP_ALPHA_CHANNEL_BLOCK) return IL_FALSE; if (Header.MajorVersion >= 4) { ChunkSize = GetLittleUInt(); StringSize = GetLittleUShort(); iseek(StringSize, IL_SEEK_CUR); if (iread(&AlphaInfo, sizeof(AlphaInfo), 1) != 1) return IL_FALSE; Padding = (ChunkSize - 4 - 2 - StringSize - sizeof(AlphaInfo)); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); ChunkSize = GetLittleUInt(); if (iread(&AlphaChunk, sizeof(AlphaChunk), 1) != 1) return IL_FALSE; Padding = (ChunkSize - 4 - sizeof(AlphaChunk)); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); } else { iseek(256, IL_SEEK_CUR); iread(&AlphaInfo, sizeof(AlphaInfo), 1); if (iread(&AlphaChunk, sizeof(AlphaChunk), 1) != 1) return IL_FALSE; } /*Alpha = (ILubyte*)ialloc(AlphaInfo.AlphaRect.x2 * AlphaInfo.AlphaRect.y2); if (Alpha == NULL) { return IL_FALSE; }*/ Alpha = GetChannel(); if (Alpha == NULL) return IL_FALSE; return IL_TRUE; } ILubyte *GetChannel() { BLOCKHEAD Block; CHANNEL_CHUNK Channel; ILubyte *CompData, *Data; ILuint ChunkSize, Padding; if (iread(&Block, 1, sizeof(Block)) != sizeof(Block)) return NULL; if (Header.MajorVersion == 3) Block.BlockLen = GetLittleUInt(); else UInt(&Block.BlockLen); if (Block.HeadID[0] != 0x7E || Block.HeadID[1] != 0x42 || Block.HeadID[2] != 0x4B || Block.HeadID[3] != 0x00) { ilSetError(IL_ILLEGAL_FILE_VALUE); return NULL; } if (Block.BlockID != PSP_CHANNEL_BLOCK) { ilSetError(IL_ILLEGAL_FILE_VALUE); return NULL; } if (Header.MajorVersion >= 4) { ChunkSize = GetLittleUInt(); if (iread(&Channel, sizeof(Channel), 1) != 1) return NULL; Padding = (ChunkSize - 4) - sizeof(Channel); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); } else { if (iread(&Channel, sizeof(Channel), 1) != 1) return NULL; } CompData = (ILubyte*)ialloc(Channel.CompLen); Data = (ILubyte*)ialloc(AttChunk.Width * AttChunk.Height); if (CompData == NULL || Data == NULL) { ifree(Data); ifree(CompData); return NULL; } if (iread(CompData, 1, Channel.CompLen) != Channel.CompLen) { ifree(CompData); ifree(Data); return NULL; } switch (AttChunk.Compression) { case PSP_COMP_NONE: ifree(Data); return CompData; break; case PSP_COMP_RLE: if (!UncompRLE(CompData, Data, Channel.CompLen)) { ifree(CompData); ifree(Data); return IL_FALSE; } break; default: ifree(CompData); ifree(Data); ilSetError(IL_INVALID_FILE_HEADER); return NULL; } ifree(CompData); return Data; } ILboolean UncompRLE(ILubyte *CompData, ILubyte *Data, ILuint CompLen) { ILubyte Run, Colour; ILint i, /*x, y,*/ Count/*, Total = 0*/; /*for (y = 0; y < AttChunk.Height; y++) { for (x = 0, Count = 0; x < AttChunk.Width; ) { Run = *CompData++; if (Run > 128) { Run -= 128; Colour = *CompData++; memset(Data, Colour, Run); Data += Run; Count += 2; } else { memcpy(Data, CompData, Run); CompData += Run; Data += Run; Count += Run; } x += Run; } Total += Count; if (Count % 4) { // Has to be on a 4-byte boundary. CompData += (4 - (Count % 4)) % 4; Total += (4 - (Count % 4)) % 4; } if (Total >= CompLen) return IL_FALSE; }*/ for (i = 0, Count = 0; i < (ILint)CompLen; ) { Run = *CompData++; i++; if (Run > 128) { Run -= 128; Colour = *CompData++; i++; memset(Data, Colour, Run); } else { memcpy(Data, CompData, Run); CompData += Run; i += Run; } Data += Run; Count += Run; } return IL_TRUE; } ILboolean ReadPalette(ILuint BlockLen) { ILuint ChunkSize, PalCount, Padding; if (Header.MajorVersion >= 4) { ChunkSize = GetLittleUInt(); PalCount = GetLittleUInt(); Padding = (ChunkSize - 4 - 4); if (Padding > 0) iseek(Padding, IL_SEEK_CUR); } else { PalCount = GetLittleUInt(); } Pal.PalSize = PalCount * 4; Pal.PalType = IL_PAL_BGRA32; Pal.Palette = (ILubyte*)ialloc(Pal.PalSize); if (Pal.Palette == NULL) return IL_FALSE; if (iread(Pal.Palette, Pal.PalSize, 1) != 1) { ifree(Pal.Palette); return IL_FALSE; } return IL_TRUE; } ILboolean AssembleImage() { ILuint Size, i, j; Size = AttChunk.Width * AttChunk.Height; if (NumChannels == 1) { ilTexImage(AttChunk.Width, AttChunk.Height, 1, 1, IL_LUMINANCE, IL_UNSIGNED_BYTE, NULL); for (i = 0; i < Size; i++) { iCurImage->Data[i] = Channels[0][i]; } if (Pal.Palette) { iCurImage->Format = IL_COLOUR_INDEX; iCurImage->Pal.PalSize = Pal.PalSize; iCurImage->Pal.PalType = Pal.PalType; iCurImage->Pal.Palette = Pal.Palette; } } else { if (Alpha) { ilTexImage(AttChunk.Width, AttChunk.Height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, NULL); for (i = 0, j = 0; i < Size; i++, j += 4) { iCurImage->Data[j ] = Channels[0][i]; iCurImage->Data[j+1] = Channels[1][i]; iCurImage->Data[j+2] = Channels[2][i]; iCurImage->Data[j+3] = Alpha[i]; } } else if (NumChannels == 4) { ilTexImage(AttChunk.Width, AttChunk.Height, 1, 4, IL_RGBA, IL_UNSIGNED_BYTE, NULL); for (i = 0, j = 0; i < Size; i++, j += 4) { iCurImage->Data[j ] = Channels[0][i]; iCurImage->Data[j+1] = Channels[1][i]; iCurImage->Data[j+2] = Channels[2][i]; iCurImage->Data[j+3] = Channels[3][i]; } } else if (NumChannels == 3) { ilTexImage(AttChunk.Width, AttChunk.Height, 1, 3, IL_RGB, IL_UNSIGNED_BYTE, NULL); for (i = 0, j = 0; i < Size; i++, j += 3) { iCurImage->Data[j ] = Channels[0][i]; iCurImage->Data[j+1] = Channels[1][i]; iCurImage->Data[j+2] = Channels[2][i]; } } else return IL_FALSE; } iCurImage->Origin = IL_ORIGIN_UPPER_LEFT; return IL_TRUE; } ILboolean Cleanup() { ILuint i; if (Channels) { for (i = 0; i < NumChannels; i++) { ifree(Channels[i]); } ifree(Channels); } if (Alpha) { ifree(Alpha); } Channels = NULL; Alpha = NULL; Pal.Palette = NULL; return IL_TRUE; } #endif//IL_NO_PSP