[libcamera-devel] [PATCH v9 1/8] libcamera: Add ColorSpace class

Laurent Pinchart laurent.pinchart at ideasonboard.com
Tue Dec 7 14:42:34 CET 2021


On Tue, Dec 07, 2021 at 03:12:44PM +0200, Laurent Pinchart wrote:
> Hi David,
> 
> Thank you for the patch.
> 
> On Mon, Dec 06, 2021 at 10:50:24AM +0000, David Plowman wrote:
> > This class represents a color space by defining its color primaries,
> > YCbCr encoding, the transfer (gamma) function it uses, and whether the
> > output is full or limited range.
> > 
> > Signed-off-by: David Plowman <david.plowman at raspberrypi.com>
> > Reviewed-by: Naushir Patuck <naush at raspberrypi.com>
> > Reviewed-by: Umang Jain <umang.jain at ideasonboard.com>
> > Reviewed-by: Jacopo Mondi <jacopo at jmondi.org>
> > ---
> >  include/libcamera/color_space.h |  69 ++++++++
> >  include/libcamera/meson.build   |   1 +
> >  src/libcamera/color_space.cpp   | 305 ++++++++++++++++++++++++++++++++
> >  src/libcamera/meson.build       |   1 +
> >  4 files changed, 376 insertions(+)
> >  create mode 100644 include/libcamera/color_space.h
> >  create mode 100644 src/libcamera/color_space.cpp
> > 
> > diff --git a/include/libcamera/color_space.h b/include/libcamera/color_space.h
> > new file mode 100644
> > index 00000000..0fe6b580
> > --- /dev/null
> > +++ b/include/libcamera/color_space.h
> > @@ -0,0 +1,69 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Raspberry Pi (Trading) Limited
> > + *
> > + * color_space.h - color space definitions
> > + */
> > +
> > +#pragma once
> > +
> > +#include <optional>
> > +#include <string>
> > +
> > +namespace libcamera {
> > +
> > +class ColorSpace
> > +{
> > +public:
> > +	enum class Primaries {
> > +		Raw,
> > +		Smpte170m,
> 
> It's always annoying to find the proper way to express abbrevitations in
> camelCase. We'll figure out any change later, if needed.
> 
> > +		Rec709,
> > +		Rec2020,
> > +	};
> > +
> > +	enum class YcbcrEncoding {
> > +		Rec601,
> > +		Rec709,
> > +		Rec2020,
> 
> Sorry if this has been discussed before, but do we need a None (or
> similar) entry for non-YUV formats ?
> 
> > +	};
> > +
> > +	enum class TransferFunction {
> > +		Linear,
> > +		Srgb,
> > +		Rec709,
> > +	};
> 
> Nitpicking (and can be handled during merging without a v10 if this is
> the only comment that needs addressing), could you move the
> TransferFunction before the YcbcrEncoding, to match the order in which a
> colour space "applies" ? Same for the function arguments, member
> functions and documentation below (and possibly in the rest of the
> series, if applicable)
> 
> > +
> > +	enum class Range {
> > +		Full,
> > +		Limited,
> > +	};
> > +
> > +	constexpr ColorSpace(Primaries p, YcbcrEncoding e, TransferFunction t, Range r)
> > +		: primaries(p), ycbcrEncoding(e), transferFunction(t), range(r)
> > +	{
> > +	}
> > +
> > +	static const ColorSpace Raw;
> > +	static const ColorSpace Jpeg;
> > +	static const ColorSpace Srgb;
> > +	static const ColorSpace Smpte170m;
> > +	static const ColorSpace Rec709;
> > +	static const ColorSpace Rec2020;
> > +
> > +	Primaries primaries;
> > +	YcbcrEncoding ycbcrEncoding;
> > +	TransferFunction transferFunction;
> > +	Range range;
> > +
> > +	const std::string toString() const;
> > +	static const std::string toString(const std::optional<ColorSpace> &colorSpace);
> 
> You return a std::string by value from both functions, you can drop the
> const.
> 
> > +};
> > +
> > +bool operator==(const ColorSpace &lhs, const ColorSpace &rhs);
> > +static inline bool operator!=(const ColorSpace &lhs, const ColorSpace &rhs)
> > +{
> > +	return !(lhs == rhs);
> > +}
> > +
> > +} /* namespace libcamera */
> 
> Looking good so far, let's continue :-)
> 
> > diff --git a/include/libcamera/meson.build b/include/libcamera/meson.build
> > index 5f42977c..fd767b11 100644
> > --- a/include/libcamera/meson.build
> > +++ b/include/libcamera/meson.build
> > @@ -5,6 +5,7 @@ libcamera_include_dir = 'libcamera' / 'libcamera'
> >  libcamera_public_headers = files([
> >      'camera.h',
> >      'camera_manager.h',
> > +    'color_space.h',
> >      'controls.h',
> >      'framebuffer.h',
> >      'framebuffer_allocator.h',
> > diff --git a/src/libcamera/color_space.cpp b/src/libcamera/color_space.cpp
> > new file mode 100644
> > index 00000000..5fe4fcc2
> > --- /dev/null
> > +++ b/src/libcamera/color_space.cpp
> > @@ -0,0 +1,305 @@
> > +/* SPDX-License-Identifier: LGPL-2.1-or-later */
> > +/*
> > + * Copyright (C) 2021, Raspberry Pi (Trading) Limited
> > + *
> > + * color_space.cpp - color spaces.
> > + */
> > +
> > +#include <libcamera/color_space.h>
> > +
> > +#include <algorithm>
> > +#include <map>
> > +#include <sstream>
> > +#include <utility>
> > +#include <vector>
> > +
> > +/**
> > + * \file color_space.h
> > + * \brief Class and enums to represent color spaces
> > + */
> > +
> > +namespace libcamera {
> > +
> > +/**
> > + * \class ColorSpace
> > + * \brief Class to describe a color space
> > + *
> > + * The ColorSpace class defines the color primaries, the Y'CbCr encoding,
> > + * the transfer function associated with the color space, and the range
> > + * (sometimes also referred to as the quantisation) of the color space.
> > + *
> > + * Certain combinations of these fields form well-known standard color
> > + * spaces such as "JPEG" or "REC709".
> > + *
> > + * In the strictest sense a "color space" formally only refers to the
> > + * color primaries and white point. Here, however, the ColorSpace class
> > + * adopts the common broader usage that includes the transfer function,
> > + * Y'CbCr encoding method and quantisation.
> > + *
> > + * For more information on the specific color spaces described here, please
> > + * see:
> > + *
> > + * <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-srgb">sRGB</a>
> > + * <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-jpeg">JPEG</a>
> > + * <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-smpte-170m">SMPTE 170M</a>
> > + * <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-rec709">Rec.709</a>
> > + * <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-bt2020">Rec.2020</a>
> 
> Let's make this a list:
> 
>  * - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-srgb">sRGB</a>
>  * - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-jpeg">JPEG</a>
>  * - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-smpte-170m">SMPTE 170M</a>
>  * - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-rec709">Rec.709</a>
>  * - <a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/colorspaces-details.html#col-bt2020">Rec.2020</a>
> 
> > + */
> > +
> > +/**
> > + * \enum ColorSpace::Primaries
> > + * \brief The color primaries for this color space
> > + *
> > + * \var ColorSpace::Primaries::Raw
> > + * \brief These are raw colors directly from a sensor
> 
> I'd expand this a bit:
> 
>  * \brief These are raw colors directly from a sensor, the primaries are
>  * unspecified
> 
> > + *
> > + * \var ColorSpace::Primaries::Smpte170m
> > + * \brief SMPTE 170M color primaries
> > + *
> > + * \var ColorSpace::Primaries::Rec709
> > + * \brief Rec.709 color primaries
> > + *
> > + * \var ColorSpace::Primaries::Rec2020
> > + * \brief Rec.2020 color primaries
> > + */
> > +
> > +/**
> > + * \enum ColorSpace::YcbcrEncoding
> > + * \brief The Y'CbCr encoding
> > + *
> > + * \var ColorSpace::YcbcrEncoding::Rec601
> > + * \brief Rec.601 Y'CbCr encoding
> > + *
> > + * \var ColorSpace::YcbcrEncoding::Rec709
> > + * \brief Rec.709 Y'CbCr encoding
> > + *
> > + * \var ColorSpace::YcbcrEncoding::Rec2020
> > + * \brief Rec.2020 Y'CbCr encoding
> > + */
> > +
> > +/**
> > + * \enum ColorSpace::TransferFunction
> > + * \brief The transfer function used for this color space
> > + *
> > + * \var ColorSpace::TransferFunction::Linear
> > + * \brief This color space uses a linear (identity) transfer function
> > + *
> > + * \var ColorSpace::TransferFunction::Srgb
> > + * \brief sRGB transfer function
> > + *
> > + * \var ColorSpace::TransferFunction::Rec709
> > + * \brief Rec.709 transfer function
> > + */
> > +
> > +/**
> > + * \enum ColorSpace::Range
> > + * \brief The range (sometimes "quantisation") for this color space
> > + *
> > + * \var ColorSpace::Range::Full
> > + * \brief This color space uses full range pixel values
> > + *
> > + * \var ColorSpace::Range::Limited
> > + * \brief This color space uses limited range pixel values, being
> > + * 16 to 235 for Y' and 16 to 240 for Cb and Cr (8 bits per sample)
> > + * or 64 to 940 for Y' and 16 to 960 for Cb and Cr (10 bits)
> > + */
> > +
> > +/**
> > + * \fn ColorSpace::ColorSpace(Primaries p, Encoding e, TransferFunction t, Range r)
> > + * \brief Construct a ColorSpace from explicit values
> > + * \param[in] p The color primaries
> > + * \param[in] e The Y'CbCr encoding
> > + * \param[in] t The transfer function for the color space
> > + * \param[in] r The range of the pixel values in this color space
> > + */
> > +
> > +/**
> > + * \brief Assemble and return a readable string representation of the
> > + * ColorSpace
> > + *
> > + * If the color space matches a standard ColorSpace (such as ColorSpace::Jpeg)
> > + * then the short name of the color space ("Jpeg") is returned. Otherwise
> > + * the four constituent parts of the ColorSpace are assembled into a longer
> > + * string.
> > + *
> > + * \return A string describing the ColorSpace
> > + */
> > +const std::string ColorSpace::toString() const
> > +{
> > +	/* Print out a brief name only for standard color spaces. */
> > +
> > +	static const std::vector<std::pair<ColorSpace, const char *>> colorSpaceNames = {
> 
> If you make this a std::array I think it can become a constexpr
> (std::vector got a constexpr constructor in C++20 only).
> 
> > +		{ ColorSpace::Raw, "Raw" },
> > +		{ ColorSpace::Jpeg, "Jpeg" },
> > +		{ ColorSpace::Srgb, "Srgb" },
> > +		{ ColorSpace::Smpte170m, "Smpte170m" },
> > +		{ ColorSpace::Rec709, "Rec709" },
> > +		{ ColorSpace::Rec2020, "Rec2020" },
> 
> Maybe "JPEG", "sRGB", "SMPTE170M" ? That's how it's usually printed.
> Same below.
> 
> > +	};
> > +	auto it = std::find_if(colorSpaceNames.begin(), colorSpaceNames.end(),
> > +			       [this](const auto &item) {
> > +				       return *this == item.first;
> > +			       });
> > +	if (it != colorSpaceNames.end())
> > +		return std::string(it->second);
> > +
> > +	/* Assemble a name made of the constituent fields. */
> > +
> > +	static const std::map<Primaries, std::string> primariesNames = {
> > +		{ Primaries::Raw, "Raw" },
> > +		{ Primaries::Smpte170m, "Smpte170m" },
> > +		{ Primaries::Rec709, "Rec709" },
> > +		{ Primaries::Rec2020, "Rec2020" },
> > +	};
> > +	static const std::map<YcbcrEncoding, std::string> encodingNames = {
> > +		{ YcbcrEncoding::Rec601, "Rec601" },
> > +		{ YcbcrEncoding::Rec709, "Rec709" },
> > +		{ YcbcrEncoding::Rec2020, "Rec2020" },
> > +	};
> > +	static const std::map<TransferFunction, std::string> transferNames = {
> > +		{ TransferFunction::Linear, "Linear" },
> > +		{ TransferFunction::Srgb, "Srgb" },
> > +		{ TransferFunction::Rec709, "Rec709" },
> > +	};
> > +	static const std::map<Range, std::string> rangeNames = {
> > +		{ Range::Full, "Full" },
> > +		{ Range::Limited, "Limited" },
> > +	};
> > +
> > +	auto itPrimaries = primariesNames.find(primaries);
> > +	std::string primariesName =
> > +		itPrimaries == primariesNames.end() ? "Invalid" : itPrimaries->second;
> > +
> > +	auto itEncoding = encodingNames.find(ycbcrEncoding);
> > +	std::string encodingName =
> > +		itEncoding == encodingNames.end() ? "Invalid" : itEncoding->second;
> > +
> > +	auto itTransfer = transferNames.find(transferFunction);
> > +	std::string transferName =
> > +		itTransfer == transferNames.end() ? "Invalid" : itTransfer->second;
> > +
> > +	auto itRange = rangeNames.find(range);
> > +	std::string rangeName =
> > +		itRange == rangeNames.end() ? "Invalid" : itRange->second;
> > +
> > +	std::stringstream ss;
> > +	ss << primariesName << "/" << encodingName << "/" << transferName << "/" << rangeName;
> > +
> > +	return ss.str();
> > +}
> > +
> > +/**
> > + * \brief Assemble and return a readable string representation of an
> > + * optional ColorSpace
> > + * \return A string describing the optional ColorSpace
> > + */
> > +const std::string ColorSpace::toString(const std::optional<ColorSpace> &colorSpace)
> 
> I'm not sure yet what this would be used for, I suppose I'll see in
> further patches.

Now that I see how this is being used, I think the documentation should
be expanded a bit.

 * \brief Assemble and return a readable string representation of an
 * optional ColorSpace
 *
 * This is a convenience helper to easily obtain a string representation for a
 * ColorSpace in the parts of the libcamera API where it is stored in a
 * std::optional<>. If the ColorSpace is set, this function returns
 * \a colorSpace.toString(), otherwise it returns "Unknown".
 *
 * \return A string describing the optional ColorSpace

And I wonder if we should return "Unset" instead of "Unknown". What do
you think ?

> > +{
> > +	if (!colorSpace)
> > +		return "Unknown";
> > +
> > +	return colorSpace->toString();
> > +}
> > +
> > +/**
> > + * \var ColorSpace::primaries
> > + * \brief The color primaries of this color space
> > + */
> > +
> > +/**
> > + * \var ColorSpace::ycbcrEncoding
> > + * \brief The Y'CbCr encoding used with this color space
> 
> s/with/by/ ? Same below.
> 
> > + */
> > +
> > +/**
> > + * \var ColorSpace::transferFunction
> > + * \brief The transfer function used with this color space
> > + */
> > +
> > +/**
> > + * \var ColorSpace::range
> > + * \brief The pixel range used with this color space
> > + */
> > +
> > +/**
> > + * \brief A constant representing a raw color space (from a sensor)
> > + */
> > +const ColorSpace ColorSpace::Raw = {
> > +	Primaries::Raw,
> > +	YcbcrEncoding::Rec601,
> > +	TransferFunction::Linear,
> > +	Range::Full
> > +};
> > +
> > +/**
> > + * \brief A constant representing the JPEG color space used for
> > + * encoding JPEG images.
> 
> s/.$//
> 
> > + */
> > +const ColorSpace ColorSpace::Jpeg = {
> > +	Primaries::Rec709,
> > +	YcbcrEncoding::Rec601,
> > +	TransferFunction::Srgb,
> > +	Range::Full
> > +};
> > +
> > +/**
> > + * \brief A constant representing the sRGB color space. This is
> > + * identical to the JPEG color space except that the range Y'CbCr
> > + * range is limited rather than full.
> 
> I'd split the second line out of the brief:
> 
>  * \brief A constant representing the sRGB color space
>  *
>  * This is identical to the JPEG color space except that the range Y'CbCr range
>  * is limited rather than full.
> 
> Reviewed-by: Laurent Pinchart <laurent.pinchart at ideasonboard.com>
> 
> > + */
> > +const ColorSpace ColorSpace::Srgb = {
> > +	Primaries::Rec709,
> > +	YcbcrEncoding::Rec601,
> > +	TransferFunction::Srgb,
> > +	Range::Limited
> > +};
> > +
> > +/**
> > + * \brief A constant representing the SMPTE170M color space
> > + */
> > +const ColorSpace ColorSpace::Smpte170m = {
> > +	Primaries::Smpte170m,
> > +	YcbcrEncoding::Rec601,
> > +	TransferFunction::Rec709,
> > +	Range::Limited
> > +};
> > +
> > +/**
> > + * \brief A constant representing the Rec.709 color space
> > + */
> > +const ColorSpace ColorSpace::Rec709 = {
> > +	Primaries::Rec709,
> > +	YcbcrEncoding::Rec709,
> > +	TransferFunction::Rec709,
> > +	Range::Limited
> > +};
> > +
> > +/**
> > + * \brief A constant representing the Rec.2020 color space
> > + */
> > +const ColorSpace ColorSpace::Rec2020 = {
> > +	Primaries::Rec2020,
> > +	YcbcrEncoding::Rec2020,
> > +	TransferFunction::Rec709,
> > +	Range::Limited
> > +};
> > +
> > +/**
> > + * \brief Compare color spaces for equality
> > + * \return True if the two color spaces are identical, false otherwise
> > + */
> > +bool operator==(const ColorSpace &lhs, const ColorSpace &rhs)
> > +{
> > +	return lhs.primaries == rhs.primaries &&
> > +	       lhs.ycbcrEncoding == rhs.ycbcrEncoding &&
> > +	       lhs.transferFunction == rhs.transferFunction &&
> > +	       lhs.range == rhs.range;
> > +}
> > +
> > +/**
> > + * \fn bool operator!=(const ColorSpace &lhs, const ColorSpace &rhs)
> > + * \brief Compare color spaces for inequality
> > + * \return True if the two color spaces are not identical, false otherwise
> > + */
> > +
> > +} /* namespace libcamera */
> > diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
> > index 2e54cc04..4045e24e 100644
> > --- a/src/libcamera/meson.build
> > +++ b/src/libcamera/meson.build
> > @@ -9,6 +9,7 @@ libcamera_sources = files([
> >      'camera_manager.cpp',
> >      'camera_sensor.cpp',
> >      'camera_sensor_properties.cpp',
> > +    'color_space.cpp',
> >      'controls.cpp',
> >      'control_serializer.cpp',
> >      'control_validator.cpp',

-- 
Regards,

Laurent Pinchart


More information about the libcamera-devel mailing list