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 更改了样式表:这 非常 重要,因为它可以为复杂的小部件设置通用属性(最重要的是,滚动区域)会产生图形问题,因为它们会传播到该小部件的 所有 个子项,包括滚动条(使用某些样式会变得丑陋)、上下文菜单和模式对话框。
我正在尝试允许我的文本编辑将图像从 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 更改了样式表:这 非常 重要,因为它可以为复杂的小部件设置通用属性(最重要的是,滚动区域)会产生图形问题,因为它们会传播到该小部件的 所有 个子项,包括滚动条(使用某些样式会变得丑陋)、上下文菜单和模式对话框。