Coverage for models/rgb/ycbcr.py: 82%

1"""

2 Y'CbCr Colour Encoding

3======================

4

5 Define the *Y'CbCr* colour encoding related attributes and objects.

6

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`

14

15 Notes

16-----

17- *Y'CbCr* is not an absolute colourspace.

18

19 References

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&\

26 id=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/\

32 R-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/\

38 R-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/\

44 R-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/\

49 INA-%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"""

53

54 from __future__ import annotations

55

56 import typing

57

58 import numpy as np

59

60 if typing.TYPE_CHECKING:

61 from colour.hints import Any, ArrayLike, Domain1, NDArrayReal, Range1

62

63 from colour.hints import Annotated, NDArrayFloat, cast

64 from colour.models.rgb.transfer_functions import (

65 CV_range,

66 oetf_BT2020,

67 oetf_inverse_BT2020,

68)

69 from 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)

79

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"

86

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]

98

99 WEIGHTS_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"""

108 Luma weightings presets.

109

110 References

111----------

112:cite:`InternationalTelecommunicationUnion2011e`,

113:cite:`InternationalTelecommunicationUnion2015i`,

114:cite:`InternationalTelecommunicationUnion2015h`,

115:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,

116:cite:`Wikipedia2004d`

117"""

118

119

120 def 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*.

124

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.

128

129 Parameters

130 ----------

131 a

132 Array :math:`a` to round.

133

134 Returns

135 -------

136 :class:`numpy.ndarray`

137 Rounded array :math:`a`.

138

139 References

140 ----------

141 :cite:`InternationalTelecommunicationUnion2018`

142

143 Examples

144 --------

145 >>> round_BT2100(np.array([0.4, 0.5, 0.6]))

146 array([ 0., 1., 1.])

147 """

148

149 return cast("NDArrayFloat", np.sign(a) * np.floor(np.abs(a) + 0.5))

150

151

152 def 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.

156

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.

166

167 Returns

168 -------

169 :class:`numpy.ndarray`

170 *Y'CbCr* colour encoding ranges array.

171

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 """

184

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])

190

191 if not is_int:

192 ranges = as_int_array(ranges) / (2**bits - 1)

193

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

198

199 if not is_int and not is_legal:

200 ranges[2] = -0.5

201 ranges[3] = 0.5

202

203 return ranges

204

205

206 def 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.

215

216 The related offset for the *R'G'B'* to *Y'CbCr* matrix can be computed

217 with the :func:`colour.offset_YCbCr` definition.

218

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.

232

233 Returns

234 -------

235 :class:`numpy.ndarray`

236 *Y'CbCr* matrix.

237

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, ...]])

252

253 Matching the default output of the :func:`colour.RGB_to_YCbCr` is done as

254 follows:

255

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...])

265

266 Matching the int output of the :func:`colour.RGB_to_YCbCr` is done as

267 follows:

268

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 """

279

280 Kr, Kb = K

281 Y_min, Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)

282

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

289

290 return np.linalg.inv(np.vstack([Y, Cb, Cr]))

291

292

293 def 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.

299

300 The related *R'G'B'* to *Y'CbCr* matrix can be computed with the

301 :func:`colour.matrix_YCbCr` definition.

302

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.

312

313 Returns

314 -------

315 :class:`numpy.ndarray`

316 *Y'CbCr* offsets.

317

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 """

325

326 Y_min, _Y_max, C_min, C_max = ranges_YCbCr(bits, is_legal, is_int)

327

328 Y_offset = Y_min

329 C_offset = (C_min + C_max) / 2

330

331 return np.array([Y_offset, C_offset, C_offset])

332

333

334 def 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.

349

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*.

381

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.

394

395 Returns

396 -------

397 :class:`numpy.ndarray`

398 *Y'CbCr* colour encoding array of int or float values.

399

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`.

408

409 Notes

410 -----

411 +------------+-----------------------+---------------+

412 | **Domain** | **Scale - Reference** | **Scale - 1** |

413 +============+=======================+===============+

414 | ``RGB`` | 1 | 1 |

415 +------------+-----------------------+---------------+

416

417 +------------+-----------------------+---------------+

418 | **Range** | **Scale - Reference** | **Scale - 1** |

419 +============+=======================+===============+

420 | ``YCbCr`` | 1 | 1 |

421 +------------+-----------------------+---------------+

422

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.

426

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.

435

436 References

437 ----------

438 :cite:`InternationalTelecommunicationUnion2011e`,

439 :cite:`InternationalTelecommunicationUnion2015i`,

440 :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,

441 :cite:`Wikipedia2004d`

442

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...])

448

449 Matching the float output of *The Foundry Nuke*'s *Colorspace* node

450 set to *YCbCr*:

451

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 ])

455

456 Matching the float output of *The Foundry Nuke*'s *Colorspace* node

457 set to *YPbPr*:

458

459 >>> RGB_to_YCbCr(RGB, out_legal=False, out_int=False)

460 ... # doctest: +ELLIPSIS

461 array([ 1., 0., 0.])

462

463 Creating int code values as per standard *10-bit SDI*:

464

465 >>> RGB_to_YCbCr(RGB, out_legal=True, out_bits=10, out_int=True)

466 ... # doctest: +ELLIPSIS

467 array([940, 512, 512]...)

468

469 For *JFIF JPEG* conversion as per *Recommendation ITU-T T.871*

470

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]...)

481

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``.

490

491 These *JFIF JPEG* ranges are also obtained as follows:

492

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 """

504

505 RGB = as_float_array(RGB) if in_int else to_domain_1(RGB)

506

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 )

512

513 RGB_float = as_float_array(RGB) - RGB_min

514 RGB_float *= 1 / (RGB_max - RGB_min)

515 R, G, B = tsplit(RGB_float)

516

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

526

527 YCbCr = tstack([Y, Cb, Cr])

528

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 )

533

534 return from_range_1(YCbCr)

535

536

537 def 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.

552

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*.

586

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.

600

601 Returns

602 -------

603 :class:`numpy.ndarray`

604 *R'G'B'* array of int or float values.

605

606 Notes

607 -----

608 +------------+-----------------------+---------------+

609 | **Domain** | **Scale - Reference** | **Scale - 1** |

610 +============+=======================+===============+

611 | ``YCbCr`` | 1 | 1 |

612 +------------+-----------------------+---------------+

613

614 +------------+-----------------------+---------------+

615 | **Range** | **Scale - Reference** | **Scale - 1** |

616 +============+=======================+===============+

617 | ``RGB`` | 1 | 1 |

618 +------------+-----------------------+---------------+

619

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.

623

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`.

632

633 References

634 ----------

635 :cite:`InternationalTelecommunicationUnion2011e`,

636 :cite:`InternationalTelecommunicationUnion2015i`,

637 :cite:`SocietyofMotionPictureandTelevisionEngineers1999b`,

638 :cite:`Wikipedia2004d`

639

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 """

646

647 YCbCr = as_float_array(YCbCr) if in_int else to_domain_1(YCbCr)

648

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))

655

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)

665

666 RGB = tstack([R, G, B])

667 RGB *= RGB_max - RGB_min

668 RGB += RGB_min

669

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 )

677

678

679 def 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.

690

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*.

708

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.

716

717 Returns

718 -------

719 :class:`numpy.ndarray`

720 *Yc'Cbc'Crc'* colour encoding array of int or float values.

721

722 Notes

723 -----

724 +----------------+-----------------------+---------------+

725 | **Domain** | **Scale - Reference** | **Scale - 1** |

726 +================+=======================+===============+

727 | ``RGB`` | 1 | 1 |

728 +----------------+-----------------------+---------------+

729

730 +----------------+-----------------------+---------------+

731 | **Range** | **Scale - Reference** | **Scale - 1** |

732 +================+=======================+===============+

733 | ``YcCbcCrc`` | 1 | 1 |

734 +----------------+-----------------------+---------------+

735

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.

739

740 Warnings

741 --------

742 This definition is specifically for usage with *Recommendation ITU-R

743 BT.2020* when adopting the constant luminance implementation.

744

745 References

746 ----------

747 :cite:`InternationalTelecommunicationUnion2015h`, :cite:`Wikipedia2004d`

748

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 """

762

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 )

767

768 Yc = 0.2627 * R + 0.6780 * G + 0.0593 * B

769

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)

774

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

783

784 YcCbcCrc = tstack([Yc, Cbc, Crc])

785

786 if out_int:

787 return as_int_array(np.round(YcCbcCrc))

788

789 return from_range_1(YcCbcCrc)

790

791

792 def 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.

803

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*.

821

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.

829

830 Returns

831 -------

832 :class:`numpy.ndarray`

833 *RGB* array of linear float values.

834

835 Notes

836 -----

837 +----------------+-----------------------+---------------+

838 | **Domain** | **Scale - Reference** | **Scale - 1** |

839 +================+=======================+===============+

840 | ``YcCbcCrc`` | 1 | 1 |

841 +----------------+-----------------------+---------------+

842

843 +----------------+-----------------------+---------------+

844 | **Range** | **Scale - Reference** | **Scale - 1** |

845 +================+=======================+===============+

846 | ``RGB`` | 1 | 1 |

847 +----------------+-----------------------+---------------+

848

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.

852

853 Warnings

854 --------

855 This definition is specifically for usage with

856 *Recommendation ITU-R BT.2020* when adopting the constant luminance

857 implementation.

858

859 References

860 ----------

861 :cite:`InternationalTelecommunicationUnion2015h`,

862 :cite:`Wikipedia2004d`

863

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 """

877

878 YcCbcCrc = as_float_array(YcCbcCrc) if in_int else to_domain_1(YcCbcCrc)

879

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 )

884

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)

893

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)

898

899 G = (Yc - 0.0593 * B - 0.2627 * R) / 0.6780

900

901 RGB = tstack([R, G, B])

902

903 return from_range_1(RGB)