You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
201 lines
7.5 KiB
201 lines
7.5 KiB
import binascii
|
|
import numpy
|
|
import png
|
|
import os
|
|
import time
|
|
from tkinter import Tk, Frame, Label, Button, TOP, BOTTOM, \
|
|
DISABLED, BOTH, PhotoImage, Canvas, StringVar
|
|
from tkinter.filedialog import askopenfilename
|
|
from tkinter.filedialog import askdirectory
|
|
|
|
|
|
PALETTE = [(0,0,0), (255,0,0), (0,0,255), (255,255,255)]
|
|
|
|
|
|
class chr_to_png(Tk):
|
|
def __init__(self, parent):
|
|
Tk.__init__(self, parent)
|
|
self.parent = parent
|
|
self.initialize()
|
|
|
|
def initialize(self):
|
|
# menu left
|
|
self.clearVars()
|
|
self.frame_w = Frame(self, width=100, bg="black")
|
|
self.frame_w2 = Frame(self.frame_w, width=100, height=150, bg='#dfdfdf')
|
|
|
|
self.left_title = Label(self.frame_w2, text='', bg='#dfdfdf')
|
|
self.left_title.pack()
|
|
self.s_title = Label(self.frame_w2, text='# @_#E', bg='#dfdfdf')
|
|
self.s_title.pack(side=BOTTOM)
|
|
|
|
self.but_file = Button(self.frame_w2, text='Dump file', command=self.getPath)
|
|
self.but_fold = Button(self.frame_w2, text='Dump folder', command=self.getPathDir)
|
|
self.but_sdir = Button(self.frame_w2, state=DISABLED, text='Save to...', command=self.setDirectory)
|
|
self.but_conv = Button(self.frame_w2, state=DISABLED, text='Go', command=self.convertFile)
|
|
self.but_exit = Button(self.frame_w2, text='Exit', command=lambda:exit())
|
|
self.but_file.pack(fill=BOTH)
|
|
self.but_fold.pack(fill=BOTH)
|
|
self.but_sdir.pack(fill=BOTH)
|
|
self.but_conv.pack(fill=BOTH)
|
|
self.but_exit.pack(fill=BOTH, side=BOTTOM)
|
|
famicom_img = PhotoImage(file = 'images/img.png')
|
|
famicom_label = Label(self.frame_w2, image=famicom_img)
|
|
famicom_label.image = famicom_img
|
|
famicom_label.pack(fill=BOTH, expand=True)
|
|
self.frame_w2.pack(side=TOP, fill=BOTH, expand=True)
|
|
|
|
# right area
|
|
self.frame_e = Frame(self, bg="#dfdfdf")
|
|
|
|
self.some_title = Label(self.frame_e, text="__ by nnov 2017 __ vaporw8bit ___", bg="#dfdfdf")
|
|
self.some_title.pack()
|
|
self.canvas_area = Canvas(self, width=500, height=400, background="#ffffff")
|
|
self.canvas_area.grid(row=1, column=1)
|
|
|
|
back_img = PhotoImage(file = 'images/back.png')
|
|
back_label = Label(self.canvas_area, image=back_img)
|
|
back_label.image = back_img
|
|
back_label.pack(fill=BOTH, expand=True)
|
|
|
|
# status
|
|
self.status_frame = Frame(self)
|
|
self.labelVariable = StringVar()
|
|
self.status = Label(self.status_frame, textvariable=self.labelVariable, anchor="w", fg="white", bg="purple")
|
|
self.status.pack(fill=BOTH, expand=True)
|
|
self.labelVariable.set('Please select a CHR file.')
|
|
|
|
self.frame_w.grid(row=0, column=0, rowspan=2, sticky="nsew")
|
|
self.frame_e.grid(row=0, column=1, sticky="ew")
|
|
self.canvas_area.grid(row=1, column=1, sticky="nsew")
|
|
self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")
|
|
|
|
self.grid_rowconfigure(1, weight=1)
|
|
self.grid_columnconfigure(1, weight=1)
|
|
|
|
self.resizable(False,False)
|
|
|
|
def createLog(self, dump_dir, num_good, num_bad, bad_files, date_start, date_end):
|
|
with open(dump_dir + '/LOG.TXT', 'w') as execution_log:
|
|
execution_log.write('EXECUTION COMPLETE.\n')
|
|
execution_log.write('Started: ' + date_start + '\n')
|
|
execution_log.write('Finished: ' + date_end + '\n')
|
|
execution_log.write('_'*50 + '\n')
|
|
execution_log.write('Total files processed: ' + str(num_good + num_bad) + '\n')
|
|
execution_log.write('Successfully converted: ' + str(num_good) + '\n')
|
|
execution_log.write('Not converted (ERROR): ' + str(num_bad) + '\n')
|
|
execution_log.write('_'*50 + '\n')
|
|
[ execution_log.write(x + '\n') for x in bad_files.split('@@')]
|
|
execution_log.write('_'*50 + '\n')
|
|
|
|
def clearVars(self):
|
|
self.save_dir = ''
|
|
self.filename = ''
|
|
|
|
def configOptions(self, enable_disable):
|
|
if enable_disable:
|
|
self.but_sdir['state'] = NORMAL
|
|
self.but_conv['state'] = NORMAL
|
|
else:
|
|
self.but_sdir['state'] = DISABLED
|
|
self.but_conv['state'] = DISABLED
|
|
|
|
def getPath(self):
|
|
self.filename = askopenfilename(filetypes=[('All files', '*'), ('CHR files','*.chr'), ('NES files', '*.nes')])
|
|
if self.filename:
|
|
self.labelVariable.set(os.path.split(self.filename)[1] + ' selected.')
|
|
self.save_dir = os.path.split(self.filename)[0]
|
|
self.configOptions(True)
|
|
|
|
def getPathDir(self):
|
|
self.filename = askdirectory()
|
|
if self.filename:
|
|
self.save_dir = self.filename
|
|
self.labelVariable.set('".../' + self.filename.split('/')[-1] + '" directory selected.')
|
|
self.configOptions(True)
|
|
|
|
def setDirectory(self):
|
|
self.save_dir = askdirectory()
|
|
|
|
def convertFile(self):
|
|
self.configOptions(False)
|
|
if (self.filename == ''):
|
|
self.labelVariable.set('Please enter a valid file or directory.')
|
|
if not (self.filename.lower().endswith('.chr') or self.filename.lower().endswith('.nes')):
|
|
dump_dir = self.filename
|
|
good_files = 0
|
|
bad_files = 0
|
|
list_bad_files = ''
|
|
starting_at = time.strftime('%x - %X')
|
|
self.labelVariable.set('Processing files.')
|
|
|
|
for a_file in sorted([x for x in \
|
|
os.listdir(self.filename) if (x.lower().endswith('.chr') or x.lower().endswith('.nes')) ]):
|
|
self.filename = dump_dir + '/' + a_file
|
|
try:
|
|
self.converter()
|
|
good_files += 1
|
|
except:
|
|
bad_files += 1
|
|
list_bad_files += '@@' + a_file
|
|
self.createLog(dump_dir, good_files, bad_files, list_bad_files, starting_at, time.strftime('%x - %X'))
|
|
self.labelVariable.set(str(good_files + bad_files) + ' files processed. See LOG.TXT for info.')
|
|
else:
|
|
try:
|
|
self.converter()
|
|
self.labelVariable.set(os.path.split(self.filename)[1] + ' was converted successfully.')
|
|
except:
|
|
self.labelVariable.set('There was an error while processing ' + os.path.split(self.filename)[1] + '.')
|
|
self.clearVars();
|
|
self.configOptions(True)
|
|
|
|
def converter(self):
|
|
PALETTE = [(0,0,0), (255,0,0), (0,0,255), (255,255,255)]
|
|
TILES_IN_ROW = 128
|
|
|
|
with open(self.filename, 'rb') as chr_file:
|
|
in_hex = str(binascii.hexlify(chr_file.read()))[2:-1]
|
|
|
|
tiles = [ in_hex[index:index + 32] for index in range(0, len(in_hex), 32) ]
|
|
SIZE_CHR = len(tiles)
|
|
|
|
mega_pixel = list();
|
|
IMG_HTILES = int(SIZE_CHR/TILES_IN_ROW)
|
|
|
|
row_count = 1
|
|
column_count = 0
|
|
|
|
for tile in tiles:
|
|
column_count += 1
|
|
if column_count == 17:
|
|
row_count + 1
|
|
column_count = 1
|
|
decode = [ x + 2*y for x, y in zip(\
|
|
[int(x) for x in list(bin(int(tile[:16], 16))[2:].zfill(64))], \
|
|
[int(x) for x in list(bin(int(tile[16:], 16))[2:].zfill(64))] ) ]
|
|
mega_pixel.append([ decode[index:index + 8] for index in range(0, len(decode), 8) ])
|
|
|
|
reshape = [ mega_pixel[index:index + IMG_HTILES] for index in range(0, SIZE_CHR, IMG_HTILES) ]
|
|
|
|
reshape.append([[[0,0,0,0,0,0,0,0]]*8])
|
|
|
|
if len(reshape[-1]) < len(reshape[-2]):
|
|
fill = len(reshape[-2]) - len(reshape[-1])
|
|
[ reshape[-1].append([[0,0,0,0,0,0,0,0]]*8) for i in range(0, fill) ]
|
|
|
|
result = [ entry for sublist in \
|
|
[ numpy.hstack(reshape[row]) for row in \
|
|
range(0, len(reshape)) ] for entry in sublist ]
|
|
|
|
png_file = open(self.save_dir + '/' + os.path.split(self.filename)[1] + '.png', 'wb')
|
|
file_writer = png.Writer(len(result[0]), len(result), palette=PALETTE)
|
|
file_writer.write(png_file, result)
|
|
png_file.close()
|
|
|
|
if __name__ == "__main__":
|
|
gui = chr_to_png(None)
|
|
gui.title('NES & FC CHR to PNG')
|
|
gui.geometry('400x300')
|
|
gui.filename = 'No CHR file selected.'
|
|
gui.mainloop()
|