How to get pixels from Gtk.DrawingArea? - python

I'm writing a simple paint like application:
#!/usr/bin/env python3
from gi.repository import Gtk
class Application(Gtk.Window):
def __init__(self):
super().__init__(Gtk.WindowType.TOPLEVEL, title='paint')
self.connect('destroy', self.__on_destroy)
vbox = Gtk.VBox()
drawing_area = Gtk.DrawingArea()
drawing_area.conenct('draw', self.__on_draw)
vbox.pack_start(drawing_area, True, True, 2)
self.add(vbox)
self.show_all()
def __on_draw(self, widget, g):
g.set_source_rgb(200, 0, 0)
g.move_to(16, 0)
g.line_to(16, 32)
g.stroke()
def save_to_file(self, filename):
# How to get pixels from drawing_area?
pass
def __on_destroy(self, e):
Gtk.main_quit()
app = Application()
Gtk.main()
Now I want to save user drawing. How can I access pixels inside Gtk.DrawingArea widget?

Quote from this following SO link
Gtk.DrawingArea derives from Gtk.Window.Hence you can use
Gdk.pixbuf_get_from_window() to get the contents of the drawing area
into a GdkPixbuf.Pixbuf and then use the GdkPixbuf.Pixbuf.savev()
function to write the pixbuf as an image on disk.
You can follow the link for complete code.Also this link can also help you.

Related

How to update (draw) a widget and only this widget in GTK

When I have, for instance the following widget hierarchy:
Window
Box
Drawing Area 1
Drawing Area 2
and I call queue_draw on Drawing Area 1 then also Drawing Area 2 is updated (draw). Is this behavior intended and can I prevent this and only update Drawing Area 1, because of performance reasons.
Edit: Added a minimal example:
#!/usr/bin/env python3
import pgi
pgi.require_version('Gtk', '3.0')
from pgi.repository import Gtk, Gdk, GdkPixbuf
import cairo
class DrawingArea(Gtk.DrawingArea):
def __init__(self, id):
super().__init__()
self.id = id
self.vexpand = True
self.hexpand = True
self.connect("draw", self.on_draw)
def on_draw(self, area, context):
print ("on_draw ", self.id)
context.set_source_rgb (1, 0, 0)
context.rectangle (0,0,20,20)
context.fill ()
return False
class Window(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
self.set_title("Test Draw Radial Gradient")
self.set_default_size(400, 200)
self.connect("destroy", Gtk.main_quit)
self.drawingArea1 = DrawingArea (1)
self.drawingArea2 = DrawingArea (2)
box = Gtk.Box ()
box .pack_start (self.drawingArea1, True, True, 0)
box .pack_start (self.drawingArea2, True, True, 0)
button = Gtk.Button.new_with_label("Click Me")
box .pack_start (button, True, True, 0)
button.connect("clicked", self.on_click_me_clicked)
self.add(box)
def on_click_me_clicked(self, button):
print ("Button clicked")
self.drawingArea1.queue_draw()
window = Window()
window.show_all()
Gtk.main()
Unless I misunderstand Gtk source, it's some kind of intended behaviour. queue_draw leads to invalidation of part of window and it recursively updates widgets. Most likely (it's just guesses!) it tells box to be redrawn and box redraws all of it's children.
Anyway, your widgets should be always ready to be redrawn. If user resizes window -- it's many redraws. If he minimizes and brings window back -- it's another redraw.
First of all, make sure your redraw is a real bottleneck. Second, I'd suggest implementing some kind of caching or proxy: a cairo surface of the same size as widget, when your data changes you draw it there and on redraw you just paint that surface on widget's surface. Another approach would be preparing drawables in another thread (which is questionable if you use python), but again: start with some measurements and find out if redraw is really slow.

Python GTK3+ Using Button to Load Images

I'm creating an app that can use a button to load and display an image. I don't understand how this would work with Python Gtk3+.
I want to load the next image into the GUI location where the first image is... a simple replacement.
image = Gtk.Image()
image.set_from_file(self.image)
grid.attach(image, 0, 2, 1, 1) #grid location
button = Gtk.Button("Load next image")
button.connect("clicked", self.load_image)
grid.attach(button, 2, 1, 1, 1) #grid location
button1 = Gtk.Button("Load next image")
button1.connect("clicked", self.load_new_image)
grid.attach(button1, 2, 2, 1, 1) #grid location
def load_image(self, widget):
self.image = 'image_path'
def load_new_image:
self.image = 'image_path'
I thought of Event Boxes, or something similar, but I'm kind of at a loss. The image section is only run once on instantiation, so I don't understand how it should get updated with events. I want the image to change if the self.image path name changes in another class method. Any ideas?
Maybe I am misunderstanding the question, but should not it be that simple?
I will explain the answer as #DanD. pointed out.
You just need to set the image path (self.image.set_from_file(img)) on the load_image method (connected with the button clicked signal) with the desired image.
Current Gtk.Image will display automatically the new loaded image.
import gi
import os
import sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class GridWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Grid Example")
grid = Gtk.Grid()
self.add(grid)
self.button = Gtk.Button(label="Button 1")
self.image = Gtk.Image()
grid.add(self.button)
grid.add(self.image)
self.button.connect("clicked", self.load_image)
self.count = 0
for root, _, files in os.walk(sys.argv[1]):
self.images = [os.path.join(root, f) for f in files]
def load_image(self, event):
img = self.images[self.count]
print(img)
self.image.set_from_file(img)
self.count = self.count + 1
win = GridWindow()
win.connect("destroy", Gtk.main_quit)
win.show_all()
Gtk.main()

How to remove border around Gtk Widgets and Window?

I'm making a Gtk.Window using PyGi Gtk3 and It adds an annoying border around widgets and the main window:
The border is this grey border between the two images and in the outside of the window. Anyone knows how to completely remove it? So the two images could be seamless joint together.
This is my code:
#!/usr/bin/python3
#encoding:utf-8
from gi.repository import Gtk, Gdk
class TestMainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Test Window")
self.set_resizable(False)
self.box = Gtk.Box(orientation='vertical')
self.add(self.box)
self.album_cover = Gtk.Image()
self.album_cover.set_from_file('../reference/album-cover.jpg')
self.box.pack_start(self.album_cover, True, True, 0)
self.album_cover2 = Gtk.Image()
self.album_cover2.set_from_file('../reference/album-cover.jpg')
self.box.pack_end(self.album_cover2, True, True, 0)
def main():
win = TestMainWindow()
win.connect('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()
if __name__ == '__main__':
main()
Give a try at setting the "margins" property inherited from GtkWidget to FALSE. In C this is done using g_object_set.

Pasting QMimeData to another window's QTextEdit

I'm still quite a newbie with Python and PyQt so I have a really basic question. I have some text and images in a parent window inside a QTextEdit widget and I'm trying to copy all the content to a child window's QTextEdit. But for some reason I can't get it to copy the image - only the text is copied not the image. Here's a snippet of the code that's giving me trouble:
self.textEdit.selectAll()
data = self.textEdit.createMimeDataFromSelection()
self.child_window.textEdit.insertFromMimeData(data) # doesn't work with images
Here's is the small program that I'm trying to run:
import sys
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MyWindow(QtGui.QWidget):
def __init__(self,parent=None):
super(MyWindow,self).__init__(parent)
self.textEdit = QtGui.QTextEdit(self)
self.textEdit.setText("Hello World\n")
self.pushButton = QtGui.QPushButton(self)
self.pushButton.setText("Copy and paste to Child Window")
self.pushButton.clicked.connect(self.click_copy_data)
self.pushButton2 = QtGui.QPushButton(self)
self.pushButton2.setText("Insert Image")
self.pushButton2.clicked.connect(self.click_file_dialog)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.textEdit)
self.layoutVertical.addWidget(self.pushButton2)
self.layoutVertical.addWidget(self.pushButton)
self.setGeometry(150, 150,640, 480)
self.child_window = CustomWindow(self)
self.child_window.show()
def click_copy_data(self):
self.textEdit.selectAll()
data = self.textEdit.createMimeDataFromSelection()
self.child_window.textEdit.insertFromMimeData(data)
def click_file_dialog(self):
filePath = QtGui.QFileDialog.getOpenFileName(
self,
"Select an image",
".",
"Image Files(*.png *.gif *.jpg *jpeg *.bmp)"
)
if not filePath.isEmpty():
self.insertImage(filePath)
def insertImage(self,filePath):
imageUri = QtCore.QUrl(QtCore.QString("file://{0}".format(filePath)))
image = QtGui.QImage(QtGui.QImageReader(filePath).read())
self.textEdit.document().addResource(
QtGui.QTextDocument.ImageResource,
imageUri,
QtCore.QVariant(image)
)
imageFormat = QtGui.QTextImageFormat()
imageFormat.setWidth(image.width())
imageFormat.setHeight(image.height())
imageFormat.setName(imageUri.toString())
textCursor = self.textEdit.textCursor()
textCursor.movePosition(
QtGui.QTextCursor.End,
QtGui.QTextCursor.MoveAnchor
)
textCursor.insertImage(imageFormat)
# This will hide the cursor
blankCursor = QtGui.QCursor(QtCore.Qt.BlankCursor)
self.textEdit.setCursor(blankCursor)
class CustomWindow(QtGui.QDialog):
def __init__(self,parent=None):
super(CustomWindow,self).__init__(parent)
self.textEdit = QtGui.QTextEdit(self)
self.layoutVertical = QtGui.QVBoxLayout(self)
self.layoutVertical.addWidget(self.textEdit)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.show()
sys.exit(app.exec_())
The way the program works is that you have some text inside the main window and then you insert an image. Then you click "Copy and paste to Child Window" button and it should paste all the contents to the child, including the image - but that doesn't work that way it's supposed to - the text is copied but I get a little file icon where the image should be.
I would appreciate your help on this.
Paul
QTextEdit doesn't decode image MIME types by default, so just subclass it to add support, you'll need to reimplement canInsertFromMimeData and insertFromMimeData, also try QTextBrowser instead. Just add this to your script:
class MyTextBrowser(QtGui.QTextBrowser):
def __init__(self, parent=None):
super(MyTextBrowser, self).__init__(parent)
self.setReadOnly(False)
def canInsertFromMimeData(self, source):
if source.hasImage():
return True
else:
return super(MyTextBrowser, self).canInsertFromMimeData(source)
def insertFromMimeData(self, source):
if source.hasImage():
image = QtCore.QVariant(source.imageData())
document = self.document()
document.addResource(
QtGui.QTextDocument.ImageResource,
QtCore.QUrl("image"),
image
)
cursor = self.textCursor()
cursor.insertImage("image")
super(MyTextBrowser, self).insertFromMimeData(source)
And change self.textEdit = QtGui.QTextEdit(self) into self.textEdit = MyTextBrowser(self) on both widgets.
This is the solution I ended using as suggested by X.Jacobs.
html = parent_textEdit.toHtml()
child_textEdit.setHtml(html)
I was making things more complicated. When I realized that QTextEdit keeps track of where the image is stored as a url inside the html generated by toHtml() then it all made sense.

Any quick Python GUI to display live images from Camera

I am trying to display live images from my 1394 camera.
Currently my code is able to obtain images in a loop from the camera and I was looking for any quick GUI that will update dynamically (as a separate thread). I can do this in PyQt maybe using QThreads but is there any recommendation or faster way of doing this??
Here's my code
#Loop capturing frames from camera
for frame in range(1,500):
print 'frame:',frame
TIME.sleep(1) #capture frame every second
image_binary = pycam.cam.RetrieveBuffer()
#convert to PIL Image
pilimg = PIL.Image.frombuffer("L",(cimg.GetCols(),cimg.GetRows()),image_binary,'raw', "RGBA", 0, 1)
# At this point I want to send my image data to a GUI window and display it
Thank you.
Here's wxPython code that will do it...
import wx
from PIL import Image
SIZE = (640, 480)
def get_image():
# Put your code here to return a PIL image from the camera.
return Image.new('L', SIZE)
def pil_to_wx(image):
width, height = image.size
buffer = image.convert('RGB').tostring()
bitmap = wx.BitmapFromBuffer(width, height, buffer)
return bitmap
class Panel(wx.Panel):
def __init__(self, parent):
super(Panel, self).__init__(parent, -1)
self.SetSize(SIZE)
self.SetBackgroundStyle(wx.BG_STYLE_CUSTOM)
self.Bind(wx.EVT_PAINT, self.on_paint)
self.update()
def update(self):
self.Refresh()
self.Update()
wx.CallLater(15, self.update)
def create_bitmap(self):
image = get_image()
bitmap = pil_to_wx(image)
return bitmap
def on_paint(self, event):
bitmap = self.create_bitmap()
dc = wx.AutoBufferedPaintDC(self)
dc.DrawBitmap(bitmap, 0, 0)
class Frame(wx.Frame):
def __init__(self):
style = wx.DEFAULT_FRAME_STYLE & ~wx.RESIZE_BORDER & ~wx.MAXIMIZE_BOX
super(Frame, self).__init__(None, -1, 'Camera Viewer', style=style)
panel = Panel(self)
self.Fit()
def main():
app = wx.PySimpleApp()
frame = Frame()
frame.Center()
frame.Show()
app.MainLoop()
if __name__ == '__main__':
main()
I thought I'd try PyQt4 imageviewer.py example and it worked for me.
Thanks for all your help guys.
Here's my modified code:
from PyQt4 import QtCore, QtGui
class CameraViewer(QtGui.QMainWindow):
def __init__(self):
super(CameraViewer, self).__init__()
self.imageLabel = QtGui.QLabel()
self.imageLabel.setBackgroundRole(QtGui.QPalette.Base)
self.imageLabel.setScaledContents(True)
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setWidget(self.imageLabel)
self.setCentralWidget(self.scrollArea)
self.setWindowTitle("Image Viewer")
self.resize(640, 480)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.open)
timer.start(33) #30 Hz
def open(self):
#get data and display
pilimg = getMyPILImageDatFromCamera()
image = PILQT.ImageQt.ImageQt(pilimg)
if image.isNull():
QtGui.QMessageBox.information(self, "Image Viewer","Cannot load %s." % fileName)
return
self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(image))
self.imageLabel.adjustSize()
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
CameraViewer = CameraViewer()
CameraViewer.show()
sys.exit(app.exec_())
I recommend using Tkinter since it's already part of python. I've never used PIL but a quick google shows it's easy to use PIL images in Tk widgets (via the pil.ImageTk.PhotoImage() method).
If you already have a Tkinter widget set up to display images (a Label widget works fine) all you need to do is arrange for the image to be updated every second or so. You can do this by using the after command of tkinter.
Here's an example; I don't have PIL so it uses a static image but it illustrates how to use the event loop to fetch images every second:
import Tkinter
class App(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
self.label = Tkinter.Label(text="your image here", compound="top")
self.label.pack(side="top", padx=8, pady=8)
self.iteration=0
self.UpdateImage(1000)
def UpdateImage(self, delay, event=None):
# this is merely so the display changes even though the image doesn't
self.iteration += 1
self.image = self.get_image()
self.label.configure(image=self.image, text="Iteration %s" % self.iteration)
# reschedule to run again in 1 second
self.after(delay, self.UpdateImage, 1000)
def get_image(self):
# this is where you get your image and convert it to
# a Tk PhotoImage. For demonstration purposes I'll
# just return a static image
data = '''
R0lGODlhIAAgALMAAAAAAAAAgHCAkC6LV76+vvXeswD/ANzc3DLNMubm+v/6zS9PT6Ai8P8A////
/////yH5BAEAAAkALAAAAAAgACAAAAS00MlJq7046803AF3ofAYYfh8GIEvpoUZcmtOKAO5rLMva
0rYVKqX5IEq3XDAZo1GGiOhw5rtJc09cVGo7orYwYtYo3d4+DBxJWuSCAQ30+vNTGcxnOIARj3eT
YhJDQ3woDGl7foNiKBV7aYeEkHEignKFkk4ciYaImJqbkZ+PjZUjaJOElKanqJyRrJyZgSKkokOs
NYa2q7mcirC5I5FofsK6hcHHgsSgx4a9yzXK0rrV19gRADs=
'''
image = Tkinter.PhotoImage(data=data)
return image
if __name__ == "__main__":
app=App()
app.mainloop()
Since the good answers are pretty large, I feel like I should post a library I built specifically for this:
from cvpubsubs.webcam_pub import VideoHandlerThread
import numpy as np
image_np = numpy.array(pilImage)
def update_function(frame, cam_id):
frame[...] = image_np[...]
VideoHandlerThread(video_source=image_np, callbacks=update_function).display()
Actually, that's if image_binary is a new numpy array every time. If it's assigned to the same location, then just this should work:
from cvpubsubs.webcam_pub import VideoHandlerThread
VideoHandlerThread(video_source=image_np).display()
I know OpenCV barely counts as a GUI, but this is quick code wise.
Try to take a look at gstreamer. This is the first result google gave me searching for "gstreamer 1394" and this one is the first for "gstreamer pyqt".

Categories