def convert(self) -> Texture2DDescription: self.validate() from PySide6.QtGui import QImage img = QImage(self.filePath) n = _cChannels[self.channels] if n == 4: img = img.convertToFormat(QImage.Format_RGBA8888) else: img = img.convertToFormat(QImage.Format_RGB888) # noinspection PyTypeChecker bits: memoryview = img.constBits() # noinspection PyTypeChecker byts: bytes = bits.tobytes() w, h = img.width(), img.height() if n == 1: assert len(byts) == w * h * 3 byts = byts[::3] assert len(byts) == w * h elif n == 2: assert len(byts) == w * h * 3 byts = b''.join(byts[i:i + 2] for i in range(len(byts) // 3)) assert len(byts) == w * h * 2 desc = Texture2DDescription(w, h, byts, self.channels, self.dataFormat, self.tilingX, self.mipMaps, self.linearFiltering) desc.tilingY = self.tilingY desc._label = self._label return desc
def display_diff(actual_image: QImage, diff_image: QImage, expected_image: QImage, diff_count: int): # Display image when in live turtle mode. display_image = getattr(turtle.Turtle, 'display_image', None) if display_image is None: return t = turtle.Turtle() # noinspection PyUnresolvedReferences screen = t.screen # type: ignore w = screen.cv.cget('width') h = screen.cv.cget('height') ox, oy = w / 2, h / 2 text_space = (h - actual_image.height() - diff_image.height() - expected_image.height()) text_height = max(20, text_space // 3) font = ('Arial', text_height // 2, 'Normal') t.penup() t.goto(-ox, oy) t.right(90) t.forward(text_height) t.write(f'Actual', font=font) display_image(ox + t.xcor(), oy - t.ycor(), image=encode_image(actual_image)) t.forward(actual_image.height()) t.forward(text_height) t.write(f'Diff ({diff_count} pixels)', font=font) display_image(ox + t.xcor(), oy - t.ycor(), image=encode_image(diff_image)) t.forward(diff_image.height()) t.forward(text_height) t.write('Expected', font=font) display_image(ox + t.xcor(), oy - t.ycor(), image=encode_image(expected_image)) t.forward(expected_image.height())
def export_arr(self, frame_index: int): self.scene.update_frame(frame_index) img = QImage(self.video_data.width, self.video_data.height, QImage.Format_ARGB32) painter = QPainter() painter.begin(img) self.scene.render(painter) painter.end() shape = (img.height(), img.bytesPerLine() * 8 // img.depth(), 4) ptr = img.bits() arr = np.array(ptr, dtype=np.uint8).reshape(shape) arr = arr[..., :3] return arr
class MainWindow(QMainWindow): bytes_key = bytes((0b11100010, 0b10011101, 0b10100100)) def __init__(self): super(MainWindow, self).__init__(None) self.ui = UiMainWindow() self.data = Coder() self.ui.setup_ui(self) self.ui.text_edit.textChanged.connect(self.message_changed) self.ui.push_button.clicked.connect(self.read_image) self.ui.push_button_2.clicked.connect(self.write_image) self.ui.push_button_4.clicked.connect(self.encode_text) self.ui.push_button_3.clicked.connect(self.decode_text) self.image = None self.ui.label.setWordWrap(True) self.reset_state() def clear_image(self): if not self.image: return del self.image self.image = None def reset_state(self): self.clear_image() self.update_activity_state() def update_activity_state(self): self.ui.text_edit.setEnabled(self.image is not None) self.ui.push_button_2.setEnabled(self.image is not None) self.ui.push_button_3.setEnabled(self.image is not None) self.ui.push_button_4.setEnabled(self.image is not None) def get_input(self): return self.ui.text_edit.toPlainText() @Slot() def read_image(self): filepath, _ = QFileDialog.getOpenFileName( self, "Открыть картинку", "", "Допустимые форматы (*.png)") if not filepath: return self.reset_state() if not QFile.exists(filepath): self.set_output_message( "Изображение {} не найдено.".format(filepath)) return self.image = QImage() if not self.image.load(filepath): self.set_output_message( "Изображение {} не загружено.".format(filepath)) self.clear_image() return self.image.convertTo(QImage.Format_ARGB32) self.data.max_power = (self.image.width() * self.image.height() * 3) // 8 - len(MainWindow.bytes_key) - 4 self.set_output_message("Изображение успешно загружено") self.update_activity_state() @Slot() def write_image(self): if self.image is None: self.set_output_message("Ошибка загрузки") return filepath, _ = QFileDialog.getSaveFileName( self, "Сохранить картинку", "", "Допустимые форматы (*.png)") if self.image.save(filepath, "PNG"): self.set_output_message( "Изображение сохранено в {}".format(filepath)) return self.set_output_message("Ошибка сохранения") @Slot() def encode_text(self): if self.image is None: self.set_output_message("Ошибка загрузки.") return array = QByteArray() array.push_back(self.get_input().encode()) self.data.curr_power = len(array) if self.data.overflow(): self.set_output_message( "Сильно большой текст для кодирования в эту картинку.") return for i in range(4): array.push_front( bytes(((self.data.curr_power >> in_bits(i)) & 0xff, ))) for i in range(len(MainWindow.bytes_key) - 1, -1, -1): array.push_front(bytes((MainWindow.bytes_key[i], ))) bytes_write(self.image, array, 0) self.set_output_message("Сообщение закодировано!") @Slot() def decode_text(self): if self.image is None: self.set_output_message("Ошибка загрузки!") return header = len(MainWindow.bytes_key) + 4 array = bytes_read(self.image, 0, header) for i in range(0, len(MainWindow.bytes_key)): if bytes((MainWindow.bytes_key[i], )) != array[i]: self.set_output_message("Сообщение отсутствует!") return size = 0 for i in range(len(MainWindow.bytes_key), len(MainWindow.bytes_key) + 4): size = (size << 8) + int.from_bytes(array[i], byteorder="big") if size > self.data.max_power: self.set_output_message( "Ошибка декодирования! Размер заголовка превышает размер сообщения." ) return array.clear() array = bytes_read(self.image, header, size) text = bytes(array).decode("utf-8") self.ui.text_edit.setPlainText(text) self.set_output_message( "Присутствует сообщение длиной {} байт.".format(size)) @Slot() def message_changed(self): array = QByteArray() array.push_back(self.get_input().encode()) self.data.curr_power = len(array) if self.data.less(): argument = self.data.diff() self.set_output_message( "Ещё можно ввести: {} байт.".format(argument)) return if self.data.max_(): self.set_output_message("Размер сообщения достиг максимума.") return argument = self.data.rdiff() self.set_output_message( "Размер сообщения превышен на: {} байт.".format(argument)) def set_output_message(self, text): self.ui.label.setText(text)
def InitializeSize(self): image = QImage(str(self.path.absolute())) self.size.setWidth(image.width()) self.size.setHeight(image.height())
class ImageView(MouseEventMixin, QGraphicsView): def __init__(self, dirname, parent=None): super(ImageView, self).__init__() super(MouseEventMixin, self).__init__(parent) self.__dirname = dirname self.__image = None self.__image_list = [] self.__timer = QTimer(self) self.init_ui() def init_ui(self): self.setCacheMode(QGraphicsView.CacheBackground) self.setRenderHints(QPainter.Antialiasing | QPainter.SmoothPixmapTransform | QPainter.TextAntialiasing) self.__timer.setInterval(IMAGE_INTERVAL) self.__timer.timeout.connect(self.on_timeout) def init_image_list(self): self.__image_list = [ str(x) for x in Path(self.__dirname).iterdir() if x.is_file() ] def start_view(self): self.init_image_list() self.random_set_image() self.__timer.start() def set_image(self, filename): self.setUpdatesEnabled(False) self.__image = QImage(filename) self.repaint() self.setUpdatesEnabled(True) def random_set_image(self): if not self.__image_list: return image = random.choice(self.__image_list) self.__image_list.remove(image) self.set_image(path.join(self.__dirname, image)) def on_timeout(self): if not self.__image_list: self.init_image_list() self.random_set_image() def paintEvent(self, event): super(MouseEventMixin, self).paintEvent(event) if not self.__image: return if self.__image.height() == 0 or self.height( ) == 0 or self.__image.width() == 0: return image_aspect_ratio = self.__image.width() / self.__image.height() view_aspect_ratio = self.width() / self.height() if view_aspect_ratio <= image_aspect_ratio: image_height = self.width() / image_aspect_ratio rect = QRectF(0, (self.height() - image_height) / 2, self.width(), image_height) else: image_widh = self.height() * image_aspect_ratio rect = QRectF((self.width() - image_widh) / 2, 0, image_widh, self.height()) painter = QPainter(self.viewport()) painter.drawImage(rect, self.__image) painter.end()
class PreviewWindow(QWidget): def __init__(self, parent=None): super().__init__(parent) self.mode = Mode.NoMode self.image = QImage() self.zoomed_image = QImage() self.svgRenderer = QSvgRenderer(self) self.zoom_scale = ZOOM_ORIGINAL_SCALE def mode(self): return self.mode def set_mode(self, mode): self.mode = mode def load(self, data): if self.mode == Mode.PngMode: self.image.loadFromData(data) self.setMinimumSize(self.image.rect().size()) elif self.mode == Mode.SvgMode: self.svgRenderer.load(data) self.zoom_image() self.update() # Public slots def zoom_original(self): self.set_zoom_scale(ZOOM_ORIGINAL_SCALE) def zoom_in(self): # new_scale = self.zoom_scale + \ # ZOOM_BIG_INCREMENT if self.zoom_scale >= ZOOM_ORIGINAL_SCALE else ZOOM_SMALL_INCREMENT new_scale = self.zoom_scale + ZOOM_SMALL_INCREMENT if new_scale > MAX_ZOOM_SCALE: new_scale = MAX_ZOOM_SCALE self.set_zoom_scale(new_scale) def zoom_out(self): # new_scale = self.zoom_scale - \ # ZOOM_SMALL_INCREMENT if self.zoom_scale <= ZOOM_ORIGINAL_SCALE else ZOOM_BIG_INCREMENT new_scale = self.zoom_scale - ZOOM_SMALL_INCREMENT if new_scale < MIN_ZOOM_SCALE: new_scale = MIN_ZOOM_SCALE self.set_zoom_scale(new_scale) # Private methods def paintEvent(self, event): painter = QPainter(self) output_size = QSize() if self.mode == Mode.PngMode: output_size = self.zoomed_image.size() output_rect = QRect(QPoint(), output_size) output_rect.translate(self.rect().center() - output_rect.center()) painter.drawImage(output_rect.topLeft(), self.zoomed_image) elif self.mode == Mode.SvgMode: output_size = self.svgRenderer.defaultSize() if self.zoom_scale != ZOOM_ORIGINAL_SCALE: zoom = float(self.zoom_scale) / ZOOM_ORIGINAL_SCALE output_size.scale(output_size.width() * zoom, output_size.height() * zoom, Qt.IgnoreAspectRatio) output_rect = QRect(QPoint(), output_size) output_rect.translate(self.rect().center() - output_rect.center()) self.svgRenderer.render(painter, output_rect) self.setMinimumSize(output_size) def zoom_image(self): if self.mode == Mode.PngMode: if self.zoom_scale == ZOOM_ORIGINAL_SCALE: self.zoomed_image = self.image else: zoom = float(self.zoom_scale) / ZOOM_ORIGINAL_SCALE self.zoomed_image = self.image.scaled( self.image.width() * zoom, self.image.height() * zoom, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) def set_zoom_scale(self, new_scale): if self.zoom_scale != new_scale: self.zoom_scale = new_scale self.zoom_image() self.update()
class HexView(QAbstractScrollArea): """ HexView is a QAbstractScrollArea (not a QScrollArea in order to handle large list of hexlines to display. A QScrollArea would require that the viewport Widget associated to it be fully defined to handle the scrolling automatically. Here we really want to render the viewport dynamically based on scrolling events, not the opposite.) """ clicked = Signal((int, int, QColor)) def __init__(self, parent=None, dataio=None): super().__init__(parent) # enforce monospaced font: f = QFont("Monospace") f.setPointSize(8) self.setFont(f) self.setFocusPolicy(Qt.StrongFocus) self.setMouseTracking(True) self.palette = cycle(colors.palette_trbg.values()) self.lastcolor = None # set default vertical scrolling steps: self.vb = self.verticalScrollBar() # addresses to be highlighted: self.highlight = {} self.selected = None # setup internal widgets: self.model = None self.statusbar = parent.statusBar() if parent else None if dataio: self.setData(dataio) @property def select_color(self): """ get next color from (cycle) palette """ self.lastcolor = next(self.palette) return self.lastcolor def setData(self, dataio): """ Define the data model associated with the HexView and initialise the vertical bar values. """ if self.model: self.model.deleteLater() # data should have DataIO API # the model is used as the interface with the data bytes self.model = models.DataIOModel(parent=self, data=dataio) # the view needs updating whenever the model emits a # "UPDATED" signal: self.model.UPDATED.connect(self.update) # since we are an "abstract" widget we need to compute the # vertical bar minimum/maximum values. self.vb.setMinimum(0) nb, r = divmod(self.model.data.size(), self.model.linesize) if r > 0: nb += 1 self.vb.setMaximum(nb) # set line definition (sizes): self.line = HexLine(self.fontMetrics(), self.model.linesize) # prepare grayscale image of the full model data: self.qimg = QImage(self.model.full, self.model.linesize, nb, QImage.Format_Grayscale8) self.update() def update(self): "trigger an update of the viewport widget (ie a paintEvent)" # this will trigger update (paint) on the children as well: self.viewport().update() def paintEvent(self, e): "(re)draw everything inside the rectangle associated with event e" if self.model is not None: # get the painting area: w = QPainter(self.viewport()) f = self.font() w.setFont(f) # get rectangle that needs paint: r = e.rect() # get line indices for this rectangle: line0 = self.vb.value() h = self.line.height first = line0 + r.top() // h last = line0 + r.bottom() // h count = last - first # update drawing: self.paintlines(w, first, count, line0) self.paintframes(w) self.paintmap(w) def keyPressEvent(self, e): self.statusbar.showMessage("key: %d ('%s') [%08x]" % (e.key(), e.text(), e.modifiers())) if self.model is not None: if e.key() == int(Qt.Key_Escape): self.highlight = {} self.update() super().keyPressEvent(e) def addrToLine(self, addr): N = self.model.linesize l, off = divmod(addr, N) return l def xyToAddr(self, x, y): if x < self.line.x_map: h = self.line.height l = y // h # get base address a = self.model.linesize * (self.model.cur + l) # add byte index [0,16[ return (a + self.line.index(x)) h = self.viewport().height() a = int((y / h) * (self.qimg.height() * self.qimg.width())) a = (a // self.model.linesize) * self.model.linesize if a != 0 and self.selected and a != (self.selected[0]): return a - 1 else: return a def colorize(self, a, nb, color): N = self.model.linesize base, o = divmod(a, N) a = base * N c = color while nb > 0: n = min(N - o, nb) if a not in self.highlight: self.highlight[a] = [None] * N for i in range(o, o + n): self.highlight[a][i] = c nb -= n a += N o = 0 def adjust_selected(self, e): x, y = e.x(), e.y() a = self.xyToAddr(e.x(), e.y()) if self.selected: aa, nb_, c = self.selected if a != aa: a, nb = (aa, a - aa) if aa < a else (a, aa - a) self.selected = (a, nb + 1, c) else: nb = 1 self.colorize(a, nb_, None) else: if x < self.line.x_end: nb = 1 elif x < self.line.x_map: nb = self.model.linesize a = a - (nb - 1) else: nb = self.model.linesize self.selected = (a, nb, self.select_color) msg = "selection: [addr=%08x, size=%d]" % (a, nb) self.statusbar.showMessage(msg) def mousePressEvent(self, e): self.adjust_selected(e) self.colorize(*self.selected) self.update() def mouseMoveEvent(self, e): if self.selected: self.adjust_selected(e) self.colorize(*self.selected) self.update() def mouseReleaseEvent(self, e): if self.selected: self.adjust_selected(e) self.colorize(*self.selected) self.update() self.clicked.emit(*self.selected) self.selected = None def wheelEvent(self, e): delta = 1 if e.angleDelta().y() < 0 else -1 v = self.vb.value() + delta self.vb.setValue(v) msg = "wheel: delta=%d, v=%d" % (delta, v) self.statusbar.showMessage(msg) self.update() def paintlines(self, surface, first, count, l0): "draws the data out of the model onto the qpainter surface" self.model.cur = first N = self.model.linesize h = self.line.height ww = surface.window().width() y = (first - l0) * h c0, c1 = brushes.xv_bg, brushes.xv_bg_alt pen = surface.pen() for address, data, txt in self.model.iterlines(count): # background rect (alternate) r = QRect(0, y, ww, h) surface.fillRect(r, c0) # address column: r.setWidth(self.line.x_hex) surface.drawText(r, Qt.AlignHCenter | Qt.AlignVCenter, "%08x" % address) # hex column: w = self.line.xb.width r.setWidth(w) r.translate(self.line.x_hex + self.line.pxpad, 0) C = self.highlight.get(address, [None] * N) for i, c in enumerate(C): try: s = "%02x" % (data[i]) except IndexError: break flg = Qt.AlignHCenter | Qt.AlignVCenter if c: surface.fillRect(r, c) surface.setPen(QPen(Qt.white)) surface.drawText(r, flg, s) surface.setPen(pen) r.translate(w, 0) # ascii column: w = self.line.xb.cw r.setX(self.line.x_txt + self.line.pxpad) r.setWidth(w) for i, c in enumerate(C): try: s = txt[i] except IndexError: break flg = Qt.AlignHCenter | Qt.AlignVCenter if c: surface.fillRect(r, c) surface.setPen(QPen(Qt.white)) surface.drawText(r, flg, s) surface.setPen(pen) r.translate(w, 0) # clear background endline: r.setX(self.line.x_end) r.setWidth(ww - self.line.x_end) surface.fillRect(r, brushes.xv_bg_alt) y += h c0, c1 = c1, c0 def paintframes(self, surface): r = surface.window() ww = surface.window().width() # top & bottom lines: surface.drawLine(0, r.top(), ww, r.top()) surface.drawLine(0, r.bottom(), ww, r.bottom()) # vertical column lines: x = self.line.x_hex surface.drawLine(x, r.top(), x, r.bottom()) x = self.line.x_txt surface.drawLine(x, r.top(), x, r.bottom()) x = self.line.x_end surface.drawLine(x, r.top(), x, r.bottom()) def paintmap(self, surface): ww = surface.window().width() wh = surface.window().height() self.vb.setMaximum(self.qimg.height() - (wh // self.line.height)) self.line.x_map = ww - 32 r = QRect(self.line.x_map, 2, 32, wh - 4) surface.drawImage(r, self.qimg) factor = wh / self.qimg.height() pen = surface.pen() p = QPen(Qt.white) p.setCapStyle(Qt.RoundCap) p.setWidth(2) surface.setPen(p) l0 = self.vb.value() * factor l1 = l0 + (wh // self.line.height) * factor pad = 4 p1 = QPointF(self.line.x_map - pad, l0) p2 = QPointF(self.line.x_map - pad, l1) if l1 - l0 > 4: p.setWidth(2) surface.drawLine(p1, p2) surface.drawLine(p1, QPointF(self.line.x_map, l0)) surface.drawLine(p2, QPointF(self.line.x_map, l1)) else: p.setWidth(4) surface.drawLine(p1, p2) surface.setPen(pen) def move(self, cur, e): return None def action(self, cur, e): return None