''' No license for now ''' import os from array import array from struct import unpack import const pyqt_version = 0 skip_pyqt5 = "PYQT4" in os.environ if not skip_pyqt5: try: from PyQt5 import QtGui from PyQt5.QtGui import QImage, QPixmap, QColor, QPainter pyqt_version = 5 except ImportError: print("Missing PyQt5, trying PyQt4...") if pyqt_version == 0: try: from PyQt4 import QtGui from PyQt4.QtGui import QImage, QPixmap, QColor, QPainter pyqt_version = 4 except ImportError: print("Missing PyQt4 dependencies") raise bg_color = QColor(0, 0, 128) bg_trans = QColor(0, 0, 0, 0) def create_tile_indexed(data): ''' Creates a QImage of a SNES tile. Useful for assigning palettes later. DO NOT USE OUTSIDE OF QApplication CONTEXT ''' planes = len(data)//8 tile = array('B', range(64)) img = QImage(8, 8, QImage.Format_Indexed8) imgbits = img.bits() imgbits.setsize(img.byteCount()) if planes == 0: raise ValueError("Empty bytes passed") if planes == 1: for i, (j, x) in enumerate([(j,x) for j in range(8) for x in reversed(range(8))]): tile[i] = (data[j] >> x & 1) else: for i, (j, x) in enumerate([(j,x) for j in range(0, 16, 2) for x in reversed(range(8))]): tile[i] = (data[j] >> x & 1) | ((data[j+1] >> x & 1) << 1) if planes == 3: for i, (j, x) in enumerate([(j,x) for j in range(16, 24, 1) for x in reversed(range(8))]): tile[i] |= ((data[j] >> x & 1) << 2) elif planes >= 4: for i, (j, x) in enumerate([(j,x) for j in range(16, 32, 2) for x in reversed(range(8))]): tile[i] |= ((data[j] >> x & 1) << 2) | ((data[j+1] >> x & 1) << 3) if planes == 8: for i, (j, x) in enumerate([(j,x) for j in range(32, 48, 2) for x in reversed(range(8))]): tile[i] |= ((data[j] >> x & 1) << 4) | ((data[j+1] >> x & 1) << 5) \ | ((data[j+16] >> x & 1) << 6) | ((data[j+17] >> x & 1) << 7) imgbits[:64] = tile return img def create_tile(data, palette=[0x00000080, 0xFFFFFFFF]): ''' Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT ''' img = create_tile_indexed(data) img.setColorTable(palette) return QPixmap.fromImage(img) def create_tile_mode7(data, palette): # Each byte is a pixel. 8bit palette. tile = array('B', range(64)) tile = data[:64] img = QImage(8, 8, QImage.Format_Indexed8) img.setColorTable(palette) imgbits = img.bits() imgbits.setsize(img.byteCount()) imgbits[:64] = tile return QPixmap.fromImage(img) def create_tile_mode7_compressed(data, palette): # Each byte is two pixels i.e. 0xEF is Mode 7 0xF 0xE # Palette is externally determined by LUT, only send 4bit palette newdata = b''.join([bytes([j%16, j//16]) for j in data]) return create_tile_mode7(newdata, palette) def create_tile_old(data, palette): ''' Creates a QPixmap of a SNES tile. DO NOT USE OUTSIDE OF QApplication CONTEXT ''' planes = len(data)//8 tile = array('B', range(64)) img = QImage(8, 8, QImage.Format_Indexed8) imgbits = img.bits() imgbits.setsize(img.byteCount()) if planes == 0: raise ValueError("Empty bytes passed") if planes == 1: img.setColorTable([0x00000080, 0xFFFFFFFF]) t_ptr = 0 for j, x in [(j,x) for j in range(8) for x in reversed(range(8))]: tile[t_ptr] = (data[j] >> x & 1) t_ptr += 1 else: img.setColorTable(palette) t_ptr = 0 for j, x in [(j,x) for j in range(0, 16, 2) for x in reversed(range(8))]: tile[t_ptr] = (data[j] >> x & 1) | ((data[j+1] >> x & 1) << 1) t_ptr += 1 t_ptr = 0 if planes == 3: for j, x in [(j,x) for j in range(16, 24, 1) for x in reversed(range(8))]: tile[t_ptr] |= ((data[j] >> x & 1) << 2) t_ptr += 1 elif planes >= 4: for j, x in [(j,x) for j in range(16, 32, 2) for x in reversed(range(8))]: tile[t_ptr] |= ((data[j] >> x & 1) << 2) | ((data[j+1] >> x & 1) << 3) t_ptr += 1 if planes == 8: t_ptr = 0 for j, x in [(j,x) for j in range(32, 48, 2) for x in reversed(range(8))]: tile[t_ptr] |= ((data[j] >> x & 1) << 4) | ((data[j+1] >> x & 1) << 5) \ | ((data[j+16] >> x & 1) << 6) | ((data[j+17] >> x & 1) << 7) t_ptr += 1 imgbits[:64] = tile return QPixmap.fromImage(img) def create_tritile(data): img = QImage(16, 12, QImage.Format_Indexed8) imgbits = img.bits() imgbits.setsize(img.byteCount()) img.setColorTable(const.dialogue_palette) tile = array('B', range(192)) for p, row, b in [(p,j,b) for p in range(2) for j in range(12) for b in reversed(range(8))]: tile[(7-b) + (row*16) + (p*8)] = (data[row + (p*12)] >> b & 1) imgbits[:192] = tile return QPixmap.fromImage(img) def create_quadtile(data, ltr=False): img = QImage(16, 16, QImage.Format_ARGB32_Premultiplied) img.fill(QColor(0,0,0,0)) painter = QtGui.QPainter(img) painter.drawPixmap(0, 0, create_tile(data[0:8])) painter.drawPixmap(8, 8, create_tile(data[24:32])) if ltr: painter.drawPixmap(8, 0, create_tile(data[8:16])) painter.drawPixmap(0, 8, create_tile(data[16:24])) else: painter.drawPixmap(0, 8, create_tile(data[8:16])) painter.drawPixmap(8, 0, create_tile(data[16:24])) del painter return QPixmap.fromImage(img) def generate_glyphs(rom, offset, num=0x100, palette=const.small_palette): spritelist = [] for i in range(num): j = offset + (i*16) spritelist.append(create_tile(rom[j:j+16], palette)) return spritelist def generate_glyphs_large(rom, offset, num=0x100): spritelist = [] for i in range(num): j = offset + (i*24) spritelist.append(create_tritile(rom[j:j+24])) return spritelist def generate_palette(rom, offset, length=32, transparent=False): ''' Length is in bytes not colors (2 bytes per color) We need to convert BGR555 to ARGB32 for each 2 bytes ''' palette = [] for i in range(offset, offset+length, 2): if (i+2) < len(rom): short = unpack('> 7 # b 0XXXXX00 00000000 -> 00000000 00000000 XXXXX000 g = (short & 0x03E0) << 6 # b 000000XX XXX00000 -> 00000000 XXXXX000 00000000 r = (short & 0x001F) << 19 # b 00000000 000XXXXX -> XXXXX000 00000000 00000000 color = 0xFF000000|r|g|b else: color = 0 # Transparent palette.append(color) if transparent: palette[0] = 0 return palette class Canvas: def __init__(self, cols, rows, color=bg_trans): self.image = QImage(8*cols, 8*rows, QImage.Format_ARGB32_Premultiplied) self.image.fill(color) self.painter = QtGui.QPainter(self.image) self.max_x = 1 self.max_y = 1 def __del__(self): del self.painter def draw_pixmap(self, col, row, pixmap): self.painter.drawPixmap(col*8, row*8, pixmap) if col > self.max_x: self.max_x = col if row > self.max_y: self.max_y = row def pixmap(self, trim=False): if trim: return QPixmap.fromImage(self.image.copy(0, 0, self.max_x*8+8, self.max_y*8+8)) return QPixmap.fromImage(self.image) class Canvas_Indexed: def __init__(self, cols, rows, color=0): self.image = QImage(8*cols, 8*rows, QImage.Format_Indexed8) self.width = 8*cols self.image.fill(0) self.imgbits = self.image.bits() self.imgbits.setsize(self.image.byteCount()) self.max_col = 1 self.max_row = 1 def draw_tile(self, col, row, image): imgbits = image.bits() imgbits.setsize(image.byteCount()) x = col*8 y = row*8 start = x + y*self.width for i in range(8): offset = i*self.width self.imgbits[start+offset:start+offset+8] = imgbits[i*8:i*8+8] self.max_col = max(col, self.max_col) self.max_row = max(row, self.max_row) def pixmap(self, palette, trim=False): if trim: img = self.image.copy(0, 0, self.max_col*8+8, self.max_row*8+8) img.setColorTable(palette) return QPixmap.fromImage(img) self.image.setColorTable(palette) return QPixmap.fromImage(self.image)