💾 Archived View for gemini.sensorstation.co › ~winduptoy › note.uxn-indexed-to-chr.gmi captured on 2023-06-16 at 16:13:21. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-06-14)

-=-=-=-=-=-=-

indexed-to-chr

2021-09-14

This Python script converts an indexed image to the 2-bits-per-pixel "chr" format used by the Uxn emulator's PPU.

0x00 0x00 0x01 0x01 0x02 0x02 0x03 0x03
                  ...
└──────────────────┬──────────────────┘
                   ▼
         0b00110011 0b00001111

Preparation

Using GIMP, open your image and change the color mode to indexed by selecting "Image → Mode → Indexed...". Then, select either "Generate optimum palette" with a maximum of 4 colors, or "Use a custom palette" and select a palette you have created with exactly four colors.

Then export your raw image data by selecting "File → Export..." and at the bottom of the file dialog, select a file type of "Raw image data". After exporting, you will have two files. The file with a .data extension contains the indexed pixel color data starting from the top left corner, one byte per pixel (color one is 0x00, color two is 0x01, color three is 0x02, color four is 0x03). The second with a .pal extension indicates the palette used for each color; the first three bytes indicate the RGB components for color one, the next three bytes for color two, and so on.

The image dimensions must be evenly divisible by 8, the dimensions of a sprite.

Usage

./indexed-to-chr.py [input] [output] [width]
./indexed-to-chr.py input.data output.chr 12

Code

#!/usr/bin/env python3

'''
Converts images with up to 4 indexed colors into the UXN 2-bits-per-pixel CHR format.

Usage:

- input: path to raw image input, where each pixel is one byte between 0x00 and 0x03 inclusive
- output: path to chr output file
- width: image width in number of 8x8 sprites (not pixels)

./indexed-to-chr.py [input] [output] [width]
./indexed-to-chr.py input.data output.chr 12
'''

import sys

WIDTH = int(sys.argv[3])

with open(sys.argv[1], "rb") as input_file, open(sys.argv[2], "wb") as output_file:
    data = input_file.read()
    sprite = 0

    while sprite * 8 * 8 < len(data):
        sprite_y = sprite // WIDTH
        sprite_x = sprite %  WIDTH

        pixel_y = sprite_y * 8
        pixel_x = sprite_x * 8

        # write a sprite
        low = bytearray(8)
        high = bytearray(8)

        for y in range(8):
            offset = (pixel_y + y) * WIDTH * 8 + pixel_x
            row = data[slice(offset, offset+8)]
            for x in range(8): # mask, shift, and combine
                low[y]  |= (row[x] & 0b00000001) << (7-x)
                high[y] |= ((row[x] & 0b00000010) >> 1) << (7-x)

        output_file.write(bytes(low))
        output_file.write(bytes(high))

        sprite += 1

Resources

Compudanzas has an excellent tutorial explaining sprite usage in Uxn.

uxn tutorial: day 2, the screen