PyQt5:从 QTextEdit 复制图像并粘贴到油漆中

发布于 2025-01-15 05:17:17 字数 4429 浏览 3 评论 0 原文

我试图允许我的文本编辑将图像从 QTextEdit 复制并粘贴到 Microsoft Paint。 目前我有一个自定义文本编辑,允许我将图像粘贴到文本编辑中。我将整个文档保存为 HTML 并稍后重新加载,因此当我粘贴图像时,我使用 其中二进制是调整大小的 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 中,而不是在该类之外完成,这样我就可以在其他项目的其他地方重复使用相同的功能。

I'm trying to allow my text edit to copy and paste an image from QTextEdit to Microsoft Paint.
Currently I have a custom text edit that allows me to paste images into the text edit. I save the entire document as HTML and reload it later so when I paste the images I use <img src= \"data:image/*;base64," + binary + "\" max-width=100% max-height=100%/> where the binary is a resized base64 binary image. This way the image binary is embedded into the HTML and I don't need to keep track of any source images.

Here is the code for my QTextEdit that allows for that paste.

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)

Now what I need to do is when the image is copied again, I basically need to remove the HTML img tag around the binary and copy just the binary.

I tried to overload the QTextEdit.copy() method which didn't seem to do anything, and I tried to overwrite QTextEdit.createMimeDataFromSelection() but I wasn't able to find a way to get the copy to work the same as before once I did that.

Does anyone have any information on how to accomplish this? I'd like the solution to be within my custom QTextEdit and not be done outside of that class just so I can re-use this same functionality elsewhere with other projects.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

碍人泪离人颜 2025-01-22 05:17:17

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

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

通过覆盖 createMimeDataFromSelection 并执行上述操作,您可以轻松返回包含从文档资源加载的图像的 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)

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

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文