[v2,29/35] libcamera: rkisp1: Handle requested orientation using dewarper
diff mbox series

Message ID 20251023144841.403689-30-stefan.klug@ideasonboard.com
State New
Headers show
Series
  • Full dewarper support on imx8mp
Related show

Commit Message

Stefan Klug Oct. 23, 2025, 2:48 p.m. UTC
When the dewarper is present it can handle arbitrary orientations
specified in the requested camera configuration. In that case handle all
transformations inside the dewarper (even if the sensor supports some of
them) because that makes it easier to handle coordinates for lens
dewarping inside the dewarper.

This complicates the path selection a bit, as for transformations that
include a transpose, the format before the dewarper has swapped
width/height.

Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>

---

Changes in v2:

- Fix path validation for cases where a transpose is involved
---
 src/libcamera/pipeline/rkisp1/rkisp1.cpp | 97 +++++++++++++++++-------
 1 file changed, 71 insertions(+), 26 deletions(-)

Comments

Umang Jain Oct. 25, 2025, 1:41 p.m. UTC | #1
On Thu, Oct 23, 2025 at 04:48:30PM +0200, Stefan Klug wrote:
> When the dewarper is present it can handle arbitrary orientations
> specified in the requested camera configuration. In that case handle all
> transformations inside the dewarper (even if the sensor supports some of
> them) because that makes it easier to handle coordinates for lens
> dewarping inside the dewarper.
> 
> This complicates the path selection a bit, as for transformations that
> include a transpose, the format before the dewarper has swapped
> width/height.
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>

One question: I see you tied it to the CameraConfiguration::orientation,
so that means the rotation (even via dewarper) will be static for the 
entire stream duration. I think that's what expected as per this patch?

But I think the dewarper also supports dynamic rotation as well? Are
their any (future) plans to support that, as I am more interested if 
there will be rotation related controls.

> 
> ---
> 
> Changes in v2:
> 
> - Fix path validation for cases where a transpose is involved
> ---
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 97 +++++++++++++++++-------
>  1 file changed, 71 insertions(+), 26 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index 943f26ece974..eaf82d0f1097 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -596,11 +596,6 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		status = Adjusted;
>  	}
>  
> -	Orientation requestedOrientation = orientation;
> -	combinedTransform_ = data_->sensor_->computeTransform(&orientation);
> -	if (orientation != requestedOrientation)
> -		status = Adjusted;
> -
>  	/*
>  	 * Simultaneous capture of raw and processed streams isn't possible. If
>  	 * there is any raw stream, cap the number of streams to one.
> @@ -618,9 +613,24 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		}
>  	}
>  
> +	/*
> +	 * If the dewarper supports orientation adjustments, apply that completely
> +	 * there. Even if the sensor supports flips, it is beneficial to do that
> +	 * in the dewarper so that lens dewarping happens on the unflipped image
> +	 */
> +	bool transposeAfterIsp = false;
>  	bool useDewarper = false;
> -	if (data_->canUseDewarper_ && !isRaw)
> +	if (data_->canUseDewarper_ && !isRaw) {
>  		useDewarper = true;
> +		combinedTransform_ = orientation / data_->sensor_->mountingOrientation();
> +		if (!!(combinedTransform_ & Transform::Transpose))
> +			transposeAfterIsp = true;
> +	} else {
> +		Orientation requestedOrientation = orientation;
> +		combinedTransform_ = data_->sensor_->computeTransform(&orientation);
> +		if (orientation != requestedOrientation)
> +			status = Adjusted;
> +	}
>  
>  	/*
>  	 * If there are more than one stream in the configuration figure out the
> @@ -636,12 +646,18 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  
>  	/*
>  	 * Validate the configuration against the desired path and, if the
> -	 * platform supports it, the dewarper.
> +	 * platform supports it, the dewarper. While iterating over the
> +	 * configurations collect the smallest common sensor format.
>  	 */
> +	Size accumulatedSensorSize;
>  	auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path,
>  				  Stream *stream, Status expectedStatus) {
>  		StreamConfiguration tryCfg = cfg;
>  
> +		/* Need to validate the path before the transpose */
> +		if (transposeAfterIsp)
> +			tryCfg.size.transpose();
> +
>  		Status ret = path->validate(sensor, sensorConfig, &tryCfg);
>  		if (ret == Invalid)
>  			return false;
> @@ -650,6 +666,8 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		    (expectedStatus == Valid && ret == Adjusted))
>  			return false;
>  
> +		Size sensorSize = tryCfg.size;
> +
>  		if (useDewarper) {
>  			/*
>  			 * The dewarper output is independent of the ISP path.
> @@ -672,6 +690,8 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  
>  		cfg = tryCfg;
>  		cfg.setStream(stream);
> +
> +		accumulatedSensorSize = std::max(accumulatedSensorSize, sensorSize);
>  		return true;
>  	};
>  
> @@ -722,13 +742,6 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  		return Invalid;
>  	}
>  
> -	/* Select the sensor format. */
> -	Size maxSize;
> -
> -	for (const StreamConfiguration &cfg : config_) {
> -		maxSize = std::max(maxSize, cfg.size);
> -	}
> -
>  	std::vector<unsigned int> mbusCodes;
>  
>  	if (isRaw) {
> @@ -739,7 +752,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  			       [](const auto &value) { return value.second; });
>  	}
>  
> -	sensorFormat_ = sensor->getFormat(mbusCodes, maxSize,
> +	sensorFormat_ = sensor->getFormat(mbusCodes, accumulatedSensorSize,
>  					  mainPath->maxResolution());
>  
>  	if (sensorFormat_.size.isNull())
> @@ -774,6 +787,22 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>  	if (roles.empty())
>  		return config;
>  
> +	Transform transform = Transform::Identity;
> +	Size previewSize = kRkISP1PreviewSize;
> +	bool transposeAfterIsp = false;
> +	if (data->canUseDewarper_) {
> +		transform = Orientation::Rotate0 / data->sensor_->mountingOrientation();
> +		if (!!(transform & Transform::Transpose))
> +			transposeAfterIsp = true;
> +	}
> +
> +	/*
> +	 * In case of a transpose transform we need to create a path for the
> +	 * transposed size.
> +	 */
> +	if (transposeAfterIsp)
> +		previewSize.transpose();
> +
>  	/*
>  	 * As the ISP can't output different color spaces for the main and self
>  	 * path, pick a sensible default color space based on the role of the
> @@ -802,7 +831,7 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>  			if (!colorSpace)
>  				colorSpace = ColorSpace::Sycc;
>  
> -			size = kRkISP1PreviewSize;
> +			size = previewSize;
>  			break;
>  
>  		case StreamRole::VideoRecording:
> @@ -810,7 +839,7 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>  			if (!colorSpace)
>  				colorSpace = ColorSpace::Rec709;
>  
> -			size = kRkISP1PreviewSize;
> +			size = previewSize;
>  			break;
>  
>  		case StreamRole::Raw:
> @@ -852,6 +881,9 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>  		if (!cfg.pixelFormat.isValid())
>  			return nullptr;
>  
> +		if (transposeAfterIsp && role != StreamRole::Raw)
> +			cfg.size.transpose();
> +
>  		cfg.colorSpace = colorSpace;
>  		cfg.bufferCount = kRkISP1MinBufferCount;
>  		config->addConfiguration(cfg);
> @@ -874,6 +906,19 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>  	if (ret)
>  		return ret;
>  
> +	const PixelFormat &streamFormat = config->at(0).pixelFormat;
> +	const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
> +	isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
> +	data->usesDewarper_ = data->canUseDewarper_ && !isRaw_;
> +
> +	Transform transform = config->combinedTransform();
> +	bool transposeAfterIsp = false;
> +	if (data->usesDewarper_) {
> +		if (!!(transform & Transform::Transpose))
> +			transposeAfterIsp = true;
> +		transform = Transform::Identity;
> +	}
> +
>  	/*
>  	 * Configure the format on the sensor output and propagate it through
>  	 * the pipeline.
> @@ -883,10 +928,10 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>  
>  	if (config->sensorConfig)
>  		ret = sensor->applyConfiguration(*config->sensorConfig,
> -						 config->combinedTransform(),
> +						 transform,
>  						 &format);
>  	else
> -		ret = sensor->setFormat(&format, config->combinedTransform());
> +		ret = sensor->setFormat(&format, transform);
>  
>  	if (ret < 0)
>  		return ret;
> @@ -913,10 +958,6 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>  		<< " crop " << inputCrop;
>  
>  	Rectangle outputCrop = inputCrop;
> -	const PixelFormat &streamFormat = config->at(0).pixelFormat;
> -	const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
> -	isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
> -	data->usesDewarper_ = dewarper_ && !isRaw_;
>  
>  	/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
>  	if (!isRaw_)
> @@ -1008,15 +1049,19 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>  					inputCrop, sensorInfo.analogCrop);
>  				auto &vertexMap = dewarper_->vertexMap(cfg.stream());
>  				vertexMap.setSensorCrop(sensorCrop);
> +				vertexMap.setTransform(config->combinedTransform());
>  				data->properties_.set(properties::ScalerCropMaximum, sensorCrop);
>  
>  				/*
>  				 * Apply a default sensor crop that keeps the
>  				 * aspect ratio.
>  				 */
> -				Size crop = format.size.boundedToAspectRatio(cfg.size);
> -				vertexMap.setScalerCrop(crop.centeredTo(
> -					Rectangle(format.size).center()));
> +				Size size = cfg.size;
> +				if (transposeAfterIsp)
> +					size.transpose();
> +				size = sensorCrop.size().boundedToAspectRatio(size);
> +				vertexMap.setScalerCrop(
> +					size.centeredTo(sensorCrop.center()));
>  			}
>  
>  			ret = mainPath_.configure(ispCfg, format);
> -- 
> 2.48.1
>
Paul Elder Nov. 6, 2025, 1:59 p.m. UTC | #2
Quoting Stefan Klug (2025-10-23 23:48:30)
> When the dewarper is present it can handle arbitrary orientations
> specified in the requested camera configuration. In that case handle all
> transformations inside the dewarper (even if the sensor supports some of
> them) because that makes it easier to handle coordinates for lens
> dewarping inside the dewarper.
> 
> This complicates the path selection a bit, as for transformations that
> include a transpose, the format before the dewarper has swapped
> width/height.
> 
> Signed-off-by: Stefan Klug <stefan.klug@ideasonboard.com>
> 

Looks good to me.

Reviewed-by: Paul Elder <paul.elder@ideasonboard.com>

> ---
> 
> Changes in v2:
> 
> - Fix path validation for cases where a transpose is involved
> ---
>  src/libcamera/pipeline/rkisp1/rkisp1.cpp | 97 +++++++++++++++++-------
>  1 file changed, 71 insertions(+), 26 deletions(-)
> 
> diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> index 943f26ece974..eaf82d0f1097 100644
> --- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> +++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
> @@ -596,11 +596,6 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>                 status = Adjusted;
>         }
>  
> -       Orientation requestedOrientation = orientation;
> -       combinedTransform_ = data_->sensor_->computeTransform(&orientation);
> -       if (orientation != requestedOrientation)
> -               status = Adjusted;
> -
>         /*
>          * Simultaneous capture of raw and processed streams isn't possible. If
>          * there is any raw stream, cap the number of streams to one.
> @@ -618,9 +613,24 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>                 }
>         }
>  
> +       /*
> +        * If the dewarper supports orientation adjustments, apply that completely
> +        * there. Even if the sensor supports flips, it is beneficial to do that
> +        * in the dewarper so that lens dewarping happens on the unflipped image
> +        */
> +       bool transposeAfterIsp = false;
>         bool useDewarper = false;
> -       if (data_->canUseDewarper_ && !isRaw)
> +       if (data_->canUseDewarper_ && !isRaw) {
>                 useDewarper = true;
> +               combinedTransform_ = orientation / data_->sensor_->mountingOrientation();
> +               if (!!(combinedTransform_ & Transform::Transpose))
> +                       transposeAfterIsp = true;
> +       } else {
> +               Orientation requestedOrientation = orientation;
> +               combinedTransform_ = data_->sensor_->computeTransform(&orientation);
> +               if (orientation != requestedOrientation)
> +                       status = Adjusted;
> +       }
>  
>         /*
>          * If there are more than one stream in the configuration figure out the
> @@ -636,12 +646,18 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  
>         /*
>          * Validate the configuration against the desired path and, if the
> -        * platform supports it, the dewarper.
> +        * platform supports it, the dewarper. While iterating over the
> +        * configurations collect the smallest common sensor format.
>          */
> +       Size accumulatedSensorSize;
>         auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path,
>                                   Stream *stream, Status expectedStatus) {
>                 StreamConfiguration tryCfg = cfg;
>  
> +               /* Need to validate the path before the transpose */
> +               if (transposeAfterIsp)
> +                       tryCfg.size.transpose();
> +
>                 Status ret = path->validate(sensor, sensorConfig, &tryCfg);
>                 if (ret == Invalid)
>                         return false;
> @@ -650,6 +666,8 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>                     (expectedStatus == Valid && ret == Adjusted))
>                         return false;
>  
> +               Size sensorSize = tryCfg.size;
> +
>                 if (useDewarper) {
>                         /*
>                          * The dewarper output is independent of the ISP path.
> @@ -672,6 +690,8 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>  
>                 cfg = tryCfg;
>                 cfg.setStream(stream);
> +
> +               accumulatedSensorSize = std::max(accumulatedSensorSize, sensorSize);
>                 return true;
>         };
>  
> @@ -722,13 +742,6 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>                 return Invalid;
>         }
>  
> -       /* Select the sensor format. */
> -       Size maxSize;
> -
> -       for (const StreamConfiguration &cfg : config_) {
> -               maxSize = std::max(maxSize, cfg.size);
> -       }
> -
>         std::vector<unsigned int> mbusCodes;
>  
>         if (isRaw) {
> @@ -739,7 +752,7 @@ CameraConfiguration::Status RkISP1CameraConfiguration::validate()
>                                [](const auto &value) { return value.second; });
>         }
>  
> -       sensorFormat_ = sensor->getFormat(mbusCodes, maxSize,
> +       sensorFormat_ = sensor->getFormat(mbusCodes, accumulatedSensorSize,
>                                           mainPath->maxResolution());
>  
>         if (sensorFormat_.size.isNull())
> @@ -774,6 +787,22 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>         if (roles.empty())
>                 return config;
>  
> +       Transform transform = Transform::Identity;
> +       Size previewSize = kRkISP1PreviewSize;
> +       bool transposeAfterIsp = false;
> +       if (data->canUseDewarper_) {
> +               transform = Orientation::Rotate0 / data->sensor_->mountingOrientation();
> +               if (!!(transform & Transform::Transpose))
> +                       transposeAfterIsp = true;
> +       }
> +
> +       /*
> +        * In case of a transpose transform we need to create a path for the
> +        * transposed size.
> +        */
> +       if (transposeAfterIsp)
> +               previewSize.transpose();
> +
>         /*
>          * As the ISP can't output different color spaces for the main and self
>          * path, pick a sensible default color space based on the role of the
> @@ -802,7 +831,7 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>                         if (!colorSpace)
>                                 colorSpace = ColorSpace::Sycc;
>  
> -                       size = kRkISP1PreviewSize;
> +                       size = previewSize;
>                         break;
>  
>                 case StreamRole::VideoRecording:
> @@ -810,7 +839,7 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>                         if (!colorSpace)
>                                 colorSpace = ColorSpace::Rec709;
>  
> -                       size = kRkISP1PreviewSize;
> +                       size = previewSize;
>                         break;
>  
>                 case StreamRole::Raw:
> @@ -852,6 +881,9 @@ PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
>                 if (!cfg.pixelFormat.isValid())
>                         return nullptr;
>  
> +               if (transposeAfterIsp && role != StreamRole::Raw)
> +                       cfg.size.transpose();
> +
>                 cfg.colorSpace = colorSpace;
>                 cfg.bufferCount = kRkISP1MinBufferCount;
>                 config->addConfiguration(cfg);
> @@ -874,6 +906,19 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>         if (ret)
>                 return ret;
>  
> +       const PixelFormat &streamFormat = config->at(0).pixelFormat;
> +       const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
> +       isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
> +       data->usesDewarper_ = data->canUseDewarper_ && !isRaw_;
> +
> +       Transform transform = config->combinedTransform();
> +       bool transposeAfterIsp = false;
> +       if (data->usesDewarper_) {
> +               if (!!(transform & Transform::Transpose))
> +                       transposeAfterIsp = true;
> +               transform = Transform::Identity;
> +       }
> +
>         /*
>          * Configure the format on the sensor output and propagate it through
>          * the pipeline.
> @@ -883,10 +928,10 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>  
>         if (config->sensorConfig)
>                 ret = sensor->applyConfiguration(*config->sensorConfig,
> -                                                config->combinedTransform(),
> +                                                transform,
>                                                  &format);
>         else
> -               ret = sensor->setFormat(&format, config->combinedTransform());
> +               ret = sensor->setFormat(&format, transform);
>  
>         if (ret < 0)
>                 return ret;
> @@ -913,10 +958,6 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>                 << " crop " << inputCrop;
>  
>         Rectangle outputCrop = inputCrop;
> -       const PixelFormat &streamFormat = config->at(0).pixelFormat;
> -       const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
> -       isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
> -       data->usesDewarper_ = dewarper_ && !isRaw_;
>  
>         /* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
>         if (!isRaw_)
> @@ -1008,15 +1049,19 @@ int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
>                                         inputCrop, sensorInfo.analogCrop);
>                                 auto &vertexMap = dewarper_->vertexMap(cfg.stream());
>                                 vertexMap.setSensorCrop(sensorCrop);
> +                               vertexMap.setTransform(config->combinedTransform());
>                                 data->properties_.set(properties::ScalerCropMaximum, sensorCrop);
>  
>                                 /*
>                                  * Apply a default sensor crop that keeps the
>                                  * aspect ratio.
>                                  */
> -                               Size crop = format.size.boundedToAspectRatio(cfg.size);
> -                               vertexMap.setScalerCrop(crop.centeredTo(
> -                                       Rectangle(format.size).center()));
> +                               Size size = cfg.size;
> +                               if (transposeAfterIsp)
> +                                       size.transpose();
> +                               size = sensorCrop.size().boundedToAspectRatio(size);
> +                               vertexMap.setScalerCrop(
> +                                       size.centeredTo(sensorCrop.center()));
>                         }
>  
>                         ret = mainPath_.configure(ispCfg, format);
> -- 
> 2.48.1
>

Patch
diff mbox series

diff --git a/src/libcamera/pipeline/rkisp1/rkisp1.cpp b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
index 943f26ece974..eaf82d0f1097 100644
--- a/src/libcamera/pipeline/rkisp1/rkisp1.cpp
+++ b/src/libcamera/pipeline/rkisp1/rkisp1.cpp
@@ -596,11 +596,6 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		status = Adjusted;
 	}
 
-	Orientation requestedOrientation = orientation;
-	combinedTransform_ = data_->sensor_->computeTransform(&orientation);
-	if (orientation != requestedOrientation)
-		status = Adjusted;
-
 	/*
 	 * Simultaneous capture of raw and processed streams isn't possible. If
 	 * there is any raw stream, cap the number of streams to one.
@@ -618,9 +613,24 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		}
 	}
 
+	/*
+	 * If the dewarper supports orientation adjustments, apply that completely
+	 * there. Even if the sensor supports flips, it is beneficial to do that
+	 * in the dewarper so that lens dewarping happens on the unflipped image
+	 */
+	bool transposeAfterIsp = false;
 	bool useDewarper = false;
-	if (data_->canUseDewarper_ && !isRaw)
+	if (data_->canUseDewarper_ && !isRaw) {
 		useDewarper = true;
+		combinedTransform_ = orientation / data_->sensor_->mountingOrientation();
+		if (!!(combinedTransform_ & Transform::Transpose))
+			transposeAfterIsp = true;
+	} else {
+		Orientation requestedOrientation = orientation;
+		combinedTransform_ = data_->sensor_->computeTransform(&orientation);
+		if (orientation != requestedOrientation)
+			status = Adjusted;
+	}
 
 	/*
 	 * If there are more than one stream in the configuration figure out the
@@ -636,12 +646,18 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 	/*
 	 * Validate the configuration against the desired path and, if the
-	 * platform supports it, the dewarper.
+	 * platform supports it, the dewarper. While iterating over the
+	 * configurations collect the smallest common sensor format.
 	 */
+	Size accumulatedSensorSize;
 	auto validateConfig = [&](StreamConfiguration &cfg, RkISP1Path *path,
 				  Stream *stream, Status expectedStatus) {
 		StreamConfiguration tryCfg = cfg;
 
+		/* Need to validate the path before the transpose */
+		if (transposeAfterIsp)
+			tryCfg.size.transpose();
+
 		Status ret = path->validate(sensor, sensorConfig, &tryCfg);
 		if (ret == Invalid)
 			return false;
@@ -650,6 +666,8 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		    (expectedStatus == Valid && ret == Adjusted))
 			return false;
 
+		Size sensorSize = tryCfg.size;
+
 		if (useDewarper) {
 			/*
 			 * The dewarper output is independent of the ISP path.
@@ -672,6 +690,8 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 
 		cfg = tryCfg;
 		cfg.setStream(stream);
+
+		accumulatedSensorSize = std::max(accumulatedSensorSize, sensorSize);
 		return true;
 	};
 
@@ -722,13 +742,6 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 		return Invalid;
 	}
 
-	/* Select the sensor format. */
-	Size maxSize;
-
-	for (const StreamConfiguration &cfg : config_) {
-		maxSize = std::max(maxSize, cfg.size);
-	}
-
 	std::vector<unsigned int> mbusCodes;
 
 	if (isRaw) {
@@ -739,7 +752,7 @@  CameraConfiguration::Status RkISP1CameraConfiguration::validate()
 			       [](const auto &value) { return value.second; });
 	}
 
-	sensorFormat_ = sensor->getFormat(mbusCodes, maxSize,
+	sensorFormat_ = sensor->getFormat(mbusCodes, accumulatedSensorSize,
 					  mainPath->maxResolution());
 
 	if (sensorFormat_.size.isNull())
@@ -774,6 +787,22 @@  PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
 	if (roles.empty())
 		return config;
 
+	Transform transform = Transform::Identity;
+	Size previewSize = kRkISP1PreviewSize;
+	bool transposeAfterIsp = false;
+	if (data->canUseDewarper_) {
+		transform = Orientation::Rotate0 / data->sensor_->mountingOrientation();
+		if (!!(transform & Transform::Transpose))
+			transposeAfterIsp = true;
+	}
+
+	/*
+	 * In case of a transpose transform we need to create a path for the
+	 * transposed size.
+	 */
+	if (transposeAfterIsp)
+		previewSize.transpose();
+
 	/*
 	 * As the ISP can't output different color spaces for the main and self
 	 * path, pick a sensible default color space based on the role of the
@@ -802,7 +831,7 @@  PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
 			if (!colorSpace)
 				colorSpace = ColorSpace::Sycc;
 
-			size = kRkISP1PreviewSize;
+			size = previewSize;
 			break;
 
 		case StreamRole::VideoRecording:
@@ -810,7 +839,7 @@  PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
 			if (!colorSpace)
 				colorSpace = ColorSpace::Rec709;
 
-			size = kRkISP1PreviewSize;
+			size = previewSize;
 			break;
 
 		case StreamRole::Raw:
@@ -852,6 +881,9 @@  PipelineHandlerRkISP1::generateConfiguration(Camera *camera,
 		if (!cfg.pixelFormat.isValid())
 			return nullptr;
 
+		if (transposeAfterIsp && role != StreamRole::Raw)
+			cfg.size.transpose();
+
 		cfg.colorSpace = colorSpace;
 		cfg.bufferCount = kRkISP1MinBufferCount;
 		config->addConfiguration(cfg);
@@ -874,6 +906,19 @@  int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
 	if (ret)
 		return ret;
 
+	const PixelFormat &streamFormat = config->at(0).pixelFormat;
+	const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
+	isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
+	data->usesDewarper_ = data->canUseDewarper_ && !isRaw_;
+
+	Transform transform = config->combinedTransform();
+	bool transposeAfterIsp = false;
+	if (data->usesDewarper_) {
+		if (!!(transform & Transform::Transpose))
+			transposeAfterIsp = true;
+		transform = Transform::Identity;
+	}
+
 	/*
 	 * Configure the format on the sensor output and propagate it through
 	 * the pipeline.
@@ -883,10 +928,10 @@  int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
 
 	if (config->sensorConfig)
 		ret = sensor->applyConfiguration(*config->sensorConfig,
-						 config->combinedTransform(),
+						 transform,
 						 &format);
 	else
-		ret = sensor->setFormat(&format, config->combinedTransform());
+		ret = sensor->setFormat(&format, transform);
 
 	if (ret < 0)
 		return ret;
@@ -913,10 +958,6 @@  int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
 		<< " crop " << inputCrop;
 
 	Rectangle outputCrop = inputCrop;
-	const PixelFormat &streamFormat = config->at(0).pixelFormat;
-	const PixelFormatInfo &info = PixelFormatInfo::info(streamFormat);
-	isRaw_ = info.colourEncoding == PixelFormatInfo::ColourEncodingRAW;
-	data->usesDewarper_ = dewarper_ && !isRaw_;
 
 	/* YUYV8_2X8 is required on the ISP source path pad for YUV output. */
 	if (!isRaw_)
@@ -1008,15 +1049,19 @@  int PipelineHandlerRkISP1::configure(Camera *camera, CameraConfiguration *c)
 					inputCrop, sensorInfo.analogCrop);
 				auto &vertexMap = dewarper_->vertexMap(cfg.stream());
 				vertexMap.setSensorCrop(sensorCrop);
+				vertexMap.setTransform(config->combinedTransform());
 				data->properties_.set(properties::ScalerCropMaximum, sensorCrop);
 
 				/*
 				 * Apply a default sensor crop that keeps the
 				 * aspect ratio.
 				 */
-				Size crop = format.size.boundedToAspectRatio(cfg.size);
-				vertexMap.setScalerCrop(crop.centeredTo(
-					Rectangle(format.size).center()));
+				Size size = cfg.size;
+				if (transposeAfterIsp)
+					size.transpose();
+				size = sensorCrop.size().boundedToAspectRatio(size);
+				vertexMap.setScalerCrop(
+					size.centeredTo(sensorCrop.center()));
 			}
 
 			ret = mainPath_.configure(ispCfg, format);