Coverage for colour/models/rgb/ycbcr.py: 100%
126 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Y'CbCr Colour Encoding
3======================
5Define the *Y'CbCr* colour encoding related attributes and objects.
7- :attr:`colour.WEIGHTS_YCBCR`
8- :func:`colour.matrix_YCbCr`
9- :func:`colour.offset_YCbCr`
10- :func:`colour.RGB_to_YCbCr`
11- :func:`colour.YCbCr_to_RGB`
12- :func:`colour.RGB_to_YcCbcCrc`
13- :func:`colour.YcCbcCrc_to_RGB`
15Notes
16-----
17- *Y'CbCr* is not an absolute colourspace.
19References
20----------
21- :cite:`InternationalTelecommunicationUnion2011e` : International
22 Telecommunication Union. (2011). Recommendation ITU-T T.871 - Information
23 technology - Digital compression and coding of continuous-tone still
24 images: JPEG File Interchange Format (JFIF).
25 https://www.itu.int/rec/dologin_pub.asp?lang=e&\
26id=T-REC-T.871-201105-I!!PDF-E&type=items
27- :cite:`InternationalTelecommunicationUnion2015h` : International
28 Telecommunication Union. (2015). Recommendation ITU-R BT.2020 - Parameter
29 values for ultra-high definition television systems for production and
30 international programme exchange (pp. 1-8).
31 https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
32R-REC-BT.2020-2-201510-I!!PDF-E.pdf
33- :cite:`InternationalTelecommunicationUnion2015i` : International
34 Telecommunication Union. (2015). Recommendation ITU-R BT.709-6 - Parameter
35 values for the HDTV standards for production and international programme
36 exchange BT Series Broadcasting service (pp. 1-32).
37 https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
38R-REC-BT.709-6-201506-I!!PDF-E.pdf
39- :cite:`InternationalTelecommunicationUnion2018` : International
40 Telecommunication Union. (2018). Recommendation ITU-R BT.2100-2 - Image
41 parameter values for high dynamic range television for use in production
42 and international programme exchange.
43 https://www.itu.int/dms_pubrec/itu-r/rec/bt/\
44R-REC-BT.2100-2-201807-I!!PDF-E.pdf
45- :cite:`SocietyofMotionPictureandTelevisionEngineers1999b` : Society of
46 Motion Picture and Television Engineers. (1999). ANSI/SMPTE 240M-1995 -
47 Signal Parameters - 1125-Line High-Definition Production Systems (pp. 1-7).
48 http://car.france3.mars.free.fr/HD/\
49INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf
50- :cite:`Wikipedia2004d` : Wikipedia. (2004). YCbCr. Retrieved February 29,
51 2016, from https://en.wikipedia.org/wiki/YCbCr
52"""
54from __future__ import annotations
56import typing
58import numpy as np
60if typing.TYPE_CHECKING:
61 from colour.hints import Any, ArrayLike, Domain1, NDArrayReal, Range1
63from colour.hints import Annotated, NDArrayFloat, cast
64from colour.models.rgb.transfer_functions import (
65 CV_range,
66 oetf_BT2020,
67 oetf_inverse_BT2020,
68)
69from colour.utilities import (
70 CanonicalMapping,
71 as_float_array,
72 as_int_array,
73 domain_range_scale,
74 from_range_1,
75 to_domain_1,
76 tsplit,
77 tstack,
78)
80__author__ = "Colour Developers"
81__copyright__ = "Copyright 2013 Colour Developers"
82__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
83__maintainer__ = "Colour Developers"
84__email__ = "colour-developers@colour-science.org"
85__status__ = "Development"
87__all__ = [
88 "WEIGHTS_YCBCR",
89 "round_BT2100",
90 "ranges_YCbCr",
91 "matrix_YCbCr",
92 "offset_YCbCr",
93 "RGB_to_YCbCr",
94 "YCbCr_to_RGB",
95 "RGB_to_YcCbcCrc",
96 "YcCbcCrc_to_RGB",
97]
99WEIGHTS_YCBCR: CanonicalMapping = CanonicalMapping(
100 {
101 "ITU-R BT.601": np.array([0.2990, 0.1140]),
102 "ITU-R BT.709": np.array([0.2126, 0.0722]),
103 "ITU-R BT.2020": np.array([0.2627, 0.0593]),
104 "SMPTE-240M": np.array([0.2122, 0.0865]),
105 }
106)
107"""
108Luma weightings presets.
110References
111----------
112:cite:`InternationalTelecommunicationUnion2011e`,
113:cite:`InternationalTelecommunicationUnion2015i`,
114:cite:`InternationalTelecommunicationUnion2015h`,
115:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
116:cite:`Wikipedia2004d`
117"""
120def round_BT2100(a: ArrayLike) -> NDArrayFloat:
121 """
122 Round the specified array :math:`a` to the nearest integer using the
123 rounding method defined in *Recommendation ITU-R BT.2100*.
125 This function implements the specific rounding behaviour required by
126 *Recommendation ITU-R BT.2100*, where values are rounded to the nearest
127 integer with 0.5 rounding up.
129 Parameters
130 ----------
131 a
132 Array :math:`a` to round.
134 Returns
135 -------
136 :class:`numpy.ndarray`
137 Rounded array :math:`a`.
139 References
140 ----------
141 :cite:`InternationalTelecommunicationUnion2018`
143 Examples
144 --------
145 >>> round_BT2100(np.array([0.4, 0.5, 0.6]))
146 array([ 0., 1., 1.])
147 """
149 return cast("NDArrayFloat", np.sign(a) * np.floor(np.abs(a) + 0.5))
152def ranges_YCbCr(bits: int, is_legal: bool, is_int: bool) -> NDArrayFloat:
153 """
154 Return the *Y'CbCr* colour encoding ranges array for the specified
155 bit-depth, range legality and representation.
157 Parameters
158 ----------
159 bits
160 Bit-depth of the *Y'CbCr* colour encoding ranges array.
161 is_legal
162 Whether the *Y'CbCr* colour encoding ranges array is legal.
163 is_int
164 Whether the *Y'CbCr* colour encoding ranges array represents integer
165 code values.
167 Returns
168 -------
169 :class:`numpy.ndarray`
170 *Y'CbCr* colour encoding ranges array.
172 Examples
173 --------
174 >>> ranges_YCbCr(8, True, True)
175 array([ 16., 235., 16., 240.])
176 >>> ranges_YCbCr(8, True, False) # doctest: +ELLIPSIS
177 array([ 0.0627451..., 0.9215686..., 0.0627451..., 0.9411764...])
178 >>> ranges_YCbCr(10, False, False)
179 array([ 0. , 1. , -0.5, 0.5])
180 >>> ranges_YCbCr(10, False, True)
181 array([ 0.0000000...e+00, 1.0230000...e+03, 5.0000000...e-01,
182 1.0235000...e+03])
183 """
185 if is_legal:
186 ranges = as_float_array([16, 235, 16, 240])
187 ranges *= 2 ** (bits - 8)
188 else:
189 ranges = as_float_array([0, 2**bits - 1, 0, 2**bits - 1])
191 if not is_int:
192 ranges = as_int_array(ranges) / (2**bits - 1)
194 if is_int and not is_legal:
195 ranges = as_float_array(ranges)
196 ranges[2] = 0.5
197 ranges[3] = 2**bits - 0.5
199 if not is_int and not is_legal:
200 ranges[2] = -0.5
201 ranges[3] = 0.5
203 return ranges
206def matrix_YCbCr(
207 K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
208 bits: int = 8,
209 is_legal: bool = False,
210 is_int: bool = False,
211) -> NDArrayFloat:
212 """
213 Compute the *Y'CbCr* to *R'G'B'* matrix for the specified weights,
214 bit-depth, range legality and representation.
216 The related offset for the *R'G'B'* to *Y'CbCr* matrix can be computed
217 with the :func:`colour.offset_YCbCr` definition.
219 Parameters
220 ----------
221 K
222 Luma weighting coefficients of red and blue. See
223 :attr:`colour.WEIGHTS_YCBCR` for presets. Default is
224 *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
225 bits
226 Bit-depth of the *Y'CbCr* colour encoding ranges array.
227 is_legal
228 Whether the *Y'CbCr* colour encoding ranges array is legal.
229 is_int
230 Whether the *Y'CbCr* colour encoding ranges array represents int
231 code values.
233 Returns
234 -------
235 :class:`numpy.ndarray`
236 *Y'CbCr* matrix.
238 Examples
239 --------
240 >>> matrix_YCbCr() # doctest: +ELLIPSIS
241 array([[ 1.0000000...e+00, ..., 1.5748000...e+00],
242 [ 1.0000000...e+00, -1.8732427...e-01, -4.6812427...e-01],
243 [ 1.0000000...e+00, 1.8556000...e+00, ...]])
244 >>> matrix_YCbCr(K=WEIGHTS_YCBCR["ITU-R BT.601"]) # doctest: +ELLIPSIS
245 array([[ 1.0000000...e+00, ..., 1.4020000...e+00],
246 [ 1.0000000...e+00, -3.4413628...e-01, -7.1413628...e-01],
247 [ 1.0000000...e+00, 1.7720000...e+00, ...]])
248 >>> matrix_YCbCr(is_legal=True) # doctest: +ELLIPSIS
249 array([[ 1.1643835...e+00, ..., 1.7927410...e+00],
250 [ 1.1643835...e+00, -2.1324861...e-01, -5.3290932...e-01],
251 [ 1.1643835...e+00, 2.1124017...e+00, ...]])
253 Matching the default output of the :func:`colour.RGB_to_YCbCr` is done as
254 follows:
256 >>> from colour.algebra import vecmul
257 >>> from colour.utilities import as_int_array
258 >>> RGB = np.array([1.0, 1.0, 1.0])
259 >>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS
260 array([ 0.9215686..., 0.5019607..., 0.5019607...])
261 >>> YCbCr = vecmul(np.linalg.inv(matrix_YCbCr(is_legal=True)), RGB)
262 >>> YCbCr += offset_YCbCr(is_legal=True)
263 >>> YCbCr # doctest: +ELLIPSIS
264 array([ 0.9215686..., 0.5019607..., 0.5019607...])
266 Matching the int output of the :func:`colour.RGB_to_YCbCr` is done as
267 follows:
269 >>> RGB = np.array([102, 0, 51])
270 >>> RGB_to_YCbCr(RGB, in_bits=8, in_int=True, out_bits=8, out_int=True)
271 ... # doctest: +SKIP
272 array([ 38, 140, 171])
273 >>> YCbCr = vecmul(np.linalg.inv(matrix_YCbCr(is_legal=True)), RGB)
274 >>> YCbCr += offset_YCbCr(is_legal=True, is_int=True)
275 >>> as_int_array(np.around(YCbCr))
276 ... # doctest: +SKIP
277 array([ 38, 140, 171])
278 """
280 Kr, Kb = K
281 Y_min, Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)
283 Y = np.array([Kr, (1 - Kr - Kb), Kb])
284 Cb = 0.5 * (np.array([0, 0, 1]) - Y) / (1 - Kb)
285 Cr = 0.5 * (np.array([1, 0, 0]) - Y) / (1 - Kr)
286 Y *= Y_max - Y_min
287 Cb *= C_max - C_min
288 Cr *= C_max - C_min
290 return np.linalg.inv(np.vstack([Y, Cb, Cr]))
293def offset_YCbCr(
294 bits: int = 8, is_legal: bool = False, is_int: bool = False
295) -> NDArrayFloat:
296 """
297 Compute the *R'G'B'* to *Y'CbCr* offsets for the specified bit-depth,
298 range legality and representation.
300 The related *R'G'B'* to *Y'CbCr* matrix can be computed with the
301 :func:`colour.matrix_YCbCr` definition.
303 Parameters
304 ----------
305 bits
306 Bit-depth of the *Y'CbCr* colour encoding ranges array.
307 is_legal
308 Whether the *Y'CbCr* colour encoding ranges array is legal.
309 is_int
310 Whether the *Y'CbCr* colour encoding ranges array represents int
311 code values.
313 Returns
314 -------
315 :class:`numpy.ndarray`
316 *Y'CbCr* offsets.
318 Examples
319 --------
320 >>> offset_YCbCr()
321 array([ 0., 0., 0.])
322 >>> offset_YCbCr(is_legal=True) # doctest: +ELLIPSIS
323 array([ 0.0627451..., 0.5019607..., 0.5019607...])
324 """
326 Y_min, _Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)
328 Y_offset = Y_min
329 C_offset = (C_min + C_max) / 2
331 return np.array([Y_offset, C_offset, C_offset])
334def RGB_to_YCbCr(
335 RGB: Domain1,
336 K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
337 in_bits: int = 10,
338 in_legal: bool = False,
339 in_int: bool = False,
340 out_bits: int = 8,
341 out_legal: bool = True,
342 out_int: bool = False,
343 clamp_int: bool = True,
344 **kwargs: Any,
345) -> Annotated[NDArrayReal, 1]:
346 """
347 Convert an array of *R'G'B'* values to the corresponding *Y'CbCr* colour
348 encoding values array.
350 Parameters
351 ----------
352 RGB
353 Input *R'G'B'* array of floats or int values.
354 K
355 Luma weighting coefficients of red and blue. See
356 :attr:`colour.WEIGHTS_YCBCR` for presets. Default is
357 *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
358 in_bits
359 Bit-depth for int input, or used in the calculation of the
360 denominator for legal range float values, i.e., 8-bit means the
361 float value for legal white is *235 / 255*. Default is *10*.
362 in_legal
363 Whether to treat the input values as legal range. Default is
364 *False*.
365 in_int
366 Whether to treat the input values as ``in_bits`` int code values.
367 Default is *False*.
368 out_bits
369 Bit-depth for int output, or used in the calculation of the
370 denominator for legal range float values, i.e., 8-bit means the
371 float value for legal white is *235 / 255*. Ignored if
372 ``out_legal`` and ``out_int`` are both *False*. Default is *8*.
373 out_legal
374 Whether to return legal range values. Default is *True*.
375 out_int
376 Whether to return values as ``out_bits`` int code values. Default
377 is *False*.
378 clamp_int
379 Whether to clamp int output to allowable range for ``out_bits``.
380 Default is *True*.
382 Other Parameters
383 ----------------
384 in_range
385 Array overriding the computed range such as
386 *in_range = (RGB_min, RGB_max)*. If ``in_range`` is undefined,
387 *RGB_min* and *RGB_max* will be computed using
388 :func:`colour.CV_range` definition.
389 out_range
390 Array overriding the computed range such as
391 *out_range = (Y_min, Y_max, C_min, C_max)`. If ``out_range`` is
392 undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed
393 using :func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
395 Returns
396 -------
397 :class:`numpy.ndarray`
398 *Y'CbCr* colour encoding array of int or float values.
400 Warnings
401 --------
402 For *Recommendation ITU-R BT.2020*,
403 :func:`colour.RGB_to_YCbCr` definition is only applicable to the
404 non-constant luminance implementation.
405 :func:`colour.RGB_to_YcCbcCrc` definition should be used for the
406 constant luminance case as per
407 :cite:`InternationalTelecommunicationUnion2015h`.
409 Notes
410 -----
411 +------------+-----------------------+---------------+
412 | **Domain** | **Scale - Reference** | **Scale - 1** |
413 +============+=======================+===============+
414 | ``RGB`` | 1 | 1 |
415 +------------+-----------------------+---------------+
417 +------------+-----------------------+---------------+
418 | **Range** | **Scale - Reference** | **Scale - 1** |
419 +============+=======================+===============+
420 | ``YCbCr`` | 1 | 1 |
421 +------------+-----------------------+---------------+
423 - This definition has input and output int switches, thus the
424 domain-range scale information is only specified for the floating point
425 mode.
427 - The default arguments, ``**{'in_bits': 10, 'in_legal': False,
428 'in_int': False, 'out_bits': 8, 'out_legal': True, 'out_int':
429 False}`` transform a float *R'G'B'* input array normalised to
430 domain [0, 1] (``in_bits`` is ignored) to a float *Y'CbCr* output
431 array where *Y'* is normalised to range [16 / 255, 235 / 255] and
432 *Cb* and *Cr* are normalised to range [16 / 255, 240./255]. The
433 float values are calculated based on an [0, 255] int range, but no
434 8-bit quantisation or clamping are performed.
436 References
437 ----------
438 :cite:`InternationalTelecommunicationUnion2011e`,
439 :cite:`InternationalTelecommunicationUnion2015i`,
440 :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
441 :cite:`Wikipedia2004d`
443 Examples
444 --------
445 >>> RGB = np.array([1.0, 1.0, 1.0])
446 >>> RGB_to_YCbCr(RGB) # doctest: +ELLIPSIS
447 array([ 0.9215686..., 0.5019607..., 0.5019607...])
449 Matching the float output of *The Foundry Nuke*'s *Colorspace* node
450 set to *YCbCr*:
452 >>> RGB_to_YCbCr(RGB, out_range=(16 / 255, 235 / 255, 15.5 / 255, 239.5 / 255))
453 ... # doctest: +ELLIPSIS
454 array([ 0.9215686..., 0.5 , 0.5 ])
456 Matching the float output of *The Foundry Nuke*'s *Colorspace* node
457 set to *YPbPr*:
459 >>> RGB_to_YCbCr(RGB, out_legal=False, out_int=False)
460 ... # doctest: +ELLIPSIS
461 array([ 1., 0., 0.])
463 Creating int code values as per standard *10-bit SDI*:
465 >>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True)
466 ... # doctest: +ELLIPSIS
467 array([940, 512, 512]...)
469 For *JFIF JPEG* conversion as per *Recommendation ITU-T T.871*
471 >>> RGB = np.array([102, 0, 51])
472 >>> RGB_to_YCbCr(
473 ... RGB,
474 ... K=WEIGHTS_YCBCR["ITU-R BT.601"],
475 ... in_range=(0, 255),
476 ... out_range=(0, 255, 0.5, 255.5),
477 ... out_int=True,
478 ... )
479 ... # doctest: +ELLIPSIS
480 array([ 36, 136, 175]...)
482 Note the use of [0.5, 255.5] for the *Cb / Cr* range, which is
483 required so that the *Cb* and *Cr* output is centered about 128. Using
484 255 centres it about 127.5, meaning that there is no int code value to
485 represent achromatic colours. This does however create the possibility
486 of output int codes with value of 256, which cannot be stored in 8-bit
487 int representation. *Recommendation ITU-T T.871* specifies these should
488 be clamped to 255, which is applied with the default
489 ``clamp_int=True``.
491 These *JFIF JPEG* ranges are also obtained as follows:
493 >>> RGB_to_YCbCr(
494 ... RGB,
495 ... K=WEIGHTS_YCBCR["ITU-R BT.601"],
496 ... in_bits=8,
497 ... in_int=True,
498 ... out_legal=False,
499 ... out_int=True,
500 ... )
501 ... # doctest: +ELLIPSIS
502 array([ 36, 136, 175]...)
503 """
505 RGB = as_float_array(RGB) if in_int else to_domain_1(RGB)
507 Kr, Kb = K
508 RGB_min, RGB_max = kwargs.get("in_range", CV_range(in_bits, in_legal, in_int))
509 Y_min, Y_max, C_min, C_max = kwargs.get(
510 "out_range", ranges_YCbCr(out_bits, out_legal, out_int)
511 )
513 RGB_float = as_float_array(RGB) - RGB_min
514 RGB_float *= 1 / (RGB_max - RGB_min)
515 R, G, B = tsplit(RGB_float)
517 Y = Kr * R + (1 - Kr - Kb) * G + Kb * B
518 Cb = 0.5 * (B - Y) / (1 - Kb)
519 Cr = 0.5 * (R - Y) / (1 - Kr)
520 Y *= Y_max - Y_min
521 Y += Y_min
522 Cb *= C_max - C_min
523 Cr *= C_max - C_min
524 Cb += (C_max + C_min) / 2
525 Cr += (C_max + C_min) / 2
527 YCbCr = tstack([Y, Cb, Cr])
529 if out_int:
530 return as_int_array(
531 round_BT2100(np.clip(YCbCr, 0, 2**out_bits - 1) if clamp_int else YCbCr)
532 )
534 return from_range_1(YCbCr)
537def YCbCr_to_RGB(
538 YCbCr: Domain1,
539 K: NDArrayFloat = WEIGHTS_YCBCR["ITU-R BT.709"],
540 in_bits: int = 8,
541 in_legal: bool = True,
542 in_int: bool = False,
543 out_bits: int = 10,
544 out_legal: bool = False,
545 out_int: bool = False,
546 clamp_int: bool = True,
547 **kwargs: Any,
548) -> Annotated[NDArrayReal, 1]:
549 """
550 Convert an array of *Y'CbCr* colour encoding values to the
551 corresponding *R'G'B'* values array.
553 Parameters
554 ----------
555 YCbCr
556 Input *Y'CbCr* colour encoding array of int or float values.
557 K
558 Luma weighting coefficients of red and blue. See
559 :attr:`colour.WEIGHTS_YCBCR` for presets. Default is
560 *(0.2126, 0.0722)*, the weightings for *ITU-R BT.709*.
561 in_bits
562 Bit-depth for int input, or used in the calculation of the
563 denominator for legal range float values, i.e., 8-bit means
564 the float value for legal white is *235 / 255*. Default is
565 *8*.
566 in_legal
567 Whether to treat the input values as legal range. Default is
568 *True*.
569 in_int
570 Whether to treat the input values as ``in_bits`` int code
571 values. Default is *False*.
572 out_bits
573 Bit-depth for int output, or used in the calculation of the
574 denominator for legal range float values, i.e., 8-bit means
575 the float value for legal white is *235 / 255*. Ignored if
576 ``out_legal`` and ``out_int`` are both *False*. Default is
577 *10*.
578 out_legal
579 Whether to return legal range values. Default is *False*.
580 out_int
581 Whether to return values as ``out_bits`` int code values.
582 Default is *False*.
583 clamp_int
584 Whether to clamp int output to allowable range for
585 ``out_bits``. Default is *True*.
587 Other Parameters
588 ----------------
589 in_range
590 Array overriding the computed range such as
591 *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range``
592 is undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be
593 computed using :func:`colour.models.rgb.ycbcr.ranges_YCbCr`
594 definition.
595 out_range
596 Array overriding the computed range such as
597 *out_range = (RGB_min, RGB_max)*. If ``out_range`` is
598 undefined, *RGB_min* and *RGB_max* will be computed using
599 :func:`colour.CV_range` definition.
601 Returns
602 -------
603 :class:`numpy.ndarray`
604 *R'G'B'* array of int or float values.
606 Notes
607 -----
608 +------------+-----------------------+---------------+
609 | **Domain** | **Scale - Reference** | **Scale - 1** |
610 +============+=======================+===============+
611 | ``YCbCr`` | 1 | 1 |
612 +------------+-----------------------+---------------+
614 +------------+-----------------------+---------------+
615 | **Range** | **Scale - Reference** | **Scale - 1** |
616 +============+=======================+===============+
617 | ``RGB`` | 1 | 1 |
618 +------------+-----------------------+---------------+
620 - This definition has input and output int switches, thus the
621 domain-range scale information is only specified for the floating point
622 mode.
624 Warnings
625 --------
626 For *Recommendation ITU-R BT.2020*,
627 :func:`colour.YCbCr_to_RGB` definition is only applicable to
628 the non-constant luminance implementation.
629 :func:`colour.YcCbcCrc_to_RGB` definition should be used for
630 the constant luminance case as per
631 :cite:`InternationalTelecommunicationUnion2015h`.
633 References
634 ----------
635 :cite:`InternationalTelecommunicationUnion2011e`,
636 :cite:`InternationalTelecommunicationUnion2015i`,
637 :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,
638 :cite:`Wikipedia2004d`
640 Examples
641 --------
642 >>> YCbCr = np.array([502, 512, 512])
643 >>> YCbCr_to_RGB(YCbCr, in_bits=10, in_legal=True, in_int=True)
644 array([ 0.5, 0.5, 0.5])
645 """
647 YCbCr = as_float_array(YCbCr) if in_int else to_domain_1(YCbCr)
649 Y, Cb, Cr = tsplit(YCbCr)
650 Kr, Kb = K
651 Y_min, Y_max, C_min, C_max = kwargs.get(
652 "in_range", ranges_YCbCr(in_bits, in_legal, in_int)
653 )
654 RGB_min, RGB_max = kwargs.get("out_range", CV_range(out_bits, out_legal, out_int))
656 Y -= Y_min
657 Cb -= (C_max + C_min) / 2
658 Cr -= (C_max + C_min) / 2
659 Y *= 1 / (Y_max - Y_min)
660 Cb *= 1 / (C_max - C_min)
661 Cr *= 1 / (C_max - C_min)
662 R = Y + (2 - 2 * Kr) * Cr
663 B = Y + (2 - 2 * Kb) * Cb
664 G = (Y - Kr * R - Kb * B) / (1 - Kr - Kb)
666 RGB = tstack([R, G, B])
667 RGB *= RGB_max - RGB_min
668 RGB += RGB_min
670 return (
671 as_int_array(
672 round_BT2100(np.clip(RGB, 0, 2**out_bits - 1) if clamp_int else RGB)
673 )
674 if out_int
675 else from_range_1(RGB)
676 )
679def RGB_to_YcCbcCrc(
680 RGB: Domain1,
681 out_bits: int = 10,
682 out_legal: bool = True,
683 out_int: bool = False,
684 is_12_bits_system: bool = False,
685 **kwargs: Any,
686) -> Annotated[NDArrayReal, 1]:
687 """
688 Convert an array of *RGB* linear values to the corresponding *Yc'Cbc'Crc'*
689 colour encoding values array.
691 Parameters
692 ----------
693 RGB
694 Input *RGB* array of linear float values.
695 out_bits
696 Bit-depth for int output, or used in the calculation of the
697 denominator for legal range float values, i.e., 8-bit means the float
698 value for legal white is *235 / 255*. Ignored if ``out_legal`` and
699 ``out_int`` are both *False*. Default is *10*.
700 out_legal
701 Whether to return legal range values. Default is *True*.
702 out_int
703 Whether to return values as ``out_bits`` int code values. Default is
704 *False*.
705 is_12_bits_system
706 *Recommendation ITU-R BT.2020* OETF (OECF) adopts different parameters
707 for 10 and 12 bit systems. Default is *False*.
709 Other Parameters
710 ----------------
711 out_range
712 Array overriding the computed range such as
713 *out_range = (Y_min, Y_max, C_min, C_max)*. If ``out_range`` is
714 undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed
715 using :func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
717 Returns
718 -------
719 :class:`numpy.ndarray`
720 *Yc'Cbc'Crc'* colour encoding array of int or float values.
722 Notes
723 -----
724 +----------------+-----------------------+---------------+
725 | **Domain** | **Scale - Reference** | **Scale - 1** |
726 +================+=======================+===============+
727 | ``RGB`` | 1 | 1 |
728 +----------------+-----------------------+---------------+
730 +----------------+-----------------------+---------------+
731 | **Range** | **Scale - Reference** | **Scale - 1** |
732 +================+=======================+===============+
733 | ``YcCbcCrc`` | 1 | 1 |
734 +----------------+-----------------------+---------------+
736 - This definition has input and output int switches, thus the
737 domain-range scale information is only specified for the floating point
738 mode.
740 Warnings
741 --------
742 This definition is specifically for usage with *Recommendation ITU-R
743 BT.2020* when adopting the constant luminance implementation.
745 References
746 ----------
747 :cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d`
749 Examples
750 --------
751 >>> RGB = np.array([0.18, 0.18, 0.18])
752 >>> RGB_to_YcCbcCrc(
753 ... RGB,
754 ... out_legal=True,
755 ... out_bits=10,
756 ... out_int=True,
757 ... is_12_bits_system=False,
758 ... )
759 ... # doctest: +ELLIPSIS
760 array([422, 512, 512]...)
761 """
763 R, G, B = tsplit(to_domain_1(RGB))
764 Y_min, Y_max, C_min, C_max = kwargs.get(
765 "out_range", ranges_YCbCr(out_bits, out_legal, out_int)
766 )
768 Yc = 0.2627 * R + 0.6780 * G + 0.0593 * B
770 with domain_range_scale("ignore"):
771 Yc = oetf_BT2020(Yc, is_12_bits_system=is_12_bits_system)
772 R = oetf_BT2020(R, is_12_bits_system=is_12_bits_system)
773 B = oetf_BT2020(B, is_12_bits_system=is_12_bits_system)
775 Cbc = np.where((B - Yc) <= 0, (B - Yc) / 1.9404, (B - Yc) / 1.5816)
776 Crc = np.where((R - Yc) <= 0, (R - Yc) / 1.7184, (R - Yc) / 0.9936)
777 Yc *= Y_max - Y_min
778 Yc += Y_min
779 Cbc *= C_max - C_min
780 Crc *= C_max - C_min
781 Cbc += (C_max + C_min) / 2
782 Crc += (C_max + C_min) / 2
784 YcCbcCrc = tstack([Yc, Cbc, Crc])
786 if out_int:
787 return as_int_array(np.round(YcCbcCrc))
789 return from_range_1(YcCbcCrc)
792def YcCbcCrc_to_RGB(
793 YcCbcCrc: Domain1,
794 in_bits: int = 10,
795 in_legal: bool = True,
796 in_int: bool = False,
797 is_12_bits_system: bool = False,
798 **kwargs: Any,
799) -> Range1:
800 """
801 Convert an array of *Yc'Cbc'Crc'* colour encoding values to the
802 corresponding *RGB* array of linear values.
804 Parameters
805 ----------
806 YcCbcCrc
807 Input *Yc'Cbc'Crc'* colour encoding array of linear float values.
808 in_bits
809 Bit-depth for int input, or used in the calculation of the
810 denominator for legal range float values, i.e., 8-bit means the
811 float value for legal white is *235 / 255*. Default is *10*.
812 in_legal
813 Whether to treat the input values as legal range. Default is
814 *True*.
815 in_int
816 Whether to treat the input values as ``in_bits`` int code values.
817 Default is *False*.
818 is_12_bits_system
819 *Recommendation ITU-R BT.2020* EOTF (EOCF) adopts different
820 parameters for 10 and 12 bit systems. Default is *False*.
822 Other Parameters
823 ----------------
824 in_range
825 Array overriding the computed range such as
826 *in_range = (Y_min, Y_max, C_min, C_max)*. If ``in_range`` is
827 undefined, *Y_min*, *Y_max*, *C_min* and *C_max* will be computed
828 using :func:`colour.models.rgb.ycbcr.ranges_YCbCr` definition.
830 Returns
831 -------
832 :class:`numpy.ndarray`
833 *RGB* array of linear float values.
835 Notes
836 -----
837 +----------------+-----------------------+---------------+
838 | **Domain** | **Scale - Reference** | **Scale - 1** |
839 +================+=======================+===============+
840 | ``YcCbcCrc`` | 1 | 1 |
841 +----------------+-----------------------+---------------+
843 +----------------+-----------------------+---------------+
844 | **Range** | **Scale - Reference** | **Scale - 1** |
845 +================+=======================+===============+
846 | ``RGB`` | 1 | 1 |
847 +----------------+-----------------------+---------------+
849 - This definition has input and output int switches, thus the
850 domain-range scale information is only specified for the floating point
851 mode.
853 Warnings
854 --------
855 This definition is specifically for usage with
856 *Recommendation ITU-R BT.2020* when adopting the constant luminance
857 implementation.
859 References
860 ----------
861 :cite:`InternationalTelecommunicationUnion2015h`,
862 :cite:`Wikipedia2004d`
864 Examples
865 --------
866 >>> YcCbcCrc = np.array([1689, 2048, 2048])
867 >>> YcCbcCrc_to_RGB(
868 ... YcCbcCrc,
869 ... in_legal=True,
870 ... in_bits=12,
871 ... in_int=True,
872 ... is_12_bits_system=True,
873 ... )
874 ... # doctest: +ELLIPSIS
875 array([ 0.1800903..., 0.1800903..., 0.1800903...])
876 """
878 YcCbcCrc = as_float_array(YcCbcCrc) if in_int else to_domain_1(YcCbcCrc)
880 Yc, Cbc, Crc = tsplit(YcCbcCrc)
881 Y_min, Y_max, C_min, C_max = kwargs.get(
882 "in_range", ranges_YCbCr(in_bits, in_legal, in_int)
883 )
885 Yc -= Y_min
886 Cbc -= (C_max + C_min) / 2
887 Crc -= (C_max + C_min) / 2
888 Yc *= 1 / (Y_max - Y_min)
889 Cbc *= 1 / (C_max - C_min)
890 Crc *= 1 / (C_max - C_min)
891 B = np.where(Cbc <= 0, Cbc * 1.9404 + Yc, Cbc * 1.5816 + Yc)
892 R = np.where(Crc <= 0, Crc * 1.7184 + Yc, Crc * 0.9936 + Yc)
894 with domain_range_scale("ignore"):
895 Yc = oetf_inverse_BT2020(Yc, is_12_bits_system)
896 B = oetf_inverse_BT2020(B, is_12_bits_system)
897 R = oetf_inverse_BT2020(R, is_12_bits_system)
899 G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780
901 RGB = tstack([R, G, B])
903 return from_range_1(RGB)