Archive for the ‘Uncategorized’ Category
ELEC Army Strikes
November 11, 2008Resizable Image Control using PyGTK and Cairo
September 21, 2008I’ve been doing a little bit of GUI work in GTK. Something I really missed was a dynamically resizable image control – the gtk.Image object just seems to assume the same dimensions as the picture it’s displaying.
I found a good tutorial about rendering a custom widget using Cairo, with the widget itself inheriting from gtk.DrawingArea. Another useful resource was a C++/gtkmm article about drawing a gtk.gdk.Pixbuf object in a Cairo context. Here’s some screenshots of a demo program in Ubuntu 8.04 and Windows XP.
resizableimage.py
Here’s the key bits of code for the ResizableImage control:
import pygtk
import gtk
from gtk import DrawingArea
class ResizableImage(DrawingArea):
def __init__(self, aspect=True, enlarge=False,
interp=gtk.gdk.INTERP_NEAREST, backcolor=None, max=(1600,1200)):
"""Construct a ResizableImage control.
Parameters:
aspect -- Maintain aspect ratio?
enlarge -- Allow image to be scaled up?
interp -- Method of interpolation to be used.
backcolor -- Tuple (R, G, B) with values ranging from 0 to 1,
or None for transparent.
max -- Max dimensions for internal image (width, height).
"""
DrawingArea.__init__(self)
self.pixbuf = None
...
self.connect('expose_event', self.expose)
The expose event is triggered when the control needs to be repainted. Here we just obtain a Cairo context and call draw().
def expose(self, widget, event):
# Load Cairo drawing context.
self.context = self.window.cairo_create()
# Set a clip region.
self.context.rectangle(
event.area.x, event.area.y,
event.area.width, event.area.height)
self.context.clip()
# Render image.
self.draw(self.context)
return False
Then this is where the image actually gets rendered to the control. The size of the image is determined by resizeToFit(). A resized version of the gtk.Pixbuf member is created using scale_simple(). The gtk.Pixbuf image is displayed using set_source_pixbuf().
def draw(self, context):
# Get dimensions.
rect = self.get_allocation()
x, y = rect.x, rect.y
# Remove parent offset, if any.
parent = self.get_parent()
if parent:
offset = parent.get_allocation()
x -= offset.x
y -= offset.y
# Fill background color.
if self.backcolor:
context.rectangle(x, y, rect.width, rect.height)
context.set_source_rgb(*self.backcolor)
context.fill_preserve()
# Check if there is an image.
if not self.pixbuf:
return
width, height = resizeToFit(
(self.pixbuf.get_width(), self.pixbuf.get_height()),
(rect.width, rect.height),
self.aspect,
self.enlarge)
x = x + (rect.width - width) / 2
y = y + (rect.height - height) / 2
context.set_source_pixbuf(
self.pixbuf.scale_simple(width, height, self.interp), x, y)
context.paint()
def set_from_pixbuf(self, pixbuf):
width, height = pixbuf.get_width(), pixbuf.get_height()
# Limit size of internal pixbuf to increase speed.
if not self.max or (width < self.max[0] and height < self.max[1]):
self.pixbuf = pixbuf
else:
width, height = resizeToFit((width, height), self.max)
self.pixbuf = pixbuf.scale_simple(
width, height,
gtk.gdk.INTERP_BILINEAR)
self.invalidate()
The self.max variable limits the size of the internal gtk.Pixbuf to prevent slow render times for larger images. The default maximum is 1600×1200. It can be disabled by setting max=None in the constructor call.
def set_from_file(self, filename):
self.set_from_pixbuf(gtk.gdk.pixbuf_new_from_file(filename))
def invalidate(self):
self.queue_draw()
...
A couple of functions external to the class:
def resizeToFit(image, frame, aspect=True, enlarge=False):
"""Resizes a rectangle to fit within another.
Parameters:
image -- A tuple of the original dimensions (width, height).
frame -- A tuple of the target dimensions (width, height).
aspect -- Maintain aspect ratio?
enlarge -- Allow image to be scaled up?
"""
if aspect:
return scaleToFit(image, frame, enlarge)
else:
return stretchToFit(image, frame, enlarge)
def scaleToFit(image, frame, enlarge=False):
image_width, image_height = image
frame_width, frame_height = frame
image_aspect = float(image_width) / image_height
frame_aspect = float(frame_width) / frame_height
# Determine maximum width/height (prevent up-scaling).
if not enlarge:
max_width = min(frame_width, image_width)
max_height = min(frame_height, image_height)
else:
max_width = frame_width
max_height = frame_height
# Frame is wider than image.
if frame_aspect > image_aspect:
height = max_height
width = int(height * image_aspect)
# Frame is taller than image.
else:
width = max_width
height = int(width / image_aspect)
return (width, height)
def stretchToFit(image, frame, enlarge=False):
image_width, image_height = image
frame_width, frame_height = frame
# Stop image from being blown up.
if not enlarge:
width = min(frame_width, image_width)
height = min(frame_height, image_height)
else:
width = frame_width
height = frame_height
return (width, height)
demogtk.py
Here’s most of the code for the demo program:
import pygtk
import gtk
import gtk.glade
import os
from resizableimage import ResizableImage
class DemoGtk:
def __init__(self):
# Initiate GUI.
self.gladefile = "demo.glade"
self.wTree = gtk.glade.XML(self.gladefile)
# Find widgets that we use.
self.frame = self.wTree.get_widget('pictureframe')
self.txtFile = self.wTree.get_widget('txtFile')
self.chkAspect = self.wTree.get_widget('chkAspect')
self.chkEnlarge = self.wTree.get_widget('chkEnlarge')
# Connect signals.
signals = {
'on_window_destroy': self.destroy,
'on_txtFile_changed': self.openFile,
'on_chkAspect_clicked': self.changeAspect,
'on_chkEnlarge_clicked': self.changeEnlarge
}
self.wTree.signal_autoconnect(signals)
# Do some runtime GUI.
self.image = ResizableImage(
self.chkAspect.get_active(),
self.chkEnlarge.get_active(),
gtk.gdk.INTERP_BILINEAR)
self.image.show()
self.frame.add(self.image)
Options for interpolation type are gtk.gdk.INTERP_NEAREST, gtk.gdk.INTERP_TILES, gtk.gdk.INTERP_BILINEAR, gtk.gdk.INTERP_HYPER. I used “bilinear” here to get decent quality pictures. The “nearest” option is alright if you need more performance.
def destroy(self, widget, data=None):
gtk.main_quit()
def openFile(self, widget, data=None):
filename = widget.get_text()
if not os.path.isfile(filename):
return
self.image.set_from_file(filename)
def changeAspect(self, widget, data=None):
self.image.set_aspect(widget.get_active())
def changeEnlarge(self, widget, data=None):
self.image.set_enlarge(widget.get_active())
def main():
gui = DemoGtk()
gtk.main()
if __name__ == '__main__':
main()
Doesn’t look like I’m able to upload the source to WordPress. Just let me know in the comments if you’re after a bit more detail!
C++/Python Image Pixel Manipulation Benchmarks
September 13, 2008I’ve been playing around with Python a bit lately for a new project. I got up to a stage where I needed image manipulation on a per-pixel level. Here’s a few different approaches I tried out, with performance results and code snippets.
The benchmark test was a transformation from rectangular to polar co-ordinates. Input image was 3328×1664 JPEG (~480kB), output was 3327×3327 JPEG (all methods produced ~790kB).
Python
I implemented it purely in Python first (using the Python Imaging Library PIL.Image module), but it took ages for higher resolution images.
from PIL import Image
def RectToPolar(oldImg):
w, h = oldImg.size
oldPixels = oldImg.load()
...
diameter = 2 * h - 1
newImg = Image.new("RGB", (diameter, diameter), bg)
newPixels = newImg.load()
...
for x in range(diameter):
for y in range(diameter):
...
newPixels[x, y] = oldPixels[s, (h-1)-r]
return newImg
original = Image.open("../Home.jpg")
warped = RectToPolar(original)
warped.save("warped.jpg")
Python and C++ (Boost)
I decided to try using Boost’s Python library, so I could write the actual processing code in C++ for improved performance. I originally tried loading the image in Python and passing the pixel data to and from C++ as a string. I expected to cop a performance hit doing this, but the Python libraries for loading an image (with pixel access) are really nice and simple, so I gave it a shot.
class ImageMapper
{
public:
ImageMapper(std::string pixelStr, int width, int height);
...
std::string rectToPolar()
{
std::string result;
for (int y = max; y >= min; y--)
{
for (int x = min; x <= max; x++)
{
...
result += pixel(u, v);
}
}
return result;
}
};
BOOST_PYTHON_MODULE(imagemap)
{
class_<ImageMapper>("ImageMapper", init<std::string, int, int>())
.def("pixel", &ImageMapper::pixel)
.def("rectToPolar", &ImageMapper::rectToPolar)
;
}
from PIL import Image
import imagemap
img1 = Image.open("../Home.jpg")
w, h = img1.size
mapper = imagemap.ImageMapper(img1.tostring(), w, h)
data = mapper.rectToPolar()
img2 = Image.fromstring("RGB", (3327, 3327), data)
img2.save("warped.jpg")
To compile against the Boost Python libraries:
g++ -Wall -O3 imagemap.cpp -shared -o imagemap.so -I/usr/include -I/usr/include/python2.5 -L/usr/lib -lpython2.5 -lboost_python
C++ and GTK/gtkmm
Finally, I tried loading, processing and saving the image purely in C++, using GTK and gtkmm (GTK wrapped up in C++).
#include <gtk/gtk.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
...
class ImageMapper
{
private:
GdkPixbuf *original_;
GdkPixbuf *warped_;
static guchar *get_pixel(GdkPixbuf *pixbuf, int x, int y)
{
return gdk_pixbuf_get_pixels(pixbuf) +
y * gdk_pixbuf_get_rowstride(pixbuf) +
x * gdk_pixbuf_get_n_channels(pixbuf);
}
public:
ImageMapper(const char *filename)
{
GError *error = NULL;
original_ = gdk_pixbuf_new_from_file(filename, &error);
if (original_ == NULL)
throw runtime_error("Could not load image");
}
...
void rectToPolar()
{
warped_ = gdk_pixbuf_new(GDK_COLORSPACE_RGB, false, 8, diameter, diameter);
if (warped_ == NULL)
throw runtime_error("Unable to create output image");
for (int j = 0; j < diameter; j++)
{
for (int i = 0; i < diameter; i++)
{
memcpy(
get_pixel(warped_, i, j),
get_pixel(original_, u, v),
channels * sizeof(guchar));
}
}
}
void transform(const char *filename, const char *type)
{
rectToPolar();
GError *error = NULL;
gboolean result = gdk_pixbuf_save(warped_, filename, type,
&error, "quality", "75", NULL);
if (!result)
throw runtime_error("Could not save image");
}
};
int main(int argc, char *argv[])
{
gtk_init (&argc, &argv);
ImageMapper mapper = ImageMapper("../Home.jpg");
mapper.transform("warped.jpg", "jpeg");
return 0;
}
To compile in Linux, it was just a matter of:
g++ -Wall -O3 warpgtk.cpp -o warpgtk `pkg-config --cflags --libs gtk+-2.0`
There was just a few differences with gtkmm, namely…
Glib::RefPtr<Gdk::Pixbuf> original_; Glib::RefPtr<Gdk::Pixbuf> warped_;
static guchar *get_pixel(Glib::RefPtr<Gdk::Pixbuf> &pixbuf, int x, int y)
{
return pixbuf->get_pixels() +
y * pixbuf->get_rowstride() +
x * pixbuf->get_n_channels();
}
original_ = Gdk::Pixbuf::create_from_file(filename);
warped_ = Gdk::Pixbuf::create(Gdk::COLORSPACE_RGB, false, 8, diameter, diameter);
And compiling: (might need to grab a few repository packages)
g++ -Wall -O3 warpgtkmm.cpp -o warpgtkmm `pkg-config gtkmm-2.4 --cflags --libs`
The Results
- 3.04s; C++ and GTK
- 3.53s; C++ and gtkmm
- 8.89s; C++ with Python data via Boost
- 32.9s; Python
So I’ll probably go with either GTK or gtkmm. The slight speed penalty is probably worth it for the C++iness.
Astronomy Symbols in TeX
September 9, 2008I’ve been hoping to do up the MECH2210 orbital mechanics project in TeX, but first I had to work out how to do those special planet symbols. I tracked them down in a LaTeX symbol list (pdf) (page 68), in a package called mathabx. There’s also astronomy symbols in the wasysym and marvosym packages, which are included in texlive-fonts-recommended from the Ubuntu repositories, but their Earth and/or Sun symbols weren’t consistent with what we used in lectures.
The mathabx font isn’t included in any software repositories that I could see, so I downloaded it from the ctan.org catalogue and unzipped to my desktop. Installing it was a tad tricky, I worked from the README and a post in the Ubuntu forums.
$ cd /home/jack/Desktop/mathabx/ $ TEXMF=/usr/share/texmf-texlive $ sudo mkdir $TEXMF/fonts/source/public/mathabx/ $ sudo cp source/*.mf $TEXMF/fonts/source/public/mathabx $ sudo cp texinputs/mathabx.* $TEXMF/tex/generic/misc/ $ cd $TEXMF $ sudo mktexlsr
Now in TeX…
\usepackage{mathabx}
...
$\Sun$ Sun
$\Earth$ Earth
$\Moon$ Moon
$\Venus$ Venus
$\Mars$ Mars
And tadah!
Talk by Microsoft at UQ this Thursday
September 2, 2008Carrie Read from Microsoft Recruiting is doing a presentation this Thursday (4 September), 1-2pm in GP South (building 78), room 622. Definitely worth checking out if you’re software-inclined! There will be pizza
Open to anyone, except maybe unco Hungarian egg-throwing nerds.
Team Project I: Killer Satellite Robot
August 13, 2008The second year mechatronics team project at the University of Queensland is to build a prototype for a defence satellite. At the end of the semester, the robot must demonstrate the ability to locate targets (infrared emitting bullseyes) and shoot them with a laser, the robot itself hanging from the test frame. The targets can be about 1-3m away.
The innermost zone on the target (about 2cm in diameter) is worth 10 points, the next is 5 points and the outer area is 1 point. The satellite has to hold the laser on the target for a minimum of 2 seconds. The least valuable zone it shoots in this time is counted. To get a 7 for the product, you need to score 200 points in 5 minutes (1 point is a pass).
No external propulsion devices are permitted, so all teams went with an inertial flywheel drive. They supplied us with a nice little Faulhaber gearmotor. We used an L298 motor controller with PWM operating at around 15kHz (nice and audible
). The flywheels designs were all submitted to the uni workshop and lathed out of stainless steel.
The course is also an introduction to simple control systems, with most teams employing some form of PID (proportional integral derivative) control. We programmed an Atmel AVR ATmega8 microcontroller in C. It was hooked up to the computer via RS232 (and a long wire) for live debugging. Shane put together a great app in C# to graph live sensor data and PID data. A pair of phototransistors was used to locate the targets, the difference between sensors giving the error signal.
The PCB’s were designed in Altium and the uni organised for them to be done up externally. Double sided components to save money – one free percent per $10 under budget. Final board size was 2×3″ (50×75mm) The total budget was $100, not including the motor or any mechanical components we designed.
Here’s the other three members in my team! Shane (left), Tom (thumbs up) and Andrew.














