summaryrefslogtreecommitdiff
path: root/src/ipa/raspberrypi/controller/rpi/geq.cpp
blob: ee0cb95d2389e4df9f745e7d7836f1576ca03cba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* SPDX-License-Identifier: BSD-2-Clause */
/*
 * Copyright (C) 2019, Raspberry Pi (Trading) Limited
 *
 * geq.cpp - GEQ (green equalisation) control algorithm
 */

#include "../device_status.h"
#include "../logging.hpp"
#include "../lux_status.h"
#include "../pwl.hpp"

#include "geq.hpp"

using namespace RPi;

// We use the lux status so that we can apply stronger settings in darkness (if
// necessary).

#define NAME "rpi.geq"

Geq::Geq(Controller *controller)
	: Algorithm(controller)
{
}

char const *Geq::Name() const
{
	return NAME;
}

void Geq::Read(boost::property_tree::ptree const &params)
{
	config_.offset = params.get<uint16_t>("offset", 0);
	config_.slope = params.get<double>("slope", 0.0);
	if (config_.slope < 0.0 || config_.slope >= 1.0)
		throw std::runtime_error("Geq: bad slope value");
	if (params.get_child_optional("strength"))
		config_.strength.Read(params.get_child("strength"));
}

void Geq::Prepare(Metadata *image_metadata)
{
	LuxStatus lux_status = {};
	lux_status.lux = 400;
	if (image_metadata->Get("lux.status", lux_status))
		RPI_WARN("Geq: no lux data found");
	DeviceStatus device_status = {};
	device_status.analogue_gain = 1.0; // in case not found
	if (image_metadata->Get("device.status", device_status))
		RPI_WARN("Geq: no device metadata - use analogue gain of 1x");
	GeqStatus geq_status = {};
	double strength =
		config_.strength.Empty()
			? 1.0
			: config_.strength.Eval(config_.strength.Domain().Clip(
				  lux_status.lux));
	strength *= device_status.analogue_gain;
	double offset = config_.offset * strength;
	double slope = config_.slope * strength;
	geq_status.offset = std::min(65535.0, std::max(0.0, offset));
	geq_status.slope = std::min(.99999, std::max(0.0, slope));
	RPI_LOG("Geq: offset " << geq_status.offset << " slope "
			       << geq_status.slope << " (analogue gain "
			       << device_status.analogue_gain << " lux "
			       << lux_status.lux << ")");
	image_metadata->Set("geq.status", geq_status);
}

// Register algorithm with the system.
static Algorithm *Create(Controller *controller)
{
	return (Algorithm *)new Geq(controller);
}
static RegisterAlgorithm reg(NAME, &Create);
id='n406' href='#n406'>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
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2022, Ideas on Board Oy
 *
 * capture_script.cpp - Capture session configuration script
 */

#include "capture_script.h"

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

using namespace libcamera;

CaptureScript::CaptureScript(std::shared_ptr<Camera> camera,
			     const std::string &fileName)
	: camera_(camera), valid_(false)
{
	FILE *fh = fopen(fileName.c_str(), "r");
	if (!fh) {
		int ret = -errno;
		std::cerr << "Failed to open capture script " << fileName
			  << ": " << strerror(-ret) << std::endl;
		return;
	}

	/*
	 * Map the camera's controls to their name so that they can be
	 * easily identified when parsing the script file.
	 */
	for (const auto &[control, info] : camera_->controls())
		controls_[control->name()] = control;

	int ret = parseScript(fh);
	fclose(fh);
	if (ret)
		return;

	valid_ = true;
}

/* Retrieve the control list associated with a frame number. */
const ControlList &CaptureScript::frameControls(unsigned int frame)
{
	static ControlList controls{};

	auto it = frameControls_.find(frame);
	if (it == frameControls_.end())
		return controls;

	return it->second;
}

CaptureScript::EventPtr CaptureScript::nextEvent(yaml_event_type_t expectedType)
{
	EventPtr event(new yaml_event_t);

	if (!yaml_parser_parse(&parser_, event.get()))
		return nullptr;

	if (expectedType != YAML_NO_EVENT && !checkEvent(event, expectedType))
		return nullptr;

	return event;
}

bool CaptureScript::checkEvent(const EventPtr &event, yaml_event_type_t expectedType) const
{
	if (event->type != expectedType) {
		std::cerr << "Capture script error on line " << event->start_mark.line
			  << " column " << event->start_mark.column << ": "
			  << "Expected " << eventTypeName(expectedType)
			  << " event, got " << eventTypeName(event->type)
			  << std::endl;
		return false;
	}

	return true;
}

std::string CaptureScript::eventScalarValue(const EventPtr &event)
{
	return std::string(reinterpret_cast<char *>(event->data.scalar.value),
			   event->data.scalar.length);
}

std::string CaptureScript::eventTypeName(yaml_event_type_t type)
{
	static const std::map<yaml_event_type_t, std::string> typeNames = {
		{ YAML_STREAM_START_EVENT, "stream-start" },
		{ YAML_STREAM_END_EVENT, "stream-end" },
		{ YAML_DOCUMENT_START_EVENT, "document-start" },
		{ YAML_DOCUMENT_END_EVENT, "document-end" },
		{ YAML_ALIAS_EVENT, "alias" },
		{ YAML_SCALAR_EVENT, "scalar" },
		{ YAML_SEQUENCE_START_EVENT, "sequence-start" },
		{ YAML_SEQUENCE_END_EVENT, "sequence-end" },
		{ YAML_MAPPING_START_EVENT, "mapping-start" },
		{ YAML_MAPPING_END_EVENT, "mapping-end" },
	};

	auto it = typeNames.find(type);
	if (it == typeNames.end())
		return "[type " + std::to_string(type) + "]";

	return it->second;
}

int CaptureScript::parseScript(FILE *script)
{
	int ret = yaml_parser_initialize(&parser_);
	if (!ret) {
		std::cerr << "Failed to initialize yaml parser" << std::endl;
		return ret;
	}

	/* Delete the parser upon function exit. */
	struct ParserDeleter {
		ParserDeleter(yaml_parser_t *parser) : parser_(parser) { }
		~ParserDeleter() { yaml_parser_delete(parser_); }
		yaml_parser_t *parser_;
	} deleter(&parser_);

	yaml_parser_set_input_file(&parser_, script);

	EventPtr event = nextEvent(YAML_STREAM_START_EVENT);
	if (!event)
		return -EINVAL;

	event = nextEvent(YAML_DOCUMENT_START_EVENT);
	if (!event)
		return -EINVAL;

	event = nextEvent(YAML_MAPPING_START_EVENT);
	if (!event)
		return -EINVAL;

	while (1) {
		event = nextEvent();
		if (!event)
			return -EINVAL;

		if (event->type == YAML_MAPPING_END_EVENT)
			return 0;

		if (!checkEvent(event, YAML_SCALAR_EVENT))
			return -EINVAL;

		std::string section = eventScalarValue(event);

		if (section == "frames") {
			parseFrames();
		} else {
			std::cerr << "Unsupported section '" << section << "'"
				  << std::endl;
			return -EINVAL;
		}
	}
}

int CaptureScript::parseFrames()
{
	EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
	if (!event)
		return -EINVAL;

	while (1) {
		event = nextEvent();
		if (!event)
			return -EINVAL;

		if (event->type == YAML_SEQUENCE_END_EVENT)
			return 0;

		int ret = parseFrame(std::move(event));
		if (ret)
			return ret;
	}
}

int CaptureScript::parseFrame(EventPtr event)
{
	if (!checkEvent(event, YAML_MAPPING_START_EVENT))
		return -EINVAL;

	std::string key = parseScalar();
	if (key.empty())
		return -EINVAL;

	unsigned int frameId = atoi(key.c_str());

	event = nextEvent(YAML_MAPPING_START_EVENT);
	if (!event)
		return -EINVAL;

	ControlList controls{};

	while (1) {
		event = nextEvent();
		if (!event)
			return -EINVAL;

		if (event->type == YAML_MAPPING_END_EVENT)
			break;

		int ret = parseControl(std::move(event), controls);
		if (ret)
			return ret;
	}

	frameControls_[frameId] = std::move(controls);

	event = nextEvent(YAML_MAPPING_END_EVENT);
	if (!event)
		return -EINVAL;

	return 0;
}

int CaptureScript::parseControl(EventPtr event, ControlList &controls)
{
	/* We expect a value after a key. */
	std::string name = eventScalarValue(event);
	if (name.empty())
		return -EINVAL;

	/* If the camera does not support the control just ignore it. */
	auto it = controls_.find(name);
	if (it == controls_.end()) {
		std::cerr << "Unsupported control '" << name << "'" << std::endl;
		return -EINVAL;
	}

	const ControlId *controlId = it->second;

	ControlValue val = unpackControl(controlId);
	if (val.isNone()) {
		std::cerr << "Error unpacking control '" << name << "'"
			  << std::endl;
		return -EINVAL;
	}

	controls.set(controlId->id(), val);

	return 0;
}

std::string CaptureScript::parseScalar()
{
	EventPtr event = nextEvent(YAML_SCALAR_EVENT);
	if (!event)
		return "";

	return eventScalarValue(event);
}

ControlValue CaptureScript::parseRectangles()
{
	std::vector<libcamera::Rectangle> rectangles;

	std::vector<std::vector<std::string>> arrays = parseArrays();
	if (arrays.empty())
		return {};

	for (const std::vector<std::string> &values : arrays) {
		if (values.size() != 4) {
			std::cerr << "Error parsing Rectangle: expected "
				  << "array with 4 parameters" << std::endl;
			return {};
		}

		Rectangle rect = unpackRectangle(values);
		rectangles.push_back(rect);
	}

	ControlValue controlValue;
	controlValue.set(Span<const Rectangle>(rectangles));

	return controlValue;
}

std::vector<std::vector<std::string>> CaptureScript::parseArrays()
{
	EventPtr event = nextEvent(YAML_SEQUENCE_START_EVENT);
	if (!event)
		return {};

	event = nextEvent();
	if (!event)
		return {};

	std::vector<std::vector<std::string>> valueArrays;

	/* Parse single array. */
	if (event->type == YAML_SCALAR_EVENT) {
		std::string firstValue = eventScalarValue(event);
		if (firstValue.empty())
			return {};

		std::vector<std::string> remaining = parseSingleArray();

		std::vector<std::string> values = { firstValue };
		values.insert(std::end(values),
			      std::begin(remaining), std::end(remaining));
		valueArrays.push_back(values);

		return valueArrays;
	}

	/* Parse array of arrays. */
	while (1) {
		switch (event->type) {
		case YAML_SEQUENCE_START_EVENT: {
			std::vector<std::string> values = parseSingleArray();
			valueArrays.push_back(values);
			break;
		}
		case YAML_SEQUENCE_END_EVENT:
			return valueArrays;
		default:
			return {};
		}

		event = nextEvent();
		if (!event)
			return {};
	}
}

std::vector<std::string> CaptureScript::parseSingleArray()
{
	std::vector<std::string> values;

	while (1) {
		EventPtr event = nextEvent();
		if (!event)
			return {};

		switch (event->type) {
		case YAML_SCALAR_EVENT: {
			std::string value = eventScalarValue(event);
			if (value.empty())
				return {};
			values.push_back(value);
			break;
		}
		case YAML_SEQUENCE_END_EVENT:
			return values;
		default:
			return {};
		}
	}
}

void CaptureScript::unpackFailure(const ControlId *id, const std::string &repr)
{
	static const std::map<unsigned int, const char *> typeNames = {
		{ ControlTypeNone, "none" },
		{ ControlTypeBool, "bool" },
		{ ControlTypeByte, "byte" },
		{ ControlTypeInteger32, "int32" },
		{ ControlTypeInteger64, "int64" },
		{ ControlTypeFloat, "float" },
		{ ControlTypeString, "string" },
		{ ControlTypeRectangle, "Rectangle" },
		{ ControlTypeSize, "Size" },
	};

	const char *typeName;
	auto it = typeNames.find(id->type());
	if (it != typeNames.end())
		typeName = it->second;
	else
		typeName = "unknown";

	std::cerr << "Unsupported control '" << repr << "' for "
		  << typeName << " control " << id->name() << std::endl;
}

ControlValue CaptureScript::unpackControl(const ControlId *id)
{
	/* Parse complex types. */
	switch (id->type()) {
	case ControlTypeRectangle:
		return parseRectangles();
	case ControlTypeSize:
		/* \todo Parse Sizes. */
		return {};
	default:
		break;
	}

	/* Parse basic types represented by a single scalar. */
	const std::string repr = parseScalar();
	if (repr.empty())
		return {};

	ControlValue value{};

	switch (id->type()) {
	case ControlTypeNone:
		break;
	case ControlTypeBool: {
		bool val;

		if (repr == "true") {
			val = true;
		} else if (repr == "false") {
			val = false;
		} else {
			unpackFailure(id, repr);
			return value;
		}

		value.set<bool>(val);
		break;
	}
	case ControlTypeByte: {
		uint8_t val = strtol(repr.c_str(), NULL, 10);
		value.set<uint8_t>(val);
		break;
	}
	case ControlTypeInteger32: {
		int32_t val = strtol(repr.c_str(), NULL, 10);
		value.set<int32_t>(val);
		break;
	}
	case ControlTypeInteger64: {
		int64_t val = strtoll(repr.c_str(), NULL, 10);