cam: Convert RGB variations to BGR
diff mbox series

Message ID Z/t0DbvYXAjjRmVB@duo.ucw.cz
State New
Headers show
Series
  • cam: Convert RGB variations to BGR
Related show

Commit Message

Pavel Machek April 13, 2025, 8:21 a.m. UTC
swIsp with libcamera produces ARGB data by default, which needs
conversion during write. Implement it and similar conversions.
    
Signed-off-by: Pavel Machek <pavel@ucw.cz>

Comments

Laurent Pinchart April 13, 2025, 11:53 p.m. UTC | #1
Hi Pavel,

Thank you for the patch.

On Sun, Apr 13, 2025 at 10:21:33AM +0200, Pavel Machek wrote:
> swIsp with libcamera produces ARGB data by default, which needs
> conversion during write. Implement it and similar conversions.
>     
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
> index 368de8bf..ddbd3263 100644
> --- a/src/apps/common/ppm_writer.cpp
> +++ b/src/apps/common/ppm_writer.cpp
> @@ -20,8 +20,56 @@ int PPMWriter::write(const char *filename,
>  		     const StreamConfiguration &config,
>  		     const Span<uint8_t> &data)
>  {
> -	if (config.pixelFormat != formats::BGR888) {
> -		std::cerr << "Only BGR888 output pixel format is supported ("
> +	int r_pos, g_pos, b_pos, bpp;
> +	switch (config.pixelFormat) {
> +	case libcamera::formats::R8:
> +		r_pos = 0;
> +		g_pos = 0;
> +		b_pos = 0;
> +		bpp = 1;
> +		break;
> +	case libcamera::formats::RGB888:
> +		r_pos = 2;
> +		g_pos = 1;
> +		b_pos = 0;
> +		bpp = 3;
> +		break;
> +	case libcamera::formats::BGR888:
> +		r_pos = 0;
> +		g_pos = 1;
> +		b_pos = 2;
> +		bpp = 3;
> +		break;
> +	case libcamera::formats::ARGB8888:
> +	case libcamera::formats::XRGB8888:
> +		r_pos = 2;
> +		g_pos = 1;
> +		b_pos = 0;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::RGBA8888:
> +	case libcamera::formats::RGBX8888:
> +		r_pos = 3;
> +		g_pos = 2;
> +		b_pos = 1;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::ABGR8888:
> +	case libcamera::formats::XBGR8888:
> +		r_pos = 0;
> +		g_pos = 1;
> +		b_pos = 2;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::BGRA8888:
> +	case libcamera::formats::BGRX8888:
> +		r_pos = 1;
> +		g_pos = 2;
> +		b_pos = 3;
> +		bpp = 4;
> +		break;
> +	default:
> +		std::cerr << "Only RGB output pixel formats are supported ("
>  			  << config.pixelFormat << " requested)" << std::endl;
>  		return -EINVAL;
>  	}
> @@ -41,14 +89,35 @@ int PPMWriter::write(const char *filename,
>  	}
>  
>  	const unsigned int rowLength = config.size.width * 3;
> -	const char *row = reinterpret_cast<const char *>(data.data());
> -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> -		output.write(row, rowLength);
> +	// Output without any conversion will be slightly faster

We use C-style coments.

> +	if (config.pixelFormat == formats::BGR888) {
> +		const char *row = reinterpret_cast<const char *>(data.data());
> +		for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> +			output.write(row, rowLength);
> +			if (!output) {
> +				std::cerr << "Failed to write image data at row " << y << std::endl;
> +				return -EIO;
> +			}
> +		}
> +		return 0;
> +	}
> +
> +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
> +	// Create a temporary buffer to hold one output row.
> +	std::vector<char> rowBuffer(rowLength);
> +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
> +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
> +		for (unsigned int x = 0; x < config.size.width; x++) {
> +			const uint8_t *pixel = row + x * bpp;
> +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
> +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
> +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
> +		}

I'm curious, what's the performance impact of the conversion ?

> +		output.write(rowBuffer.data(), rowLength);
>  		if (!output) {
>  			std::cerr << "Failed to write image data at row " << y << std::endl;
>  			return -EIO;
>  		}
>  	}
> -
>  	return 0;
>  }
>
Pavel Machek April 19, 2025, 10:05 a.m. UTC | #2
Hi!

> Thank you for the patch.
> 
> On Sun, Apr 13, 2025 at 10:21:33AM +0200, Pavel Machek wrote:
> > swIsp with libcamera produces ARGB data by default, which needs
> > conversion during write. Implement it and similar conversions.
> >     
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>

> > +	case libcamera::formats::BGRA8888:
> > +	case libcamera::formats::BGRX8888:
> > +		r_pos = 1;
> > +		g_pos = 2;
> > +		b_pos = 3;
> > +		bpp = 4;
> > +		break;
> > +	default:
> > +		std::cerr << "Only RGB output pixel formats are supported ("
> >  			  << config.pixelFormat << " requested)" << std::endl;
> >  		return -EINVAL;
> >  	}
> > @@ -41,14 +89,35 @@ int PPMWriter::write(const char *filename,
> >  	}
> >  
> >  	const unsigned int rowLength = config.size.width * 3;
> > -	const char *row = reinterpret_cast<const char *>(data.data());
> > -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> > -		output.write(row, rowLength);
> > +	// Output without any conversion will be slightly faster
> 
> We use C-style coments.

Can you adjust that while applying?

> > +	if (config.pixelFormat == formats::BGR888) {
> > +		const char *row = reinterpret_cast<const char *>(data.data());
> > +		for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> > +			output.write(row, rowLength);
> > +			if (!output) {
> > +				std::cerr << "Failed to write image data at row " << y << std::endl;
> > +				return -EIO;
> > +			}
> > +		}
> > +		return 0;
> > +	}
> > +
> > +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
> > +	// Create a temporary buffer to hold one output row.
> > +	std::vector<char> rowBuffer(rowLength);
> > +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
> > +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
> > +		for (unsigned int x = 0; x < config.size.width; x++) {
> > +			const uint8_t *pixel = row + x * bpp;
> > +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
> > +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
> > +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
> > +		}
> 
> I'm curious, what's the performance impact of the conversion ?

I hacked Librem 5 a lot to get libcamera working, and previous config
was not working at all, so ... I can't easily benchmark that.

So, this one is not free as it does one more copy of image
data. Compared to swIsp, it is going to be lost in the noise.

There's easy optimalization in the original case not to do writes per
row if width == stride. There are other options, but they will
complicate the code.

Best regards,
								Pavel
Laurent Pinchart April 21, 2025, 2:16 p.m. UTC | #3
Hi Pavel,

On Sat, Apr 19, 2025 at 12:05:34PM +0200, Pavel Machek wrote:
> Hi!
> 
> > Thank you for the patch.
> > 
> > On Sun, Apr 13, 2025 at 10:21:33AM +0200, Pavel Machek wrote:
> > > swIsp with libcamera produces ARGB data by default, which needs
> > > conversion during write. Implement it and similar conversions.
> > >     
> > > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> > > +	case libcamera::formats::BGRA8888:
> > > +	case libcamera::formats::BGRX8888:
> > > +		r_pos = 1;
> > > +		g_pos = 2;
> > > +		b_pos = 3;
> > > +		bpp = 4;
> > > +		break;
> > > +	default:
> > > +		std::cerr << "Only RGB output pixel formats are supported ("
> > >  			  << config.pixelFormat << " requested)" << std::endl;
> > >  		return -EINVAL;
> > >  	}
> > > @@ -41,14 +89,35 @@ int PPMWriter::write(const char *filename,
> > >  	}
> > >  
> > >  	const unsigned int rowLength = config.size.width * 3;
> > > -	const char *row = reinterpret_cast<const char *>(data.data());
> > > -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> > > -		output.write(row, rowLength);
> > > +	// Output without any conversion will be slightly faster
> > 
> > We use C-style coments.
> 
> Can you adjust that while applying?

If that's the only change required, yes.

> > > +	if (config.pixelFormat == formats::BGR888) {
> > > +		const char *row = reinterpret_cast<const char *>(data.data());
> > > +		for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> > > +			output.write(row, rowLength);
> > > +			if (!output) {
> > > +				std::cerr << "Failed to write image data at row " << y << std::endl;
> > > +				return -EIO;
> > > +			}
> > > +		}
> > > +		return 0;
> > > +	}
> > > +
> > > +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
> > > +	// Create a temporary buffer to hold one output row.
> > > +	std::vector<char> rowBuffer(rowLength);
> > > +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
> > > +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
> > > +		for (unsigned int x = 0; x < config.size.width; x++) {
> > > +			const uint8_t *pixel = row + x * bpp;
> > > +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
> > > +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
> > > +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
> > > +		}
> > 
> > I'm curious, what's the performance impact of the conversion ?
> 
> I hacked Librem 5 a lot to get libcamera working, and previous config
> was not working at all, so ... I can't easily benchmark that.
> 
> So, this one is not free as it does one more copy of image
> data. Compared to swIsp, it is going to be lost in the noise.
> 
> There's easy optimalization in the original case not to do writes per
> row if width == stride. There are other options, but they will
> complicate the code.

I wonder if it would make sense for the soft ISP to support other RGB
formats. CC'ing Milan and Hans to get their opinion.
Pavel Machek April 21, 2025, 9:15 p.m. UTC | #4
Hi!

> > > > swIsp with libcamera produces ARGB data by default, which needs
> > > > conversion during write. Implement it and similar conversions.
> > > >     
> > > > Signed-off-by: Pavel Machek <pavel@ucw.cz>

> > > > -	const char *row = reinterpret_cast<const char *>(data.data());
> > > > -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> > > > -		output.write(row, rowLength);
> > > > +	// Output without any conversion will be slightly faster
> > > 
> > > We use C-style coments.
> > 
> > Can you adjust that while applying?
> 
> If that's the only change required, yes.

Thanks!

> > > > +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
> > > > +	// Create a temporary buffer to hold one output row.
> > > > +	std::vector<char> rowBuffer(rowLength);
> > > > +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
> > > > +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
> > > > +		for (unsigned int x = 0; x < config.size.width; x++) {
> > > > +			const uint8_t *pixel = row + x * bpp;
> > > > +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
> > > > +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
> > > > +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
> > > > +		}
> > > 
> > > I'm curious, what's the performance impact of the conversion ?
> > 
> > I hacked Librem 5 a lot to get libcamera working, and previous config
> > was not working at all, so ... I can't easily benchmark that.
> > 
> > So, this one is not free as it does one more copy of image
> > data. Compared to swIsp, it is going to be lost in the noise.
> > 
> > There's easy optimalization in the original case not to do writes per
> > row if width == stride. There are other options, but they will
> > complicate the code.
> 
> I wonder if it would make sense for the soft ISP to support other RGB
> formats. CC'ing Milan and Hans to get their opinion.

From looking at the code, soft ISP seemed to be able to do other stuff
than ARGB, but for some reason ARGB is selected by default, and I
guess I can get better performance by forcing right format.

Conversion will still be useful for when hardware produces some other
permutation than BGR.

Best regards,
								Pavel
Barnabás Pőcze April 23, 2025, 7:52 a.m. UTC | #5
Hi


2025. 04. 13. 10:21 keltezéssel, Pavel Machek írta:
> swIsp with libcamera produces ARGB data by default, which needs
> conversion during write. Implement it and similar conversions.
>      
> Signed-off-by: Pavel Machek <pavel@ucw.cz>
> 
> diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
> index 368de8bf..ddbd3263 100644
> --- a/src/apps/common/ppm_writer.cpp
> +++ b/src/apps/common/ppm_writer.cpp
> @@ -20,8 +20,56 @@ int PPMWriter::write(const char *filename,
>   		     const StreamConfiguration &config,
>   		     const Span<uint8_t> &data)
>   {
> -	if (config.pixelFormat != formats::BGR888) {
> -		std::cerr << "Only BGR888 output pixel format is supported ("
> +	int r_pos, g_pos, b_pos, bpp;

I think the "pos" variables should probably be camelCase.
And please add an empty line after them.


> +	switch (config.pixelFormat) {
> +	case libcamera::formats::R8:
> +		r_pos = 0;
> +		g_pos = 0;
> +		b_pos = 0;
> +		bpp = 1;
> +		break;
> +	case libcamera::formats::RGB888:
> +		r_pos = 2;
> +		g_pos = 1;
> +		b_pos = 0;
> +		bpp = 3;
> +		break;
> +	case libcamera::formats::BGR888:
> +		r_pos = 0;
> +		g_pos = 1;
> +		b_pos = 2;
> +		bpp = 3;
> +		break;
> +	case libcamera::formats::ARGB8888:
> +	case libcamera::formats::XRGB8888:
> +		r_pos = 2;
> +		g_pos = 1;
> +		b_pos = 0;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::RGBA8888:
> +	case libcamera::formats::RGBX8888:
> +		r_pos = 3;
> +		g_pos = 2;
> +		b_pos = 1;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::ABGR8888:
> +	case libcamera::formats::XBGR8888:
> +		r_pos = 0;
> +		g_pos = 1;
> +		b_pos = 2;
> +		bpp = 4;
> +		break;
> +	case libcamera::formats::BGRA8888:
> +	case libcamera::formats::BGRX8888:
> +		r_pos = 1;
> +		g_pos = 2;
> +		b_pos = 3;
> +		bpp = 4;
> +		break;
> +	default:
> +		std::cerr << "Only RGB output pixel formats are supported ("
>   			  << config.pixelFormat << " requested)" << std::endl;
>   		return -EINVAL;
>   	}
> @@ -41,14 +89,35 @@ int PPMWriter::write(const char *filename,
>   	}
>   
>   	const unsigned int rowLength = config.size.width * 3;
> -	const char *row = reinterpret_cast<const char *>(data.data());
> -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> -		output.write(row, rowLength);
> +	// Output without any conversion will be slightly faster
> +	if (config.pixelFormat == formats::BGR888) {
> +		const char *row = reinterpret_cast<const char *>(data.data());
> +		for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
> +			output.write(row, rowLength);
> +			if (!output) {
> +				std::cerr << "Failed to write image data at row " << y << std::endl;
> +				return -EIO;
> +			}
> +		}
> +		return 0;
> +	}
> +
> +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4

What is the reasoning for saving this into a separate variable here? But not above in
the `BGR888` case? And not `config.size`?

And why "should be >= width * 4"?


> +	// Create a temporary buffer to hold one output row.
> +	std::vector<char> rowBuffer(rowLength);
> +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
> +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
> +		for (unsigned int x = 0; x < config.size.width; x++) {
> +			const uint8_t *pixel = row + x * bpp;
> +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
> +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
> +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
> +		}

Empty line here please.


> +		output.write(rowBuffer.data(), rowLength);
>   		if (!output) {
>   			std::cerr << "Failed to write image data at row " << y << std::endl;
>   			return -EIO;
>   		}
>   	}
> -

Don't remove this.


>   	return 0;
>   }
> 


As for performance, I have briefly taken a look at the x86 assembly of the inner
loop, it looks pretty acceptable to me. Apart from the fact that the vector's
operator[] adds three branches because meson sets `_GLIBCXX_ASSERTIONS=1` unless
`b_ndebug=true`. So that is not too good, but it is probably not too significant.

But whether or not the soft ISP will support other formats, I think it's useful to
support multiple formats here, even at the cost of conversion. Especially since PPM
is not a container or such, so if the user explicitly selects it, I think they
expect that anything coming out of the camera will be converted appropriately.

In addition, it appears to me that qcam already has a sizable set of format
conversions implemented. I wonder if those could be used somehow.


Regards,
Barnabás Pőcze
Milan Zamazal April 26, 2025, 6:30 p.m. UTC | #6
Hi Pavel,

Pavel Machek <pavel@ucw.cz> writes:

> Hi!
>
>> > > > swIsp with libcamera produces ARGB data by default, which needs
>> > > > conversion during write. Implement it and similar conversions.
>> > > >     
>> > > > Signed-off-by: Pavel Machek <pavel@ucw.cz>
>
>> > > > -	const char *row = reinterpret_cast<const char *>(data.data());
>> > > > -	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
>> > > > -		output.write(row, rowLength);
>> > > > +	// Output without any conversion will be slightly faster
>> > > 
>> > > We use C-style coments.
>> > 
>> > Can you adjust that while applying?
>> 
>> If that's the only change required, yes.
>
> Thanks!
>
>> > > > +	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
>> > > > +	// Create a temporary buffer to hold one output row.
>> > > > +	std::vector<char> rowBuffer(rowLength);
>> > > > +	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
>> > > > +	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
>> > > > +		for (unsigned int x = 0; x < config.size.width; x++) {
>> > > > +			const uint8_t *pixel = row + x * bpp;
>> > > > +			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
>> > > > +			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
>> > > > +			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
>> > > > +		}
>> > > 
>> > > I'm curious, what's the performance impact of the conversion ?
>> > 
>> > I hacked Librem 5 a lot to get libcamera working, and previous config
>> > was not working at all, so ... I can't easily benchmark that.
>> > 
>> > So, this one is not free as it does one more copy of image
>> > data. Compared to swIsp, it is going to be lost in the noise.
>> > 
>> > There's easy optimalization in the original case not to do writes per
>> > row if width == stride. There are other options, but they will
>> > complicate the code.
>> 
>> I wonder if it would make sense for the soft ISP to support other RGB
>> formats. CC'ing Milan and Hans to get their opinion.
>
> From looking at the code, soft ISP seemed to be able to do other stuff
> than ARGB, but for some reason ARGB is selected by default, and I
> guess I can get better performance by forcing right format.

Software ISP debayering supports multiple output formats and `cam' can
request a particular output format.  For example,

  cam -c1 -C4 -s pixelformat=BGR888 -Ffoo#.ppm

outputs the right thing.  Example of requesting an output in a different
format:

  cam -c1 -C4 -s pixelformat=ARGB8888 -Ffoo#.raw

It works; is it a problem in your application?

I don't mind enhancing the PPM writer but maybe you don't need the
conversion there in the first place.

> Conversion will still be useful for when hardware produces some other
> permutation than BGR.
>
> Best regards,
> 								Pavel
Pavel Machek May 2, 2025, 8:07 p.m. UTC | #7
Hi!

> > From looking at the code, soft ISP seemed to be able to do other stuff
> > than ARGB, but for some reason ARGB is selected by default, and I
> > guess I can get better performance by forcing right format.
> 
> Software ISP debayering supports multiple output formats and `cam' can
> request a particular output format.  For example,
> 
>   cam -c1 -C4 -s pixelformat=BGR888 -Ffoo#.ppm
> 
> outputs the right thing.  Example of requesting an output in a different
> format:
> 
>   cam -c1 -C4 -s pixelformat=ARGB8888 -Ffoo#.raw
> 
> It works; is it a problem in your application?

Well, problem is that if you do something like "cam -c1 -Ffoo#.ppm" it
decides to use ARGB8888 and fails writing into ppm. That is not great.

> I don't mind enhancing the PPM writer but maybe you don't need the
> conversion there in the first place.

Agreed, I don't need it. People with hardware outputting RGB888 or
ARGB variants might like it.

Best regards,
								Pavel
Pavel Machek May 2, 2025, 8:12 p.m. UTC | #8
Hi!

> > swIsp with libcamera produces ARGB data by default, which needs
> > conversion during write. Implement it and similar conversions.
> > Signed-off-by: Pavel Machek <pavel@ucw.cz>
> > 
> > diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
> > index 368de8bf..ddbd3263 100644
> > --- a/src/apps/common/ppm_writer.cpp
> > +++ b/src/apps/common/ppm_writer.cpp
> > @@ -20,8 +20,56 @@ int PPMWriter::write(const char *filename,
> >   		     const StreamConfiguration &config,
> >   		     const Span<uint8_t> &data)
> >   {
> > -	if (config.pixelFormat != formats::BGR888) {
> > -		std::cerr << "Only BGR888 output pixel format is supported ("
> > +	int r_pos, g_pos, b_pos, bpp;
> 
> I think the "pos" variables should probably be camelCase.
> And please add an empty line after them.

...thanks for comments, I can fix the style. Laurent, is there still
interest in this patch? swIsp does not really need it, but some
hardware might and tiff writer has similar conversions.

> As for performance, I have briefly taken a look at the x86 assembly of the inner
> loop, it looks pretty acceptable to me. Apart from the fact that the vector's
> operator[] adds three branches because meson sets `_GLIBCXX_ASSERTIONS=1` unless
> `b_ndebug=true`. So that is not too good, but it is probably not too significant.

Well, I strongly suspect reducing ammount of write syscalls (writev)
would help significantly. But that's already problem with existing
code and would make code more complex.

> But whether or not the soft ISP will support other formats, I think it's useful to
> support multiple formats here, even at the cost of conversion. Especially since PPM
> is not a container or such, so if the user explicitly selects it, I think they
> expect that anything coming out of the camera will be converted appropriately.
> 
> In addition, it appears to me that qcam already has a sizable set of format
> conversions implemented. I wonder if those could be used somehow.

Ok, someone let me know if I should clean this up and retry.

Best regards,
									Pavel

Patch
diff mbox series

diff --git a/src/apps/common/ppm_writer.cpp b/src/apps/common/ppm_writer.cpp
index 368de8bf..ddbd3263 100644
--- a/src/apps/common/ppm_writer.cpp
+++ b/src/apps/common/ppm_writer.cpp
@@ -20,8 +20,56 @@  int PPMWriter::write(const char *filename,
 		     const StreamConfiguration &config,
 		     const Span<uint8_t> &data)
 {
-	if (config.pixelFormat != formats::BGR888) {
-		std::cerr << "Only BGR888 output pixel format is supported ("
+	int r_pos, g_pos, b_pos, bpp;
+	switch (config.pixelFormat) {
+	case libcamera::formats::R8:
+		r_pos = 0;
+		g_pos = 0;
+		b_pos = 0;
+		bpp = 1;
+		break;
+	case libcamera::formats::RGB888:
+		r_pos = 2;
+		g_pos = 1;
+		b_pos = 0;
+		bpp = 3;
+		break;
+	case libcamera::formats::BGR888:
+		r_pos = 0;
+		g_pos = 1;
+		b_pos = 2;
+		bpp = 3;
+		break;
+	case libcamera::formats::ARGB8888:
+	case libcamera::formats::XRGB8888:
+		r_pos = 2;
+		g_pos = 1;
+		b_pos = 0;
+		bpp = 4;
+		break;
+	case libcamera::formats::RGBA8888:
+	case libcamera::formats::RGBX8888:
+		r_pos = 3;
+		g_pos = 2;
+		b_pos = 1;
+		bpp = 4;
+		break;
+	case libcamera::formats::ABGR8888:
+	case libcamera::formats::XBGR8888:
+		r_pos = 0;
+		g_pos = 1;
+		b_pos = 2;
+		bpp = 4;
+		break;
+	case libcamera::formats::BGRA8888:
+	case libcamera::formats::BGRX8888:
+		r_pos = 1;
+		g_pos = 2;
+		b_pos = 3;
+		bpp = 4;
+		break;
+	default:
+		std::cerr << "Only RGB output pixel formats are supported ("
 			  << config.pixelFormat << " requested)" << std::endl;
 		return -EINVAL;
 	}
@@ -41,14 +89,35 @@  int PPMWriter::write(const char *filename,
 	}
 
 	const unsigned int rowLength = config.size.width * 3;
-	const char *row = reinterpret_cast<const char *>(data.data());
-	for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
-		output.write(row, rowLength);
+	// Output without any conversion will be slightly faster
+	if (config.pixelFormat == formats::BGR888) {
+		const char *row = reinterpret_cast<const char *>(data.data());
+		for (unsigned int y = 0; y < config.size.height; y++, row += config.stride) {
+			output.write(row, rowLength);
+			if (!output) {
+				std::cerr << "Failed to write image data at row " << y << std::endl;
+				return -EIO;
+			}
+		}
+		return 0;
+	}
+
+	const unsigned int inputRowBytes = config.stride; // should be >= width * 4
+	// Create a temporary buffer to hold one output row.
+	std::vector<char> rowBuffer(rowLength);
+	const uint8_t *row = reinterpret_cast<const uint8_t *>(data.data());
+	for (unsigned int y = 0; y < config.size.height; y++, row += inputRowBytes) {
+		for (unsigned int x = 0; x < config.size.width; x++) {
+			const uint8_t *pixel = row + x * bpp;
+			rowBuffer[x * 3 + 0] = static_cast<char>(pixel[r_pos]);
+			rowBuffer[x * 3 + 1] = static_cast<char>(pixel[g_pos]);
+			rowBuffer[x * 3 + 2] = static_cast<char>(pixel[b_pos]);
+		}
+		output.write(rowBuffer.data(), rowLength);
 		if (!output) {
 			std::cerr << "Failed to write image data at row " << y << std::endl;
 			return -EIO;
 		}
 	}
-
 	return 0;
 }