c++qtcore-graphicsframe-rate

Comparision of two QImages is wrong


I am working on an app that measures FPS in a specified area. The problem is that if nothing is happening in the are I want the fps value to be 0. But when I start measuring and for exemple move a window close to the place where measurement is ongoing the fps value changes. As if the area where I am measuring is bigger than what I actually want.

I even tried to save the QImages that are compared and visually there is no difference. So the comparison of the two QImages should have returned false but it returned true. Here is my code:

#include "framecounter.h"

FrameCounter::FrameCounter(QWidget* parent) : QWidget(parent)
{
    // Connect the captureTimer's timeout signal to the captureScreen slot.
    connect(&m_captureTimer, &QTimer::timeout, this, &FrameCounter::captureScreen);

    m_elapsedTimer.start(); // Start the elapsed timer.
}

void FrameCounter::captureScreen()
{
#ifdef Q_OS_MAC
    // Capture the screen using Core Graphics
    CGImageRef cgImage = CGWindowListCreateImage(CGRectMake(m_x, m_y, m_size, m_size), kCGWindowListOptionOnScreenOnly, kCGNullWindowID, kCGWindowImageDefault);
    QImage screenCapture = CGImageToQImage(cgImage);
    CGImageRelease(cgImage);
#else
    // Capture the screen using Qt
    QScreen* screen = QGuiApplication::primaryScreen();
    if (!screen)
        return;

    QImage screenCapture = screen->grabWindow(0, m_x, m_y, m_size, m_size).toImage();
#endif

    if (!m_previousFrame.isNull() && screenCapture != m_previousFrame)
    {
        ++m_frameCount;
    }

    m_previousFrame = screenCapture;
    calculateFps();
}

void FrameCounter::calculateFps()
{
    qint64 elapsed = m_elapsedTimer.elapsed();
    //Calculate the current FPS and update the list of FPS values every 300 milliseconds.
    if (elapsed >= 300)
    {
        m_currentFps = static_cast<double>(m_frameCount) / (static_cast<double>(elapsed) / 1000.0);
        if(m_currentFps!=0){
            m_listOfFps.append(m_currentFps);
        }

        // Reset the frame count and restart the timer.
        m_frameCount = 0;
        m_elapsedTimer.restart();
        emit newfpsvalue();
    }
}

void FrameCounter::startMeasurement(int x, int y, int size)
{
    // Define the measurement area and start timers
    m_x = x;
    m_y = y;
    m_size = size;
    // Start the capture timer to capture frames every 16 milliseconds.
    m_captureTimer.start(16);

    m_elapsedTimer.restart();
}

void FrameCounter::stopMeasurement()
{
    m_captureTimer.stop();

    // Reset FPS and clear the list of FPS values.
    m_currentFps = 0;
    m_listOfFps.clear();
}

double FrameCounter::getCurrentFps() const
{
    // Return the current FPS value.
    return m_currentFps;
}

#ifdef Q_OS_MAC
CGBitmapInfo FrameCounter::CGBitmapInfoForQImage(const QImage &image)
{
    CGBitmapInfo bitmapInfo = kCGImageAlphaNone;

    switch (image.format()) {
    case QImage::Format_ARGB32:
        bitmapInfo = kCGImageAlphaFirst | kCGBitmapByteOrder32Host;
        break;
    case QImage::Format_RGB32:
        bitmapInfo = kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host;
        break;
    case QImage::Format_RGBA8888_Premultiplied:
        bitmapInfo = kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big;
        break;
    case QImage::Format_RGBA8888:
        bitmapInfo = kCGImageAlphaLast | kCGBitmapByteOrder32Big;
        break;
    case QImage::Format_RGBX8888:
        bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrder32Big;
        break;
    case QImage::Format_ARGB32_Premultiplied:
        bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
        break;
    default:
        break;
    }

    return bitmapInfo;
}

QImage FrameCounter::CGImageToQImage(CGImageRef cgImage)
{
    const size_t width = CGImageGetWidth(cgImage);
    const size_t height = CGImageGetHeight(cgImage);
    QImage image(static_cast<int>(width), static_cast<int>(height), QImage::Format_ARGB32_Premultiplied);
    image.fill(Qt::transparent);

    CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
    CGContextRef context = CGBitmapContextCreate(static_cast<void*>(image.bits()), static_cast<size_t>(image.width()), static_cast<size_t>(image.height()), 8,
                                                 static_cast<size_t>(image.bytesPerLine()), colorSpace, CGBitmapInfoForQImage(image));

           // Scale the context so that painting happens in device-independent pixels
    const qreal devicePixelRatio = image.devicePixelRatio();
    CGContextScaleCTM(context, devicePixelRatio, devicePixelRatio);

    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextDrawImage(context, rect, cgImage);

    CFRelease(colorSpace);
    CGContextRelease(context);

    return image;
}
#endif

Does anyone know what the cause of this might be? Is it realted to the capture or the comparison of the QImages?

If it might help, if I am measuring the area where top left corner has these coordinates (300,300) and with the size of (250,250), the FPS value changes whenever something happens in the area with top left corner at approximately (245,205) ans with size of approximately (360,375).

EDIT: As suggested I compared the pixel values and saw that when a window get close to the are some RGB values have a small change so I implemented a function that compares two QImages and tolerate small changes:

bool FrameCounter::framesAreEqual(const QImage& img1, const QImage& img2) const
{
    if (img1.size() != img2.size())
        return false;

    int width  = img1.width();
    int height = img1.height();

    for (int y = 0; y < height; ++y)
    {
        for (int x = 0; x < width; ++x)
        {
            QRgb pixel1 = img1.pixel(x, y);
            QRgb pixel2 = img2.pixel(x, y);

            int redDiff   = qAbs(qRed(pixel1) - qRed(pixel2));
            int greenDiff = qAbs(qGreen(pixel1) - qGreen(pixel2));
            int blueDiff  = qAbs(qBlue(pixel1) - qBlue(pixel2));
            if (redDiff > 15 || greenDiff > 15 || blueDiff > 15)
            {
                return false;
            }
        }
    }

    return true;
}

It is working but I still am not fully satisfied with this solution. Does anyone know why the pixel value changes when movements close to the area the measurement is ongoing?

EDIT 2: The problem was because to the shadow of windows. When a window get close it shadow enters the area of measurement.


Solution

  • This problem was caused, as I mentionned in EDIT 2, by the shadows of windows that changed the value of the pixels. So I removed the function FramesAreEqual that tolerate a little change and left the code as it was since the app will most likely be used on full screen windows.