summaryrefslogtreecommitdiff
path: root/src/qcam
diff options
context:
space:
mode:
Diffstat (limited to 'src/qcam')
-rw-r--r--src/qcam/format_converter.cpp104
-rw-r--r--src/qcam/format_converter.h25
-rw-r--r--src/qcam/main.cpp75
-rw-r--r--src/qcam/main_window.cpp228
-rw-r--r--src/qcam/main_window.h54
-rw-r--r--src/qcam/meson.build19
-rw-r--r--src/qcam/qt_event_dispatcher.cpp145
-rw-r--r--src/qcam/qt_event_dispatcher.h62
-rw-r--r--src/qcam/viewfinder.cpp46
-rw-r--r--src/qcam/viewfinder.h34
10 files changed, 792 insertions, 0 deletions
diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp
new file mode 100644
index 00000000..6979a054
--- /dev/null
+++ b/src/qcam/format_converter.cpp
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * format_convert.cpp - qcam - Convert buffer to RGB
+ */
+
+#include <errno.h>
+
+#include <linux/videodev2.h>
+
+#include "format_converter.h"
+
+#define RGBSHIFT 8
+#ifndef MAX
+#define MAX(a,b) ((a)>(b)?(a):(b))
+#endif
+#ifndef MIN
+#define MIN(a,b) ((a)<(b)?(a):(b))
+#endif
+#ifndef CLAMP
+#define CLAMP(a,low,high) MAX((low),MIN((high),(a)))
+#endif
+#ifndef CLIP
+#define CLIP(x) CLAMP(x,0,255)
+#endif
+
+int FormatConverter::configure(unsigned int format, unsigned int width,
+ unsigned int height)
+{
+ switch (format) {
+ case V4L2_PIX_FMT_VYUY:
+ y_pos_ = 1;
+ cb_pos_ = 2;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ y_pos_ = 0;
+ cb_pos_ = 3;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ y_pos_ = 1;
+ cb_pos_ = 0;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ y_pos_ = 0;
+ cb_pos_ = 1;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ width_ = width;
+ height_ = height;
+
+ return 0;
+}
+
+static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
+{
+ int c = y - 16;
+ int d = u - 128;
+ int e = v - 128;
+ *r = CLIP(( 298 * c + 409 * e + 128) >> RGBSHIFT);
+ *g = CLIP(( 298 * c - 100 * d - 208 * e + 128) >> RGBSHIFT);
+ *b = CLIP(( 298 * c + 516 * d + 128) >> RGBSHIFT);
+}
+
+void FormatConverter::convert(const unsigned char *src, unsigned char *dst)
+{
+ unsigned int src_x, src_y, dst_x, dst_y;
+ unsigned int src_stride;
+ unsigned int dst_stride;
+ unsigned int cr_pos;
+ int r, g, b, y, cr, cb;
+
+ cr_pos = (cb_pos_ + 2) % 4;
+ src_stride = width_ * 2;
+ dst_stride = width_ * 4;
+
+ for (src_y = 0, dst_y = 0; dst_y < height_; src_y++, dst_y++) {
+ for (src_x = 0, dst_x = 0; dst_x < width_; ) {
+ cb = src[src_y * src_stride + src_x * 4 + cb_pos_];
+ cr = src[src_y * src_stride + src_x * 4 + cr_pos];
+
+ y = src[src_y * src_stride + src_x * 4 + y_pos_];
+ yuv_to_rgb(y, cb, cr, &r, &g, &b);
+ dst[dst_y * dst_stride + 4 * dst_x + 0] = b;
+ dst[dst_y * dst_stride + 4 * dst_x + 1] = g;
+ dst[dst_y * dst_stride + 4 * dst_x + 2] = r;
+ dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;
+ dst_x++;
+
+ y = src[src_y * src_stride + src_x * 4 + y_pos_ + 2];
+ yuv_to_rgb(y, cb, cr, &r, &g, &b);
+ dst[dst_y * dst_stride + 4 * dst_x + 0] = b;
+ dst[dst_y * dst_stride + 4 * dst_x + 1] = g;
+ dst[dst_y * dst_stride + 4 * dst_x + 2] = r;
+ dst[dst_y * dst_stride + 4 * dst_x + 3] = 0xff;
+ dst_x++;
+
+ src_x++;
+ }
+ }
+}
diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h
new file mode 100644
index 00000000..196064c7
--- /dev/null
+++ b/src/qcam/format_converter.h
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * format_convert.h - qcam - Convert buffer to RGB
+ */
+#ifndef __QCAM_FORMAT_CONVERTER_H__
+#define __QCAM_FORMAT_CONVERTER_H__
+
+class FormatConverter
+{
+public:
+ int configure(unsigned int format, unsigned int width,
+ unsigned int height);
+
+ void convert(const unsigned char *src, unsigned char *dst);
+
+private:
+ unsigned int width_;
+ unsigned int height_;
+ unsigned int y_pos_;
+ unsigned int cb_pos_;
+};
+
+#endif /* __QCAM_FORMAT_CONVERTER_H__ */
diff --git a/src/qcam/main.cpp b/src/qcam/main.cpp
new file mode 100644
index 00000000..106b8a16
--- /dev/null
+++ b/src/qcam/main.cpp
@@ -0,0 +1,75 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main.cpp - cam - The libcamera swiss army knife
+ */
+
+#include <iostream>
+#include <signal.h>
+#include <string.h>
+
+#include <QApplication>
+
+#include <libcamera/camera_manager.h>
+
+#include "main_window.h"
+#include "../cam/options.h"
+#include "qt_event_dispatcher.h"
+
+void signalHandler(int signal)
+{
+ std::cout << "Exiting" << std::endl;
+ qApp->quit();
+}
+
+OptionsParser::Options parseOptions(int argc, char *argv[])
+{
+ OptionsParser parser;
+ parser.addOption(OptCamera, OptionString,
+ "Specify which camera to operate on", "camera",
+ ArgumentRequired, "camera");
+ parser.addOption(OptHelp, OptionNone, "Display this help message",
+ "help");
+
+ OptionsParser::Options options = parser.parse(argc, argv);
+ if (options.isSet(OptHelp))
+ parser.usage();
+
+ return options;
+}
+
+int main(int argc, char **argv)
+{
+ QApplication app(argc, argv);
+ int ret;
+
+ OptionsParser::Options options = parseOptions(argc, argv);
+ if (!options.valid())
+ return EXIT_FAILURE;
+ if (options.isSet(OptHelp))
+ return 0;
+
+ struct sigaction sa = {};
+ sa.sa_handler = &signalHandler;
+ sigaction(SIGINT, &sa, nullptr);
+
+ std::unique_ptr<EventDispatcher> dispatcher(new QtEventDispatcher());
+ CameraManager *cm = CameraManager::instance();
+ cm->setEventDispatcher(std::move(dispatcher));
+
+ ret = cm->start();
+ if (ret) {
+ std::cout << "Failed to start camera manager: "
+ << strerror(-ret) << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ MainWindow *mainWindow = new MainWindow(options);
+ mainWindow->show();
+ ret = app.exec();
+ delete mainWindow;
+
+ cm->stop();
+ return ret;
+}
diff --git a/src/qcam/main_window.cpp b/src/qcam/main_window.cpp
new file mode 100644
index 00000000..a148aa4d
--- /dev/null
+++ b/src/qcam/main_window.cpp
@@ -0,0 +1,228 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main_window.cpp - qcam - Main application window
+ */
+
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <QCoreApplication>
+#include <QInputDialog>
+#include <QTimer>
+
+#include <libcamera/camera_manager.h>
+
+#include "main_window.h"
+#include "viewfinder.h"
+
+using namespace libcamera;
+
+MainWindow::MainWindow(const OptionsParser::Options &options)
+ : options_(options), isCapturing_(false)
+{
+ int ret;
+
+ viewfinder_ = new ViewFinder(this);
+ setCentralWidget(viewfinder_);
+ viewfinder_->setFixedSize(500, 500);
+ adjustSize();
+
+ ret = openCamera();
+ if (!ret)
+ ret = startCapture();
+
+ if (ret < 0)
+ QTimer::singleShot(0, QCoreApplication::instance(),
+ &QCoreApplication::quit);
+}
+
+MainWindow::~MainWindow()
+{
+ if (camera_) {
+ stopCapture();
+ camera_->release();
+ camera_.reset();
+ }
+
+ CameraManager::instance()->stop();
+}
+
+int MainWindow::openCamera()
+{
+ CameraManager *cm = CameraManager::instance();
+ std::string cameraName;
+
+ if (!options_.isSet(OptCamera)) {
+ QStringList cameras;
+ bool result;
+
+ for (const std::shared_ptr<Camera> &cam : cm->cameras())
+ cameras.append(QString::fromStdString(cam->name()));
+
+ QString name = QInputDialog::getItem(this, "Select Camera",
+ "Camera:", cameras, 0,
+ false, &result);
+ if (!result)
+ return -EINVAL;
+
+ cameraName = name.toStdString();
+ } else {
+ cameraName = static_cast<std::string>(options_[OptCamera]);
+ }
+
+ camera_ = cm->get(cameraName);
+ if (!camera_) {
+ std::cout << "Camera " << cameraName << " not found"
+ << std::endl;
+ return -ENODEV;
+ }
+
+ if (camera_->acquire()) {
+ std::cout << "Failed to acquire camera" << std::endl;
+ camera_.reset();
+ return -EBUSY;
+ }
+
+ std::cout << "Using camera " << camera_->name() << std::endl;
+
+ camera_->requestCompleted.connect(this, &MainWindow::requestComplete);
+
+ return 0;
+}
+
+int MainWindow::startCapture()
+{
+ int ret;
+
+ Stream *stream = *camera_->streams().begin();
+ std::set<Stream *> streams{ stream };
+ config_ = camera_->streamConfiguration(streams);
+ ret = camera_->configureStreams(config_);
+ if (ret < 0) {
+ std::cout << "Failed to configure camera" << std::endl;
+ return ret;
+ }
+
+ const StreamConfiguration &sconf = config_[stream];
+ ret = viewfinder_->setFormat(sconf.pixelFormat, sconf.width, sconf.height);
+ if (ret < 0) {
+ std::cout << "Failed to set viewfinder format" << std::endl;
+ return ret;
+ }
+
+ adjustSize();
+
+ ret = camera_->allocateBuffers();
+ if (ret) {
+ std::cerr << "Failed to allocate buffers"
+ << std::endl;
+ return ret;
+ }
+
+ BufferPool &pool = stream->bufferPool();
+ std::vector<Request *> requests;
+
+ for (Buffer &buffer : pool.buffers()) {
+ Request *request = camera_->createRequest();
+ if (!request) {
+ std::cerr << "Can't create request" << std::endl;
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ std::map<Stream *, Buffer *> map;
+ map[stream] = &buffer;
+ ret = request->setBuffers(map);
+ if (ret < 0) {
+ std::cerr << "Can't set buffers for request" << std::endl;
+ goto error;
+ }
+
+ requests.push_back(request);
+ }
+
+ ret = camera_->start();
+ if (ret) {
+ std::cout << "Failed to start capture" << std::endl;
+ goto error;
+ }
+
+ for (Request *request : requests) {
+ ret = camera_->queueRequest(request);
+ if (ret < 0) {
+ std::cerr << "Can't queue request" << std::endl;
+ goto error;
+ }
+ }
+
+ isCapturing_ = true;
+ return 0;
+
+error:
+ for (Request *request : requests)
+ delete request;
+
+ camera_->freeBuffers();
+ return ret;
+}
+
+void MainWindow::stopCapture()
+{
+ if (!isCapturing_)
+ return;
+
+ int ret = camera_->stop();
+ if (ret)
+ std::cout << "Failed to stop capture" << std::endl;
+
+ camera_->freeBuffers();
+ isCapturing_ = false;
+}
+
+void MainWindow::requestComplete(Request *request,
+ const std::map<Stream *, Buffer *> &buffers)
+{
+ static uint64_t last = 0;
+
+ if (request->status() == Request::RequestCancelled)
+ return;
+
+ Buffer *buffer = buffers.begin()->second;
+
+ double fps = buffer->timestamp() - last;
+ fps = last && fps ? 1000000000.0 / fps : 0.0;
+ last = buffer->timestamp();
+
+ std::cout << "seq: " << std::setw(6) << std::setfill('0') << buffer->sequence()
+ << " buf: " << buffer->index()
+ << " bytesused: " << buffer->bytesused()
+ << " timestamp: " << buffer->timestamp()
+ << " fps: " << std::fixed << std::setprecision(2) << fps
+ << std::endl;
+
+ display(buffer);
+
+ request = camera_->createRequest();
+ if (!request) {
+ std::cerr << "Can't create request" << std::endl;
+ return;
+ }
+
+ request->setBuffers(buffers);
+ camera_->queueRequest(request);
+}
+
+int MainWindow::display(Buffer *buffer)
+{
+ if (buffer->planes().size() != 1)
+ return -EINVAL;
+
+ Plane &plane = buffer->planes().front();
+ unsigned char *raw = static_cast<unsigned char *>(plane.mem());
+ viewfinder_->display(raw);
+
+ return 0;
+}
diff --git a/src/qcam/main_window.h b/src/qcam/main_window.h
new file mode 100644
index 00000000..5e27a8fd
--- /dev/null
+++ b/src/qcam/main_window.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * main_window.h - qcam - Main application window
+ */
+#ifndef __QCAM_MAIN_WINDOW_H__
+#define __QCAM_MAIN_WINDOW_H__
+
+#include <map>
+
+#include <QMainWindow>
+
+#include <libcamera/camera.h>
+#include <libcamera/stream.h>
+
+#include "../cam/options.h"
+
+using namespace libcamera;
+
+class ViewFinder;
+
+enum {
+ OptCamera = 'c',
+ OptHelp = 'h',
+};
+
+class MainWindow : public QMainWindow
+{
+public:
+ MainWindow(const OptionsParser::Options &options);
+ ~MainWindow();
+
+private:
+ int openCamera();
+
+ int startCapture();
+ int configureStreams(Camera *camera, std::set<Stream *> &streams);
+ void stopCapture();
+
+ void requestComplete(Request *request,
+ const std::map<Stream *, Buffer *> &buffers);
+ int display(Buffer *buffer);
+
+ const OptionsParser::Options &options_;
+
+ std::shared_ptr<Camera> camera_;
+ bool isCapturing_;
+ std::map<Stream *, StreamConfiguration> config_;
+
+ ViewFinder *viewfinder_;
+};
+
+#endif /* __QCAM_MAIN_WINDOW__ */
diff --git a/src/qcam/meson.build b/src/qcam/meson.build
new file mode 100644
index 00000000..8a71cda3
--- /dev/null
+++ b/src/qcam/meson.build
@@ -0,0 +1,19 @@
+qcam_sources = files([
+ 'format_converter.cpp',
+ 'main.cpp',
+ 'main_window.cpp',
+ '../cam/options.cpp',
+ 'qt_event_dispatcher.cpp',
+ 'viewfinder.cpp',
+])
+
+import('qt5')
+qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Widgets'], required : false)
+
+if qt5_dep.found()
+ qcam = executable('qcam', qcam_sources,
+ link_with : libcamera,
+ include_directories : libcamera_includes,
+ dependencies : qt5_dep,
+ cpp_args : '-DQT_NO_KEYWORDS')
+endif
diff --git a/src/qcam/qt_event_dispatcher.cpp b/src/qcam/qt_event_dispatcher.cpp
new file mode 100644
index 00000000..5ba451bf
--- /dev/null
+++ b/src/qcam/qt_event_dispatcher.cpp
@@ -0,0 +1,145 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * qt_event_dispatcher.cpp - qcam - Qt-based event dispatcher
+ */
+
+#include <iostream>
+
+#include <QAbstractEventDispatcher>
+#include <QCoreApplication>
+#include <QSocketNotifier>
+#include <QTimerEvent>
+
+#include <libcamera/event_notifier.h>
+#include <libcamera/timer.h>
+
+#include "qt_event_dispatcher.h"
+
+using namespace libcamera;
+
+QtEventDispatcher::QtEventDispatcher()
+{
+}
+
+QtEventDispatcher::~QtEventDispatcher()
+{
+ for (auto &it : notifiers_) {
+ NotifierSet &set = it.second;
+ delete set.read.qnotifier;
+ delete set.write.qnotifier;
+ delete set.exception.qnotifier;
+ }
+}
+
+void QtEventDispatcher::registerEventNotifier(EventNotifier *notifier)
+{
+ NotifierSet &set = notifiers_[notifier->fd()];
+ QSocketNotifier::Type qtype;
+ void (QtEventDispatcher::*method)(int);
+ NotifierPair *pair;
+
+ switch (notifier->type()) {
+ case EventNotifier::Read:
+ default:
+ qtype = QSocketNotifier::Read;
+ method = &QtEventDispatcher::readNotifierActivated;
+ pair = &set.read;
+ break;
+
+ case EventNotifier::Write:
+ qtype = QSocketNotifier::Write;
+ method = &QtEventDispatcher::writeNotifierActivated;
+ pair = &set.write;
+ break;
+
+ case EventNotifier::Exception:
+ qtype = QSocketNotifier::Exception;
+ method = &QtEventDispatcher::exceptionNotifierActivated;
+ pair = &set.exception;
+ break;
+ }
+
+ QSocketNotifier *qnotifier = new QSocketNotifier(notifier->fd(), qtype);
+ connect(qnotifier, &QSocketNotifier::activated, this, method);
+ pair->notifier = notifier;
+ pair->qnotifier = qnotifier;
+}
+
+void QtEventDispatcher::unregisterEventNotifier(EventNotifier *notifier)
+{
+ NotifierSet &set = notifiers_[notifier->fd()];
+ NotifierPair *pair;
+
+ switch (notifier->type()) {
+ case EventNotifier::Read:
+ default:
+ pair = &set.read;
+ break;
+
+ case EventNotifier::Write:
+ pair = &set.write;
+ break;
+
+ case EventNotifier::Exception:
+ pair = &set.exception;
+ break;
+ }
+
+ delete pair->qnotifier;
+ pair->qnotifier = nullptr;
+ pair->notifier = nullptr;
+}
+
+void QtEventDispatcher::readNotifierActivated(int socket)
+{
+ EventNotifier *notifier = notifiers_[socket].read.notifier;
+ notifier->activated.emit(notifier);
+}
+
+void QtEventDispatcher::writeNotifierActivated(int socket)
+{
+ EventNotifier *notifier = notifiers_[socket].write.notifier;
+ notifier->activated.emit(notifier);
+}
+
+void QtEventDispatcher::exceptionNotifierActivated(int socket)
+{
+ EventNotifier *notifier = notifiers_[socket].exception.notifier;
+ notifier->activated.emit(notifier);
+}
+
+void QtEventDispatcher::registerTimer(Timer *timer)
+{
+ int timerId = startTimer(timer->interval());
+ timers_[timerId] = timer;
+ timerIds_[timer] = timerId;
+}
+
+void QtEventDispatcher::unregisterTimer(Timer *timer)
+{
+ auto it = timerIds_.find(timer);
+ timers_.erase(it->second);
+ killTimer(it->second);
+ timerIds_.erase(it);
+}
+
+void QtEventDispatcher::timerEvent(QTimerEvent *event)
+{
+ auto it = timers_.find(event->timerId());
+ timerIds_.erase(it->second);
+ killTimer(it->first);
+ timers_.erase(it);
+}
+
+void QtEventDispatcher::processEvents()
+{
+ std::cout << "QtEventDispatcher::processEvents() should not be called"
+ << std::endl;
+}
+
+void QtEventDispatcher::interrupt()
+{
+ QCoreApplication::eventDispatcher()->interrupt();
+}
diff --git a/src/qcam/qt_event_dispatcher.h b/src/qcam/qt_event_dispatcher.h
new file mode 100644
index 00000000..b0f123e5
--- /dev/null
+++ b/src/qcam/qt_event_dispatcher.h
@@ -0,0 +1,62 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * qt_event_dispatcher.h - qcam - Qt-based event dispatcher
+ */
+#ifndef __QCAM_QT_EVENT_DISPATCHER_H__
+#define __QCAM_QT_EVENT_DISPATCHER_H__
+
+#include <map>
+
+#include <libcamera/event_dispatcher.h>
+
+using namespace libcamera;
+
+class QSocketNotifier;
+
+class QtEventDispatcher final : public EventDispatcher, public QObject
+{
+public:
+ QtEventDispatcher();
+ ~QtEventDispatcher();
+
+ void registerEventNotifier(EventNotifier *notifier);
+ void unregisterEventNotifier(EventNotifier *notifier);
+
+ void registerTimer(Timer *timer);
+ void unregisterTimer(Timer *timer);
+
+ void processEvents();
+
+ void interrupt();
+
+protected:
+ void timerEvent(QTimerEvent *event);
+
+private:
+ void readNotifierActivated(int socket);
+ void writeNotifierActivated(int socket);
+ void exceptionNotifierActivated(int socket);
+
+ struct NotifierPair {
+ NotifierPair()
+ : notifier(nullptr), qnotifier(nullptr)
+ {
+ }
+ EventNotifier *notifier;
+ QSocketNotifier *qnotifier;
+ };
+
+ struct NotifierSet {
+ NotifierPair read;
+ NotifierPair write;
+ NotifierPair exception;
+ };
+
+ std::map<int, NotifierSet> notifiers_;
+ std::map<int, Timer *> timers_;
+ std::map<Timer *, int> timerIds_;
+};
+
+#endif /* __QCAM_QT_EVENT_DISPATCHER_H__ */
diff --git a/src/qcam/viewfinder.cpp b/src/qcam/viewfinder.cpp
new file mode 100644
index 00000000..5841dc03
--- /dev/null
+++ b/src/qcam/viewfinder.cpp
@@ -0,0 +1,46 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * viewfinder.cpp - qcam - Viewfinder
+ */
+
+#include <QImage>
+#include <QPixmap>
+
+#include "format_converter.h"
+#include "viewfinder.h"
+
+ViewFinder::ViewFinder(QWidget *parent)
+ : QLabel(parent), format_(0), width_(0), height_(0), image_(nullptr)
+{
+}
+
+void ViewFinder::display(const unsigned char *raw)
+{
+ converter_.convert(raw, image_->bits());
+
+ QPixmap pixmap = QPixmap::fromImage(*image_);
+ setPixmap(pixmap);
+}
+
+int ViewFinder::setFormat(unsigned int format, unsigned int width,
+ unsigned int height)
+{
+ int ret;
+
+ ret = converter_.configure(format, width, height);
+ if (ret < 0)
+ return ret;
+
+ format_ = format;
+ width_ = width;
+ height_ = height;
+
+ setFixedSize(width, height);
+
+ delete image_;
+ image_ = new QImage(width, height, QImage::Format_RGB32);
+
+ return 0;
+}
diff --git a/src/qcam/viewfinder.h b/src/qcam/viewfinder.h
new file mode 100644
index 00000000..df490169
--- /dev/null
+++ b/src/qcam/viewfinder.h
@@ -0,0 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2019, Google Inc.
+ *
+ * viewfinder.h - qcam - Viewfinder
+ */
+#ifndef __QCAM_VIEWFINDER_H__
+#define __QCAM_VIEWFINDER_H__
+
+#include <QLabel>
+
+#include "format_converter.h"
+
+class QImage;
+
+class ViewFinder : public QLabel
+{
+public:
+ ViewFinder(QWidget *parent);
+
+ int setFormat(unsigned int format, unsigned int width,
+ unsigned int height);
+ void display(const unsigned char *rgb);
+
+private:
+ unsigned int format_;
+ unsigned int width_;
+ unsigned int height_;
+
+ FormatConverter converter_;
+ QImage *image_;
+};
+
+#endif /* __QCAM_VIEWFINDER__ */