[{"id":21625,"web_url":"https://patchwork.libcamera.org/comment/21625/","msgid":"<Ya9dyyANEMpiPKYy@pendragon.ideasonboard.com>","date":"2021-12-07T13:12:43","subject":"Re: [libcamera-devel] [PATCH v9 1/8] libcamera: Add ColorSpace class","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 Mon, Dec 06, 2021 at 10:50:24AM +0000, David Plowman wrote:\n> This class represents a color space by defining its color primaries,\n> YCbCr encoding, the transfer (gamma) function it uses, and whether the\n> 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> ---\n>  include/libcamera/color_space.h |  69 ++++++++\n>  include/libcamera/meson.build   |   1 +\n>  src/libcamera/color_space.cpp   | 305 ++++++++++++++++++++++++++++++++\n>  src/libcamera/meson.build       |   1 +\n>  4 files changed, 376 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..0fe6b580\n> --- /dev/null\n> +++ b/include/libcamera/color_space.h\n> @@ -0,0 +1,69 @@\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\nIt's always annoying to find the proper way to express abbrevitations in\ncamelCase. We'll figure out any change later, if needed.\n\n> +\t\tRec709,\n> +\t\tRec2020,\n> +\t};\n> +\n> +\tenum class YcbcrEncoding {\n> +\t\tRec601,\n> +\t\tRec709,\n> +\t\tRec2020,\n\nSorry if this has been discussed before, but do we need a None (or\nsimilar) entry for non-YUV formats ?\n\n> +\t};\n> +\n> +\tenum class TransferFunction {\n> +\t\tLinear,\n> +\t\tSrgb,\n> +\t\tRec709,\n> +\t};\n\nNitpicking (and can be handled during merging without a v10 if this is\nthe only comment that needs addressing), could you move the\nTransferFunction before the YcbcrEncoding, to match the order in which a\ncolour space \"applies\" ? Same for the function arguments, member\nfunctions and documentation below (and possibly in the rest of the\nseries, if applicable)\n\n> +\n> +\tenum class Range {\n> +\t\tFull,\n> +\t\tLimited,\n> +\t};\n> +\n> +\tconstexpr ColorSpace(Primaries p, YcbcrEncoding e, TransferFunction t, Range r)\n> +\t\t: primaries(p), ycbcrEncoding(e), transferFunction(t), 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> +\tYcbcrEncoding ycbcrEncoding;\n> +\tTransferFunction transferFunction;\n> +\tRange range;\n> +\n> +\tconst std::string toString() const;\n> +\tstatic const std::string toString(const std::optional<ColorSpace> &colorSpace);\n\nYou return a std::string by value from both functions, you can drop the\nconst.\n\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\nLooking good so far, let's continue :-)\n\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..5fe4fcc2\n> --- /dev/null\n> +++ b/src/libcamera/color_space.cpp\n> @@ -0,0 +1,305 @@\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 <map>\n> +#include <sstream>\n> +#include <utility>\n> +#include <vector>\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\nLet's make this a list:\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> +/**\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\n\nI'd expand this a bit:\n\n * \\brief These are raw colors directly from a sensor, the primaries are\n * unspecified\n\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::YcbcrEncoding\n> + * \\brief The Y'CbCr encoding\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::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::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, Encoding e, TransferFunction t, Range r)\n> + * \\brief Construct a ColorSpace from explicit values\n> + * \\param[in] p The color primaries\n> + * \\param[in] e The Y'CbCr encoding\n> + * \\param[in] t The transfer function for the color space\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> +const std::string ColorSpace::toString() const\n> +{\n> +\t/* Print out a brief name only for standard color spaces. */\n> +\n> +\tstatic const std::vector<std::pair<ColorSpace, const char *>> colorSpaceNames = {\n\nIf you make this a std::array I think it can become a constexpr\n(std::vector got a constexpr constructor in C++20 only).\n\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\nMaybe \"JPEG\", \"sRGB\", \"SMPTE170M\" ? That's how it's usually printed.\nSame below.\n\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<YcbcrEncoding, std::string> encodingNames = {\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<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<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 itEncoding = encodingNames.find(ycbcrEncoding);\n> +\tstd::string encodingName =\n> +\t\titEncoding == encodingNames.end() ? \"Invalid\" : itEncoding->second;\n> +\n> +\tauto itTransfer = transferNames.find(transferFunction);\n> +\tstd::string transferName =\n> +\t\titTransfer == transferNames.end() ? \"Invalid\" : itTransfer->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 << \"/\" << encodingName << \"/\" << transferName << \"/\" << rangeName;\n> +\n> +\treturn ss.str();\n> +}\n> +\n> +/**\n> + * \\brief Assemble and return a readable string representation of an\n> + * optional ColorSpace\n> + * \\return A string describing the optional ColorSpace\n> + */\n> +const std::string ColorSpace::toString(const std::optional<ColorSpace> &colorSpace)\n\nI'm not sure yet what this would be used for, I suppose I'll see in\nfurther patches.\n\n> +{\n> +\tif (!colorSpace)\n> +\t\treturn \"Unknown\";\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 with this color space\n\ns/with/by/ ? Same below.\n\n> + */\n> +\n> +/**\n> + * \\var ColorSpace::transferFunction\n> + * \\brief The transfer function used with this color space\n> + */\n> +\n> +/**\n> + * \\var ColorSpace::range\n> + * \\brief The pixel range used with this 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> +\tYcbcrEncoding::Rec601,\n> +\tTransferFunction::Linear,\n> +\tRange::Full\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the JPEG color space used for\n> + * encoding JPEG images.\n\ns/.$//\n\n> + */\n> +const ColorSpace ColorSpace::Jpeg = {\n> +\tPrimaries::Rec709,\n> +\tYcbcrEncoding::Rec601,\n> +\tTransferFunction::Srgb,\n> +\tRange::Full\n> +};\n> +\n> +/**\n> + * \\brief A constant representing the sRGB color space. This is\n> + * identical to the JPEG color space except that the range Y'CbCr\n> + * range is limited rather than full.\n\nI'd split the second line out of the brief:\n\n * \\brief A constant representing the sRGB color space\n *\n * This is identical to the JPEG color space except that the range Y'CbCr range\n * is limited rather than full.\n\nReviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n\n> + */\n> +const ColorSpace ColorSpace::Srgb = {\n> +\tPrimaries::Rec709,\n> +\tYcbcrEncoding::Rec601,\n> +\tTransferFunction::Srgb,\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> +\tYcbcrEncoding::Rec601,\n> +\tTransferFunction::Rec709,\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> +\tYcbcrEncoding::Rec709,\n> +\tTransferFunction::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> +\tYcbcrEncoding::Rec2020,\n> +\tTransferFunction::Rec709,\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.ycbcrEncoding == rhs.ycbcrEncoding &&\n> +\t       lhs.transferFunction == rhs.transferFunction &&\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 A2FE7BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Dec 2021 13:13:15 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C3E506086A;\n\tTue,  7 Dec 2021 14:13:14 +0100 (CET)","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 9EE1D60592\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Dec 2021 14:13:12 +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 EE26C556;\n\tTue,  7 Dec 2021 14:13:11 +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=\"uUNTwdtb\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1638882792;\n\tbh=CJfZ+ICLEI8Dshdnolg49Kc54ZQp0sPjQ1rf0+sjWFA=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=uUNTwdtbcrgpyWVNpPE0K5KX4KDAmrfK2ZyOimbjAXTuqmJpbQQsu03wVMLYwgWqd\n\tqJBwxt1fo7xx/MGya+A3dYj85pcvxb0TlfZWsoLFuRURqbdsQ6tDis/ND3IM4Qh95j\n\tFW8JbMAqFrPIzLH+N6w/pVQcyYszRn9jtZGh+hm8=","Date":"Tue, 7 Dec 2021 15:12:43 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<Ya9dyyANEMpiPKYy@pendragon.ideasonboard.com>","References":"<20211206105032.13876-1-david.plowman@raspberrypi.com>\n\t<20211206105032.13876-2-david.plowman@raspberrypi.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<20211206105032.13876-2-david.plowman@raspberrypi.com>","Subject":"Re: [libcamera-devel] [PATCH v9 1/8] libcamera: Add ColorSpace class","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":"tfiga@google.com, libcamera-devel@lists.libcamera.org,\n\thverkuil-cisco@xs4all.nl","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}},{"id":21636,"web_url":"https://patchwork.libcamera.org/comment/21636/","msgid":"<Ya9kyuuFolshCyon@pendragon.ideasonboard.com>","date":"2021-12-07T13:42:34","subject":"Re: [libcamera-devel] [PATCH v9 1/8] libcamera: Add ColorSpace class","submitter":{"id":2,"url":"https://patchwork.libcamera.org/api/people/2/","name":"Laurent Pinchart","email":"laurent.pinchart@ideasonboard.com"},"content":"On Tue, Dec 07, 2021 at 03:12:44PM +0200, Laurent Pinchart wrote:\n> Hi David,\n> \n> Thank you for the patch.\n> \n> On Mon, Dec 06, 2021 at 10:50:24AM +0000, David Plowman wrote:\n> > This class represents a color space by defining its color primaries,\n> > YCbCr encoding, the transfer (gamma) function it uses, and whether the\n> > 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> > ---\n> >  include/libcamera/color_space.h |  69 ++++++++\n> >  include/libcamera/meson.build   |   1 +\n> >  src/libcamera/color_space.cpp   | 305 ++++++++++++++++++++++++++++++++\n> >  src/libcamera/meson.build       |   1 +\n> >  4 files changed, 376 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..0fe6b580\n> > --- /dev/null\n> > +++ b/include/libcamera/color_space.h\n> > @@ -0,0 +1,69 @@\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> \n> It's always annoying to find the proper way to express abbrevitations in\n> camelCase. We'll figure out any change later, if needed.\n> \n> > +\t\tRec709,\n> > +\t\tRec2020,\n> > +\t};\n> > +\n> > +\tenum class YcbcrEncoding {\n> > +\t\tRec601,\n> > +\t\tRec709,\n> > +\t\tRec2020,\n> \n> Sorry if this has been discussed before, but do we need a None (or\n> similar) entry for non-YUV formats ?\n> \n> > +\t};\n> > +\n> > +\tenum class TransferFunction {\n> > +\t\tLinear,\n> > +\t\tSrgb,\n> > +\t\tRec709,\n> > +\t};\n> \n> Nitpicking (and can be handled during merging without a v10 if this is\n> the only comment that needs addressing), could you move the\n> TransferFunction before the YcbcrEncoding, to match the order in which a\n> colour space \"applies\" ? Same for the function arguments, member\n> functions and documentation below (and possibly in the rest of the\n> series, if applicable)\n> \n> > +\n> > +\tenum class Range {\n> > +\t\tFull,\n> > +\t\tLimited,\n> > +\t};\n> > +\n> > +\tconstexpr ColorSpace(Primaries p, YcbcrEncoding e, TransferFunction t, Range r)\n> > +\t\t: primaries(p), ycbcrEncoding(e), transferFunction(t), 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> > +\tYcbcrEncoding ycbcrEncoding;\n> > +\tTransferFunction transferFunction;\n> > +\tRange range;\n> > +\n> > +\tconst std::string toString() const;\n> > +\tstatic const std::string toString(const std::optional<ColorSpace> &colorSpace);\n> \n> You return a std::string by value from both functions, you can drop the\n> const.\n> \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> \n> Looking good so far, let's continue :-)\n> \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..5fe4fcc2\n> > --- /dev/null\n> > +++ b/src/libcamera/color_space.cpp\n> > @@ -0,0 +1,305 @@\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 <map>\n> > +#include <sstream>\n> > +#include <utility>\n> > +#include <vector>\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> Let's make this a list:\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> > +/**\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\n> \n> I'd expand this a bit:\n> \n>  * \\brief These are raw colors directly from a sensor, the primaries are\n>  * unspecified\n> \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::YcbcrEncoding\n> > + * \\brief The Y'CbCr encoding\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::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::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, Encoding e, TransferFunction t, Range r)\n> > + * \\brief Construct a ColorSpace from explicit values\n> > + * \\param[in] p The color primaries\n> > + * \\param[in] e The Y'CbCr encoding\n> > + * \\param[in] t The transfer function for the color space\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> > +const std::string ColorSpace::toString() const\n> > +{\n> > +\t/* Print out a brief name only for standard color spaces. */\n> > +\n> > +\tstatic const std::vector<std::pair<ColorSpace, const char *>> colorSpaceNames = {\n> \n> If you make this a std::array I think it can become a constexpr\n> (std::vector got a constexpr constructor in C++20 only).\n> \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> \n> Maybe \"JPEG\", \"sRGB\", \"SMPTE170M\" ? That's how it's usually printed.\n> Same below.\n> \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<YcbcrEncoding, std::string> encodingNames = {\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<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<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 itEncoding = encodingNames.find(ycbcrEncoding);\n> > +\tstd::string encodingName =\n> > +\t\titEncoding == encodingNames.end() ? \"Invalid\" : itEncoding->second;\n> > +\n> > +\tauto itTransfer = transferNames.find(transferFunction);\n> > +\tstd::string transferName =\n> > +\t\titTransfer == transferNames.end() ? \"Invalid\" : itTransfer->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 << \"/\" << encodingName << \"/\" << transferName << \"/\" << rangeName;\n> > +\n> > +\treturn ss.str();\n> > +}\n> > +\n> > +/**\n> > + * \\brief Assemble and return a readable string representation of an\n> > + * optional ColorSpace\n> > + * \\return A string describing the optional ColorSpace\n> > + */\n> > +const std::string ColorSpace::toString(const std::optional<ColorSpace> &colorSpace)\n> \n> I'm not sure yet what this would be used for, I suppose I'll see in\n> further patches.\n\nNow that I see how this is being used, I think the documentation should\nbe expanded a bit.\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 for a\n * ColorSpace in the 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 \"Unknown\".\n *\n * \\return A string describing the optional ColorSpace\n\nAnd I wonder if we should return \"Unset\" instead of \"Unknown\". What do\nyou think ?\n\n> > +{\n> > +\tif (!colorSpace)\n> > +\t\treturn \"Unknown\";\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 with this color space\n> \n> s/with/by/ ? Same below.\n> \n> > + */\n> > +\n> > +/**\n> > + * \\var ColorSpace::transferFunction\n> > + * \\brief The transfer function used with this color space\n> > + */\n> > +\n> > +/**\n> > + * \\var ColorSpace::range\n> > + * \\brief The pixel range used with this 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> > +\tYcbcrEncoding::Rec601,\n> > +\tTransferFunction::Linear,\n> > +\tRange::Full\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the JPEG color space used for\n> > + * encoding JPEG images.\n> \n> s/.$//\n> \n> > + */\n> > +const ColorSpace ColorSpace::Jpeg = {\n> > +\tPrimaries::Rec709,\n> > +\tYcbcrEncoding::Rec601,\n> > +\tTransferFunction::Srgb,\n> > +\tRange::Full\n> > +};\n> > +\n> > +/**\n> > + * \\brief A constant representing the sRGB color space. This is\n> > + * identical to the JPEG color space except that the range Y'CbCr\n> > + * range is limited rather than full.\n> \n> I'd split the second line out of the brief:\n> \n>  * \\brief A constant representing the sRGB color space\n>  *\n>  * This is identical to the JPEG color space except that the range Y'CbCr range\n>  * is limited rather than full.\n> \n> Reviewed-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>\n> \n> > + */\n> > +const ColorSpace ColorSpace::Srgb = {\n> > +\tPrimaries::Rec709,\n> > +\tYcbcrEncoding::Rec601,\n> > +\tTransferFunction::Srgb,\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> > +\tYcbcrEncoding::Rec601,\n> > +\tTransferFunction::Rec709,\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> > +\tYcbcrEncoding::Rec709,\n> > +\tTransferFunction::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> > +\tYcbcrEncoding::Rec2020,\n> > +\tTransferFunction::Rec709,\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.ycbcrEncoding == rhs.ycbcrEncoding &&\n> > +\t       lhs.transferFunction == rhs.transferFunction &&\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 3D1AFBDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue,  7 Dec 2021 13:43:05 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id E0B016086A;\n\tTue,  7 Dec 2021 14:43:04 +0100 (CET)","from perceval.ideasonboard.com (perceval.ideasonboard.com\n\t[213.167.242.64])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 8E69160592\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue,  7 Dec 2021 14:43:03 +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 F0DAF556;\n\tTue,  7 Dec 2021 14:43:02 +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=\"fehlgac+\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/simple; d=ideasonboard.com;\n\ts=mail; t=1638884583;\n\tbh=765fSi798HedyJEiozl5JmDadbJFET0VflB0lHsjElo=;\n\th=Date:From:To:Cc:Subject:References:In-Reply-To:From;\n\tb=fehlgac+5gXfNy1e1qBxm73UyDhAj4gQkbF5B4OORJsU4R+h0Snuvs4MSjPe7cCl6\n\tdW8otL0zU1IllcsjYAy+cpWjxttwjKvl3iFFi7dbTCjkABvTFTySSY+AxLojjnjmYe\n\t+K5bKG+GaAK87zylBqsi3XhjQ5QjxMQcw/lLkPUs=","Date":"Tue, 7 Dec 2021 15:42:34 +0200","From":"Laurent Pinchart <laurent.pinchart@ideasonboard.com>","To":"David Plowman <david.plowman@raspberrypi.com>","Message-ID":"<Ya9kyuuFolshCyon@pendragon.ideasonboard.com>","References":"<20211206105032.13876-1-david.plowman@raspberrypi.com>\n\t<20211206105032.13876-2-david.plowman@raspberrypi.com>\n\t<Ya9dyyANEMpiPKYy@pendragon.ideasonboard.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=utf-8","Content-Disposition":"inline","In-Reply-To":"<Ya9dyyANEMpiPKYy@pendragon.ideasonboard.com>","Subject":"Re: [libcamera-devel] [PATCH v9 1/8] libcamera: Add ColorSpace class","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":"tfiga@google.com, libcamera-devel@lists.libcamera.org,\n\thverkuil-cisco@xs4all.nl","Errors-To":"libcamera-devel-bounces@lists.libcamera.org","Sender":"\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"}}]