new file mode 100644
@@ -0,0 +1,79 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#include "lsc.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include <libcamera/base/log.h>
+
+#include "awb.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftLsc)
+
+int Lsc::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData)
+{
+ int ret_r = lscR.readYaml(tuningData["grids"], "ct", "r");
+ int ret_g = lscG.readYaml(tuningData["grids"], "ct", "g");
+ int ret_b = lscB.readYaml(tuningData["grids"], "ct", "b");
+
+ if (ret_r < 0 || ret_g < 0 || ret_b < 0) {
+ LOG(IPASoftLsc, Error)
+ << "Failed to parse 'lsc' parameter from tuning file.";
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+int Lsc::configure([[maybe_unused]] IPAContext &context,
+ [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+ return 0;
+}
+
+void Lsc::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
+ [[maybe_unused]] IPAFrameContext &frameContext, DebayerParams *params)
+{
+ unsigned int ct =
+ context.activeState.awb.temperatureK.value_or(kDefaultTemperature);
+
+ const LscMatrix matrixR = lscR.getInterpolated(ct);
+ const LscMatrix matrixG = lscG.getInterpolated(ct);
+ const LscMatrix matrixB = lscB.getInterpolated(ct);
+
+ /*
+ * The constructed table is compressed by converting from floats to bytes.
+ * This makes the texture uploaded to a GPU smaller and we don't have to
+ * deal with textures containing float values.
+ * The byte range 0..255 represents floating point values 1.0..4.0. Values
+ * outside this range are clamped. When accessed in the shader, the byte
+ * range is represented by 0.0..1.0 range. Then the resulting pixel value
+ * can be computed as
+ * rgb + rgb * 3.0 * LUT_VALUE
+ */
+ DebayerParams::LscLookupTable lut;
+ constexpr unsigned int gridSize = DebayerParams::kLscGridSize;
+ auto float2byte = [](float factor) -> uint8_t {
+ return std::round((std::clamp(factor, 1.0f, 4.0f) - 1.0) / 3.0 * 255);
+ };
+ for (unsigned int i = 0, j = 0; i < gridSize * gridSize; i++) {
+ lut[j++] = float2byte(matrixR.data()[i]);
+ lut[j++] = float2byte(matrixG.data()[i]);
+ lut[j++] = float2byte(matrixB.data()[i]);
+ }
+ params->lscLut = lut;
+}
+
+REGISTER_IPA_ALGORITHM(Lsc, "Lsc")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
new file mode 100644
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Lens shading correction
+ */
+
+#pragma once
+
+#include "libcamera/internal/matrix.h"
+
+#include <libipa/interpolator.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Lsc : public Algorithm
+{
+public:
+ Lsc() = default;
+ ~Lsc() = default;
+
+ int init(IPAContext &context, const YamlObject &tuningData) override;
+ int configure(IPAContext &context,
+ const IPAConfigInfo &configInfo) override;
+ void prepare(IPAContext &context,
+ const uint32_t frame,
+ IPAFrameContext &frameContext,
+ DebayerParams *params) override;
+
+private:
+ using LscMatrix = Matrix<float, DebayerParams::kLscGridSize, DebayerParams::kLscGridSize>;
+ Interpolator<LscMatrix> lscR;
+ Interpolator<LscMatrix> lscG;
+ Interpolator<LscMatrix> lscB;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
@@ -6,4 +6,5 @@ soft_simple_ipa_algorithms = files([
'agc.cpp',
'blc.cpp',
'ccm.cpp',
+ 'lsc.cpp',
])