Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. Qt 6.6.1 Android. glReadPixels reads the pixel color from wrong position
Forum Update on Monday, May 27th 2025

Qt 6.6.1 Android. glReadPixels reads the pixel color from wrong position

Scheduled Pinned Locked Moved Solved Mobile and Embedded
11 Posts 2 Posters 1.1k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • 8Observer88 Offline
    8Observer88 Offline
    8Observer8
    wrote on last edited by 8Observer8
    #1

    Hello,

    I read the pixel color and print it to the console. It looks like triangle is not on his place:

    pick-triangle-opengl-qt6.gif

    main.cpp

    #include <QtGui/QOpenGLFunctions>
    #include <QtGui/QMouseEvent>
    #include <QtOpenGL/QOpenGLBuffer>
    #include <QtOpenGL/QOpenGLShaderProgram>
    #include <QtOpenGLWidgets/QOpenGLWidget>
    #include <QtWidgets/QApplication>
    #include <QtWidgets/QLabel>
    #include <QtWidgets/QVBoxLayout>
    #include <QtWidgets/QWidget>
    
    class OpenGLWidget : public QOpenGLWidget, private QOpenGLFunctions
    {
        Q_OBJECT
    
    private:
        void initializeGL() override
        {
            initializeOpenGLFunctions();
            glClearColor(0.f, 1.f, 0.f, 1.f);
    
            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"
                "//out vec4 fragColor;\n"
                "void main()\n"
                "{\n"
                "    gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                "}\n";
    
            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_aPositionLocation = m_program.attributeLocation("aPosition");
        }
    
        void paintGL() override
        {
            glClear(GL_COLOR_BUFFER_BIT);
    
            m_program.setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2);
            m_program.enableAttributeArray(m_aPositionLocation);
            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() << 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();
            m_mouseY = height() - event->pos().y() - 1;
            m_mouseClicked = true;
            update();
        }
    
    private:
        int m_mouseX;
        int m_mouseY;
        bool m_mouseClicked = false;
        QOpenGLBuffer m_vertPosBuffer;
        QOpenGLShaderProgram m_program;
        int m_aPositionLocation;
    };
    
    class MainWindow : public QWidget
    {
        Q_OBJECT
    
    public:
        MainWindow()
        {
            setWindowTitle("OpenGL, Qt6, C++");
            resize(300, 300);
    
            m_nameLabel = new QLabel("Click on object or background");
            m_nameLabel->setSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed);
            OpenGLWidget *openGLWidget = new OpenGLWidget();
    
            QVBoxLayout *layout = new QVBoxLayout();
            layout->addWidget(m_nameLabel);
            layout->addWidget(openGLWidget);
            setLayout(layout);
        }
    
    private:
        QLabel *m_nameLabel;
    };
    
    #include "main.moc"
    
    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        MainWindow w;
        w.show();
        return app.exec();
    }
    

    pro

    QT += core gui openglwidgets widgets
    
    win32: LIBS += -lopengl32
    
    CONFIG += c++17
    
    SOURCES += \
        main.cpp
    
    TARGET = app
    

    Cross-refs:

    • https://community.khronos.org/t/qt-6-6-1-android-glreadpixels-reads-the-pixel-color-from-wrong-position/110404
    • https://stackoverflow.com/questions/77819421/color-picking-with-glreadpixels-works-with-offset-on-android
    1 Reply Last reply
    0
    • jeremy_kJ Offline
      jeremy_kJ Offline
      jeremy_k
      wrote on last edited by
      #6

      The factor of ~2 sounds like a high dpi issue. Does using QScreen::devicePixelRatio() produce better results?

      Asking a question about code? http://eel.is/iso-c++/testcase/

      8Observer88 1 Reply Last reply
      0
      • 8Observer88 Offline
        8Observer88 Offline
        8Observer8
        wrote on last edited by 8Observer8
        #2

        I tried to call glGetError() after glReadPixels() but it returns 0.

                glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
                qDebug() << glGetError();
        
        1 Reply Last reply
        0
        • 8Observer88 Offline
          8Observer88 Offline
          8Observer8
          wrote on last edited by
          #3

          I created the bug report: https://bugreports.qt.io/browse/QTBUG-120648

          1 Reply Last reply
          0
          • 8Observer88 Offline
            8Observer88 Offline
            8Observer8
            wrote on last edited by
            #4

            I attacked a zip file of the example to the bug report above. You can try to run on real device and the Android emulator. This example works as expected on Desktop and WebAssembly. It prints a color in mouse click position:

            pick-color-of-triangle-opengl-qt6.gif

            1 Reply Last reply
            0
            • 8Observer88 Offline
              8Observer88 Offline
              8Observer8
              wrote on last edited by 8Observer8
              #5

              I tried to make an offset with m_mouseX + width() / 2.f for X and m_mouseY + height() / 2.f for glReadPixels(). It is close on Android but it is not enough.

                  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 + width() / 2.f, m_mouseY + height() / 2.f, 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();
                  }
              
              1 Reply Last reply
              0
              • jeremy_kJ Offline
                jeremy_kJ Offline
                jeremy_k
                wrote on last edited by
                #6

                The factor of ~2 sounds like a high dpi issue. Does using QScreen::devicePixelRatio() produce better results?

                Asking a question about code? http://eel.is/iso-c++/testcase/

                8Observer88 1 Reply Last reply
                0
                • jeremy_kJ jeremy_k

                  The factor of ~2 sounds like a high dpi issue. Does using QScreen::devicePixelRatio() produce better results?

                  8Observer88 Offline
                  8Observer88 Offline
                  8Observer8
                  wrote on last edited by
                  #7

                  @jeremy_k I tried to print this value:

                  qDebug() << devicePixelRatio();
                  

                  It shows 1. I use QWidget::devicePixelRatio() because QOpenGLWidget inherits from QWidget. I didn't use QScreen. I think QWidget::devicePixelRatio() and QScreen::devicePixelRatio() is the same. But I see that I cannot set this value. It is a read-only value. I see in docs that I can set DPI to 2 by running an app from the console QT_SCALE_FACTOR=2 ./myapp but I did find how to set it to 2 for Android.

                  1 Reply Last reply
                  0
                  • jeremy_kJ Offline
                    jeremy_kJ Offline
                    jeremy_k
                    wrote on last edited by
                    #8

                    QWidget's devicePixelRatio is really QPaintDevice::devicePixelRatio(). Given the different painting path, I don't know if that is reliable in this situation. Checking both the paint device and the screen is a cheap development-time test.

                    The DPI isn't intended to be configured by application code. It's detected via the platform plugin.

                    Asking a question about code? http://eel.is/iso-c++/testcase/

                    8Observer88 2 Replies Last reply
                    0
                    • jeremy_kJ jeremy_k

                      QWidget's devicePixelRatio is really QPaintDevice::devicePixelRatio(). Given the different painting path, I don't know if that is reliable in this situation. Checking both the paint device and the screen is a cheap development-time test.

                      The DPI isn't intended to be configured by application code. It's detected via the platform plugin.

                      8Observer88 Offline
                      8Observer88 Offline
                      8Observer8
                      wrote on last edited by
                      #9

                      @jeremy_k I was wrong. Unfortunately, "adding half a screen to the coordinates of glReadPixels" is not close. I added a projection matrix and the second triangle to my example. You can see how far a real triangle from what glReadPixels reads:

                      pick-a-few-triangles-opengl-qt6-cpp.gif

                      1 Reply Last reply
                      0
                      • 8Observer88 Offline
                        8Observer88 Offline
                        8Observer8
                        wrote on last edited by
                        #10

                        I rewrote the first example to JavaScript/WebGL and built it to APK with using Cordova. It works without problems with glReadPixels. But EXE with Cordova requires 150 MB. I don't like it. I think it is good idea to use Qt for Desktop/WebAssembly and WebGL/Cordova for Android and Web.

                        > npm install cordova -g
                        > npm install http-server -g
                        > cordova create read-pixels-webgl-js io.github.ivan_8observer8.read_pixels AppClass
                        > emulator.exe -avd GalaxyNexus
                        > cordova build android
                        > cd platforms\android\app\build\outputs\apk\debug
                        > adb install app-debug.apk
                        > adb logcat "eglCodecCommon:S"
                        

                        If you changed a source code you should to rebuild an app for Android: cordova build android, uninstall the app: adb uninstall io.github.ivan_8observer8.read_pixels and install it again: adb install app-debug.apk

                        read-pixels-webgl-js.gif

                        Demo: https://replit.com/@8Observer8/read-pixels-webgl-js

                        index.html

                        <!DOCTYPE html>
                        <html>
                        
                        <head>
                            <meta charset="utf-8">
                            <meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
                            <link rel="stylesheet" type="text/css" href="css/style.css">
                            <title>Read Pixels</title>
                        </head>
                        
                        <body>
                            <canvas id="renderCanvas" width="350" height="350"></canvas>
                        
                            <script type="module" src="./js/index.js"></script>
                        </body>
                        
                        </html>
                        

                        js/index.js

                        import { gl, initWebGLContext } from "./webgl-context.js";
                        
                        function init() {
                            console.log("------------------init------------------");
                            if (!initWebGLContext("renderCanvas")) return;
                        
                            gl.clearColor(0.2, 0.2, 0.2, 1.0);
                        
                            const vertexShaderSource =
                                `
                                    attribute vec2 aPosition;
                                    
                                    void main()
                                    {
                                        gl_Position = vec4(aPosition, 0.0, 1.0);
                                    }`;
                        
                            const fragmentShaderSource =
                                `
                                    precision mediump float;
                                    
                                    void main()
                                    {
                                        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
                                    }`;
                        
                            const vShader = gl.createShader(gl.VERTEX_SHADER);
                            gl.shaderSource(vShader, vertexShaderSource);
                            gl.compileShader(vShader);
                        
                            const fShader = gl.createShader(gl.FRAGMENT_SHADER);
                            gl.shaderSource(fShader, fragmentShaderSource);
                            gl.compileShader(fShader);
                        
                            const program = gl.createProgram();
                            gl.attachShader(program, vShader);
                            gl.attachShader(program, fShader);
                            gl.linkProgram(program);
                            gl.useProgram(program);
                        
                            const vertices = new Float32Array([
                                0, 0.5,
                                -0.5, -0.5,
                                0.5, -0.5
                            ]);
                        
                            const vbo = gl.createBuffer();
                            gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
                            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
                        
                            const aPositionLocation = gl.getAttribLocation(program, "aPosition");
                            gl.vertexAttribPointer(aPositionLocation, 2, gl.FLOAT, false, 0, 0);
                            gl.enableVertexAttribArray(aPositionLocation);
                        
                            window.onresize = () => {
                                const w = gl.canvas.clientWidth;
                                const h = gl.canvas.clientHeight;
                                gl.canvas.width = w;
                                gl.canvas.height = h;
                                gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
                                draw();
                            };
                            window.onresize(null);
                        
                            gl.canvas.onclick = (event) => {
                                const rect = event.target.getBoundingClientRect();
                                const x = event.clientX - rect.left;
                                const y = rect.bottom - event.clientY - 1;
                        
                                draw();
                        
                                const pixels = new Uint8Array(4);
                                gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
                                const output = `${(pixels[0] / 255.0).toFixed(2)} ${(pixels[1] / 255.0).toFixed(2)} ${(pixels[2] / 255.0).toFixed(2)} `;
                                // console.log(
                                //     (pixels[0] / 255.0).toFixed(2),
                                //     (pixels[1] / 255.0).toFixed(2),
                                //     (pixels[2] / 255.0).toFixed(2));
                                console.log(output);
                            };
                        }
                        
                        function draw() {
                            gl.clear(gl.COLOR_BUFFER_BIT);
                            gl.drawArrays(gl.TRIANGLES, 0, 3);
                        }
                        
                        init();
                        

                        js/webgl-context.js

                        export let gl = null;
                        
                        export function initWebGLContext(canvasName) {
                            const canvas = document.getElementById(canvasName);
                            if (canvas === null) {
                                console.log(`Failed to get a canvas element with the name "${canvasName}"`);
                                return false;
                            }
                            gl = canvas.getContext("webgl", { alpha: false, premultipliedAlpha: false });
                            return true;
                        }
                        

                        css/style.css

                        html,
                        body {
                            overflow: hidden;
                            width: 100%;
                            height: 100%;
                            margin: 0;
                            padding: 0;
                        }
                        
                        #renderCanvas {
                            width: 100%;
                            height: 100%;
                        }
                        
                        1 Reply Last reply
                        0
                        • jeremy_kJ jeremy_k

                          QWidget's devicePixelRatio is really QPaintDevice::devicePixelRatio(). Given the different painting path, I don't know if that is reliable in this situation. Checking both the paint device and the screen is a cheap development-time test.

                          The DPI isn't intended to be configured by application code. It's detected via the platform plugin.

                          8Observer88 Offline
                          8Observer88 Offline
                          8Observer8
                          wrote on last edited by
                          #11

                          @jeremy_k you was right! Thank you very much! You showed me a way and 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().

                          Solution:

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

                          pick-color-of-simple-triangle-qopenglwindow-qt6-cpp-android-emulator.gif

                          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
                          {
                              int m_mouseX;
                              int m_mouseY;
                              bool m_mouseClicked = false;
                              QOpenGLBuffer m_vertPosBuffer;
                              QOpenGLShaderProgram m_program;
                          
                              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();
                              }
                          };
                          
                          int main(int argc, char *argv[])
                          {
                              QApplication app(argc, argv);
                              OpenGLWindow w;
                              w.show();
                              return app.exec();
                          }
                          

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

                          QT += core gui opengl widgets
                          
                          win32: LIBS += -lopengl32
                          
                          CONFIG += c++17
                          
                          SOURCES += \
                              main.cpp
                          
                          TARGET = app
                          
                          1 Reply Last reply
                          1
                          • 8Observer88 8Observer8 has marked this topic as solved on
                          • A Alojz Holubek referenced this topic on

                          • Login

                          • Login or register to search.
                          • First post
                            Last post
                          0
                          • Categories
                          • Recent
                          • Tags
                          • Popular
                          • Users
                          • Groups
                          • Search
                          • Get Qt Extensions
                          • Unsolved