Mercurial > hg > tilerswift
changeset 106:d5e69fe5c81a
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.
author | Jordi Gutiérrez Hermoso <jordigh@octave.org> |
---|---|
date | Sun, 15 Sep 2019 20:47:01 -0400 |
parents | 37e3f2781fdf |
children | 06d752537e21 |
files | img/darkside/go-down.svg img/darkside/go-up.svg img/lightside/go-down.svg img/lightside/go-up.svg nes.py widgets.py |
diffstat | 6 files changed, 122 insertions(+), 31 deletions(-) [+] |
line wrap: on
line diff
new file mode 100644 --- /dev/null +++ b/img/darkside/go-down.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g color="#FFF" fill="#b8b8b8"> + <path d="M3.707 5.293L2.293 6.707 8 12.414l5.707-5.707-1.414-1.414L8 9.586z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/> + <path d="M13 6V5h1v1zM2 6V5h1v1z" style="marker:none" overflow="visible"/> + <path d="M2 6c0-.554.446-1 1-1s1 .446 1 1-.446 1-1 1-1-.446-1-1zM12 6c0-.554.446-1 1-1s1 .446 1 1-.446 1-1 1-1-.446-1-1z" style="marker:none" overflow="visible"/> + </g> +</svg>
new file mode 100644 --- /dev/null +++ b/img/darkside/go-up.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g color="#FFF" fill="#b8b8b8"> + <path d="M8 3.586L2.293 9.293l1.414 1.414L8 6.414l4.293 4.293 1.414-1.414z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/> + <path d="M13 10v1h1v-1zM2 10v1h1v-1z" style="marker:none" overflow="visible"/> + <path d="M2 10c0 .554.446 1 1 1s1-.446 1-1-.446-1-1-1-1 .446-1 1zM12 10c0 .554.446 1 1 1s1-.446 1-1-.446-1-1-1-1 .446-1 1z" style="marker:none" overflow="visible"/> + </g> +</svg>
new file mode 100644 --- /dev/null +++ b/img/lightside/go-down.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g color="#000" fill="#474747"> + <path d="M3.707 5.293L2.293 6.707 8 12.414l5.707-5.707-1.414-1.414L8 9.586z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/> + <path d="M13 6V5h1v1zM2 6V5h1v1z" style="marker:none" overflow="visible"/> + <path d="M2 6c0-.554.446-1 1-1s1 .446 1 1-.446 1-1 1-1-.446-1-1zM12 6c0-.554.446-1 1-1s1 .446 1 1-.446 1-1 1-1-.446-1-1z" style="marker:none" overflow="visible"/> + </g> +</svg>
new file mode 100644 --- /dev/null +++ b/img/lightside/go-up.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"> + <g color="#000" fill="#474747"> + <path d="M8 3.586L2.293 9.293l1.414 1.414L8 6.414l4.293 4.293 1.414-1.414z" style="line-height:normal;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000;text-transform:none;text-orientation:mixed;shape-padding:0;isolation:auto;mix-blend-mode:normal;marker:none" font-weight="400" font-family="sans-serif" white-space="normal" overflow="visible"/> + <path d="M13 10v1h1v-1zM2 10v1h1v-1z" style="marker:none" overflow="visible"/> + <path d="M2 10c0 .554.446 1 1 1s1-.446 1-1-.446-1-1-1-1 .446-1 1zM12 10c0 .554.446 1 1 1s1-.446 1-1-.446-1-1-1-1 .446-1 1z" style="marker:none" overflow="visible"/> + </g> +</svg>
--- 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):
--- 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)