pythonpyqt5pyside2qimagememoryview

Dividing QImage into sub-images in PySide2


I have a large image composed of subimages I have received from another program, I will eventually need to use the subimages as icons on buttons. I understand the math to access the bits + bytes of the QImage, it is described clearly in the below C++ question. What I don't understand is how to obtain the offseted bits/memory location with PySide2.

Dividing QImage to smaller pieces

It's a simple conversion of the above C++ to Python:

def myCreateSubImage(largeImage, subRect):
    nOffset = subRect.x() * largeImage.depth() / 8 + subRect.y() * largeImage.bytesPerLine()
    return QImage(largeImage.bits() + nOffset, subRect.width(), subRect.height(), largeImage.bytesPerLine(), subRect.format())

However:


Solution

  • The correct solution depends on whether PySide or PyQt is being used. When accessing the raw data of a QImage, PySide uses a python memoryview object, whereas PyQt uses a sip.array object (which is obtained via a sip.voidptr). However, both these objects provide support for Python's buffer protocol, so they can be used in the same way.

    To fix your example, you must therefore create a slice of the array of image data that begins at the calculated offset. It's more efficient to use constBits for this as (unlike bits) it does not make a deep copy of the data first.

    Below is simple demo script that works with both PySide and PyQt. When used with the sample image from the other question, it produces this result (for grid-ref [2, 4]):

    screenshot

    from PySide2 import QtCore, QtGui, QtWidgets
    # from PyQt5 import QtCore, QtGui, QtWidgets
    
    image = QtGui.QImage('sample.png')
    
    width, height = 192, 108
    row, column = 2, 4
    
    rect = QtCore.QRect(column * width, row * height, width, height)
    
    offset = rect.x() * image.depth() // 8 + rect.y() * image.bytesPerLine()
    
    bits = image.constBits()
    if QtCore.__name__.startswith('PyQt'):
        bits = bits.asarray(image.sizeInBytes())
    
    subimage = QtGui.QImage(bits[offset:], width, height,
                            image.bytesPerLine(), image.format())
    
    app = QtWidgets.QApplication(['Test'])
    label = QtWidgets.QLabel()
    label.setGeometry(600, 100, width + 50, height + 50)
    label.setAlignment(QtCore.Qt.AlignCenter)
    label.setPixmap(QtGui.QPixmap.fromImage(subimage))
    label.show()
    app.exec_()