How to fill a QImage with pil image data in python

417 views Asked by At

I want to crop a portion of a screenshot in python using qt. I use PIL for grabing the screen and i convert it back to QImage to QPixmap for my QLabel My code works with square width and height. I have trouble converting PIL image to QImage.

load_image_from_pil variable loads an image from IMAGE_PATH if it is False, else it grabs the creen

Any help appreciated

import sys
import cv2
import numpy as np
from PIL import ImageGrab, Image
from PySide2.QtCore import QSize, Qt, QRect
from PySide2.QtGui import QImage,QColor,QPixmap
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton,QLabel


#True = from screen using pil image grab
#False = from path
load_image_from_pil=True

IMAGE_PATH=r"C:\Users\kosta\Pictures\Art_inspiration1.png"

#####    EXPECTED FUNCTIONALITY   #######
#crop a screenshot of the screen to x,y=0,0 with width=100 ,height=100

def NumpyArray_to_qimage(np_arr,x,y,w,h):
    #make qimage from numpy
    qimage = QImage(np_arr.data, w, h, 3 * w, QImage.Format_RGB888) 
    return qimage

def PIL_to_NumpyArray(pil_img):
    return np.array(pil_img)
def PIL_to_qimage(pil_img, x,y,w,h):
    #convert pil to numpy array
    np_arr = PIL_to_NumpyArray(pil_img)


    qimage = NumpyArray_to_qimage(np_arr,x,y,w,h)
    return qimage


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # global x,y,w,h
        x,y,w,h=0,0,100,300

        self.setWindowTitle("My App1")

        self.label = QLabel("label")
        self.label.setGeometry(QRect(x,y,w,h))

        qimage=None
        # qimage = QImage(im_np.data, w, h, 3 * w, QImage.Format_RGB32) 
        if load_image_from_pil:
            print("screenshot and cropping image")
            #screenshot whole screen
            grab_image = ImageGrab.grab()
            
            # crop portion of image
            image_arr = np.array(grab_image)
            
            cropped_image= image_arr[x:x+w,y:y+h]

            # cropped_image = Image.fromarray(np.uint8(cropped_image)).convert('RGB')
            # cropped_image = Image.fromarray(image_arr.astype('uint8'), 'RGB')
            cropped_image = Image.fromarray(cropped_image)

            cv2.imshow('Live', image_arr[y:y+h,x:x+w])


            # image_arr = np.array(grab_image)
            # image_arr = image_arr[x:x+w,y:y+h]
            # image = Image.fromarray(image_arr)
            
            qimage = PIL_to_qimage(cropped_image,x,y,w,h)
        else:
            print("loading image from file")
            qimage = QImage(IMAGE_PATH)
        
        pixmap = QPixmap.fromImage(qimage)
        print(qimage)
        self.label.setPixmap(pixmap)   
        # Set the central widget of the Window.
        self.setCentralWidget(self.label)
        self.resize(self.label.width(), self.label.height())

app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()
2

There are 2 answers

1
Hoodlum On

I believe the easiest way to get what you are looking for is by replacing your PIL_to_qimage function as follows (as suggested in this answer):

def PIL_to_qimage(pil_img):
    temp = pil_img.convert('RGBA')
    return QImage(
        temp.tobytes('raw', "RGBA"),
        temp.size[0],
        temp.size[1],
        QImage.Format.Format_RGBA8888
    )

That being said, there seems to be a lot of confusion in your code concerning the indices. For cropping a PIL image, it might be interesting to look into the Image.crop method, like so:

cropped_image = grab_image.crop((x, y, x+w, y+h))

This lets you skip a number of error-prone steps, which is always nice.

1
kostas petsis On

i went with the solution of @musicamante that suggested to grab the screen using QScreen.grabWindow for screenshot and QPixmap.copy(QRect) for croppping, and dropping the PIL library as it is not needed.

Here is the complete working code for anyone having the same issue or want to test it.

import sys
import cv2
import numpy as np
from PIL import ImageGrab, Image
from PySide2.QtCore import QSize, Qt, QRect
from PySide2.QtGui import QImage,QColor,QPixmap,QScreen
from PySide2.QtWidgets import QApplication, QMainWindow, QPushButton,QLabel

import PySide2
#True = from screen using pil image grab
#False = from path
load_image_from_pil=True

IMAGE_PATH=r"C:\Users\kosta\Pictures\Art_inspiration1.png"

#####    EXPECTED FUNCTIONALITY   #######
#crop a screenshot of the screen to x,y=0,0 with width=100 ,height=100

def NumpyArray_to_qimage(np_arr,x,y,w,h):
    #make qimage from numpy
    qimage = QImage(np_arr.data, w, h, 3 * w, QImage.Format_RGB888) 
    return qimage

def PIL_to_NumpyArray(pil_img):
    return np.array(pil_img)
def PIL_to_qimage(pil_img, x,y,w,h):
    #convert pil to numpy array
    np_arr = PIL_to_NumpyArray(pil_img)


    qimage = NumpyArray_to_qimage(np_arr,x,y,w,h)
    return qimage


# Subclass QMainWindow to customize your application's main window
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        # global x,y,w,h
        x,y,w,h=0,0,800,780

        self.setWindowTitle("My App1")

        self.label = QLabel("label")
        self.label.setGeometry(QRect(x,y,w,h))

        qimage=None
        pixmap=None
        # qimage = QImage(im_np.data, w, h, 3 * w, QImage.Format_RGB32) 
        if load_image_from_pil:
            print("screenshot and cropping image")
            # screenshot whole screen
            screen = QApplication.primaryScreen()
            winid = QApplication.desktop().winId()
            grab_image = screen.grabWindow(winid)

            # grab_image is a Qpixmap now
            
            
            #### crop portion of image ####
            # convert grab_image to QImage in order to crop by QImage.copy(QRect)
            # grab_image = grab_image.toImage().copy(x,y,w,h)
            rect = QRect(x,y,w,h)
            cropped_image = grab_image.copy(rect)
            
            # return qimage
            pixmap=cropped_image
        else:
            print("loading image from file")
            qimage = QImage(IMAGE_PATH)
        
        # pixmap = QPixmap.fromImage(qimage)
        # print(qimage)
        self.label.setPixmap(pixmap)   
        # Set the central widget of the Window.
        self.setCentralWidget(self.label)
        self.resize(self.label.width(), self.label.height())

app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec_()