Skip to content

Commit 0ade3c7

Browse files
author
etet100
committed
Camera crosshair
1 parent c3463e0 commit 0ade3c7

4 files changed

Lines changed: 145 additions & 12 deletions

File tree

src/astrocore/modules/camera/camera.cpp

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <QMessageBox>
99
#include <QtWidgets>
1010
#include <QImageCapture>
11+
#include "utils/cache.h"
1112

1213
Camera::Camera(QWidget* parent) : QVideoWidget(parent), m_frameProcessor(this)
1314
{
@@ -28,6 +29,9 @@ Camera::Camera(QWidget* parent) : QVideoWidget(parent), m_frameProcessor(this)
2829
findBestResolution(this->width(), this->height());
2930
});
3031

32+
buildCrosshairMenu();
33+
loadCrosshairStyle();
34+
3135
init();
3236
}
3337

@@ -227,6 +231,11 @@ void Camera::updateCameras()
227231
m_menu.addAction(videoDeviceAction);
228232
m_camerasCount++;
229233
}
234+
235+
if (m_crosshairMenu != nullptr) {
236+
m_menu.addSeparator();
237+
m_menu.addMenu(m_crosshairMenu);
238+
}
230239
}
231240

232241
void Camera::findBestResolution(int w, int h)
@@ -262,3 +271,57 @@ void Camera::findBestResolution(int w, int h)
262271
}
263272
}
264273

274+
void Camera::buildCrosshairMenu()
275+
{
276+
using Style = CameraFrameProcessor::CrosshairStyle;
277+
278+
m_crosshairMenu = new QMenu(tr("Crosshair"), this);
279+
m_crosshairGroup = new QActionGroup(this);
280+
m_crosshairGroup->setExclusive(true);
281+
282+
const QList<QPair<QString, Style>> styles = {
283+
{ tr("None"), Style::None },
284+
{ tr("Cross"), Style::Cross },
285+
{ tr("Cross with gap"), Style::CrossGap },
286+
{ tr("Dot"), Style::Dot },
287+
{ tr("Circle with ticks"), Style::CircleTicks }
288+
};
289+
290+
for (const auto &[label, style] : styles) {
291+
QAction *action = new QAction(label, m_crosshairGroup);
292+
action->setCheckable(true);
293+
action->setData(static_cast<int>(style));
294+
m_crosshairMenu->addAction(action);
295+
}
296+
297+
connect(m_crosshairGroup, &QActionGroup::triggered, this, &Camera::updateCrosshairStyle);
298+
}
299+
300+
void Camera::loadCrosshairStyle()
301+
{
302+
using Style = CameraFrameProcessor::CrosshairStyle;
303+
304+
const int stored = Cache::instance().getInt("camera/crosshairStyle", static_cast<int>(Style::CrossGap));
305+
306+
m_frameProcessor.setCrosshairStyle(static_cast<Style>(stored));
307+
308+
for (QAction *action : m_crosshairGroup->actions()) {
309+
if (action->data().toInt() == stored) {
310+
action->setChecked(true);
311+
break;
312+
}
313+
}
314+
}
315+
316+
void Camera::updateCrosshairStyle(QAction *action)
317+
{
318+
using Style = CameraFrameProcessor::CrosshairStyle;
319+
320+
const int value = action->data().toInt();
321+
322+
m_frameProcessor.setCrosshairStyle(static_cast<Style>(value));
323+
324+
Cache::instance().set("camera/crosshairStyle", value);
325+
Cache::instance().flush();
326+
}
327+

src/astrocore/modules/camera/camera.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ private slots:
3131
void displayCameraError();
3232
void updateCameraDevice(QAction *action);
3333
void updateCameraActive(bool active);
34+
void updateCrosshairStyle(QAction *action);
3435

3536
protected:
3637
void resizeEvent(QResizeEvent *event) override;
@@ -39,12 +40,14 @@ private slots:
3940

4041
private:
4142
QActionGroup *videoDevicesGroup = nullptr;
43+
QActionGroup *m_crosshairGroup = nullptr;
4244
QMediaDevices m_devices;
4345
QVideoSink m_videoSink;
4446
QScopedPointer<QCamera> m_camera;
4547
QMediaCaptureSession m_captureSession;
4648
QTimer m_resizeTimer;
4749
QMenu m_menu;
50+
QMenu *m_crosshairMenu = nullptr;
4851
CameraFrameProcessor m_frameProcessor;
4952
int m_camerasCount = 0;
5053

@@ -53,6 +56,8 @@ private slots:
5356
void findBestResolution(int, int);
5457
void showCameraDisabled();
5558
void disableCamera();
59+
void buildCrosshairMenu();
60+
void loadCrosshairStyle();
5661
};
5762

5863
#endif

src/astrocore/modules/camera/cameraframeprocessor.cpp

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ void CameraFrameProcessor::setVideoSinks(QVideoSink *inputSink, QVideoSink *outp
2727

2828
void CameraFrameProcessor::processFrame(const QVideoFrame &frame)
2929
{
30-
if (!m_overlay) {
30+
if (m_style == CrosshairStyle::None) {
3131
m_outputSink->setVideoFrame(frame);
3232

3333
return;
@@ -40,19 +40,70 @@ void CameraFrameProcessor::processFrame(const QVideoFrame &frame)
4040
return;
4141
}
4242

43-
QFont font = painter.font();
44-
font.setPixelSize(100);
45-
font.setBold(true);
46-
painter.setFont(font);
47-
48-
painter.drawText(50, 50, "Example text");
43+
drawCrosshair(painter, image.size());
4944

5045
QVideoFrame frameCopy(image);
5146

5247
m_outputSink->setVideoFrame(frameCopy);
53-
m_outputSink->setSubtitleText("abc");
5448
}
55-
void CameraFrameProcessor::setOverlay(bool overlay)
49+
50+
void CameraFrameProcessor::drawCrosshair(QPainter &painter, const QSize &size)
51+
{
52+
const int cx = size.width() / 2;
53+
const int cy = size.height() / 2;
54+
55+
// scale line width and center gap to the frame size so the crosshair
56+
// stays visible on both low- and high-resolution cameras
57+
const int penWidth = qMax(2, size.height() / 300);
58+
const int gap = qMax(6, size.height() / 40);
59+
60+
painter.setRenderHint(QPainter::Antialiasing, true);
61+
62+
QPen pen(Qt::red);
63+
pen.setWidth(penWidth);
64+
painter.setPen(pen);
65+
painter.setBrush(Qt::NoBrush);
66+
67+
switch (m_style) {
68+
case CrosshairStyle::Cross:
69+
// full lines crossing the whole frame
70+
painter.drawLine(0, cy, size.width(), cy);
71+
painter.drawLine(cx, 0, cx, size.height());
72+
break;
73+
74+
case CrosshairStyle::CrossGap:
75+
// lines with a gap in the middle and a circle at the center
76+
painter.drawLine(0, cy, cx - gap, cy);
77+
painter.drawLine(cx + gap, cy, size.width(), cy);
78+
painter.drawLine(cx, 0, cx, cy - gap);
79+
painter.drawLine(cx, cy + gap, cx, size.height());
80+
painter.drawEllipse(QPoint(cx, cy), gap, gap);
81+
break;
82+
83+
case CrosshairStyle::Dot:
84+
// single filled dot marking the target point
85+
painter.setBrush(Qt::red);
86+
painter.drawEllipse(QPoint(cx, cy), penWidth * 2, penWidth * 2);
87+
break;
88+
89+
case CrosshairStyle::CircleTicks: {
90+
// center circle with short ticks pointing inward
91+
const int radius = gap * 2;
92+
const int tick = gap;
93+
painter.drawEllipse(QPoint(cx, cy), radius, radius);
94+
painter.drawLine(cx, cy - radius - tick, cx, cy - radius);
95+
painter.drawLine(cx, cy + radius, cx, cy + radius + tick);
96+
painter.drawLine(cx - radius - tick, cy, cx - radius, cy);
97+
painter.drawLine(cx + radius, cy, cx + radius + tick, cy);
98+
break;
99+
}
100+
101+
case CrosshairStyle::None:
102+
break;
103+
}
104+
}
105+
106+
void CameraFrameProcessor::setCrosshairStyle(CrosshairStyle style)
56107
{
57-
m_overlay = overlay;
108+
m_style = style;
58109
}

src/astrocore/modules/camera/cameraframeprocessor.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,26 @@
88
#include <QVideoSink>
99
#include <QVideoFrame>
1010

11+
class QPainter;
12+
1113
class CameraFrameProcessor : public QObject
1214
{
1315
Q_OBJECT
1416

1517
public:
18+
enum class CrosshairStyle {
19+
None,
20+
Cross,
21+
CrossGap,
22+
Dot,
23+
CircleTicks
24+
};
25+
Q_ENUM(CrosshairStyle)
26+
1627
CameraFrameProcessor(QObject *parent);
1728
void setVideoSinks(QVideoSink *inputSink, QVideoSink *outputSink);
18-
void setOverlay(bool overlay);
29+
void setCrosshairStyle(CrosshairStyle style);
30+
CrosshairStyle crosshairStyle() const { return m_style; }
1931

2032
signals:
2133
void videoSinkChanged();
@@ -24,9 +36,11 @@ class CameraFrameProcessor : public QObject
2436
void processFrame(const QVideoFrame &frame);
2537

2638
private:
39+
void drawCrosshair(QPainter &painter, const QSize &size);
40+
2741
QVideoSink *m_inputSink = nullptr;
2842
QVideoSink *m_outputSink = nullptr;
29-
bool m_overlay = false;
43+
CrosshairStyle m_style = CrosshairStyle::CrossGap;
3044
};
3145

3246
#endif // CAMERAFRAMEPROCESSOR_H

0 commit comments

Comments
 (0)