[{"id":21722,"web_url":"https://patchwork.libcamera.org/comment/21722/","msgid":"<YbJsLKRVfe+ZWMsS@pendragon.ideasonboard.com>","date":"2021-12-09T20:50:52","subject":"Re: [libcamera-devel] [PATCH v10 1/8] libcamera: Add ColorSpace\n\tclass","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"Hi David,\n\nThank you for the patch.\n\nOn Thu, Dec 09, 2021 at 10:12:38AM +0000, David Plowman wrote:\n> This class represents a color space by defining its color primaries,\n> the transfer (gamma) function it uses, the YCbCr encoding and whether\n> the output is full or limited range.\n> \n> Signed-off-by: David Plowman <david.plowman@raspberrypi.com>\n> Reviewed-by: Naushir Patuck <naush@raspberrypi.com>\n> Reviewed-by: Umang Jain <umang.jain@ideasonboard.com>\n> Reviewed-by: Jacopo Mondi <jacopo@jmondi.org>\n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> ---\n>  include/libcamera/color_space.h |  70 +++++++\n>  include/libcamera/meson.build   |   1 +\n>  src/libcamera/color_space.cpp   | 318 ++++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build       |   1 +\n>  4 files changed, 390 insertions(+)\n>  create mode 100644 include/libcamera/color_space.h\n>  create mode 100644 src/libcamera/color_space.cpp\n> \n> diff --git a/include/libcamera/color_space.h b/include/libcamera/color_space.h\n> new file mode 100644\n> index 00000000..086c56c1\n> --- /dev/null\n> +++ b/include/libcamera/color_space.h\n> @@ -0,0 +1,70 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021, Raspberry Pi (Trading) Limited\n> + *\n> + * color_space.h - color space definitions\n> + */\n> +\n> +#pragma once\n> +\n> +#include <optional>\n> +#include <string>\n> +\n> +namespace libcamera {\n> +\n> +class ColorSpace\n> +{\n> +public:\n> +\tenum class Primaries {\n> +\t\tRaw,\n> +\t\tSmpte170m,\n> +\t\tRec709,\n> +\t\tRec2020,\n> +\t};\n> +\n> +\tenum class TransferFunction {\n> +\t\tLinear,\n> +\t\tSrgb,\n> +\t\tRec709,\n> +\t};\n> +\n> +\tenum class YcbcrEncoding {\n> +\t\tNone,\n> +\t\tRec601,\n> +\t\tRec709,\n> +\t\tRec2020,\n> +\t};\n> +\n> +\tenum class Range {\n> +\t\tFull,\n> +\t\tLimited,\n> +\t};\n> +\n> +\tconstexpr ColorSpace(Primaries p, TransferFunction t, YcbcrEncoding e, Range r)\n> +\t\t: primaries(p), transferFunction(t), ycbcrEncoding(e), range(r)\n> +\t{\n> +\t}\n> +\n> +\tstatic const ColorSpace Raw;\n> +\tstatic const ColorSpace Jpeg;\n> +\tstatic const ColorSpace Srgb;\n> +\tstatic const ColorSpace Smpte170m;\n> +\tstatic const ColorSpace Rec709;\n> +\tstatic const ColorSpace Rec2020;\n> +\n> +\tPrimaries primaries;\n> +\tTransferFunction transferFunction;\n> +\tYcbcrEncoding ycbcrEncoding;\n> +\tRange range;\n> +\n> +\tstd::string toString() const;\n> +\tstatic std::string toString(const std::optional<ColorSpace> &colorSpace);\n> +};\n> +\n> +bool operator==(const ColorSpace &lhs, const ColorSpace &rhs);\n> +static inline bool operator!=(const ColorSpace &lhs, const ColorSpace &rhs)\n> +{\n> +\treturn !(lhs == rhs);\n> +}\n> +\n> +} /* namespace libcamera */\n> diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build\n> index 5f42977c..fd767b11 100644\n> --- a/include/libcamera/meson.build\n> +++ b/include/libcamera/meson.build\n> @@ -5,6 +5,7 @@ libcamera_include_dir = 'libcamera' / 'libcamera'\n>  libcamera_public_headers = files([\n>      'camera.h',\n>      'camera_manager.h',\n> +    'color_space.h',\n>      'controls.h',\n>      'framebuffer.h',\n>      'framebuffer_allocator.h',\n> diff --git a/src/libcamera/color_space.cpp b/src/libcamera/color_space.cpp\n> new file mode 100644\n> index 00000000..6c587068\n> --- /dev/null\n> +++ b/src/libcamera/color_space.cpp\n> @@ -0,0 +1,318 @@\n> +/* SPDX-License-Identifier: LGPL-2.1-or-later */\n> +/*\n> + * Copyright (C) 2021, Raspberry Pi (Trading) Limited\n> + *\n> + * color_space.cpp - color spaces.\n> + */\n> +\n> +#include <libcamera/color_space.h>\n> +\n> +#include <algorithm>\n> +#include <array>\n> +#include <map>\n> +#include <sstream>\n> +#include <utility>\n> +\n> +/**\n> + * \\file color_space.h\n> + * \\brief Class and enums to represent color spaces\n> + */\n> +\n> +namespace libcamera {\n> +\n> +/**\n> + * \\class ColorSpace\n> + * \\brief Class to describe a color space\n> + *\n> + * The ColorSpace class defines the color primaries, the Y'CbCr encoding,\n> + * the transfer function associated with the color space, and the range\n> + * (sometimes also referred to as the quantisation) of the color space.\n> + *\n> + * Certain combinations of these fields form well-known standard color\n> + * spaces such as \"JPEG\" or \"REC709\".\n> + *\n> + * In the strictest sense a \"color space\" formally only refers to the\n> + * color primaries and white point. Here, however, the ColorSpace class\n> + * adopts the common broader usage that includes the transfer function,\n> + * Y'CbCr encoding method and quantisation.\n> + *\n> + * For more information on the specific color spaces described here, please\n> + * see:\n> + *\n> + * - <a href=\"https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-srgb\">sRGB</a>\n> + * - <a href=\"https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-jpeg\">JPEG</a>\n> + * - <a href=\"https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-smpte-170m\">SMPTE 170M</a>\n> + * - <a href=\"https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-rec709\">Rec.709</a>\n> + * - <a href=\"https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-bt2020\">Rec.2020</a>\n> + */\n> +\n> +/**\n> + * \\enum ColorSpace::Primaries\n> + * \\brief The color primaries for this color space\n> + *\n> + * \\var ColorSpace::Primaries::Raw\n> + * \\brief These are raw colors directly from a sensor, the primaries are\n> + * unspecified\n> + *\n> + * \\var ColorSpace::Primaries::Smpte170m\n> + * \\brief SMPTE 170M color primaries\n> + *\n> + * \\var ColorSpace::Primaries::Rec709\n> + * \\brief Rec.709 color primaries\n> + *\n> + * \\var ColorSpace::Primaries::Rec2020\n> + * \\brief Rec.2020 color primaries\n> + */\n> +\n> +/**\n> + * \\enum ColorSpace::TransferFunction\n> + * \\brief The transfer function used for this color space\n> + *\n> + * \\var ColorSpace::TransferFunction::Linear\n> + * \\brief This color space uses a linear (identity) transfer function\n> + *\n> + * \\var ColorSpace::TransferFunction::Srgb\n> + * \\brief sRGB transfer function\n> + *\n> + * \\var ColorSpace::TransferFunction::Rec709\n> + * \\brief Rec.709 transfer function\n> + */\n> +\n> +/**\n> + * \\enum ColorSpace::YcbcrEncoding\n> + * \\brief The Y'CbCr encoding\n> + *\n> + * \\var ColorSpace::YcbcrEncoding::None\n> + * \\brief There is no defined Y'CbCr encoding\n\nI'd write\n\n * \\brief There is no defined Y'CbCr encoding (used for non-YUV formats)\n\n> + *\n> + * \\var ColorSpace::YcbcrEncoding::Rec601\n> + * \\brief Rec.601 Y'CbCr encoding\n> + *\n> + * \\var ColorSpace::YcbcrEncoding::Rec709\n> + * \\brief Rec.709 Y'CbCr encoding\n> + *\n> + * \\var ColorSpace::YcbcrEncoding::Rec2020\n> + * \\brief Rec.2020 Y'CbCr encoding\n> + */\n> +\n> +/**\n> + * \\enum ColorSpace::Range\n> + * \\brief The range (sometimes \"quantisation\") for this color space\n> + *\n> + * \\var ColorSpace::Range::Full\n> + * \\brief This color space uses full range pixel values\n> + *\n> + * \\var ColorSpace::Range::Limited\n> + * \\brief This color space uses limited range pixel values, being\n> + * 16 to 235 for Y' and 16 to 240 for Cb and Cr (8 bits per sample)\n> + * or 64 to 940 for Y' and 16 to 960 for Cb and Cr (10 bits)\n> + */\n> +\n> +/**\n> + * \\fn ColorSpace::ColorSpace(Primaries p, TransferFunction t, Encoding e, Range r)\n> + * \\brief Construct a ColorSpace from explicit values\n> + * \\param[in] p The color primaries\n> + * \\param[in] t The transfer function for the color space\n> + * \\param[in] e The Y'CbCr encoding\n> + * \\param[in] r The range of the pixel values in this color space\n> + */\n> +\n> +/**\n> + * \\brief Assemble and return a readable string representation of the\n> + * ColorSpace\n> + *\n> + * If the color space matches a standard ColorSpace (such as ColorSpace::Jpeg)\n> + * then the short name of the color space (\"JPEG\") is returned. Otherwise\n> + * the four constituent parts of the ColorSpace are assembled into a longer\n> + * string.\n> + *\n> + * \\return A string describing the ColorSpace\n> + */\n> +std::string ColorSpace::toString() const\n> +{\n> +\t/* Print out a brief name only for standard color spaces. */\n> +\n> +\tstatic const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = {\n> +\t\t{\n> +\t\t\t{ ColorSpace::Raw, \"RAW\" },\n> +\t\t\t{ ColorSpace::Jpeg, \"JPEG\" },\n> +\t\t\t{ ColorSpace::Srgb, \"sRGB\" },\n> +\t\t\t{ ColorSpace::Smpte170m, \"SMPTE170M\" },\n> +\t\t\t{ ColorSpace::Rec709, \"Rec709\" },\n> +\t\t\t{ ColorSpace::Rec2020, \"Rec2020\" },\n> +\t\t}\n> +\t};\n\nThat's a bit of a peculiar construct, I wonder why the compiler accepts\n\n\tstatic const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = {\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Raw, \"RAW\" },\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Jpeg, \"JPEG\" },\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Srgb, \"sRGB\" },\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Smpte170m, \"SMPTE170M\" },\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Rec709, \"Rec709\" },\n\t\tstd::pair<ColorSpace, const char *>{ ColorSpace::Rec2020, \"Rec2020\" },\n\t};\n\nbut requires double braces when not specifying the type of the elements\nexplicitly. I'm sure there's an explanation.\n\nWe could make it a bit nicer-looking with\n\n\tstatic const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = { {\n\t\t{ ColorSpace::Raw, \"RAW\" },\n\t\t{ ColorSpace::Jpeg, \"JPEG\" },\n\t\t{ ColorSpace::Srgb, \"sRGB\" },\n\t\t{ ColorSpace::Smpte170m, \"SMPTE170M\" },\n\t\t{ ColorSpace::Rec709, \"Rec709\" },\n\t\t{ ColorSpace::Rec2020, \"Rec2020\" },\n\t} };\n\n> +\tauto it = std::find_if(colorSpaceNames.begin(), colorSpaceNames.end(),\n> +\t\t\t       [this](const auto &item) {\n> +\t\t\t\t       return *this == item.first;\n> +\t\t\t       });\n> +\tif (it != colorSpaceNames.end())\n> +\t\treturn std::string(it->second);\n> +\n> +\t/* Assemble a name made of the constituent fields. */\n> +\n> +\tstatic const std::map<Primaries, std::string> primariesNames = {\n> +\t\t{ Primaries::Raw, \"Raw\" },\n> +\t\t{ Primaries::Smpte170m, \"Smpte170m\" },\n\nSMPTE170M here too.\n\n> +\t\t{ Primaries::Rec709, \"Rec709\" },\n> +\t\t{ Primaries::Rec2020, \"Rec2020\" },\n> +\t};\n> +\tstatic const std::map<TransferFunction, std::string> transferNames = {\n> +\t\t{ TransferFunction::Linear, \"Linear\" },\n> +\t\t{ TransferFunction::Srgb, \"Srgb\" },\n\nAnd sRGB.\n\n> +\t\t{ TransferFunction::Rec709, \"Rec709\" },\n> +\t};\n> +\tstatic const std::map<YcbcrEncoding, std::string> encodingNames = {\n> +\t\t{ YcbcrEncoding::None, \"None\" },\n> +\t\t{ YcbcrEncoding::Rec601, \"Rec601\" },\n> +\t\t{ YcbcrEncoding::Rec709, \"Rec709\" },\n> +\t\t{ YcbcrEncoding::Rec2020, \"Rec2020\" },\n> +\t};\n> +\tstatic const std::map<Range, std::string> rangeNames = {\n> +\t\t{ Range::Full, \"Full\" },\n> +\t\t{ Range::Limited, \"Limited\" },\n> +\t};\n> +\n> +\tauto itPrimaries = primariesNames.find(primaries);\n> +\tstd::string primariesName =\n> +\t\titPrimaries == primariesNames.end() ? \"Invalid\" : itPrimaries->second;\n> +\n> +\tauto itTransfer = transferNames.find(transferFunction);\n> +\tstd::string transferName =\n> +\t\titTransfer == transferNames.end() ? \"Invalid\" : itTransfer->second;\n> +\n> +\tauto itEncoding = encodingNames.find(ycbcrEncoding);\n> +\tstd::string encodingName =\n> +\t\titEncoding == encodingNames.end() ? \"Invalid\" : itEncoding->second;\n> +\n> +\tauto itRange = rangeNames.find(range);\n> +\tstd::string rangeName =\n> +\t\titRange == rangeNames.end() ? \"Invalid\" : itRange->second;\n> +\n> +\tstd::stringstream ss;\n> +\tss << primariesName << \"/\" << transferName << \"/\" << encodingName << \"/\" << rangeName;\n> +\n> +\treturn ss.str();\n> +}\n> +\n> +/**\n> + * \\brief Assemble and return a readable string representation of an\n> + * optional ColorSpace\n> + *\n> + * This is a convenience helper to easily obtain a string representation\n> + * for a ColorSpace in parts of the libcamera API where it is stored in a\n> + * std::optional<>. If the ColorSpace is set, this function returns\n> + * \\a colorSpace.toString(), otherwise it returns \"Unset\".\n> + *\n> + * \\return A string describing the optional ColorSpace\n> + */\n> +std::string ColorSpace::toString(const std::optional<ColorSpace> &colorSpace)\n> +{\n> +\tif (!colorSpace)\n> +\t\treturn \"Unset\";\n> +\n> +\treturn colorSpace->toString();\n> +}\n> +\n> +/**\n> + * \\var ColorSpace::primaries\n> + * \\brief The color primaries of this color space\n> + */\n> +\n> +/**\n> + * \\var ColorSpace::ycbcrEncoding\n> + * \\brief The Y'CbCr encoding used by this color space\n> + */\n> +\n> +/**\n> + * \\var ColorSpace::transferFunction\n> + * \\brief The transfer function used by this color space\n> + */\n> +\n> +/**\n> + * \\var ColorSpace::range\n> + * \\brief The pixel range used with by color space\n> + */\n> +\n> +/**\n> + * \\brief A constant representing a raw color space (from a sensor)\n> + */\n> +const ColorSpace ColorSpace::Raw = {\n> +\tPrimaries::Raw,\n> +\tTransferFunction::Linear,\n> +\tYcbcrEncoding::None,\n> +\tRange::Full\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the JPEG color space used for\n> + * encoding JPEG images\n> + */\n> +const ColorSpace ColorSpace::Jpeg = {\n> +\tPrimaries::Rec709,\n> +\tTransferFunction::Srgb,\n> +\tYcbcrEncoding::Rec601,\n> +\tRange::Full\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the sRGB color space.\n\nBlank line after \\brief, or it won't be split to a different paragraph.\n\nIf you're fine with these small changes, I'll apply them when merging\nthe series.\n\n> + * This is identical to the JPEG color space except that the Y'CbCr\n> + * range is limited rather than full.\n> + */\n> +const ColorSpace ColorSpace::Srgb = {\n> +\tPrimaries::Rec709,\n> +\tTransferFunction::Srgb,\n> +\tYcbcrEncoding::Rec601,\n> +\tRange::Limited\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the SMPTE170M color space\n> + */\n> +const ColorSpace ColorSpace::Smpte170m = {\n> +\tPrimaries::Smpte170m,\n> +\tTransferFunction::Rec709,\n> +\tYcbcrEncoding::Rec601,\n> +\tRange::Limited\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the Rec.709 color space\n> + */\n> +const ColorSpace ColorSpace::Rec709 = {\n> +\tPrimaries::Rec709,\n> +\tTransferFunction::Rec709,\n> +\tYcbcrEncoding::Rec709,\n> +\tRange::Limited\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the Rec.2020 color space\n> + */\n> +const ColorSpace ColorSpace::Rec2020 = {\n> +\tPrimaries::Rec2020,\n> +\tTransferFunction::Rec709,\n> +\tYcbcrEncoding::Rec2020,\n> +\tRange::Limited\n> +};\n> +\n> +/**\n> + * \\brief Compare color spaces for equality\n> + * \\return True if the two color spaces are identical, false otherwise\n> + */\n> +bool operator==(const ColorSpace &lhs, const ColorSpace &rhs)\n> +{\n> +\treturn lhs.primaries == rhs.primaries &&\n> +\t       lhs.transferFunction == rhs.transferFunction &&\n> +\t       lhs.ycbcrEncoding == rhs.ycbcrEncoding &&\n> +\t       lhs.range == rhs.range;\n> +}\n> +\n> +/**\n> + * \\fn bool operator!=(const ColorSpace &lhs, const ColorSpace &rhs)\n> + * \\brief Compare color spaces for inequality\n> + * \\return True if the two color spaces are not identical, false otherwise\n> + */\n> +\n> +} /* namespace libcamera */\n> diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build\n> index 2e54cc04..4045e24e 100644\n> --- a/src/libcamera/meson.build\n> +++ b/src/libcamera/meson.build\n> @@ -9,6 +9,7 @@ libcamera_sources = files([\n>      'camera_manager.cpp',\n>      'camera_sensor.cpp',\n>      'camera_sensor_properties.cpp',\n> +    'color_space.cpp',\n>      'controls.cpp',\n>      'control_serializer.cpp',\n>      'control_validator.cpp',","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 BAF63BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 20:51:25 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 056A06086A;\n\tThu,  9 Dec 2021 21:51:25 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id EB5FD607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 21:51:22 +0100 (CET)","from pendragon.ideasonboard.com (62-78-145-57.bb.dnainternet.fi\n\t[62.78.145.57])\n\tby perceval.ideasonboard.com (Postfix) with ESMTPSA id 47D8E501;\n\tThu,  9 Dec 2021 21:51:22 +0100 (CET)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (1024-bit key;\n\tunprotected) header.d=ideasonboard.com header.i=@ideasonboard.com\n\theader.b=\"FTZUgdG2\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1639083082;\n\tbh=PVoPxliCdBRpKm2D5DcBB8ZiEONl4JRiQmAtg7oIK0w=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=FTZUgdG2ur2C1lPwSNL9Rpu9LOAFkltG6dJmv4huErTWKTiyrLQR+8u86vANX+xSd\n\tRFhSEBBWlkOtCxlBLa8B09FltB/+PDssb/vRXMrRFwVdYYnBdQx1cW3g4aszHgCSR7\n\tk2mt9+/v/G9HGTwVCLUteCTbHnZGr5t4rMSXhgZY=","Date":"Thu, 9 Dec 2021 22:50:52 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<YbJsLKRVfe+ZWMsS@pendragon.ideasonboard.com>","References":"<20211209101245.6187-1-david.plowman@raspberrypi.com>\n\t<20211209101245.6187-2-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20211209101245.6187-2-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v10 1/8] libcamera: Add ColorSpace\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>","Cc":"Tomasz Figa <tfiga@google.com>, libcamera-devel@lists.libcamera.org,\n\tHans Verkuil <hverkuil-cisco@xs4all.nl>","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]