Task 645: .SCR File Format

Task 645: .SCR File Format

File Format Specifications for .SCR (ZX Spectrum Screen Format)

The .SCR file format is a raster image format primarily associated with the ZX Spectrum home computer, used for storing screenshots or screen dumps. It represents a direct memory dump of the ZX Spectrum's video RAM, capturing both pixel bitmap data and color attribute information. The format is simple, headerless, and fixed-size, with no compression or variable metadata. It supports a fixed resolution of 256×192 pixels in monochrome (1 bit per pixel) for the bitmap, augmented by an 8×8 block-based attribute map that defines colors, brightness, and flashing effects from a fixed 15-color palette (8 base colors with bright variants).

Key aspects of the format:

  • Total file size: Exactly 6912 bytes.
  • Bitmap data: First 6144 bytes (256×192 pixels, 1 bpp, row-major order with 32 bytes per scanline; each byte's bits are MSB-first, left-to-right).
  • Attribute data: Last 768 bytes (32×24 grid of 8×8 pixel blocks; each byte encodes foreground/background colors (bits 0–3/4–7), brightness (bit 6), and flash (bit 7)).
  • Palette: Fixed ZX Spectrum colors (black=0, blue=1, red=2, magenta=3, green=4, cyan=5, yellow=6, white=7; bits 0–2 select color, bit 3 unused for base, bright mode shifts to 8–15 equivalents).
  • No endianness issues: Byte order is as stored in memory.
  • Validation: Files must be precisely 6912 bytes; deviations indicate invalid format.

This format is uncompressed and self-contained, ideal for emulation and retro computing applications. It lacks advanced features like transparency or multiple layers but efficiently captures the ZX Spectrum's unique attribute-clash rendering.

1. List of Properties Intrinsic to the File Format

The following properties are inherent to the .SCR format's structure, derived from its memory-dump design. These are fixed and non-variable, forming the "file system" of the format (i.e., its rigid layout without headers or extensible sections).

Property Description Value/Type
File Size Total length of the valid file 6912 bytes (fixed)
Bitmap Offset Starting position of pixel data 0 bytes
Bitmap Size Length of the monochrome pixel bitmap 6144 bytes
Pixel Width Horizontal resolution in pixels 256 pixels
Pixel Height Vertical resolution in pixels 192 pixels
Bits per Pixel Depth of the bitmap data 1 bit
Bytes per Scanline Bytes required per horizontal line in the bitmap 32 bytes
Scanline Order Layout of pixel lines Row-major (sequential)
Bit Order in Byte Pixel bit positioning within each byte MSB-first (leftmost pixel)
Attribute Offset Starting position of color attribute data 6144 bytes
Attribute Size Length of the attribute map 768 bytes
Attribute Columns Horizontal blocks (each covering 8 pixels) 32
Attribute Rows Vertical blocks (each covering 8 pixels) 24
Foreground Color Bits Bits defining ink (text/draw) color in each attribute byte Bits 0–3 (0–7)
Background Color Bits Bits defining paper (background) color in each attribute byte Bits 4–7 (0–7)
Brightness Flag Bit indicating bright color mode Bit 6 (0=normal, 1=bright)
Flash Flag Bit indicating inverse color swapping Bit 7 (0=off, 1=on)
Palette Size Number of base colors available 8 (with 7 bright variants for 15 total)
Compression Data encoding method None (raw binary)
Header Presence Existence of metadata header None

These properties ensure compatibility across ZX Spectrum emulators and tools, with no optional fields.

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

The following is a self-contained HTML snippet with embedded JavaScript, suitable for embedding in a Ghost blog post via the HTML card. It enables drag-and-drop of a .SCR file onto a designated area, validates the format, decodes the structural properties, and dumps them to the screen in a formatted list. No external dependencies are required; it uses the File API for browser compatibility.

Drag and drop a .SCR file here to view properties.

4. Python Class for .SCR Handling

This class opens a .SCR file, decodes and validates the properties, prints them to console, and supports writing a new valid .SCR file (e.g., blank screen). It uses built-in struct and bytes for binary handling.

import os

class SCRDecoder:
    FILE_SIZE = 6912
    BITMAP_SIZE = 6144
    BITMAP_OFFSET = 0
    ATTR_OFFSET = 6144
    WIDTH = 256
    HEIGHT = 192
    BYTES_PER_LINE = 32
    ATTR_COLS = 32
    ATTR_ROWS = 24

    def __init__(self, filepath=None):
        self.filepath = filepath
        self.data = None
        if filepath:
            self.load()

    def load(self):
        if not os.path.exists(self.filepath):
            raise FileNotFoundError(f".SCR file not found: {self.filepath}")
        with open(self.filepath, 'rb') as f:
            self.data = f.read()
        if len(self.data) != self.FILE_SIZE:
            raise ValueError(f"Invalid .SCR: Expected {self.FILE_SIZE} bytes, got {len(self.data)}")

    def print_properties(self):
        if not self.data:
            print("No data loaded. Call load() first.")
            return
        props = {
            'File Size': f"{len(self.data)} bytes (valid)",
            'Bitmap Offset': f"{self.BITMAP_OFFSET} bytes",
            'Bitmap Size': f"{self.BITMAP_SIZE} bytes",
            'Pixel Width': f"{self.WIDTH} pixels",
            'Pixel Height': f"{self.HEIGHT} pixels",
            'Bits per Pixel': "1 bit",
            'Bytes per Scanline': f"{self.BYTES_PER_LINE} bytes",
            'Attribute Offset': f"{self.ATTR_OFFSET} bytes",
            'Attribute Size': f"{self.FILE_SIZE - self.ATTR_OFFSET} bytes",
            'Attribute Columns': f"{self.ATTR_COLS}",
            'Attribute Rows': f"{self.ATTR_ROWS}",
            'Palette Size': "8 base colors (15 total with bright)",
            'Compression': "None",
            'Header Presence': "None"
        }
        for key, value in props.items():
            print(f"{key}: {value}")

    def write(self, output_path, bitmap_data=None, attr_data=None):
        if bitmap_data is None:
            bitmap_data = b'\x00' * self.BITMAP_SIZE  # Blank bitmap
        if attr_data is None:
            attr_data = b'\x00' * (self.FILE_SIZE - self.BITMAP_SIZE)  # Default attributes (black on black)
        if len(bitmap_data) != self.BITMAP_SIZE or len(attr_data) != (self.FILE_SIZE - self.BITMAP_SIZE):
            raise ValueError("Invalid data sizes for write.")
        with open(output_path, 'wb') as f:
            f.write(bitmap_data + attr_data)
        print(f"Wrote valid .SCR to {output_path}")

# Example usage:
# decoder = SCRDecoder('example.scr')
# decoder.print_properties()
# decoder.write('blank.scr')

5. Java Class for .SCR Handling

This class uses java.io for file I/O, decodes properties upon loading, prints them to console via System.out, and supports writing a new .SCR file. Compile with javac SCRDecoder.java and run with java SCRDecoder example.scr.

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class SCRDecoder {
    private static final int FILE_SIZE = 6912;
    private static final int BITMAP_SIZE = 6144;
    private static final int BITMAP_OFFSET = 0;
    private static final int ATTR_OFFSET = 6144;
    private static final int WIDTH = 256;
    private static final int HEIGHT = 192;
    private static final int BYTES_PER_LINE = 32;
    private static final int ATTR_COLS = 32;
    private static final int ATTR_ROWS = 24;

    private byte[] data;
    private String filepath;

    public SCRDecoder(String filepath) throws IOException {
        this.filepath = filepath;
        load();
    }

    private void load() throws IOException {
        this.data = Files.readAllBytes(Paths.get(filepath));
        if (data.length != FILE_SIZE) {
            throw new IOException("Invalid .SCR: Expected " + FILE_SIZE + " bytes, got " + data.length);
        }
    }

    public void printProperties() {
        if (data == null) {
            System.out.println("No data loaded.");
            return;
        }
        Map<String, String> props = new HashMap<>();
        props.put("File Size", data.length + " bytes (valid)");
        props.put("Bitmap Offset", BITMAP_OFFSET + " bytes");
        props.put("Bitmap Size", BITMAP_SIZE + " bytes");
        props.put("Pixel Width", WIDTH + " pixels");
        props.put("Pixel Height", HEIGHT + " pixels");
        props.put("Bits per Pixel", "1 bit");
        props.put("Bytes per Scanline", BYTES_PER_LINE + " bytes");
        props.put("Attribute Offset", ATTR_OFFSET + " bytes");
        props.put("Attribute Size", (FILE_SIZE - ATTR_OFFSET) + " bytes");
        props.put("Attribute Columns", Integer.toString(ATTR_COLS));
        props.put("Attribute Rows", Integer.toString(ATTR_ROWS));
        props.put("Palette Size", "8 base colors (15 total with bright)");
        props.put("Compression", "None");
        props.put("Header Presence", "None");
        props.forEach((key, value) -> System.out.println(key + ": " + value));
    }

    public void write(String outputPath, byte[] bitmapData, byte[] attrData) throws IOException {
        if (bitmapData.length != BITMAP_SIZE || attrData.length != (FILE_SIZE - BITMAP_SIZE)) {
            throw new IllegalArgumentException("Invalid data sizes for write.");
        }
        try (FileOutputStream fos = new FileOutputStream(outputPath)) {
            fos.write(bitmapData);
            fos.write(attrData);
        }
        System.out.println("Wrote valid .SCR to " + outputPath);
    }

    public static void main(String[] args) throws IOException {
        if (args.length == 0) {
            System.out.println("Usage: java SCRDecoder <scr-file>");
            return;
        }
        SCRDecoder decoder = new SCRDecoder(args[0]);
        decoder.printProperties();
        // Example write (blank):
        // decoder.write("blank.scr", new byte[BITMAP_SIZE], new byte[FILE_SIZE - BITMAP_SIZE]);
    }
}

6. JavaScript Class for .SCR Handling

This Node.js-compatible class uses the fs module to open files, decode properties, print to console, and write new files. Run with node scr-decoder.js example.scr. For browser use, adapt fs to File API.

const fs = require('fs');

class SCRDecoder {
  constructor(filepath = null) {
    this.FILE_SIZE = 6912;
    this.BITMAP_SIZE = 6144;
    this.BITMAP_OFFSET = 0;
    this.ATTR_OFFSET = 6144;
    this.WIDTH = 256;
    this.HEIGHT = 192;
    this.BYTES_PER_LINE = 32;
    this.ATTR_COLS = 32;
    this.ATTR_ROWS = 24;
    this.filepath = filepath;
    this.data = null;
    if (filepath) {
      this.load();
    }
  }

  load() {
    if (!fs.existsSync(this.filepath)) {
      throw new Error(`.SCR file not found: ${this.filepath}`);
    }
    this.data = fs.readFileSync(this.filepath);
    if (this.data.length !== this.FILE_SIZE) {
      throw new Error(`Invalid .SCR: Expected ${this.FILE_SIZE} bytes, got ${this.data.length}`);
    }
  }

  printProperties() {
    if (!this.data) {
      console.log('No data loaded. Call load() first.');
      return;
    }
    const props = {
      'File Size': `${this.data.length} bytes (valid)`,
      'Bitmap Offset': `${this.BITMAP_OFFSET} bytes`,
      'Bitmap Size': `${this.BITMAP_SIZE} bytes`,
      'Pixel Width': `${this.WIDTH} pixels`,
      'Pixel Height': `${this.HEIGHT} pixels`,
      'Bits per Pixel': '1 bit',
      'Bytes per Scanline': `${this.BYTES_PER_LINE} bytes`,
      'Attribute Offset': `${this.ATTR_OFFSET} bytes`,
      'Attribute Size': `${this.FILE_SIZE - this.ATTR_OFFSET} bytes`,
      'Attribute Columns': `${this.ATTR_COLS}`,
      'Attribute Rows': `${this.ATTR_ROWS}`,
      'Palette Size': '8 base colors (15 total with bright)',
      'Compression': 'None',
      'Header Presence': 'None'
    };
    Object.entries(props).forEach(([key, value]) => {
      console.log(`${key}: ${value}`);
    });
  }

  write(outputPath, bitmapData = null, attrData = null) {
    if (bitmapData === null) bitmapData = Buffer.alloc(this.BITMAP_SIZE, 0); // Blank
    if (attrData === null) attrData = Buffer.alloc(this.FILE_SIZE - this.BITMAP_SIZE, 0); // Default
    if (bitmapData.length !== this.BITMAP_SIZE || attrData.length !== (this.FILE_SIZE - this.BITMAP_SIZE)) {
      throw new Error('Invalid data sizes for write.');
    }
    fs.writeFileSync(outputPath, Buffer.concat([bitmapData, attrData]));
    console.log(`Wrote valid .SCR to ${outputPath}`);
}

// Example usage:
// const decoder = new SCRDecoder('example.scr');
// decoder.printProperties();
// decoder.write('blank.scr');

7. C Implementation for .SCR Handling

This C program defines functions equivalent to a class (load, print properties, write), using stdio.h and stdlib.h for file I/O. Compile with gcc scr_decoder.c -o scr_decoder and run ./scr_decoder example.scr. It validates and prints properties; write creates a blank file.

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

#define FILE_SIZE 6912
#define BITMAP_SIZE 6144
#define BITMAP_OFFSET 0
#define ATTR_OFFSET 6144
#define WIDTH 256
#define HEIGHT 192
#define BYTES_PER_LINE 32
#define ATTR_COLS 32
#define ATTR_ROWS 24

unsigned char *data = NULL;

int load_scr(const char *filepath) {
    FILE *fp = fopen(filepath, "rb");
    if (!fp) {
        perror("File not found");
        return 0;
    }
    data = (unsigned char *)malloc(FILE_SIZE);
    if (fread(data, 1, FILE_SIZE, fp) != FILE_SIZE) {
        free(data);
        data = NULL;
        fclose(fp);
        fprintf(stderr, "Invalid .SCR: Expected %d bytes\n", FILE_SIZE);
        return 0;
    }
    fclose(fp);
    return 1;
}

void print_properties() {
    if (!data) {
        printf("No data loaded.\n");
        return;
    }
    printf("ZX Spectrum .SCR Properties:\n");
    printf("File Size: %d bytes (valid)\n", FILE_SIZE);
    printf("Bitmap Offset: %d bytes\n", BITMAP_OFFSET);
    printf("Bitmap Size: %d bytes\n", BITMAP_SIZE);
    printf("Pixel Width: %d pixels\n", WIDTH);
    printf("Pixel Height: %d pixels\n", HEIGHT);
    printf("Bits per Pixel: 1 bit\n");
    printf("Bytes per Scanline: %d bytes\n", BYTES_PER_LINE);
    printf("Attribute Offset: %d bytes\n", ATTR_OFFSET);
    printf("Attribute Size: %d bytes\n", FILE_SIZE - ATTR_OFFSET);
    printf("Attribute Columns: %d\n", ATTR_COLS);
    printf("Attribute Rows: %d\n", ATTR_ROWS);
    printf("Palette Size: 8 base colors (15 total with bright)\n");
    printf("Compression: None\n");
    printf("Header Presence: None\n");
}

void write_scr(const char *output_path) {
    FILE *fp = fopen(output_path, "wb");
    if (!fp) {
        perror("Write failed");
        return;
    }
    // Blank data
    unsigned char blank[FILE_SIZE];
    memset(blank, 0, FILE_SIZE);
    if (fwrite(blank, 1, FILE_SIZE, fp) != FILE_SIZE) {
        perror("Write incomplete");
    } else {
        printf("Wrote valid .SCR to %s\n", output_path);
    }
    fclose(fp);
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        printf("Usage: %s <scr-file>\n", argv[0]);
        return 1;
    }
    if (!load_scr(argv[1])) {
        return 1;
    }
    print_properties();
    // Example write: write_scr("blank.scr");
    free(data);
    return 0;
}