8
$\begingroup$

I'm trying to figure out how to get the color of the screen pixel under the mouse with python like in this video:

Python-scripted color picker

I tried importing bgl and adding bgl.glReadPixels in to the SimpleMouseOperator sample to print out both cursor coordinates and color value but even after reading a ton of docs I'm having trouble figuring out how to use the bgl operators.

    x = bpy.props.IntProperty()
    y = bpy.props.IntProperty()

    def execute(self, context):
        # rather than printing, use the report function,
        # this way the message appears in the header,
        buff = bgl.Buffer(bgl.GL_FLOAT, 4)
        bgl.glReadPixels(self.x, self.y, 1, 1, bgl.GL_RGB, bgl.GL_FLOAT, buff)
        self.report({'INFO'}, "Mouse coords are %d %d." % (self.x, self.y))
        print (buff[0])

Can anyone help point me in the right direction? Thanks.

$\endgroup$
1
  • $\begingroup$ you should be use something like buff = bgl.Buffer(bgl.GL_FLOAT, [4,1]) bgl.glReadPixels(x,y,1,1,bgl.GL_RGB,bgl.GL_FLOAT,buff); and the colors are in buff[0][0], buff[1][0], buff[2][0], buff[2][0], buff[3][0], ..l please try that and give some feed back ... $\endgroup$
    – yhoyo
    Commented Jan 9, 2017 at 18:45

2 Answers 2

7
$\begingroup$

here an example about how you can use the glReadPixels + mouse events;

I did some changes to code from https://blenderartists.org/forum/showthread.php?362427-3D-coordinates-of-the-mouse

import bpy
import bgl
from bpy_extras import view3d_utils
import mathutils

"""Functions for the mouse_coords_to_3D_view"""
def get_viewport():
    view = bgl.Buffer(bgl.GL_INT, 4)
    bgl.glGetIntegerv(bgl.GL_VIEWPORT, view)
    return view


def get_modelview_matrix():
    model_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_MODELVIEW_MATRIX, model_matrix)
    return model_matrix


def get_projection_matrix():
    proj_matrix = bgl.Buffer(bgl.GL_DOUBLE, [4, 4])
    bgl.glGetDoublev(bgl.GL_PROJECTION_MATRIX, proj_matrix)
    return proj_matrix


"""Function mouse_coords_to_3D_view"""
def mouse_coords_to_3D_view(x, y):    
    depth = bgl.Buffer(bgl.GL_FLOAT, [0.0])
    bgl.glReadPixels(x, y, 1, 1, bgl.GL_DEPTH_COMPONENT, bgl.GL_FLOAT, depth)
    #if (depth[0] != 1.0):
    world_x = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_y = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    world_z = bgl.Buffer(bgl.GL_DOUBLE, 1, [0.0])
    view1 = get_viewport()
    model = get_modelview_matrix()
    proj = get_projection_matrix ()   
    bgl.gluUnProject(x, y, depth[0], 
                     model, proj,
                     view1,
                     world_x, world_y, world_z)
    return world_x[0], world_y[0], world_z[0]


"""drawing point OpenGL in mouse_coords_to_3D_view"""
def draw_callback_px(self, context):
    # mouse coordinates relative to 3d view
    x, y= self.mouse_path

    # mouse coordinates relative to Blender interface
    view = get_viewport()
    gmx = view[0] + x 
    gmy = view[1] + y 

    #c= bgl.Buffer(bgl.GL_UNSIGNED_BYTE, [3,1])
    #bgl.glReadPixels(x, y, 1, 1, bgl.GL_RGB, bgl.GL_FLOAT, c)
    #c= bgl.Buffer(bgl.GL_SHORT, [3,1])
    c = bgl.Buffer(bgl.GL_FLOAT, [3,1])
    bgl.glReadPixels(gmx, gmy,1,1,bgl.GL_RGB,bgl.GL_FLOAT,c);


    draw_square_follow_cursor(c, gmx, gmy)
    draw_corner_square(c)    
    draw_square(c)

def draw_square_follow_cursor(c, gmx, gmy):

    # draw 3d mouse OpenGL point in the 3D View
    dist= 50 #distancia del punto al cursor
    mouse3d = mouse_coords_to_3D_view(gmx+dist, gmy+dist)        
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(c[0][0],c[1][0],c[2][0])
    bgl.glPointSize(30)    
    bgl.glBegin(bgl.GL_POINTS)
    #bgl.glVertex3f(*(mouse3d))
    bgl.glVertex3f(mouse3d[0], mouse3d[1], mouse3d[2])
    #bgl.glVertex2f(gmx,gmy)
    bgl.glEnd()



    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

def draw_corner_square(c):
    for area in bpy.context.screen.areas:
        if area.type=='VIEW_3D':
            X= area.x
            Y= area.y
    dist= 100 #distancia del punto al cursor
    mouse3d = mouse_coords_to_3D_view(X+dist,Y+dist)        
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(c[0][0],c[1][0],c[2][0])
    bgl.glPointSize(30)    
    bgl.glBegin(bgl.GL_POINTS)
    bgl.glVertex3f(mouse3d[0], mouse3d[1], mouse3d[2])    
    bgl.glEnd()



    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


def draw_square(c):
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor3f(c[0][0],c[1][0],c[2][0])
    bgl.glBegin(bgl.GL_POLYGON)
    bgl.glVertex2f(-0.5, -0.5) 
    bgl.glVertex2f(-0.5, 0.5)  
    bgl.glVertex2f(0.5, 0.5) 
    bgl.glVertex2f(0.5, -0.5)
    bgl.glEnd( )   

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)

class ModalDrawOperator(bpy.types.Operator):
    """Draw a point with the mouse"""
    bl_idname = "view3d.modal_operator"
    bl_label = "Simple Modal View3D Operator"   

    def modal(self, context, event):
        context.area.tag_redraw()


        if event.type == 'MOUSEMOVE':
            self.mouse_path = (event.mouse_region_x, event.mouse_region_y)            

        elif event.type == 'LEFTMOUSE':
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'FINISHED'}


        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, 'WINDOW')
            context.area.header_text_set()
            return {'CANCELLED'}


        return {'PASS_THROUGH'}


    def invoke(self, context, event):
        # the arguments we pass the the callback
        args = (self, context)
        # Add the region OpenGL drawing callback
        # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
        self._handle = bpy.types.SpaceView3D.draw_handler_add(draw_callback_px, args, 'WINDOW', 'POST_VIEW')
        self.mouse_path = []
        #self.wx = bpy.context.window.width
        #self.wy = bpy.context.window.height
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalDrawOperator)


def unregister():
    bpy.utils.unregister_class(ModalDrawOperator)


if __name__ == "__main__":
    register()

some example

colorpicker

$\endgroup$
2
  • $\begingroup$ This is a lot of code so I'll have some studying to do but this is exactly what I was trying to do. Thank you! Though for some reason when running this script I'm only getting the color sample that follows the mouse and not the other two that appear in your example, plus it's a circle instead of a square? Weird, but still a great example. Very helpful, thanks. $\endgroup$
    – andyV
    Commented Jan 10, 2017 at 6:44
  • $\begingroup$ On closer look I am actually getting the color sample square at the center of the grid. Just not the square in the lower left corner of the 3d scene. Anyway, thanks again. $\endgroup$
    – andyV
    Commented Jan 10, 2017 at 7:11
4
+50
$\begingroup$

To migrate to the gpu module, here's a simple operator created from Color Picker Pro and the built-in Blender operator template script:

import bpy
import gpu


class SimpleOperator(bpy.types.Operator):
    bl_idname = 'image.screen_picker'
    bl_label = 'Simple Color Picker'
    bl_description = 'Extract color information from multiple adjacent pixels'
    bl_options = {'REGISTER'}

    def modal(self, context, event):
        context.area.tag_redraw()
        wm = context.window_manager

        if event.type == 'LEFTMOUSE':
            fb = gpu.state.active_framebuffer_get()
            screen_buffer = fb.read_color(event.mouse_x, event.mouse_y, 1, 1, 3, 0, 'FLOAT')

            rgb_as_list = screen_buffer.to_list()[0]

            print(rgb_as_list)

        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            context.window.cursor_modal_restore()
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        wm = context.window_manager
        context.window_manager.modal_handler_add(self)
        context.window.cursor_modal_set('EYEDROPPER')
        return {'RUNNING_MODAL'}


def menu_func(self, context):
    self.layout.operator(SimpleOperator.bl_idname, text=SimpleOperator.bl_label)


# Register and add to the "object" menu (required to also use F3 search "Simple Object Operator" for quick access).
def register():
    bpy.utils.register_class(SimpleOperator)
    bpy.types.VIEW3D_MT_object.append(menu_func)


def unregister():
    bpy.utils.unregister_class(SimpleOperator)
    bpy.types.VIEW3D_MT_object.remove(menu_func)


if __name__ == "__main__":
    register()

(due to how mouse events work, the "LEFTMOUSE" event only happens when the mouse does not move and a mouse click is made)

$\endgroup$
3
  • 2
    $\begingroup$ Thanks in advance, It works perfectly in v3.2 but not in v2.92 because state is not in gpu: ['__doc__', '__loader__', '__name__', '__package__', '__spec__', 'matrix', 'select', 'shader', 'types'] $\endgroup$
    – X Y
    Commented Sep 20, 2022 at 1:25
  • $\begingroup$ Good to know. Unfortunately, there was a migratory period around 2.9x from bgl and prepping gpu, so a latter-only solution is unlikely (speaking from someone updating an add-on to do this very thing). If anything, it'll likely be a mixture of the two. $\endgroup$ Commented Sep 20, 2022 at 21:17
  • 1
    $\begingroup$ Thank you so much @S.Magnusson, works as a charm (v3.4) $\endgroup$
    – Ommadawn
    Commented Oct 7, 2023 at 16:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .