I'd like to print a file selected with a file picker (or somehow) with
certain extension, so that PyQt or printer will automatically recognize
the format (e.g. pdf, ms word, excel, txt, html, jpg etc.)
So far, I have found here how to print the content
of the TextEdit, but I'd like to print files with various formats.
Is it possible with PyQt5 or should I search elsewhere?
Printing a plain text document does not require a viewer, since the print_() function actually calls the internal QDocument's print_() function:
filePath, filter = QFileDialog.getOpenFileName(self, 'Open file', '', 'Text (*.txt)')
if not filePath:
return
doc = QtGui.QTextDocument()
try:
with open(filePath, 'r') as txtFile:
doc.setPlainText(txtFile.read())
printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
if not QtPrintSupport.QPrintDialog(printer, self).exec_():
return
doc.print_(printer)
except Exception as e:
print('Error trying to print: {}'.format(e))
You might want to add some functionalities to set the page size, document margins, font sizes etc, before actually printing (just read the QTextDocument docs), I'll leave that to you.
Printing from html files is almost similar, but you'll need to use the QWebEnginePage class from QtWebEngineWidgets. See this answer.
Do not use QTextDocument.setHtml(), as Qt has limited support for html tags.
The same is valid for PDF files too, the difference is that the file has to be loaded through setUrl() and the QWebEngineSettings.PluginsEnabled setting has to be enabled through page.settings().setAttribute(setting, bool) in case it's not.
Read the documentation about PDF File Viewing.
Printing images could be done through two approaches.
The first and simpler, is to create a temporary html file that embeds the image and load to a webengine page as above (you could add controls for zoom/scaling).
Alternatively, you could directly print using QPainter, but you'll have to relate to the printer resolution and image size, so you'd probably want to have a preview dialog before actually printing the image, otherwise it might be too small (or too big).
While more complex than a plain <html><img src=""></html>, this allows a better control on the positioning and sizing of the image[s].
class ImagePrintPreview(QtWidgets.QDialog):
def __init__(self, parent, printer, pixmap):
super().__init__(parent)
self.printer = printer
self.pixmap = pixmap
layout = QtWidgets.QGridLayout(self)
self.viewer = QtWidgets.QLabel()
layout.addWidget(self.viewer, 0, 0, 1, 2)
self.resoCombo = QtWidgets.QComboBox()
layout.addWidget(self.resoCombo, 1, 0)
self.zoom = QtWidgets.QSpinBox(minimum=50, maximum=200, suffix='%')
self.zoom.setValue(100)
self.zoom.setAccelerated(True)
layout.addWidget(self.zoom, 1, 1)
self.zoom.valueChanged.connect(self.updatePreview)
self.buttonBox = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok|QtWidgets.QDialogButtonBox.Cancel)
layout.addWidget(self.buttonBox)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
default = printer.resolution()
self.resoCombo.addItem(str(default), default)
for dpi in (150, 300, 600, 1200):
if dpi == default:
continue
self.resoCombo.addItem(str(dpi), dpi)
self.resoCombo.currentIndexChanged.connect(self.updatePreview)
self.updatePreview()
def updatePreview(self):
# create a preview to show how the image will be printed
self.printer.setResolution(self.resoCombo.currentData())
paperRect = self.printer.paperRect(self.printer.DevicePixel)
printRect = self.printer.pageRect(self.printer.DevicePixel)
# a temporary pixmap that will use the printer's page size
# note that page/paper are QRectF, they have a QSizeF which has to
# be converted to a QSize
pm = QtGui.QPixmap(paperRect.size().toSize())
# new pixmap have allocated memory for their contents, which usually
# result in some random pixels, just fill it with white
pm.fill(QtCore.Qt.white)
# start a qpainter on the pixmap
qp = QtGui.QPainter(pm)
# scale the pixmap to the wanted zoom value
zoom = self.zoom.value() * .01
scaled = self.pixmap.scaledToWidth(int(self.pixmap.width() * zoom), QtCore.Qt.SmoothTransformation)
# paint the pixmap aligned to the printing margins
qp.drawPixmap(printRect.topLeft(), scaled)
# other possible alternatives:
# Center the image:
# qp.translate(printRect.center())
# delta = QtCore.QPointF(scaled.rect().center())
# qp.drawPixmap(-delta, scaled)
# To also rotate 90° clockwise, add this to the above:
# qp.rotate(90)
# *after* qp.translate() and before qp.drawPixmap()
# when painting to a non QWidget device, you always have to end the
# painter before being able to use it
qp.end()
# scale the temporary pixmap to a fixed width
self.viewer.setPixmap(pm.scaledToWidth(300, QtCore.Qt.SmoothTransformation))
def exec_(self):
if super().exec_():
self.printer.setResolution(self.resoCombo.currentData())
# do the same as above, but paint directly on the printer device
printRect = self.printer.pageRect(self.printer.DevicePixel)
qp = QtGui.QPainter(self.printer)
zoom = self.zoom.value() * .01
scaled = self.pixmap.scaledToWidth(int(self.pixmap.width() * zoom), QtCore.Qt.SmoothTransformation)
qp.drawPixmap(printRect.topLeft(), scaled)
# as above, that's important!
qp.end()
class ImagePrinter(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
selBtn = QtWidgets.QPushButton('Open image')
layout.addWidget(selBtn)
selBtn.clicked.connect(self.selectFile)
def selectFile(self):
filePath, filter = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/tmp', 'Images (*.jpg *.png)')
if not filePath:
return
pixmap = QtGui.QPixmap(filePath)
if pixmap.isNull():
return
printer = QtPrintSupport.QPrinter(QtPrintSupport.QPrinter.HighResolution)
if QtPrintSupport.QPrintDialog(printer, self).exec_():
ImagePrintPreview(self, printer, pixmap).exec_()
Note that I couldn't test this under windows, so it might be necessary to change things related to the resolution (possibly by using printer.supportedResolutions()).
As already explained in the comments, printing to other (and possibly proprietary) formats requires external modules.
Related
I'm trying to learn PyQt5, and I've got this code:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.label = QLabel()
canvas = QPixmap(400, 300)
canvas.fill(Qt.white)
self.label.setPixmap(canvas)
self.setCentralWidget(self.label)
def mouseMoveEvent(self, e):
painter = QPainter(self.label.pixmap())
painter.drawPoint(e.x(), e.y())
painter.end()
self.update()
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
And I can draw using right click to draw, but when I left click, it drags the window instead of drawing. This even happens when I make the window fullscreen so I can't move it. How can I stop it from dragging the window so it will draw instead?
In some configurations (specifically, on Linux, and depending on the window manager settings), dragging the left mouse button on an empty (non interactive) area of a QMainWindow allows dragging the whole window.
To prevent that, the mouse move event has to be accepted by the child widget that receives it.
While this can be achieved with an event filter, it's usually better to use a subclass, and this is even more important whenever the widget has to deal with mouse events it receives, exactly like in this case.
Another aspect that has to be considered is that just updating the QLabel pixmap is not completely sufficient, because it doesn't automatically force its update. Also, since Qt 5.15, QLabel.pixmap() doesn't return a pointer to the pixmap, but rather its copy. This means that you should always keep a local reference to the pixmap for the whole time required to access it (otherwise your program will crash), and then call setPixmap() again with the updated pixmap after "ending" the painter. This will automatically schedule an update of the label.
The above may be a bit confusing if you're not used to languages that allow pointers as arguments, but, in order to clarify how it works, you can consider the pixmap() property similarly to the text() one:
text = self.label.text()
text += 'some other text'
The above will obviously not change the text of the label, most importantly because, in most languages (including Python) strings are always immutable objects, so text += ... actually replaces the text reference with another string object.
To clarify, consider the following:
text1 = text2 = self.label.text()
text1 += 'some other text'
print(text1 == text2)
Which will return False.
Now consider this instead:
list1 = list2 = []
list1 += ['item']
print(list1 == list2)
Which will return True, because list is a mutable type, and in python changing the content of a mutable type will affect any reference to it[1], since they refer to the same object.
Until Qt < 5.15, the pixmap of QLabel behaved similarly to a list, meaning that any painting on the label.pixmap() would actually change the content of the displayed pixmap (requiring label.update() to actually show the change). After Qt 5.15 this is no longer valid, as the returned pixmap behaves similarly to a returned string: altering its contents won't change the label's pixmap.
So, the proper way to update the pixmap is to:
handle the mouse event in the label instance (either by subclassing or using an event filter), and not in a parent;
get the pixmap, keep its reference until painting has completed, and call setPixmap() afterwards (mandatory since Qt 5.15, but also suggested anyway);
Finally, QLabel has an alignment property that, when using a pixmap, is used to set the alignment of the pixmap to the available space that the layout manager provides. The default is left aligned and vertically centered (Qt.AlignLeft|Qt.AlignVCenter).
QLabel also features the scaledContents property, which always scales the pixmap to the current size of the label (not considering the aspect ratio).
The above means one of the following:
the pixmap will always be displayed at its actual size, and eventually aligned within its available space;
if the scaledContents property is True, the alignment is ignored and the pixmap will be always scaled to the full extent of its available space; whenever that property is True, the resulting pixmap is also cached, so you have to clear its cache every time (at least, with Qt5);
if you need to always keep aspect ratio, using QLabel is probably pointless, and you may prefer a plain QWidget that actively draws the pixmap within a paintEvent() override;
Considering the above, here is a possible implementation of the label (ignoring the ratio):
class PaintLabel(QLabel):
def mouseMoveEvent(self, event):
pixmap = self.pixmap()
if pixmap is None:
return
pmSize = pixmap.size()
if not pmSize.isValid():
return
pos = event.pos()
scaled = self.hasScaledContents()
if scaled:
# scale the mouse position to the actual pixmap size
pos = QPoint(
round(pos.x() * pmSize.width() / self.width()),
round(pos.y() * pmSize.height() / self.height())
)
else:
# translate the mouse position depending on the alignment
alignment = self.alignment()
dx = dy = 0
if alignment & Qt.AlignRight:
dx += pmSize.width() - self.width()
elif alignment & Qt.AlignHCenter:
dx += round((pmSize.width() - self.width()) / 2)
if alignment & Qt.AlignBottom:
dy += pmSize.height() - self.height()
elif alignment & Qt.AlignVCenter:
dy += round((pmSize.height() - self.height()) // 2)
pos += QPoint(dx, dy)
painter = QPainter(pixmap)
painter.drawPoint(pos)
painter.end()
# this will also force a scheduled update
self.setPixmap(pixmap)
if scaled:
# force pixmap cache clearing
self.setScaledContents(False)
self.setScaledContents(True)
def minimumSizeHint(self):
# just for example purposes
return QSize(10, 10)
And here is an example of its usage:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.label = PaintLabel()
canvas = QPixmap(400, 300)
canvas.fill(Qt.white)
self.label.setPixmap(canvas)
self.hCombo = QComboBox()
for i, hPos in enumerate(('Left', 'HCenter', 'Right')):
hAlign = getattr(Qt, 'Align' + hPos)
self.hCombo.addItem(hPos, hAlign)
if self.label.alignment() & hAlign:
self.hCombo.setCurrentIndex(i)
self.vCombo = QComboBox()
for i, vPos in enumerate(('Top', 'VCenter', 'Bottom')):
vAlign = getattr(Qt, 'Align' + vPos)
self.vCombo.addItem(vPos, vAlign)
if self.label.alignment() & vAlign:
self.vCombo.setCurrentIndex(i)
self.scaledChk = QCheckBox('Scaled')
central = QWidget()
mainLayout = QVBoxLayout(central)
panel = QHBoxLayout()
mainLayout.addLayout(panel)
panel.addWidget(self.hCombo)
panel.addWidget(self.vCombo)
panel.addWidget(self.scaledChk)
mainLayout.addWidget(self.label)
self.setCentralWidget(central)
self.hCombo.currentIndexChanged.connect(self.updateLabel)
self.vCombo.currentIndexChanged.connect(self.updateLabel)
self.scaledChk.toggled.connect(self.updateLabel)
def updateLabel(self):
self.label.setAlignment(Qt.AlignmentFlag(
self.hCombo.currentData() | self.vCombo.currentData()
))
self.label.setScaledContents(self.scaledChk.isChecked())
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())
Note that if you need more advanced control over the pixmap display and painting (including aspect ratio, but also proper zoom capabilities and any possible complex feature), then the common suggestion is to completely ignore QLabel, as said above: either use a basic QWidget, or consider the more complex (but much more powerful) Graphics View Framework. This will also allow proper editing features, as you can add non-destructive editing that will show ("paint") the result without affecting the actual, original object.
[1]: The above is based on the fact that a function or operator can actually mutate the object: the += operator actually calls the __add__ magic method that, in the case of lists, updates the contents of the same list.
I am new to QT. I have a frame on which both A4Size and extra are. Each one of them is a Qlabel and I set images to them. Now, I want to save everything found on the frame (A4Size and extra) but I find no way that works through the research I've done (saving everything from the frame to file). Is there a way to save both images (or even more) on the frame as a single image?
class Picture(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self)
self.ui = Ui_Picture()
self.ui.setupUi(self)
self.setFixedSize(1430, 1000)
def show_image(self):
if self.ui.A4_sheet.isChecked():
self.ui.A4Size.setPixmap("all.png")
self.ui.extra.setPixmap("new.png")
print("A4")
def download(self):
fname, filter = QFileDialog.getSaveFileName(self, 'Save Image')
self.ui.A4Size.pixmap().save(fname + ".png", "PNG", -1)
print("saved")
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = QtWidgets.QStackedWidget()
home_screen = MainWindow()
picture_screen = Picture()
widget.show()
widget.addWidget(home_screen)
widget.addWidget(picture_screen)
sys.exit(app.exec_())
My code above only saves A4Size even though extra is right on it.
Thank You.
You need to draw the two pixmaps into one with QPainter and then save it.
I am a C++ programmer, but I will try to write Python code now, I hope it will work for you.
source1 = QPixmap("all.png")
source2 = QPixmap("new.png")
# we will put the pixmaps next to each other horizontally
width = source1.width() + source2.width()
height = max(source1.height(), source2.height())
pixmap = QPixmap(width, height)
painter = QPainter(pixmap)
painter.drawPixmap(0, 0, source1)
painter.drawPixmap(source1.width(), 0, source2)
pixmap.save(fname + ".png")
I have window size issue according to the display resolution.
To dynamic control for window size, I set the size policy as Preferred
In case of 1280 X 1024 resolution, the auto adjusting window size function works well.
Low resolution such as 1280 X 800, the vertical window size is slightly larger than actual resolution.
But I found out the window size fit the display resolution, when i changed screen resolution in windows display configuration.
Could you share your comment or suggest resize the window according to display resolution ?
Qt provides a QScreen interface, which allows access to the "portion" of the visible desktop (theoretically) shown in each physical screen.
"Screens" can be retrieved from the QGuiApplication in various ways, so that we can have an interface to do various things:
access to all screens(), including the main one (primaryScreen();
signals that notify whenever screens are added or removed;
full and available geometries (the latter being the actual geometry excluding things like system menus or task bar), and various change notifications;
Considering the above, it's pretty easy to create a window that automatically adapts to screen changes.
In the following example, I've created a basic QWidget that adapts to the current main screen (or the cursor position) and accepts a scale ratio based on the screen size, and an aspect ratio that properly sets the size based on the screen.
class AutoResizeWindow(QtWidgets.QWidget):
def __init__(self, scale=2/3, aspectRatio=None):
super().__init__()
self.scale = scale
self.aspectRatio = aspectRatio
font = self.font()
font.setPointSize(font.pointSize() * 2)
font.setBold(True)
self.sizeLabel = QtWidgets.QLabel(font=font, alignment=QtCore.Qt.AlignCenter)
self.resizeButton = QtWidgets.QPushButton('Update to screen size')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.sizeLabel)
layout.addWidget(self.resizeButton)
self.updateScreens()
self.updateSize()
self.resizeButton.clicked.connect(self.updateSize)
QtWidgets.QApplication.instance().screenAdded.connect(self.updateScreens)
QtWidgets.QApplication.instance().screenRemoved.connect(self.updateSize)
def updateScreens(self):
for screen in QtWidgets.QApplication.screens():
try:
screen.availableGeometryChanged.connect(
self.updateSize, QtCore.Qt.UniqueConnection)
except TypeError:
# already connected
pass
def updateSize(self):
if not self.isVisible():
screen = QtWidgets.QApplication.screenAt(QtGui.QCursor.pos())
else:
center = self.geometry().center()
screen = QtWidgets.QApplication.screenAt(center)
if not center in screen.geometry():
screen = QtWidgets.QApplication.screenAt(QtGui.QCursor.pos())
screenGeo = screen.geometry()
if self.aspectRatio:
baseSize = QtCore.QSize(round(self.aspectRatio * 100), 100)
else:
baseSize = screenGeo.size()
newSize = baseSize.scaled(screenGeo.size(), QtCore.Qt.KeepAspectRatio)
newSize *= self.scale
windowGeo = QtCore.QRect(QtCore.QPoint(), newSize)
windowGeo.moveCenter(screenGeo.center())
self.setGeometry(windowGeo)
self.updateLabel()
def updateLabel(self):
screen = QtWidgets.QApplication.screenAt(self.geometry().center())
screenIndex = QtWidgets.QApplication.screens().index(screen)
screenSize = screen.size()
self.sizeLabel.setText('''
Screen {index} ("{name}")<br/>
Size: {sw}x{sh} ({sr:.02f}:1)<br/><br/>
Window size: {ww}x{wh} ({wr:.02f}:1)
'''.format(
index=screenIndex,
name = screen.name(),
sw = screenSize.width(),
sh = screenSize.height(),
sr = screenSize.width() / screenSize.height(),
ww = self.width(),
wh = self.height(),
wr = self.width() / self.height()
))
def moveEvent(self, event):
self.updateLabel()
def resizeEvent(self, event):
super().resizeEvent(event)
self.updateLabel()
Important notes:
by default, Qt resizes top-level windows to 2/3 of the screen size (unless any content forces a bigger size); overriding sizeHint() is not enough, as Qt will always limit the size to 2/3 of the screen width or height;
with the above code, switching screens can result in recursion problems, depending on the DPI scaling;
depending on the OS and screen layout, the geometry() and availableGeometry() might not always correspond to the real value for extended ("virtual") desktops;
this question is tagged for pyqt, which, unlike pyside, implements "magic methods" for some classes; among these, it supports __contains__() for both QRect and QRectF, allowing the usage of point in rect (which actually calls rect.contains(point) internally); I believe that the PyQt syntax is better, smart and more pythonic, but, if you use PySide, you must use the full Qt compliant syntax, otherwise you'll get an exception (because in considers the target object as an iterator if __contains__ is not defined):
if not screen.geometry().contains(center):
Short question:
I know how to draw text on a wx.Bitmap, but how can I draw text on a wx.Icon in wxpython so that it does not appear transparent?
Long question:
I have a wxpython based GUI application, that has a taskbar icon, which I set using mytaskbaricon.SetIcon("myicon.ico").
Now I would like to dynamically put some text on the icon, so I tried to use the wx .DrawText method as explained here.This works fine if I test this for bitmaps (which I use in menu items).
However, the taskbar requires an wxIcon instead of a wxBitmap. So I figured I'll convert the icon to a bitmap, draw the text, and then convert it back to an icon. This works, except that the text is not shown transparent. Why ? And how can I make the text NOT transparent ?
My code is as roughly follows:
import wx
class MyTaskBarIcon(wx.TaskBarIcon):
...
icon = wx.Icon("myicon.ico", wx.BITMAP_TYPE_ICO)
bmp = wx.Bitmap("myicon.ico", wx.BITMAP_TYPE_ICO)
memDC = wx.MemoryDC()
memDC.SetTextForeground(wx.RED)
memDC.SelectObject(bmp)
memDC.DrawText("A", 0, 0)
icon.CopyFromBitmap(bmp)
self.SetIcon(icon, APP_NAME_WITH_VERSION)
...
So, no errors raised and myicon.ico is shown, but the letter A is transparant (instead of red). If I use a .bmp file to start with (myicon.bmp) the text appears in the correct color (but the borders are jagged). I've played around with masks, foreground and background colors, but that didn't help.
(I am using Windows 7, Python 2.6, wxpython 2.8)
Edit: I've shortened my explanation, and made the code more self-contained
Short answer: It seems to me that there is a bug in this particular piece of wx code. I am going to report it and see what comes out of it.
Long answer: You can hack your way around. Setup a color, which is not used in the image. Then draw using that color and when done, fix alpha values and color of those pixels to match your expectation:
import wx
from wx import ImageFromStream, BitmapFromImage, EmptyIcon
import cStringIO, zlib
# ================================ ICON ======================================
def getData():
return zlib.decompress(
'x\xda\x01\x97\x03h\xfc\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\
\x00\x00\x00\x10\x08\x06\x00\x00\x00\x1f\xf3\xffa\x00\x00\x00\x04sBIT\x08\
\x08\x08\x08|\x08d\x88\x00\x00\x03NIDAT8\x8dm\xd2ML\x9bu\x00\xc7\xf1\xef\xf3\
<\xed\xda><\xa3#\xcb\x8a\x0cp\xac8\x15\x87\x89/ \x11\xd1d&:5&#n\xc9\\\xa2\
\xc6\xc3b\xe2\xd1y0Y2\xa3q^\xcc\xb8\x9a\xb9\xf9rQc\xc6\x0es\xa4\xd1\x91\xe98\
\xc8\x96\xb98H\xc3\x8b\xc0\xc6\x00\x91\xd2\xb2\xa7}\xda\xe7\xa5\xcf\xd3\xf6\
\xf9{0\xa2\x07\xbf\xf7_\xf29\xfc$\x00\xf1>\xb2\xd9\xc7\tI0$\xc0\xd5d\x06\xa5\
\x17q\xf9O\xa5\x0b$$\x85KB\xa2\xec\xcb\xbc\x1e}\x81\xdf\x01$q\x9a`>\xce\xc9`\
\xc7\x91#\xa1\xce\xa3;\xed\xdbg\xb3s\x19c\xe1\x9cz\xbe*A\x0f\x80\x80\xf4A\
\xeb\xb0\xfcPG\xa2;\x10\x8aI\xe5\xd9\x93\x8bB\xe6`l\x88U)\xf3-\xc7\xc3\xbb_{\
;r\xef\xe1Vci\xa4\xb0\xbc:\x17\xb8\xdczQ\xd3B5"A\x1f\x00\xa7"\xe39\x16\xfb\
\xd6_\xb1wu\x1f#\xa9\x15-k\xe6\xd4j\xa2D\xbf\xec\x95\x91\xe5PGX_\x18),.\xcei\
W\xdb\xbf\xd3:\xb7{49\x0e\xeem\x1dkAG+Z\xb4l\xdf\xc6o-\xc3\xea\x9fK\xbf\x84\
\xe5\xa6\xfe&\xa1>\xa8\xad)\xec\x96n}\xc6`E\xa8g7\x95d\xdbD\xf2\x82\xda\xae\
\x06\x08\xd95\x1e\xeej\xa2\xa1^F \xa1\x1b5\xae\xcf\xe5\xa8D\x14\xea\xf4\xf3\
\xdco\x9es\xb7\x9933\xe1Z\xe9U\t\xe0\xd8\xe7\x17?\t4\xecz7\x99\xd0hp\x05\x87\
\xf6u\x927\x0c6-\x87\xf6\xd6\x16\x00\xaa\x02\xbeN\xdd\xc2\xd7\x04\x99\xec:9K\
\xf9\xf8\xd37\x07\x8e\xcb\x00\x99\xca=\xbd\xbe\x00\xbf\xe4\xb1wO\x0c\xbb*\
\x08\x06\x83\x8c\xfd\xf8\x03E\xc3\xa0\xe2\xba\\\x1a\xfb\x99\xee=q\x8c\xac\
\x83#7RtC\x03\x00\x01\x80r\xd9\xea\xa9z2\x86\xeb\x13\x8bEpk\x82:U\xe5\x8f\
\x95\x15\xc6~\x1a\'=5\xc9\xb3\xcf\xef\xa7q\x87Jn\xd3A4\x04)\x97\xad\x1e\x00\
\x19\xc0\xb3-,\xbb\x82\xe3\xf9\xb85\xa8\xf8\x905J\xd4i\x1a\xe9\xa9I^:0\xc4#\
\xbd}\xb8U\xa8x>\x96]\xc1\xb3-\xb6\x04^\xd9N\x17K\x91gv\xc6\x03,el\xeek\x8b\
\x82\x1c\xe6\xd1\xc7\xfby\xa0g/j\xb4\x1e\xd3\x85\xd5\x8cE0"\x91+\xd9xe;\xfd\
\xaf\xc0\xb1\xae\x14\r\x03\xbd\xecr\xf5\xe6\x06\xc1\x10\xd4\x85\x83<5\xf8$\
\xf1\xc6zB\x80\x16\x86_of\xf1\xf0(\x1a\x06\x9ec]\xd9\x12\xb8\xb63\xea:\xe6\
\xa1\xd9\x9a\xd2-\xb7U\xf9bD\xf0\\o\x82\xaeD\x1d\x08X\xc9Z\x8c^\xcbP4\xd6\
\x99\xdf\xb00\xf3k3\x08e\x14#\xfa\xe7\xeb}GO\xbd\xf5Xr\xc7\xf0BAS[\xe3\x1a\
\xb1P\x08\xc5\x97\xa9\xf9\x82\x8aT\xc5\xf0\\\xaa\xd5*\xaa\xb8k\xa7\xefl\xbes\
\xfd\xcc\xb1\xd3[\x02\x80\xe17\x9e\x98\x8fF\xa3jv3_;12\xaf\xccJ*\xb2\x12\x06\
\xc0\xaf\x95iV+\xbc\xf7rR\xc8rcD\xa2kv\xe0\xcc\xdf;\x19 \x95J5\x17\n\x85\xef\
\xc3\xe10f\xa9`\x98\xf9;\x1f\xda\xb9\xe9qk\xe3\x86nm\xdc\xd0\xed\xdc\xf4\xf8\
\xf2\xf2\xfc\x07\x85B\xdel\x8e\xc7%]\xd7/\xa7R\xa9\xe4\x96\xc04M\xc7q\x9c\
\xb5\x89\x89\x89N!\xc4\xd3S\xdf|4\xcd\xfftw\xff\x97_]\xd3\xf5I\xc0\xf2}\xdf\
\x02\xf8\x0b\xc1.\x9e\xd8Y.\x85\x85\x00\x00\x00\x00IEND\xaeB`\x822\x86\xba\
\xb3' )
def getBitmap():
return BitmapFromImage(getImage())
def getImage():
stream = cStringIO.StringIO(getData())
return ImageFromStream(stream)
def getIcon():
icon = EmptyIcon()
icon.CopyFromBitmap(getBitmap())
return icon
# ============================================================================
class MainWindow(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.number = 0
self.Bind(wx.EVT_CLOSE, self.OnClose)
self.panel = wx.Panel(self)
self.button = wx.Button(self.panel, label="Test")
self.button.Bind(wx.EVT_BUTTON, self.OnButton)
self.tbicon = wx.TaskBarIcon()
self.tbicon.SetIcon(getIcon(), "Test")
self.sizer = wx.BoxSizer()
self.sizer.Add(self.button)
self.panel.SetSizerAndFit(self.sizer)
self.Show()
# --------------------------------------------------------------------------
def OnClose(self, e):
self.tbicon.Destroy()
self.Destroy()
wx.Exit()
# --------------------------------------------------------------------------
def OnButton(self, e):
# HERE WE GO!
self.number += 1
bitmap = getBitmap()
# Find unused color
image = bitmap.ConvertToImage()
my_solid_color = wx.Color(*image.FindFirstUnusedColour(0, 0, 0)[1:])
# Use the unused *unique* color to draw
dc = wx.MemoryDC()
dc.SetTextForeground(my_solid_color)
dc.SelectObject(bitmap)
dc.DrawText(str(self.number), 0, 0)
dc.SelectObject(wx.NullBitmap)
# Convert the bitmap to Image again
# and fix the alpha of pixels with that color
image = bitmap.ConvertToImage()
for x in range(image.GetWidth()):
for y in range(image.GetHeight()):
p = wx.Colour(image.GetRed(x, y),
image.GetGreen(x, y),
image.GetBlue(x, y))
if p == my_solid_color:
image.SetAlpha(x, y, 255) # Clear the alpha
image.SetRGB(x, y, 0, 0, 0) # Set the color that we want
# Convert back to Bitmap and save to Icon
bitmap = image.ConvertToBitmap()
icon = wx.IconFromBitmap(bitmap)
self.tbicon.SetIcon(icon, "Test")
app = wx.App(False)
win = MainWindow(None)
app.MainLoop()
Note: A had to add some icon. You can ignore that part of the code.
Just a guess, but perhaps create your initial icon as an "EmptyIcon", then copy the bmp to it.
import wx
class MyTaskBarIcon(wx.TaskBarIcon):
...
icon = wx.EmptyIcon()
bmp = wx.Bitmap("myicon.ico", wx.BITMAP_TYPE_ICO)
bmp = WriteTextOnBitmap("A", bmp, color=wx.RED) #this function is as in the link above
icon.CopyFromBitmap(bmp)
self.SetIcon(icon, APP_NAME_WITH_VERSION)
...
I've isolated the cause of the problem to be the image, since the code seems to work with other png images with transparency. However, it doesn't seem to work with the one image I need it to. This would be of great help seeing as I'm trying to make a nice shaped window.
The image:
The code:
import wx
class PictureWindow(wx.Frame):
def __init__(self, parent, id, title, pic_location):
# For PNGs. Must be PNG-8 for transparency...
self.bmp = wx.Image(pic_location, wx.BITMAP_TYPE_PNG).ConvertToBitmap()
framesize = (self.bmp.GetWidth(), self.bmp.GetHeight())
# Launch a frame the size of our image. Note the position and style stuff...
# (Set pos to (-1, -1) to let the OS place it.
# This style wx.FRAME_SHAPED is a frameless plain window.
wx.Frame.__init__(self, parent, id, title, size=framesize, pos = (50, 50), style = wx.FRAME_SHAPED)
r = wx.RegionFromBitmap(self.bmp)
self.SetShape(r)
# Define the panel and place the pic
panel = wx.Panel(self, -1)
self.mainPic = wx.StaticBitmap(panel, -1, self.bmp)
# Set an icon for the window if we'd like
#icon1 = wx.Icon("icon.ico", wx.BITMAP_TYPE_ICO)
#self.SetIcon(icon1)
self.Show()
# The paint stuff is only necessary if doing a shaped window
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Main()
def OnPaint(self, event):
dc = wx.PaintDC(self)
dc.DrawBitmap(self.bmp, 0, 0, True)
def Main(self):
sizer = wx.GridBagSizer()
button = wx.Button(self,-1,label="Click me !")
sizer.Add(button, (0,1))
# What pic are we opening?
pic_location = r"C:\Users\user\Pictures\CPUBAR\A4.png" # PNG must be png-8 for transparency...
app = wx.App(redirect=0) # the redirect parameter keeps stdout from opening a wx gui window
PictureWindow(None, -1, 'Picture Viewer', pic_location)
app.MainLoop()
This is in Windows 7, btw.
wx.RegionFromBitmap uses the bitmap's mask to set the shape. If your source image has an alpha channel then it won't have a mask and so wx.RegionFromBitmap will not be able to determine what shape to use. You can convert the source image such that all pixels are either fully opaque or fully transparent, and then wx.Image will load it with a mask instead of an alpha channel. Or you can convert it at runtime using wx.Image's ConvertAlphaToMask method before converting it to a wx.Bitmap.
Your code says that it needs to be in png-8 format in order for transparency to work.
First of all, is the image in png-8 format?
second, why is this a requisite for transparency???
You're converting your image to a bitmap - bitmaps do not support transparency.
As a workaround you could use a .gif (if you can stand the limited color set).
They only support a 1-bit alpha channel.