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

1""" 

2Y'CbCr Colour Encoding 

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

4 

5Define 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 

15Notes 

16----- 

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

18 

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

53 

54from __future__ import annotations 

55 

56import typing 

57 

58import numpy as np 

59 

60if typing.TYPE_CHECKING: 

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

62 

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) 

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 

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. 

109 

110References 

111---------- 

112:cite:`InternationalTelecommunicationUnion2011e`, 

113:cite:`InternationalTelecommunicationUnion2015i`, 

114:cite:`InternationalTelecommunicationUnion2015h`, 

115:cite:`SocietyofMotionPictureandTelevisionEngineers1999b`, 

116:cite:`Wikipedia2004d` 

117""" 

118 

119 

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

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 

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. 

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 

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. 

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 

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. 

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 

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. 

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 

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. 

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 

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. 

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 

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. 

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)