diff --git a/src/ipa/rkisp1/algorithms/lsc.cpp b/src/ipa/rkisp1/algorithms/lsc.cpp
index 29cd7efd83ef..68cf35064182 100644
--- a/src/ipa/rkisp1/algorithms/lsc.cpp
+++ b/src/ipa/rkisp1/algorithms/lsc.cpp
@@ -70,38 +70,124 @@ namespace ipa::rkisp1::algorithms {
 
 LOG_DEFINE_CATEGORY(RkISP1Lsc)
 
-class LscPolynomialLoader
+class LscPolynomialShadingDescriptor : public LensShadingCorrection::ShadingDescriptor
 {
 public:
-	LscPolynomialLoader(const Size &sensorSize,
-			    const Rectangle &cropRectangle,
-			    const std::vector<double> &xSizes,
-			    const std::vector<double> &ySizes)
-		: sensorSize_(sensorSize),
-		  cropRectangle_(cropRectangle),
-		  xSizes_(xSizes),
-		  ySizes_(ySizes)
+	LscPolynomialShadingDescriptor(const LscPolynomial &pr, const LscPolynomial &pgr,
+				       const LscPolynomial &pgb, const LscPolynomial &pb)
+		: pr_(pr), pgr_(pgr), pgb_(pgb), pb_(pb)
 	{
 	}
 
-	int parseLscData(const YamlObject &yamlSets,
-			 std::map<unsigned int, LensShadingCorrection::Components> &lscData);
+	void sampleForCrop(const Rectangle &cropRectangle,
+			   const std::vector<double> &xSizes,
+			   const std::vector<double> &ySizes,
+			   LensShadingCorrection::Components &components) override;
 
 private:
-	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
 	std::vector<uint16_t> samplePolynomial(const LscPolynomial &poly,
 					       const std::vector<double> &xPositions,
 					       const std::vector<double> &yPositions,
 					       const Rectangle &cropRectangle);
 
+	std::vector<double> sizesListToPositions(const std::vector<double> &sizes);
+
+	LscPolynomial pr_;
+	LscPolynomial pgr_;
+	LscPolynomial pgb_;
+	LscPolynomial pb_;
+};
+
+void LscPolynomialShadingDescriptor::sampleForCrop(const Rectangle &cropRectangle,
+						   const std::vector<double> &xSizes,
+						   const std::vector<double> &ySizes,
+						   LensShadingCorrection::Components &components)
+{
+	std::vector<double> xPos = sizesListToPositions(xSizes);
+	std::vector<double> yPos = sizesListToPositions(ySizes);
+	components.r = samplePolynomial(pr_, xPos, yPos, cropRectangle);
+	components.gr = samplePolynomial(pgr_, xPos, yPos, cropRectangle);
+	components.gb = samplePolynomial(pgb_, xPos, yPos, cropRectangle);
+	components.b = samplePolynomial(pb_, xPos, yPos, cropRectangle);
+}
+
+std::vector<uint16_t>
+LscPolynomialShadingDescriptor::samplePolynomial(const LscPolynomial &poly,
+						 const std::vector<double> &xPositions,
+						 const std::vector<double> &yPositions,
+						 const Rectangle &cropRectangle)
+{
+	double m = poly.getM();
+	double x0 = cropRectangle.x / m;
+	double y0 = cropRectangle.y / m;
+	double w = cropRectangle.width / m;
+	double h = cropRectangle.height / m;
+	std::vector<uint16_t> samples;
+
+	samples.reserve(xPositions.size() * yPositions.size());
+
+	for (double y : yPositions) {
+		for (double x : xPositions) {
+			double xp = x0 + x * w;
+			double yp = y0 + y * h;
+			/*
+			 * The hardware uses 2.10 fixed point format and limits
+			 * the legal values to [1..3.999]. Scale and clamp the
+			 * sampled value accordingly.
+			 */
+			int v = static_cast<int>(
+				poly.sampleAtNormalizedPixelPos(xp, yp) *
+				1024);
+			v = std::min(std::max(v, 1024), 4095);
+			samples.push_back(v);
+		}
+	}
+	return samples;
+}
+
+/*
+ * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of
+ * the grid. This is then mirrored in hardware to the other half. See
+ * parseSizes() for further details. For easier handling, this function converts
+ * the cell sizes of half the grid to a list of position of the whole grid (on
+ * one axis). Example:
+ *
+ * input:   | 0.2 | 0.3 |
+ * output: 0.0   0.2   0.5   0.8   1.0
+ */
+std::vector<double>
+LscPolynomialShadingDescriptor::sizesListToPositions(const std::vector<double> &sizes)
+{
+	const int half = sizes.size();
+	std::vector<double> positions(half * 2 + 1);
+	double x = 0.0;
+
+	positions[half] = 0.5;
+	for (int i = 1; i <= half; i++) {
+		x += sizes[half - i];
+		positions[half - i] = 0.5 - x;
+		positions[half + i] = 0.5 + x;
+	}
+
+	return positions;
+}
+
+class LscPolynomialLoader
+{
+public:
+	LscPolynomialLoader(const Size &sensorSize) : sensorSize_(sensorSize)
+	{
+	}
+
+	int parseLscData(const YamlObject &yamlSets,
+			 LensShadingCorrection::ShadingDescriptorMap &lscData);
+
+private:
 	Size sensorSize_;
-	Rectangle cropRectangle_;
-	const std::vector<double> &xSizes_;
-	const std::vector<double> &ySizes_;
 };
 
 int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
-				      std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+				      LensShadingCorrection::ShadingDescriptorMap &lscData)
 {
 	const auto &sets = yamlSets.asList();
 	for (const auto &yamlSet : sets) {
@@ -115,7 +201,6 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 			return -EINVAL;
 		}
 
-		LensShadingCorrection::Components &set = lscData[ct];
 		pr = yamlSet["r"].get<LscPolynomial>();
 		pgr = yamlSet["gr"].get<LscPolynomial>();
 		pgb = yamlSet["gb"].get<LscPolynomial>();
@@ -133,12 +218,9 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 		pgb->setReferenceImageSize(sensorSize_);
 		pb->setReferenceImageSize(sensorSize_);
 
-		std::vector<double> xPos(sizesListToPositions(xSizes_));
-		std::vector<double> yPos(sizesListToPositions(ySizes_));
-		set.r = samplePolynomial(*pr, xPos, yPos, cropRectangle_);
-		set.gr = samplePolynomial(*pgr, xPos, yPos, cropRectangle_);
-		set.gb = samplePolynomial(*pgb, xPos, yPos, cropRectangle_);
-		set.b = samplePolynomial(*pb, xPos, yPos, cropRectangle_);
+		lscData.emplace(
+			ct, std::make_unique<LscPolynomialShadingDescriptor>(
+				    *pr, *pgr, *pgb, *pb));
 	}
 
 	if (lscData.empty()) {
@@ -149,70 +231,33 @@ int LscPolynomialLoader::parseLscData(const YamlObject &yamlSets,
 	return 0;
 }
 
-/*
- * The rkisp1 LSC grid spacing is defined by the cell sizes on the first half of
- * the grid. This is then mirrored in hardware to the other half. See
- * parseSizes() for further details. For easier handling, this function converts
- * the cell sizes of half the grid to a list of position of the whole grid (on
- * one axis). Example:
- *
- * input:   | 0.2 | 0.3 |
- * output: 0.0   0.2   0.5   0.8   1.0
- */
-std::vector<double> LscPolynomialLoader::sizesListToPositions(const std::vector<double> &sizes)
+class LscTableShadingDescriptor : public LensShadingCorrection::ShadingDescriptor
 {
-	const int half = sizes.size();
-	std::vector<double> positions(half * 2 + 1);
-	double x = 0.0;
-
-	positions[half] = 0.5;
-	for (int i = 1; i <= half; i++) {
-		x += sizes[half - i];
-		positions[half - i] = 0.5 - x;
-		positions[half + i] = 0.5 + x;
+public:
+	LscTableShadingDescriptor(LensShadingCorrection::Components components)
+		: lscData_(std::move(components))
+	{
 	}
 
-	return positions;
-}
-
-std::vector<uint16_t> LscPolynomialLoader::samplePolynomial(const LscPolynomial &poly,
-							    const std::vector<double> &xPositions,
-							    const std::vector<double> &yPositions,
-							    const Rectangle &cropRectangle)
-{
-	double m = poly.getM();
-	double x0 = cropRectangle.x / m;
-	double y0 = cropRectangle.y / m;
-	double w = cropRectangle.width / m;
-	double h = cropRectangle.height / m;
-	std::vector<uint16_t> samples;
-
-	samples.reserve(xPositions.size() * yPositions.size());
-
-	for (double y : yPositions) {
-		for (double x : xPositions) {
-			double xp = x0 + x * w;
-			double yp = y0 + y * h;
-			/*
-			 * The hardware uses 2.10 fixed point format and limits
-			 * the legal values to [1..3.999]. Scale and clamp the
-			 * sampled value accordingly.
-			 */
-			int v = static_cast<int>(
-				poly.sampleAtNormalizedPixelPos(xp, yp) *
-				1024);
-			v = std::min(std::max(v, 1024), 4095);
-			samples.push_back(v);
-		}
+	void sampleForCrop([[maybe_unused]] const Rectangle &cropRectangle,
+			   [[maybe_unused]] const std::vector<double> &xSizes,
+			   [[maybe_unused]] const std::vector<double> &ySizes,
+			   LensShadingCorrection::Components &components)
+	{
+		LOG(RkISP1Lsc, Warning)
+			<< "Tabular LSC data doesn't support resampling.";
+		components = lscData_;
 	}
-	return samples;
-}
+
+private:
+	LensShadingCorrection::Components lscData_;
+};
 
 class LscTableLoader
 {
 public:
 	int parseLscData(const YamlObject &yamlSets,
-			 std::map<unsigned int, LensShadingCorrection::Components> &lscData);
+			 LensShadingCorrection::ShadingDescriptorMap &lscData);
 
 private:
 	std::vector<uint16_t> parseTable(const YamlObject &tuningData,
@@ -220,7 +265,7 @@ private:
 };
 
 int LscTableLoader::parseLscData(const YamlObject &yamlSets,
-				 std::map<unsigned int, LensShadingCorrection::Components> &lscData)
+				 LensShadingCorrection::ShadingDescriptorMap &lscData)
 {
 	const auto &sets = yamlSets.asList();
 
@@ -234,8 +279,7 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,
 			return -EINVAL;
 		}
 
-		LensShadingCorrection::Components &set = lscData[ct];
-
+		LensShadingCorrection::Components set;
 		set.r = parseTable(yamlSet, "r");
 		set.gr = parseTable(yamlSet, "gr");
 		set.gb = parseTable(yamlSet, "gb");
@@ -248,6 +292,9 @@ int LscTableLoader::parseLscData(const YamlObject &yamlSets,
 				<< " is missing tables";
 			return -EINVAL;
 		}
+
+		lscData.emplace(
+			ct, std::make_unique<LscTableShadingDescriptor>(std::move(set)));
 	}
 
 	if (lscData.empty()) {
@@ -334,7 +381,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	}
 
 	int ret = 0;
-	std::map<unsigned int, Components> lscData;
+	std::map<unsigned int, std::unique_ptr<ShadingDescriptor>> lscData;
 
 	std::string type = tuningData["type"].get<std::string>("table");
 	if (type == "table") {
@@ -343,10 +390,11 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 		ret = loader.parseLscData(yamlSets, lscData);
 	} else if (type == "polynomial") {
 		LOG(RkISP1Lsc, Debug) << "Loading polynomial LSC data.";
-		auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize,
-						  context.sensorInfo.analogCrop,
-						  xSize_,
-						  ySize_);
+		/*
+		 * \todo: Most likely the reference frame should be native_size.
+		 * Let's wait how the internal discussions progress.
+		 */
+		auto loader = LscPolynomialLoader(context.sensorInfo.activeAreaSize);
 		ret = loader.parseLscData(yamlSets, lscData);
 	} else {
 		LOG(RkISP1Lsc, Error) << "Unsupported LSC data type '"
@@ -357,7 +405,7 @@ int LensShadingCorrection::init([[maybe_unused]] IPAContext &context,
 	if (ret)
 		return ret;
 
-	sets_.setData(std::move(lscData));
+	shadingDescriptors_.swap(lscData);
 
 	return 0;
 }
@@ -393,6 +441,14 @@ int LensShadingCorrection::configure(IPAContext &context,
 		yGrad_[i] = std::round(32768 / ySizes_[i]);
 	}
 
+	LOG(RkISP1Lsc, Debug) << "Sample LSC data for " << configInfo.analogCrop;
+	std::map<unsigned int, LensShadingCorrection::Components> shadingData;
+	for (auto const &[t, descriptor] : shadingDescriptors_)
+		descriptor->sampleForCrop(configInfo.analogCrop, xSize_, ySize_,
+					  shadingData[t]);
+
+	sets_.setData(std::move(shadingData));
+
 	context.configuration.lsc.enabled = true;
 	return 0;
 }
diff --git a/src/ipa/rkisp1/algorithms/lsc.h b/src/ipa/rkisp1/algorithms/lsc.h
index 7b68dda1a0d4..43fee337931f 100644
--- a/src/ipa/rkisp1/algorithms/lsc.h
+++ b/src/ipa/rkisp1/algorithms/lsc.h
@@ -8,6 +8,7 @@
 #pragma once
 
 #include <map>
+#include <memory>
 
 #include "libipa/interpolator.h"
 
@@ -36,10 +37,23 @@ public:
 		std::vector<uint16_t> b;
 	};
 
+	class ShadingDescriptor
+	{
+	public:
+		virtual ~ShadingDescriptor() = default;
+		virtual void sampleForCrop(const Rectangle &cropRectangle,
+					   const std::vector<double> &xSizes,
+					   const std::vector<double> &ySizes,
+					   Components &components) = 0;
+	};
+
+	using ShadingDescriptorMap = std::map<unsigned int, std::unique_ptr<ShadingDescriptor>>;
+
 private:
 	void setParameters(rkisp1_cif_isp_lsc_config &config);
 	void copyTable(rkisp1_cif_isp_lsc_config &config, const Components &set0);
 
+	std::map<unsigned int, std::unique_ptr<ShadingDescriptor>> shadingDescriptors_;
 	ipa::Interpolator<Components> sets_;
 	std::vector<double> xSize_;
 	std::vector<double> ySize_;
