Rcjp's Weblog

October 17, 2007

Show Nearest X11 System Colours

Filed under: python, utils — rcjp @ 1:16 pm

ColourInfo
Just a little utility to show the nearest system rgb colours (taken from rgb.txt) on an X11 system compared to the one under the cursor.

I couldn’t work out how to listen to mouse messages when I don’t own the window under the mouse pointer (on X Windows anyway) – so I cheated and this code takes a snapshot of the screen and displays it in the background. Obviously you can’t move/click on any windows when this thing is running! quit with ‘q’ or ‘ESC’ or just close the window.

#!/usr/bin/env python
#
# Takes a screenshot image of the root window and display a table of 
# nearest system colours compared to that under the mouse pointer
# can specify an argument 1..29 to show more colours (default 6)
# The window title shows the exact rgb values of pointer in hex
#
import gtk, re
from sys import argv

RGBCOLOURS = '/etc/X11/rgb.txt'   # rgb colour data

class ScreenColour(object):

    syscolours = {}  # hold system rgb.txt relating colours to names

    def __init__(self, rgbfile=RGBCOLOURS):
        # 1 pixel buffer for pixel under mouse pointer
        self.pix = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB,False, 8, 1, 1)
        for line in file(rgbfile).readlines():
            if not line.startswith('!'):
                rgbname = re.compile(r'\s*(\d+)\s*(\d+)\s*(\d+)\s*(.+)')
                (r,g,b,name) = rgbname.match(line).groups()
                self.syscolours[name.rstrip()] = (int(r),int(g),int(b))

    def cmp_screencolour(self,col,basecol):
        """Numerical difference between colours col(name) and basecol(r,g,b)"""
        return sum(abs(a-b) for a, b in zip(basecol, ScreenColour.syscolours[col]))

    def pixelinfo(self):
        """Returns the (r,g,b) value of colour under mouse pointer"""
        (_, x, y, _) = gtk.gdk.display_get_default().get_pointer()
        self.pix.get_from_drawable(gtk.gdk.screen_get_default().get_root_window(),
                                   gtk.gdk.colormap_get_system(),
                                   x,y, 0,0, 1,1)
        col = self.pix.get_pixels_array()
        return (int(col[0,0,0]), int(col[0,0,1]), int(col[0,0,2]))

    def nearest_colours(self, n, basecol):
        """Return the nearest n system colours compared to basecol"""
        nearest = self.syscolours.keys()
        nearest.sort(key=lambda (c): self.cmp_screencolour(c, basecol))
        return nearest[:n]


class ColourInfoWindow(ScreenColour):

    """Draw a table of colours nearest to that under mouse pointer"""
    label = []
    eb = []
    oldrgb = (-1,-1,-1)

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self, tablesize):
        self.tablesize = tablesize
        self.image = gtk.Window()
        self.image.set_decorated(False)
        self.image.add_events(gtk.gdk.POINTER_MOTION_MASK)
        self.image.connect("motion_notify_event", self.event_handler)
        #
        # set background from a screen shot of the root window
        #
        screen = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8,
                                gtk.gdk.screen_width(), gtk.gdk.screen_height())
        screen.get_from_drawable(gtk.gdk.get_default_root_window(),
                                 gtk.gdk.colormap_get_system(), 0, 0, 0, 0,
                                 gtk.gdk.screen_width(), gtk.gdk.screen_height())
        pixmap, mask = screen.render_pixmap_and_mask()
        self.image.set_app_paintable(True)
        self.image.realize()
        self.image.window.set_back_pixmap(pixmap, False)
        del pixmap
        self.image.fullscreen()
        self.image.show()

        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_size_request(200, 40*self.tablesize)
        self.window.set_resizable(False)
        self.window.set_transient_for(self.image)  # stay above screen image
        self.window.set_title("Colour Info")
        self.window.connect("delete_event", self.delete_event)
        self.window.add_events(gtk.gdk.KEY_PRESS_MASK)
        self.window.connect("key-press-event", self.event_keys)

        self.Colour = ScreenColour()
        vbox = gtk.VBox(True,2)
        for i in range(self.tablesize):
            self.label.append(gtk.Label("Unknown"))
            self.eb.append(gtk.EventBox())
            self.eb[i].add(self.label[i])
            vbox.add(self.eb[i])
        self.window.add(vbox)
        self.window.show_all()
        self.update_colours()

    def update_colours(self):
        rgb = self.Colour.pixelinfo()
        if self.oldrgb != rgb:
            self.oldrgb = rgb
            nearest = self.Colour.nearest_colours(self.tablesize, rgb)
            self.window.set_title("%02X %02X %02X" % rgb)
            for i in range(self.tablesize):
                try:
                    self.eb[i].modify_bg(gtk.STATE_NORMAL,
                                         gtk.gdk.color_parse(nearest[i]))
                    #
                    # if brighter than 128,128,128 switch to 
                    # a black background so text is visable
                    #
                    if (sum(self.syscolours[nearest[i]]) > 384):
                        w = '<span foreground="black">%s</span>' % nearest[i]
                    else:
                        w = '<span foreground="white">%s</span>' % nearest[i]
                    self.label[i].set_markup(w)
                except ValueError:
                    print 'unknown colour... ', nearest[i]

    def event_handler(self, widget, event=None):
        if event and event.type == gtk.gdk.MOTION_NOTIFY:
            self.update_colours()

    def event_keys(self, widget, event=None):
        if event:
            if event.keyval == gtk.gdk.keyval_from_name("Escape") \
               or event.keyval == gtk.gdk.keyval_from_name("q"):
                  self.window.destroy()
                  self.image.destroy()
                  gtk.main_quit()


if __name__ == "__main__":
    if len(argv) == 2 and (0<int(argv[1])<30):
        ColourInfoWindow(int(argv[1]))
    else:
        ColourInfoWindow(6)
    gtk.main()

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: