71b7950b113227cf9094b166f8f99c03fd73b190
[OpenColorIO-Configs.git] / aces_1.0.1 / python / aces_ocio / colorspaces / general.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 Implements support for general colorspaces conversions and transfer functions.
6 """
7
8 from __future__ import division
9
10 import array
11 import os
12
13 import PyOpenColorIO as ocio
14
15 import aces_ocio.generate_lut as genlut
16 from aces_ocio.colorspaces import aces
17 from aces_ocio.utilities import ColorSpace, mat44_from_mat33
18
19 __author__ = 'ACES Developers'
20 __copyright__ = 'Copyright (C) 2014 - 2015 - ACES Developers'
21 __license__ = ''
22 __maintainer__ = 'ACES Developers'
23 __email__ = 'aces@oscars.org'
24 __status__ = 'Production'
25
26 __all__ = ['create_matrix_colorspace',
27            'create_transfer_colorspace',
28            'create_matrix_plus_transfer_colorspace',
29            'transfer_function_sRGB_to_linear',
30            'transfer_function_Rec709_to_linear',
31            'transfer_function_Rec2020_10bit_to_linear',
32            'transfer_function_Rec2020_12bit_to_linear',
33            'transfer_function_Rec1886_to_linear',
34            'create_colorspaces',
35            'create_raw']
36
37
38 # -------------------------------------------------------------------------
39 # *Matrix Transform*
40 # -------------------------------------------------------------------------
41 def create_matrix_colorspace(name='matrix',
42                              from_reference_values=None,
43                              to_reference_values=None,
44                              aliases=None):
45     """
46     Creates a ColorSpace that only uses *Matrix Transforms*
47
48     Parameters
49     ----------
50     name : str, optional
51         Aliases for this colorspace
52     from_reference_values : list of matrices
53         List of matrices to convert from the reference colorspace to this space        
54     to_reference_values : list of matrices
55         List of matrices to convert to the reference colorspace from this space
56     aliases : list of str, optional
57         Aliases for this colorspace
58
59     Returns
60     -------
61     ColorSpace
62          A *Matrix Transform*-based ColorSpace
63     """
64
65     if from_reference_values is None:
66         from_reference_values = []
67
68     if to_reference_values is None:
69         to_reference_values = []
70
71     if aliases is None:
72         aliases = []
73
74     cs = ColorSpace(name)
75     cs.description = 'The %s color space' % name
76     cs.aliases = aliases
77     cs.equality_group = name
78     cs.family = 'Utility'
79     cs.is_data = False
80
81     # A linear space needs allocation variables.
82     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
83     cs.allocation_vars = [0, 1]
84
85     cs.to_reference_transforms = []
86     if to_reference_values:
87         for matrix in to_reference_values:
88             cs.to_reference_transforms.append({
89                 'type': 'matrix',
90                 'matrix': mat44_from_mat33(matrix),
91                 'direction': 'forward'})
92
93     cs.from_reference_transforms = []
94     if from_reference_values:
95         for matrix in from_reference_values:
96             cs.from_reference_transforms.append({
97                 'type': 'matrix',
98                 'matrix': mat44_from_mat33(matrix),
99                 'direction': 'forward'})
100
101     return cs
102
103
104 # -------------------------------------------------------------------------
105 # *Transfer Function Transform*
106 # -------------------------------------------------------------------------
107 def create_transfer_colorspace(name='transfer',
108                                transfer_function_name='transfer_function',
109                                transfer_function=lambda x: x,
110                                lut_directory='/tmp',
111                                lut_resolution_1d=1024,
112                                aliases=None):
113     """
114     Creates a ColorSpace that only uses transfer functions encoded as 1D LUTs
115
116     Parameters
117     ----------
118     name : str, optional
119         Aliases for this colorspace
120     transfer_function_name : str, optional
121         The name of the transfer function
122     transfer_function : function, optional
123         The transfer function to be evaluated
124     lut_directory : str or unicode 
125         The directory to use when generating LUTs
126     lut_resolution_1d : int
127         The resolution of generated 1D LUTs
128     aliases : list of str
129         Aliases for this colorspace
130
131     Returns
132     -------
133     ColorSpace
134          A *LUT1D Transform*-based ColorSpace representing a transfer function
135     """
136
137     if aliases is None:
138         aliases = []
139
140     cs = ColorSpace(name)
141     cs.description = 'The %s color space' % name
142     cs.aliases = aliases
143     cs.equality_group = name
144     cs.family = 'Utility'
145     cs.is_data = False
146
147     # A linear space needs allocation variables.
148     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
149     cs.allocation_vars = [0, 1]
150
151     # Sampling the transfer function.
152     data = array.array('f', '\0' * lut_resolution_1d * 4)
153     for c in range(lut_resolution_1d):
154         data[c] = transfer_function(c / (lut_resolution_1d - 1))
155
156     # Writing the sampled data to a *LUT*.
157     lut = '%s_to_linear.spi1d' % transfer_function_name
158     genlut.write_SPI_1d(
159         os.path.join(lut_directory, lut),
160         0,
161         1,
162         data,
163         lut_resolution_1d,
164         1)
165
166     # Creating the *to_reference* transforms.
167     cs.to_reference_transforms = []
168     cs.to_reference_transforms.append({
169         'type': 'lutFile',
170         'path': lut,
171         'interpolation': 'linear',
172         'direction': 'forward'})
173
174     # Creating the *from_reference* transforms.
175     cs.from_reference_transforms = []
176
177     return cs
178
179
180 # -------------------------------------------------------------------------
181 # *Transfer Function + Matrix Transform*
182 # -------------------------------------------------------------------------
183 def create_matrix_plus_transfer_colorspace(
184         name='matrix_plus_transfer',
185         transfer_function_name='transfer_function',
186         transfer_function=lambda x: x,
187         lut_directory='/tmp',
188         lut_resolution_1d=1024,
189         from_reference_values=None,
190         to_reference_values=None,
191         aliases=None):
192     """
193     Creates a ColorSpace that uses transfer functions encoded as 1D LUTs and
194     matrice
195
196     Parameters
197     ----------
198     name : str, optional
199         Aliases for this colorspace
200     transfer_function_name : str, optional
201         The name of the transfer function
202     transfer_function : function, optional
203         The transfer function to be evaluated
204     lut_directory : str or unicode 
205         The directory to use when generating LUTs
206     lut_resolution_1d : int
207         The resolution of generated 1D LUTs
208     from_reference_values : list of matrices
209         List of matrices to convert from the reference colorspace to this space        
210     to_reference_values : list of matrices
211         List of matrices to convert to the reference colorspace from this space
212     aliases : list of str
213         Aliases for this colorspace
214
215     Returns
216     -------
217     ColorSpace
218          A *Matrx and LUT1D Transform*-based ColorSpace representing a transfer 
219          function and matrix
220     """
221
222     if from_reference_values is None:
223         from_reference_values = []
224
225     if to_reference_values is None:
226         to_reference_values = []
227
228     if aliases is None:
229         aliases = []
230
231     cs = ColorSpace(name)
232     cs.description = 'The %s color space' % name
233     cs.aliases = aliases
234     cs.equality_group = name
235     cs.family = 'Utility'
236     cs.is_data = False
237
238     # A linear space needs allocation variables.
239     cs.allocation_type = ocio.Constants.ALLOCATION_UNIFORM
240     cs.allocation_vars = [0, 1]
241
242     # Sampling the transfer function.
243     data = array.array('f', '\0' * lut_resolution_1d * 4)
244     for c in range(lut_resolution_1d):
245         data[c] = transfer_function(c / (lut_resolution_1d - 1))
246
247     # Writing the sampled data to a *LUT*.
248     lut = '%s_to_linear.spi1d' % transfer_function_name
249     genlut.write_SPI_1d(
250         os.path.join(lut_directory, lut),
251         0,
252         1,
253         data,
254         lut_resolution_1d,
255         1)
256
257     # Creating the *to_reference* transforms.
258     cs.to_reference_transforms = []
259     if to_reference_values:
260         cs.to_reference_transforms.append({
261             'type': 'lutFile',
262             'path': lut,
263             'interpolation': 'linear',
264             'direction': 'forward'})
265
266         for matrix in to_reference_values:
267             cs.to_reference_transforms.append({
268                 'type': 'matrix',
269                 'matrix': mat44_from_mat33(matrix),
270                 'direction': 'forward'})
271
272     # Creating the *from_reference* transforms.
273     cs.from_reference_transforms = []
274     if from_reference_values:
275         for matrix in from_reference_values:
276             cs.from_reference_transforms.append({
277                 'type': 'matrix',
278                 'matrix': mat44_from_mat33(matrix),
279                 'direction': 'forward'})
280
281         cs.from_reference_transforms.append({
282             'type': 'lutFile',
283             'path': lut,
284             'interpolation': 'linear',
285             'direction': 'inverse'})
286
287     return cs
288
289
290 # Transfer functions for standard colorspaces.
291 def transfer_function_sRGB_to_linear(v):
292     """
293     The sRGB (IEC 61966-2-1) transfer function
294
295     Parameters
296     ----------
297     v : float
298         The normalized value to pass through the function
299
300     Returns
301     -------
302     float
303         A converted value
304     """
305     a = 1.055
306     b = 0.04045
307     d = 12.92
308     g = 2.4
309
310     if v < b:
311         return v / d
312     return pow(((v + (a - 1)) / a), g)
313
314
315 def transfer_function_Rec709_to_linear(v):
316     """
317     The Rec.709 transfer function
318
319     Parameters
320     ----------
321     v : float
322         The normalized value to pass through the function
323
324     Returns
325     -------
326     float
327         A converted value
328     """
329     a = 1.099
330     b = 0.018
331     d = 4.5
332     g = (1.0 / 0.45)
333
334     if v < b * d:
335         return v / d
336
337     return pow(((v + (a - 1)) / a), g)
338
339
340 def transfer_function_Rec2020_10bit_to_linear(v):
341     """
342     The Rec.2020 10-bit transfer function
343
344     Parameters
345     ----------
346     v : float
347         The normalized value to pass through the function
348
349     Returns
350     -------
351     float
352         A converted value
353     """
354     a = 1.099
355     b = 0.018
356     d = 4.5
357     g = (1.0 / 0.45)
358
359     if v < b * d:
360         return v / d
361
362     return pow(((v + (a - 1)) / a), g)
363
364
365 def transfer_function_Rec2020_12bit_to_linear(v):
366     """
367     The Rec.2020 12-bit transfer function
368
369     Parameters
370     ----------
371     v : float
372         The normalized value to pass through the function
373
374     Returns
375     -------
376     float
377         A converted value
378     """
379     a = 1.0993
380     b = 0.0181
381     d = 4.5
382     g = (1.0 / 0.45)
383
384     if v < b * d:
385         return v / d
386
387     return pow(((v + (a - 1)) / a), g)
388
389
390 def transfer_function_Rec1886_to_linear(v):
391     """
392     The Rec.1886 transfer function
393
394     Parameters
395     ----------
396     v : float
397         The normalized value to pass through the function
398
399     Returns
400     -------
401     float
402         A converted value
403     """
404     g = 2.4
405     Lw = 1
406     Lb = 0
407
408     # Ignoring legal to full scaling for now.
409     # v = (1023.0*v - 64.0)/876.0
410
411     t = pow(Lw, 1.0 / g) - pow(Lb, 1.0 / g)
412     a = pow(t, g)
413     b = pow(Lb, 1.0 / g) / t
414
415     return a * pow(max((v + b), 0.0), g)
416
417
418 def create_colorspaces(lut_directory,
419                        lut_resolution_1d):
420     """
421     Generates the colorspace conversions.
422
423     Parameters
424     ----------
425     lut_directory : str or unicode 
426         The directory to use when generating LUTs
427     lut_resolution_1d : int
428         The resolution of generated 1D LUTs
429
430     Returns
431     -------
432     list
433          A list of colorspaces for general colorspaces and encodings 
434     """
435
436     colorspaces = []
437
438     # -------------------------------------------------------------------------
439     # XYZ
440     # -------------------------------------------------------------------------
441     cs = create_matrix_colorspace('XYZ - D60',
442                                   to_reference_values=[aces.ACES_XYZ_TO_AP0],
443                                   from_reference_values=[aces.ACES_AP0_TO_XYZ],
444                                   aliases=['lin_xyz_d60'])
445     colorspaces.append(cs)
446
447     # -------------------------------------------------------------------------
448     # P3-D60
449     # -------------------------------------------------------------------------
450     # *ACES* to *Linear*, *P3D60* primaries
451     XYZ_to_P3D60 = [2.4027414142, -0.8974841639, -0.3880533700,
452                     -0.8325796487, 1.7692317536, 0.0237127115,
453                     0.0388233815, -0.0824996856, 1.0363685997]
454
455     cs = create_matrix_colorspace(
456         'Linear - P3-D60',
457         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_P3D60],
458         aliases=['lin_p3d60'])
459     colorspaces.append(cs)
460
461     # -------------------------------------------------------------------------
462     # P3-DCI
463     # -------------------------------------------------------------------------
464     # *ACES* to *Linear*, *P3DCI* primaries, using Bradford chromatic 
465     # adaptation
466     XYZ_to_P3DCI = [2.66286135, -1.11031783, -0.42271635,
467                     -0.82282376, 1.75861704, 0.02502194,
468                     0.03932561, -0.08383448, 1.0372175]
469
470     cs = create_matrix_colorspace(
471         'Linear - P3-DCI',
472         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_P3DCI],
473         aliases=['lin_p3dci'])
474     colorspaces.append(cs)
475
476     # -------------------------------------------------------------------------
477     # sRGB
478     # -------------------------------------------------------------------------
479     # *sRGB* and *Rec 709* use the same gamut.
480
481     # *ACES* to *Linear*, *Rec. 709* primaries, D65 white point, using 
482     # Bradford chromatic adaptation
483     XYZ_to_Rec709 = [3.20959735, -1.55742955, -0.49580497,
484                      -0.97098887, 1.88517118, 0.03948941,
485                      0.05971934, -0.21010444, 1.14312482]
486
487     cs = create_matrix_colorspace(
488         'Linear - sRGB',
489         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
490         aliases=['lin_srgb'])
491     colorspaces.append(cs)
492
493     # *Linear* to *sRGB* Transfer Function*
494     cs = create_transfer_colorspace(
495         'Curve - sRGB',
496         'sRGB',
497         transfer_function_sRGB_to_linear,
498         lut_directory,
499         lut_resolution_1d,
500         aliases=['crv_srgb'])
501     colorspaces.append(cs)
502
503     # *ACES* to *sRGB* Primaries + Transfer Function*
504     cs = create_matrix_plus_transfer_colorspace(
505         'sRGB - Texture',
506         'sRGB',
507         transfer_function_sRGB_to_linear,
508         lut_directory,
509         lut_resolution_1d,
510         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
511         aliases=['srgb_texture'])
512     colorspaces.append(cs)
513
514     # -------------------------------------------------------------------------
515     # Rec 709
516     # -------------------------------------------------------------------------
517     # *sRGB* and *Rec 709* use the same gamut.
518     cs = create_matrix_colorspace(
519         'Linear - Rec.709',
520         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
521         aliases=['lin_rec709'])
522     colorspaces.append(cs)
523
524     # *Linear* to *Rec. 709* Transfer Function*
525     cs = create_transfer_colorspace(
526         'Curve - Rec.709',
527         'rec709',
528         transfer_function_Rec709_to_linear,
529         lut_directory,
530         lut_resolution_1d,
531         aliases=['crv_rec709'])
532     colorspaces.append(cs)
533
534     # *ACES* to *Rec. 709* Primaries + Transfer Function*
535     cs = create_matrix_plus_transfer_colorspace(
536         'Rec.709 - Camera',
537         'rec709',
538         transfer_function_Rec709_to_linear,
539         lut_directory,
540         lut_resolution_1d,
541         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
542         aliases=['rec709_camera'])
543     colorspaces.append(cs)
544
545     # -------------------------------------------------------------------------
546     # Rec 2020
547     # -------------------------------------------------------------------------
548     # *ACES* to *Linear*, *Rec. 2020* primaries, D65 white point, using 
549     # Bradford chromatic adaptation
550     XYZ_to_Rec2020 = [1.69662619, -0.36551982, -0.24857099,
551                       -0.67039877, 1.62348187, 0.01503821,
552                       0.02063163, -0.04775634, 1.01910818]
553
554     cs = create_matrix_colorspace(
555         'Linear - Rec.2020',
556         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
557         aliases=['lin_rec2020'])
558     colorspaces.append(cs)
559
560     # *Linear* to *Rec. 2020 10 bit* Transfer Function*
561     cs = create_transfer_colorspace(
562         'Curve - Rec.2020',
563         'rec2020',
564         transfer_function_Rec2020_10bit_to_linear,
565         lut_directory,
566         lut_resolution_1d,
567         aliases=['crv_rec2020'])
568     colorspaces.append(cs)
569
570     # *ACES* to *Rec. 2020 10 bit* Primaries + Transfer Function*
571     cs = create_matrix_plus_transfer_colorspace(
572         'Rec.2020 - Camera',
573         'rec2020',
574         transfer_function_Rec2020_10bit_to_linear,
575         lut_directory,
576         lut_resolution_1d,
577         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
578         aliases=['rec2020_camera'])
579     colorspaces.append(cs)
580
581     # -------------------------------------------------------------------------
582     # Rec 1886
583     # -------------------------------------------------------------------------
584     # *Linear* to *Rec.1886* Transfer Function*
585     cs = create_transfer_colorspace(
586         'Curve - Rec.1886',
587         'rec1886',
588         transfer_function_Rec1886_to_linear,
589         lut_directory,
590         lut_resolution_1d,
591         aliases=['crv_rec1886'])
592     colorspaces.append(cs)
593
594     # *ACES* to *Rec. 709* Primaries + Transfer Function*
595     cs = create_matrix_plus_transfer_colorspace(
596         'Rec.709 - Display',
597         'rec1886',
598         transfer_function_Rec1886_to_linear,
599         lut_directory,
600         lut_resolution_1d,
601         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec709],
602         aliases=['rec709_display'])
603     colorspaces.append(cs)
604
605     # *ACES* to *Rec. 2020* Primaries + Transfer Function*
606     cs = create_matrix_plus_transfer_colorspace(
607         'Rec.2020 - Display',
608         'rec1886',
609         transfer_function_Rec1886_to_linear,
610         lut_directory,
611         lut_resolution_1d,
612         from_reference_values=[aces.ACES_AP0_TO_XYZ, XYZ_to_Rec2020],
613         aliases=['rec2020_display'])
614     colorspaces.append(cs)
615
616     # -------------------------------------------------------------------------
617     # ProPhoto
618     # -------------------------------------------------------------------------
619     # *ACES* to *Linear*, *Pro Photo* primaries, D50 white point, using 
620     # Bradford chromatic adaptation
621     AP0_to_RIMM = [1.2412367771, -0.1685692287, -0.0726675484,
622                    0.0061203066, 1.083151174, -0.0892714806,
623                    -0.0032853314, 0.0099796402, 0.9933056912]
624
625     cs = create_matrix_colorspace(
626         'Linear - RIMM ROMM (ProPhoto)',
627         from_reference_values=[AP0_to_RIMM],
628         aliases=['lin_prophoto', 'lin_rimm'])
629     colorspaces.append(cs)
630
631     # -------------------------------------------------------------------------
632     # Adobe RGB
633     # -------------------------------------------------------------------------
634     # *ACES* to *Linear*, *Adobe RGB* primaries, D65 white point, using 
635     # Bradford chromatic adaptation
636     AP0_to_ADOBERGB = [1.7245603168, -0.4199935942, -0.3045667227,
637                        -0.2764799142, 1.3727190877, -0.0962391734,
638                        -0.0261255258, -0.0901747807, 1.1163003065]
639
640     cs = create_matrix_colorspace(
641         'Linear - Adobe RGB',
642         from_reference_values=[AP0_to_ADOBERGB],
643         aliases=['lin_adobergb'])
644     colorspaces.append(cs)
645
646     # -------------------------------------------------------------------------
647     # Adobe Wide Gamut RGB
648     # -------------------------------------------------------------------------
649     # *ACES* to *Linear*, *Adobe Wide Gamut RGB* primaries, D50 white point, 
650     # using Bradford chromatic adaptation
651     AP0_to_ADOBEWIDEGAMUT = [1.3809814778, -0.1158594573, -0.2651220205,
652                              0.0057015535, 1.0402949043, -0.0459964578,
653                             -0.0038908746, -0.0597091815, 1.0636000561]
654
655     cs = create_matrix_colorspace(
656         'Linear - Adobe Wide Gamut RGB',
657         from_reference_values=[AP0_to_ADOBEWIDEGAMUT],
658         aliases=['lin_adobewidegamutrgb'])
659     colorspaces.append(cs)
660
661     return colorspaces
662
663
664 def create_raw():
665     """
666     Creates the *raw* color space
667
668     Parameters
669     ----------
670     None
671
672     Returns
673     -------
674     ColorSpace
675          *raw* and all its identifying information
676     """
677     # *Raw* utility space
678     name = 'Raw'
679     raw = ColorSpace(name)
680     raw.description = 'The %s color space' % name
681     raw.aliases = ['raw']
682     raw.equality_group = name
683     raw.family = 'Utility'
684     raw.is_data = True
685
686     return raw