[{"id":21743,"web_url":"https://patchwork.libcamera.org/comment/21743/","msgid":"<YbNL1mMs3P5zaNFg@pendragon.ideasonboard.com>","date":"2021-12-10T12:45:10","subject":"Re: [libcamera-devel] [PATCH v11 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 Fri, Dec 10, 2021 at 11:21:35AM +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   | 317 ++++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build       |   1 +\n>  4 files changed, 389 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..85524b96\n> --- /dev/null\n> +++ b/src/libcamera/color_space.cpp\n> @@ -0,0 +1,317 @@\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 mentioned in the review of v10 that I'd write\n\n * \\brief There is no defined Y'CbCr encoding (used for non-YUV formats)\n\nNow I'm fully aware that not all of my comments are good, but without a\nreply to v10 I can't tell if you've missed that comment, or have decided\nto keep the current text.\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{ 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> +\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> +\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> +\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> + *\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 7FC42BF415\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 10 Dec 2021 12:45:42 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id BDEDF60897;\n\tFri, 10 Dec 2021 13:45:41 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 744FB60868\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 10 Dec 2021 13:45:40 +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 BA6DF1172;\n\tFri, 10 Dec 2021 13:45:39 +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=\"CxJLq7i3\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1639140340;\n\tbh=Opkud6QUT9MGRo3nMAchaJUSmCyH0GXvOAaCNe3mScE=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=CxJLq7i3s6CUNrL9+iTWStpLYWyj/wR4ioEQFJUzR00l5Rjgy+xNXKnyPropiWKfr\n\t0OQyfyzvV0SzzL0Qo0ItwRKTgOE0NrbbJzvdqsNy+2eoic99o9m8B2OtkoP3WJ9UUs\n\tf4VSXUUXtm5N2QgRUcS40O2UC4+rRsWxDNHZhw1k=","Date":"Fri, 10 Dec 2021 14:45:10 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<YbNL1mMs3P5zaNFg@pendragon.ideasonboard.com>","References":"<20211210112142.18441-1-david.plowman@raspberrypi.com>\n\t<20211210112142.18441-2-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20211210112142.18441-2-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v11 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>"}},{"id":21745,"web_url":"https://patchwork.libcamera.org/comment/21745/","msgid":"<CAHW6GY+MiFP2tqkDXoxMt8apzja4RxjJ0v7MdmW8e0H-XeDd1A@mail.gmail.com>","date":"2021-12-10T12:48:59","subject":"Re: [libcamera-devel] [PATCH v11 1/8] libcamera: Add ColorSpace\n\tclass","submitter":{"id":42,"url":"https://patchwork.libcamera.org/api/people/42/","name":"David Plowman","email":"david.plowman@raspberrypi.com"},"content":"Hi Laurent\n\nSorry, I thought I'd taken all the proposed changes, but actually I\nseem to have missed this one. Anyway, let me send along a v12 which\nwill have this plus the logging messages in those static member\nfunctions.\n\nThanks!\nDavid\n\nOn Fri, 10 Dec 2021 at 12:45, Laurent Pinchart\n<laurent.pinchart@ideasonboard.com> wrote:\n>\n> Hi David,\n>\n> Thank you for the patch.\n>\n> On Fri, Dec 10, 2021 at 11:21:35AM +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   | 317 ++++++++++++++++++++++++++++++++\n> >  src/libcamera/meson.build       |   1 +\n> >  4 files changed, 389 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> > +     enum class Primaries {\n> > +             Raw,\n> > +             Smpte170m,\n> > +             Rec709,\n> > +             Rec2020,\n> > +     };\n> > +\n> > +     enum class TransferFunction {\n> > +             Linear,\n> > +             Srgb,\n> > +             Rec709,\n> > +     };\n> > +\n> > +     enum class YcbcrEncoding {\n> > +             None,\n> > +             Rec601,\n> > +             Rec709,\n> > +             Rec2020,\n> > +     };\n> > +\n> > +     enum class Range {\n> > +             Full,\n> > +             Limited,\n> > +     };\n> > +\n> > +     constexpr ColorSpace(Primaries p, TransferFunction t, YcbcrEncoding e, Range r)\n> > +             : primaries(p), transferFunction(t), ycbcrEncoding(e), range(r)\n> > +     {\n> > +     }\n> > +\n> > +     static const ColorSpace Raw;\n> > +     static const ColorSpace Jpeg;\n> > +     static const ColorSpace Srgb;\n> > +     static const ColorSpace Smpte170m;\n> > +     static const ColorSpace Rec709;\n> > +     static const ColorSpace Rec2020;\n> > +\n> > +     Primaries primaries;\n> > +     TransferFunction transferFunction;\n> > +     YcbcrEncoding ycbcrEncoding;\n> > +     Range range;\n> > +\n> > +     std::string toString() const;\n> > +     static 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> > +     return !(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..85524b96\n> > --- /dev/null\n> > +++ b/src/libcamera/color_space.cpp\n> > @@ -0,0 +1,317 @@\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>\n> I mentioned in the review of v10 that I'd write\n>\n>  * \\brief There is no defined Y'CbCr encoding (used for non-YUV formats)\n>\n> Now I'm fully aware that not all of my comments are good, but without a\n> reply to v10 I can't tell if you've missed that comment, or have decided\n> to keep the current text.\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> > +     /* Print out a brief name only for standard color spaces. */\n> > +\n> > +     static const std::array<std::pair<ColorSpace, const char *>, 6> colorSpaceNames = { {\n> > +             { ColorSpace::Raw, \"RAW\" },\n> > +             { ColorSpace::Jpeg, \"JPEG\" },\n> > +             { ColorSpace::Srgb, \"sRGB\" },\n> > +             { ColorSpace::Smpte170m, \"SMPTE170M\" },\n> > +             { ColorSpace::Rec709, \"Rec709\" },\n> > +             { ColorSpace::Rec2020, \"Rec2020\" },\n> > +     } };\n> > +     auto it = std::find_if(colorSpaceNames.begin(), colorSpaceNames.end(),\n> > +                            [this](const auto &item) {\n> > +                                    return *this == item.first;\n> > +                            });\n> > +     if (it != colorSpaceNames.end())\n> > +             return std::string(it->second);\n> > +\n> > +     /* Assemble a name made of the constituent fields. */\n> > +\n> > +     static const std::map<Primaries, std::string> primariesNames = {\n> > +             { Primaries::Raw, \"RAW\" },\n> > +             { Primaries::Smpte170m, \"SMPTE170M\" },\n> > +             { Primaries::Rec709, \"Rec709\" },\n> > +             { Primaries::Rec2020, \"Rec2020\" },\n> > +     };\n> > +     static const std::map<TransferFunction, std::string> transferNames = {\n> > +             { TransferFunction::Linear, \"Linear\" },\n> > +             { TransferFunction::Srgb, \"sRGB\" },\n> > +             { TransferFunction::Rec709, \"Rec709\" },\n> > +     };\n> > +     static const std::map<YcbcrEncoding, std::string> encodingNames = {\n> > +             { YcbcrEncoding::None, \"None\" },\n> > +             { YcbcrEncoding::Rec601, \"Rec601\" },\n> > +             { YcbcrEncoding::Rec709, \"Rec709\" },\n> > +             { YcbcrEncoding::Rec2020, \"Rec2020\" },\n> > +     };\n> > +     static const std::map<Range, std::string> rangeNames = {\n> > +             { Range::Full, \"Full\" },\n> > +             { Range::Limited, \"Limited\" },\n> > +     };\n> > +\n> > +     auto itPrimaries = primariesNames.find(primaries);\n> > +     std::string primariesName =\n> > +             itPrimaries == primariesNames.end() ? \"Invalid\" : itPrimaries->second;\n> > +\n> > +     auto itTransfer = transferNames.find(transferFunction);\n> > +     std::string transferName =\n> > +             itTransfer == transferNames.end() ? \"Invalid\" : itTransfer->second;\n> > +\n> > +     auto itEncoding = encodingNames.find(ycbcrEncoding);\n> > +     std::string encodingName =\n> > +             itEncoding == encodingNames.end() ? \"Invalid\" : itEncoding->second;\n> > +\n> > +     auto itRange = rangeNames.find(range);\n> > +     std::string rangeName =\n> > +             itRange == rangeNames.end() ? \"Invalid\" : itRange->second;\n> > +\n> > +     std::stringstream ss;\n> > +     ss << primariesName << \"/\" << transferName << \"/\" << encodingName << \"/\" << rangeName;\n> > +\n> > +     return 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> > +     if (!colorSpace)\n> > +             return \"Unset\";\n> > +\n> > +     return 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> > +     Primaries::Raw,\n> > +     TransferFunction::Linear,\n> > +     YcbcrEncoding::None,\n> > +     Range::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> > +     Primaries::Rec709,\n> > +     TransferFunction::Srgb,\n> > +     YcbcrEncoding::Rec601,\n> > +     Range::Full\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the sRGB color space.\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> > +     Primaries::Rec709,\n> > +     TransferFunction::Srgb,\n> > +     YcbcrEncoding::Rec601,\n> > +     Range::Limited\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the SMPTE170M color space\n> > + */\n> > +const ColorSpace ColorSpace::Smpte170m = {\n> > +     Primaries::Smpte170m,\n> > +     TransferFunction::Rec709,\n> > +     YcbcrEncoding::Rec601,\n> > +     Range::Limited\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the Rec.709 color space\n> > + */\n> > +const ColorSpace ColorSpace::Rec709 = {\n> > +     Primaries::Rec709,\n> > +     TransferFunction::Rec709,\n> > +     YcbcrEncoding::Rec709,\n> > +     Range::Limited\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the Rec.2020 color space\n> > + */\n> > +const ColorSpace ColorSpace::Rec2020 = {\n> > +     Primaries::Rec2020,\n> > +     TransferFunction::Rec709,\n> > +     YcbcrEncoding::Rec2020,\n> > +     Range::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> > +     return lhs.primaries == rhs.primaries &&\n> > +            lhs.transferFunction == rhs.transferFunction &&\n> > +            lhs.ycbcrEncoding == rhs.ycbcrEncoding &&\n> > +            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',\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 C7A70BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 10 Dec 2021 12:49:12 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7F0CF60890;\n\tFri, 10 Dec 2021 13:49:12 +0100 (CET)","from mail-wr1-x434.google.com (mail-wr1-x434.google.com\n\t[IPv6:2a00:1450:4864:20::434])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E832860868\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 10 Dec 2021 13:49:10 +0100 (CET)","by mail-wr1-x434.google.com with SMTP id d24so14872371wra.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 10 Dec 2021 04:49:10 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"MV3BAG3z\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\n\th=mime-version:references:in-reply-to:from:date:message-id:subject:to\n\t:cc; bh=+lhiNr7GWkWnR6K0F9Dg1tgLTxWHTfb0gsH9A+tlLhI=;\n\tb=MV3BAG3z/wHqTVpDdlpPwvfLa5U/08H2Y0yM/uq2o4uyz1U/5ryN7g9oxg3HOnmkIB\n\tprGa3xU1OdZiF4+mficSlREUnJ3RdxIH78FoeGKXTwnSnKVGq+oZ2h1OvwLYvSE4jL+m\n\tSs8rb57cv2fBz/sy/OMRV33HBN8gkUFj+wxEEnMcw2Z23tzkEkmU+T6+HoeSzvjNGkG+\n\tIx2fXs1fu7SdzGbOdJnOzAeG5bK3dMHJ30uXVbEMuTExJH7/zXj5aaJ85mLDRmq1doj+\n\t3PW+CeN4kQZ2/A+Y49o6UXxThSsu/Kf8QUoetJPwjOi/xtR5fIuw9vZLRPkB6KzPTT1y\n\taCow==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:references:in-reply-to:from:date\n\t:message-id:subject:to:cc;\n\tbh=+lhiNr7GWkWnR6K0F9Dg1tgLTxWHTfb0gsH9A+tlLhI=;\n\tb=ulIdYLm+GoUS7S4yDJtNymu3CHLsVx6xRgDzhjmmNerICP+lf9oYp4NKQOVRuLvhw5\n\tq+q1s5l7Uj27HEoYHA2e6XklyDnrHNpH72gppXoC4COwIto03av8s74sMAPPBgV+xVBV\n\t0eE9rZYRQubPOB/jTYllGd+r4HqrZ53XQVx9ovYtEtbcYzVJAODuhawKT0KjkKMpdgLv\n\tfZFoJ5LO2U1NZxay8Y0EAxNEGCP070ikhm+Soa5P4K6DAIoYNi5IJ/nfijvLkzWQ5heB\n\tuNkpIk8OARBtdQi4Zy/Oa2Amncv+kTHO04zldxbrXtWjOvgIGQkyzMvH4dpzU73IZnRy\n\tu+iA==","X-Gm-Message-State":"AOAM533hVZUNyp6Lu7ntBbiRb4hAgJhfBBzcea9n2IWQ2edxSIPKyBRg\n\tDd7oawqZ4I2+/9P6iaTC4nge1kTK+Dgff6oQpQAHng==","X-Google-Smtp-Source":"ABdhPJyI1/BkGpDSyeLMQrN28ZOmYv4ekkPzfQ6fVDBWYrqfQ1lBpxFCy3sznrecVd41h7yfkTAUNoHA0BdXg7XOMAY=","X-Received":"by 2002:a5d:52c2:: with SMTP id\n\tr2mr13977598wrv.548.1639140550464; \n\tFri, 10 Dec 2021 04:49:10 -0800 (PST)","MIME-Version":"1.0","References":"<20211210112142.18441-1-david.plowman@raspberrypi.com>\n\t<20211210112142.18441-2-david.plowman@raspberrypi.com>\n\t<YbNL1mMs3P5zaNFg@pendragon.ideasonboard.com>","In-Reply-To":"<YbNL1mMs3P5zaNFg@pendragon.ideasonboard.com>","From":"David Plowman <david.plowman@raspberrypi.com>","Date":"Fri, 10 Dec 2021 12:48:59 +0000","Message-ID":"<CAHW6GY+MiFP2tqkDXoxMt8apzja4RxjJ0v7MdmW8e0H-XeDd1A@mail.gmail.com>","To":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"Re: [libcamera-devel] [PATCH v11 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>,\n\tlibcamera devel <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>"}}]