Files
external_libcamera/src/ipa/simple/algorithms/lut.cpp
T
Bryan O'Donoghue a1a6253ff9 libcamera: software_isp: debayer: Latch contrastExp not contrast to debayer parameters
Pass contrastExp as calculated in lut to debayer params not the raw
contrast. This way we calculate contrastExp once per frame in lut and pass
the calculated value into the shaders, instead of passing contrast and
calculating contrastExp once per pixel in the shaders.

Signed-off-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org>
Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
Tested-by: Robert Mader <robert.mader@collabora.com>
Tested-by: Hans de Goede <johannes.goede@oss.qualcomm.com> # ThinkPad T14s gen 6 (arm64) ov02c10 + X1c gen 12 ov08x40
Tested-by: Kieran Bingham <kieran.bingham@ideasonboard.com> # Lenovo X13s
Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
2026-01-07 17:02:57 +00:00

172 lines
5.5 KiB
C++

/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2024-2025, Red Hat Inc.
*
* Color lookup tables construction
*/
#include "lut.h"
#include <algorithm>
#include <cmath>
#include <optional>
#include <stdint.h>
#include <libcamera/base/log.h>
#include <libcamera/control_ids.h>
#include "simple/ipa_context.h"
namespace libcamera {
LOG_DEFINE_CATEGORY(IPASoftLut)
namespace ipa::soft::algorithms {
int Lut::init(IPAContext &context,
[[maybe_unused]] const YamlObject &tuningData)
{
context.ctrlMap[&controls::Contrast] = ControlInfo(0.0f, 2.0f, 1.0f);
return 0;
}
int Lut::configure(IPAContext &context,
[[maybe_unused]] const IPAConfigInfo &configInfo)
{
/* Gamma value is fixed */
context.configuration.gamma = 0.5;
context.activeState.knobs.contrast = std::optional<double>();
updateGammaTable(context);
return 0;
}
void Lut::queueRequest(typename Module::Context &context,
[[maybe_unused]] const uint32_t frame,
[[maybe_unused]] typename Module::FrameContext &frameContext,
const ControlList &controls)
{
const auto &contrast = controls.get(controls::Contrast);
if (contrast.has_value()) {
context.activeState.knobs.contrast = contrast;
LOG(IPASoftLut, Debug) << "Setting contrast to " << contrast.value();
}
}
void Lut::updateGammaTable(IPAContext &context)
{
auto &gammaTable = context.activeState.gamma.gammaTable;
const auto blackLevel = context.activeState.blc.level;
const unsigned int blackIndex = blackLevel * gammaTable.size() / 256;
const auto contrast = context.activeState.knobs.contrast.value_or(1.0);
/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */
double contrastExp = tan(std::clamp(contrast * M_PI_4, 0.0, M_PI_2 - 0.00001));
const float divisor = gammaTable.size() - blackIndex - 1.0;
for (unsigned int i = blackIndex; i < gammaTable.size(); i++) {
double normalized = (i - blackIndex) / divisor;
/* Convert 0..2 to 0..infinity; avoid actual inifinity at tan(pi/2) */
/* Apply simple S-curve */
if (normalized < 0.5)
normalized = 0.5 * std::pow(normalized / 0.5, contrastExp);
else
normalized = 1.0 - 0.5 * std::pow((1.0 - normalized) / 0.5, contrastExp);
gammaTable[i] = UINT8_MAX *
std::pow(normalized, context.configuration.gamma);
}
/*
* Due to CCM operations, the table lookup may reach indices below the black
* level. Let's set the table values below black level to the minimum
* non-black value to prevent problems when the minimum value is
* significantly non-zero (for example, when the image should be all grey).
*/
std::fill(gammaTable.begin(), gammaTable.begin() + blackIndex,
gammaTable[blackIndex]);
context.activeState.gamma.blackLevel = blackLevel;
context.activeState.gamma.contrastExp = contrastExp;
}
int16_t Lut::ccmValue(unsigned int i, float ccm) const
{
return std::round(i * ccm);
}
void Lut::prepare(IPAContext &context,
[[maybe_unused]] const uint32_t frame,
IPAFrameContext &frameContext,
DebayerParams *params)
{
frameContext.contrast = context.activeState.knobs.contrast;
/*
* Update the gamma table if needed. This means if black level changes
* and since the black level gets updated only if a lower value is
* observed, it's not permanently prone to minor fluctuations or
* rounding errors.
*/
const bool gammaUpdateNeeded =
context.activeState.gamma.blackLevel != context.activeState.blc.level ||
context.activeState.gamma.contrast != context.activeState.knobs.contrast;
if (gammaUpdateNeeded)
updateGammaTable(context);
auto &gains = context.activeState.awb.gains;
auto &gammaTable = context.activeState.gamma.gammaTable;
const unsigned int gammaTableSize = gammaTable.size();
const double div = static_cast<double>(DebayerParams::kRGBLookupSize) /
gammaTableSize;
if (!context.ccmEnabled) {
for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
/* Apply gamma after gain! */
const RGB<float> lutGains = (gains * i / div).min(gammaTableSize - 1);
params->red[i] = gammaTable[static_cast<unsigned int>(lutGains.r())];
params->green[i] = gammaTable[static_cast<unsigned int>(lutGains.g())];
params->blue[i] = gammaTable[static_cast<unsigned int>(lutGains.b())];
}
} else if (context.activeState.ccm.changed || gammaUpdateNeeded) {
Matrix<float, 3, 3> gainCcm = { { gains.r(), 0, 0,
0, gains.g(), 0,
0, 0, gains.b() } };
auto ccm = context.activeState.ccm.ccm * gainCcm;
auto &red = params->redCcm;
auto &green = params->greenCcm;
auto &blue = params->blueCcm;
params->ccm = ccm;
for (unsigned int i = 0; i < DebayerParams::kRGBLookupSize; i++) {
red[i].r = ccmValue(i, ccm[0][0]);
red[i].g = ccmValue(i, ccm[1][0]);
red[i].b = ccmValue(i, ccm[2][0]);
green[i].r = ccmValue(i, ccm[0][1]);
green[i].g = ccmValue(i, ccm[1][1]);
green[i].b = ccmValue(i, ccm[2][1]);
blue[i].r = ccmValue(i, ccm[0][2]);
blue[i].g = ccmValue(i, ccm[1][2]);
blue[i].b = ccmValue(i, ccm[2][2]);
params->gammaLut[i] = gammaTable[i / div];
}
}
params->gamma = context.configuration.gamma;
params->contrastExp = context.activeState.gamma.contrastExp;
}
void Lut::process([[maybe_unused]] IPAContext &context,
[[maybe_unused]] const uint32_t frame,
[[maybe_unused]] IPAFrameContext &frameContext,
[[maybe_unused]] const SwIspStats *stats,
ControlList &metadata)
{
const auto &contrast = frameContext.contrast;
if (contrast)
metadata.set(controls::Contrast, contrast.value());
}
REGISTER_IPA_ALGORITHM(Lut, "Lut")
} /* namespace ipa::soft::algorithms */
} /* namespace libcamera */