Search code examples
androidc++qtopengl-esglreadpixels

Color picking with glReadPixels works with offset on Android


I use Qt 6.7 and Window 10. I wrote a simple example that shows a problem. This example draws a red triangle on the background with (0.2, 0.2, 0.2) color. I implemented the mousePressEvent method. When you click on the canvas the example will print a color to the console. I tested this example on Windows 10 and WebAssembly - it works as expected. For example how it works on WebAssembly:

enter image description here

But on Android (both: real device connected with USB-cable and Android Emulator) it works with offset. So I draw a triangle in the center but it looks like I draw it in the top right corner. When I touch on the triangle it prints (0.2, 0.2, 0.2) to the console but when I touch to the top right corner it prints (1, 0, 0) like the triangle in the top right corner. It looks like the beginning of coordinate system for glReadPixels is in the center of the window:

enter image description here

    void paintGL() override
    {
        glClearColor(0.2f, 0.2f, 0.2f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        m_program.bind();
        m_vertPosBuffer.bind();
        m_program.setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2);
        m_program.enableAttributeArray(m_aPositionLocation);
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // qDebug() << glGetError() << "\n";

        if (m_mouseClicked)
        {
            // Read the pixel
            GLubyte pixel[4];
            glReadPixels(m_mouseX, m_mouseY, 1, 1,
                         GL_RGBA, GL_UNSIGNED_BYTE, pixel);
            // qDebug() << glGetError() << "\n";
            qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f << "\n";
            qDebug() << m_mouseX << m_mouseY << "\n";
            m_mouseClicked = false;
        }
    }

    void mousePressEvent(QMouseEvent *event) override
    {
        m_mouseX = event->pos().x();
        m_mouseY = height() - event->pos().y() - 1;
        m_mouseClicked = true;
        update();
    }

Solution

  • I found a solution in comments here: OpenGL support broken with high-dpi (Retina) on OS X. It is for macOS but it is true for Android too. The comment of Laszlo Agocs helped me:

    You need to adjust the GL positions based on the devicePixelRatio(). If the window is size N,M and devicePixelRatio() is 2 then the GL framebuffer, viewport will all have a size of 2N,2M. Try multiplying mouseX and mouseY with devicePixelRatio().

    So, QWindow::devicePixelRatio() must be used. Notice that mousePressEvent() works on Android too. You don't need to use touchEvent unless you need to handle multiple touches.

        void mousePressEvent(QMouseEvent *event) override
        {
            m_mouseX = event->pos().x() * devicePixelRatio();
            m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio();
            m_mouseClicked = true;
            update();
        }
    

    I tested the following example on a real smartphone and an emulator. It prints red (1, 0, 1) to the console when you touch the red triangle, and prints gray when you touch the gray background:

    enter image description here

    main.cpp

    #include <QtGui/QMouseEvent>
    #include <QtGui/QOpenGLFunctions>
    #include <QtOpenGL/QOpenGLBuffer>
    #include <QtOpenGL/QOpenGLShaderProgram>
    #include <QtOpenGL/QOpenGLWindow>
    #include <QtWidgets/QApplication>
    
    class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions
    {
    public:
        OpenGLWindow()
        {
            setTitle("OpenGL ES 2.0, Qt6, C++");
            resize(380, 380);
        }
    
    private:
        void initializeGL() override
        {
            initializeOpenGLFunctions();
            glClearColor(0.2f, 0.2f, 0.2f, 1.f);
            qDebug() << "Device pixel ratio:" << devicePixelRatio();
    
            QString vertexShaderSource =
                "attribute vec2 aPosition;\n"
                "void main()\n"
                "{\n"
                "    gl_Position = vec4(aPosition, 0.0, 1.0);\n"
                "}\n";
    
            QString fragmentShaderSource =
                "#ifdef GL_ES\n"
                "precision mediump float;\n"
                "#endif\n"
                "void main()\n"
                "{\n"
                "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                "}\n";
    
            m_program.create();
            m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex,
                                              vertexShaderSource);
            m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment,
                                              fragmentShaderSource);
            m_program.link();
            m_program.bind();
    
            float vertPositions[] = {
                -0.5f, -0.5f,
                0.5f, -0.5f,
                0.f, 0.5f
            };
            m_vertPosBuffer.create();
            m_vertPosBuffer.bind();
            m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions));
    
            m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2);
            m_program.enableAttributeArray("aPosition");
        }
    
        void paintGL() override
        {
            glClear(GL_COLOR_BUFFER_BIT);
            glDrawArrays(GL_TRIANGLES, 0, 3);
    
            if (m_mouseClicked)
            {
                // Read the pixel
                GLubyte pixel[4];
                glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
                // qDebug() << glGetError() << "\n";
                qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f;
                m_mouseClicked = false;
            }
        }
    
        void mousePressEvent(QMouseEvent *event) override
        {
            m_mouseX = event->pos().x() * devicePixelRatio();
            m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio();
            m_mouseClicked = true;
            update();
        }
    
    private:
        int m_mouseX;
        int m_mouseY;
        bool m_mouseClicked = false;
        QOpenGLBuffer m_vertPosBuffer;
        QOpenGLShaderProgram m_program;
    };
    
    int main(int argc, char *argv[])
    {
        QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL);
        QApplication app(argc, argv);
        OpenGLWindow w;
        w.show();
        return app.exec();
    }
    

    pick-color-of-simple-triangle-qopenglwindow-qt6-cpp.pro

    QT += core gui openglwidgets
    
    CONFIG += c++17
    
    SOURCES += \
        main.cpp