[libcamera-devel,10/14] py: implement PixelFormat class
diff mbox series

Message ID 20220516141022.96327-11-tomi.valkeinen@ideasonboard.com
State Superseded
Headers show
Series
  • Misc Python bindings patches
Related show

Commit Message

Tomi Valkeinen May 16, 2022, 2:10 p.m. UTC
Implement PixelFormat bindings properly with a PixelFormat class. Change
the bindings to use the new class instead of a string.

Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
---
 src/py/cam/cam.py              |  4 +--
 src/py/cam/cam_kms.py          |  9 +-----
 src/py/cam/cam_qt.py           |  2 +-
 src/py/cam/cam_qtgl.py         | 17 +-----------
 src/py/cam/gl_helpers.py       |  8 ------
 src/py/libcamera/pymain.cpp    | 51 ++++++++++++++++++++--------------
 src/py/libcamera/utils/conv.py |  2 ++
 7 files changed, 37 insertions(+), 56 deletions(-)

Comments

Laurent Pinchart May 17, 2022, 8:32 a.m. UTC | #1
Hi Tomi,

Thank you for the patch.

On Mon, May 16, 2022 at 05:10:18PM +0300, Tomi Valkeinen wrote:
> Implement PixelFormat bindings properly with a PixelFormat class. Change
> the bindings to use the new class instead of a string.
> 
> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> ---
>  src/py/cam/cam.py              |  4 +--
>  src/py/cam/cam_kms.py          |  9 +-----
>  src/py/cam/cam_qt.py           |  2 +-
>  src/py/cam/cam_qtgl.py         | 17 +-----------
>  src/py/cam/gl_helpers.py       |  8 ------
>  src/py/libcamera/pymain.cpp    | 51 ++++++++++++++++++++--------------
>  src/py/libcamera/utils/conv.py |  2 ++
>  7 files changed, 37 insertions(+), 56 deletions(-)
> 
> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
> index c7da97d7..63c67126 100755
> --- a/src/py/cam/cam.py
> +++ b/src/py/cam/cam.py
> @@ -80,7 +80,7 @@ def do_cmd_info(ctx):
>  
>          formats = stream_config.formats
>          for fmt in formats.pixel_formats:
> -            print('\t * Pixelformat:', fmt, formats.range(fmt))
> +            print('\t * Pixelformat:', fmt.name, formats.range(fmt))
>  
>              for size in formats.sizes(fmt):
>                  print('\t  -', size)
> @@ -164,7 +164,7 @@ def configure(ctx):
>              stream_config.size = (stream_opts['width'], stream_opts['height'])
>  
>          if 'pixelformat' in stream_opts:
> -            stream_config.pixel_format = stream_opts['pixelformat']
> +            stream_config.pixel_format = libcam.PixelFormat.from_name(stream_opts['pixelformat'])
>  
>      stat = camconfig.validate()
>  
> diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py
> index ae6be277..7d11564b 100644
> --- a/src/py/cam/cam_kms.py
> +++ b/src/py/cam/cam_kms.py
> @@ -5,13 +5,6 @@ import pykms
>  import selectors
>  import sys
>  
> -FMT_MAP = {
> -    'RGB888': pykms.PixelFormat.RGB888,
> -    'YUYV': pykms.PixelFormat.YUYV,
> -    'ARGB8888': pykms.PixelFormat.ARGB8888,
> -    'XRGB8888': pykms.PixelFormat.XRGB8888,
> -}
> -
>  
>  class KMSRenderer:
>      def __init__(self, state):
> @@ -118,7 +111,7 @@ class KMSRenderer:
>  
>                  cfg = stream.configuration
>                  fmt = cfg.pixel_format
> -                fmt = FMT_MAP[fmt]
> +                fmt = pykms.PixelFormat(fmt.fourcc)
>  
>                  plane = self.resman.reserve_generic_plane(self.crtc, fmt)
>                  assert(plane is not None)
> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
> index dc5219eb..64f49f33 100644
> --- a/src/py/cam/cam_qt.py
> +++ b/src/py/cam/cam_qt.py
> @@ -139,7 +139,7 @@ class MainWindow(QtWidgets.QWidget):
>              w, h = cfg.size
>              pitch = cfg.stride
>  
> -            if cfg.pixel_format == 'MJPEG':
> +            if cfg.pixel_format.name == 'MJPEG':
>                  img = Image.open(BytesIO(mfb.planes[0]))
>                  qim = ImageQt(img).copy()
>                  pix = QtGui.QPixmap.fromImage(qim)
> diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
> index 8a95994e..261accb8 100644
> --- a/src/py/cam/cam_qtgl.py
> +++ b/src/py/cam/cam_qtgl.py
> @@ -30,14 +30,6 @@ from OpenGL.GL import shaders
>  
>  from gl_helpers import *
>  
> -# libcamera format string -> DRM fourcc
> -FMT_MAP = {
> -    'RGB888': 'RG24',
> -    'XRGB8888': 'XR24',
> -    'ARGB8888': 'AR24',
> -    'YUYV': 'YUYV',
> -}
> -
>  
>  class EglState:
>      def __init__(self):
> @@ -204,12 +196,6 @@ class MainWindow(QtWidgets.QWidget):
>              self.current[ctx['idx']] = []
>  
>              for stream in ctx['streams']:
> -                fmt = stream.configuration.pixel_format
> -                size = stream.configuration.size
> -
> -                if fmt not in FMT_MAP:
> -                    raise Exception('Unsupported pixel format: ' + str(fmt))
> -
>                  self.textures[stream] = None
>  
>          num_tiles = len(self.textures)
> @@ -281,8 +267,7 @@ class MainWindow(QtWidgets.QWidget):
>  
>      def create_texture(self, stream, fb):
>          cfg = stream.configuration
> -        fmt = cfg.pixel_format
> -        fmt = str_to_fourcc(FMT_MAP[fmt])
> +        fmt = cfg.pixel_format.fourcc
>          w, h = cfg.size
>  
>          attribs = [
> diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py
> index ac5e6889..53b3e9df 100644
> --- a/src/py/cam/gl_helpers.py
> +++ b/src/py/cam/gl_helpers.py
> @@ -30,14 +30,6 @@ def getglEGLImageTargetTexture2DOES():
>  
>  glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()
>  
> -# \todo This can be dropped when we have proper PixelFormat bindings
> -def str_to_fourcc(str):
> -    assert(len(str) == 4)
> -    fourcc = 0
> -    for i, v in enumerate([ord(c) for c in str]):
> -        fourcc |= v << (i * 8)
> -    return fourcc
> -
>  
>  def get_gl_extensions():
>      n = GLint()
> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> index af22205e..cc2ddee5 100644
> --- a/src/py/libcamera/pymain.cpp
> +++ b/src/py/libcamera/pymain.cpp
> @@ -8,7 +8,6 @@
>  /*
>   * \todo Add geometry classes (Point, Rectangle...)
>   * \todo Add bindings for the ControlInfo class
> - * \todo Add bindings for the PixelFormat class
>   */
>  
>  #include <mutex>
> @@ -173,6 +172,7 @@ PYBIND11_MODULE(_libcamera, m)
>  	auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
>  	auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
>  	auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
> +	auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
>  
>  	/* Global functions */
>  	m.def("log_set_level", &logSetLevel);
> @@ -404,14 +404,7 @@ PYBIND11_MODULE(_libcamera, m)
>  				self.size.width = std::get<0>(size);
>  				self.size.height = std::get<1>(size);
>  			})
> -		.def_property(
> -			"pixel_format",
> -			[](StreamConfiguration &self) {
> -				return self.pixelFormat.toString();
> -			},
> -			[](StreamConfiguration &self, std::string fmt) {
> -				self.pixelFormat = PixelFormat::fromString(fmt);
> -			})
> +		.def_readwrite("pixel_format", &StreamConfiguration::pixelFormat)
>  		.def_readwrite("stride", &StreamConfiguration::stride)
>  		.def_readwrite("frame_size", &StreamConfiguration::frameSize)
>  		.def_readwrite("buffer_count", &StreamConfiguration::bufferCount)
> @@ -420,22 +413,15 @@ PYBIND11_MODULE(_libcamera, m)
>  		.def_readwrite("color_space", &StreamConfiguration::colorSpace);
>  
>  	pyStreamFormats
> -		.def_property_readonly("pixel_formats", [](StreamFormats &self) {
> -			std::vector<std::string> fmts;
> -			for (auto &fmt : self.pixelformats())
> -				fmts.push_back(fmt.toString());
> -			return fmts;
> -		})
> -		.def("sizes", [](StreamFormats &self, const std::string &pixelFormat) {
> -			auto fmt = PixelFormat::fromString(pixelFormat);
> +		.def_property_readonly("pixel_formats", &StreamFormats::pixelformats)
> +		.def("sizes", [](StreamFormats &self, const PixelFormat &pixelFormat) {
>  			std::vector<std::tuple<uint32_t, uint32_t>> fmts;
> -			for (const auto &s : self.sizes(fmt))
> +			for (const auto &s : self.sizes(pixelFormat))
>  				fmts.push_back(std::make_tuple(s.width, s.height));
>  			return fmts;
>  		})
> -		.def("range", [](StreamFormats &self, const std::string &pixelFormat) {
> -			auto fmt = PixelFormat::fromString(pixelFormat);
> -			const auto &range = self.range(fmt);
> +		.def("range", [](StreamFormats &self, const PixelFormat &pixelFormat) {
> +			const auto &range = self.range(pixelFormat);
>  			return make_tuple(std::make_tuple(range.hStep, range.vStep),
>  					  std::make_tuple(range.min.width, range.min.height),
>  					  std::make_tuple(range.max.width, range.max.height));
> @@ -648,4 +634,27 @@ PYBIND11_MODULE(_libcamera, m)
>  	pyColorSpaceRange
>  		.value("Full", ColorSpace::Range::Full)
>  		.value("Limited", ColorSpace::Range::Limited);
> +
> +	pyPixelFormat
> +		.def(py::init<>())
> +		.def(py::init<uint32_t, uint64_t>())
> +		.def_property_readonly("fourcc", &PixelFormat::fourcc)
> +		.def_property_readonly("modifier", &PixelFormat::modifier)
> +		.def_property_readonly("name", &PixelFormat::toString)
> +		.def(py::self == py::self)
> +		.def("__str__", [](const PixelFormat &self) {
> +			return "<libcamera.PixelFormat '" + self.toString() + "'>";
> +		})

I'd just return self.toString() here. We could also implement a __repr__
that prints "libcamera.PixelFormat('" + self.toString() + "')" if we add
a constructor that takes a string as __repr__ is supposed to return a
canonical string representation that can be run to recreate the object.

> +		.def_static("from_name", [](const std::string &name) {

Could we name this "from_string" to avoid diverging from the C++ API ?

> +			return PixelFormat::fromString(name);
> +		})
> +		.def_static("from_fourcc", [](const std::string &fourcc) {
> +			if (fourcc.size() != 4)
> +				throw std::invalid_argument("Invalid fourcc length");
> +
> +			uint32_t v = fourcc[0] | (fourcc[1] << 8) |
> +				     (fourcc[2] << 16) | (fourcc[3] << 24);
> +
> +			return PixelFormat(v);
> +		});

This isn't used in your series, so I'd drop it.

>  }
> diff --git a/src/py/libcamera/utils/conv.py b/src/py/libcamera/utils/conv.py
> index 2e483003..30f6733e 100644
> --- a/src/py/libcamera/utils/conv.py
> +++ b/src/py/libcamera/utils/conv.py
> @@ -76,6 +76,8 @@ def to_rgb(fmt, size, data):
>      w = size[0]
>      h = size[1]
>  
> +    fmt = fmt.name
> +
>      if fmt == 'YUYV':
>          # YUV422
>          yuyv = data.reshape((h, w // 2 * 4))
Tomi Valkeinen May 17, 2022, 8:53 a.m. UTC | #2
On 17/05/2022 11:32, Laurent Pinchart wrote:
> Hi Tomi,
> 
> Thank you for the patch.
> 
> On Mon, May 16, 2022 at 05:10:18PM +0300, Tomi Valkeinen wrote:
>> Implement PixelFormat bindings properly with a PixelFormat class. Change
>> the bindings to use the new class instead of a string.
>>
>> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
>> ---
>>   src/py/cam/cam.py              |  4 +--
>>   src/py/cam/cam_kms.py          |  9 +-----
>>   src/py/cam/cam_qt.py           |  2 +-
>>   src/py/cam/cam_qtgl.py         | 17 +-----------
>>   src/py/cam/gl_helpers.py       |  8 ------
>>   src/py/libcamera/pymain.cpp    | 51 ++++++++++++++++++++--------------
>>   src/py/libcamera/utils/conv.py |  2 ++
>>   7 files changed, 37 insertions(+), 56 deletions(-)
>>
>> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
>> index c7da97d7..63c67126 100755
>> --- a/src/py/cam/cam.py
>> +++ b/src/py/cam/cam.py
>> @@ -80,7 +80,7 @@ def do_cmd_info(ctx):
>>   
>>           formats = stream_config.formats
>>           for fmt in formats.pixel_formats:
>> -            print('\t * Pixelformat:', fmt, formats.range(fmt))
>> +            print('\t * Pixelformat:', fmt.name, formats.range(fmt))
>>   
>>               for size in formats.sizes(fmt):
>>                   print('\t  -', size)
>> @@ -164,7 +164,7 @@ def configure(ctx):
>>               stream_config.size = (stream_opts['width'], stream_opts['height'])
>>   
>>           if 'pixelformat' in stream_opts:
>> -            stream_config.pixel_format = stream_opts['pixelformat']
>> +            stream_config.pixel_format = libcam.PixelFormat.from_name(stream_opts['pixelformat'])
>>   
>>       stat = camconfig.validate()
>>   
>> diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py
>> index ae6be277..7d11564b 100644
>> --- a/src/py/cam/cam_kms.py
>> +++ b/src/py/cam/cam_kms.py
>> @@ -5,13 +5,6 @@ import pykms
>>   import selectors
>>   import sys
>>   
>> -FMT_MAP = {
>> -    'RGB888': pykms.PixelFormat.RGB888,
>> -    'YUYV': pykms.PixelFormat.YUYV,
>> -    'ARGB8888': pykms.PixelFormat.ARGB8888,
>> -    'XRGB8888': pykms.PixelFormat.XRGB8888,
>> -}
>> -
>>   
>>   class KMSRenderer:
>>       def __init__(self, state):
>> @@ -118,7 +111,7 @@ class KMSRenderer:
>>   
>>                   cfg = stream.configuration
>>                   fmt = cfg.pixel_format
>> -                fmt = FMT_MAP[fmt]
>> +                fmt = pykms.PixelFormat(fmt.fourcc)
>>   
>>                   plane = self.resman.reserve_generic_plane(self.crtc, fmt)
>>                   assert(plane is not None)
>> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
>> index dc5219eb..64f49f33 100644
>> --- a/src/py/cam/cam_qt.py
>> +++ b/src/py/cam/cam_qt.py
>> @@ -139,7 +139,7 @@ class MainWindow(QtWidgets.QWidget):
>>               w, h = cfg.size
>>               pitch = cfg.stride
>>   
>> -            if cfg.pixel_format == 'MJPEG':
>> +            if cfg.pixel_format.name == 'MJPEG':
>>                   img = Image.open(BytesIO(mfb.planes[0]))
>>                   qim = ImageQt(img).copy()
>>                   pix = QtGui.QPixmap.fromImage(qim)
>> diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
>> index 8a95994e..261accb8 100644
>> --- a/src/py/cam/cam_qtgl.py
>> +++ b/src/py/cam/cam_qtgl.py
>> @@ -30,14 +30,6 @@ from OpenGL.GL import shaders
>>   
>>   from gl_helpers import *
>>   
>> -# libcamera format string -> DRM fourcc
>> -FMT_MAP = {
>> -    'RGB888': 'RG24',
>> -    'XRGB8888': 'XR24',
>> -    'ARGB8888': 'AR24',
>> -    'YUYV': 'YUYV',
>> -}
>> -
>>   
>>   class EglState:
>>       def __init__(self):
>> @@ -204,12 +196,6 @@ class MainWindow(QtWidgets.QWidget):
>>               self.current[ctx['idx']] = []
>>   
>>               for stream in ctx['streams']:
>> -                fmt = stream.configuration.pixel_format
>> -                size = stream.configuration.size
>> -
>> -                if fmt not in FMT_MAP:
>> -                    raise Exception('Unsupported pixel format: ' + str(fmt))
>> -
>>                   self.textures[stream] = None
>>   
>>           num_tiles = len(self.textures)
>> @@ -281,8 +267,7 @@ class MainWindow(QtWidgets.QWidget):
>>   
>>       def create_texture(self, stream, fb):
>>           cfg = stream.configuration
>> -        fmt = cfg.pixel_format
>> -        fmt = str_to_fourcc(FMT_MAP[fmt])
>> +        fmt = cfg.pixel_format.fourcc
>>           w, h = cfg.size
>>   
>>           attribs = [
>> diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py
>> index ac5e6889..53b3e9df 100644
>> --- a/src/py/cam/gl_helpers.py
>> +++ b/src/py/cam/gl_helpers.py
>> @@ -30,14 +30,6 @@ def getglEGLImageTargetTexture2DOES():
>>   
>>   glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()
>>   
>> -# \todo This can be dropped when we have proper PixelFormat bindings
>> -def str_to_fourcc(str):
>> -    assert(len(str) == 4)
>> -    fourcc = 0
>> -    for i, v in enumerate([ord(c) for c in str]):
>> -        fourcc |= v << (i * 8)
>> -    return fourcc
>> -
>>   
>>   def get_gl_extensions():
>>       n = GLint()
>> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
>> index af22205e..cc2ddee5 100644
>> --- a/src/py/libcamera/pymain.cpp
>> +++ b/src/py/libcamera/pymain.cpp
>> @@ -8,7 +8,6 @@
>>   /*
>>    * \todo Add geometry classes (Point, Rectangle...)
>>    * \todo Add bindings for the ControlInfo class
>> - * \todo Add bindings for the PixelFormat class
>>    */
>>   
>>   #include <mutex>
>> @@ -173,6 +172,7 @@ PYBIND11_MODULE(_libcamera, m)
>>   	auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
>>   	auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
>>   	auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
>> +	auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
>>   
>>   	/* Global functions */
>>   	m.def("log_set_level", &logSetLevel);
>> @@ -404,14 +404,7 @@ PYBIND11_MODULE(_libcamera, m)
>>   				self.size.width = std::get<0>(size);
>>   				self.size.height = std::get<1>(size);
>>   			})
>> -		.def_property(
>> -			"pixel_format",
>> -			[](StreamConfiguration &self) {
>> -				return self.pixelFormat.toString();
>> -			},
>> -			[](StreamConfiguration &self, std::string fmt) {
>> -				self.pixelFormat = PixelFormat::fromString(fmt);
>> -			})
>> +		.def_readwrite("pixel_format", &StreamConfiguration::pixelFormat)
>>   		.def_readwrite("stride", &StreamConfiguration::stride)
>>   		.def_readwrite("frame_size", &StreamConfiguration::frameSize)
>>   		.def_readwrite("buffer_count", &StreamConfiguration::bufferCount)
>> @@ -420,22 +413,15 @@ PYBIND11_MODULE(_libcamera, m)
>>   		.def_readwrite("color_space", &StreamConfiguration::colorSpace);
>>   
>>   	pyStreamFormats
>> -		.def_property_readonly("pixel_formats", [](StreamFormats &self) {
>> -			std::vector<std::string> fmts;
>> -			for (auto &fmt : self.pixelformats())
>> -				fmts.push_back(fmt.toString());
>> -			return fmts;
>> -		})
>> -		.def("sizes", [](StreamFormats &self, const std::string &pixelFormat) {
>> -			auto fmt = PixelFormat::fromString(pixelFormat);
>> +		.def_property_readonly("pixel_formats", &StreamFormats::pixelformats)
>> +		.def("sizes", [](StreamFormats &self, const PixelFormat &pixelFormat) {
>>   			std::vector<std::tuple<uint32_t, uint32_t>> fmts;
>> -			for (const auto &s : self.sizes(fmt))
>> +			for (const auto &s : self.sizes(pixelFormat))
>>   				fmts.push_back(std::make_tuple(s.width, s.height));
>>   			return fmts;
>>   		})
>> -		.def("range", [](StreamFormats &self, const std::string &pixelFormat) {
>> -			auto fmt = PixelFormat::fromString(pixelFormat);
>> -			const auto &range = self.range(fmt);
>> +		.def("range", [](StreamFormats &self, const PixelFormat &pixelFormat) {
>> +			const auto &range = self.range(pixelFormat);
>>   			return make_tuple(std::make_tuple(range.hStep, range.vStep),
>>   					  std::make_tuple(range.min.width, range.min.height),
>>   					  std::make_tuple(range.max.width, range.max.height));
>> @@ -648,4 +634,27 @@ PYBIND11_MODULE(_libcamera, m)
>>   	pyColorSpaceRange
>>   		.value("Full", ColorSpace::Range::Full)
>>   		.value("Limited", ColorSpace::Range::Limited);
>> +
>> +	pyPixelFormat
>> +		.def(py::init<>())
>> +		.def(py::init<uint32_t, uint64_t>())
>> +		.def_property_readonly("fourcc", &PixelFormat::fourcc)
>> +		.def_property_readonly("modifier", &PixelFormat::modifier)
>> +		.def_property_readonly("name", &PixelFormat::toString)
>> +		.def(py::self == py::self)
>> +		.def("__str__", [](const PixelFormat &self) {
>> +			return "<libcamera.PixelFormat '" + self.toString() + "'>";
>> +		})
> 
> I'd just return self.toString() here. We could also implement a __repr__
> that prints "libcamera.PixelFormat('" + self.toString() + "')" if we add
> a constructor that takes a string as __repr__ is supposed to return a
> canonical string representation that can be run to recreate the object.

Yes. I missed __repr__ here. I think I implemented __str__ and __repr__ 
better with the geometry classes. I'll improve this.

>> +		.def_static("from_name", [](const std::string &name) {
> 
> Could we name this "from_string" to avoid diverging from the C++ API ?

We could, but... The reason I didn't use "string" was that fourcc is a 
string too. I used "name" in the property above too.

But I guess the libcamera strategy is to never expose fourcc as a 
string? It's always uint32_t if it's fourcc, or string if it's the "long 
name" of the format?

I've always used fourcc as a string (e.g. in kms++) as it's easy, but 
perhaps the above is a better approach.

>> +			return PixelFormat::fromString(name);
>> +		})
>> +		.def_static("from_fourcc", [](const std::string &fourcc) {
>> +			if (fourcc.size() != 4)
>> +				throw std::invalid_argument("Invalid fourcc length");
>> +
>> +			uint32_t v = fourcc[0] | (fourcc[1] << 8) |
>> +				     (fourcc[2] << 16) | (fourcc[3] << 24);
>> +
>> +			return PixelFormat(v);
>> +		});
> 
> This isn't used in your series, so I'd drop it.

True.

  Tomi
Laurent Pinchart May 17, 2022, 8:58 a.m. UTC | #3
Hi Tomi,

On Tue, May 17, 2022 at 11:53:39AM +0300, Tomi Valkeinen wrote:
> On 17/05/2022 11:32, Laurent Pinchart wrote:
> > On Mon, May 16, 2022 at 05:10:18PM +0300, Tomi Valkeinen wrote:
> >> Implement PixelFormat bindings properly with a PixelFormat class. Change
> >> the bindings to use the new class instead of a string.
> >>
> >> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>
> >> ---
> >>   src/py/cam/cam.py              |  4 +--
> >>   src/py/cam/cam_kms.py          |  9 +-----
> >>   src/py/cam/cam_qt.py           |  2 +-
> >>   src/py/cam/cam_qtgl.py         | 17 +-----------
> >>   src/py/cam/gl_helpers.py       |  8 ------
> >>   src/py/libcamera/pymain.cpp    | 51 ++++++++++++++++++++--------------
> >>   src/py/libcamera/utils/conv.py |  2 ++
> >>   7 files changed, 37 insertions(+), 56 deletions(-)
> >>
> >> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
> >> index c7da97d7..63c67126 100755
> >> --- a/src/py/cam/cam.py
> >> +++ b/src/py/cam/cam.py
> >> @@ -80,7 +80,7 @@ def do_cmd_info(ctx):
> >>   
> >>           formats = stream_config.formats
> >>           for fmt in formats.pixel_formats:
> >> -            print('\t * Pixelformat:', fmt, formats.range(fmt))
> >> +            print('\t * Pixelformat:', fmt.name, formats.range(fmt))
> >>   
> >>               for size in formats.sizes(fmt):
> >>                   print('\t  -', size)
> >> @@ -164,7 +164,7 @@ def configure(ctx):
> >>               stream_config.size = (stream_opts['width'], stream_opts['height'])
> >>   
> >>           if 'pixelformat' in stream_opts:
> >> -            stream_config.pixel_format = stream_opts['pixelformat']
> >> +            stream_config.pixel_format = libcam.PixelFormat.from_name(stream_opts['pixelformat'])
> >>   
> >>       stat = camconfig.validate()
> >>   
> >> diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py
> >> index ae6be277..7d11564b 100644
> >> --- a/src/py/cam/cam_kms.py
> >> +++ b/src/py/cam/cam_kms.py
> >> @@ -5,13 +5,6 @@ import pykms
> >>   import selectors
> >>   import sys
> >>   
> >> -FMT_MAP = {
> >> -    'RGB888': pykms.PixelFormat.RGB888,
> >> -    'YUYV': pykms.PixelFormat.YUYV,
> >> -    'ARGB8888': pykms.PixelFormat.ARGB8888,
> >> -    'XRGB8888': pykms.PixelFormat.XRGB8888,
> >> -}
> >> -
> >>   
> >>   class KMSRenderer:
> >>       def __init__(self, state):
> >> @@ -118,7 +111,7 @@ class KMSRenderer:
> >>   
> >>                   cfg = stream.configuration
> >>                   fmt = cfg.pixel_format
> >> -                fmt = FMT_MAP[fmt]
> >> +                fmt = pykms.PixelFormat(fmt.fourcc)
> >>   
> >>                   plane = self.resman.reserve_generic_plane(self.crtc, fmt)
> >>                   assert(plane is not None)
> >> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
> >> index dc5219eb..64f49f33 100644
> >> --- a/src/py/cam/cam_qt.py
> >> +++ b/src/py/cam/cam_qt.py
> >> @@ -139,7 +139,7 @@ class MainWindow(QtWidgets.QWidget):
> >>               w, h = cfg.size
> >>               pitch = cfg.stride
> >>   
> >> -            if cfg.pixel_format == 'MJPEG':
> >> +            if cfg.pixel_format.name == 'MJPEG':
> >>                   img = Image.open(BytesIO(mfb.planes[0]))
> >>                   qim = ImageQt(img).copy()
> >>                   pix = QtGui.QPixmap.fromImage(qim)
> >> diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
> >> index 8a95994e..261accb8 100644
> >> --- a/src/py/cam/cam_qtgl.py
> >> +++ b/src/py/cam/cam_qtgl.py
> >> @@ -30,14 +30,6 @@ from OpenGL.GL import shaders
> >>   
> >>   from gl_helpers import *
> >>   
> >> -# libcamera format string -> DRM fourcc
> >> -FMT_MAP = {
> >> -    'RGB888': 'RG24',
> >> -    'XRGB8888': 'XR24',
> >> -    'ARGB8888': 'AR24',
> >> -    'YUYV': 'YUYV',
> >> -}
> >> -
> >>   
> >>   class EglState:
> >>       def __init__(self):
> >> @@ -204,12 +196,6 @@ class MainWindow(QtWidgets.QWidget):
> >>               self.current[ctx['idx']] = []
> >>   
> >>               for stream in ctx['streams']:
> >> -                fmt = stream.configuration.pixel_format
> >> -                size = stream.configuration.size
> >> -
> >> -                if fmt not in FMT_MAP:
> >> -                    raise Exception('Unsupported pixel format: ' + str(fmt))
> >> -
> >>                   self.textures[stream] = None
> >>   
> >>           num_tiles = len(self.textures)
> >> @@ -281,8 +267,7 @@ class MainWindow(QtWidgets.QWidget):
> >>   
> >>       def create_texture(self, stream, fb):
> >>           cfg = stream.configuration
> >> -        fmt = cfg.pixel_format
> >> -        fmt = str_to_fourcc(FMT_MAP[fmt])
> >> +        fmt = cfg.pixel_format.fourcc
> >>           w, h = cfg.size
> >>   
> >>           attribs = [
> >> diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py
> >> index ac5e6889..53b3e9df 100644
> >> --- a/src/py/cam/gl_helpers.py
> >> +++ b/src/py/cam/gl_helpers.py
> >> @@ -30,14 +30,6 @@ def getglEGLImageTargetTexture2DOES():
> >>   
> >>   glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()
> >>   
> >> -# \todo This can be dropped when we have proper PixelFormat bindings
> >> -def str_to_fourcc(str):
> >> -    assert(len(str) == 4)
> >> -    fourcc = 0
> >> -    for i, v in enumerate([ord(c) for c in str]):
> >> -        fourcc |= v << (i * 8)
> >> -    return fourcc
> >> -
> >>   
> >>   def get_gl_extensions():
> >>       n = GLint()
> >> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
> >> index af22205e..cc2ddee5 100644
> >> --- a/src/py/libcamera/pymain.cpp
> >> +++ b/src/py/libcamera/pymain.cpp
> >> @@ -8,7 +8,6 @@
> >>   /*
> >>    * \todo Add geometry classes (Point, Rectangle...)
> >>    * \todo Add bindings for the ControlInfo class
> >> - * \todo Add bindings for the PixelFormat class
> >>    */
> >>   
> >>   #include <mutex>
> >> @@ -173,6 +172,7 @@ PYBIND11_MODULE(_libcamera, m)
> >>   	auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
> >>   	auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
> >>   	auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
> >> +	auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
> >>   
> >>   	/* Global functions */
> >>   	m.def("log_set_level", &logSetLevel);
> >> @@ -404,14 +404,7 @@ PYBIND11_MODULE(_libcamera, m)
> >>   				self.size.width = std::get<0>(size);
> >>   				self.size.height = std::get<1>(size);
> >>   			})
> >> -		.def_property(
> >> -			"pixel_format",
> >> -			[](StreamConfiguration &self) {
> >> -				return self.pixelFormat.toString();
> >> -			},
> >> -			[](StreamConfiguration &self, std::string fmt) {
> >> -				self.pixelFormat = PixelFormat::fromString(fmt);
> >> -			})
> >> +		.def_readwrite("pixel_format", &StreamConfiguration::pixelFormat)
> >>   		.def_readwrite("stride", &StreamConfiguration::stride)
> >>   		.def_readwrite("frame_size", &StreamConfiguration::frameSize)
> >>   		.def_readwrite("buffer_count", &StreamConfiguration::bufferCount)
> >> @@ -420,22 +413,15 @@ PYBIND11_MODULE(_libcamera, m)
> >>   		.def_readwrite("color_space", &StreamConfiguration::colorSpace);
> >>   
> >>   	pyStreamFormats
> >> -		.def_property_readonly("pixel_formats", [](StreamFormats &self) {
> >> -			std::vector<std::string> fmts;
> >> -			for (auto &fmt : self.pixelformats())
> >> -				fmts.push_back(fmt.toString());
> >> -			return fmts;
> >> -		})
> >> -		.def("sizes", [](StreamFormats &self, const std::string &pixelFormat) {
> >> -			auto fmt = PixelFormat::fromString(pixelFormat);
> >> +		.def_property_readonly("pixel_formats", &StreamFormats::pixelformats)
> >> +		.def("sizes", [](StreamFormats &self, const PixelFormat &pixelFormat) {
> >>   			std::vector<std::tuple<uint32_t, uint32_t>> fmts;
> >> -			for (const auto &s : self.sizes(fmt))
> >> +			for (const auto &s : self.sizes(pixelFormat))
> >>   				fmts.push_back(std::make_tuple(s.width, s.height));
> >>   			return fmts;
> >>   		})
> >> -		.def("range", [](StreamFormats &self, const std::string &pixelFormat) {
> >> -			auto fmt = PixelFormat::fromString(pixelFormat);
> >> -			const auto &range = self.range(fmt);
> >> +		.def("range", [](StreamFormats &self, const PixelFormat &pixelFormat) {
> >> +			const auto &range = self.range(pixelFormat);
> >>   			return make_tuple(std::make_tuple(range.hStep, range.vStep),
> >>   					  std::make_tuple(range.min.width, range.min.height),
> >>   					  std::make_tuple(range.max.width, range.max.height));
> >> @@ -648,4 +634,27 @@ PYBIND11_MODULE(_libcamera, m)
> >>   	pyColorSpaceRange
> >>   		.value("Full", ColorSpace::Range::Full)
> >>   		.value("Limited", ColorSpace::Range::Limited);
> >> +
> >> +	pyPixelFormat
> >> +		.def(py::init<>())
> >> +		.def(py::init<uint32_t, uint64_t>())
> >> +		.def_property_readonly("fourcc", &PixelFormat::fourcc)
> >> +		.def_property_readonly("modifier", &PixelFormat::modifier)
> >> +		.def_property_readonly("name", &PixelFormat::toString)
> >> +		.def(py::self == py::self)
> >> +		.def("__str__", [](const PixelFormat &self) {
> >> +			return "<libcamera.PixelFormat '" + self.toString() + "'>";
> >> +		})
> > 
> > I'd just return self.toString() here. We could also implement a __repr__
> > that prints "libcamera.PixelFormat('" + self.toString() + "')" if we add
> > a constructor that takes a string as __repr__ is supposed to return a
> > canonical string representation that can be run to recreate the object.
> 
> Yes. I missed __repr__ here. I think I implemented __str__ and __repr__ 
> better with the geometry classes. I'll improve this.
> 
> >> +		.def_static("from_name", [](const std::string &name) {
> > 
> > Could we name this "from_string" to avoid diverging from the C++ API ?
> 
> We could, but... The reason I didn't use "string" was that fourcc is a 
> string too. I used "name" in the property above too.
> 
> But I guess the libcamera strategy is to never expose fourcc as a 
> string? It's always uint32_t if it's fourcc, or string if it's the "long 
> name" of the format?
> 
> I've always used fourcc as a string (e.g. in kms++) as it's easy, but 
> perhaps the above is a better approach.

Correct, in libcamera we don't expose the fourcc as a string. A pixel
format is defined as a combination of a fourcc and modifiers, so I think
handling fourccs as string could easily get misleading. The only string
for pixel formats is their canonical name.

> >> +			return PixelFormat::fromString(name);
> >> +		})
> >> +		.def_static("from_fourcc", [](const std::string &fourcc) {
> >> +			if (fourcc.size() != 4)
> >> +				throw std::invalid_argument("Invalid fourcc length");
> >> +
> >> +			uint32_t v = fourcc[0] | (fourcc[1] << 8) |
> >> +				     (fourcc[2] << 16) | (fourcc[3] << 24);
> >> +
> >> +			return PixelFormat(v);
> >> +		});
> > 
> > This isn't used in your series, so I'd drop it.
> 
> True.

Patch
diff mbox series

diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py
index c7da97d7..63c67126 100755
--- a/src/py/cam/cam.py
+++ b/src/py/cam/cam.py
@@ -80,7 +80,7 @@  def do_cmd_info(ctx):
 
         formats = stream_config.formats
         for fmt in formats.pixel_formats:
-            print('\t * Pixelformat:', fmt, formats.range(fmt))
+            print('\t * Pixelformat:', fmt.name, formats.range(fmt))
 
             for size in formats.sizes(fmt):
                 print('\t  -', size)
@@ -164,7 +164,7 @@  def configure(ctx):
             stream_config.size = (stream_opts['width'], stream_opts['height'])
 
         if 'pixelformat' in stream_opts:
-            stream_config.pixel_format = stream_opts['pixelformat']
+            stream_config.pixel_format = libcam.PixelFormat.from_name(stream_opts['pixelformat'])
 
     stat = camconfig.validate()
 
diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py
index ae6be277..7d11564b 100644
--- a/src/py/cam/cam_kms.py
+++ b/src/py/cam/cam_kms.py
@@ -5,13 +5,6 @@  import pykms
 import selectors
 import sys
 
-FMT_MAP = {
-    'RGB888': pykms.PixelFormat.RGB888,
-    'YUYV': pykms.PixelFormat.YUYV,
-    'ARGB8888': pykms.PixelFormat.ARGB8888,
-    'XRGB8888': pykms.PixelFormat.XRGB8888,
-}
-
 
 class KMSRenderer:
     def __init__(self, state):
@@ -118,7 +111,7 @@  class KMSRenderer:
 
                 cfg = stream.configuration
                 fmt = cfg.pixel_format
-                fmt = FMT_MAP[fmt]
+                fmt = pykms.PixelFormat(fmt.fourcc)
 
                 plane = self.resman.reserve_generic_plane(self.crtc, fmt)
                 assert(plane is not None)
diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py
index dc5219eb..64f49f33 100644
--- a/src/py/cam/cam_qt.py
+++ b/src/py/cam/cam_qt.py
@@ -139,7 +139,7 @@  class MainWindow(QtWidgets.QWidget):
             w, h = cfg.size
             pitch = cfg.stride
 
-            if cfg.pixel_format == 'MJPEG':
+            if cfg.pixel_format.name == 'MJPEG':
                 img = Image.open(BytesIO(mfb.planes[0]))
                 qim = ImageQt(img).copy()
                 pix = QtGui.QPixmap.fromImage(qim)
diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py
index 8a95994e..261accb8 100644
--- a/src/py/cam/cam_qtgl.py
+++ b/src/py/cam/cam_qtgl.py
@@ -30,14 +30,6 @@  from OpenGL.GL import shaders
 
 from gl_helpers import *
 
-# libcamera format string -> DRM fourcc
-FMT_MAP = {
-    'RGB888': 'RG24',
-    'XRGB8888': 'XR24',
-    'ARGB8888': 'AR24',
-    'YUYV': 'YUYV',
-}
-
 
 class EglState:
     def __init__(self):
@@ -204,12 +196,6 @@  class MainWindow(QtWidgets.QWidget):
             self.current[ctx['idx']] = []
 
             for stream in ctx['streams']:
-                fmt = stream.configuration.pixel_format
-                size = stream.configuration.size
-
-                if fmt not in FMT_MAP:
-                    raise Exception('Unsupported pixel format: ' + str(fmt))
-
                 self.textures[stream] = None
 
         num_tiles = len(self.textures)
@@ -281,8 +267,7 @@  class MainWindow(QtWidgets.QWidget):
 
     def create_texture(self, stream, fb):
         cfg = stream.configuration
-        fmt = cfg.pixel_format
-        fmt = str_to_fourcc(FMT_MAP[fmt])
+        fmt = cfg.pixel_format.fourcc
         w, h = cfg.size
 
         attribs = [
diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py
index ac5e6889..53b3e9df 100644
--- a/src/py/cam/gl_helpers.py
+++ b/src/py/cam/gl_helpers.py
@@ -30,14 +30,6 @@  def getglEGLImageTargetTexture2DOES():
 
 glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()
 
-# \todo This can be dropped when we have proper PixelFormat bindings
-def str_to_fourcc(str):
-    assert(len(str) == 4)
-    fourcc = 0
-    for i, v in enumerate([ord(c) for c in str]):
-        fourcc |= v << (i * 8)
-    return fourcc
-
 
 def get_gl_extensions():
     n = GLint()
diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp
index af22205e..cc2ddee5 100644
--- a/src/py/libcamera/pymain.cpp
+++ b/src/py/libcamera/pymain.cpp
@@ -8,7 +8,6 @@ 
 /*
  * \todo Add geometry classes (Point, Rectangle...)
  * \todo Add bindings for the ControlInfo class
- * \todo Add bindings for the PixelFormat class
  */
 
 #include <mutex>
@@ -173,6 +172,7 @@  PYBIND11_MODULE(_libcamera, m)
 	auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, "TransferFunction");
 	auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, "YcbcrEncoding");
 	auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, "Range");
+	auto pyPixelFormat = py::class_<PixelFormat>(m, "PixelFormat");
 
 	/* Global functions */
 	m.def("log_set_level", &logSetLevel);
@@ -404,14 +404,7 @@  PYBIND11_MODULE(_libcamera, m)
 				self.size.width = std::get<0>(size);
 				self.size.height = std::get<1>(size);
 			})
-		.def_property(
-			"pixel_format",
-			[](StreamConfiguration &self) {
-				return self.pixelFormat.toString();
-			},
-			[](StreamConfiguration &self, std::string fmt) {
-				self.pixelFormat = PixelFormat::fromString(fmt);
-			})
+		.def_readwrite("pixel_format", &StreamConfiguration::pixelFormat)
 		.def_readwrite("stride", &StreamConfiguration::stride)
 		.def_readwrite("frame_size", &StreamConfiguration::frameSize)
 		.def_readwrite("buffer_count", &StreamConfiguration::bufferCount)
@@ -420,22 +413,15 @@  PYBIND11_MODULE(_libcamera, m)
 		.def_readwrite("color_space", &StreamConfiguration::colorSpace);
 
 	pyStreamFormats
-		.def_property_readonly("pixel_formats", [](StreamFormats &self) {
-			std::vector<std::string> fmts;
-			for (auto &fmt : self.pixelformats())
-				fmts.push_back(fmt.toString());
-			return fmts;
-		})
-		.def("sizes", [](StreamFormats &self, const std::string &pixelFormat) {
-			auto fmt = PixelFormat::fromString(pixelFormat);
+		.def_property_readonly("pixel_formats", &StreamFormats::pixelformats)
+		.def("sizes", [](StreamFormats &self, const PixelFormat &pixelFormat) {
 			std::vector<std::tuple<uint32_t, uint32_t>> fmts;
-			for (const auto &s : self.sizes(fmt))
+			for (const auto &s : self.sizes(pixelFormat))
 				fmts.push_back(std::make_tuple(s.width, s.height));
 			return fmts;
 		})
-		.def("range", [](StreamFormats &self, const std::string &pixelFormat) {
-			auto fmt = PixelFormat::fromString(pixelFormat);
-			const auto &range = self.range(fmt);
+		.def("range", [](StreamFormats &self, const PixelFormat &pixelFormat) {
+			const auto &range = self.range(pixelFormat);
 			return make_tuple(std::make_tuple(range.hStep, range.vStep),
 					  std::make_tuple(range.min.width, range.min.height),
 					  std::make_tuple(range.max.width, range.max.height));
@@ -648,4 +634,27 @@  PYBIND11_MODULE(_libcamera, m)
 	pyColorSpaceRange
 		.value("Full", ColorSpace::Range::Full)
 		.value("Limited", ColorSpace::Range::Limited);
+
+	pyPixelFormat
+		.def(py::init<>())
+		.def(py::init<uint32_t, uint64_t>())
+		.def_property_readonly("fourcc", &PixelFormat::fourcc)
+		.def_property_readonly("modifier", &PixelFormat::modifier)
+		.def_property_readonly("name", &PixelFormat::toString)
+		.def(py::self == py::self)
+		.def("__str__", [](const PixelFormat &self) {
+			return "<libcamera.PixelFormat '" + self.toString() + "'>";
+		})
+		.def_static("from_name", [](const std::string &name) {
+			return PixelFormat::fromString(name);
+		})
+		.def_static("from_fourcc", [](const std::string &fourcc) {
+			if (fourcc.size() != 4)
+				throw std::invalid_argument("Invalid fourcc length");
+
+			uint32_t v = fourcc[0] | (fourcc[1] << 8) |
+				     (fourcc[2] << 16) | (fourcc[3] << 24);
+
+			return PixelFormat(v);
+		});
 }
diff --git a/src/py/libcamera/utils/conv.py b/src/py/libcamera/utils/conv.py
index 2e483003..30f6733e 100644
--- a/src/py/libcamera/utils/conv.py
+++ b/src/py/libcamera/utils/conv.py
@@ -76,6 +76,8 @@  def to_rgb(fmt, size, data):
     w = size[0]
     h = size[1]
 
+    fmt = fmt.name
+
     if fmt == 'YUYV':
         # YUV422
         yuyv = data.reshape((h, w // 2 * 4))