PyQt5:从 QTextEdit 复制图像并粘贴到画图

PyQt5: Copy an Image from QTextEdit and paste into paint

我正在尝试允许我的文本编辑将图像从 QTextEdit 复制并粘贴到 Microsoft Paint。 目前我有一个自定义文本编辑器,允许我将图像粘贴到文本编辑器中。我将整个文档保存为 HTML 并稍后重新加载它,所以当我粘贴图像时我使用 <img src= \"data:image/*;base64," + binary + "\" max-width=100% max-height=100%/>,其中二进制文件是调整大小的 base64 二进制图像。通过这种方式,图像二进制文件被嵌入到 HTML 中,我不需要跟踪任何源图像。

这是允许粘贴的 QTextEdit 代码。

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

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

    def insertFromMimeData(self, source):

        cursor = self.textCursor()
        document = self.document()

        if source.hasUrls():

            for u in source.urls():
                file_ext = os.path.splitext(str(u.toLocalFile()))[1].lower()
                if u.isLocalFile() and file_ext in IMAGE_EXTENSIONS:
                    
                    img = Image.open(u.toLocalFile())
                    width, height = img.size

                    max_width  = int(document.pageSize().width())
                    max_height = int(max_width * height / width)
                    
                    if width > max_width or height > max_height:
                        img = img.resize((max_width, max_height), Image.ANTIALIAS)

                    output = io.BytesIO()
                    img.save(output, format=file_ext.replace(".",""), quality=3000)
                    binary = str(base64.b64encode(output.getvalue()))
                    binary = binary.replace("b'", "")
                    binary = binary.replace("'", "")
                    
                    HTMLBin = "<img src= \"data:image/*;base64," + binary + "\" max-width=100% max-height=100%/>"
                    cursor.insertHtml(HTMLBin)                   
                    
                    return

                else:
                    # If we hit a non-image or non-local URL break the loop and fall out
                    # to the super call & let Qt handle it
                    break

            else:
                # If all were valid images, finish here.
                return
            
        elif source.hasImage():
            img = source.imageData()
            
            #Save the image from the clipboard into a buffer
            ba = QtCore.QByteArray()
            buffer = QtCore.QBuffer(ba)
            buffer.open(QtCore.QIODevice.WriteOnly)
            img.save(buffer, 'PNG')
          
            #Load the image binary into PIL.Image (Python Image Library)
            img = Image.open(io.BytesIO(ba.data()))
            
            #Find what the max size of the document is.
            width, height = img.size
            
            max_width = 775
            max_height = int(max_width * height / width)
            
            #Resize to make sure the image fits in the document.
            if (width > max_width or height > max_height) and (max_width!=0 and max_height!=0):
                img = img.resize((max_width, max_height), Image.ANTIALIAS)

            #Convert binary into a string representation of the base64 to embed the image into the HTML
            output = io.BytesIO()
            img.save(output, format='PNG', quality=95)
            binary = str(base64.b64encode(output.getvalue()))
            
            # base64_data = ba.toBase64().data()
            # binary = str(base64_data)
            binary = binary.replace("b'", "")
            binary = binary.replace("'", "")
            
            #Write the HTML and insert it at the Document Cursor.
            HTMLBin = "<img src= \"data:image/*;base64," + binary + "\" max-width=100% max-height=100%> </img>"
            cursor.insertHtml(HTMLBin)
            return
            
        elif source.hasHtml():
            html = source.html()
            cursor.insertHtml(html)
            return

        super(TextEdit, self).insertFromMimeData(source)

现在我需要做的是再次复制图像时,我基本上需要删除二进制文件周围的 HTML img 标签并只复制二进制文件。

我试图重载似乎没有做任何事情的 QTextEdit.copy() 方法,我试图覆盖 QTextEdit.createMimeDataFromSelection() 但我找不到方法完成后让副本像以前一样工作。

有人知道如何完成此操作吗?我希望解决方案在我的自定义 QTextEdit 中,而不是在 class 之外完成,这样我就可以在其他项目的其他地方重新使用相同的功能。

为了将图像复制到剪贴板,您必须首先确保所选内容仅包含 一张 图像而没有其他任何内容:没有文本也没有其他图像。

考虑到图片在文本文档中只占用一个字符(用作图片对象的“占位符”),可以检查选择的长度是否实际为1,然后验证字符格式在选择的末尾是一个QTextImageFormat,它是一种用于图像的特殊类型的QTextFormat;选择末尾的位置是基本的,因为格式总是在光标的左侧。

通过重写 createMimeDataFromSelection 并执行上述操作,您可以轻松 return 包含从文档资源加载的图像的 QMimeData。

请注意,从剪贴板添加图像的更好(也更简单)的方法是使用现有的 Qt 功能,这样您就可以避免通过 PIL 和传递两个数据缓冲区进行不必要的转换。

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)

请注意,我使用适当的 class selector 更改了样式表:这 非常 重要,因为它可以为复杂的小部件设置通用属性(最重要的是,滚动区域)会产生图形问题,因为它们会传播到该小部件的 所有 个子项,包括滚动条(使用某些样式会变得丑陋)、上下文菜单和模式对话框。