[{"id":22998,"web_url":"https://patchwork.libcamera.org/comment/22998/","msgid":"<YoPKK8CekgI3liH8@pendragon.ideasonboard.com>","date":"2022-05-17T16:15:39","subject":"Re: [libcamera-devel] [PATCH v2 11/13] py: implement PixelFormat\n\tclass","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi Tomi,\n\nThank you for the patch.\n\nOn Tue, May 17, 2022 at 05:33:23PM +0300, Tomi Valkeinen wrote:\n> Implement PixelFormat bindings properly with a PixelFormat class. Change\n> the bindings to use the new class instead of a string.\n> \n> Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> ---\n>  src/py/cam/cam.py           |  2 +-\n>  src/py/cam/cam_kms.py       | 10 +--------\n>  src/py/cam/cam_qt.py        |  4 +++-\n>  src/py/cam/cam_qtgl.py      | 17 +--------------\n>  src/py/cam/gl_helpers.py    |  8 -------\n>  src/py/libcamera/pymain.cpp | 42 ++++++++++++++++++-------------------\n>  6 files changed, 27 insertions(+), 56 deletions(-)\n> \n> diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\n> index c7da97d7..001fb9de 100755\n> --- a/src/py/cam/cam.py\n> +++ b/src/py/cam/cam.py\n> @@ -164,7 +164,7 @@ def configure(ctx):\n>              stream_config.size = (stream_opts['width'], stream_opts['height'])\n>  \n>          if 'pixelformat' in stream_opts:\n> -            stream_config.pixel_format = stream_opts['pixelformat']\n> +            stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])\n>  \n>      stat = camconfig.validate()\n>  \n> diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py\n> index d8ff0284..04381da1 100644\n> --- a/src/py/cam/cam_kms.py\n> +++ b/src/py/cam/cam_kms.py\n> @@ -5,14 +5,6 @@ import pykms\n>  import selectors\n>  import sys\n>  \n> -FMT_MAP = {\n> -    'RGB888': pykms.PixelFormat.RGB888,\n> -    'YUYV': pykms.PixelFormat.YUYV,\n> -    'ARGB8888': pykms.PixelFormat.ARGB8888,\n> -    'XRGB8888': pykms.PixelFormat.XRGB8888,\n> -    'NV12': pykms.PixelFormat.NV12,\n> -}\n> -\n>  \n>  class KMSRenderer:\n>      def __init__(self, state):\n> @@ -120,7 +112,7 @@ class KMSRenderer:\n>  \n>                  cfg = stream.configuration\n>                  fmt = cfg.pixel_format\n> -                fmt = FMT_MAP[fmt]\n> +                fmt = pykms.PixelFormat(fmt.fourcc)\n>  \n>                  plane = self.resman.reserve_generic_plane(self.crtc, fmt)\n>                  assert(plane is not None)\n> diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py\n> index fb485b9b..45a30aeb 100644\n> --- a/src/py/cam/cam_qt.py\n> +++ b/src/py/cam/cam_qt.py\n> @@ -87,6 +87,8 @@ def to_rgb(fmt, size, data):\n>      w = size[0]\n>      h = size[1]\n>  \n> +    fmt = str(fmt)\n> +\n>      if fmt == 'YUYV':\n>          # YUV422\n>          yuyv = data.reshape((h, w // 2 * 4))\n> @@ -293,7 +295,7 @@ class MainWindow(QtWidgets.QWidget):\n>              w, h = cfg.size\n>              pitch = cfg.stride\n>  \n> -            if cfg.pixel_format == 'MJPEG':\n> +            if str(cfg.pixel_format) == 'MJPEG':\n\nIdeally this should be\n\n\tif cfg.pixel_format == libcamera.formats.MJPEG\n\nbut that can come later. Maybe a todo comment in the bindings to\nremember that ?\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n>                  img = Image.open(BytesIO(mfb.planes[0]))\n>                  qim = ImageQt(img).copy()\n>                  pix = QtGui.QPixmap.fromImage(qim)\n> diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py\n> index 8a95994e..261accb8 100644\n> --- a/src/py/cam/cam_qtgl.py\n> +++ b/src/py/cam/cam_qtgl.py\n> @@ -30,14 +30,6 @@ from OpenGL.GL import shaders\n>  \n>  from gl_helpers import *\n>  \n> -# libcamera format string -> DRM fourcc\n> -FMT_MAP = {\n> -    'RGB888': 'RG24',\n> -    'XRGB8888': 'XR24',\n> -    'ARGB8888': 'AR24',\n> -    'YUYV': 'YUYV',\n> -}\n> -\n>  \n>  class EglState:\n>      def __init__(self):\n> @@ -204,12 +196,6 @@ class MainWindow(QtWidgets.QWidget):\n>              self.current[ctx['idx']] = []\n>  \n>              for stream in ctx['streams']:\n> -                fmt = stream.configuration.pixel_format\n> -                size = stream.configuration.size\n> -\n> -                if fmt not in FMT_MAP:\n> -                    raise Exception('Unsupported pixel format: ' + str(fmt))\n> -\n>                  self.textures[stream] = None\n>  \n>          num_tiles = len(self.textures)\n> @@ -281,8 +267,7 @@ class MainWindow(QtWidgets.QWidget):\n>  \n>      def create_texture(self, stream, fb):\n>          cfg = stream.configuration\n> -        fmt = cfg.pixel_format\n> -        fmt = str_to_fourcc(FMT_MAP[fmt])\n> +        fmt = cfg.pixel_format.fourcc\n>          w, h = cfg.size\n>  \n>          attribs = [\n> diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py\n> index ac5e6889..53b3e9df 100644\n> --- a/src/py/cam/gl_helpers.py\n> +++ b/src/py/cam/gl_helpers.py\n> @@ -30,14 +30,6 @@ def getglEGLImageTargetTexture2DOES():\n>  \n>  glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()\n>  \n> -# \\todo This can be dropped when we have proper PixelFormat bindings\n> -def str_to_fourcc(str):\n> -    assert(len(str) == 4)\n> -    fourcc = 0\n> -    for i, v in enumerate([ord(c) for c in str]):\n> -        fourcc |= v << (i * 8)\n> -    return fourcc\n> -\n>  \n>  def get_gl_extensions():\n>      n = GLint()\n> diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp\n> index af22205e..97b05903 100644\n> --- a/src/py/libcamera/pymain.cpp\n> +++ b/src/py/libcamera/pymain.cpp\n> @@ -8,7 +8,6 @@\n>  /*\n>   * \\todo Add geometry classes (Point, Rectangle...)\n>   * \\todo Add bindings for the ControlInfo class\n> - * \\todo Add bindings for the PixelFormat class\n>   */\n>  \n>  #include <mutex>\n> @@ -173,6 +172,7 @@ PYBIND11_MODULE(_libcamera, m)\n>  \tauto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, \"TransferFunction\");\n>  \tauto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, \"YcbcrEncoding\");\n>  \tauto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, \"Range\");\n> +\tauto pyPixelFormat = py::class_<PixelFormat>(m, \"PixelFormat\");\n>  \n>  \t/* Global functions */\n>  \tm.def(\"log_set_level\", &logSetLevel);\n> @@ -404,14 +404,7 @@ PYBIND11_MODULE(_libcamera, m)\n>  \t\t\t\tself.size.width = std::get<0>(size);\n>  \t\t\t\tself.size.height = std::get<1>(size);\n>  \t\t\t})\n> -\t\t.def_property(\n> -\t\t\t\"pixel_format\",\n> -\t\t\t[](StreamConfiguration &self) {\n> -\t\t\t\treturn self.pixelFormat.toString();\n> -\t\t\t},\n> -\t\t\t[](StreamConfiguration &self, std::string fmt) {\n> -\t\t\t\tself.pixelFormat = PixelFormat::fromString(fmt);\n> -\t\t\t})\n> +\t\t.def_readwrite(\"pixel_format\", &StreamConfiguration::pixelFormat)\n>  \t\t.def_readwrite(\"stride\", &StreamConfiguration::stride)\n>  \t\t.def_readwrite(\"frame_size\", &StreamConfiguration::frameSize)\n>  \t\t.def_readwrite(\"buffer_count\", &StreamConfiguration::bufferCount)\n> @@ -420,22 +413,15 @@ PYBIND11_MODULE(_libcamera, m)\n>  \t\t.def_readwrite(\"color_space\", &StreamConfiguration::colorSpace);\n>  \n>  \tpyStreamFormats\n> -\t\t.def_property_readonly(\"pixel_formats\", [](StreamFormats &self) {\n> -\t\t\tstd::vector<std::string> fmts;\n> -\t\t\tfor (auto &fmt : self.pixelformats())\n> -\t\t\t\tfmts.push_back(fmt.toString());\n> -\t\t\treturn fmts;\n> -\t\t})\n> -\t\t.def(\"sizes\", [](StreamFormats &self, const std::string &pixelFormat) {\n> -\t\t\tauto fmt = PixelFormat::fromString(pixelFormat);\n> +\t\t.def_property_readonly(\"pixel_formats\", &StreamFormats::pixelformats)\n> +\t\t.def(\"sizes\", [](StreamFormats &self, const PixelFormat &pixelFormat) {\n>  \t\t\tstd::vector<std::tuple<uint32_t, uint32_t>> fmts;\n> -\t\t\tfor (const auto &s : self.sizes(fmt))\n> +\t\t\tfor (const auto &s : self.sizes(pixelFormat))\n>  \t\t\t\tfmts.push_back(std::make_tuple(s.width, s.height));\n>  \t\t\treturn fmts;\n>  \t\t})\n> -\t\t.def(\"range\", [](StreamFormats &self, const std::string &pixelFormat) {\n> -\t\t\tauto fmt = PixelFormat::fromString(pixelFormat);\n> -\t\t\tconst auto &range = self.range(fmt);\n> +\t\t.def(\"range\", [](StreamFormats &self, const PixelFormat &pixelFormat) {\n> +\t\t\tconst auto &range = self.range(pixelFormat);\n>  \t\t\treturn make_tuple(std::make_tuple(range.hStep, range.vStep),\n>  \t\t\t\t\t  std::make_tuple(range.min.width, range.min.height),\n>  \t\t\t\t\t  std::make_tuple(range.max.width, range.max.height));\n> @@ -648,4 +634,18 @@ PYBIND11_MODULE(_libcamera, m)\n>  \tpyColorSpaceRange\n>  \t\t.value(\"Full\", ColorSpace::Range::Full)\n>  \t\t.value(\"Limited\", ColorSpace::Range::Limited);\n> +\n> +\tpyPixelFormat\n> +\t\t.def(py::init<>())\n> +\t\t.def(py::init<uint32_t, uint64_t>())\n> +\t\t.def(py::init<>([](const std::string &str) {\n> +\t\t\treturn PixelFormat::fromString(str);\n> +\t\t}))\n> +\t\t.def_property_readonly(\"fourcc\", &PixelFormat::fourcc)\n> +\t\t.def_property_readonly(\"modifier\", &PixelFormat::modifier)\n> +\t\t.def(py::self == py::self)\n> +\t\t.def(\"__str__\", &PixelFormat::toString)\n> +\t\t.def(\"__repr__\", [](const PixelFormat &self) {\n> +\t\t\treturn \"libcamera.PixelFormat('\" + self.toString() + \"')\";\n> +\t\t});\n>  }","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id A654DC3256\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 17 May 2022 16:15:48 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id F007B65656;\n\tTue, 17 May 2022 18:15:47 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 534656041D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 17 May 2022 18:15:47 +0200 (CEST)","from pendragon.ideasonboard.com (unknown [45.131.31.124])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 7F6DA48F;\n\tTue, 17 May 2022 18:15:46 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1652804148;\n\tbh=QpkATh/9/e0OBqRWTSmBWW8f7l0fBKxcr4J73oQZQTs=;\n\th=Date:To:References:In-Reply-To:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=fAYfQwY0GwziNUxZF/D/zyjmy4sH+wBl1KXRe1OpF6nAvYpNg8Wo5UopnYxU6bVcs\n\troPFsHwmfDbkYwISc+Y/ICvi36YYMYxrXN6I1XBqCqp2JP7Wl/0qvrax1ljsGhSG5F\n\tmREt3LZdcX4RreUrrJtI9FH8cuIN4npezUIxz7jZiPqzmBovtvbYQ+5XpA/xfJBPak\n\tkpub2UXURpiYxh51EbmtRGV0aCww+49xcQxlBwDDNscAFgkYO7au491PUaWQmfAUmC\n\ty1eAmX7xYiIdiX1PC2ddVrvnMFuX3vnIKTgha/Vv1qp2fhCT3nqlnyeJiMjiPrc7HL\n\tccyPNSvSkeqxQ==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1652804146;\n\tbh=QpkATh/9/e0OBqRWTSmBWW8f7l0fBKxcr4J73oQZQTs=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=RPyE91lLYtw4SSoQfZc1IzG0oEVteFaQ1i3kRPRWZdybHUvbL08iLq3I/2JMC2qTP\n\tGnEw4gAzQpJ4tV9meYWs8Vuewc3ur4Dd8Q3IyAFonxCMtMk0w1LNipR4mrpDGTFPGs\n\t9EuFQYnSgAbyC8jopASpz4V/0a1aIWwV7YPMtK5Y="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"RPyE91lL\"; dkim-atps=neutral","Date":"Tue, 17 May 2022 19:15:39 +0300","To":"Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Message-ID":"<YoPKK8CekgI3liH8@pendragon.ideasonboard.com>","References":"<20220517143325.71784-1-tomi.valkeinen@ideasonboard.com>\n\t<20220517143325.71784-12-tomi.valkeinen@ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20220517143325.71784-12-tomi.valkeinen@ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v2 11/13] py: implement PixelFormat\n\tclass","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Laurent Pinchart via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":23013,"web_url":"https://patchwork.libcamera.org/comment/23013/","msgid":"<165280751676.2416244.10359645275113825114@Monstersaurus>","date":"2022-05-17T17:11:56","subject":"Re: [libcamera-devel] [PATCH v2 11/13] py: implement PixelFormat\n\tclass","submitter":{"id":4,"url":"https://patchwork.libcamera.org/api/people/4/","name":"Kieran Bingham","email":"kieran.bingham@ideasonboard.com"},"content":"Quoting Laurent Pinchart (2022-05-17 17:15:39)\n> Hi Tomi,\n> \n> Thank you for the patch.\n> \n> On Tue, May 17, 2022 at 05:33:23PM +0300, Tomi Valkeinen wrote:\n> > Implement PixelFormat bindings properly with a PixelFormat class. Change\n> > the bindings to use the new class instead of a string.\n> > \n> > Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ideasonboard.com>\n> > ---\n> >  src/py/cam/cam.py           |  2 +-\n> >  src/py/cam/cam_kms.py       | 10 +--------\n> >  src/py/cam/cam_qt.py        |  4 +++-\n> >  src/py/cam/cam_qtgl.py      | 17 +--------------\n> >  src/py/cam/gl_helpers.py    |  8 -------\n> >  src/py/libcamera/pymain.cpp | 42 ++++++++++++++++++-------------------\n> >  6 files changed, 27 insertions(+), 56 deletions(-)\n> > \n> > diff --git a/src/py/cam/cam.py b/src/py/cam/cam.py\n> > index c7da97d7..001fb9de 100755\n> > --- a/src/py/cam/cam.py\n> > +++ b/src/py/cam/cam.py\n> > @@ -164,7 +164,7 @@ def configure(ctx):\n> >              stream_config.size = (stream_opts['width'], stream_opts['height'])\n> >  \n> >          if 'pixelformat' in stream_opts:\n> > -            stream_config.pixel_format = stream_opts['pixelformat']\n> > +            stream_config.pixel_format = libcam.PixelFormat(stream_opts['pixelformat'])\n> >  \n> >      stat = camconfig.validate()\n> >  \n> > diff --git a/src/py/cam/cam_kms.py b/src/py/cam/cam_kms.py\n> > index d8ff0284..04381da1 100644\n> > --- a/src/py/cam/cam_kms.py\n> > +++ b/src/py/cam/cam_kms.py\n> > @@ -5,14 +5,6 @@ import pykms\n> >  import selectors\n> >  import sys\n> >  \n> > -FMT_MAP = {\n> > -    'RGB888': pykms.PixelFormat.RGB888,\n> > -    'YUYV': pykms.PixelFormat.YUYV,\n> > -    'ARGB8888': pykms.PixelFormat.ARGB8888,\n> > -    'XRGB8888': pykms.PixelFormat.XRGB8888,\n> > -    'NV12': pykms.PixelFormat.NV12,\n> > -}\n> > -\n> >  \n> >  class KMSRenderer:\n> >      def __init__(self, state):\n> > @@ -120,7 +112,7 @@ class KMSRenderer:\n> >  \n> >                  cfg = stream.configuration\n> >                  fmt = cfg.pixel_format\n> > -                fmt = FMT_MAP[fmt]\n> > +                fmt = pykms.PixelFormat(fmt.fourcc)\n> >  \n> >                  plane = self.resman.reserve_generic_plane(self.crtc, fmt)\n> >                  assert(plane is not None)\n> > diff --git a/src/py/cam/cam_qt.py b/src/py/cam/cam_qt.py\n> > index fb485b9b..45a30aeb 100644\n> > --- a/src/py/cam/cam_qt.py\n> > +++ b/src/py/cam/cam_qt.py\n> > @@ -87,6 +87,8 @@ def to_rgb(fmt, size, data):\n> >      w = size[0]\n> >      h = size[1]\n> >  \n> > +    fmt = str(fmt)\n> > +\n> >      if fmt == 'YUYV':\n> >          # YUV422\n> >          yuyv = data.reshape((h, w // 2 * 4))\n> > @@ -293,7 +295,7 @@ class MainWindow(QtWidgets.QWidget):\n> >              w, h = cfg.size\n> >              pitch = cfg.stride\n> >  \n> > -            if cfg.pixel_format == 'MJPEG':\n> > +            if str(cfg.pixel_format) == 'MJPEG':\n> \n> Ideally this should be\n> \n>         if cfg.pixel_format == libcamera.formats.MJPEG\n> \n> but that can come later. Maybe a todo comment in the bindings to\n> remember that ?\n\nI assume this isn't currently possible, as there's no full conversion of\nthe types, just runtime conversion with fromString() etc?\n\nBut a todo works for me too, and the str() conversion looks like it\nalready does the right thing at least in this instance. so\n\n\nReviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>\n\n\n> \n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> >                  img = Image.open(BytesIO(mfb.planes[0]))\n> >                  qim = ImageQt(img).copy()\n> >                  pix = QtGui.QPixmap.fromImage(qim)\n> > diff --git a/src/py/cam/cam_qtgl.py b/src/py/cam/cam_qtgl.py\n> > index 8a95994e..261accb8 100644\n> > --- a/src/py/cam/cam_qtgl.py\n> > +++ b/src/py/cam/cam_qtgl.py\n> > @@ -30,14 +30,6 @@ from OpenGL.GL import shaders\n> >  \n> >  from gl_helpers import *\n> >  \n> > -# libcamera format string -> DRM fourcc\n> > -FMT_MAP = {\n> > -    'RGB888': 'RG24',\n> > -    'XRGB8888': 'XR24',\n> > -    'ARGB8888': 'AR24',\n> > -    'YUYV': 'YUYV',\n> > -}\n> > -\n> >  \n> >  class EglState:\n> >      def __init__(self):\n> > @@ -204,12 +196,6 @@ class MainWindow(QtWidgets.QWidget):\n> >              self.current[ctx['idx']] = []\n> >  \n> >              for stream in ctx['streams']:\n> > -                fmt = stream.configuration.pixel_format\n> > -                size = stream.configuration.size\n> > -\n> > -                if fmt not in FMT_MAP:\n> > -                    raise Exception('Unsupported pixel format: ' + str(fmt))\n> > -\n> >                  self.textures[stream] = None\n> >  \n> >          num_tiles = len(self.textures)\n> > @@ -281,8 +267,7 @@ class MainWindow(QtWidgets.QWidget):\n> >  \n> >      def create_texture(self, stream, fb):\n> >          cfg = stream.configuration\n> > -        fmt = cfg.pixel_format\n> > -        fmt = str_to_fourcc(FMT_MAP[fmt])\n> > +        fmt = cfg.pixel_format.fourcc\n> >          w, h = cfg.size\n> >  \n> >          attribs = [\n> > diff --git a/src/py/cam/gl_helpers.py b/src/py/cam/gl_helpers.py\n> > index ac5e6889..53b3e9df 100644\n> > --- a/src/py/cam/gl_helpers.py\n> > +++ b/src/py/cam/gl_helpers.py\n> > @@ -30,14 +30,6 @@ def getglEGLImageTargetTexture2DOES():\n> >  \n> >  glEGLImageTargetTexture2DOES = getglEGLImageTargetTexture2DOES()\n> >  \n> > -# \\todo This can be dropped when we have proper PixelFormat bindings\n> > -def str_to_fourcc(str):\n> > -    assert(len(str) == 4)\n> > -    fourcc = 0\n> > -    for i, v in enumerate([ord(c) for c in str]):\n> > -        fourcc |= v << (i * 8)\n> > -    return fourcc\n> > -\n> >  \n> >  def get_gl_extensions():\n> >      n = GLint()\n> > diff --git a/src/py/libcamera/pymain.cpp b/src/py/libcamera/pymain.cpp\n> > index af22205e..97b05903 100644\n> > --- a/src/py/libcamera/pymain.cpp\n> > +++ b/src/py/libcamera/pymain.cpp\n> > @@ -8,7 +8,6 @@\n> >  /*\n> >   * \\todo Add geometry classes (Point, Rectangle...)\n> >   * \\todo Add bindings for the ControlInfo class\n> > - * \\todo Add bindings for the PixelFormat class\n> >   */\n> >  \n> >  #include <mutex>\n> > @@ -173,6 +172,7 @@ PYBIND11_MODULE(_libcamera, m)\n> >       auto pyColorSpaceTransferFunction = py::enum_<ColorSpace::TransferFunction>(pyColorSpace, \"TransferFunction\");\n> >       auto pyColorSpaceYcbcrEncoding = py::enum_<ColorSpace::YcbcrEncoding>(pyColorSpace, \"YcbcrEncoding\");\n> >       auto pyColorSpaceRange = py::enum_<ColorSpace::Range>(pyColorSpace, \"Range\");\n> > +     auto pyPixelFormat = py::class_<PixelFormat>(m, \"PixelFormat\");\n> >  \n> >       /* Global functions */\n> >       m.def(\"log_set_level\", &logSetLevel);\n> > @@ -404,14 +404,7 @@ PYBIND11_MODULE(_libcamera, m)\n> >                               self.size.width = std::get<0>(size);\n> >                               self.size.height = std::get<1>(size);\n> >                       })\n> > -             .def_property(\n> > -                     \"pixel_format\",\n> > -                     [](StreamConfiguration &self) {\n> > -                             return self.pixelFormat.toString();\n> > -                     },\n> > -                     [](StreamConfiguration &self, std::string fmt) {\n> > -                             self.pixelFormat = PixelFormat::fromString(fmt);\n> > -                     })\n> > +             .def_readwrite(\"pixel_format\", &StreamConfiguration::pixelFormat)\n> >               .def_readwrite(\"stride\", &StreamConfiguration::stride)\n> >               .def_readwrite(\"frame_size\", &StreamConfiguration::frameSize)\n> >               .def_readwrite(\"buffer_count\", &StreamConfiguration::bufferCount)\n> > @@ -420,22 +413,15 @@ PYBIND11_MODULE(_libcamera, m)\n> >               .def_readwrite(\"color_space\", &StreamConfiguration::colorSpace);\n> >  \n> >       pyStreamFormats\n> > -             .def_property_readonly(\"pixel_formats\", [](StreamFormats &self) {\n> > -                     std::vector<std::string> fmts;\n> > -                     for (auto &fmt : self.pixelformats())\n> > -                             fmts.push_back(fmt.toString());\n> > -                     return fmts;\n> > -             })\n> > -             .def(\"sizes\", [](StreamFormats &self, const std::string &pixelFormat) {\n> > -                     auto fmt = PixelFormat::fromString(pixelFormat);\n> > +             .def_property_readonly(\"pixel_formats\", &StreamFormats::pixelformats)\n> > +             .def(\"sizes\", [](StreamFormats &self, const PixelFormat &pixelFormat) {\n> >                       std::vector<std::tuple<uint32_t, uint32_t>> fmts;\n> > -                     for (const auto &s : self.sizes(fmt))\n> > +                     for (const auto &s : self.sizes(pixelFormat))\n> >                               fmts.push_back(std::make_tuple(s.width, s.height));\n> >                       return fmts;\n> >               })\n> > -             .def(\"range\", [](StreamFormats &self, const std::string &pixelFormat) {\n> > -                     auto fmt = PixelFormat::fromString(pixelFormat);\n> > -                     const auto &range = self.range(fmt);\n> > +             .def(\"range\", [](StreamFormats &self, const PixelFormat &pixelFormat) {\n> > +                     const auto &range = self.range(pixelFormat);\n> >                       return make_tuple(std::make_tuple(range.hStep, range.vStep),\n> >                                         std::make_tuple(range.min.width, range.min.height),\n> >                                         std::make_tuple(range.max.width, range.max.height));\n> > @@ -648,4 +634,18 @@ PYBIND11_MODULE(_libcamera, m)\n> >       pyColorSpaceRange\n> >               .value(\"Full\", ColorSpace::Range::Full)\n> >               .value(\"Limited\", ColorSpace::Range::Limited);\n> > +\n> > +     pyPixelFormat\n> > +             .def(py::init<>())\n> > +             .def(py::init<uint32_t, uint64_t>())\n> > +             .def(py::init<>([](const std::string &str) {\n> > +                     return PixelFormat::fromString(str);\n> > +             }))\n> > +             .def_property_readonly(\"fourcc\", &PixelFormat::fourcc)\n> > +             .def_property_readonly(\"modifier\", &PixelFormat::modifier)\n> > +             .def(py::self == py::self)\n> > +             .def(\"__str__\", &PixelFormat::toString)\n> > +             .def(\"__repr__\", [](const PixelFormat &self) {\n> > +                     return \"libcamera.PixelFormat('\" + self.toString() + \"')\";\n> > +             });\n> >  }\n> \n> -- \n> Regards,\n> \n> Laurent Pinchart","headers":{"Return-Path":"<libcamera-devel-bounces@lists.libcamera.org>","X-Original-To":"parsemail@patchwork.libcamera.org","Delivered-To":"parsemail@patchwork.libcamera.org","Received":["from lancelot.ideasonboard.com (lancelot.ideasonboard.com\n\t[92.243.16.209])\n\tby patchwork.libcamera.org (Postfix) with ESMTPS id E5FA9C0F2A\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 17 May 2022 17:12:02 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 53F306041E;\n\tTue, 17 May 2022 19:12:02 +0200 (CEST)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[IPv6:2001:4b98:dc2:55:216:3eff:fef7:d647])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 863F86041D\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 17 May 2022 19:12:00 +0200 (CEST)","from pendragon.ideasonboard.com\n\t(cpc89244-aztw30-2-0-cust3082.18-1.cable.virginm.net [86.31.172.11])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id DEE354A8;\n\tTue, 17 May 2022 19:11:59 +0200 (CEST)"],"DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1652807522;\n\tbh=6kvJNp1ev3rm4SKZP0cHEJFSLw1MpGcm+CDRkcmFYkI=;\n\th=In-Reply-To:References:To:Date:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:Cc:\n\tFrom;\n\tb=dZ90Ixbtgyfdk4OrR8cSyQOl5LIxQ8wUhlTpwA3Cd8adEfLsObvn3fnMKn41suLt7\n\tosrs4om4MT/YbnWZYVPXG7YDPM9rJryfKaxjIeiLpg/yHHkdoX9nUU1RiE3oOHsLpm\n\th5peEEl2bCA9vbKN9D6TbiqA24BPZ+C4MfIqQ+s6I/VTwbk0X+JS+l5u9t7pGfIKmo\n\tyd1uf1/w/Sk4EVb/DfUEwcPa8Ia2/QYkcvExfdGD4A+LJA3UzBerjWaJSR07+mrTzP\n\ta+QIM/2DwiGQm40fe/bnDGgr7TS3hQsB5aHuyDxjJOiNKCzXmq+L7zqNN981jl4KUf\n\tgHyyiZxfrHPoA==","v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1652807520;\n\tbh=6kvJNp1ev3rm4SKZP0cHEJFSLw1MpGcm+CDRkcmFYkI=;\n\th=In-Reply-To:References:Subject:From:Cc:To:Date:From;\n\tb=QHAQ4HO8/6V6qQjVee4cK9YIvggl/lc3HANIp0UBeqvxDzbvaRJqtt5XP60obny/g\n\t+30Znl9eKm2xHPuDXmZO1YBFAPYQverhbud+YcOg2zhVZQL1uxanlwt+u4xepwLzXS\n\tZ1CZFRZIpSNtT1z6+iMStsEmQyiliavPuaGO/1lE="],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key; \n\tunprotected) header.d=ideasonboard.com\n\theader.i=@ideasonboard.com\n\theader.b=\"QHAQ4HO8\"; dkim-atps=neutral","Content-Type":"text/plain; charset=\"utf-8\"","MIME-Version":"1.0","Content-Transfer-Encoding":"quoted-printable","In-Reply-To":"<YoPKK8CekgI3liH8@pendragon.ideasonboard.com>","References":"<20220517143325.71784-1-tomi.valkeinen@ideasonboard.com>\n\t<20220517143325.71784-12-tomi.valkeinen@ideasonboard.com>\n\t<YoPKK8CekgI3liH8@pendragon.ideasonboard.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>,\n\tTomi Valkeinen <tomi.valkeinen@ideasonboard.com>","Date":"Tue, 17 May 2022 18:11:56 +0100","Message-ID":"<165280751676.2416244.10359645275113825114@Monstersaurus>","User-Agent":"alot/0.10","Subject":"Re: [libcamera-devel] [PATCH v2 11/13] py: implement PixelFormat\n\tclass","X-BeenThere":"libcamera-devel@lists.libcamera.org","X-Mailman-Version":"2.1.29","Precedence":"list","List-Id":"<libcamera-devel.lists.libcamera.org>","List-Unsubscribe":"<https://lists.libcamera.org/options/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=unsubscribe>","List-Archive":"<https://lists.libcamera.org/pipermail/libcamera-devel/>","List-Post":"<mailto:libcamera-devel@lists.libcamera.org>","List-Help":"<mailto:libcamera-devel-request@lists.libcamera.org?subject=help>","List-Subscribe":"<https://lists.libcamera.org/listinfo/libcamera-devel>,\n\t<mailto:libcamera-devel-request@lists.libcamera.org?subject=subscribe>","From":"Kieran Bingham via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>","Reply-To":"Kieran Bingham <kieran.bingham@ideasonboard.com>","Cc":"libcamera-devel@lists.libcamera.org","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]