/*****************************************************************************/ /* */ /* pcx.c */ /* */ /* Read PCX files */ /* */ /* */ /* */ /* (C) 2012, Ullrich von Bassewitz */ /* Roemerstrasse 52 */ /* D-70794 Filderstadt */ /* EMail: uz@cc65.org */ /* */ /* */ /* This software is provided 'as-is', without any expressed or implied */ /* warranty. In no event will the authors be held liable for any damages */ /* arising from the use of this software. */ /* */ /* Permission is granted to anyone to use this software for any purpose, */ /* including commercial applications, and to alter it and redistribute it */ /* freely, subject to the following restrictions: */ /* */ /* 1. The origin of this software must not be misrepresented; you must not */ /* claim that you wrote the original software. If you use this software */ /* in a product, an acknowledgment in the product documentation would be */ /* appreciated but is not required. */ /* 2. Altered source versions must be plainly marked as such, and must not */ /* be misrepresented as being the original software. */ /* 3. This notice may not be removed or altered from any source */ /* distribution. */ /* */ /*****************************************************************************/ #include #include #include /* common */ #include "print.h" #include "xmalloc.h" /* sp65 */ #include "attr.h" #include "error.h" #include "fileio.h" #include "pcx.h" /*****************************************************************************/ /* Macros */ /*****************************************************************************/ /* Some PCX constants */ #define PCX_MAGIC_ID 0x0A #define PCX_MAX_PLANES 4 /* A raw PCX header is just a block of bytes */ typedef unsigned char RawPCXHeader[128]; /* Structured PCX header */ typedef struct PCXHeader PCXHeader; struct PCXHeader { unsigned Id; unsigned FileVersion; unsigned Compressed; unsigned BPP; unsigned XMin; unsigned YMin; unsigned XMax; unsigned YMax; unsigned XDPI; unsigned YDPI; unsigned Planes; unsigned BytesPerPlane; unsigned PalInfo; unsigned ScreenWidth; unsigned ScreenHeight; /* Calculated data */ unsigned Width; unsigned Height; }; /* Read a little endian word from a byte array at offset O */ #define WORD(H, O) ((H)[O] | ((H)[O+1] << 8)) /*****************************************************************************/ /* Code */ /*****************************************************************************/ static PCXHeader* NewPCXHeader (void) /* Allocate a new PCX header and return it */ { /* No initialization here */ return xmalloc (sizeof (PCXHeader)); } static void FreePCXHeader (PCXHeader* H) /* Free a PCX header structure */ { xfree (H); } static PCXHeader* ReadPCXHeader (FILE* F, const char* Name) /* Read a structured PCX header from the given file and return it */ { RawPCXHeader H; /* Allocate a new PCXHeader structure */ PCXHeader* P = NewPCXHeader (); /* Read the raw header */ ReadData (F, H, sizeof (H)); /* Convert the data into structured form */ P->Id = H[0]; P->FileVersion = H[1]; P->Compressed = H[2]; P->BPP = H[3]; P->XMin = WORD (H, 4); P->YMin = WORD (H, 6); P->XMax = WORD (H, 8); P->YMax = WORD (H, 10); P->XDPI = WORD (H, 12); P->YDPI = WORD (H, 14); P->Planes = H[65]; P->BytesPerPlane = WORD (H, 66); P->PalInfo = WORD (H, 68); P->ScreenWidth = WORD (H, 70); P->ScreenHeight = WORD (H, 72); P->Width = P->XMax - P->XMin + 1; P->Height = P->YMax - P->YMin + 1; /* Check the header data */ if (P->Id != PCX_MAGIC_ID || P->FileVersion == 1 || P->FileVersion > 5) { Error ("'%s' is not a PCX file", Name); } if (P->Compressed > 1) { Error ("Unsupported compression (%d) in PCX file '%s'", P->Compressed, Name); } /* We support: ** - one plane with either 1, 4 or 8 bits per pixel ** - three planes with 8 bits per pixel ** - four planes with 8 bits per pixel (does this exist?) */ if (!((P->BPP == 1 && P->Planes == 1) || (P->BPP == 4 && P->Planes == 1) || (P->BPP == 8 && (P->Planes == 1 || P->Planes == 3 || P->Planes == 4)))) { /* We could support others, but currently we don't */ Error ("Unsupported PCX format: %u planes, %u bpp in PCX file '%s'", P->Planes, P->BPP, Name); } if (P->PalInfo != 1 && P->PalInfo != 2) { Error ("Unsupported palette info (%u) in PCX file '%s'", P->PalInfo, Name); } if (!ValidBitmapSize (P->Width, P->Height)) { Error ("PCX file '%s' has an unsupported size (w=%u, h=%d)", Name, P->Width, P->Height); } /* Return the structured header */ return P; } static void DumpPCXHeader (const PCXHeader* P, const char* Name) /* Dump the header of the PCX file in readable form to stdout */ { printf ("File name: %s\n", Name); printf ("PCX Version: "); switch (P->FileVersion) { case 0: puts ("2.5"); break; case 2: puts ("2.8 with palette"); break; case 3: puts ("2.8 without palette"); break; case 4: puts ("PCX for Windows without palette"); break; case 5: puts ("3.0"); break; } printf ("Image type: %s\n", P->PalInfo? "color" : "grayscale"); printf ("Compression: %s\n", P->Compressed? "RLE" : "None"); printf ("Structure: %u planes of %u bits\n", P->Planes, P->BPP); printf ("Bounding box: [%u/%u - %u/%u]\n", P->XMin, P->YMin, P->XMax, P->YMax); printf ("Resolution: %u/%u DPI\n", P->XDPI, P->YDPI); printf ("Screen size: %u/%u\n", P->ScreenWidth, P->ScreenHeight); printf ("Bytes per plane: %u\n", P->BytesPerPlane); } static void ReadPlane (FILE* F, PCXHeader* P, unsigned char* L) /* Read one (possibly compressed) plane from the file */ { unsigned i; if (P->Compressed) { /* Uncompress RLE data */ signed Remaining = P->BytesPerPlane; signed WidthCounter = P->Width; while (Remaining > 0) { unsigned char C; /* Read the next byte */ unsigned char B = Read8 (F); /* Check for a run length */ if ((B & 0xC0) == 0xC0) { C = (B & 0x3F); /* Count */ B = Read8 (F); /* Value */ } else { C = 1; } /* Write the data to the buffer */ switch (P->BPP) { default: for (i = 0; i < C; i++) { if (WidthCounter > 0) { *L = B; L += 1; WidthCounter -= 1; } Remaining -= 1; } break; case 4: for (i = 0; i < C; i++) { if (WidthCounter > 0) { *L = B >> 4; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = B & 15; L += 1; WidthCounter -= 1; } Remaining -= 1; } break; case 2: for (i = 0; i < C; i++) { if (WidthCounter > 0) { *L = (B >> 6) & 3; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 4) & 3; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 2) & 3; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = B & 3; L += 1; WidthCounter -= 1; } Remaining -= 1; } break; case 1: for (i = 0; i < C; i++) { if (WidthCounter > 0) { *L = (B >> 7) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 6) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 5) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 4) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 3) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 2) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = (B >> 1) & 1; L += 1; WidthCounter -= 1; } if (WidthCounter > 0) { *L = B & 1; L += 1; WidthCounter -= 1; } Remaining -= 1; } break; } } } else { /* Just read one line */ if (P->BPP == 4) { printf("Not implemented\n"); } else { ReadData (F, L, P->Width); } } } Bitmap* ReadPCXFile (const Collection* A) /* Read a bitmap from a PCX file */ { PCXHeader* P; Bitmap* B; unsigned char* L; Pixel* Px; unsigned MaxIdx = 0; unsigned X, Y; /* Get the file name */ const char* Name = NeedAttrVal (A, "name", "read pcx file"); /* Open the file */ FILE* F = fopen (Name, "rb"); if (F == 0) { Error ("Cannot open PCX file '%s': %s", Name, strerror (errno)); } /* Read the PCX header */ P = ReadPCXHeader (F, Name); /* Dump the header if requested */ if (Verbosity > 0) { DumpPCXHeader (P, Name); } /* Create the bitmap */ B = NewBitmap (P->Width, P->Height); /* Copy the name */ SB_CopyStr (&B->Name, Name); /* Allocate memory for the scan line */ L = xmalloc (P->Width); /* Read the pixel data */ Px = B->Data; if (P->Planes == 1) { /* This is either monochrome or indexed */ if (P->BPP == 1) { /* Monochrome */ for (Y = 0, Px = B->Data; Y < P->Height; ++Y) { unsigned I; unsigned char Mask; /* Read the plane */ ReadPlane (F, P, L); /* Create pixels */ for (X = 0, I = 0, Mask = 0x01; X < P->Width; ++Px) { Px->Index = (L[I] & Mask) != 0; if (Mask == 0x80) { Mask = 0x01; ++I; } else { Mask <<= 1; } } } } else { /* One plane with 8bpp is indexed */ for (Y = 0, Px = B->Data; Y < P->Height; ++Y) { /* Read the plane */ ReadPlane (F, P, L); /* Create pixels */ for (X = 0; X < P->Width; ++X, ++Px) { if (L[X] > MaxIdx) { MaxIdx = L[X]; } Px->Index = L[X]; } } } /* One plane means we have a palette which is either part of the header ** or follows. */ if (P->PalInfo == 0) { /* Create the monochrome palette */ B->Pal = NewMonochromePalette (); } else { unsigned Count; unsigned I; unsigned char Palette[256][3]; unsigned long EndPos; /* Determine the current file position */ unsigned long CurPos = FileGetPos (F); /* Seek to the end of the file */ (void) fseek (F, 0, SEEK_END); /* Get this position */ EndPos = FileGetPos (F); /* There's a palette if the old location is 769 bytes from the end */ if (EndPos - CurPos == sizeof (Palette) + 1) { /* Seek back */ FileSetPos (F, CurPos); /* Check for palette marker */ if (Read8 (F) != 0x0C) { Error ("Invalid palette marker in PCX file '%s'", Name); } } else if (EndPos == CurPos) { /* The palette is in the header */ FileSetPos (F, 16); /* Check the maximum index for safety */ if (MaxIdx > 15) { Error ("PCX file '%s' contains more than 16 indexed colors " "but no extra palette", Name); } } else { Error ("Error in PCX file '%s': %lu bytes at end of pixel data", Name, EndPos - CurPos); } /* Read the palette. We will just read what we need. */ Count = MaxIdx + 1; ReadData (F, Palette, Count * sizeof (Palette[0])); /* Create the palette from the data */ B->Pal = NewPalette (Count); for (I = 0; I < Count; ++I) { B->Pal->Entries[I].R = Palette[I][0]; B->Pal->Entries[I].G = Palette[I][1]; B->Pal->Entries[I].B = Palette[I][2]; B->Pal->Entries[I].A = 0; } } } else { /* 3 or 4 planes are RGB or RGBA (don't know if this exists) */ for (Y = 0, Px = B->Data; Y < P->Height; ++Y) { /* Read the R plane and move the data */ ReadPlane (F, P, L); for (X = 0; X < P->Width; ++X, ++Px) { Px->C.R = L[X]; } /* Read the G plane and move the data */ ReadPlane (F, P, L); for (X = 0; X < P->Width; ++X, ++Px) { Px->C.G = L[X]; } /* Read the B plane and move the data */ ReadPlane (F, P, L); for (X = 0; X < P->Width; ++X, ++Px) { Px->C.B = L[X]; } /* Either read the A plane or clear it */ if (P->Planes == 4) { ReadPlane (F, P, L); for (X = 0; X < P->Width; ++X, ++Px) { Px->C.A = L[X]; } } else { for (X = 0; X < P->Width; ++X, ++Px) { Px->C.A = 0; } } } } /* Close the file */ fclose (F); /* Free memory for the scan line */ xfree (L); /* Free the PCX header */ FreePCXHeader (P); /* Return the bitmap */ return B; }