diff --git a/include/libcamera/internal/software_isp/debayer_geometry.h b/include/libcamera/internal/software_isp/debayer_geometry.h
index 46721753..8409fd09 100644
--- a/include/libcamera/internal/software_isp/debayer_geometry.h
+++ b/include/libcamera/internal/software_isp/debayer_geometry.h
@@ -9,8 +9,10 @@
 
 #include <algorithm>
 #include <array>
+#include <utility>
 
 #include <libcamera/geometry.h>
+#include <libcamera/transform.h>
 
 namespace libcamera {
 
@@ -48,4 +50,29 @@ inline Rectangle debayerCrop(const Size &inputSize, const Size &nativeSize,
 	return visibleSize.centeredTo(Rectangle(inputSize).center());
 }
 
+/*
+ * Map an image crop to native sensor pixels relative to the active array.
+ * The input must cover the whole analogue crop. Undo the sensor transform
+ * before accounting for binning and the analogue crop offset. Integer
+ * coordinates and dimensions are truncated as in transformedBetween().
+ */
+inline Rectangle debayerCropToSensor(Rectangle crop, Size inputSize,
+				     const Rectangle &analogCrop,
+				     const Rectangle &activeArea,
+				     Transform transform)
+{
+	if (!!(transform & Transform::Transpose)) {
+		std::swap(crop.x, crop.y);
+		std::swap(crop.width, crop.height);
+		std::swap(inputSize.width, inputSize.height);
+	}
+	if (!!(transform & Transform::HFlip))
+		crop.x = inputSize.width - crop.x - crop.width;
+	if (!!(transform & Transform::VFlip))
+		crop.y = inputSize.height - crop.y - crop.height;
+
+	return crop.transformedBetween(Rectangle(inputSize), analogCrop)
+		.translatedBy(-activeArea.topLeft());
+}
+
 } /* namespace libcamera */
