[Image-SIG] "Point in polygon" test?

Donn donn.ingle at gmail.com
Thu Jul 3 16:21:19 CEST 2008


> Hi!  Does PIL have already implemented a method to return true/false
> according as a provided point is inside a provided polygon?  Thanks!
I went through this a while back. The code pasted (sorry) is from a GTK and 
pyCairo point of view, but the algorithm you need is in the function hitTest.

HTH
\d


#! /usr/bin/env python
import pygtk
pygtk.require('2.0')
import gtk, gobject, cairo
from gtk import gdk

# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):

    # Draw in response to an expose-event
    __gsignals__ = { "expose-event": "override" }

    def __init__(self):
        super(Screen,self).__init__()
        # gtk.Widget signals
        self.connect("button_press_event", self.button_press)
        self.connect("button_release_event", self.button_release)
        self.connect("motion_notify_event", self.motion_notify)
        # More GTK voodoo : unmask events
        self.add_events(gdk.BUTTON_PRESS_MASK |
                        gdk.BUTTON_RELEASE_MASK |
                        gdk.POINTER_MOTION_MASK)
        
    # Handle the expose-event by drawing
    def do_expose_event(self, event):

        # Create the cairo context
        cr = self.window.cairo_create()
        self.hitpath = None #Is set later
        
        # Restrict Cairo to the exposed area; avoid extra work
        cr.rectangle(event.area.x, event.area.y,
                event.area.width, event.area.height)
        cr.clip()

        self.draw(cr, *self.window.get_size())
        
    def makeHitPath(self,cairopath):
        ## Make a simpler list of tuples
        
        ##        Internally, a cairo path looks like this:
        ##        (0, (10.0, 10.0))
        ##        (1, (60.0, 10.0))
        ##        (1, (60.0, 60.0))
        ##        (1, (35.0, 60.0))
        ##        (1, (35.0, 35.0))
        ##        (1, (10.0, 35.0))
        ##        (1, (10.0, 60.0))
        ##        (1, (-40.0, 60.0))
        ##        (3, ()) #want to ignore this one
        ##        (0, (10.0, 10.0))        
        
        self.hitpath = []
        for sub in cairopath:
            if sub[1]: #kick out the close path () empty tuple
                self.hitpath.append(sub[1]) #list of tuples
        
    def draw(self, cr, width, height):
        # Fill the background with gray
        cr.set_source_rgb(0.5, 0.5, 0.5)
        cr.rectangle(0, 0, width, height)
        cr.fill()
        
    def hitTest(self,*p):
        ## Code lifted from 
http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
        ## converted to Python. I won't pretend I grok it at all, just glad it 
works!
        ## Not sure how well it works yet, it might have edge flaws.
        px = p[0]
        py = p[1]
        counter = i = xinters = 0
        p1 = p2 = ()
            
        p1 = self.hitpath[0]
        N = len(self.hitpath)
        
        # Mathemagic loop-de-loop
        for i in range(0,N):
            p2 = self.hitpath[i % N]
            if py > min( p1[1] , p2[1] ):
                if py <= max( p1[1], p2[1] ):
                    if px <= max( p1[0], p2[0] ):
                        if p1[1] != p2[1]:
                            xinters = ( py - p1[1] ) * ( p2[0] - p1[0] ) / ( 
p2[1] - p1[1] ) + p1[0]
                            if p1[0] == p2[0] or px <= xinters: counter += 1
            p1 = p2

        if counter % 2 == 0:
            return "outside"
        return "inside"
        
    def button_press(self,widget,event):
        pass
    def button_release(self,widget,event):
        pass
    def motion_notify(self,widget,event):
        pass

# GTK mumbo-jumbo to show the widget in a window and quit when it's closed
def run(Widget):
    window = gtk.Window()
    window.connect("delete-event", gtk.main_quit)
    widget = Widget()
    widget.show()
    window.add(widget)
    window.present()
    gtk.main()

class Shapes(Screen):
    #Override the press event
    def button_press(self,widget,event):
        print self.hitTest(event.x, event.y)
        
    def draw(self, cr, width, height):
        x = y = 10
        sx = sy = 50
        cr.move_to(x,y)
        cr.line_to(x+sx,y)
        cr.line_to(x+sx,y+sy)
        cr.line_to(x+(sx/2),y+sy)
        cr.line_to(x+(sx/2),y+(sy/2))
        cr.line_to(x,y+(sy/2))
        cr.line_to(x,y+sy)
        cr.line_to(x-sx,y+sy)
        cr.close_path()
        cr.set_source_rgb(1,0,0)
        
        self.makeHitPath(cr.copy_path_flat()) #record the path to use as a hit 
area.
        
        cr.fill() #consumes the path, so get it before the fill
        

run(Shapes)


More information about the Image-SIG mailing list