[libcamera-devel] [PATCH 2/2] android: Plumb tonemap curves

Paul Elder paul.elder at ideasonboard.com
Tue Dec 21 06:28:54 CET 2021


Plumb everything related to tonemaps:
- checking for manual post processing capability
- static metadata: set available modes
- static metadata: enable keys (if the libcamera camera supports
  tonemapping)
- fill out template fields
- converting between the android tonemap controls and libcamera tonemap
  controls (for request and result)

Notably, for sending the tonemap in the request, we build the tonemap
incrementally. That is, if the red, green, and blue parts of the curve
are passed in separate requests, we "pool" them together into a single
curve and pass it to the camera. As libcamera will have its own request
control caching, we only send the tonemap curve to libcamera when at
least one of the three curves is provided.

Signed-off-by: Paul Elder <paul.elder at ideasonboard.com>
---
 src/android/camera_capabilities.cpp | 113 +++++++++++++++++++
 src/android/camera_device.cpp       | 162 ++++++++++++++++++++++++++++
 src/android/camera_device.h         |   3 +
 3 files changed, 278 insertions(+)

diff --git a/src/android/camera_capabilities.cpp b/src/android/camera_capabilities.cpp
index cb5ea5e9..2ce465aa 100644
--- a/src/android/camera_capabilities.cpp
+++ b/src/android/camera_capabilities.cpp
@@ -281,6 +281,8 @@ bool CameraCapabilities::validateManualSensorCapability()
 
 bool CameraCapabilities::validateManualPostProcessingCapability()
 {
+	camera_metadata_ro_entry_t entry;
+
 	const char *noMode = "Manual post processing capability unavailable: ";
 
 	if (!staticMetadata_->entryContains<uint8_t>(ANDROID_CONTROL_AWB_AVAILABLE_MODES,
@@ -307,6 +309,28 @@ bool CameraCapabilities::validateManualPostProcessingCapability()
 		return false;
 	}
 
+	bool found = staticMetadata_->getEntry(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES, &entry);
+	if (!found) {
+		LOG(HAL, Info) << noMode << "missing tonemapping";
+		return false;
+	}
+
+	std::set<uint8_t> tonemapModes;
+	for (unsigned int i = 0; i < entry.count; i++)
+		tonemapModes.insert(entry.data.u8[i]);
+
+	if ((!tonemapModes.count(ANDROID_TONEMAP_MODE_CONTRAST_CURVE) &&
+	     !(tonemapModes.count(ANDROID_TONEMAP_MODE_GAMMA_VALUE) &&
+	       tonemapModes.count(ANDROID_TONEMAP_MODE_PRESET_CURVE))) ||
+	    !tonemapModes.count(ANDROID_TONEMAP_MODE_FAST) ||
+	    !tonemapModes.count(ANDROID_TONEMAP_MODE_HIGH_QUALITY)) {
+		LOG(HAL, Info)
+			<< noMode
+			<< "tonemap modes must contain at least {contrast, fast, hq} "
+			<< "or {gamma, preset, fast, hq}";
+		return false;
+	}
+
 	/*
 	 * \todo return true here after we satisfy all the requirements:
 	 * https://developer.android.com/reference/android/hardware/camera2/CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING
@@ -1165,6 +1189,77 @@ int CameraCapabilities::initializeStaticMetadata()
 		availableResultKeys_.insert(ANDROID_EDGE_MODE);
 	}
 
+	std::vector<uint8_t> availableTonemapModes;
+	bool tonemapGammaSupported = false;
+	bool tonemapPresetSupported = false;
+	const auto tonemapModeInfo = controlsInfo.find(&controls::TonemapMode);
+	if (tonemapModeInfo != controlsInfo.end()) {
+		for (const auto &value : tonemapModeInfo->second.values()) {
+			uint8_t mode;
+			switch (value.get<int32_t>()) {
+			case controls::TonemapModeContrastCurve:
+				mode = ANDROID_TONEMAP_MODE_CONTRAST_CURVE;
+				break;
+			case controls::TonemapModeFast:
+				mode = ANDROID_TONEMAP_MODE_FAST;
+				break;
+			case controls::TonemapModeHighQuality:
+				mode = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
+				break;
+			case controls::TonemapModeGammaValue:
+				mode = ANDROID_TONEMAP_MODE_GAMMA_VALUE;
+				tonemapGammaSupported = true;
+				break;
+			case controls::TonemapModePresetCurve:
+				mode = ANDROID_TONEMAP_MODE_PRESET_CURVE;
+				tonemapPresetSupported = true;
+				break;
+			default:
+				LOG(HAL, Error) << "Unknown tonemap mode";
+				continue;
+			}
+			availableTonemapModes.push_back(mode);
+		}
+	}
+
+	/* \todo Figure out how to report TonemapCurve ControlInfo */
+	const auto tonemapSizeInfo = controlsInfo.find(&controls::TonemapCurveSize);
+	if (!availableTonemapModes.empty() &&
+	    (tonemapSizeInfo != controlsInfo.end())) {
+		/* Available tonemap modes */
+		staticMetadata_->addEntry(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES,
+					  availableTonemapModes);
+		availableCharacteristicsKeys_.insert(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES);
+
+		/* Tonemap size */
+		int32_t tonemapMaxCurvePoints = tonemapSizeInfo->second.max().get<int32_t>();
+		staticMetadata_->addEntry(ANDROID_TONEMAP_MAX_CURVE_POINTS,
+					  tonemapMaxCurvePoints);
+		availableCharacteristicsKeys_.insert(ANDROID_TONEMAP_MAX_CURVE_POINTS);
+
+		/* Tonemap mode */
+		availableRequestKeys_.insert(ANDROID_TONEMAP_MODE);
+		availableResultKeys_.insert(ANDROID_TONEMAP_MODE);
+
+		/* Tonemap curve */
+		availableRequestKeys_.insert(ANDROID_TONEMAP_CURVE_RED);
+		availableRequestKeys_.insert(ANDROID_TONEMAP_CURVE_GREEN);
+		availableRequestKeys_.insert(ANDROID_TONEMAP_CURVE_BLUE);
+		availableResultKeys_.insert(ANDROID_TONEMAP_CURVE_RED);
+		availableResultKeys_.insert(ANDROID_TONEMAP_CURVE_GREEN);
+		availableResultKeys_.insert(ANDROID_TONEMAP_CURVE_BLUE);
+
+		if (tonemapGammaSupported) {
+			availableRequestKeys_.insert(ANDROID_TONEMAP_GAMMA);
+			availableResultKeys_.insert(ANDROID_TONEMAP_GAMMA);
+		}
+
+		if (tonemapPresetSupported) {
+			availableRequestKeys_.insert(ANDROID_TONEMAP_PRESET_CURVE);
+			availableResultKeys_.insert(ANDROID_TONEMAP_PRESET_CURVE);
+		}
+	}
+
 	/* JPEG static metadata. */
 
 	/*
@@ -1894,6 +1989,12 @@ std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplatePreview() con
 		requestTemplate->addEntry(ANDROID_SHADING_MODE, shadingMode);
 	}
 
+	if (staticMetadata_->entryContains<uint8_t>(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES,
+						    ANDROID_TONEMAP_MODE_FAST)) {
+		uint8_t tonemapMode = ANDROID_TONEMAP_MODE_FAST;
+		requestTemplate->addEntry(ANDROID_TONEMAP_MODE, tonemapMode);
+	}
+
 	return requestTemplate;
 }
 
@@ -1922,6 +2023,12 @@ std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplateStill() const
 		stillTemplate->appendEntry(ANDROID_NOISE_REDUCTION_MODE, noiseReduction);
 	}
 
+	if (staticMetadata_->entryContains<uint8_t>(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES,
+						    ANDROID_TONEMAP_MODE_HIGH_QUALITY)) {
+		uint8_t tonemapMode = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
+		stillTemplate->appendEntry(ANDROID_TONEMAP_MODE, tonemapMode);
+	}
+
 	return stillTemplate;
 }
 
@@ -1945,6 +2052,12 @@ std::unique_ptr<CameraMetadata> CameraCapabilities::requestTemplateVideo() const
 		previewTemplate->appendEntry(ANDROID_EDGE_MODE, edgeMode);
 	}
 
+	if (staticMetadata_->entryContains<uint8_t>(ANDROID_TONEMAP_AVAILABLE_TONE_MAP_MODES,
+						    ANDROID_TONEMAP_MODE_FAST)) {
+		uint8_t tonemapMode = ANDROID_TONEMAP_MODE_FAST;
+		previewTemplate->appendEntry(ANDROID_TONEMAP_MODE, tonemapMode);
+	}
+
 	/*
 	 * Assume the AE_AVAILABLE_TARGET_FPS_RANGE static metadata
 	 * has been assembled as {{min, max} {max, max}}.
diff --git a/src/android/camera_device.cpp b/src/android/camera_device.cpp
index 7be0ce45..7cffb4b1 100644
--- a/src/android/camera_device.cpp
+++ b/src/android/camera_device.cpp
@@ -1014,6 +1014,95 @@ int CameraDevice::processControls(Camera3RequestDescriptor *descriptor)
 		controls.set(controls::LensShadingMode, shadingMode);
 	}
 
+	if (settings.getEntry(ANDROID_TONEMAP_MODE, &entry)) {
+		const int32_t data = static_cast<int32_t>(*entry.data.u8);
+		int32_t tonemapMode;
+		switch (data) {
+		case ANDROID_TONEMAP_MODE_CONTRAST_CURVE:
+			tonemapMode = controls::TonemapModeContrastCurve;
+			break;
+		case ANDROID_TONEMAP_MODE_FAST:
+			tonemapMode = controls::TonemapModeFast;
+			break;
+		case ANDROID_TONEMAP_MODE_HIGH_QUALITY:
+			tonemapMode = controls::TonemapModeHighQuality;
+			break;
+		case ANDROID_TONEMAP_MODE_GAMMA_VALUE:
+			tonemapMode = controls::TonemapModeGammaValue;
+			break;
+		case ANDROID_TONEMAP_MODE_PRESET_CURVE:
+			tonemapMode = controls::TonemapModePresetCurve;
+			break;
+		default:
+			LOG(HAL, Error)
+				<< "Unknown tonemap mode: " << data;
+			return -EINVAL;
+		}
+
+		controls.set(controls::TonemapMode, tonemapMode);
+	}
+
+	bool curveUpdated = false;
+	tonemapCurve.resize(3);
+	static std::map<camera_metadata_tag, unsigned int> curveTags = {
+		{ ANDROID_TONEMAP_CURVE_RED,   0 },
+		{ ANDROID_TONEMAP_CURVE_GREEN, 1 },
+		{ ANDROID_TONEMAP_CURVE_BLUE,  2 },
+	};
+
+	for (const std::pair<camera_metadata_tag, unsigned int> &tag : curveTags) {
+		if (!settings.getEntry(tag.first, &entry))
+			continue;
+
+		tonemapCurve[tag.second].resize(entry.count);
+		for (unsigned int i = 0; i < entry.count; i++)
+			tonemapCurve[tag.second][i] = *(entry.data.f + i);
+
+		curveUpdated = true;
+	}
+
+	if (curveUpdated) {
+		size_t size = std::max(tonemapCurve[0].size(),
+				       tonemapCurve[1].size());
+		size = std::max(size, tonemapCurve[2].size());
+		controls.set(controls::TonemapCurveSize, size);
+
+		std::vector<float> curve;
+		curve.resize(3 * size);
+		curve.insert(curve.begin(),
+			     tonemapCurve[0].begin(), tonemapCurve[0].end());
+		curve.insert(curve.begin() + size,
+			     tonemapCurve[1].begin(), tonemapCurve[1].end());
+		curve.insert(curve.begin() + 2 * size,
+			     tonemapCurve[2].begin(), tonemapCurve[2].end());
+		controls.set(controls::TonemapCurve, curve);
+	}
+
+	if (settings.getEntry(ANDROID_TONEMAP_GAMMA, &entry)) {
+		const float data = *entry.data.f;
+		controls.set(controls::TonemapGamma, data);
+	}
+
+	if (settings.getEntry(ANDROID_TONEMAP_PRESET_CURVE, &entry)) {
+		const int32_t data = static_cast<int32_t>(*entry.data.u8);
+		int32_t presetCurve;
+		switch (data) {
+		case ANDROID_TONEMAP_PRESET_CURVE_SRGB:
+			presetCurve = controls::TonemapPresetCurveSRGB;
+			break;
+		case ANDROID_TONEMAP_PRESET_CURVE_REC709:
+			presetCurve = controls::TonemapPresetCurveREC709;
+			break;
+		default:
+			LOG(HAL, Error)
+				<< "Unknown tonemap mode: " << data;
+			return -EINVAL;
+		}
+
+		controls.set(controls::TonemapPresetCurve, presetCurve);
+
+	}
+
 	return 0;
 }
 
@@ -1812,6 +1901,79 @@ CameraDevice::getResultMetadata(const Camera3RequestDescriptor &descriptor) cons
 		}
 	}
 
+	if (metadata.contains(controls::TonemapMode)) {
+		bool valid;
+		switch (metadata.get(controls::TonemapMode)) {
+		case controls::TonemapModeContrastCurve:
+			value = ANDROID_TONEMAP_MODE_CONTRAST_CURVE;
+			valid = true;
+			break;
+		case controls::TonemapModeFast:
+			value = ANDROID_TONEMAP_MODE_FAST;
+			valid = true;
+			break;
+		case controls::TonemapModeHighQuality:
+			value = ANDROID_TONEMAP_MODE_HIGH_QUALITY;
+			valid = true;
+			break;
+		case controls::TonemapModeGammaValue:
+			value = ANDROID_TONEMAP_MODE_GAMMA_VALUE;
+			valid = true;
+			break;
+		case controls::TonemapModePresetCurve:
+			value = ANDROID_TONEMAP_MODE_PRESET_CURVE;
+			valid = true;
+			break;
+		default:
+			LOG(HAL, Error) << "Invalid tonemap mode";
+			valid = false;
+		}
+
+		/* Can be null on non-FULL */
+		if (valid)
+			resultMetadata->addEntry(ANDROID_TONEMAP_MODE, value);
+	}
+
+	if (metadata.contains(controls::TonemapCurve) &&
+	    metadata.contains(controls::TonemapCurveSize)) {
+		size_t size = metadata.get(controls::TonemapCurveSize);
+
+		Span<const float> curve = metadata.get(controls::TonemapCurve);
+		Span<const float> red = curve.subspan(0, size);
+		Span<const float> green = curve.subspan(size, size);
+		Span<const float> blue = curve.subspan(2 * size, size);
+
+		resultMetadata->addEntry(ANDROID_TONEMAP_CURVE_RED, red);
+		resultMetadata->addEntry(ANDROID_TONEMAP_CURVE_GREEN, green);
+		resultMetadata->addEntry(ANDROID_TONEMAP_CURVE_BLUE, blue);
+	}
+
+	if (metadata.contains(controls::TonemapGamma)) {
+		float gamma = metadata.get(controls::TonemapGamma);
+		resultMetadata->addEntry(ANDROID_TONEMAP_GAMMA, gamma);
+	}
+
+	if (metadata.contains(controls::TonemapPresetCurve)) {
+		bool valid;
+		switch (metadata.get(controls::TonemapPresetCurve)) {
+		case controls::TonemapPresetCurveSRGB:
+			value = ANDROID_TONEMAP_PRESET_CURVE_SRGB;
+			valid = false;
+			break;
+		case controls::TonemapPresetCurveREC709:
+			value = ANDROID_TONEMAP_PRESET_CURVE_REC709;
+			valid = false;
+			break;
+		default:
+			LOG(HAL, Error) << "Invalid tonemap preset curve";
+			valid = false;
+		}
+
+		/* Can be null on non-FULL */
+		if (valid)
+			resultMetadata->addEntry(ANDROID_TONEMAP_PRESET_CURVE, value);
+	}
+
 	/*
 	 * Return the result metadata pack even is not valid: get() will return
 	 * nullptr.
diff --git a/src/android/camera_device.h b/src/android/camera_device.h
index 01c269d3..2cab11c5 100644
--- a/src/android/camera_device.h
+++ b/src/android/camera_device.h
@@ -140,5 +140,8 @@ private:
 	float lastAnalogueGain_;
 	float lastDigitalGain_;
 
+	/* Build the tonemap curve incrementally */
+	std::vector<std::vector<float>> tonemapCurve;
+
 	CameraMetadata lastSettings_;
 };
-- 
2.27.0



More information about the libcamera-devel mailing list