Basic Pixel Perfect Point Collision in kivy

1,348 views
Skip to first unread message

embryo

unread,
Jan 4, 2015, 8:54:50 PM1/4/15
to kivy-...@googlegroups.com
A friend of mine, after 2 days of search finally discovered a solution to the Pixel Perfect Collision detection.
I'm posting it here with his permission.

You can use the attached "wolf.png" but it should work with any image with an alpha channel.

"""
Basic Pixel Perfect Point Collision in kivy

Overriding the Image's [collide_point] method to use the underlying [core.image.Image]'s
[read_pixel] method and access the alpha information of the colliding pixel.
The [keep_data: True] statement is essential, before assigning (loading) a texture.
A deviation from the [size_hint: None, None] and [size: self.texture_size], along with
the use of [allow_stretch] and other size modifications, could probably be allowed,
by using the [norm_image_size] property, but the scaling and rotation of a ScatterLayout
(as in the current example) works fine as is.
Also note that the image's local [y] coordinate is inverse as far as [read_pixel] is
concerned.
(Could it be the reason why this approach doesn't seem to work with atlases?)

Thanks to niavlys for his 3/14/14 post:
(https://groups.google.com/forum/#!topic/kivy-users/BnjrZT0NkhU)
and to tshirtman for his:
(7/24/14 https://groups.google.com/forum/#!topic/kivy-dev/dpT7yqs_Mq0)

unjuan 2015
"""


from kivy.app import App
from kivy.uix.scatterlayout import ScatterLayout
from kivy.uix.image import Image
from kivy.lang import Builder

Builder.load_string('''
<RootLayout>:
    CustomImage:
        keep_data: True
        size_hint: None, None
        size: self.texture_size
        pos_hint: {'
center_x': .5, 'center_y': .5}
        source: '
wolf.png'
        opacity: .3
'''
)


class CustomImage(Image):
   
def __init__(self, **kwargs):
       
# ################################### un-comment for a python-only version
       
# kwargs.setdefault('keep_data', True)
       
# kwargs.setdefault('size_hint', (None,None))
       
super(CustomImage, self).__init__(**kwargs)
       
# self.pos_hint = {'center_x': .5, 'center_y': .5}
       
# self.opacity = .3
       
#######################################################################

   
def collide_point(self, x, y):
       
# Do not want to upset the read_pixel method, in case of a bound error
       
try:
            color
= self._coreimage.read_pixel(x - self.x, self.height - (y - self.y))
       
except:
            color
= 0, 0, 0, 0
       
if color[-1] > 0:
           
return True
       
return False

   
def on_touch_down(self, touch):
       
if self.collide_point(*touch.pos):
           
self.opacity = 1
       
else:
           
self.opacity = .3


class RootLayout(ScatterLayout):
   
def __init__(self, **kwargs):
       
super(RootLayout, self).__init__(**kwargs)
       
# ################################### un-comment for python-only version
       
# cimage = CustomImage()
       
# cimage.source = 'wolf.png'
       
# cimage.size = cimage.texture.size
       
# self.add_widget(cimage)
       
###########################################################################


class CollTestApp(App):
   
def build(self):
       
return RootLayout()


if __name__ == '__main__':
   
CollTestApp().run()

wolf.png

me2 beats

unread,
Aug 8, 2018, 7:23:18 PM8/8/18
to Kivy users support
Wow! It's really cool, I was just looking for something like that.
Is this one of the best solutions for this task (to find out if the pixel is transparent under the mouse for a widget) or are there more effective ways?

And is it possible to make this work with SVG?
Message has been deleted
Message has been deleted

me2 beats

unread,
Aug 13, 2018, 3:40:18 AM8/13/18
to Kivy users support
[I deleted the last message since it now does not do any good]

ok this one is much better
it uses fbo.get_pixel_color()


now it works when scaling

but I caught an error when "do_rotation" option is enabled
(I'll create a new topic about this)

it's still possible to do something simpler in this code

but basically now it works without tweaks such as saving to disk.



import sys
from glob import glob
from os.path import join, dirname
from kivy.uix.scatter import Scatter
from kivy.app import App
from kivy.graphics.svg import Svg
from kivy.graphics import (
Canvas, Translate, Fbo, ClearColor, ClearBuffers, Scale)
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.uix.floatlayout import  FloatLayout

Builder.load_string("""
<SvgWidget>:
    id:s
    do_rotation: False

<FloatLayout>:
    id:f
    canvas.before:
        Color:
            rgb: (1, 1, 1)
        Rectangle:
            pos: self.pos
            size: self.size
"""
)


class SvgWidget(Scatter):

   
def __init__(self, filename, **kwargs):
       
super(SvgWidget, self).__init__(**kwargs)
       
with self.canvas:
            svg
= Svg(filename)
       
self.size = svg.width, svg.height




   
def get_widget_pixel_color(self,x_pos,y_pos):

       
self.size = (Window.size[0]-self.x, Window.size[1]-self.y)


       
if self.parent is not None:
            canvas_parent_index
= self.parent.canvas.indexof(self.canvas)
           
if canvas_parent_index > -1:
               
self.parent.canvas.remove(self.canvas)

        fbo
= Fbo(size=self.size, with_stencilbuffer=True)

       
with fbo:
           
ClearColor(0, 0, 0, 0)
           
ClearBuffers()
           
Scale(1, -1, 1)
           
Translate(-self.x, -self.y - self.height, 0)

        fbo
.add(self.canvas)
        fbo
.draw()

        x_pos
= int(x_pos)
        y_pos
= int(y_pos)
 


        color
= fbo.get_pixel_color(x_pos-self.x, self.height-y_pos+self.y)



        fbo
.remove(self.canvas)

       
if self.parent is not None and canvas_parent_index > -1:
           
self.parent.canvas.insert(canvas_parent_index, self.canvas)


       
#======fixed but WTH?

       
if x_pos-self.x<0:
           
#print color
            color
= [0,0,0,0]
       
#====================


       
return color




   
def my_collide_point(self, x_pos, y_pos):

        color
= self.get_widget_pixel_color(x_pos,y_pos)
        alpha
= color[-1]
       
print color
       
if alpha > 0:

           
return True
       
return False



   
def on_touch_down(self,touch):


       
print self.my_collide_point(*touch.pos)
       
return super(SvgWidget, self).on_touch_down(touch)


class SvgApp(App):

   
def build(self):
       
self.root = FloatLayout()

        filenames
= sys.argv[1:]
       
if not filenames:
            filenames
= glob(join(dirname(__file__), '*.svg'))

       
for filename in filenames:
            svg
= SvgWidget(filename, size_hint=(None, None))
#            svg = SvgWidget(filename)
           
self.root.add_widget(svg)
            svg
.scale = 5.
            svg
.center = Window.center


if __name__ == '__main__':
   
SvgApp().run()



Reply all
Reply to author
Forward
0 new messages