How to display PDF with python-poppler-qt4? - python

I have downloaded and installed python-poppler-qt4 and I am now trying out a simple Qt application to display a PDF page. I've followed what I've been able to get from the web, i.e. convert the PDF to a QImage, then to a QPixMap, but it doesn't work (all I get is a small window with no visible content).
I may have failed at some point (QImage.width() returns the width I have input, QPixMap.width() returns 0).
Here is the code:
#!/usr/bin/env python
import sys
from PyQt4 import QtGui, QtCore
import popplerqt4
class Application(QtGui.QApplication):
def __init__(self):
QtGui.QApplication.__init__(self, sys.argv)
self.main = MainWindow()
self.main.show()
class MainWindow(QtGui.QFrame):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.layout = QtGui.QVBoxLayout()
self.doc = popplerqt4.Poppler.Document.load('/home/benjamin/test.pdf')
self.page = self.doc.page(1)
# here below i entered almost random dpi, position and size, just to test really
self.image = self.page.renderToImage(150, 150, 0, 0, 210, 297)
self.pixmap = QtGui.QPixmap()
self.pixmap.fromImage(self.image)
self.label = QtGui.QLabel(self)
self.label.setPixmap(self.pixmap)
self.layout.addWidget(self.label)
self.setLayout(self.layout)
if __name__ == "__main__":
application = Application()
sys.exit(application.exec_())
Where does it go wrong here? Thanks.

I'm not familiar with python, so this might not apply directly, but QPixmap::fromImage is a static function that returns a QPixmap. So your code should read something like:
self.pixmap = QtGui.QPixmap.fromImage(self.image)
In other words, self.pixmap.fromImage doesn't change self.pixmap, it returns a new pixmap generated from the image you give it as a parameter.

yes i know that the question has an answer.
but i faced an error when i do the same thing in PyQt5 to show a PDF file as an image.
and i found the solution of my problem.
i posted this answer to help who faced the same problem.
if you want to show a pdf file in your PyQt5 program you have 2 choices
1 - the first one is to use web engine (but it takes a lot of resources from the ram)
2 - the second it to convert the pdf into an image and show it on label
i chose the second choice
and this is my code to show a pdf file as an image and to solve the problem :
from PyQt5 import QtWidgets,QtCore,QtGui
import pypdfium2 as pdfium
the_file = "My_PDF_File.pdf"
application = QtWidgets.QApplication([])
window = QtWidgets.QWidget()
window.resize(700,600)
window_layout = QtWidgets.QGridLayout()
label_to_display_the_page = QtWidgets.QLabel()
label_to_display_the_page.setAlignment(QtCore.Qt.AlignCenter)
label_to_display_the_page_geometry = label_to_display_the_page.geometry()
pdf = pdfium.PdfDocument(the_file)
page = pdf.get_page(1)
pil_image = page.render_topil(scale=1,rotation=0,crop=(0, 0, 0, 0),greyscale=False,optimise_mode=pdfium.OptimiseMode.NONE)
image = pil_image.toqimage()
label_pixmap = QtGui.QPixmap.fromImage(image)
size = QtCore.QSize(label_to_display_the_page_geometry.width()-50,label_to_display_the_page_geometry.height()-50)
label_to_display_the_page.setPixmap(label_pixmap.scaled(size,QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
window_layout.addWidget(label_to_display_the_page)
window.setLayout(window_layout)
window.show()
application.exec()

Related

PyQt 5: QPainter returns false while rendering QGraphicsScene to a QImage

Currently I am working on a program, to display SIP-Trace log files. It is written in Python 3.7 using the PyQt 5(.11.3) module to load and operate a GUI made in QDesigner. As a main feature it parses the SIP-Trace file and displays it as a sequence diagram to a QGraphicsScene with QGraphicsObjects.
My problem lies in the following: For later reference, the content of the QGraphicsScene should be saved as an image file, like .jpg or .png. In the Qt/PyQt documentation I found the useful sounding command QGraphicsScene.render() which renders the content of the GraphicsScene to a saveable file like QImage using QPainter. In the last days, I tried a couple of ways/sample codes found here and elsewhere, but cannot render the GraphicsScene to the QImage much less to an image file. Since I am rather new to Python and Qt, I think I am missing some basic setting somewhere. Following is a minimal version of my code.
# -*- coding: utf8 -*-
"""Class for getting a sequence diagram of a sip traffic"""
from PyQt5.QtWidgets import *
from PyQt5 import uic
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class VoipGui(QMainWindow):
""" Class that handles the interaction with the UI """
def __init__(self, parent=None):
super().__init__(parent)
self.ui = uic.loadUi("main_window.ui", self)
self.showMaximized()
self.sequence_scene = QGraphicsScene()
self.ui.graphicsView.setScene(self.sequence_scene)
# self.sequence_scene.setSceneRect(0, 0, 990, 2048)
# sets the spacing between nodes
# For more than three nodes columns should be generated in a more automatic way
self.left_column = 51
self.middle_column = 381
self.right_column = 711
self.flow_height = 60 # Sets the spacing between the arrows in the flowchart
# --------------------------------- /class init and var set -------------------------------------------
self.actionOpenFile.triggered.connect(self.on_open_file)
self.actionCloseFile.triggered.connect(self.on_close_file)
self.actionCloseProgram.triggered.connect(self.close)
self.actionSaveFile.triggered.connect(self.save_seq_image)
# --------------------------------- /connecting slots and signals ----------------------------
def on_open_file(self):
"""Dummy version of the open file dialog"""
self.draw_node(self.left_column, 5, "192.168.2.1", 10)
self.draw_node(self.middle_column, 5, "192.168.2.22", 10)
def on_close_file(self):
self.ui.textBrowser.clear()
self.sequence_scene.clear()
def save_seq_image(self):
""" Here lies the problem: Save the rendered sequence scene to file for later use"""
rect_f = self.sequence_scene.sceneRect()
# rect = self.sequence_scene.sceneRect().toRect()
# img = QPixmap(rect.size())
img = QImage()
p = QPainter()
# p.setPen(QColor(255, 255, 255))
# p.setViewport(rect)
painting = p.begin(img)
self.sequence_scene.render(p, target=QRectF(img.rect()), source=rect_f)
p.end()
if painting:
print("Painter init pass")
elif not painting:
print("Painter init fail")
saving = img.save("save.jpg")
if saving:
print("Saving Pass")
elif not saving:
print("Saving Not Pass")
def draw_node(self, x_pos, y_pos, ip_address, y_stops):
"""Participating devices are displayed as these nodes"""
width = 100.0
height = 40.0
pc_box = QGraphicsRectItem(x_pos - 50, y_pos, width, height)
self.sequence_scene.addItem(pc_box)
pc_ip = QGraphicsTextItem("%s" % ip_address)
pc_ip.setPos(x_pos - 50, y_pos)
self.sequence_scene.addItem(pc_ip)
node_line = QGraphicsLineItem(x_pos, y_pos + 40, x_pos, y_pos + (y_stops * self.flow_height))
self.sequence_scene.addItem(node_line)
def show_window():
app = QApplication(sys.argv)
dialog = VoipGui()
dialog.show()
sys.exit(app.exec_())
if __name__ == "__main__":
show_window()
The problem is simple, in render() you are indicating that the size of the target is equal to that of QImage, and how size is QImage?, how are you using QImage() the size is QSize(0, 0) so it can not be generated the image, the solution is to create a QImage with a size:
def save_seq_image(self):
""" Here lies the problem: Save the rendered sequence scene to file for later use"""
rect_f = self.sequence_scene.sceneRect()
img = QImage(QSize(640, 480), QImage.Format_RGB888)
img.fill(Qt.white)
p = QPainter(img)
self.sequence_scene.render(p, target=QRectF(img.rect()), source=rect_f)
p.end()
saving = img.save("save.jpg")
print("Saving Pass" if saving else "Saving Not Pass")
Output:

QPalette for beginners - where to start

I recently made my first python and PyQt program and now I'm looking to play with colors to make it look good. I've been looking around the documentation and the QPalette documentation seems to suggest that a QPalette is the way to go as opposed to manually setting the colors for each widget. Problem is I can't find a whole lot of info out there on how to use this in practice and while there is certainly a lot of data in the documentation for a beginner like me it is lacking in many examples!
From what I understand my goal here is to establish within a QPalette object the sort of "global" colors for my app, and then I assign that palette to all of my widgets right? So what is the best way to go about this? For example I want to set all of my buttons to a dark gray background. Looking at the documentation it seems you need to set the QPalette.button() color using .setColor but I'm not able to get this to work (see line #83). But I was able to set the background color of the dialog specifically just above that.
Here's just the Gui part of my code:
import sys
from PyQt4 import QtGui, QtCore
class BatchTable(QtGui.QTableWidget):
def __init__(self, parent):
super(BatchTable, self).__init__(parent)
self.setAcceptDrops(True)
self.setColumnCount(5)
self.setColumnWidth(1,50)
self.hideColumn(1)
self.hideColumn(3)
self.hideColumn(4)
self.horizontalHeader().setStretchLastSection(True)
self.setHorizontalHeaderLabels(QtCore.QString("Status;Alpha;File;Full Path;Process ID").split(";"))
class ffmpegBatch(QtGui.QWidget):
def __init__(self):
super(ffmpegBatch, self).__init__()
self.initUI()
def initUI(self):
self.pBar = QtGui.QProgressBar()
self.edit = QtGui.QTextEdit()
cmdGroup = QtGui.QGroupBox("Commandline arguments")
self.alphaCheck = QtGui.QCheckBox("Output alpha as separate file")
fpsLbl = QtGui.QLabel("FPS:")
self.fpsCombo = QtGui.QComboBox()
self.fpsCombo.addItem("29.97")
self.fpsCombo.addItem("23.976")
hbox1 = QtGui.QHBoxLayout()
hbox1.addWidget(self.alphaCheck)
hbox1.addWidget(fpsLbl)
hbox1.addWidget(self.fpsCombo)
cmdGroup.setLayout(hbox1)
saveGroup = QtGui.QGroupBox("Output")
saveLocationBox = QtGui.QHBoxLayout()
self.outputLocation = QtGui.QLineEdit()
self.popBtn = QtGui.QPushButton("Pop dir")
saveLocationBox.addWidget(self.outputLocation)
saveLocationBox.addWidget(self.popBtn)
saveBtnsBox = QtGui.QHBoxLayout()
pasteFromClipboard = QtGui.QPushButton("Paste from clipboard")
upOneBtn = QtGui.QPushButton("./")
upTwoBtn = QtGui.QPushButton("././")
saveBtnsBox.addWidget(pasteFromClipboard)
saveBtnsBox.addWidget(upOneBtn)
saveBtnsBox.addWidget(upTwoBtn)
saveMasterBox = QtGui.QVBoxLayout()
saveMasterBox.addLayout(saveLocationBox)
saveMasterBox.addLayout(saveBtnsBox)
saveGroup.setLayout(saveMasterBox)
self.runBtn = QtGui.QPushButton("Run Batch Transcode")
showDebugger = QtGui.QPushButton("Show debugger")
showDebugger.setCheckable(True)
self.mainBox = QtGui.QVBoxLayout()
self.table = BatchTable(self)
self.mainBox.addWidget(self.table)
self.mainBox.addWidget(cmdGroup)
self.mainBox.addWidget(saveGroup)
self.mainBox.addWidget(self.runBtn)
self.mainBox.addWidget(self.pBar)
self.mainBox.addWidget(showDebugger)
self.mainBox.addWidget(self.edit)
self.edit.hide()
self.setLayout(self.mainBox)
self.setGeometry(300, 300, 600, 700)
self.setWindowTitle('FFMPEG Batch Converter')
# make pretty
palette = QtGui.QPalette()
palette.setColor(self.backgroundRole(), QtGui.QColor(40, 40, 40))
self.setPalette(palette)
palette.setColor(palette.button(), QtGui.QColor(100, 100, 100))
self.runBtn.setPalette(palette)
def main():
app = QtGui.QApplication(sys.argv)
ex = ffmpegBatch()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Never mind, looks like stylesheets with cascading are a way better way to go. For example to set all buttons within the application to an absolutely beautiful yellow color: QApplication.setStyleSheet("QPushButton { background: yellow }")

PySide Custom Widget w/ Pixmap error (QPainter::begin: Paint device returned engine == 0, type: 2)

I have written my first custom widget -- a small status light I use to show the progress of a background task. I am doing something wrong as I repeatedly keep getting the following error emitted when the mouse is over, or interacting with the Qt Window that contains the custom widget:
QPainter::begin: Paint device returned engine == 0, type: 2
The application does not crash but keeps dumping this error to std out. I am fairly new to Qt and have looked around a bit to try and figure out how to resolve this issue to no avail. I am hoping someone with more Qt experience than I can walk me through the proper way to go about implementing a custom widget with a paintEvent(). I have attached the code below:
from PySide import QtGui, QtCore
class LightWidget(QtGui.QLabel):
def __init__(self,parent=None):
super(LightWidget,self).__init__(parent)
self.setPixmap(QtGui.QPixmap())
self.setMinimumSize(15,15)
self.setMaximumSize(15,15)
self.set_fill_color(.6,.6,.6)
self.set_line_color(0,0,0)
self.set_line_width(1)
def set_fill_color(self,r,b,g,a=1.0,hsv=False):
ncolor=QtGui.QColor()
if hsv:
ncolor.setHsvF(r,g,b,a)
else:
ncolor.setRgbF(r,g,b,a)
self._fill_color=ncolor
def set_line_color(self,r,g,b,a=1.0,hsv=False):
ncolor=QtGui.QColor()
if hsv:
ncolor.setHsvF(r,g,b,a)
else:
ncolor.setRgbF(r,g,b,a)
self._line_color=(ncolor)
def set_line_width(self,width):
self._line_width=width
def paintEvent(self,e):
painter=QtGui.QPainter(self.pixmap())
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
radx=self.width()*0.5
rady=self.height()*0.5
offset=self._line_width
center=QtCore.QPoint(radx,rady)
gradient = QtGui.QRadialGradient(center
,radx
,QtCore.QPointF(radx*0.5, rady*0.5))
c_color = QtGui.QColor(0,0,0,0)
c_color.setHsvF(self._fill_color.hueF()
,self._fill_color.saturationF()
,max(min(self._fill_color.valueF()*1.5,1.0),0) )
o_color = QtGui.QColor(0,0,0,0)
o_color.setHsvF(self._fill_color.hueF()
,self._fill_color.saturationF()
,max(min(self._fill_color.valueF()*.5,1),0))
gradient.setColorAt(0, c_color)
gradient.setColorAt(0.25, self._fill_color)
gradient.setColorAt(1, o_color)
pen=QtGui.QPen()
pen.setColor(self._line_color)
pen.setWidthF(self._line_width)
brush = QtGui.QBrush(gradient)
painter.setPen(pen)
painter.setBrush(gradient)
painter.drawEllipse(center,radx-offset,rady-offset)
painter.end()
def test():
form = QtGui.QWidget()
wid = LightWidget(form)
form.setWindowTitle('Status Light Widget')
form.show()
return form
def main():
import sys
app = QtGui.QApplication(sys.argv)
window =test()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You're conflating what you paint on:
def paintEvent(self,e):
# Here you start the painter on the pixmap
painter=QtGui.QPainter(self.pixmap())
# But here you start it on the widget proper
painter.begin(self)
...
The truth is, you don't need any pixmaps, and you don't need to derive from QLabel. Derive from QWidget, and paint directly on the widget:
def paintEvent(self,e):
painter=QtGui.QPainter(self)
# The begin() is redundant. Don't invoke it.
...

Interpreting simple code to python code

I build a lot of very basic QT repetitive programs at work with QT python then compile them with py2exe. These are used by my coworkers at controllers for another program. I am wonder how I could build a python interpreter that would convert simple commands into actual python code similar to the way Processing converts a simplified code into java.
For example the "simple code" file may read:
slider('Distance', 'dist', 50, 100, 'int')
button('Apply Changes', 'apply')
which I would then interpret into a pyQT program form using types below:
slider(label, name, min, max, type)
button(label, name)
These would all be written to new python file, which when run would generate the appropriate form. The part I am stuck on is how to interpret the "simple code" into python code.
Thanks in advance
Solution #1
The code below is uses the regex and split idea by SPEN-zar to identify the widget type, then parse the inputs to generate the necessary outputs. Obviously, this will be expanded upon to generate real pyQT file, but this is the basic demonstrates the basic logic.
Thank you for the help.
import re
input = ["slider('Distance', 'dist', 50, 100, 'int')", "button('Apply Changes', 'apply')"]
pattern = r"([a-z]+)\s*\((.*)\)"
rexp = re.compile(pattern)
for line in input:
content = rexp.findall(line)
if content[0][0] == 'slider':
params = content[0][1].split(',')
name = params[0]
label = params[1]
minimum = float(params[2])
maximum = float(params[3])
print 'Slider Type: name-%s, label-%s, min-%f, max-%f' % (name, label, minimum, maximum)
elif content[0][0] == 'button':
params = content[0][1].split(',')
name = params[0]
label = params[1]
print 'Button Type: name-%s, label-%s' % (name, label)
else:
print 'This widget type is not recognized'
Solution #2
After doing further research into blender's suggestion, I have modified the code below that uses a class to define a button. This class can then be easily added to the form as many times as needed. By building classes for all the types needed it would be easy to easy generate forms as well maintain and add to the library.
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent = None):
super(Main, self).__init__(parent)
# main button
self.addButton = QtGui.QPushButton('button to add other widgets')
self.addButton.clicked.connect(self.addWidget)
# scroll area widget contents - layout
self.scrollLayout = QtGui.QFormLayout()
# scroll area widget contents
self.scrollWidget = QtGui.QWidget()
self.scrollWidget.setLayout(self.scrollLayout)
# scroll area
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.scrollWidget)
# main layout
self.mainLayout = QtGui.QVBoxLayout()
# add all main to the main vLayout
self.mainLayout.addWidget(self.addButton)
self.mainLayout.addWidget(self.scrollArea)
# central widget
self.centralWidget = QtGui.QWidget()
self.centralWidget.setLayout(self.mainLayout)
# set central widget
self.setCentralWidget(self.centralWidget)
def addButton(self):
self.scrollLayout.addRow(Test())
class Test(QtGui.QWidget):
def __init__( self, parent=None):
super(Test, self).__init__(parent)
self.pushButton = QtGui.QPushButton('I am in Test widget')
self.pushButton.clicked.connect(self.testPush)
layout = QtGui.QHBoxLayout()
layout.addWidget(self.pushButton)
self.setLayout(layout)
def testPush(self):
print "The test button was pushed!"
app = QtGui.QApplication(sys.argv)
myWidget = Main()
for i in xrange(5):
myWidget.addButton()
myWidget.show()
app.exec_()
Have you tried just parsing it into a .ui format? That is XML, which is pretty straightforward.
Whichever output format you prefer, try PLY for the lexing and parsing. Otherwise, just use a regular expression to search for the substring with "(" ")" surrounding it and use .split(',') to get the arguments. That's my take.

PyQt4 - add a text edit area animation example

I have realized a python simple application, without any animation on it.
Now I want to add a simple animation, triggered by a signal (a button click for example), which on trigger enlarges the width of the windows and shows a new text area with some text in it.
Honestly, I am quite new to python/pyqt4, and I do not know much about the animation framework.
I tried to add this to my class code, for example in a method called clicking on the about menu :) :
self.anim = QPropertyAnimation(self, "size")
self.anim.setDuration(2500)
self.anim.setStartValue(QSize(self.width(), self.height()))
self.anim.setEndValue(QSize(self.width()+100, self.height()))
self.anim.start()
and this enlarge my window as I want.
Unfortunately I have no idea how to insert a new text area, avoiding the widgets already present to fill the new space (actually, when the window enlarge, the widgets use
all the spaces, thus enlarging themselves)
Could someone help me knowing how to add the text area appearance animation?
Any help is appreciated...really...
One way to achieve this is to animate the maximumWidth property on both the window and the text-edit.
The main difficulty is doing it in a way that plays nicely with standard layouts whilst also allowing resizing of the window. Avoiding flicker during the animation is also quite tricky.
The following demo is almost there (the animation is slightly jerky at the beginning and end):
from PyQt4 import QtGui, QtCore
class Window(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self)
self._offset = 200
self._closed = False
self._maxwidth = self.maximumWidth()
self.widget = QtGui.QWidget(self)
self.listbox = QtGui.QListWidget(self.widget)
self.button = QtGui.QPushButton('Slide', self.widget)
self.button.clicked.connect(self.handleButton)
self.editor = QtGui.QTextEdit(self)
self.editor.setMaximumWidth(self._offset)
vbox = QtGui.QVBoxLayout(self.widget)
vbox.setContentsMargins(0, 0, 0, 0)
vbox.addWidget(self.listbox)
vbox.addWidget(self.button)
layout = QtGui.QHBoxLayout(self)
layout.addWidget(self.widget)
layout.addWidget(self.editor)
layout.setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
self.animator = QtCore.QParallelAnimationGroup(self)
for item in (self, self.editor):
animation = QtCore.QPropertyAnimation(item, 'maximumWidth')
animation.setDuration(800)
animation.setEasingCurve(QtCore.QEasingCurve.OutCubic)
self.animator.addAnimation(animation)
self.animator.finished.connect(self.handleFinished)
def handleButton(self):
for index in range(self.animator.animationCount()):
animation = self.animator.animationAt(index)
width = animation.targetObject().width()
animation.setStartValue(width)
if self._closed:
self.editor.show()
animation.setEndValue(width + self._offset)
else:
animation.setEndValue(width - self._offset)
self._closed = not self._closed
self.widget.setMinimumSize(self.widget.size())
self.layout().setSizeConstraint(QtGui.QLayout.SetFixedSize)
self.animator.start()
def handleFinished(self):
if self._closed:
self.editor.hide()
self.layout().setSizeConstraint(QtGui.QLayout.SetMinAndMaxSize)
self.widget.setMinimumSize(0, 0)
self.setMaximumWidth(self._maxwidth)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.move(500, 300)
window.show()
sys.exit(app.exec_())

Categories

Resources