Skip to content

Instantly share code, notes, and snippets.

@bbbradsmith
Last active June 24, 2023 02:04
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bbbradsmith/da99f4dacd48d8b3dfa5e824848dc21b to your computer and use it in GitHub Desktop.
Save bbbradsmith/da99f4dacd48d8b3dfa5e824848dc21b to your computer and use it in GitHub Desktop.
NES blocky graphics converter - Details: https://www.patreon.com/posts/22735126
#!/usr/bin/env python3
# nes_blocky.py
# Brad Smith, 2018
# http://rainwarrior.ca
#
# Finds .NES ROM files in the current folder,
# and generates "blocky" pixellated versions of each,
# if the game does not use CHR-RAM.
import sys
import os
import random
assert sys.version_info[0] >= 3, "Python 3 required."
# make this true to overwrite existing files
overwrite = False
# turn 16 byte CHR tile into 64 pixels, valued 0-3
def chr_to_pixel(tile):
pixels = bytearray([0]*64)
for y in range(0,8):
for x in range(0,8):
plane0 = ((tile[y+0] << x) & 0x80) >> 7
plane1 = ((tile[y+8] << x) & 0x80) >> 6
pixel = plane0 | plane1
pixels[(y*8)+x] = pixel
return pixels
# turn 64 pixels into 16 byte CHR tile
def pixel_to_chr(pixels):
tile = bytearray([0]*16)
for y in range(0,8):
plane0 = 0
plane1 = 0
for x in range(0,8):
pixel = pixels[(y*8)+x]
plane0 <<= 1
plane1 <<= 1
plane0 |= (pixel & 1)
plane1 |= (pixel & 2) >> 1
tile[y+0] = plane0
tile[y+8] = plane1
return tile
# blockify an 8x8 pixel tile
def blocky(pixels,block):
# blockify tile
y = 0
while y < 8:
x = 0
while x < 8:
# count the pixels of each colour in the block
freq = [0,0,0,0]
seed = 0
for by in range(0,block):
for bx in range(0,block):
p = pixels[((y+by)*8)+(x+bx)]
freq[p] += 1
seed += (p * (bx+7) * (by+13)) # simple hash random seed
random.seed(seed) # keep randomized tie-breaking consistent
# determine the most common colour
most = 0
#most_freq = freq[0]
most_freq = 0.75 # only choose 0 if no other coloured pixel
freq[1] += 0.1
freq[2] += 0.2
freq[3] += 0.3 # favour colour 3 in ties
for i in range(1,4):
if freq[i] > most_freq:
most = i
most_freq = freq[i]
# fill the block
for by in range(0,block):
for bx in range(0,block):
pixels[((y+by)*8)+(x+bx)] = most
# next block
x += block
y += block
# for debugging
def print_pixel(pixels):
for y in range(0,8):
s = "%d:" % y
for x in range(0,8):
s += " %d" % pixels[(y*8)+x]
print(s)
# blockify a CHR=ROM
def blocky_chr(chr_rom,block):
for i in range(0,len(chr_rom),16):
tile = chr_rom[i:i+16]
pixels = chr_to_pixel(tile)
#print("Tile %d" % (i/16))
#print_pixel(pixels)
blocky(pixels,block)
#print("Blocky %d" % (i/16))
#print_pixel(pixels)
tile = pixel_to_chr(pixels)
for j in range(0,16):
chr_rom[i+j] = tile[j]
return
# create a blocky version of a ROM
def blocky_nes(rom,filename,block):
filename_out = "%s (%dx%d)%s" % (os.path.splitext(filename)[0],8/block,8/block,os.path.splitext(filename)[1])
if not overwrite and os.path.exists(filename_out):
print("File already exists: "+filename_out)
return
prg_size = rom[4] * 16 * 1024
chr_size = rom[5] * 8 * 1024
chr_rom = bytearray(rom[16+prg_size:16+prg_size+chr_size])
blocky_chr(chr_rom,block)
f = open(filename_out,"wb")
f.write(rom[0:16+prg_size])
f.write(chr_rom)
f.close()
print("Saved: "+filename_out)
return
# create 2x2, 4x4, and 8x8 blocky versions of a ROM
def blocky_nes_group(filename):
if not os.path.exists(filename):
print("Error! File not found: " + filename)
return
print("Processing: "+filename)
rom = open(filename,"rb").read()
prg_size = rom[4] * 16 * 1024
chr_size = rom[5] * 8 * 1024
if chr_size < 1:
print("Error! No CHR-ROM present.")
return
if len(rom) < (16+prg_size+chr_size):
print("Error! ROM is missing data.")
return
if len(rom) > (16+prg_size+chr_size):
print("Error! ROM has extra data.")
return
blocky_nes(rom,filename,2)
blocky_nes(rom,filename,4)
blocky_nes(rom,filename,8)
# to remove generated files
def cleanup():
for f in os.listdir():
if ("(4x4)" in f) or ("(2x2)" in f) or ("(1x1)" in f):
print("Deleting: %s" % f)
os.remove(f)
# main program
# find all .NES files in the current directory and blockify them
random.seed()
for filename in os.listdir():
f = filename.lower()
if ("(4x4)" in f) or ("(2x2)" in f) or ("(1x1)" in f):
continue
if (f.endswith(".nes")):
blocky_nes_group(filename)
# end of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment