Coding Style Guidelines ======================= The libcamera project has high standards of stability, efficiency and reliability. To achieve those, the project goes to great length to produce code that is as easy to read, understand and maintain as possible. These coding guidelines are meant to ensure code quality. As a contributor you are expected to follow them in all code submitted to the project. While strict compliance is desired, exceptions are tolerated when justified with good reasons. Please read the whole coding guidelines and use common sense to decide when departing from them is appropriate. libcamera is written in C++, a language that has seen many revisions and offers an extensive set of features that are easy to abuse. These coding guidelines establish the subset of C++ used by the project. Coding Style ------------ Even if the programming language in use is different, the project embraces the `Linux Kernel Coding Style`_ with a few exception and some C++ specificities. .. _Linux Kernel Coding Style: https://www.kernel.org/doc/html/latest/process/coding-style.html In particular, from the kernel style document, the following section are adopted: * 1 "Indentation" * 2 "Breaking Long Lines" striving to fit code within 80 columns and accepting up to 120 columns when necessary * 3 "Placing Braces and Spaces" * 3.1 "Spaces" * 8 "Commenting" with the exception that in-function comments are not always un-welcome. While libcamera uses the kernel coding style for all typographic matters, the project is a user space library, developed in a different programming language, and the kernel guidelines fall short for this use case. For this reason, rules and guidelines from the `Google C++ Style Guide`_ have been adopted as well as most coding principles specified therein, with a few exceptions and relaxed limitations on some subjects. .. _Google C++ Style Guide: https://google.github.io/styleguide/cppguide.html The following exceptions apply to the naming conventions specified in the document: * File names: libcamera uses the .cpp extensions for C++ source files and the .h extension for header files * Variables, function parameters, function names and class members use camel case style, with the first letter in lower-case (as in 'camelCase' and not 'CamelCase') * Types (classes, structs, type aliases, and type template parameters) use camel case, with the first letter in capital case (as in 'CamelCase' and not 'camelCase') * Enum members use 'CamelCase', while macros are in capital case with underscores in between * All formatting rules specified in the selected sections of the Linux kernel Code Style for indentation, braces, spacing, etc * Header guards are formatted as '__LIBCAMERA_FILE_NAME_H__' C++ Specific Rules ------------------ The code shall be implemented in C++03, extended with the following C++-11-specific features: * Initializer lists * Type inference (auto and decltype) Type inference shall be used with caution, to avoid drifting towards an untyped language. * Range-based for loop * Lambda functions * Explicit overrides and final * Null pointer constant * General-purpose smart pointers (std::unique_ptr), deprecating std::auto_ptr. Smart pointers, as well as shared pointers and weak pointers, shall not be overused. * Variadic class and function templates * rvalue references, move constructor and move assignment Object Ownership ~~~~~~~~~~~~~~~~ libcamera creates and destroys many objects at runtime, for both objects internal to the library and objects exposed to the user. To guarantee proper operation without use after free, double free or memory leaks, knowing who owns each object at any time is crucial. The project has enacted a set of rules to make object ownership tracking as explicit and fool-proof as possible. In the context of this section, the terms object and instance are used interchangeably and both refer to an instance of a class. The term reference refers to both C++ references and C++ pointers in their capacity to refer to an object. Passing a reference means offering a way to a callee to obtain a reference to an object that the caller has a valid reference to. Borrowing a reference means using a reference passed by a caller without ownership transfer based on the assumption that the caller guarantees the validity of the reference for the duration of the operation that borrows it. #. Single Owner Objects * By default an object has a single owner at any time. * Storage of single owner objects varies depending on how the object ownership will evolve through the lifetime of the object. * Objects whose ownership needs to be transferred shall be stored as std::unique_ptr<> as much as possible to emphasize the single ownership. * Objects whose owner doesn't change may be embedded in other objects, or stored as pointer or references. They may be stored as std::unique_ptr<> for automatic deletion if desired. * Ownership is transferred by passing the reference as a std::unique_ptr<> and using std::move(). After ownership transfer the former owner has no valid reference to the object anymore and shall not access it without first obtaining a valid reference. * Objects may be borrowed by passing an object reference from the owner to the borrower, providing that * the owner guarantees the validity of the reference for the whole duration of the borrowing, and * the borrower doesn't access the reference after the end of the borrowing. When borrowing from caller to callee for the duration of a function call, this implies that the callee shall not keep any stored reference after it returns. These rules apply to the callee and all the functions it calls, directly or indirectly. When the object is stored in a std::unique_ptr<>, borrowing passes a reference to the object, not to the std::unique_ptr<>, as * a 'const &' when the object doesn't need to be modified and may not be null. * a pointer when the object may be modified or may be null. Unless otherwise specified, pointers passed to functions are considered as borrowed references valid for the duration of the function only. #. Shared Objects * Objects that may have multiple owners at a given time are called shared objects. They are reference-counted and live as long as any references to the object exist. * Shared objects are created with std::make_shared<> or std::allocate_shared<> and stored in an std::shared_ptr<>. * Ownership is shared by creating and passing copies of any valid std::shared_ptr<>. Ownership is released by destroying the corresponding std::shared_ptr<>. * When passed to a function, std::shared_ptr<> are always passed by value, never by reference. The caller can decide whether to transfer its ownership of the std::shared_ptr<> with std::move() or retain it. The callee shall use std::move() if it needs to store the shared pointer. * Do not over-use std::move(), as it may prevent copy-elision. In particular a function returning a std::shared_ptr<> value shall not use std::move() in its return statements, and its callers shall not wrap the function call with std::move(). * Borrowed references to shared objects are passed as references to the objects themselves, not to the std::shared_ptr<>, with the same rules as for single owner objects. These rules match the `object ownership rules from the Chromium C++ Style Guide`_. .. _object ownership rules from the Chromium C++ Style Guide: https://chromium.googlesource.com/chromium/src/+/master/styleguide/c++/c++.md#object-ownership-and-calling-conventions .. attention:: Long term borrowing of single owner objects is allowed. Example use cases are implementation of the singleton pattern (where the singleton guarantees the validity of the reference forever), or returning references to global objects whose lifetime matches the lifetime of the application. As long term borrowing isn't marked through language constructs, it shall be documented explicitly in details in the API. Tools ----- The 'clang-format' code formatting tool can be used to reformat source files with the libcamera coding style, defined in the .clang-format file at the root of the source tree. Alternatively the 'astyle' tool can also be used, with the following arguments. :: --style=linux --indent=force-tab=8 --attach-namespaces --attach-extern-c --pad-oper --align-pointer=name --align-reference=name --max-code-length=120 Use of astyle is discouraged as clang-format better matches the libcamera coding style. As both astyle and clang-format are code formatters, they operate on full files and output reformatted source code. While they can be used to reformat code before sending patches, it may generate unrelated changes. To avoid this, libcamera provides a 'checkstyle.py' script wrapping the formatting tools to only retain related changes. This should be used to validate modifications before submitting them for review. The script operates on one or multiple git commits specified on the command line. It does not modify the git tree, the index or the working directory and is thus safe to run at any point. Commits are specified using the same revision range syntax as 'git log'. The most usual use cases are to specify a single commit by sha1, branch name or tag name, or a commit range with the .. syntax. When no arguments are given, the topmost commit of the current branch is selected. :: $ ./utils/checkstyle.py cc7d204b2c51 ---------------------------------------------------------------------------------- cc7d204b2c51853f7d963d144f5944e209e7ea29 libcamera: Use the logger instead of cout ---------------------------------------------------------------------------------- No style issue detected When operating on a range of commits, style checks are performed on each commit from oldest to newest. :: $ ../utils/checkstyle.py 3b56ddaa96fb~3..3b56ddaa96fb ---------------------------------------------------------------------------------- b4351e1a6b83a9cfbfc331af3753602a02dbe062 libcamera: log: Fix Doxygen documentation ---------------------------------------------------------------------------------- No style issue detected -------------------------------------------------------------------------------------- 6ab3ff4501fcfa24db40fcccbce35bdded7cd4bc libcamera: log: Document the LogMessage class -------------------------------------------------------------------------------------- No style issue detected --------------------------------------------------------------------------------- 3b56ddaa96fbccf4eada05d378ddaa1cb6209b57 build: Add 'std=c++11' cpp compiler flag --------------------------------------------------------------------------------- Commit doesn't touch source files, skipping Commits that do not touch any .c, .cpp or .h files are skipped. :: $ ./utils/checkstyle.py edbd2059d8a4 ---------------------------------------------------------------------- edbd2059d8a4bd759302ada4368fa4055638fd7f libcamera: Add initial logger ---------------------------------------------------------------------- --- src/libcamera/include/log.h +++ src/libcamera/include/log.h @@ -21,11 +21,14 @@ { public: LogMessage(const char *fileName, unsigned int line, - LogSeverity severity); - LogMessage(const LogMessage&) = delete; + LogSeverity severity); + LogMessage(const LogMessage &) = delete; ~LogMessage(); - std::ostream& stream() { return msgStream; } + std::ostream &stream() + { + return msgStream; + } private: std::ostringstream msgStream; --- src/libcamera/log.cpp +++ src/libcamera/log.cpp @@ -42,7 +42,7 @@ static const char *log_severity_name(LogSeverity severity) { - static const char * const names[] = { + static const char *const names[] = { "INFO", "WARN", " ERR", --- 2 potential style issues detected, please review When potential style issues are detected, they are displayed in the form of a diff that fixes the issues, on top of the corresponding commit. As the script is in early development false positive are expected. The flagged issues should be reviewed, but the diff doesn't need to be applied blindly. The checkstyle.py script uses clang-format by default if found, and otherwise falls back to astyle. The formatter can be manually selected with the '--formatter' argument. Happy hacking, libcamera awaits your patches! href='#n295'>295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2019, Google Inc.
 *
 * main_window.cpp - qcam - Main application window
 */

#include "main_window.h"

#include <iomanip>
#include <string>
#include <sys/mman.h>

#include <QComboBox>
#include <QCoreApplication>
#include <QFileDialog>
#include <QImage>
#include <QImageWriter>
#include <QInputDialog>
#include <QMutexLocker>
#include <QStandardPaths>
#include <QTimer>
#include <QToolBar>
#include <QToolButton>
#include <QtDebug>

#include <libcamera/camera_manager.h>
#include <libcamera/version.h>

#include "dng_writer.h"

using namespace libcamera;

#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
/*
 * Qt::fixed was introduced in v5.14, and ::fixed deprecated in v5.15. Allow
 * usage of Qt::fixed unconditionally.
 */
namespace Qt {
constexpr auto fixed = ::fixed;
} /* namespace Qt */
#endif

/**
 * \brief Custom QEvent to signal capture completion
 */
class CaptureEvent : public QEvent
{
public:
	CaptureEvent()
		: QEvent(type())
	{
	}

	static Type type()
	{
		static int type = QEvent::registerEventType();
		return static_cast<Type>(type);
	}
};

MainWindow::MainWindow(CameraManager *cm, const OptionsParser::Options &options)
	: saveRaw_(nullptr), options_(options), cm_(cm), allocator_(nullptr),
	  isCapturing_(false), captureRaw_(false)
{
	int ret;

	/*
	 * Initialize the UI: Create the toolbar, set the window title and
	 * create the viewfinder widget.
	 */
	createToolbars();

	title_ = "QCam " + QString::fromStdString(CameraManager::version());
	setWindowTitle(title_);
	connect(&titleTimer_, SIGNAL(timeout()), this, SLOT(updateTitle()));

	viewfinder_ = new ViewFinder(this);
	connect(viewfinder_, &ViewFinder::renderComplete,
		this, &MainWindow::queueRequest);
	setCentralWidget(viewfinder_);
	adjustSize();

	/* Open the camera and start capture. */
	ret = openCamera();
	if (ret < 0) {
		quit();
		return;
	}

	startStopAction_->setChecked(true);
}

MainWindow::~MainWindow()
{
	if (camera_) {
		stopCapture();
		camera_->release();
		camera_.reset();
	}
}

bool MainWindow::event(QEvent *e)
{
	if (e->type() == CaptureEvent::type()) {
		processCapture();
		return true;
	}

	return QMainWindow::event(e);
}

int MainWindow::createToolbars()
{
	QAction *action;

	toolbar_ = addToolBar("Main");

	/* Disable right click context menu. */
	toolbar_->setContextMenuPolicy(Qt::PreventContextMenu);

	/* Quit action. */
	action = toolbar_->addAction(QIcon::fromTheme("application-exit",
						      QIcon(":x-circle.svg")),
				     "Quit");
	action->setShortcut(Qt::CTRL | Qt::Key_Q);
	connect(action, &QAction::triggered, this, &MainWindow::quit);

	/* Camera selector. */
	cameraCombo_ = new QComboBox();
	connect(cameraCombo_, QOverload<int>::of(&QComboBox::activated),
		this, &MainWindow::switchCamera);

	for (const std::shared_ptr<Camera> &cam : cm_->cameras())
		cameraCombo_->addItem(QString::fromStdString(cam->name()));

	toolbar_->addWidget(cameraCombo_);

	toolbar_->addSeparator();

	/* Start/Stop action. */
	iconPlay_ = QIcon::fromTheme("media-playback-start",
				     QIcon(":play-circle.svg"));
	iconStop_ = QIcon::fromTheme("media-playback-stop",
				     QIcon(":stop-circle.svg"));

	action = toolbar_->addAction(iconPlay_, "Start Capture");
	action->setCheckable(true);
	action->setShortcut(Qt::Key_Space);
	connect(action, &QAction::toggled, this, &MainWindow::toggleCapture);
	startStopAction_ = action;

	/* Save As... action. */
	action = toolbar_->addAction(QIcon::fromTheme("document-save-as",
						      QIcon(":save.svg")),
				     "Save As...");
	action->setShortcut(QKeySequence::SaveAs);
	connect(action, &QAction::triggered, this, &MainWindow::saveImageAs);

#ifdef HAVE_DNG
	/* Save Raw action. */
	action = toolbar_->addAction(QIcon::fromTheme("camera-photo",
						      QIcon(":aperture.svg")),
				     "Save Raw");
	action->setEnabled(false);
	connect(action, &QAction::triggered, this, &MainWindow::captureRaw);
	saveRaw_ = action;
#endif

	return 0;
}

void MainWindow::quit()
{
	QTimer::singleShot(0, QCoreApplication::instance(),
			   &QCoreApplication::quit);
}

void MainWindow::updateTitle()
{
	/* Calculate the average frame rate over the last period. */
	unsigned int duration = frameRateInterval_.elapsed();
	unsigned int frames = framesCaptured_ - previousFrames_;
	double fps = frames * 1000.0 / duration;

	/* Restart counters. */
	frameRateInterval_.start();
	previousFrames_ = framesCaptured_;

	setWindowTitle(title_ + " : " + QString::number(fps, 'f', 2) + " fps");
}

/* -----------------------------------------------------------------------------
 * Camera Selection
 */

void MainWindow::switchCamera(int index)
{
	/* Get and acquire the new camera. */
	const auto &cameras = cm_->cameras();
	if (static_cast<unsigned int>(index) >= cameras.size())
		return;

	const std::shared_ptr<Camera> &cam = cameras[index];

	if (cam->acquire()) {
		qInfo() << "Failed to acquire camera" << cam->name().c_str();
		return;
	}

	qInfo() << "Switching to camera" << cam->name().c_str();

	/*
	 * Stop the capture session, release the current camera, replace it with
	 * the new camera and start a new capture session.
	 */
	startStopAction_->setChecked(false);

	camera_->release();
	camera_ = cam;

	startStopAction_->setChecked(true);
}

std::string MainWindow::chooseCamera()
{
	QStringList cameras;
	bool result;

	/* If only one camera is available, use it automatically. */
	if (cm_->cameras().size() == 1)
		return cm_->cameras()[0]->name();

	/* Present a dialog box to pick a camera. */
	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 std::string();

	return name.toStdString();
}

int MainWindow::openCamera()
{
	std::string cameraName;

	/*
	 * Use the camera specified on the command line, if any, or display the
	 * camera selection dialog box otherwise.
	 */
	if (options_.isSet(OptCamera))
		cameraName = static_cast<std::string>(options_[OptCamera]);
	else
		cameraName = chooseCamera();

	if (cameraName == "")
		return -EINVAL;

	/* Get and acquire the camera. */
	camera_ = cm_->get(cameraName);
	if (!camera_) {
		qInfo() << "Camera" << cameraName.c_str() << "not found";
		return -ENODEV;
	}

	if (camera_->acquire()) {
		qInfo() << "Failed to acquire camera";
		camera_.reset();
		return -EBUSY;
	}

	/* Set the combo-box entry with the currently selected Camera. */
	cameraCombo_->setCurrentText(QString::fromStdString(cameraName));

	return 0;
}

/* -----------------------------------------------------------------------------
 * Capture Start & Stop
 */

void MainWindow::toggleCapture(bool start)
{
	if (start) {
		startCapture();
		startStopAction_->setIcon(iconStop_);
		startStopAction_->setText("Stop Capture");
	} else {
		stopCapture();
		startStopAction_->setIcon(iconPlay_);
		startStopAction_->setText("Start Capture");
	}
}

/**
 * \brief Start capture with the current camera
 *
 * This function shall not be called directly, use toggleCapture() instead.
 */
int MainWindow::startCapture()
{
	StreamRoles roles = StreamKeyValueParser::roles(options_[OptStream]);
	std::vector<Request *> requests;
	int ret;

	/* Verify roles are supported. */
	switch (roles.size()) {
	case 1:
		if (roles[0] != StreamRole::Viewfinder) {
			qWarning() << "Only viewfinder supported for single stream";
			return -EINVAL;
		}
		break;
	case 2:
		if (roles[0] != StreamRole::Viewfinder ||
		    roles[1] != StreamRole::StillCaptureRaw) {
			qWarning() << "Only viewfinder + raw supported for dual streams";
			return -EINVAL;
		}
		break;
	default:
		if (roles.size() != 1) {
			qWarning() << "Unsuported stream configuration";
			return -EINVAL;
		}
		break;
	}

	/* Configure the camera. */
	config_ = camera_->generateConfiguration(roles);
	if (!config_) {
		qWarning() << "Failed to generate configuration from roles";
		return -EINVAL;
	}

	StreamConfiguration &vfConfig = config_->at(0);

	/* Use a format supported by the viewfinder if available. */
	std::vector<PixelFormat> formats = vfConfig.formats().pixelformats();
	for (const PixelFormat &format : viewfinder_->nativeFormats()) {
		auto match = std::find_if(formats.begin(), formats.end(),
					  [&](const PixelFormat &f) {
						  return f == format;
					  });
		if (match != formats.end()) {
			vfConfig.pixelFormat = format;
			break;
		}
	}

	/* Allow user to override configuration. */
	if (StreamKeyValueParser::updateConfiguration(config_.get(),
						      options_[OptStream])) {
		qWarning() << "Failed to update configuration";
		return -EINVAL;
	}

	CameraConfiguration::Status validation = config_->validate();
	if (validation == CameraConfiguration::Invalid) {
		qWarning() << "Failed to create valid camera configuration";
		return -EINVAL;
	}

	if (validation == CameraConfiguration::Adjusted)
		qInfo() << "Stream configuration adjusted to "
			<< vfConfig.toString().c_str();

	ret = camera_->configure(config_.get());
	if (ret < 0) {
		qInfo() << "Failed to configure camera";
		return ret;
	}

	/* Store stream allocation. */
	vfStream_ = config_->at(0).stream();
	if (config_->size() == 2)
		rawStream_ = config_->at(1).stream();
	else
		rawStream_ = nullptr;

	/* Configure the viewfinder. */
	ret = viewfinder_->setFormat(vfConfig.pixelFormat,
				     QSize(vfConfig.size.width, vfConfig.size.height));
	if (ret < 0) {
		qInfo() << "Failed to set viewfinder format";
		return ret;
	}

	adjustSize();

	/* Configure the raw capture button. */
	if (saveRaw_)
		saveRaw_->setEnabled(config_->size() == 2);

	/* Allocate and map buffers. */
	allocator_ = new FrameBufferAllocator(camera_);
	for (StreamConfiguration &config : *config_) {
		Stream *stream = config.stream();

		ret = allocator_->allocate(stream);
		if (ret < 0) {
			qWarning() << "Failed to allocate capture buffers";
			goto error;
		}

		for (const std::unique_ptr<FrameBuffer> &buffer : allocator_->buffers(stream)) {
			/* Map memory buffers and cache the mappings. */
			const FrameBuffer::Plane &plane = buffer->planes().front();
			void *memory = mmap(NULL, plane.length, PROT_READ, MAP_SHARED,
					    plane.fd.fd(), 0);
			mappedBuffers_[buffer.get()] = { memory, plane.length };

			/* Store buffers on the free list. */
			freeBuffers_[stream].enqueue(buffer.get());
		}
	}

	/* Create requests and fill them with buffers from the viewfinder. */
	while (!freeBuffers_[vfStream_].isEmpty()) {
		FrameBuffer *buffer = freeBuffers_[vfStream_].dequeue();

		Request *request = camera_->createRequest();
		if (!request) {
			qWarning() << "Can't create request";
			ret = -ENOMEM;
			goto error;
		}

		ret = request->addBuffer(vfStream_, buffer);
		if (ret < 0) {
			qWarning() << "Can't set buffer for request";
			goto error;
		}

		requests.push_back(request);
	}

	/* Start the title timer and the camera. */
	titleTimer_.start(2000);
	frameRateInterval_.start();
	previousFrames_ = 0;
	framesCaptured_ = 0;
	lastBufferTime_ = 0;

	ret = camera_->start();
	if (ret) {
		qInfo() << "Failed to start capture";
		goto error;
	}

	camera_->requestCompleted.connect(this, &MainWindow::requestComplete);

	/* Queue all requests. */
	for (Request *request : requests) {
		ret = camera_->queueRequest(request);
		if (ret < 0) {
			qWarning() << "Can't queue request";
			goto error_disconnect;
		}
	}

	isCapturing_ = true;

	return 0;

error_disconnect:
	camera_->requestCompleted.disconnect(this, &MainWindow::requestComplete);
	camera_->stop();

error:
	for (Request *request : requests)
		delete request;

	for (auto &iter : mappedBuffers_) {
		const MappedBuffer &buffer = iter.second;
		munmap(buffer.memory, buffer.size);
	}
	mappedBuffers_.clear();

	freeBuffers_.clear();

	delete allocator_;
	allocator_ = nullptr;

	return ret;
}

/**
 * \brief Stop ongoing capture
 *
 * This function may be called directly when tearing down the MainWindow. Use
 * toggleCapture() instead in all other cases.
 */
void MainWindow::stopCapture()
{
	if (!isCapturing_)
		return;

	viewfinder_->stop();
	if (saveRaw_)
		saveRaw_->setEnabled(false);
	captureRaw_ = false;

	int ret = camera_->stop();
	if (ret)
		qInfo() << "Failed to stop capture";

	camera_->requestCompleted.disconnect(this, &MainWindow::requestComplete);

	for (auto &iter : mappedBuffers_) {
		const MappedBuffer &buffer = iter.second;
		munmap(buffer.memory, buffer.size);
	}
	mappedBuffers_.clear();

	delete allocator_;

	isCapturing_ = false;

	config_.reset();

	/*
	 * A CaptureEvent may have been posted before we stopped the camera,
	 * but not processed yet. Clear the queue of done buffers to avoid
	 * racing with the event handler.
	 */
	freeBuffers_.clear();
	doneQueue_.clear();

	titleTimer_.stop();
	setWindowTitle(title_);
}

/* -----------------------------------------------------------------------------
 * Image Save
 */

void MainWindow::saveImageAs()
{
	QImage image = viewfinder_->getCurrentImage();
	QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);

	QString filename = QFileDialog::getSaveFileName(this, "Save Image", defaultPath,
							"Image Files (*.png *.jpg *.jpeg)");
	if (filename.isEmpty())
		return;

	QImageWriter writer(filename);