converting-images-to-nes-chr-format.md
2026-05-11 by h2k1 #nesdev #graphics
NES PPU stores each tile in a 8x8 bit planar format. Each pixel has 2 bit to describe its palette index. So each tile weights 16 bytes, and each bit plane is 64 bits or 8 bytes.
It is easy to convert a grayscale PNG file to NES CHR format. For each 8x8 pixels tile, read each pixel, get its red (or green/blue) value, divide by 64 to get the value in range 0-3. Write each bit in seperate planes.
import { Jimp } from "jimp";
import { intToRGBA } from "@jimp/utils";
const picture = "picture.png"
let output = ""
let image = await Jimp.read(picture);
const ww = 32
for (let yy = 0; yy < 16; yy++) {
for (let xx = 0; xx < ww; xx++) {
let plane0 = "; plane0\n";
let plane1 = "; plane1\n";
for (let y = 0; y < 8; y++) {
plane0 += ".byte %";
plane1 += ".byte %";
for (let x = 0; x < 8; x++) {
const color = Math.floor(
intToRGBA(image.getPixelColor(x + xx * 8, y + yy * 8)).r / 64,
);
plane0 += color == 1 || color == 3 ? "1" : "0";
plane1 += color == 2 || color == 3 ? "1" : "0";
}
plane0 += "\n";
plane1 += "\n";
}
output += plane0;
output += plane1;
}
}
