Mime.Hasimage()' Returns 'True' But 'Mime.Imagedata()' Returns 'None' on Linux

`mime.hasImage()` returns `true` but `mime.imageData()` returns `None` on Linux

That hasImage() returns True does not imply that imageData() returns a QImage since it only indicates that the user copied an image to the clipboard, and in what format do I copy the image? Well, it could be png, jpg, etc or it could provide the url for the client application to download or html to insert it into the client application and then obtain the image by rendering the HTML.

So in general the application from which the image was copied is responsible for the sending format and that there is no restrictive standard for that format but there are common formats.

The following example shows the logic to handle the images that come from urls and HTML:

#!/usr/bin/python

import sys
from functools import cached_property

from PyQt5.QtCore import pyqtSignal, QObject, QUrl
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
from PyQt5.QtGui import QGuiApplication, QImage, QPixmap
from PyQt5.QtWidgets import QApplication, QWidget, QLabel

from bs4 import BeautifulSoup

class ImageDownloader(QObject):
finished = pyqtSignal(QImage)

def __init__(self, parent=None):
super().__init__(parent)

self.manager.finished.connect(self.handle_finished)

@cached_property
def manager(self):
return QNetworkAccessManager()

def start_download(self, url):
self.manager.get(QNetworkRequest(url))

def handle_finished(self, reply):
if reply.error() != QNetworkReply.NoError:
print("error: ", reply.errorString())
return
image = QImage()
image.loadFromData(reply.readAll())
self.finished.emit(image)

class ClipboardManager(QObject):
imageChanged = pyqtSignal(QImage)

def __init__(self, parent=None):
super().__init__(parent)

QGuiApplication.clipboard().dataChanged.connect(
self.handle_clipboard_datachanged
)

self.downloader.finished.connect(self.imageChanged)

@cached_property
def downloader(self):
return ImageDownloader()

def handle_clipboard_datachanged(self):
mime = QGuiApplication.clipboard().mimeData()
if mime.hasImage():
image = mime.imageData()
if image is not None:
self.imageChanged.emit(image)
elif mime.hasUrls():
url = mime.urls()[0]
self.downloader.start_download(urls[0])
elif mime.hasHtml():
html = mime.html()
soup = BeautifulSoup(html, features="lxml")
imgs = soup.findAll("img")
if imgs:
url = QUrl.fromUserInput(imgs[0]["src"])
self.downloader.start_download(url)
else:
for fmt in mime.formats():
print(fmt, mime.data(fmt))

def main():
app = QApplication(sys.argv)

label = QLabel(scaledContents=True)
label.resize(250, 150)
label.move(300, 300)
label.setWindowTitle("Simple")
label.show()

manager = ClipboardManager()
manager.imageChanged.connect(
lambda image: label.setPixmap(QPixmap.fromImage(image))
)

sys.exit(app.exec_())

if __name__ == "__main__":
main()

PyQt5: Copy an Image from QTextEdit and paste into paint

In order to copy an image to the clipboard, you have to first ensure that the selection only contains one image and nothing else: no text and no other images.

Considering that images occupy only one character in the text document (used as a "placeholder" for the image object), you can check if the length of the selection is actually 1, and then verify that the character format at the end of the selection is a QTextImageFormat, which is a special type of QTextFormat used for images; the position at the end of the selection is fundamental, as formats are always considered at the left of the cursor.

By overriding createMimeDataFromSelection and doing the above you can easily return a QMimeData that contains the image loaded from the document's resources.

Note that a better (and simpler) approach to add images from the clipboard is to use the existing Qt features, so that you can avoid an unnecessary conversion through PIL and passing through two data buffers.

IMAGE_EXTENSIONS = [
str(f, 'utf-8') for f in QtGui.QImageReader.supportedImageFormats()]

class TextEdit(QtWidgets.QTextEdit):
def __init__(self):
super().__init__()
self.setMaximumWidth(800)
self.setStyleSheet("QTextEdit { background-color: rgb(255, 255, 255); }")

def canInsertFromMimeData(self, source):
if source.hasImage():
return True
else:
return super(TextEdit, self).canInsertFromMimeData(source)

def createMimeDataFromSelection(self):
cursor = self.textCursor()
if len(cursor.selectedText()) == 1:
cursor.setPosition(cursor.selectionEnd())
fmt = cursor.charFormat()
if fmt.isImageFormat():
url = QtCore.QUrl(fmt.property(fmt.ImageName))
image = self.document().resource(
QtGui.QTextDocument.ImageResource, url)
mime = QtCore.QMimeData()
mime.setImageData(image)
return mime
return super().createMimeDataFromSelection()

def insertImage(self, image):
if image.isNull():
return False
if isinstance(image, QtGui.QPixmap):
image = image.toImage()

doc = self.document()
if image.width() > doc.pageSize().width():
image = image.scaledToWidth(int(doc.pageSize().width()),
QtCore.Qt.SmoothTransformation)

ba = QtCore.QByteArray()
buffer = QtCore.QBuffer(ba)
image.save(buffer, 'PNG', quality=95)
binary = base64.b64encode(ba.data())
HTMLBin = "<img src= \"data:image/*;base64,{}\" max-width=100% max-height=100%></img>".format(
str(binary, 'utf-8'))
self.textCursor().insertHtml(HTMLBin)

return True

def insertFromMimeData(self, source):
if source.hasImage() and self.insertImage(source.imageData()):
return
elif source.hasUrls():
for url in source.urls():
if not url.isLocalFile():
continue
path = url.toLocalFile()
info = QtCore.QFileInfo(path)
if not info.suffix().lower() in IMAGE_EXTENSIONS:
continue
elif self.insertImage(QtGui.QImage(path)):
return
super().insertFromMimeData(source)

Note that I changed the stylesheet with a proper class selector: this is very important as setting generic properties for complex widgets (most importantly, scroll areas) can create graphical issues, as they are propagated to all children of that widget, including scroll bars (which become ugly with certain styles), context menus and modal dialogs.

Drag and Drop QLabels with PyQt5 Pixmap and text

When you set a QPixmap to your label, it loses its text. So, you can save the labels text before setting the pixmap:

class DraggableLabel(QLabel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setText(args[0])
self._text = None
self.setAcceptDrops(True)
print("my text in init ", self.text())

def setPixmap(self, pixmap):
if pixmap.isNull():
self._text = None
else:
self._text = self.text()
super().setPixmap(pixmap)

def text(self):
if self._text:
return self._text
return super().text()


Related Topics



Leave a reply



Submit