[v4,07/15] libcamera: ipa: simple: Separate saturation from CCM
diff mbox series

Message ID 20260122161935.208562-8-mzamazal@redhat.com
State Superseded
Headers show
Series
  • Simple pipeline IPA cleanup
Related show

Commit Message

Milan Zamazal Jan. 22, 2026, 4:19 p.m. UTC
Saturation adjustments are implemented using matrix operations.  They
are currently applied to the colour correction matrix.  Let's move them
to a newly introduced separate "Adjust" algorithm.

This separation has the following advantages:

- It allows disabling general colour adjustments algorithms without
  disabling the CCM algorithm.

- It keeps the CCM separated from other corrections.

- It's generally cleaner.

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
---
 src/ipa/simple/algorithms/adjust.cpp  | 106 ++++++++++++++++++++++++++
 src/ipa/simple/algorithms/adjust.h    |  52 +++++++++++++
 src/ipa/simple/algorithms/ccm.cpp     |  60 +--------------
 src/ipa/simple/algorithms/ccm.h       |   9 ---
 src/ipa/simple/algorithms/meson.build |   1 +
 src/ipa/simple/data/uncalibrated.yaml |   1 +
 6 files changed, 164 insertions(+), 65 deletions(-)
 create mode 100644 src/ipa/simple/algorithms/adjust.cpp
 create mode 100644 src/ipa/simple/algorithms/adjust.h

Comments

Robert Mader Jan. 26, 2026, 3:57 p.m. UTC | #1
Reviewed-by: Robert Mader <robert.mader@collabora.com>

On 22.01.26 17:19, Milan Zamazal wrote:
> Saturation adjustments are implemented using matrix operations.  They
> are currently applied to the colour correction matrix.  Let's move them
> to a newly introduced separate "Adjust" algorithm.
>
> This separation has the following advantages:
>
> - It allows disabling general colour adjustments algorithms without
>    disabling the CCM algorithm.
>
> - It keeps the CCM separated from other corrections.
>
> - It's generally cleaner.
>
> Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
> Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
> ---
>   src/ipa/simple/algorithms/adjust.cpp  | 106 ++++++++++++++++++++++++++
>   src/ipa/simple/algorithms/adjust.h    |  52 +++++++++++++
>   src/ipa/simple/algorithms/ccm.cpp     |  60 +--------------
>   src/ipa/simple/algorithms/ccm.h       |   9 ---
>   src/ipa/simple/algorithms/meson.build |   1 +
>   src/ipa/simple/data/uncalibrated.yaml |   1 +
>   6 files changed, 164 insertions(+), 65 deletions(-)
>   create mode 100644 src/ipa/simple/algorithms/adjust.cpp
>   create mode 100644 src/ipa/simple/algorithms/adjust.h
>
> diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp
> new file mode 100644
> index 000000000..8ce0631e4
> --- /dev/null
> +++ b/src/ipa/simple/algorithms/adjust.cpp
> @@ -0,0 +1,106 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024, Ideas On Board
> + * Copyright (C) 2024-2025, Red Hat Inc.
> + *
> + * Common image adjustments
> + */
> +
> +#include "adjust.h"
> +
> +#include <libcamera/base/log.h>
> +#include <libcamera/base/utils.h>
> +
> +#include <libcamera/control_ids.h>
> +
> +#include "libcamera/internal/matrix.h"
> +
> +namespace libcamera {
> +
> +namespace ipa::soft::algorithms {
> +
> +LOG_DEFINE_CATEGORY(IPASoftAdjust)
> +
> +int Adjust::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
> +{
> +	if (context.ccmEnabled)
> +		context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
> +	return 0;
> +}
> +
> +int Adjust::configure(IPAContext &context,
> +		      [[maybe_unused]] const IPAConfigInfo &configInfo)
> +{
> +	context.activeState.knobs.saturation = std::optional<double>();
> +
> +	return 0;
> +}
> +
> +void Adjust::queueRequest(typename Module::Context &context,
> +			  [[maybe_unused]] const uint32_t frame,
> +			  [[maybe_unused]] typename Module::FrameContext &frameContext,
> +			  const ControlList &controls)
> +{
> +	const auto &saturation = controls.get(controls::Saturation);
> +	if (saturation.has_value()) {
> +		context.activeState.knobs.saturation = saturation;
> +		LOG(IPASoftAdjust, Debug) << "Setting saturation to " << saturation.value();
> +	}
> +}
> +
> +void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)
> +{
> +	/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
> +	const Matrix<float, 3, 3> rgb2ycbcr{
> +		{ 0.256788235294, 0.504129411765, 0.0979058823529,
> +		  -0.148223529412, -0.290992156863, 0.439215686275,
> +		  0.439215686275, -0.367788235294, -0.0714274509804 }
> +	};
> +	const Matrix<float, 3, 3> ycbcr2rgb{
> +		{ 1.16438356164, 0, 1.59602678571,
> +		  1.16438356164, -0.391762290094, -0.812967647235,
> +		  1.16438356164, 2.01723214285, 0 }
> +	};
> +	const Matrix<float, 3, 3> saturationMatrix{
> +		{ 1, 0, 0,
> +		  0, saturation, 0,
> +		  0, 0, saturation }
> +	};
> +	matrix =
> +		ycbcr2rgb * saturationMatrix * rgb2ycbcr * matrix;
> +}
> +
> +void Adjust::prepare(IPAContext &context,
> +		     [[maybe_unused]] const uint32_t frame,
> +		     IPAFrameContext &frameContext,
> +		     [[maybe_unused]] DebayerParams *params)
> +{
> +	if (!context.ccmEnabled)
> +		return;
> +
> +	auto &saturation = context.activeState.knobs.saturation;
> +	frameContext.saturation = saturation;
> +	if (saturation)
> +		applySaturation(context.activeState.combinedMatrix, saturation.value());
> +
> +	if (saturation != lastSaturation_) {
> +		context.activeState.matrixChanged = true;
> +		lastSaturation_ = saturation;
> +	}
> +}
> +
> +void Adjust::process([[maybe_unused]] IPAContext &context,
> +		     [[maybe_unused]] const uint32_t frame,
> +		     IPAFrameContext &frameContext,
> +		     [[maybe_unused]] const SwIspStats *stats,
> +		     ControlList &metadata)
> +{
> +	const auto &saturation = frameContext.saturation;
> +	metadata.set(controls::Saturation, saturation.value_or(1.0));
> +}
> +
> +REGISTER_IPA_ALGORITHM(Adjust, "Adjust")
> +
> +} /* namespace ipa::soft::algorithms */
> +
> +} /* namespace libcamera */
> diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h
> new file mode 100644
> index 000000000..c4baa2503
> --- /dev/null
> +++ b/src/ipa/simple/algorithms/adjust.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> +/*
> + * Copyright (C) 2024-2025, Red Hat Inc.
> + *
> + * Color correction matrix
> + */
> +
> +#pragma once
> +
> +#include <optional>
> +
> +#include "libcamera/internal/matrix.h"
> +
> +#include <libipa/interpolator.h>
> +
> +#include "algorithm.h"
> +
> +namespace libcamera {
> +
> +namespace ipa::soft::algorithms {
> +
> +class Adjust : public Algorithm
> +{
> +public:
> +	Adjust() = default;
> +	~Adjust() = default;
> +
> +	int init(IPAContext &context, const YamlObject &tuningData) override;
> +	int configure(IPAContext &context,
> +		      const IPAConfigInfo &configInfo) override;
> +	void queueRequest(typename Module::Context &context,
> +			  const uint32_t frame,
> +			  typename Module::FrameContext &frameContext,
> +			  const ControlList &controls) override;
> +	void prepare(IPAContext &context,
> +		     const uint32_t frame,
> +		     IPAFrameContext &frameContext,
> +		     DebayerParams *params) override;
> +	void process(IPAContext &context, const uint32_t frame,
> +		     IPAFrameContext &frameContext,
> +		     const SwIspStats *stats,
> +		     ControlList &metadata) override;
> +
> +private:
> +	void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
> +
> +	std::optional<float> lastSaturation_;
> +};
> +
> +} /* namespace ipa::soft::algorithms */
> +
> +} /* namespace libcamera */
> diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
> index 85643645b..5576a301f 100644
> --- a/src/ipa/simple/algorithms/ccm.cpp
> +++ b/src/ipa/simple/algorithms/ccm.cpp
> @@ -1,9 +1,9 @@
>   /* SPDX-License-Identifier: LGPL-2.1-or-later */
>   /*
>    * Copyright (C) 2024, Ideas On Board
> - * Copyright (C) 2024-2025, Red Hat Inc.
> + * Copyright (C) 2024-2026, Red Hat Inc.
>    *
> - * Color correction matrix + saturation
> + * Color correction matrix
>    */
>   
>   #include "ccm.h"
> @@ -37,74 +37,25 @@ int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
>   	}
>   
>   	context.ccmEnabled = true;
> -	context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
>   
>   	return 0;
>   }
>   
> -int Ccm::configure(IPAContext &context,
> -		   [[maybe_unused]] const IPAConfigInfo &configInfo)
> -{
> -	context.activeState.knobs.saturation = std::optional<double>();
> -
> -	return 0;
> -}
> -
> -void Ccm::queueRequest(typename Module::Context &context,
> -		       [[maybe_unused]] const uint32_t frame,
> -		       [[maybe_unused]] typename Module::FrameContext &frameContext,
> -		       const ControlList &controls)
> -{
> -	const auto &saturation = controls.get(controls::Saturation);
> -	if (saturation.has_value()) {
> -		context.activeState.knobs.saturation = saturation;
> -		LOG(IPASoftCcm, Debug) << "Setting saturation to " << saturation.value();
> -	}
> -}
> -
> -void Ccm::applySaturation(Matrix<float, 3, 3> &ccm, float saturation)
> -{
> -	/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
> -	const Matrix<float, 3, 3> rgb2ycbcr{
> -		{ 0.256788235294, 0.504129411765, 0.0979058823529,
> -		  -0.148223529412, -0.290992156863, 0.439215686275,
> -		  0.439215686275, -0.367788235294, -0.0714274509804 }
> -	};
> -	const Matrix<float, 3, 3> ycbcr2rgb{
> -		{ 1.16438356164, 0, 1.59602678571,
> -		  1.16438356164, -0.391762290094, -0.812967647235,
> -		  1.16438356164, 2.01723214285, 0 }
> -	};
> -	const Matrix<float, 3, 3> saturationMatrix{
> -		{ 1, 0, 0,
> -		  0, saturation, 0,
> -		  0, 0, saturation }
> -	};
> -	ccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;
> -}
> -
>   void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
>   		  IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)
>   {
> -	auto &saturation = context.activeState.knobs.saturation;
> -
>   	const unsigned int ct = context.activeState.awb.temperatureK;
>   
> -	/* Change CCM only on saturation or bigger temperature changes. */
> +	/* Change CCM only on bigger temperature changes. */
>   	if (!currentCcm_ ||
> -	    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold ||
> -	    saturation != lastSaturation_) {
> +	    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {
>   		currentCcm_ = ccm_.getInterpolated(ct);
> -		if (saturation)
> -			applySaturation(currentCcm_.value(), saturation.value());
>   		lastCt_ = ct;
> -		lastSaturation_ = saturation;
>   		context.activeState.matrixChanged = true;
>   	}
>   
>   	context.activeState.combinedMatrix =
>   		currentCcm_.value() * context.activeState.combinedMatrix;
> -	frameContext.saturation = saturation;
>   	frameContext.ccm = currentCcm_.value();
>   }
>   
> @@ -115,9 +66,6 @@ void Ccm::process([[maybe_unused]] IPAContext &context,
>   		  ControlList &metadata)
>   {
>   	metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data());
> -
> -	const auto &saturation = frameContext.saturation;
> -	metadata.set(controls::Saturation, saturation.value_or(1.0));
>   }
>   
>   REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
> diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h
> index 867a680c3..62009a375 100644
> --- a/src/ipa/simple/algorithms/ccm.h
> +++ b/src/ipa/simple/algorithms/ccm.h
> @@ -26,12 +26,6 @@ public:
>   	~Ccm() = default;
>   
>   	int init(IPAContext &context, const YamlObject &tuningData) override;
> -	int configure(IPAContext &context,
> -		      const IPAConfigInfo &configInfo) override;
> -	void queueRequest(typename Module::Context &context,
> -			  const uint32_t frame,
> -			  typename Module::FrameContext &frameContext,
> -			  const ControlList &controls) override;
>   	void prepare(IPAContext &context,
>   		     const uint32_t frame,
>   		     IPAFrameContext &frameContext,
> @@ -42,10 +36,7 @@ public:
>   		     ControlList &metadata) override;
>   
>   private:
> -	void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
> -
>   	unsigned int lastCt_;
> -	std::optional<float> lastSaturation_;
>   	Interpolator<Matrix<float, 3, 3>> ccm_;
>   	std::optional<Matrix<float, 3, 3>> currentCcm_;
>   };
> diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
> index 2d0adb059..ebe9f20dd 100644
> --- a/src/ipa/simple/algorithms/meson.build
> +++ b/src/ipa/simple/algorithms/meson.build
> @@ -1,6 +1,7 @@
>   # SPDX-License-Identifier: CC0-1.0
>   
>   soft_simple_ipa_algorithms = files([
> +    'adjust.cpp',
>       'awb.cpp',
>       'agc.cpp',
>       'blc.cpp',
> diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml
> index 8b6df9afc..e389e0588 100644
> --- a/src/ipa/simple/data/uncalibrated.yaml
> +++ b/src/ipa/simple/data/uncalibrated.yaml
> @@ -14,6 +14,7 @@ algorithms:
>             ccm: [ 1, 0, 0,
>                    0, 1, 0,
>                    0, 0, 1]
> +  - Adjust:
>     - Lut:
>     - Agc:
>   ...

Patch
diff mbox series

diff --git a/src/ipa/simple/algorithms/adjust.cpp b/src/ipa/simple/algorithms/adjust.cpp
new file mode 100644
index 000000000..8ce0631e4
--- /dev/null
+++ b/src/ipa/simple/algorithms/adjust.cpp
@@ -0,0 +1,106 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024, Ideas On Board
+ * Copyright (C) 2024-2025, Red Hat Inc.
+ *
+ * Common image adjustments
+ */
+
+#include "adjust.h"
+
+#include <libcamera/base/log.h>
+#include <libcamera/base/utils.h>
+
+#include <libcamera/control_ids.h>
+
+#include "libcamera/internal/matrix.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+LOG_DEFINE_CATEGORY(IPASoftAdjust)
+
+int Adjust::init(IPAContext &context, [[maybe_unused]] const YamlObject &tuningData)
+{
+	if (context.ccmEnabled)
+		context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
+	return 0;
+}
+
+int Adjust::configure(IPAContext &context,
+		      [[maybe_unused]] const IPAConfigInfo &configInfo)
+{
+	context.activeState.knobs.saturation = std::optional<double>();
+
+	return 0;
+}
+
+void Adjust::queueRequest(typename Module::Context &context,
+			  [[maybe_unused]] const uint32_t frame,
+			  [[maybe_unused]] typename Module::FrameContext &frameContext,
+			  const ControlList &controls)
+{
+	const auto &saturation = controls.get(controls::Saturation);
+	if (saturation.has_value()) {
+		context.activeState.knobs.saturation = saturation;
+		LOG(IPASoftAdjust, Debug) << "Setting saturation to " << saturation.value();
+	}
+}
+
+void Adjust::applySaturation(Matrix<float, 3, 3> &matrix, float saturation)
+{
+	/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
+	const Matrix<float, 3, 3> rgb2ycbcr{
+		{ 0.256788235294, 0.504129411765, 0.0979058823529,
+		  -0.148223529412, -0.290992156863, 0.439215686275,
+		  0.439215686275, -0.367788235294, -0.0714274509804 }
+	};
+	const Matrix<float, 3, 3> ycbcr2rgb{
+		{ 1.16438356164, 0, 1.59602678571,
+		  1.16438356164, -0.391762290094, -0.812967647235,
+		  1.16438356164, 2.01723214285, 0 }
+	};
+	const Matrix<float, 3, 3> saturationMatrix{
+		{ 1, 0, 0,
+		  0, saturation, 0,
+		  0, 0, saturation }
+	};
+	matrix =
+		ycbcr2rgb * saturationMatrix * rgb2ycbcr * matrix;
+}
+
+void Adjust::prepare(IPAContext &context,
+		     [[maybe_unused]] const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     [[maybe_unused]] DebayerParams *params)
+{
+	if (!context.ccmEnabled)
+		return;
+
+	auto &saturation = context.activeState.knobs.saturation;
+	frameContext.saturation = saturation;
+	if (saturation)
+		applySaturation(context.activeState.combinedMatrix, saturation.value());
+
+	if (saturation != lastSaturation_) {
+		context.activeState.matrixChanged = true;
+		lastSaturation_ = saturation;
+	}
+}
+
+void Adjust::process([[maybe_unused]] IPAContext &context,
+		     [[maybe_unused]] const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     [[maybe_unused]] const SwIspStats *stats,
+		     ControlList &metadata)
+{
+	const auto &saturation = frameContext.saturation;
+	metadata.set(controls::Saturation, saturation.value_or(1.0));
+}
+
+REGISTER_IPA_ALGORITHM(Adjust, "Adjust")
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/adjust.h b/src/ipa/simple/algorithms/adjust.h
new file mode 100644
index 000000000..c4baa2503
--- /dev/null
+++ b/src/ipa/simple/algorithms/adjust.h
@@ -0,0 +1,52 @@ 
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ * Copyright (C) 2024-2025, Red Hat Inc.
+ *
+ * Color correction matrix
+ */
+
+#pragma once
+
+#include <optional>
+
+#include "libcamera/internal/matrix.h"
+
+#include <libipa/interpolator.h>
+
+#include "algorithm.h"
+
+namespace libcamera {
+
+namespace ipa::soft::algorithms {
+
+class Adjust : public Algorithm
+{
+public:
+	Adjust() = default;
+	~Adjust() = default;
+
+	int init(IPAContext &context, const YamlObject &tuningData) override;
+	int configure(IPAContext &context,
+		      const IPAConfigInfo &configInfo) override;
+	void queueRequest(typename Module::Context &context,
+			  const uint32_t frame,
+			  typename Module::FrameContext &frameContext,
+			  const ControlList &controls) override;
+	void prepare(IPAContext &context,
+		     const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     DebayerParams *params) override;
+	void process(IPAContext &context, const uint32_t frame,
+		     IPAFrameContext &frameContext,
+		     const SwIspStats *stats,
+		     ControlList &metadata) override;
+
+private:
+	void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
+
+	std::optional<float> lastSaturation_;
+};
+
+} /* namespace ipa::soft::algorithms */
+
+} /* namespace libcamera */
diff --git a/src/ipa/simple/algorithms/ccm.cpp b/src/ipa/simple/algorithms/ccm.cpp
index 85643645b..5576a301f 100644
--- a/src/ipa/simple/algorithms/ccm.cpp
+++ b/src/ipa/simple/algorithms/ccm.cpp
@@ -1,9 +1,9 @@ 
 /* SPDX-License-Identifier: LGPL-2.1-or-later */
 /*
  * Copyright (C) 2024, Ideas On Board
- * Copyright (C) 2024-2025, Red Hat Inc.
+ * Copyright (C) 2024-2026, Red Hat Inc.
  *
- * Color correction matrix + saturation
+ * Color correction matrix
  */
 
 #include "ccm.h"
@@ -37,74 +37,25 @@  int Ccm::init([[maybe_unused]] IPAContext &context, const YamlObject &tuningData
 	}
 
 	context.ccmEnabled = true;
-	context.ctrlMap[&controls::Saturation] = ControlInfo(0.0f, 2.0f, 1.0f);
 
 	return 0;
 }
 
-int Ccm::configure(IPAContext &context,
-		   [[maybe_unused]] const IPAConfigInfo &configInfo)
-{
-	context.activeState.knobs.saturation = std::optional<double>();
-
-	return 0;
-}
-
-void Ccm::queueRequest(typename Module::Context &context,
-		       [[maybe_unused]] const uint32_t frame,
-		       [[maybe_unused]] typename Module::FrameContext &frameContext,
-		       const ControlList &controls)
-{
-	const auto &saturation = controls.get(controls::Saturation);
-	if (saturation.has_value()) {
-		context.activeState.knobs.saturation = saturation;
-		LOG(IPASoftCcm, Debug) << "Setting saturation to " << saturation.value();
-	}
-}
-
-void Ccm::applySaturation(Matrix<float, 3, 3> &ccm, float saturation)
-{
-	/* https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion */
-	const Matrix<float, 3, 3> rgb2ycbcr{
-		{ 0.256788235294, 0.504129411765, 0.0979058823529,
-		  -0.148223529412, -0.290992156863, 0.439215686275,
-		  0.439215686275, -0.367788235294, -0.0714274509804 }
-	};
-	const Matrix<float, 3, 3> ycbcr2rgb{
-		{ 1.16438356164, 0, 1.59602678571,
-		  1.16438356164, -0.391762290094, -0.812967647235,
-		  1.16438356164, 2.01723214285, 0 }
-	};
-	const Matrix<float, 3, 3> saturationMatrix{
-		{ 1, 0, 0,
-		  0, saturation, 0,
-		  0, 0, saturation }
-	};
-	ccm = ycbcr2rgb * saturationMatrix * rgb2ycbcr * ccm;
-}
-
 void Ccm::prepare(IPAContext &context, [[maybe_unused]] const uint32_t frame,
 		  IPAFrameContext &frameContext, [[maybe_unused]] DebayerParams *params)
 {
-	auto &saturation = context.activeState.knobs.saturation;
-
 	const unsigned int ct = context.activeState.awb.temperatureK;
 
-	/* Change CCM only on saturation or bigger temperature changes. */
+	/* Change CCM only on bigger temperature changes. */
 	if (!currentCcm_ ||
-	    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold ||
-	    saturation != lastSaturation_) {
+	    utils::abs_diff(ct, lastCt_) >= kTemperatureThreshold) {
 		currentCcm_ = ccm_.getInterpolated(ct);
-		if (saturation)
-			applySaturation(currentCcm_.value(), saturation.value());
 		lastCt_ = ct;
-		lastSaturation_ = saturation;
 		context.activeState.matrixChanged = true;
 	}
 
 	context.activeState.combinedMatrix =
 		currentCcm_.value() * context.activeState.combinedMatrix;
-	frameContext.saturation = saturation;
 	frameContext.ccm = currentCcm_.value();
 }
 
@@ -115,9 +66,6 @@  void Ccm::process([[maybe_unused]] IPAContext &context,
 		  ControlList &metadata)
 {
 	metadata.set(controls::ColourCorrectionMatrix, frameContext.ccm.data());
-
-	const auto &saturation = frameContext.saturation;
-	metadata.set(controls::Saturation, saturation.value_or(1.0));
 }
 
 REGISTER_IPA_ALGORITHM(Ccm, "Ccm")
diff --git a/src/ipa/simple/algorithms/ccm.h b/src/ipa/simple/algorithms/ccm.h
index 867a680c3..62009a375 100644
--- a/src/ipa/simple/algorithms/ccm.h
+++ b/src/ipa/simple/algorithms/ccm.h
@@ -26,12 +26,6 @@  public:
 	~Ccm() = default;
 
 	int init(IPAContext &context, const YamlObject &tuningData) override;
-	int configure(IPAContext &context,
-		      const IPAConfigInfo &configInfo) override;
-	void queueRequest(typename Module::Context &context,
-			  const uint32_t frame,
-			  typename Module::FrameContext &frameContext,
-			  const ControlList &controls) override;
 	void prepare(IPAContext &context,
 		     const uint32_t frame,
 		     IPAFrameContext &frameContext,
@@ -42,10 +36,7 @@  public:
 		     ControlList &metadata) override;
 
 private:
-	void applySaturation(Matrix<float, 3, 3> &ccm, float saturation);
-
 	unsigned int lastCt_;
-	std::optional<float> lastSaturation_;
 	Interpolator<Matrix<float, 3, 3>> ccm_;
 	std::optional<Matrix<float, 3, 3>> currentCcm_;
 };
diff --git a/src/ipa/simple/algorithms/meson.build b/src/ipa/simple/algorithms/meson.build
index 2d0adb059..ebe9f20dd 100644
--- a/src/ipa/simple/algorithms/meson.build
+++ b/src/ipa/simple/algorithms/meson.build
@@ -1,6 +1,7 @@ 
 # SPDX-License-Identifier: CC0-1.0
 
 soft_simple_ipa_algorithms = files([
+    'adjust.cpp',
     'awb.cpp',
     'agc.cpp',
     'blc.cpp',
diff --git a/src/ipa/simple/data/uncalibrated.yaml b/src/ipa/simple/data/uncalibrated.yaml
index 8b6df9afc..e389e0588 100644
--- a/src/ipa/simple/data/uncalibrated.yaml
+++ b/src/ipa/simple/data/uncalibrated.yaml
@@ -14,6 +14,7 @@  algorithms:
           ccm: [ 1, 0, 0,
                  0, 1, 0,
                  0, 0, 1]
+  - Adjust:
   - Lut:
   - Agc:
 ...