#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright 2020 Free Software Foundation, Inc. # # This file is part of GNU Radio # # SPDX-License-Identifier: GPL-3.0-or-later # # from PyQt5.QtWidgets import QLabel from PyQt5.QtGui import QPixmap, QPainter from PyQt5.QtCore import Qt as Qtc from PyQt5.QtCore import QSize import os import sys from gnuradio import gr import pmt class GrGraphicItem(gr.sync_block, QLabel): """ This block displays the selected graphic item. You can pass a filename as a string in a message to change the image on the fly. overlays can also be added by passing in a message with a dictionary of a list of dictionaries in the car portion of the message. Each dictionary should have the following keys: 'filename','x','y', and an optional 'scalefactor'. Setting the x/y attributes to -1,-1 will remove an overlay. Otherwise items are indexed by filename and can be animated throughout the background image. """ def __init__(self, image_file, scaleImage=True, fixedSize=False, setWidth=0, setHeight=0): gr.sync_block.__init__(self, name="GrGraphicsItem", in_sig=None, out_sig=None) QLabel.__init__(self) if not os.path.isfile(image_file): gr.log.error("ERROR: Unable to find file " + image_file) sys.exit(1) try: self.pixmap = QPixmap(image_file) self.originalPixmap = QPixmap(image_file) except OSError as e: gr.log.error("ERROR: " + e.strerror) sys.exit(1) self.image_file = image_file self.scaleImage = scaleImage self.fixedSize = fixedSize self.setWidth = setWidth self.setHeight = setHeight super().setPixmap(self.pixmap) super().setMinimumSize(1, 1) self.overlays = {} self.message_port_register_in(pmt.intern("filename")) self.set_msg_handler(pmt.intern("filename"), self.msgHandler) self.message_port_register_in(pmt.intern("overlay")) self.set_msg_handler(pmt.intern("overlay"), self.overlayHandler) def overlayHandler(self, msg): try: overlayitem = pmt.to_python(pmt.car(msg)) if overlayitem is None: gr.log.error('Overlay message contains None in the car portion ' 'of the message. Please pass in a dictionary or list of dictionaries in this ' 'portion of the message. Each dictionary should have the following keys: ' 'filename,x,y. Use x=y=-1 to remove an overlay item.') return if type(overlayitem) is dict: itemlist = [] itemlist.append(overlayitem) elif type(overlayitem) is list: itemlist = overlayitem else: gr.log.error("Overlay message type is not correct. Please pass in " "a dictionary or list of dictionaries in this portion of the message. Each " "dictionary should have the following keys: filename,x,y. Use x=y=-1 to " "remove an overlay item.") return # Check each dict item to make sure it's valid. for curitem in itemlist: if type(curitem) == dict: if 'filename' not in curitem: gr.log.error("Dictionary item did not contain the 'filename' key.") gr.log.error("Received " + str(curitem)) continue if 'x' not in curitem: gr.log.error("The dictionary for filename " + curitem['filename'] + " did not contain an 'x' key.") gr.log.error("Received " + str(curitem)) continue if 'y' not in curitem: gr.log.error("The dictionary for filename " + curitem['filename'] + " did not contain an 'y' key.") gr.log.error("Received " + str(curitem)) continue if not os.path.isfile(curitem['filename']): gr.log.error("Unable to find overlay file " + curitem['filename']) gr.log.error("Received " + str(curitem)) continue # Now either add/update our list or remove the item. if curitem['x'] == -1 and curitem['y'] == -1: try: del self.overlays[curitem['filename']] # remove item except: pass else: self.overlays[curitem['filename']] = curitem self.updateGraphic() except Exception as e: gr.log.error("Error with overlay message conversion: %s" % str(e)) def updateGraphic(self): if (len(self.overlays.keys()) == 0): try: super().setPixmap(self.pixmap) except Exception as e: gr.log.error("Error updating graphic: %s" % str(e)) return else: # Need to deal with overlays tmpPxmap = self.pixmap.copy(self.pixmap.rect()) painter = QPainter(tmpPxmap) for curkey in self.overlays.keys(): curOverlay = self.overlays[curkey] try: newOverlay = QPixmap(curkey) if 'scalefactor' in curOverlay: scale = curOverlay['scalefactor'] w = newOverlay.width() h = newOverlay.height() newOverlay = newOverlay.scaled(int(w*scale), int(h*scale), Qtc.KeepAspectRatio) painter.drawPixmap(curOverlay['x'], curOverlay['y'], newOverlay) except Exception as e: gr.log.error("Error adding overlay: %s" % str(e)) return painter.end() super().setPixmap(tmpPxmap) def msgHandler(self, msg): try: new_val = pmt.to_python(pmt.cdr(msg)) image_file = new_val if type(new_val) == str: if not os.path.isfile(image_file): gr.log.error("ERROR: Unable to find file " + image_file) return try: self.pixmap = QPixmap(image_file) self.image_file = image_file except OSError as e: gr.log.error("ERROR: " + e.strerror) return self.updateGraphic() else: gr.log.error("Value received was not an int or " "a bool: %s" % str(type(new_val))) except Exception as e: gr.log.error("Error with message conversion: %s" % str(e)) def minimumSizeHint(self): return QSize(self.pixmap.width(),self.pixmap.height()) def resizeEvent(self, event): if self.scaleImage: w = super().width() h = super().height() self.pixmap = self.originalPixmap.scaled(w, h, Qtc.KeepAspectRatio) elif self.fixedSize and self.setWidth > 0 and self.setHeight > 0: self.pixmap = self.originalPixmap.scaled(self.setWidth, self.setHeight, Qtc.KeepAspectRatio) self.updateGraphic()