"""
Y'CbCr Colour Encoding
======================
Defines the *Y'CbCr* colour encoding related attributes and objects:
- :attr:`colour.WEIGHTS_YCBCR`
- :func:`colour.matrix_YCbCr`
- :func:`colour.offset_YCbCr`
- :func:`colour.RGB_to_YCbCr`
- :func:`colour.YCbCr_to_RGB`
- :func:`colour.RGB_to_YcCbcCrc`
- :func:`colour.YcCbcCrc_to_RGB`
Notes
-----
- *Y'CbCr* is not an absolute colourspace.
References
----------
- :cite:`InternationalTelecommunicationUnion2011e` : International
Telecommunication Union. (2011). Recommendation ITU-T T.871 - Information
technology - Digital compression and coding of continuous-tone still
images: JPEG File Interchange Format (JFIF).
https://www.itu.int/rec/dologin_pub.asp?lang=e&\
id=T-REC-T.871-201105-I!!PDF-E&type=items
- :cite:`InternationalTelecommunicationUnion2015h` : International
Telecommunication Union. (2015). Recommendation ITU-R BT.2020 - Parameter
values for ultra-high definition television systems for production and
international programme exchange (pp. 1-8).
https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
R-REC-BT.2020-2-201510-I!!PDF-E.pdf
- :cite:`InternationalTelecommunicationUnion2015i` : International
Telecommunication Union. (2015). Recommendation ITU-R BT.709-6 - Parameter
values for the HDTV standards for production and international programme
exchange BT Series Broadcasting service (pp. 1-32).
https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
R-REC-BT.709-6-201506-I!!PDF-E.pdf
- :cite:`InternationalTelecommunicationUnion2018` : International
Telecommunication Union. (2018). Recommendation ITU-R BT.2100-2 - Image
parameter values for high dynamic range television for use in production
and international programme exchange.
https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
R-REC-BT.2100-2-201807-I!!PDF-E.pdf
- :cite:`SocietyofMotionPictureandTelevisionEngineers1999b` : Society of
Motion Picture and Television Engineers. (1999). ANSI/SMPTE 240M-1995 -
Signal Parameters - 1125-Line High-Definition Production Systems (pp. 1-7).
http://car.france3.mars.free.fr/HD/\
INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf
- :cite:`Wikipedia2004d` : Wikipedia. (2004). YCbCr. Retrieved February 29,
2016, from https://en.wikipedia.org/wiki/YCbCr
"""
from __future__ import annotations
import numpy as np
from colour.hints import Any, ArrayLike, NDArrayFloat, NDArrayReal, cast
from colour.models.rgb.transfer_functions import (
CV_range,
oetf_BT2020,
oetf_inverse_BT2020,
)
from colour.utilities import (
CanonicalMapping,
as_float_array,
as_int_array,
domain_range_scale,
from_range_1,
to_domain_1,
tsplit,
tstack,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Development"
__all__ = [
"WEIGHTS_YCBCR",
"round_BT2100",
"ranges_YCbCr",
"matrix_YCbCr",
"offset_YCbCr",
"RGB_to_YCbCr",
"YCbCr_to_RGB",
"RGB_to_YcCbcCrc",
"YcCbcCrc_to_RGB",
]
WEIGHTS_YCBCR: CanonicalMapping = CanonicalMapping(
{
"ITU-R BT.601": np.array([0.2990, 0.1140]),
"ITU-R BT.709": np.array([0.2126, 0.0722]),
"ITU-R BT.2020": np.array([0.2627, 0.0593]),
"SMPTE-240M": np.array([0.2122, 0.0865]),
}
)
"""
Luma weightings presets.
References
----------
:cite:`InternationalTelecommunicationUnion2011e`,
:cite:`InternationalTelecommunicationUnion2015i`,
:cite:`InternationalTelecommunicationUnion2015h`,
:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
:cite:`Wikipedia2004d`
"""
def round_BT2100(a: ArrayLike) -> NDArrayFloat:
"""
Round given array :math:`a` to the nearest int using the method define
as `Round` in *RecommendationITU-R BT.2100*.
Parameters
----------
a
Array :math:`a` to round.
Returns
-------
:class:`numpy.ndarray`
Rounded array :math:`a`.
References
----------
:cite:`InternationalTelecommunicationUnion2018`
Examples
--------
>>> round_BT2100(np.array([0.4, 0.5, 0.6]))
array([ 0., 1., 1.])
"""
return cast(NDArrayFloat, np.sign(a) * np.floor(np.abs(a) + 0.5))
def ranges_YCbCr(bits: int, is_legal: bool, is_int: bool) -> NDArrayFloat:
"""
Return the *Y'CbCr* colour encoding ranges array for given bit-depth,
range legality and representation.
Parameters
----------
bits
Bit-depth of the *Y'CbCr* colour encoding ranges array.
is_legal
Whether the *Y'CbCr* colour encoding ranges array is legal.
is_int
Whether the *Y'CbCr* colour encoding ranges array represents int
code values.
Returns
-------
:class:`numpy.ndarray`
*Y'CbCr* colour encoding ranges array.
Examples
--------
>>> ranges_YCbCr(8, True, True)
array([ 16., 235., 16., 240.])
>>> ranges_YCbCr(8, True, False) # doctest: +ELLIPSIS
array([ 0.0627451..., 0.9215686..., 0.0627451..., 0.9411764...])
>>> ranges_YCbCr(10, False, False)
array([ 0. , 1. , -0.5, 0.5])
>>> ranges_YCbCr(10, False, True)
array([ 0.0000000...e+00, 1.0230000...e+03, 5.0000000...e-01,
1.0235000...e+03])
"""
if is_legal:
ranges = as_float_array([16, 235, 16, 240])
ranges *= 2 ** (bits - 8)
else:
ranges = as_float_array([0, 2**bits - 1, 0, 2**bits - 1])
if not is_int:
ranges = as_int_array(ranges) / (2**bits - 1)
if is_int and not is_legal:
ranges = as_float_array(ranges)
ranges[2] = 0.5
ranges[3] = 2**bits - 0.5
if not is_int and not is_legal:
ranges[2] = -0.5
ranges[3] = 0.5
return ranges
[docs]def matrix_YCbCr(
K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
bits: int = 8,
is_legal: bool = False,
is_int: bool = False,
) -> NDArrayFloat:
"""
Compute the *Y'CbCr* to *R'G'B'* matrix for given weights, bit-depth,
range legality and representation.
The related offset for the *R'G'B'* to *Y'CbCr* matrix can be computed with
the :func:`colour.offset_YCbCr` definition.
Parameters
----------
K
Luma weighting coefficients of red and blue. See
:attr:`colour.WEIGHTS_YCBCR` for presets. Default is
*(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
bits
Bit-depth of the *Y'CbCr* colour encoding ranges array.
is_legal
Whether the *Y'CbCr* colour encoding ranges array is legal.
is_int
Whether the *Y'CbCr* colour encoding ranges array represents int
code values.
Returns
-------
:class:`numpy.ndarray`
*Y'CbCr* matrix.
Examples
--------
>>> matrix_YCbCr() # doctest: +ELLIPSIS
array([[ 1.0000000...e+00, ..., 1.5748000...e+00],
[ 1.0000000...e+00, -1.8732427...e-01, -4.6812427...e-01],
[ 1.0000000...e+00, 1.8556000...e+00, ...]])
>>> matrix_YCbCr(K=WEIGHTS_YCBCR["ITU-R BT.601"]) # doctest: +ELLIPSIS
array([[ 1.0000000...e+00, ..., 1.4020000...e+00],
[ 1.0000000...e+00, -3.4413628...e-01, -7.1413628...e-01],
[ 1.0000000...e+00, 1.7720000...e+00, ...]])
>>> matrix_YCbCr(is_legal=True) # doctest: +ELLIPSIS
array([[ 1.1643835...e+00, ..., 1.7927410...e+00],
[ 1.1643835...e+00, -2.1324861...e-01, -5.3290932...e-01],
[ 1.1643835...e+00, 2.1124017...e+00, ...]])
Matching the default output of the :func:`colour.RGB_to_YCbCr` is done as
follows:
>>> from colour.algebra import vector_dot
>>> from colour.utilities import as_int_array
>>> RGB = np.array([1.0, 1.0, 1.0])
>>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS
array([ 0.9215686..., 0.5019607..., 0.5019607...])
>>> YCbCr = vector_dot(np.linalg.inv(matrix_YCbCr(is_legal=True)), RGB)
>>> YCbCr += offset_YCbCr(is_legal=True)
>>> YCbCr # doctest: +ELLIPSIS
array([ 0.9215686..., 0.5019607..., 0.5019607...])
Matching the int output of the :func:`colour.RGB_to_YCbCr` is done as
follows:
>>> RGB = np.array([102, 0, 51])
>>> RGB_to_YCbCr(RGB, in_bits=8, in_int=True, out_bits=8, out_int=True)
... # doctest: +SKIP
array([ 38, 140, 171])
>>> YCbCr = vector_dot(np.linalg.inv(matrix_YCbCr(is_legal=True)), RGB)
>>> YCbCr += offset_YCbCr(is_legal=True, is_int=True)
>>> as_int_array(np.around(YCbCr))
... # doctest: +SKIP
array([ 38, 140, 171])
"""
Kr, Kb = K
Y_min, Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)
Y = np.array([Kr, (1 - Kr - Kb), Kb])
Cb = 0.5 * (np.array([0, 0, 1]) - Y) / (1 - Kb)
Cr = 0.5 * (np.array([1, 0, 0]) - Y) / (1 - Kr)
Y *= Y_max - Y_min
Cb *= C_max - C_min
Cr *= C_max - C_min
return np.linalg.inv(np.vstack([Y, Cb, Cr]))
[docs]def offset_YCbCr(
bits: int = 8, is_legal: bool = False, is_int: bool = False
) -> NDArrayFloat:
"""
Compute the *R'G'B'* to *Y'CbCr* offsets for given bit-depth, range
legality and representation.
The related *R'G'B'* to *Y'CbCr* matrix can be computed with the
:func:`colour.matrix_YCbCr` definition.
Parameters
----------
bits
Bit-depth of the *Y'CbCr* colour encoding ranges array.
is_legal
Whether the *Y'CbCr* colour encoding ranges array is legal.
is_int
Whether the *Y'CbCr* colour encoding ranges array represents int
code values.
Returns
-------
:class:`numpy.ndarray`
*Y'CbCr* matrix.
Examples
--------
>>> offset_YCbCr()
array([ 0., 0., 0.])
>>> offset_YCbCr(is_legal=True) # doctest: +ELLIPSIS
array([ 0.0627451..., 0.5019607..., 0.5019607...])
"""
Y_min, _Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)
Y_offset = Y_min
C_offset = (C_min + C_max) / 2
return np.array([Y_offset, C_offset, C_offset])
[docs]def RGB_to_YCbCr(
RGB: ArrayLike,
K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
in_bits: int = 10,
in_legal: bool = False,
in_int: bool = False,
out_bits: int = 8,
out_legal: bool = True,
out_int: bool = False,
clamp_int: bool = True,
**kwargs: Any,
) -> NDArrayReal:
"""
Convert an array of *R'G'B'* values to the corresponding *Y'CbCr* colour
encoding values array.
Parameters
----------
RGB
Input *R'G'B'* array of floats or int values.
K
Luma weighting coefficients of red and blue. See
:attr:`colour.WEIGHTS_YCBCR` for presets. Default is
*(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
in_bits
Bit-depth for int input, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Default is *10*.
in_legal
Whether to treat the input values as legal range. Default is *False*.
in_int
Whether to treat the input values as ``in_bits`` int code values.
Default is *False*.
out_bits
Bit-depth for int output, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Ignored if ``out_legal`` and
``out_int`` are both *False*. Default is *8*.
out_legal
Whether to return legal range values. Default is *True*.
out_int
Whether to return values as ``out_bits`` int code values. Default
is *False*.
clamp_int
Whether to clamp int output to allowable range for ``out_bits``.
Default is *True*.
Other Parameters
----------------
in_range
Array overriding the computed range such as
*in_range = (RGB_min, RGB_max)*. If ``in_range`` is undefined,
*RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range`
definition.
out_range
Array overriding the computed range such as
*out_range = (Y_min, Y_max, C_min, C_max)`. If ``out_range`` is
undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed
using :func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
Returns
-------
:class:`numpy.ndarray`
*Y'CbCr* colour encoding array of int or float values.
Warnings
--------
For *Recommendation ITU-R BT.2020*, :func:`colour.RGB_to_YCbCr` definition
is only applicable to the non-constant luminance implementation.
:func:`colour.RGB_to_YcCbcCrc` definition should be used for the constant
luminance case as per :cite:`InternationalTelecommunicationUnion2015h`.
Notes
-----
+----------------+-----------------------+---------------+
| **Domain \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``RGB`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
+----------------+-----------------------+---------------+
| **Range \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``YCbCr`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
\\* This definition has input and output int switches, thus the
domain-range scale information is only given for the floating point mode.
- The default arguments, ``**{'in_bits': 10, 'in_legal': False,
'in_int': False, 'out_bits': 8, 'out_legal': True, 'out_int': False}``
transform a float *R'G'B'* input array normalised to domain [0, 1]
(``in_bits`` is ignored) to a float *Y'CbCr* output array where *Y'* is
normalised to range [16 / 255, 235 / 255] and *Cb* and *Cr* are
normalised to range [16 / 255, 240./255]. The float values are
calculated based on an [0, 255] int range, but no 8-bit
quantisation or clamping are performed.
References
----------
:cite:`InternationalTelecommunicationUnion2011e`,
:cite:`InternationalTelecommunicationUnion2015i`,
:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
:cite:`Wikipedia2004d`
Examples
--------
>>> RGB = np.array([1.0, 1.0, 1.0])
>>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS
array([ 0.9215686..., 0.5019607..., 0.5019607...])
Matching the float output of *The Foundry Nuke*'s *Colorspace* node set to
*YCbCr*:
>>> RGB_to_YCbCr(
... RGB, out_range=(16 / 255, 235 / 255, 15.5 / 255, 239.5 / 255)
... )
... # doctest: +ELLIPSIS
array([ 0.9215686..., 0.5 , 0.5 ])
Matching the float output of *The Foundry Nuke*'s *Colorspace* node set to
*YPbPr*:
>>> RGB_to_YCbCr(RGB, out_legal=False, out_int=False)
... # doctest: +ELLIPSIS
array([ 1., 0., 0.])
Creating int code values as per standard *10-bit SDI*:
>>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True)
... # doctest: +ELLIPSIS
array([940, 512, 512]...)
For *JFIF JPEG* conversion as per *Recommendation ITU-T T.871*
>>> RGB = np.array([102, 0, 51])
>>> RGB_to_YCbCr(
... RGB,
... K=WEIGHTS_YCBCR["ITU-R BT.601"],
... in_range=(0, 255),
... out_range=(0, 255, 0.5, 255.5),
... out_int=True,
... )
... # doctest: +ELLIPSIS
array([ 36, 136, 175]...)
Note the use of [0.5, 255.5] for the *Cb / Cr* range, which is required so
that the *Cb* and *Cr* output is centered about 128. Using 255 centres it
about 127.5, meaning that there is no int code value to represent
achromatic colours. This does however create the possibility of output
int codes with value of 256, which cannot be stored in 8-bit int
representation. *Recommendation ITU-T T.871* specifies these should be
clamped to 255, which is applied with the default ``clamp_int=True``.
These *JFIF JPEG* ranges are also obtained as follows:
>>> RGB_to_YCbCr(
... RGB,
... K=WEIGHTS_YCBCR["ITU-R BT.601"],
... in_bits=8,
... in_int=True,
... out_legal=False,
... out_int=True,
... )
... # doctest: +ELLIPSIS
array([ 36, 136, 175]...)
"""
RGB = as_float_array(RGB) if in_int else to_domain_1(RGB)
Kr, Kb = K
RGB_min, RGB_max = kwargs.get(
"in_range", CV_range(in_bits, in_legal, in_int)
)
Y_min, Y_max, C_min, C_max = kwargs.get(
"out_range", ranges_YCbCr(out_bits, out_legal, out_int)
)
RGB_float = as_float_array(RGB) - RGB_min
RGB_float *= 1 / (RGB_max - RGB_min)
R, G, B = tsplit(RGB_float)
Y = Kr * R + (1 - Kr - Kb) * G + Kb * B
Cb = 0.5 * (B - Y) / (1 - Kb)
Cr = 0.5 * (R - Y) / (1 - Kr)
Y *= Y_max - Y_min
Y += Y_min
Cb *= C_max - C_min
Cr *= C_max - C_min
Cb += (C_max + C_min) / 2
Cr += (C_max + C_min) / 2
YCbCr = tstack([Y, Cb, Cr])
if out_int:
return as_int_array(
round_BT2100(
np.clip(YCbCr, 0, 2**out_bits - 1) if clamp_int else YCbCr
)
)
else:
return from_range_1(YCbCr)
[docs]def YCbCr_to_RGB(
YCbCr: ArrayLike,
K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
in_bits: int = 8,
in_legal: bool = True,
in_int: bool = False,
out_bits: int = 10,
out_legal: bool = False,
out_int: bool = False,
clamp_int: bool = True,
**kwargs: Any,
) -> NDArrayReal:
"""
Convert an array of *Y'CbCr* colour encoding values to the corresponding
*R'G'B'* values array.
Parameters
----------
YCbCr
Input *Y'CbCr* colour encoding array of int or float values.
K
Luma weighting coefficients of red and blue. See
:attr:`colour.WEIGHTS_YCBCR` for presets. Default is
*(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
in_bits
Bit-depth for int input, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Default is *8*.
in_legal
Whether to treat the input values as legal range. Default is *True*.
in_int
Whether to treat the input values as ``in_bits`` int code values.
Default is *False*.
out_bits
Bit-depth for int output, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Ignored if ``out_legal`` and
``out_int`` are both *False*. Default is *10*.
out_legal
Whether to return legal range values. Default is *False*.
out_int
Whether to return values as ``out_bits`` int code values. Default
is *False*.
clamp_int
Whether to clamp int output to allowable range for ``out_bits``.
Default is *True*.
Other Parameters
----------------
in_range
Array overriding the computed range such as
*in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is
undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using
:func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
out_range
Array overriding the computed range such as
*out_range = (RGB_min, RGB_max)*. If ``out_range`` is undefined,
*RGB_min* and *RGB_max* will be computed using :func:`colour.CV_range`
definition.
Returns
-------
:class:`numpy.ndarray`
*R'G'B'* array of int or float values.
Notes
-----
+----------------+-----------------------+---------------+
| **Domain \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``YCbCr`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
+----------------+-----------------------+---------------+
| **Range \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``RGB`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
\\* This definition has input and output int switches, thus the
domain-range scale information is only given for the floating point mode.
Warnings
--------
For *Recommendation ITU-R BT.2020*, :func:`colour.YCbCr_to_RGB`
definition is only applicable to the non-constant luminance implementation.
:func:`colour.YcCbcCrc_to_RGB` definition should be used for the constant
luminance case as per :cite:`InternationalTelecommunicationUnion2015h`.
References
----------
:cite:`InternationalTelecommunicationUnion2011e`,
:cite:`InternationalTelecommunicationUnion2015i`,
:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
:cite:`Wikipedia2004d`
Examples
--------
>>> YCbCr = np.array([502, 512, 512])
>>> YCbCr_to_RGB(YCbCr, in_bits=10, in_legal=True, in_int=True)
array([ 0.5, 0.5, 0.5])
"""
YCbCr = as_float_array(YCbCr) if in_int else to_domain_1(YCbCr)
Y, Cb, Cr = tsplit(YCbCr)
Kr, Kb = K
Y_min, Y_max, C_min, C_max = kwargs.get(
"in_range", ranges_YCbCr(in_bits, in_legal, in_int)
)
RGB_min, RGB_max = kwargs.get(
"out_range", CV_range(out_bits, out_legal, out_int)
)
Y -= Y_min
Cb -= (C_max + C_min) / 2
Cr -= (C_max + C_min) / 2
Y *= 1 / (Y_max - Y_min)
Cb *= 1 / (C_max - C_min)
Cr *= 1 / (C_max - C_min)
R = Y + (2 - 2 * Kr) * Cr
B = Y + (2 - 2 * Kb) * Cb
G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb)
RGB = tstack([R, G, B])
RGB *= RGB_max - RGB_min
RGB += RGB_min
RGB = (
as_int_array(
round_BT2100(
np.clip(RGB, 0, 2**out_bits - 1) if clamp_int else RGB
)
)
if out_int
else from_range_1(RGB)
)
return RGB
[docs]def RGB_to_YcCbcCrc(
RGB: ArrayLike,
out_bits: int = 10,
out_legal: bool = True,
out_int: bool = False,
is_12_bits_system: bool = False,
**kwargs: Any,
) -> NDArrayReal:
"""
Convert an array of *RGB* linear values to the corresponding *Yc'Cbc'Crc'*
colour encoding values array.
Parameters
----------
RGB
Input *RGB* array of linear float values.
out_bits
Bit-depth for int output, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Ignored if ``out_legal`` and
``out_int`` are both *False*. Default is *10*.
out_legal
Whether to return legal range values. Default is *True*.
out_int
Whether to return values as ``out_bits`` int code values. Default
is *False*.
is_12_bits_system
*Recommendation ITU-R BT.2020* OETF (OECF) adopts different parameters
for 10 and 12 bit systems. Default is *False*.
Other Parameters
----------------
out_range
Array overriding the computed range such as
*out_range = (Y_min, Y_max, C_min, C_max)*. If ``out_range`` is
undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed
using :func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
Returns
-------
:class:`numpy.ndarray`
*Yc'Cbc'Crc'* colour encoding array of int or float values.
Notes
-----
+----------------+-----------------------+---------------+
| **Domain \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``RGB`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
+----------------+-----------------------+---------------+
| **Range \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``YcCbcCrc`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
\\* This definition has input and output int switches, thus the
domain-range scale information is only given for the floating point mode.
Warnings
--------
This definition is specifically for usage with
*Recommendation ITU-R BT.2020* when adopting the constant luminance
implementation.
References
----------
:cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d`
Examples
--------
>>> RGB = np.array([0.18, 0.18, 0.18])
>>> RGB_to_YcCbcCrc(
... RGB,
... out_legal=True,
... out_bits=10,
... out_int=True,
... is_12_bits_system=False,
... )
... # doctest: +ELLIPSIS
array([422, 512, 512]...)
"""
R, G, B = tsplit(to_domain_1(RGB))
Y_min, Y_max, C_min, C_max = kwargs.get(
"out_range", ranges_YCbCr(out_bits, out_legal, out_int)
)
Yc = 0.2627 * R + 0.6780 * G + 0.0593 * B
with domain_range_scale("ignore"):
Yc = oetf_BT2020(Yc, is_12_bits_system=is_12_bits_system)
R = oetf_BT2020(R, is_12_bits_system=is_12_bits_system)
B = oetf_BT2020(B, is_12_bits_system=is_12_bits_system)
Cbc = np.where((B - Yc) <= 0, (B - Yc) / 1.9404, (B - Yc) / 1.5816)
Crc = np.where((R - Yc) <= 0, (R - Yc) / 1.7184, (R - Yc) / 0.9936)
Yc *= Y_max - Y_min
Yc += Y_min
Cbc *= C_max - C_min
Crc *= C_max - C_min
Cbc += (C_max + C_min) / 2
Crc += (C_max + C_min) / 2
YcCbcCrc = tstack([Yc, Cbc, Crc])
if out_int:
return as_int_array(np.round(YcCbcCrc))
else:
return from_range_1(YcCbcCrc)
[docs]def YcCbcCrc_to_RGB(
YcCbcCrc: ArrayLike,
in_bits: int = 10,
in_legal: bool = True,
in_int: bool = False,
is_12_bits_system: bool = False,
**kwargs: Any,
) -> NDArrayFloat:
"""
Convert an array of *Yc'Cbc'Crc'* colour encoding values to the
corresponding *RGB* array of linear values.
Parameters
----------
YcCbcCrc
Input *Yc'Cbc'Crc'* colour encoding array of linear float values.
in_bits
Bit-depth for int input, or used in the calculation of the
denominator for legal range float values, i.e. 8-bit means the float
value for legal white is *235 / 255*. Default is *10*.
in_legal
Whether to treat the input values as legal range. Default is *False*.
in_int
Whether to treat the input values as ``in_bits`` int code values.
Default is *False*.
is_12_bits_system
*Recommendation ITU-R BT.2020* EOTF (EOCF) adopts different parameters
for 10 and 12 bit systems. Default is *False*.
Other Parameters
----------------
in_range
Array overriding the computed range such as
*in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is
undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed using
:func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
Returns
-------
:class:`numpy.ndarray`
*RGB* array of linear float values.
Notes
-----
+----------------+-----------------------+---------------+
| **Domain \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``YcCbcCrc`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
+----------------+-----------------------+---------------+
| **Range \\*** | **Scale - Reference** | **Scale - 1** |
+================+=======================+===============+
| ``RGB`` | [0, 1] | [0, 1] |
+----------------+-----------------------+---------------+
\\* This definition has input and output int switches, thus the
domain-range scale information is only given for the floating point mode.
Warnings
--------
This definition is specifically for usage with
*Recommendation ITU-R BT.2020* when adopting the constant luminance
implementation.
References
----------
:cite:`InternationalTelecommunicationUnion2015h`,
:cite:`Wikipedia2004d`
Examples
--------
>>> YcCbcCrc = np.array([1689, 2048, 2048])
>>> YcCbcCrc_to_RGB(
... YcCbcCrc,
... in_legal=True,
... in_bits=12,
... in_int=True,
... is_12_bits_system=True,
... )
... # doctest: +ELLIPSIS
array([ 0.1800903..., 0.1800903..., 0.1800903...])
"""
YcCbcCrc = as_float_array(YcCbcCrc) if in_int else to_domain_1(YcCbcCrc)
Yc, Cbc, Crc = tsplit(YcCbcCrc)
Y_min, Y_max, C_min, C_max = kwargs.get(
"in_range", ranges_YCbCr(in_bits, in_legal, in_int)
)
Yc -= Y_min
Cbc -= (C_max + C_min) / 2
Crc -= (C_max + C_min) / 2
Yc *= 1 / (Y_max - Y_min)
Cbc *= 1 / (C_max - C_min)
Crc *= 1 / (C_max - C_min)
B = np.where(Cbc <= 0, Cbc * 1.9404 + Yc, Cbc * 1.5816 + Yc)
R = np.where(Crc <= 0, Crc * 1.7184 + Yc, Crc * 0.9936 + Yc)
with domain_range_scale("ignore"):
Yc = oetf_inverse_BT2020(Yc, is_12_bits_system)
B = oetf_inverse_BT2020(B, is_12_bits_system)
R = oetf_inverse_BT2020(R, is_12_bits_system)
G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780
RGB = tstack([R, G, B])
return from_range_1(RGB)