Task 578: .PSPPALETTE File Format

Task 578: .PSPPALETTE File Format

.PSPPALETTE File Format Specifications

The .PSPPALETTE file format is used by PaintShop Pro (now by Corel) for storing color palettes. It is essentially the JASC-PAL format, named after Jasc Software, the original developer of PaintShop Pro. The file is a plain text file with Windows line endings (\r\n), containing a header, version, color count, and RGB color values.

1. List of All Properties Intrinsic to This File Format

  • Signature: The string "JASC-PAL" (first line).
  • Version: The string "0100" (second line, indicating version 1.00).
  • Number of Colors: An integer representing the number of colors in the palette (third line, typically 16 or 256, but can be any positive integer).
  • Color Entries: A list of RGB triples, one per line starting from the fourth line, with each color specified as three space-separated integers (0-255) in the format "R G B". There are exactly as many entries as specified in the number of colors.

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop File Dump

Here's a self-contained HTML snippet with JavaScript that can be embedded in a Ghost blog (or any HTML page). It allows dragging and dropping a .PSPPALETTE file (or .pal) and dumps the properties to the screen.

Drag and drop a .PSPPALETTE file here

4. Python Class for .PSPPALETTE Files

import sys

class PspPalette:
    def __init__(self, filename=None):
        self.signature = "JASC-PAL"
        self.version = "0100"
        self.num_colors = 0
        self.colors = []  # list of (r, g, b) tuples
        if filename:
            self.read(filename)

    def read(self, filename):
        with open(filename, 'r') as f:
            lines = f.readlines()
        if len(lines) < 3:
            raise ValueError("Invalid .PSPPALETTE file")
        self.signature = lines[0].strip()
        self.version = lines[1].strip()
        try:
            self.num_colors = int(lines[2].strip())
        except ValueError:
            raise ValueError("Invalid number of colors")
        self.colors = []
        for line in lines[3:3 + self.num_colors]:
            parts = line.strip().split()
            if len(parts) != 3:
                raise ValueError("Invalid color entry")
            self.colors.append(tuple(map(int, parts)))

    def write(self, filename):
        with open(filename, 'w') as f:
            f.write(f"{self.signature}\r\n")
            f.write(f"{self.version}\r\n")
            f.write(f"{self.num_colors}\r\n")
            for r, g, b in self.colors:
                f.write(f"{r} {g} {b}\r\n")

    def print_properties(self):
        print(f"Signature: {self.signature}")
        print(f"Version: {self.version}")
        print(f"Number of Colors: {self.num_colors}")
        print("Colors:")
        for color in self.colors:
            print(color)

# Example usage:
if __name__ == "__main__":
    if len(sys.argv) > 1:
        pal = PspPalette(sys.argv[1])
        pal.print_properties()

5. Java Class for .PSPPALETTE Files

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class PspPalette {
    private String signature = "JASC-PAL";
    private String version = "0100";
    private int numColors = 0;
    private List<int[]> colors = new ArrayList<>(); // each int[3] is {r, g, b}

    public PspPalette(String filename) throws IOException {
        read(filename);
    }

    public PspPalette() {}

    public void read(String filename) throws IOException {
        try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
            String line = reader.readLine();
            if (line == null) throw new IOException("Invalid file");
            signature = line.trim();
            line = reader.readLine();
            if (line == null) throw new IOException("Invalid file");
            version = line.trim();
            line = reader.readLine();
            if (line == null) throw new IOException("Invalid file");
            numColors = Integer.parseInt(line.trim());
            colors.clear();
            for (int i = 0; i < numColors; i++) {
                line = reader.readLine();
                if (line == null) throw new IOException("Incomplete colors");
                String[] parts = line.trim().split("\\s+");
                if (parts.length != 3) throw new IOException("Invalid color");
                int[] rgb = {Integer.parseInt(parts[0]), Integer.parseInt(parts[1]), Integer.parseInt(parts[2])};
                colors.add(rgb);
            }
        }
    }

    public void write(String filename) throws IOException {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(filename))) {
            writer.write(signature + "\r\n");
            writer.write(version + "\r\n");
            writer.write(numColors + "\r\n");
            for (int[] rgb : colors) {
                writer.write(rgb[0] + " " + rgb[1] + " " + rgb[2] + "\r\n");
            }
        }
    }

    public void printProperties() {
        System.out.println("Signature: " + signature);
        System.out.println("Version: " + version);
        System.out.println("Number of Colors: " + numColors);
        System.out.println("Colors:");
        for (int[] rgb : colors) {
            System.out.println("(" + rgb[0] + ", " + rgb[1] + ", " + rgb[2] + ")");
        }
    }

    public static void main(String[] args) throws IOException {
        if (args.length > 0) {
            PspPalette pal = new PspPalette(args[0]);
            pal.printProperties();
        }
    }
}

6. JavaScript Class for .PSPPALETTE Files

class PspPalette {
  constructor(filename = null) {
    this.signature = 'JASC-PAL';
    this.version = '0100';
    this.numColors = 0;
    this.colors = []; // array of [r, g, b]
    if (filename) {
      this.read(filename);
    }
  }

  async read(filename) {
    const response = await fetch(filename);
    if (!response.ok) throw new Error('Failed to read file');
    const text = await response.text();
    const lines = text.split(/\r?\n/).filter(line => line.trim() !== '');
    if (lines.length < 3) throw new Error('Invalid file');
    this.signature = lines[0].trim();
    this.version = lines[1].trim();
    this.numColors = parseInt(lines[2].trim(), 10);
    this.colors = [];
    for (let i = 3; i < 3 + this.numColors; i++) {
      const parts = lines[i].trim().split(/\s+/).map(Number);
      if (parts.length !== 3) throw new Error('Invalid color');
      this.colors.push(parts);
    }
  }

  write() {
    // For browser, this returns a Blob for download
    let content = `${this.signature}\r\n${this.version}\r\n${this.numColors}\r\n`;
    this.colors.forEach(([r, g, b]) => {
      content += `${r} ${g} ${b}\r\n`;
    });
    const blob = new Blob([content], { type: 'text/plain' });
    return blob; // Use URL.createObjectURL(blob) to download
  }

  printProperties() {
    console.log(`Signature: ${this.signature}`);
    console.log(`Version: ${this.version}`);
    console.log(`Number of Colors: ${this.numColors}`);
    console.log('Colors:', this.colors);
  }
}

// Example usage (in browser console or Node with fs/promises):
// const pal = new PspPalette('example.pal');
// await pal.read('example.pal');
// pal.printProperties();

7. C "Class" (Struct with Functions) for .PSPPALETTE Files

Since C does not have classes, here's a struct with associated functions for reading, writing, and printing.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *signature;
    char *version;
    int num_colors;
    int (*colors)[3]; // array of [r, g, b]
} PspPalette;

PspPalette* psp_palette_new() {
    PspPalette* pal = malloc(sizeof(PspPalette));
    pal->signature = strdup("JASC-PAL");
    pal->version = strdup("0100");
    pal->num_colors = 0;
    pal->colors = NULL;
    return pal;
}

void psp_palette_free(PspPalette* pal) {
    free(pal->signature);
    free(pal->version);
    free(pal->colors);
    free(pal);
}

int psp_palette_read(PspPalette* pal, const char* filename) {
    FILE* f = fopen(filename, "r");
    if (!f) return -1;
    
    char line[256];
    if (!fgets(line, sizeof(line), f)) goto error;
    pal->signature = strdup(strtok(line, "\r\n"));
    
    if (!fgets(line, sizeof(line), f)) goto error;
    pal->version = strdup(strtok(line, "\r\n"));
    
    if (!fgets(line, sizeof(line), f)) goto error;
    pal->num_colors = atoi(line);
    
    pal->colors = malloc(pal->num_colors * sizeof(int[3]));
    for (int i = 0; i < pal->num_colors; i++) {
        if (!fgets(line, sizeof(line), f)) goto error;
        sscanf(line, "%d %d %d", &pal->colors[i][0], &pal->colors[i][1], &pal->colors[i][2]);
    }
    fclose(f);
    return 0;
    
error:
    fclose(f);
    return -1;
}

int psp_palette_write(PspPalette* pal, const char* filename) {
    FILE* f = fopen(filename, "w");
    if (!f) return -1;
    fprintf(f, "%s\r\n", pal->signature);
    fprintf(f, "%s\r\n", pal->version);
    fprintf(f, "%d\r\n", pal->num_colors);
    for (int i = 0; i < pal->num_colors; i++) {
        fprintf(f, "%d %d %d\r\n", pal->colors[i][0], pal->colors[i][1], pal->colors[i][2]);
    }
    fclose(f);
    return 0;
}

void psp_palette_print_properties(PspPalette* pal) {
    printf("Signature: %s\n", pal->signature);
    printf("Version: %s\n", pal->version);
    printf("Number of Colors: %d\n", pal->num_colors);
    printf("Colors:\n");
    for (int i = 0; i < pal->num_colors; i++) {
        printf("(%d, %d, %d)\n", pal->colors[i][0], pal->colors[i][1], pal->colors[i][2]);
    }
}

int main(int argc, char** argv) {
    if (argc > 1) {
        PspPalette* pal = psp_palette_new();
        if (psp_palette_read(pal, argv[1]) == 0) {
            psp_palette_print_properties(pal);
        } else {
            printf("Error reading file\n");
        }
        psp_palette_free(pal);
    }
    return 0;
}