Task 446: .NEO File Format

Task 446: .NEO File Format

.NEO File Format Specifications

The .NEO file format is used for NEOchrome bitmap images, primarily associated with the Atari ST computer system. It is a raster graphics format supporting low, medium, and high resolutions, with a fixed image size of 320x200 pixels in low resolution (16 colors). The total file size is always 32128 bytes, consisting of a 128-byte header followed by 32000 bytes of uncompressed bitmap data. The format includes provisions for color palette, animation settings, and reserved space for future expansions.

List of all properties of this file format intrinsic to its file system:

  • Flag (2 bytes): Always 0, serving as a file identifier.
  • Resolution (2 bytes): Indicates screen resolution (0 = low res, 1 = medium res, 2 = high res).
  • Palette (32 bytes): 16 words defining the color palette (each word: 4 bits unused, 3 bits red, 3 bits green, 3 bits blue, intensities 0-7).
  • Filename (12 bytes): Padded filename string, often "        .   ".
  • Color animation limits (2 bytes): High bit set if valid; low byte defines left/lower (high 4 bits) and right/upper (low 4 bits) limits.
  • Color animation speed and direction (2 bytes): High bit set if animation enabled; low byte is vblanks per step (negative for left scroll).
  • Number of color steps (2 bytes): Steps to display before advancing (for slide shows).
  • Image X offset (2 bytes): Unused, always 0.
  • Image Y offset (2 bytes): Unused, always 0.
  • Image width (2 bytes): Unused, always 320.
  • Image height (2 bytes): Unused, always 200.
  • Reserved (66 bytes): Unused space for future expansion.
  • Picture data (32000 bytes): Raw bitmap screen memory data.

Two direct download links for files of format .NEO:

Ghost blog embedded HTML JavaScript for drag and drop .NEO file dump:

.NEO File Parser
Drag and drop a .NEO file here
  1. Python class for .NEO file handling:
import struct
import sys

class NeoFile:
    def __init__(self, filepath):
        self.filepath = filepath
        self.flag = None
        self.resolution = None
        self.palette = []
        self.filename = None
        self.color_anim_limits = None
        self.color_anim_speed_dir = None
        self.color_steps = None
        self.x_offset = None
        self.y_offset = None
        self.width = None
        self.height = None
        self.reserved = None
        self.picture_data = None

    def read(self):
        with open(self.filepath, 'rb') as f:
            data = f.read()
            if len(data) != 32128:
                raise ValueError("Invalid .NEO file size")

            # Unpack header
            self.flag = struct.unpack_from('>H', data, 0)[0]
            self.resolution = struct.unpack_from('>H', data, 2)[0]
            for i in range(16):
                color = struct.unpack_from('>H', data, 4 + i*2)[0]
                r = (color >> 8) & 0x7
                g = (color >> 4) & 0x7
                b = color & 0x7
                self.palette.append((r, g, b, hex(color)))
            self.filename = data[36:48].decode('ascii', errors='ignore').strip()
            self.color_anim_limits = struct.unpack_from('>H', data, 48)[0]
            self.color_anim_speed_dir = struct.unpack_from('>H', data, 50)[0]
            self.color_steps = struct.unpack_from('>H', data, 52)[0]
            self.x_offset = struct.unpack_from('>H', data, 54)[0]
            self.y_offset = struct.unpack_from('>H', data, 56)[0]
            self.width = struct.unpack_from('>H', data, 58)[0]
            self.height = struct.unpack_from('>H', data, 60)[0]
            self.reserved = data[62:128]
            self.picture_data = data[128:]

    def print_properties(self):
        print(f"Flag: {self.flag}")
        print(f"Resolution: {self.resolution}")
        print("Palette:")
        for i, (r, g, b, raw) in enumerate(self.palette):
            print(f"  Color {i}: R={r}, G={g}, B={b} (raw: {raw})")
        print(f"Filename: {self.filename}")
        print(f"Color animation limits: {self.color_anim_limits}")
        print(f"Color animation speed and direction: {self.color_anim_speed_dir}")
        print(f"Number of color steps: {self.color_steps}")
        print(f"Image X offset: {self.x_offset}")
        print(f"Image Y offset: {self.y_offset}")
        print(f"Image width: {self.width}")
        print(f"Image height: {self.height}")
        print(f"Reserved size: {len(self.reserved)} bytes")
        print(f"Picture data size: {len(self.picture_data)} bytes")

    def write(self, new_filepath=None):
        if not new_filepath:
            new_filepath = self.filepath
        with open(new_filepath, 'wb') as f:
            f.write(struct.pack('>H', self.flag))
            f.write(struct.pack('>H', self.resolution))
            for r, g, b, _ in self.palette:
                color = ((r & 0x7) << 8) | ((g & 0x7) << 4) | (b & 0x7)
                f.write(struct.pack('>H', color))
            f.write(self.filename.ljust(12, b' ').encode('ascii'))
            f.write(struct.pack('>H', self.color_anim_limits))
            f.write(struct.pack('>H', self.color_anim_speed_dir))
            f.write(struct.pack('>H', self.color_steps))
            f.write(struct.pack('>H', self.x_offset))
            f.write(struct.pack('>H', self.y_offset))
            f.write(struct.pack('>H', self.width))
            f.write(struct.pack('>H', self.height))
            f.write(self.reserved)
            f.write(self.picture_data)

# Example usage:
# if __name__ == "__main__":
#     neo = NeoFile("example.neo")
#     neo.read()
#     neo.print_properties()
#     neo.write("output.neo")
  1. Java class for .NEO file handling:
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

public class NeoFile {
    private String filepath;
    private int flag;
    private int resolution;
    private int[] palette = new int[16]; // raw values
    private String filename;
    private int colorAnimLimits;
    private int colorAnimSpeedDir;
    private int colorSteps;
    private int xOffset;
    private int yOffset;
    private int width;
    private int height;
    private byte[] reserved = new byte[66];
    private byte[] pictureData = new byte[32000];

    public NeoFile(String filepath) {
        this.filepath = filepath;
    }

    public void read() throws IOException {
        try (FileInputStream fis = new FileInputStream(filepath)) {
            byte[] data = new byte[32128];
            if (fis.read(data) != 32128) {
                throw new IOException("Invalid .NEO file size");
            }
            ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);

            flag = bb.getShort(0) & 0xFFFF;
            resolution = bb.getShort(2) & 0xFFFF;
            for (int i = 0; i < 16; i++) {
                palette[i] = bb.getShort(4 + i * 2) & 0xFFFF;
            }
            byte[] fnBytes = new byte[12];
            bb.position(36);
            bb.get(fnBytes);
            filename = new String(fnBytes).trim();
            colorAnimLimits = bb.getShort(48) & 0xFFFF;
            colorAnimSpeedDir = bb.getShort(50) & 0xFFFF;
            colorSteps = bb.getShort(52) & 0xFFFF;
            xOffset = bb.getShort(54) & 0xFFFF;
            yOffset = bb.getShort(56) & 0xFFFF;
            width = bb.getShort(58) & 0xFFFF;
            height = bb.getShort(60) & 0xFFFF;
            bb.position(62);
            bb.get(reserved);
            bb.position(128);
            bb.get(pictureData);
        }
    }

    public void printProperties() {
        System.out.println("Flag: " + flag);
        System.out.println("Resolution: " + resolution);
        System.out.println("Palette:");
        for (int i = 0; i < 16; i++) {
            int color = palette[i];
            int r = (color >> 8) & 0x7;
            int g = (color >> 4) & 0x7;
            int b = color & 0x7;
            System.out.printf("  Color %d: R=%d, G=%d, B=%d (raw: 0x%X)\n", i, r, g, b, color);
        }
        System.out.println("Filename: " + filename);
        System.out.println("Color animation limits: " + colorAnimLimits);
        System.out.println("Color animation speed and direction: " + colorAnimSpeedDir);
        System.out.println("Number of color steps: " + colorSteps);
        System.out.println("Image X offset: " + xOffset);
        System.out.println("Image Y offset: " + yOffset);
        System.out.println("Image width: " + width);
        System.out.println("Image height: " + height);
        System.out.println("Reserved size: " + reserved.length + " bytes");
        System.out.println("Picture data size: " + pictureData.length + " bytes");
    }

    public void write(String newFilepath) throws IOException {
        if (newFilepath == null) {
            newFilepath = filepath;
        }
        try (FileOutputStream fos = new FileOutputStream(newFilepath)) {
            ByteBuffer bb = ByteBuffer.allocate(32128).order(ByteOrder.BIG_ENDIAN);
            bb.putShort((short) flag);
            bb.putShort((short) resolution);
            for (int color : palette) {
                bb.putShort((short) color);
            }
            byte[] fnBytes = filename.getBytes();
            for (int i = 0; i < 12; i++) {
                bb.put(i < fnBytes.length ? fnBytes[i] : (byte) ' ');
            }
            bb.putShort((short) colorAnimLimits);
            bb.putShort((short) colorAnimSpeedDir);
            bb.putShort((short) colorSteps);
            bb.putShort((short) xOffset);
            bb.putShort((short) yOffset);
            bb.putShort((short) width);
            bb.putShort((short) height);
            bb.put(reserved);
            bb.put(pictureData);
            fos.write(bb.array());
        }
    }

    // Example usage:
    // public static void main(String[] args) throws IOException {
    //     NeoFile neo = new NeoFile("example.neo");
    //     neo.read();
    //     neo.printProperties();
    //     neo.write("output.neo");
    // }
}
  1. JavaScript class for .NEO file handling (Node.js compatible):
const fs = require('fs');

class NeoFile {
    constructor(filepath) {
        this.filepath = filepath;
        this.flag = null;
        this.resolution = null;
        this.palette = [];
        this.filename = null;
        this.colorAnimLimits = null;
        this.colorAnimSpeedDir = null;
        this.colorSteps = null;
        this.xOffset = null;
        this.yOffset = null;
        this.width = null;
        this.height = null;
        this.reserved = null;
        this.pictureData = null;
    }

    read() {
        const data = fs.readFileSync(this.filepath);
        if (data.length !== 32128) {
            throw new Error('Invalid .NEO file size');
        }
        const dv = new DataView(data.buffer);

        this.flag = dv.getUint16(0, false);
        this.resolution = dv.getUint16(2, false);
        for (let i = 0; i < 16; i++) {
            const color = dv.getUint16(4 + i * 2, false);
            const r = (color >> 8) & 0x7;
            const g = (color >> 4) & 0x7;
            const b = color & 0x7;
            this.palette.push({ r, g, b, raw: color.toString(16) });
        }
        let fn = '';
        for (let i = 36; i < 48; i++) {
            const char = dv.getUint8(i);
            fn += char ? String.fromCharCode(char) : ' ';
        }
        this.filename = fn.trim();
        this.colorAnimLimits = dv.getUint16(48, false);
        this.colorAnimSpeedDir = dv.getUint16(50, false);
        this.colorSteps = dv.getUint16(52, false);
        this.xOffset = dv.getUint16(54, false);
        this.yOffset = dv.getUint16(56, false);
        this.width = dv.getUint16(58, false);
        this.height = dv.getUint16(60, false);
        this.reserved = data.slice(62, 128);
        this.pictureData = data.slice(128);
    }

    printProperties() {
        console.log(`Flag: ${this.flag}`);
        console.log(`Resolution: ${this.resolution}`);
        console.log('Palette:');
        this.palette.forEach((col, i) => {
            console.log(`  Color ${i}: R=${col.r}, G=${col.g}, B=${col.b} (raw: 0x${col.raw})`);
        });
        console.log(`Filename: ${this.filename}`);
        console.log(`Color animation limits: ${this.colorAnimLimits}`);
        console.log(`Color animation speed and direction: ${this.colorAnimSpeedDir}`);
        console.log(`Number of color steps: ${this.colorSteps}`);
        console.log(`Image X offset: ${this.xOffset}`);
        console.log(`Image Y offset: ${this.yOffset}`);
        console.log(`Image width: ${this.width}`);
        console.log(`Image height: ${this.height}`);
        console.log(`Reserved size: ${this.reserved.length} bytes`);
        console.log(`Picture data size: ${this.pictureData.length} bytes`);
    }

    write(newFilepath = this.filepath) {
        const buffer = Buffer.alloc(32128);
        const dv = new DataView(buffer.buffer);

        dv.setUint16(0, this.flag, false);
        dv.setUint16(2, this.resolution, false);
        this.palette.forEach((col, i) => {
            const color = ((col.r & 0x7) << 8) | ((col.g & 0x7) << 4) | (col.b & 0x7);
            dv.setUint16(4 + i * 2, color, false);
        });
        const fnBytes = Buffer.from(this.filename.padEnd(12, ' '));
        for (let i = 0; i < 12; i++) {
            dv.setUint8(36 + i, fnBytes[i]);
        }
        dv.setUint16(48, this.colorAnimLimits, false);
        dv.setUint16(50, this.colorAnimSpeedDir, false);
        dv.setUint16(52, this.colorSteps, false);
        dv.setUint16(54, this.xOffset, false);
        dv.setUint16(56, this.yOffset, false);
        dv.setUint16(58, this.width, false);
        dv.setUint16(60, this.height, false);
        buffer.copy(this.reserved, 62, 0, 66);
        buffer.copy(this.pictureData, 128, 0, 32000);

        fs.writeFileSync(newFilepath, buffer);
    }
}

// Example usage:
// const neo = new NeoFile('example.neo');
// neo.read();
// neo.printProperties();
// neo.write('output.neo');
  1. C "class" for .NEO file handling (using struct and functions):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef struct {
    char* filepath;
    uint16_t flag;
    uint16_t resolution;
    uint16_t palette[16];
    char filename[13];
    uint16_t color_anim_limits;
    uint16_t color_anim_speed_dir;
    uint16_t color_steps;
    uint16_t x_offset;
    uint16_t y_offset;
    uint16_t width;
    uint16_t height;
    uint8_t reserved[66];
    uint8_t* picture_data;
} NeoFile;

NeoFile* neo_create(const char* filepath) {
    NeoFile* neo = (NeoFile*)malloc(sizeof(NeoFile));
    neo->filepath = strdup(filepath);
    neo->picture_data = NULL;
    return neo;
}

void neo_destroy(NeoFile* neo) {
    free(neo->filepath);
    if (neo->picture_data) free(neo->picture_data);
    free(neo);
}

int neo_read(NeoFile* neo) {
    FILE* f = fopen(neo->filepath, "rb");
    if (!f) return -1;

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);
    if (size != 32128) {
        fclose(f);
        return -1;
    }

    uint8_t* data = (uint8_t*)malloc(32128);
    fread(data, 1, 32128, f);
    fclose(f);

    neo->flag = (data[0] << 8) | data[1];
    neo->resolution = (data[2] << 8) | data[3];
    for (int i = 0; i < 16; i++) {
        neo->palette[i] = (data[4 + i*2] << 8) | data[4 + i*2 + 1];
    }
    memcpy(neo->filename, &data[36], 12);
    neo->filename[12] = '\0';
    // Trim trailing spaces
    for (int i = 11; i >= 0; i--) {
        if (neo->filename[i] != ' ') break;
        neo->filename[i] = '\0';
    }
    neo->color_anim_limits = (data[48] << 8) | data[49];
    neo->color_anim_speed_dir = (data[50] << 8) | data[51];
    neo->color_steps = (data[52] << 8) | data[53];
    neo->x_offset = (data[54] << 8) | data[55];
    neo->y_offset = (data[56] << 8) | data[57];
    neo->width = (data[58] << 8) | data[59];
    neo->height = (data[60] << 8) | data[61];
    memcpy(neo->reserved, &data[62], 66);
    neo->picture_data = (uint8_t*)malloc(32000);
    memcpy(neo->picture_data, &data[128], 32000);

    free(data);
    return 0;
}

void neo_print_properties(const NeoFile* neo) {
    printf("Flag: %u\n", neo->flag);
    printf("Resolution: %u\n", neo->resolution);
    printf("Palette:\n");
    for (int i = 0; i < 16; i++) {
        uint16_t color = neo->palette[i];
        uint8_t r = (color >> 8) & 0x7;
        uint8_t g = (color >> 4) & 0x7;
        uint8_t b = color & 0x7;
        printf("  Color %d: R=%u, G=%u, B=%u (raw: 0x%X)\n", i, r, g, b, color);
    }
    printf("Filename: %s\n", neo->filename);
    printf("Color animation limits: %u\n", neo->color_anim_limits);
    printf("Color animation speed and direction: %u\n", neo->color_anim_speed_dir);
    printf("Number of color steps: %u\n", neo->color_steps);
    printf("Image X offset: %u\n", neo->x_offset);
    printf("Image Y offset: %u\n", neo->y_offset);
    printf("Image width: %u\n", neo->width);
    printf("Image height: %u\n", neo->height);
    printf("Reserved size: 66 bytes\n");
    printf("Picture data size: 32000 bytes\n");
}

int neo_write(const NeoFile* neo, const char* new_filepath) {
    const char* path = new_filepath ? new_filepath : neo->filepath;
    FILE* f = fopen(path, "wb");
    if (!f) return -1;

    uint8_t header[128];
    memset(header, 0, 128);

    header[0] = (neo->flag >> 8) & 0xFF;
    header[1] = neo->flag & 0xFF;
    header[2] = (neo->resolution >> 8) & 0xFF;
    header[3] = neo->resolution & 0xFF;
    for (int i = 0; i < 16; i++) {
        header[4 + i*2] = (neo->palette[i] >> 8) & 0xFF;
        header[4 + i*2 + 1] = neo->palette[i] & 0xFF;
    }
    size_t fn_len = strlen(neo->filename);
    memcpy(&header[36], neo->filename, fn_len < 12 ? fn_len : 12);
    for (int i = fn_len; i < 12; i++) {
        header[36 + i] = ' ';
    }
    header[48] = (neo->color_anim_limits >> 8) & 0xFF;
    header[49] = neo->color_anim_limits & 0xFF;
    header[50] = (neo->color_anim_speed_dir >> 8) & 0xFF;
    header[51] = neo->color_anim_speed_dir & 0xFF;
    header[52] = (neo->color_steps >> 8) & 0xFF;
    header[53] = neo->color_steps & 0xFF;
    header[54] = (neo->x_offset >> 8) & 0xFF;
    header[55] = neo->x_offset & 0xFF;
    header[56] = (neo->y_offset >> 8) & 0xFF;
    header[57] = neo->y_offset & 0xFF;
    header[58] = (neo->width >> 8) & 0xFF;
    header[59] = neo->width & 0xFF;
    header[60] = (neo->height >> 8) & 0xFF;
    header[61] = neo->height & 0xFF;
    memcpy(&header[62], neo->reserved, 66);

    fwrite(header, 1, 128, f);
    fwrite(neo->picture_data, 1, 32000, f);
    fclose(f);
    return 0;
}

// Example usage:
// int main() {
//     NeoFile* neo = neo_create("example.neo");
//     if (neo_read(neo) == 0) {
//         neo_print_properties(neo);
//         neo_write(neo, "output.neo");
//     }
//     neo_destroy(neo);
//     return 0;
// }