`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
I Cant Init Google Cloud Sdk on Ubuntu
Finding the Command for a Specific Pid in Linux from Python
How Does One Set Specific Vim-Bindings in Ipython 5.0.0
Permission Denied When Executing Python File in Linux
Python Sigkill Catching Strategies
Python:Undefined Symbol: Pyunicodeucs2_Decodeutf8
Shell Start/Stop for Python Script
How to Pass a Keystroke (Alt+Tab) Using Popen.Communicate (On Linux)
Subprocess.Popen: 'Oserror: [Errno 13] Permission Denied' Only on Linux
Mime.Hasimage()' Returns 'True' But 'Mime.Imagedata()' Returns 'None' on Linux
Trying to Simulate Constant Byte Rate. Confusion with Time.Sleep Results
How to Check Which Version of Nltk, Scikit Learn Installed