0

The answer in the post How to show tooltip image when hover on button pyqt5 shows that one can display a saved image on QToolTip. Is there way to achieve an equivalent result on an image represented by an numpy nd array without saving?

More precisely, if panda.jpg is a saved image right under C drive, then the following code modifed from the above reference link runs:

import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        pybutton = QPushButton('Display Image via Tool Tip', self)
        pybutton.setToolTip(r'<img src="C:\panda.jpg">')

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit( app.exec_() )

The code gives:

enter image description here

Consider now instead:

import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import numpy as np

class MainWindow(QMainWindow):
    def __init__(self):
        QMainWindow.__init__(self)
        pybutton = QPushButton('Display Image via Tool Tip', self)
        imarray = np.random.rand(1000,1000,3) * 255
        #pybutton.setToolTip(imarray) #This line is not working

if __name__ == "__main__":
    app = QApplication(sys.argv)
    mainWin = MainWindow()
    mainWin.show()
    sys.exit( app.exec_() )

Is there a way to attain an equivalent result without any saving? Converting to QImage does not seem to help.

This question is motivated because:

  1. I have lots of array to display via a lot of tooltips and none of them will be used at all after they are displayed on the tooltip.

  2. I have one tooltip where I want to display a video, which I will be able to do as soon as I know how to display one image using arrays without any saving because all I will need then is the QTime and update of the array.

温泽海
  • 216
  • 3
  • 16
  • Thanks to whoever reopens the question. I did not understand why the question was closed. – 温泽海 Jun 16 '22 at 00:27
  • We get hundreds of questions every day; we're all human (at least, right now ;-) ), and, sometimes we close questions for the "wrong" reason: users voting to close might have differing opinions about those reasons, or maybe they just didn't understand the question. In case you believe you get a close and you don't agree with it, always remember to post a comment explaining *why* (or edit the question and add a comment to notify about that change): doing this will not only clarify your explanation, but also add more visibility to the issue. – musicamante Jun 16 '22 at 01:15
  • BTW, the commented line didn't work for a simple reason: `setToolTip` accepts a *printable* text, while the contents of a numpy array is clearly not any of that. Qt is able to understand if a string is (possibly) a "rich text" (aka: HTML), but that only works for proper ascii/utf data, which is not the case of a memory dump such as a numpy container. – musicamante Jun 16 '22 at 01:18

1 Answers1

1

While Qt support for HTML is limited to a smaller subset of HTML4, that support is quite compliant and consistent, including the Base64 encoding for embedded image data.

The solution is then to save the image data as an image file in a memory buffer, convert its contents to the base 64 encoding and use that for the img tag.

Be aware: base64 is a six-bit encoding, and any unencoded data cannot be divided with that data size will cause padding of the bytes. This obviously means that the memory footprint of the stored data will always be equal (rarely) or bigger (most likely) than the original.

In the following example I'm showing the random image as a QPixmap set for a QLabel, and the tooltip of that image can be visible by hovering it.

I also added a basic text viewer to show the actual contents of the "raw data" in order to realize the possible size (and memory requirement) for each possible image tooltip. Note that Qt will obviously use memory for both the base64 data and the cached image.

If you are not interested in high quality of the tooltip image, you can obviously use the 'JPG' format: quality results will vary, but you will certainly get a smaller memory requirement.

Remember that the above is quite important: the contents of a QToolTip are evaluated dynamically at runtime, and since every time a different tool tip content is going to be shown, the whole contents of the next tool tip will be evaluated, this will add a considerable overhead: Qt will check if the tool tip text possibly contains rich text, then create a new QTextDocument, parse the HTML and convert it to its own layout, compute all required sizes, and finally update the new QToolTip with the updated and laid out contents. Use this with extreme awareness, especially for high resolution images, for which you should really consider resizing before setting the tool tip contents.

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        imarray = np.random.rand(256, 256, 3) * 255
        image = QImage(imarray, imarray.shape[1], imarray.shape[0], 
            QImage.Format_RGB888)

        central = QWidget()
        layout = QVBoxLayout(central)
        self.setCentralWidget(central)

        label = QLabel()
        layout.addWidget(label, alignment=Qt.AlignCenter)
        label.setPixmap(QPixmap.fromImage(image))

        monospace = QFont()
        monospace.setFamily('monospace')
        dataView = QPlainTextEdit(readOnly=True, font=monospace)
        dataView.setWordWrapMode(QTextOption.WrapAnywhere)
        layout.addWidget(dataView)

        bytearray = QByteArray()
        buffer = QBuffer(bytearray)
        image.save(buffer, 'PNG')
        base64data = bytes(bytearray.toBase64()).decode()

        dataView.appendHtml('''
            Raw image data size: {}<br/>
            Saved image data size: {}<br/>
            Base64 data size: {}<br/>
            Ratio: {}<br/><br/>
            Base64 contents:<br/><br/>
            {}
        '''.format(
            imarray.size, 
            len(bytearray), 
            len(base64data), 
            len(base64data) / imarray.size, 
            base64data
        ))
        dataView.moveCursor(QTextCursor.Start)

        imageData = '''
            <img src="data:image/png;base64,{}" width=128 height=128>
        '''.format(base64data)
        label.setToolTip('This is a tooltip.<br/>' + imageData)

    def sizeHint(self):
        return QApplication.primaryScreen().size()
musicamante
  • 41,230
  • 6
  • 33
  • 58