Мерцает при захвате/рисовании в реальном времени на QGraphicsViewC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 Мерцает при захвате/рисовании в реальном времени на QGraphicsView

Сообщение Anonymous »

Я работаю над виджетом «Выбор цвета», используя Qt 6.6, QWidget и QGraphicsView, у меня возникли две проблемы:
Проблема 1)
Когда у QGraphicsView нет масштабирования, окно перемещается и изображение рисуется, оно мерцает, я не уверен, можно ли это назвать мерцанием, ореолы, ??, я не могу найти причину такого дрожания изображения:
Изображение
Я попробовал несколько вещей, чтобы сделать захват как можно быстрее:
  • Установите QTimer TimerType в Qt::PresizedTimer
  • Установите интервал таймера на 0
  • Использовал BitBlt в окне захвата Qt
  • Кэшировал биты QImage в DIBSection (ensureCaptureSurface)
  • Задайте для QGraphicsViewviewportUpdateMode значение SmartViewportUpdate
  • Задайте для QGraphicsView  RenderHint(QPainter::SmoothPixmapTransform, false);
Проблема 2)
Когда GrapchisView увеличен, я пытаюсь заставить мышь или окно двигаться медленнее, потому что пиксели большие, и при быстром движении мышь прыгает по пикселям, и вы не можете точно переместить ее над определенным пикселем:
Изображение
Чтобы добиться этого, я возвращаю false в mouseProcHookCallback функция, которая заставляет перехватчик мыши в CALLBACK MouseProc возвращать 1; которые мешают продолжению ввода сообщения, затем я попытался манипулировать позицией курсора с помощью SendInput, как видно на рисунке выше, это также вызывает мерцание, возможно, мерцание из проблемы 1 плюс моя попытка изменить положение курсора.
Я также попробовал использовать WINAPI ClipCursor , это уменьшило мерцание, но движение курсора по-прежнему выглядит странно и не выглядит естественно.
Даже при тестировании В режиме выпуска и без регистрации обе проблемы сохраняются.
Как я могу остановить эти проблемы с мерцанием?
(Примечание: я пытался добавить сюда обе картинки, но ничего не вышло, может ли это исправить мод?)
Main.cpp
#include "ColorPicker.h"

static bool createColorPicker(WPARAM wParam, LPARAM lParam)
{
if (wParam == WM_KEYDOWN && ((KBDLLHOOKSTRUCT*)lParam)->vkCode == VK_F4)
new ColorPicker();
return true;
}

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
qApp->setQuitOnLastWindowClosed(false);

setInputHooks();
setKeyboardHookCallback(new QObject(), createColorPicker);

new ColorPicker();

return app.exec();
}

ColorPicker.h
#pragma once
#include

inline HHOOK g_keyboardHook = NULL;
inline HHOOK g_mouseHook = NULL;
inline QMap g_keyboardHookCallbacks;
inline QMap g_mouseHookCallbacks;

inline LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode != HC_ACTION)
return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam);

for (const auto& callback : g_keyboardHookCallbacks.values())
{
if (!callback(wParam, lParam))
return 1; // Block the event if any callback returns false
}

return CallNextHookEx(g_keyboardHook, nCode, wParam, lParam);
}

inline LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode != HC_ACTION)
return CallNextHookEx(g_mouseHook, nCode, wParam, lParam);

MSLLHOOKSTRUCT* mouseStruct = (MSLLHOOKSTRUCT*)lParam;

for (const auto& callback : g_mouseHookCallbacks.values())
{
if (!callback(wParam, lParam))
return 1; // Block the event if any callback returns false
}

return CallNextHookEx(g_mouseHook, nCode, wParam, lParam);
}

template
static void setKeyboardHookCallback(T* obj, bool(T::*callback)(WPARAM, LPARAM))
{
QObject::connect(obj, &QObject::destroyed, [obj]
{
g_keyboardHookCallbacks.remove(obj);
});
g_keyboardHookCallbacks[obj] = ([obj, callback](WPARAM wParam, LPARAM lParam) { return (obj->*callback)(wParam, lParam); });
}

template
static void setMouseHookCallback(T* obj, bool(T::*callback)(WPARAM, LPARAM))
{
QObject::connect(obj, &QObject::destroyed, [obj]
{
g_mouseHookCallbacks.remove(obj);
});
g_mouseHookCallbacks[obj] = ([obj, callback](WPARAM wParam, LPARAM lParam) { return (obj->*callback)(wParam, lParam); });
}

inline void setKeyboardHookCallback(QObject* obj, std::function callback)
{
QObject::connect(obj, &QObject::destroyed, [obj]
{
g_keyboardHookCallbacks.remove(obj);
});
g_keyboardHookCallbacks[obj] = callback;
}

inline void setInputHooks()
{
QSettings settings("HKEY_CURRENT_USER\\Control Panel\\Desktop", QSettings::NativeFormat);
settings.setValue("LowLevelHooksTimeout", 100);
settings.sync();

g_keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0);
g_mouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseProc, NULL, 0);
if (g_keyboardHook == NULL || g_mouseHook == NULL)
MessageBox(NULL, L"Failed to set input hook", L"Error", MB_ICONERROR);
}

class GridView : public QGraphicsView
{
public:
QGraphicsScene* m_scene = nullptr;
QPixmap m_pixmap; // Pixmap set on the pixmapItem
QGraphicsPixmapItem* m_pixmapItem;
QGraphicsItemGroup* m_gridGroup;
bool m_enabled = false;
bool m_gridEnabled = false;
QRect m_sceneRect;
QPoint m_lastMousePos;
qreal m_baseScale = 1.0;
qreal m_currentScale = 1.0;

GridView(QWidget* p = nullptr);

void setPixmap(const QPixmap& pixmap);
void setGraphicsViewEnabled();
void setGridEnabled();
void resetView();
void centerScene();
void updateGrid();

void wheelEvent(QWheelEvent* event) override;
};

class ColorPicker : public QWidget
{
Q_OBJECT
public:
const int GRID_WIDTH = 256;
const int GRID_HEIGHT = 256;

QTimer* m_captureScreenTimer = nullptr;
GridView* m_gridView = nullptr;

QPointF m_cursorAccum;
QPoint m_lastCursorPos;
QSize m_captureSize;

qreal m_captureDpr = 0.0;
HDC m_captureDC = nullptr;
HBITMAP m_captureBitmap = nullptr;
HGDIOBJ m_captureOldBitmap = nullptr;

QImage m_captureImage;

ColorPicker();
~ColorPicker();

void destroyCaptureSurface();
bool ensureCaptureSurface();

void lockCursor();
void unlockCursor();
void updateOverlayPosition(const QPoint& pt);

void captureScreenTimerTimeout();

bool keyboardProcHookCallback(WPARAM wParam, LPARAM lParam);
bool mouseProcHookCallback(WPARAM wParam, LPARAM lParam);
};

ColorPicker.cpp
#include "ColorPicker.h"
#include
#include
#include

static std::ofstream& getLog()
{
static std::ofstream log("D:\\colorpicker_debug.log", std::ios::trunc);
return log;
}

void logCaptureState(const QPoint& cursorPos, const QPoint& windowPos, const QSize& captureSize, qreal dpr)
{
static QPoint lastCursorPos(std::numeric_limits::min(), std::numeric_limits::min());
static QPoint lastWindowPos(std::numeric_limits::min(), std::numeric_limits::min());
static QSize lastCaptureSize;
static qreal lastDpr = 0.0;

if (cursorPos == lastCursorPos && windowPos == lastWindowPos && captureSize == lastCaptureSize && dpr == lastDpr)
return;

lastCursorPos = cursorPos;
lastWindowPos = windowPos;
lastCaptureSize = captureSize;
lastDpr = dpr;

auto& log = getLog();
log 0 || topLeft.y() > 0)
{
QTransform t;
t.translate(
topLeft.x() > 0 ? -topLeft.x() / m_currentScale : 0,
topLeft.y() > 0 ? -topLeft.y() / m_currentScale : 0
);
setTransform(t, true);
}
else
{
// If not, then check if perhaps the bottom right corner is.
QPointF bottomRight = mapFromScene(width(), height());
if (bottomRight.x() < width() || bottomRight.y() < height())
{
QTransform t;
t.translate(
bottomRight.x() < width() ? - (bottomRight.x() - width()) / m_currentScale : 0,
bottomRight.y() < height() ? -(bottomRight.y() - height()) / m_currentScale : 0
);
setTransform(t, true);
}
}

QPolygonF point = mapToScene(viewport()->rect());
if (!point.isEmpty())
m_sceneRect = QRect(point[0].toPoint(), point[2].toPoint());
}

void GridView::updateGrid()
{
if (!m_gridEnabled)
return;

// Clear existing grid
while (!m_gridGroup->childItems().isEmpty()) {
delete m_gridGroup->childItems().first();
}

// Get visible area in scene coordinates
QRectF visibleRect = mapToScene(viewport()->rect()).boundingRect();
QRectF imageRect = m_pixmapItem->boundingRect();
QRectF gridRect = visibleRect.intersected(imageRect);

// Calculate grid spacing in scene coordinates
qreal gridSpacing = 1.0; // One pixel in image coordinates

QPen gridPen(QColor(200, 200, 200, 255), 0); // Width 0 for single pixel line

// Calculate grid line positions
qreal startX = std::ceil(gridRect.left() / gridSpacing) * gridSpacing;
qreal startY = std::ceil(gridRect.top() / gridSpacing) * gridSpacing;

// Draw vertical lines
for (qreal x = startX; x setPen(gridPen);
m_gridGroup->addToGroup(line);
}

// Draw horizontal lines
for (qreal y = startY; y setPen(gridPen);
m_gridGroup->addToGroup(line);
}
}

void GridView::wheelEvent(QWheelEvent* event)
{
if (m_pixmapItem->pixmap().isNull() || !m_enabled)
return QGraphicsView::wheelEvent(event);

// Store cursor position relative to scene
QPointF mousePos = event->position(),
mousePosCurrent = mapToScene(mousePos.x(), mousePos.y());

// Calculate zoom factor
double factor = pow(1.5, event->angleDelta().y() / 240.0);
double newScale = m_currentScale * factor;
if (newScale < 1.) { // Do not allow zooming out (in absolute)
factor /= newScale;
newScale = 1.;
}
else if (newScale > 50.) { // Do not allow zoom > 50x
factor = factor * 50. / newScale;
newScale = 50.;
}

// Zoom centered on the mouse cursor
QTransform t;
t.translate(mousePosCurrent.x(), mousePosCurrent.y());
t.scale(factor, factor);
t.translate(-mousePosCurrent.x(), -mousePosCurrent.y());
setTransform(t, true);

m_currentScale = newScale;
centerScene();

// Update grid based on zoom level
if (m_gridEnabled)
{
m_gridGroup->setVisible(m_currentScale > 3.0);
if (m_currentScale > 3.0)
updateGrid();
}

event->accept();
}

ColorPicker::ColorPicker() : QWidget()
{
setWindowFlags(Qt::FramelessWindowHint|Qt::WindowStaysOnTopHint);

setKeyboardHookCallback(this, &ColorPicker::keyboardProcHookCallback);
setMouseHookCallback(this, &ColorPicker::mouseProcHookCallback);

QWidget* widget = new QWidget(this);
widget->setFixedSize(GRID_WIDTH + 4, GRID_HEIGHT + 5);
widget->setStyleSheet("background-color: red;");
widget->move(0, 0);

m_gridView = new GridView(this);
m_gridView->setGraphicsViewEnabled();
m_gridView->setGridEnabled();
m_gridView->setFixedSize(GRID_WIDTH, GRID_HEIGHT);
m_gridView->move(2, 3);

m_captureScreenTimer = new QTimer(this);
m_captureScreenTimer->setTimerType(Qt::PreciseTimer);
m_captureScreenTimer->setInterval(0);
m_captureScreenTimer->start();
connect(m_captureScreenTimer, &QTimer::timeout, this, &ColorPicker::captureScreenTimerTimeout);

m_lastCursorPos = QCursor::pos();

HWND hwnd = (HWND)winId();
LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_LAYERED | WS_EX_TRANSPARENT);
SetLayeredWindowAttributes(hwnd, 0, 255, LWA_ALPHA);

SetWindowDisplayAffinity(hwnd, WDA_EXCLUDEFROMCAPTURE);
ensureCaptureSurface();

show();
}

ColorPicker::~ColorPicker()
{
if (m_captureScreenTimer != nullptr)
m_captureScreenTimer->stop();
destroyCaptureSurface();
unlockCursor();
}

void ColorPicker::destroyCaptureSurface()
{
m_captureImage = QImage();

if (m_captureDC != nullptr && m_captureOldBitmap != nullptr)
{
SelectObject(m_captureDC, m_captureOldBitmap);
m_captureOldBitmap = nullptr;
}

if (m_captureBitmap != nullptr)
{
DeleteObject(m_captureBitmap);
m_captureBitmap = nullptr;
}

if (m_captureDC != nullptr)
{
DeleteDC(m_captureDC);
m_captureDC = nullptr;
}

m_captureSize = QSize();
m_captureDpr = 0.0;
}

bool ColorPicker::ensureCaptureSurface()
{
qreal dpr = m_gridView->devicePixelRatioF();
QSize captureSize(qRound(256 * dpr), qRound(256 * dpr));
if (captureSize.isEmpty())
return false;

if (m_captureBitmap != nullptr && captureSize == m_captureSize && dpr == m_captureDpr)
return true;

destroyCaptureSurface();

BITMAPINFO bmi = {};
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = captureSize.width();
bmi.bmiHeader.biHeight = -captureSize.height();
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32;
bmi.bmiHeader.biCompression = BI_RGB;

void* dibBits = nullptr;
m_captureBitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &dibBits, nullptr, 0);
if (m_captureBitmap == nullptr || dibBits == nullptr)
{
destroyCaptureSurface();
return false;
}

m_captureDC = CreateCompatibleDC(nullptr);
if (m_captureDC == nullptr)
{
destroyCaptureSurface();
return false;
}

m_captureOldBitmap = SelectObject(m_captureDC, m_captureBitmap);
m_captureImage = QImage(static_cast(dibBits), captureSize.width(), captureSize.height(), captureSize.width() * 4, QImage::Format_ARGB32);
m_captureImage.setDevicePixelRatio(dpr);
m_captureSize = captureSize;
m_captureDpr = dpr;
return !m_captureImage.isNull();
}

void ColorPicker::lockCursor()
{
POINT pt;
GetCursorPos(&pt);
RECT rc = { pt.x, pt.y, pt.x + 1, pt.y + 1 };
ClipCursor(&rc);
}

void ColorPicker::unlockCursor()
{
ClipCursor(NULL);
}

void ColorPicker::updateOverlayPosition(const QPoint& cursorPos)
{
QPoint newPos = QPoint(cursorPos.x() - (size().width() / 2) + 9,
cursorPos.y() - (size().height() / 2));
if (pos() != newPos)
move(newPos);
}

bool ColorPicker::keyboardProcHookCallback(WPARAM wParam, LPARAM lParam)
{
KBDLLHOOKSTRUCT* kbStruct = (KBDLLHOOKSTRUCT*)lParam;
if (wParam == WM_KEYDOWN && kbStruct->vkCode == VK_ESCAPE)
{
close();
deleteLater();
return false;
}
return true;
}

bool ColorPicker::mouseProcHookCallback(WPARAM wParam, LPARAM lParam)
{
MSLLHOOKSTRUCT* mouseStruct = (MSLLHOOKSTRUCT*)lParam;
short delta = HIWORD(mouseStruct->mouseData);
QPoint pt(mouseStruct->pt.x, mouseStruct->pt.y);

if (wParam == WM_MOUSEMOVE)
{
auto& log = getLog();
qreal scale = m_gridView->m_currentScale;

if (scale > 1.0)
{
// Accept our own injected moves
if (mouseStruct->flags & LLMHF_INJECTED)
{
log

Подробнее здесь: https://stackoverflow.com/questions/799 ... aphicsview
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»