ListViewでアイコン一覧
ViewとModelに抽象化されている。
成果物
コード
コード(150行)
main.py
#!/usr/bin/python3 # -*- coding: utf-8 -*- import sys, os, numpy, PIL from PySide2 import QtCore, QtGui, QtWidgets from PIL import Image, ImagePalette class Window(QtWidgets.QMainWindow): def __init__(self): super(self.__class__, self).__init__() self.setAcceptDrops(True) self.widget = Widget(self) self.setCentralWidget(self.widget) self.show() class Widget(QtWidgets.QWidget): def __init__(self, parent): super(self.__class__, self).__init__(parent) self.setAcceptDrops(True) self.framelist = FrameListView() scroller = QtWidgets.QScrollArea() scroller.setWidget(self.framelist) scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) layout = QtWidgets.QGridLayout() layout.addWidget(scroller, 0, 0) self.setLayout(layout) # self.resize(self.view.width(), self.view.height()) self.setWindowTitle("QAction") self.show() class FrameListView(QtWidgets.QListView): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) # self.resizeContents(16, 16*16) self.model = FrameListModel() here = os.path.abspath(os.path.dirname(__file__)) res = os.path.join(os.path.dirname(here), 'res') red = os.path.join(res, 'red.png') green = os.path.join(res, 'green.png') self.model.appendRow(red) self.model.appendRow(green) self.model.appendRow(red) self.model.appendRow(green) self.model.appendRow(red) self.model.appendRow(green) self.setModel(self.model) # self.resize(64, 16*16) self.show() def mousePressEvent(self, event): super(self.__class__, self).mousePressEvent(event) for idx in self.selectedIndexes(): frame = idx.data(QtCore.Qt.UserRole) class FrameListModel(QtCore.QAbstractListModel): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.frames = [] def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return len(self.frames) def data(self, index, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.DecorationRole: return self.frames[index.row()].Icon elif role == QtCore.Qt.UserRole: return self.frames[index.row()] def appendRow(self, file_path): self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) self.frames.append(Frame(file_path)) self.endInsertRows() class Frame: def __init__(self, file_path=None): self.pixels = Pixels() self.icon = QtGui.QIcon(file_path) @property def Pixels(self): return self.pixels @Pixels.setter def Pixels(self, value): self.pixels = value @property def Icon(self): return self.icon @Icon.setter def Icon(self, value): self.icon = value class Pixels: def __init__(self): self.width = 16 self.height = 16 self.pixels = numpy.zeros(self.width*self.height, dtype=int).reshape(self.height, self.width) @property def Pixels(self): return self.pixels @property def Width(self): return self.width @property def Height(self): return self.height def save(self): print(os.getcwd()) self.save_txt() for ext in ('gif', 'png', 'webp'): self.save_raster(ext) def load(self, file_path): ext = os.path.splitext(file_path)[1].lower()[1:] if '' == ext: raise Exception('拡張子が必要です。png,gif,webp,txt形式のいずれかに対応しています。') elif 'txt' == ext: self.load_txt(file_path) elif 'gif' == ext: self.load_gif(file_path) elif 'png' == ext: self.load_png(file_path) elif 'webp' == ext: self.load_webp(file_path) else: raise Exception('拡張子が未対応です。png,gif,webp,txt形式のいずれかに対応しています。') def save_txt(self): with open(os.path.join(os.getcwd(), 'pixels.txt'), 'w') as f: f.write('\n'.join([''.join(map(str, self.pixels[y].tolist())) for y in range(self.height)])) def load_txt(self, file_path): with open(file_path, 'r') as f: lines = f.read().split('\n') self.height = len(lines) self.width = len(lines[0]) self.pixels = numpy.zeros(self.width*self.height, dtype=int).reshape(self.height, self.width) x = 0; y = 0; for line in lines: for c in line: self.pixels[y][x] = int(c, 16) x += 1 y += 1 x = 0 def save_raster(self, ext): image = Image.new('1', (self.width, self.height)) image.putdata(self.pixels.reshape(self.width * self.height).tolist()) print(ext) image.save(os.path.join(os.getcwd(), 'pixels.' + ext), optimize=True, lossless=True) # image.save(os.path.join(os.getcwd(), 'pixels.' + ext), optimize=True, lossless=True, transparency=0) def load_png(self, file_path): image = Image.open(file_path, mode='r') image = image.convert('1') self.pixels = numpy.array(list(map(lambda x: 0 if 0 == x else 1, list(image.getdata())))).reshape(image.size[1], image.size[0]) self.width, self.height = image.size def load_gif(self, file_path): # 値が0/255で出力されてしまうので0/1に変換する image = Image.open(file_path, mode='r') self.width, self.height = image.size self.pixels = numpy.array(list(map(lambda x: 0 if 0 == x else 1, list(image.getdata())))).reshape(self.height, self.width) def load_webp(self, file_path): # 値が[0,0,0]/[255,255,255]で出力されてしまうので0/1に変換する image = Image.open(file_path, mode='r') self.width, self.height = image.size self.pixels = numpy.array(list(map(lambda x: 0 if (0,0,0) == x else 1, list(image.getdata())))).reshape(self.height, self.width) if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = Window() sys.exit(app.exec_())
python3 main.py
苦労した所
Model/View
Qtはすでにモデルとビューに抽象化されている。その構造を理解した上でどのクラスやメソッドを使うか判断せねばならない。
主にQListView, QAbstractListModelを使う。
Model
class FrameListModel(QtCore.QAbstractListModel): def __init__(self, parent=None): super(self.__class__, self).__init__(parent) self.frames = [] def rowCount(self, parent=QtCore.QModelIndex()): if parent.isValid(): return 0 return len(self.frames) def data(self, index, role=QtCore.Qt.DisplayRole): if role == QtCore.Qt.DecorationRole: return self.frames[index.row()].Icon elif role == QtCore.Qt.UserRole: return self.frames[index.row()] def appendRow(self, file_path): self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) self.frames.append(Frame(file_path)) self.endInsertRows()
rowCount
, data
, appendRow
は必須。
data
はロールとそれに対して適切な型を返さねばならない。今回はアイコンのみ表示するため、DecorationRole
ロールに対してアイコン型を返している。UserRole
は任意の型を返せる。
横スクロールバー非表示
横スクロールバー非表示は以下のようにする。
scroller.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
所感
ドット絵のアニメ用フレーム画像をリストで管理したい。だが、リストの扱いが難しかったため、一旦リストだけ触って確かめてみることにした。
特にドット絵の編集をリアルタイムでアイコン描画する方法がわからない。マウスイベントと絡めてみたがアイコン画像が更新されなかった。しばらくはListViewを勉強することになるだろう。
対象環境
- Raspbierry pi 4 Model B
- Raspbian buster 10.0 2019-09-26 ※
- bash 5.0.3(1)-release
$ uname -a Linux raspberrypi 4.19.97-v7l+ #1294 SMP Thu Jan 30 13:21:14 GMT 2020 armv7l GNU/Linux