[libcamera-devel,v2,3/5] qcam: format_converter: Rename YUV and NV to YUVPacked and YUVSemiPlanar
diff mbox series

Message ID 20210907002044.7319-4-laurent.pinchart@ideasonboard.com
State Accepted
Headers show
Series
  • qcam: Fix stride handling
Related show

Commit Message

Laurent Pinchart Sept. 7, 2021, 12:20 a.m. UTC
To prepare for fully-planar YUV support, rename the existing format
families YUV and NV to YUVPacked and YUVSemiPlanar respectively.

Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
---
 src/qcam/format_converter.cpp | 114 +++++++++++++++++-----------------
 src/qcam/format_converter.h   |   8 +--
 2 files changed, 61 insertions(+), 61 deletions(-)

Comments

Kieran Bingham Sept. 21, 2021, 3:01 p.m. UTC | #1
On 07/09/2021 01:20, Laurent Pinchart wrote:
> To prepare for fully-planar YUV support, rename the existing format
> families YUV and NV to YUVPacked and YUVSemiPlanar respectively.
> 

Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>

> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
> ---
>  src/qcam/format_converter.cpp | 114 +++++++++++++++++-----------------
>  src/qcam/format_converter.h   |   8 +--
>  2 files changed, 61 insertions(+), 61 deletions(-)
> 
> diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp
> index b4c7b0ca0edf..7979ea8a77ff 100644
> --- a/src/qcam/format_converter.cpp
> +++ b/src/qcam/format_converter.cpp
> @@ -34,37 +34,37 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
>  {
>  	switch (format) {
>  	case libcamera::formats::NV12:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 2;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV21:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 2;
>  		nvSwap_ = true;
>  		break;
>  	case libcamera::formats::NV16:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 1;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV61:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 1;
>  		nvSwap_ = true;
>  		break;
>  	case libcamera::formats::NV24:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 1;
>  		vertSubSample_ = 1;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV42:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 1;
>  		vertSubSample_ = 1;
>  		nvSwap_ = true;
> @@ -121,22 +121,22 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
>  		break;
>  
>  	case libcamera::formats::VYUY:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 1;
>  		cb_pos_ = 2;
>  		break;
>  	case libcamera::formats::YVYU:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 0;
>  		cb_pos_ = 3;
>  		break;
>  	case libcamera::formats::UYVY:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 1;
>  		cb_pos_ = 0;
>  		break;
>  	case libcamera::formats::YUYV:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 0;
>  		cb_pos_ = 1;
>  		break;
> @@ -163,14 +163,14 @@ void FormatConverter::convert(const Image *src, size_t size, QImage *dst)
>  	case MJPEG:
>  		dst->loadFromData(src->data(0).data(), size, "JPEG");
>  		break;
> -	case YUV:
> -		convertYUV(src, dst->bits());
> -		break;
>  	case RGB:
>  		convertRGB(src, dst->bits());
>  		break;
> -	case NV:
> -		convertNV(src, dst->bits());
> +	case YUVPacked:
> +		convertYUVPacked(src, dst->bits());
> +		break;
> +	case YUVSemiPlanar:
> +		convertYUVSemiPlanar(src, dst->bits());
>  		break;
>  	};
>  }
> @@ -185,47 +185,6 @@ static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
>  	*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);
>  }
>  
> -void FormatConverter::convertNV(const Image *srcImage, unsigned char *dst)
> -{
> -	unsigned int c_stride = stride_ * (2 / horzSubSample_);
> -	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
> -	unsigned int cb_pos = nvSwap_ ? 1 : 0;
> -	unsigned int cr_pos = nvSwap_ ? 0 : 1;
> -	const unsigned char *src = srcImage->data(0).data();
> -	const unsigned char *src_c = srcImage->data(1).data();
> -	int r, g, b;
> -
> -	for (unsigned int y = 0; y < height_; y++) {
> -		const unsigned char *src_y = src + y * stride_;
> -		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
> -					      c_stride + cb_pos;
> -		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
> -					      c_stride + cr_pos;
> -
> -		for (unsigned int x = 0; x < width_; x += 2) {
> -			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> -			dst[0] = b;
> -			dst[1] = g;
> -			dst[2] = r;
> -			dst[3] = 0xff;
> -			src_y++;
> -			src_cb += c_inc;
> -			src_cr += c_inc;
> -			dst += 4;
> -
> -			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> -			dst[0] = b;
> -			dst[1] = g;
> -			dst[2] = r;
> -			dst[3] = 0xff;
> -			src_y++;
> -			src_cb += 2;
> -			src_cr += 2;
> -			dst += 4;
> -		}
> -	}
> -}
> -
>  void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
>  {
>  	const unsigned char *src = srcImage->data(0).data();
> @@ -249,7 +208,7 @@ void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
>  	}
>  }
>  
> -void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
> +void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst)
>  {
>  	const unsigned char *src = srcImage->data(0).data();
>  	unsigned int src_x, src_y, dst_x, dst_y;
> @@ -287,3 +246,44 @@ void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
>  		}
>  	}
>  }
> +
> +void FormatConverter::convertYUVSemiPlanar(const Image *srcImage, unsigned char *dst)
> +{
> +	unsigned int c_stride = stride_ * (2 / horzSubSample_);
> +	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
> +	unsigned int cb_pos = nvSwap_ ? 1 : 0;
> +	unsigned int cr_pos = nvSwap_ ? 0 : 1;
> +	const unsigned char *src = srcImage->data(0).data();
> +	const unsigned char *src_c = srcImage->data(1).data();
> +	int r, g, b;
> +
> +	for (unsigned int y = 0; y < height_; y++) {
> +		const unsigned char *src_y = src + y * stride_;
> +		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
> +					      c_stride + cb_pos;
> +		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
> +					      c_stride + cr_pos;
> +
> +		for (unsigned int x = 0; x < width_; x += 2) {
> +			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> +			dst[0] = b;
> +			dst[1] = g;
> +			dst[2] = r;
> +			dst[3] = 0xff;
> +			src_y++;
> +			src_cb += c_inc;
> +			src_cr += c_inc;
> +			dst += 4;
> +
> +			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> +			dst[0] = b;
> +			dst[1] = g;
> +			dst[2] = r;
> +			dst[3] = 0xff;
> +			src_y++;
> +			src_cb += 2;
> +			src_cr += 2;
> +			dst += 4;
> +		}
> +	}
> +}
> diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h
> index bb04aa959a1a..9ce2cc6da7f2 100644
> --- a/src/qcam/format_converter.h
> +++ b/src/qcam/format_converter.h
> @@ -27,14 +27,14 @@ public:
>  private:
>  	enum FormatFamily {
>  		MJPEG,
> -		NV,
>  		RGB,
> -		YUV,
> +		YUVPacked,
> +		YUVSemiPlanar,
>  	};
>  
> -	void convertNV(const Image *src, unsigned char *dst);
>  	void convertRGB(const Image *src, unsigned char *dst);
> -	void convertYUV(const Image *src, unsigned char *dst);
> +	void convertYUVPacked(const Image *src, unsigned char *dst);
> +	void convertYUVSemiPlanar(const Image *src, unsigned char *dst);
>  
>  	libcamera::PixelFormat format_;
>  	unsigned int width_;
>
Paul Elder Sept. 22, 2021, 7:02 a.m. UTC | #2
Hi Laurent,

On Tue, Sep 07, 2021 at 03:20:42AM +0300, Laurent Pinchart wrote:
> To prepare for fully-planar YUV support, rename the existing format
> families YUV and NV to YUVPacked and YUVSemiPlanar respectively.
> 
> Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

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

> ---
>  src/qcam/format_converter.cpp | 114 +++++++++++++++++-----------------
>  src/qcam/format_converter.h   |   8 +--
>  2 files changed, 61 insertions(+), 61 deletions(-)
> 
> diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp
> index b4c7b0ca0edf..7979ea8a77ff 100644
> --- a/src/qcam/format_converter.cpp
> +++ b/src/qcam/format_converter.cpp
> @@ -34,37 +34,37 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
>  {
>  	switch (format) {
>  	case libcamera::formats::NV12:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 2;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV21:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 2;
>  		nvSwap_ = true;
>  		break;
>  	case libcamera::formats::NV16:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 1;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV61:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 2;
>  		vertSubSample_ = 1;
>  		nvSwap_ = true;
>  		break;
>  	case libcamera::formats::NV24:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 1;
>  		vertSubSample_ = 1;
>  		nvSwap_ = false;
>  		break;
>  	case libcamera::formats::NV42:
> -		formatFamily_ = NV;
> +		formatFamily_ = YUVSemiPlanar;
>  		horzSubSample_ = 1;
>  		vertSubSample_ = 1;
>  		nvSwap_ = true;
> @@ -121,22 +121,22 @@ int FormatConverter::configure(const libcamera::PixelFormat &format,
>  		break;
>  
>  	case libcamera::formats::VYUY:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 1;
>  		cb_pos_ = 2;
>  		break;
>  	case libcamera::formats::YVYU:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 0;
>  		cb_pos_ = 3;
>  		break;
>  	case libcamera::formats::UYVY:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 1;
>  		cb_pos_ = 0;
>  		break;
>  	case libcamera::formats::YUYV:
> -		formatFamily_ = YUV;
> +		formatFamily_ = YUVPacked;
>  		y_pos_ = 0;
>  		cb_pos_ = 1;
>  		break;
> @@ -163,14 +163,14 @@ void FormatConverter::convert(const Image *src, size_t size, QImage *dst)
>  	case MJPEG:
>  		dst->loadFromData(src->data(0).data(), size, "JPEG");
>  		break;
> -	case YUV:
> -		convertYUV(src, dst->bits());
> -		break;
>  	case RGB:
>  		convertRGB(src, dst->bits());
>  		break;
> -	case NV:
> -		convertNV(src, dst->bits());
> +	case YUVPacked:
> +		convertYUVPacked(src, dst->bits());
> +		break;
> +	case YUVSemiPlanar:
> +		convertYUVSemiPlanar(src, dst->bits());
>  		break;
>  	};
>  }
> @@ -185,47 +185,6 @@ static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
>  	*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);
>  }
>  
> -void FormatConverter::convertNV(const Image *srcImage, unsigned char *dst)
> -{
> -	unsigned int c_stride = stride_ * (2 / horzSubSample_);
> -	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
> -	unsigned int cb_pos = nvSwap_ ? 1 : 0;
> -	unsigned int cr_pos = nvSwap_ ? 0 : 1;
> -	const unsigned char *src = srcImage->data(0).data();
> -	const unsigned char *src_c = srcImage->data(1).data();
> -	int r, g, b;
> -
> -	for (unsigned int y = 0; y < height_; y++) {
> -		const unsigned char *src_y = src + y * stride_;
> -		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
> -					      c_stride + cb_pos;
> -		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
> -					      c_stride + cr_pos;
> -
> -		for (unsigned int x = 0; x < width_; x += 2) {
> -			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> -			dst[0] = b;
> -			dst[1] = g;
> -			dst[2] = r;
> -			dst[3] = 0xff;
> -			src_y++;
> -			src_cb += c_inc;
> -			src_cr += c_inc;
> -			dst += 4;
> -
> -			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> -			dst[0] = b;
> -			dst[1] = g;
> -			dst[2] = r;
> -			dst[3] = 0xff;
> -			src_y++;
> -			src_cb += 2;
> -			src_cr += 2;
> -			dst += 4;
> -		}
> -	}
> -}
> -
>  void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
>  {
>  	const unsigned char *src = srcImage->data(0).data();
> @@ -249,7 +208,7 @@ void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
>  	}
>  }
>  
> -void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
> +void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst)
>  {
>  	const unsigned char *src = srcImage->data(0).data();
>  	unsigned int src_x, src_y, dst_x, dst_y;
> @@ -287,3 +246,44 @@ void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
>  		}
>  	}
>  }
> +
> +void FormatConverter::convertYUVSemiPlanar(const Image *srcImage, unsigned char *dst)
> +{
> +	unsigned int c_stride = stride_ * (2 / horzSubSample_);
> +	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
> +	unsigned int cb_pos = nvSwap_ ? 1 : 0;
> +	unsigned int cr_pos = nvSwap_ ? 0 : 1;
> +	const unsigned char *src = srcImage->data(0).data();
> +	const unsigned char *src_c = srcImage->data(1).data();
> +	int r, g, b;
> +
> +	for (unsigned int y = 0; y < height_; y++) {
> +		const unsigned char *src_y = src + y * stride_;
> +		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
> +					      c_stride + cb_pos;
> +		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
> +					      c_stride + cr_pos;
> +
> +		for (unsigned int x = 0; x < width_; x += 2) {
> +			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> +			dst[0] = b;
> +			dst[1] = g;
> +			dst[2] = r;
> +			dst[3] = 0xff;
> +			src_y++;
> +			src_cb += c_inc;
> +			src_cr += c_inc;
> +			dst += 4;
> +
> +			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
> +			dst[0] = b;
> +			dst[1] = g;
> +			dst[2] = r;
> +			dst[3] = 0xff;
> +			src_y++;
> +			src_cb += 2;
> +			src_cr += 2;
> +			dst += 4;
> +		}
> +	}
> +}
> diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h
> index bb04aa959a1a..9ce2cc6da7f2 100644
> --- a/src/qcam/format_converter.h
> +++ b/src/qcam/format_converter.h
> @@ -27,14 +27,14 @@ public:
>  private:
>  	enum FormatFamily {
>  		MJPEG,
> -		NV,
>  		RGB,
> -		YUV,
> +		YUVPacked,
> +		YUVSemiPlanar,
>  	};
>  
> -	void convertNV(const Image *src, unsigned char *dst);
>  	void convertRGB(const Image *src, unsigned char *dst);
> -	void convertYUV(const Image *src, unsigned char *dst);
> +	void convertYUVPacked(const Image *src, unsigned char *dst);
> +	void convertYUVSemiPlanar(const Image *src, unsigned char *dst);
>  
>  	libcamera::PixelFormat format_;
>  	unsigned int width_;
> -- 
> Regards,
> 
> Laurent Pinchart
>

Patch
diff mbox series

diff --git a/src/qcam/format_converter.cpp b/src/qcam/format_converter.cpp
index b4c7b0ca0edf..7979ea8a77ff 100644
--- a/src/qcam/format_converter.cpp
+++ b/src/qcam/format_converter.cpp
@@ -34,37 +34,37 @@  int FormatConverter::configure(const libcamera::PixelFormat &format,
 {
 	switch (format) {
 	case libcamera::formats::NV12:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 2;
 		vertSubSample_ = 2;
 		nvSwap_ = false;
 		break;
 	case libcamera::formats::NV21:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 2;
 		vertSubSample_ = 2;
 		nvSwap_ = true;
 		break;
 	case libcamera::formats::NV16:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 2;
 		vertSubSample_ = 1;
 		nvSwap_ = false;
 		break;
 	case libcamera::formats::NV61:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 2;
 		vertSubSample_ = 1;
 		nvSwap_ = true;
 		break;
 	case libcamera::formats::NV24:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 1;
 		vertSubSample_ = 1;
 		nvSwap_ = false;
 		break;
 	case libcamera::formats::NV42:
-		formatFamily_ = NV;
+		formatFamily_ = YUVSemiPlanar;
 		horzSubSample_ = 1;
 		vertSubSample_ = 1;
 		nvSwap_ = true;
@@ -121,22 +121,22 @@  int FormatConverter::configure(const libcamera::PixelFormat &format,
 		break;
 
 	case libcamera::formats::VYUY:
-		formatFamily_ = YUV;
+		formatFamily_ = YUVPacked;
 		y_pos_ = 1;
 		cb_pos_ = 2;
 		break;
 	case libcamera::formats::YVYU:
-		formatFamily_ = YUV;
+		formatFamily_ = YUVPacked;
 		y_pos_ = 0;
 		cb_pos_ = 3;
 		break;
 	case libcamera::formats::UYVY:
-		formatFamily_ = YUV;
+		formatFamily_ = YUVPacked;
 		y_pos_ = 1;
 		cb_pos_ = 0;
 		break;
 	case libcamera::formats::YUYV:
-		formatFamily_ = YUV;
+		formatFamily_ = YUVPacked;
 		y_pos_ = 0;
 		cb_pos_ = 1;
 		break;
@@ -163,14 +163,14 @@  void FormatConverter::convert(const Image *src, size_t size, QImage *dst)
 	case MJPEG:
 		dst->loadFromData(src->data(0).data(), size, "JPEG");
 		break;
-	case YUV:
-		convertYUV(src, dst->bits());
-		break;
 	case RGB:
 		convertRGB(src, dst->bits());
 		break;
-	case NV:
-		convertNV(src, dst->bits());
+	case YUVPacked:
+		convertYUVPacked(src, dst->bits());
+		break;
+	case YUVSemiPlanar:
+		convertYUVSemiPlanar(src, dst->bits());
 		break;
 	};
 }
@@ -185,47 +185,6 @@  static void yuv_to_rgb(int y, int u, int v, int *r, int *g, int *b)
 	*b = CLIP(( 298 * c + 516 * d           + 128) >> RGBSHIFT);
 }
 
-void FormatConverter::convertNV(const Image *srcImage, unsigned char *dst)
-{
-	unsigned int c_stride = stride_ * (2 / horzSubSample_);
-	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
-	unsigned int cb_pos = nvSwap_ ? 1 : 0;
-	unsigned int cr_pos = nvSwap_ ? 0 : 1;
-	const unsigned char *src = srcImage->data(0).data();
-	const unsigned char *src_c = srcImage->data(1).data();
-	int r, g, b;
-
-	for (unsigned int y = 0; y < height_; y++) {
-		const unsigned char *src_y = src + y * stride_;
-		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
-					      c_stride + cb_pos;
-		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
-					      c_stride + cr_pos;
-
-		for (unsigned int x = 0; x < width_; x += 2) {
-			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
-			dst[0] = b;
-			dst[1] = g;
-			dst[2] = r;
-			dst[3] = 0xff;
-			src_y++;
-			src_cb += c_inc;
-			src_cr += c_inc;
-			dst += 4;
-
-			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
-			dst[0] = b;
-			dst[1] = g;
-			dst[2] = r;
-			dst[3] = 0xff;
-			src_y++;
-			src_cb += 2;
-			src_cr += 2;
-			dst += 4;
-		}
-	}
-}
-
 void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
 {
 	const unsigned char *src = srcImage->data(0).data();
@@ -249,7 +208,7 @@  void FormatConverter::convertRGB(const Image *srcImage, unsigned char *dst)
 	}
 }
 
-void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
+void FormatConverter::convertYUVPacked(const Image *srcImage, unsigned char *dst)
 {
 	const unsigned char *src = srcImage->data(0).data();
 	unsigned int src_x, src_y, dst_x, dst_y;
@@ -287,3 +246,44 @@  void FormatConverter::convertYUV(const Image *srcImage, unsigned char *dst)
 		}
 	}
 }
+
+void FormatConverter::convertYUVSemiPlanar(const Image *srcImage, unsigned char *dst)
+{
+	unsigned int c_stride = stride_ * (2 / horzSubSample_);
+	unsigned int c_inc = horzSubSample_ == 1 ? 2 : 0;
+	unsigned int cb_pos = nvSwap_ ? 1 : 0;
+	unsigned int cr_pos = nvSwap_ ? 0 : 1;
+	const unsigned char *src = srcImage->data(0).data();
+	const unsigned char *src_c = srcImage->data(1).data();
+	int r, g, b;
+
+	for (unsigned int y = 0; y < height_; y++) {
+		const unsigned char *src_y = src + y * stride_;
+		const unsigned char *src_cb = src_c + (y / vertSubSample_) *
+					      c_stride + cb_pos;
+		const unsigned char *src_cr = src_c + (y / vertSubSample_) *
+					      c_stride + cr_pos;
+
+		for (unsigned int x = 0; x < width_; x += 2) {
+			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			src_y++;
+			src_cb += c_inc;
+			src_cr += c_inc;
+			dst += 4;
+
+			yuv_to_rgb(*src_y, *src_cb, *src_cr, &r, &g, &b);
+			dst[0] = b;
+			dst[1] = g;
+			dst[2] = r;
+			dst[3] = 0xff;
+			src_y++;
+			src_cb += 2;
+			src_cr += 2;
+			dst += 4;
+		}
+	}
+}
diff --git a/src/qcam/format_converter.h b/src/qcam/format_converter.h
index bb04aa959a1a..9ce2cc6da7f2 100644
--- a/src/qcam/format_converter.h
+++ b/src/qcam/format_converter.h
@@ -27,14 +27,14 @@  public:
 private:
 	enum FormatFamily {
 		MJPEG,
-		NV,
 		RGB,
-		YUV,
+		YUVPacked,
+		YUVSemiPlanar,
 	};
 
-	void convertNV(const Image *src, unsigned char *dst);
 	void convertRGB(const Image *src, unsigned char *dst);
-	void convertYUV(const Image *src, unsigned char *dst);
+	void convertYUVPacked(const Image *src, unsigned char *dst);
+	void convertYUVSemiPlanar(const Image *src, unsigned char *dst);
 
 	libcamera::PixelFormat format_;
 	unsigned int width_;