1
0
mirror of https://github.com/cc65/cc65.git synced 2024-06-16 09:29:32 +00:00
cc65/src/sp65/pcx.c
2022-12-29 21:55:25 +02:00

537 lines
17 KiB
C

/*****************************************************************************/
/* */
/* 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 <errno.h>
#include <stdio.h>
#include <string.h>
/* 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;
}