# HG changeset patch # User Jordi GutiƩrrez Hermoso # Date 1568594821 14400 # Node ID d5e69fe5c81ae7a3f6f3b2ef6ca3ae3e3fc0c1e4 # Parent 37e3f2781fdf3ba87c2e0170b7a9aa3f82f74d3c NES_ROM: implement circular byte shifting For ROMs that have no CHR, sometimes the tiles aren't aligned to 16-byte boundaries. Circularly shifting the PRG data may be necessary to get proper tiles. In order to do so efficiently, this also requires changing the Tile object to be more like a cached look into the underlying tile data. Thus, I turned its `pixmap` and the `raw_tile` properties into descriptors so I can properly clear those cached properties and can thus lazily update them, as required by the update functions. diff --git a/img/darkside/go-down.svg b/img/darkside/go-down.svg new file mode 100644 --- /dev/null +++ b/img/darkside/go-down.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/img/darkside/go-up.svg b/img/darkside/go-up.svg new file mode 100644 --- /dev/null +++ b/img/darkside/go-up.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/img/lightside/go-down.svg b/img/lightside/go-down.svg new file mode 100644 --- /dev/null +++ b/img/lightside/go-down.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/img/lightside/go-up.svg b/img/lightside/go-up.svg new file mode 100644 --- /dev/null +++ b/img/lightside/go-up.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/nes.py b/nes.py --- a/nes.py +++ b/nes.py @@ -6,6 +6,10 @@ class NES_ROM(object): def __init__(self, filename): self.filename = filename + # To track circular rotation of the ROM bytes, in case the + # tiles aren't aligned to 16-byte boundaries and the user + # moves it. + self.initial_position = 0 self.read_rom() def read_rom(self): @@ -20,23 +24,42 @@ self.PRG = body[0:PRG_size] if CHR_size == 0: # No chr, just read the whole ROM as tiles - tile_data = body + self.tile_data = list(body) self.CHR = None else: self.CHR = body[PRG_size:PRG_size+CHR_size] - tile_data = self.CHR + self.tile_data = list(self.CHR) + + self.tiles = [Tile(i, self.tile_data) + for i in range(0, len(self.tile_data)//16)] + + def update_tiles(self): + for tile in self.tiles: + tile.clear_caches() - self.tiles = [Tile(tile_data[i:i+16]) for i in range(0, len(tile_data), 16)] + def byte_forward(self): + x = self.tile_data.pop(0) + self.tile_data.append(x) + self.initial_position -= 1 + self.update_tiles() + + def byte_backward(self): + x = self.tile_data.pop(-1) + self.tile_data.insert(0, x) + self.initial_position += 1 + self.update_tiles() class Tile(object): default_palette = TILE_PALETTES["Primary"] - def __init__(self, raw_tile): + def __init__(self, index, tile_data): super().__init__() - self.raw_tile = raw_tile + self.index = index + self.tile_data = tile_data + self.clear_caches() self.set_palette(self.default_palette.copy()) def __repr__(self): @@ -46,6 +69,11 @@ for row in self.tile ) + "\n" + "-"*10 + def clear_caches(self): + self._raw_tile = None + self._tile = None + self._pixmap = None + def set_palette(self, new_palette): self.palette = new_palette.copy() self.update_pixmap() @@ -54,24 +82,40 @@ img_data = bytes(sum(self.tile, [])) image = QG.QImage(img_data, 8, 8, QG.QImage.Format_Indexed8) image.setColorTable(palette_to_qt(self.palette)) - self.pixmap = QG.QPixmap(image) + self._pixmap = QG.QPixmap(image) + + def get_pixmap(self): + if self._pixmap is None: + self.update_pixmap() + return self._pixmap + + def set_pixmap(self, pixmap): + # Not implemented + pass + + pixmap = property(get_pixmap, set_pixmap, clear_caches, "Pixmap representation of tile") + + def get_raw_tile(self): + if self._raw_tile is None: + self._raw_tile = self.tile_data[16*self.index:16*(self.index + 1)] + return self._raw_tile + + def set_raw_tile(self, raw_tile): + self.clear_caches() + self.tile_data[16*self.index:16*(self.index + 1)] = raw_tile + + raw_tile = property(get_raw_tile, set_raw_tile, clear_caches, "Raw tile bytes as found in ROM") def get_tile(self): - if getattr(self, "_tile", None) is None: + if self._tile is None: self._tile = self.parse_tile(self.raw_tile) return self._tile def set_tile(self, tile): - del self._tile + self.clear_caches() self.raw_tile = self.unparse_tile(tile) - self.update_pixmap() - - def del_tile(self): - del self._tile - del self.raw_tile - - tile = property(get_tile, set_tile, del_tile, "A tile parsed as an 8x8 array of ints") + tile = property(get_tile, set_tile, clear_caches, "A tile parsed as an 8x8 array of ints") @classmethod def parse_tile(cls, tile): diff --git a/widgets.py b/widgets.py --- a/widgets.py +++ b/widgets.py @@ -5,6 +5,7 @@ from nes import NES_ROM, Tile BUTTON_SIZE = 32 +ICON_SIZE = 24 class GridHolder(object): @@ -184,6 +185,16 @@ self.tile_picker.picked_tile = tile + def byteForward(self): + self.rom.byte_forward() + self.update() + self.tile_picker.update() + + def byteBackward(self): + self.rom.byte_backward() + self.update() + self.tile_picker.update() + class TilePicker(TileGrid): CSS = """ @@ -422,23 +433,31 @@ self.toolbar.addWidget(self.rom_palette) - for icon, tooltip, function in [ - ("zoom-in", "Zoom in", "zoomIn"), - ("zoom-out", "Zoom out", "zoomOut"), - ("zoom-original", "Restore zoom", "zoomOriginal"), - ("increaseCols", "Increase number of columns", "increaseCols"), - ("decreaseCols", "Decrease number of columns", "decreaseCols"), + for group in [ + [ + ("zoom-in", "Zoom in", "zoomIn"), + ("zoom-out", "Zoom out", "zoomOut"), + ("zoom-original", "Restore zoom", "zoomOriginal"), + ], + [ + ("increaseCols", "Increase number of columns", "increaseCols"), + ("decreaseCols", "Decrease number of columns", "decreaseCols"), + ], + [ + ("go-up", "Move all tiles one byte forward", "byteForward"), + ("go-down", "Move all tiles one byte backard", "byteBackward"), + ] ]: - button = QW.QPushButton("") - iconpath = widget_icon_path(button) - button.setIcon(QG.QIcon(f"img/{iconpath}/{icon}.svg")) - button.setFixedSize(QC.QSize(32, 32)) - button.setIconSize(QC.QSize(24, 24)) - button.setToolTip(tooltip) - button.pressed.connect(getattr(self.rom_canvas, function)) - self.toolbar.addWidget(button) - - self.toolbar.addStretch() + for icon, tooltip, function in group: + button = QW.QPushButton("") + iconpath = widget_icon_path(button) + button.setIcon(QG.QIcon(f"img/{iconpath}/{icon}.svg")) + button.setFixedSize(QC.QSize(BUTTON_SIZE, BUTTON_SIZE)) + button.setIconSize(QC.QSize(ICON_SIZE, ICON_SIZE)) + button.setToolTip(tooltip) + button.pressed.connect(getattr(self.rom_canvas, function)) + self.toolbar.addWidget(button) + self.toolbar.addStretch() layout = QW.QVBoxLayout() layout.addWidget(scroll)