1#-------------------------------------------------------------------------------
2# Post processing (color management) related Mari scripts
3# coding: utf-8
4# Copyright (c) 2011 The Foundry Visionmongers Ltd.  All Rights Reserved.
5#-------------------------------------------------------------------------------
6
7import mari, time, PythonQt, os, math
8QtGui  = PythonQt.QtGui
9QtCore = PythonQt.QtCore
10ocio   = mari.utils.ocio
11
12##############################################################################################
13
14GAIN_GROUP_MAX_WIDTH = 312
15FSTOP_MAX_WIDTH      = 50
16EXPOSURE_MAX_WIDTH   = 102
17GAIN_MAX_WIDTH       = 80
18GAMMA_MAX_WIDTH      = 200
19TOOLBAR_SPACING      = 3
20
21toolbar              = None
22
23class OcioToolBar():
24
25    #-----------------------------------------------------------------------------------------
26
27    def __init__(self):
28        # Default all members...
29        self._config_file_list         = mari.FileList(ocio.config_file_list_default)
30        self._config                   = ocio.config_default
31        self._lut_file_list            = mari.FileList(ocio.lut_file_list_default)
32        self._lut_extrapolate          = ocio.lut_extrapolate_default
33        self._color_space              = ocio.color_space_default
34        self._display                  = ocio.display_default
35        self._view                     = ocio.view_default
36        self._swizzle                  = ocio.swizzle_default
37        self._gain                     = ocio.gain_default
38        self._gamma                    = ocio.gamma_default
39
40        self._lut_filter               = None
41        self._lut_filter_cache_id      = None
42        self._lut_texture_cache_id     = None
43        self._lut_sampler_name         = None
44
45        self._display_filter           = None
46        self._display_filter_cache_id  = None
47        self._display_texture_cache_id = None
48        self._display_sampler_name     = None
49
50        self._lut_extrapolate_widget   = None
51        self._color_space_widget       = None
52        self._display_widget           = None
53        self._view_widget              = None
54        self._swizzle_widget           = None
55        self._fstop_widget             = None
56        self._fstop_decrement_widget   = None
57        self._fstop_increment_widget   = None
58        self._gain_widget              = None
59        self._exposure_widget          = None
60        self._gain_reset_widget        = None
61        self._gamma_widget             = None
62        self._gamma_reset_widget       = None
63
64        self._buildWidgets()
65        self._toggle_color_management_action.setEnabled(False)
66        self._enableWidgets(False)
67
68        # Enable/disable color management.
69        mari.gl_render.setPostProcessingEnabled(self.isColorManagementEnabled())
70
71        # *** IMPORTANT *** The post filter collection used to be called 'OpenColorIO' but was renamed to hide the fact
72        # we use OpenColorIO from our users. So as a temporary workaround we need to check for the old filter collection
73        # on startup and remove it if found.
74        delete_filter_collection = mari.gl_render.findPostFilterCollection('OpenColorIO')
75        if delete_filter_collection is not None:
76            mari.gl_render.deletePostFilterCollection(delete_filter_collection)
77
78        # Create the OCIO post filter collection if not present.
79        self._filter_collection = mari.gl_render.findPostFilterCollection('Color Space')
80        if self._filter_collection is None:
81            self._filter_collection = mari.gl_render.createPostFilterCollection('Color Space')
82        else:
83            self._filter_collection.clear()
84        self._filter_collection.setReadOnly(True)
85
86        self._lut_filter = self._filter_collection.createGLSL('LUT Transform')
87        if not self._lut_file_list.isEmpty() and not self._rebuildLUTFilter(self._lut_file_list.at(0)):
88            self._lut_file_list.clear()
89
90        self._display_filter = self._filter_collection.createGLSL('Display Transform')
91        self._rebuildDisplayFilter()
92
93        self._buildMetadata()
94
95        # Set the color management filter stack as the current.
96        mari.gl_render.setPostFilterCollection(self._filter_collection)
97
98        # Attach ourselves to the applications toolbar created signal so we can rebuild the toolbar when it's been
99        # destoyed.
100        mari.utils.connect(mari.app.toolBarsCreated, self._toolBarsCreated)
101
102        # Attach ourselves to the appropriate GL signals so we can enable and disable widgets.
103        mari.utils.connect(mari.gl_render.postProcessingEnabled,          self._postProcessingEnabled)
104        mari.utils.connect(mari.gl_render.setCurrentPostFilterCollection, self._setCurrentPostFilterCollection)
105
106        # Attach ourselves to the appropriate project signals so we can load and save settings.
107        mari.utils.connect(mari.projects.openedProject,      self._openedProject)
108        mari.utils.connect(mari.projects.aboutToSaveProject, self._aboutToSaveProject)
109        mari.utils.connect(mari.projects.projectClosed,      self._closedProject)
110
111        # Update the UI to match the current project, if we have one.
112        current_project = mari.projects.current()
113        if current_project is not None:
114            self._openedProject(current_project)
115
116    #-----------------------------------------------------------------------------------------
117
118    def isColorManagementEnabled(self):
119        return self._toggle_color_management_action.isChecked()
120
121    #-----------------------------------------------------------------------------------------
122
123    def setLUTPath(self, value, update_metadata = True, force_shader_build = False):
124        if (self._lut_file_list.isEmpty() and value != '') or \
125           (not self._lut_file_list.isEmpty() and value == '') or \
126           (not self._lut_file_list.isEmpty() and value != self._lut_file_list.at(0)) \
127           :
128            if self._rebuildLUTFilter(value, force_shader_build):
129                self._lut_file_list.clear()
130                if value != '':
131                    self._lut_file_list.append(value)
132                    self._lut_file_list.setPickedFile(value)
133
134                    self._clear_lut_action.setEnabled(True)
135                    self._lut_extrapolate_widget.setEnabled(True)
136                    self._lut_filter.setEnabled(True)
137                else:
138                    self._clear_lut_action.setEnabled(False)
139                    self._lut_extrapolate_widget.setEnabled(False)
140                    self._lut_filter.setEnabled(False)
141
142                if update_metadata:
143                    mari.utils.disconnect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
144                    self._lut_filter.setMetadata('File', self._lut_file_list)
145                    mari.utils.connect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
146
147            else:
148                # If this was a request via the metadata system we will need to put the value back to what it was
149                # before.
150                if not update_metadata:
151                    mari.utils.disconnect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
152                    self._lut_filter.setMetadata('File', self._lut_file_list)
153                    mari.utils.connect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
154
155                return False
156
157        return True
158
159    #-----------------------------------------------------------------------------------------
160
161    def resetLUT(self):
162        if ocio.lut_file_list_default.isEmpty() or not self.setLUTPath(ocio.lut_file_list_default.at(0)):
163            self.setLUTPath('')
164
165    #-----------------------------------------------------------------------------------------
166
167    def selectLUT(self):
168        lut_path = mari.utils.misc.getOpenFileName(None,
169                                                   'Select LUT File',
170                                                   '' if self._lut_file_list.isEmpty() else self._lut_file_list.at(0),
171                                                   ocio.lutFileFilter(),
172                                                   None,
173                                                   0)
174        if os.path.isfile(lut_path):
175            self.setLUTPath(lut_path)
176
177    #-----------------------------------------------------------------------------------------
178
179    def setExtrapolateEnabled(self, value, update_widget = True, update_metadata = True):
180        if value != self._lut_extrapolate:
181            self._lut_extrapolate = value
182
183            if update_widget:
184                block = self._lut_extrapolate_widget.blockSignals(True)
185                self._lut_extrapolate_widget.setChecked(self._lut_extrapolate)
186                self._lut_extrapolate_widget.blockSignals(block)
187
188            if update_metadata:
189                mari.utils.disconnect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
190                self._lut_filter.setMetadata('Extrapolate', self._lut_extrapolate)
191                mari.utils.connect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
192
193            if not self._rebuildLUTFilter(lut_path = '' if self._lut_file_list.isEmpty() else self._lut_file_list.at(0),
194                                          force_shader_build = True):
195                self.resetLUT()
196
197            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed extrapolate to \'%s\'' % self._lut_extrapolate)
198
199    #-----------------------------------------------------------------------------------------
200
201    def setConfigPath(self, value, update_metadata = True):
202        if self._config_file_list.isEmpty() or value != self._config_file_list.at(0):
203            config = ocio.loadConfig(value, True)
204            if config is not None:
205                self._config_file_list.clear()
206                self._config_file_list.append(value)
207                self._config_file_list.setPickedFile(value)
208
209                self._config = config
210
211                self._updateDisplayWidgets()
212                self._updateDisplayMetadata()
213
214                self._rebuildDisplayFilter()
215
216                ocio.printMessage(ocio.MessageType.DEBUG, 'Changed config to \'%s\'' % self._config_file_list.at(0))
217
218            else:
219                # If this was a request via the metadata system we will need to put the value back to what it was
220                # before.
221                if not update_metadata:
222                    mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
223                    self._display_filter.setMetadata('ConfigPath', self._config_file_list)
224                    mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
225
226                return False
227
228        return True
229
230    #-----------------------------------------------------------------------------------------
231
232    def selectConfig(self):
233        config_path = mari.utils.misc.getOpenFileName(None,
234                                                      'Select Configuration File',
235                                                      '' if self._config_file_list.isEmpty() else self._config_file_list.at(0),
236                                                      ocio.configFileFilter(),
237                                                      None,
238                                                      0)
239        if os.path.isfile(config_path):
240            self.setConfigPath(config_path)
241
242    #-----------------------------------------------------------------------------------------
243
244    def setColorSpace(self, value, update_widget = True, update_metadata = True):
245        if value != self._color_space:
246            self._color_space = value
247
248            if update_widget:
249                block = self._color_space_widget.blockSignals(True)
250                index = self._color_space_widget.findText(self._color_space)
251                self._color_space_widget.setCurrentIndex(index)
252                self._color_space_widget.blockSignals(block)
253
254            if update_metadata:
255                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
256                self._display_filter.setMetadata('InputColorSpace', self._color_space)
257                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
258
259            self._rebuildDisplayFilter()
260
261            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed input color space to \'%s\'' % self._color_space)
262
263    #-----------------------------------------------------------------------------------------
264
265    def setDisplay(self, value, update_widget = True, update_metadata = True):
266        if value != self._display:
267            self._display = value
268
269            if update_widget:
270                block = self._display_widget.blockSignals(True)
271                index = self._display_widget.findText(self._display)
272                self._display_widget.setCurrentIndex(index)
273                self._display_widget.blockSignals(block)
274
275            if update_metadata:
276                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
277                self._display_filter.setMetadata('Display', self._display)
278                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
279
280            self.setView(self._config.getDefaultView(self._display), update_widget, update_metadata)
281
282            self._rebuildDisplayFilter()
283
284            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed display to \'%s\'' % self._display)
285
286    #-----------------------------------------------------------------------------------------
287
288    def setView(self, value, update_widget = True, update_metadata = True):
289        if value != self._view:
290            self._view = value
291
292            if update_widget:
293                block = self._view_widget.blockSignals(True)
294                index = self._view_widget.findText(self._view)
295                self._view_widget.setCurrentIndex(index)
296                self._view_widget.blockSignals(block)
297
298            if update_metadata:
299                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
300                self._display_filter.setMetadata('View', self._view)
301                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
302
303            self._rebuildDisplayFilter()
304
305            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed view to \'%s\'' % self._view)
306
307    #-----------------------------------------------------------------------------------------
308
309    def setSwizzle(self, value, update_widget = True, update_metadata = True):
310        if value != self._swizzle:
311            self._swizzle = value
312
313            if update_widget:
314                block = self._swizzle_widget.blockSignals(True)
315                index = self._swizzle_widget.findText(self._swizzle)
316                self._swizzle_widget.setCurrentIndex(index)
317                self._swizzle_widget.blockSignals(block)
318
319            if update_metadata:
320                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
321                self._display_filter.setMetadata('Swizzle', self._swizzle)
322                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
323
324            self._rebuildDisplayFilter()
325
326            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed swizzle to \'%s\'' % self._swizzle)
327
328    #-----------------------------------------------------------------------------------------
329
330    def setGain(self, value, update_widget = True, update_metadata = True):
331        if value != self._gain:
332            self._gain = value
333
334            if update_widget:
335                self._updateGainWidgets()
336
337            if update_metadata:
338                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
339                self._display_filter.setMetadata('Gain', self._gain)
340                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
341
342            self._rebuildDisplayFilter()
343
344            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed gain to \'%s\'' % self._gain)
345
346    #-----------------------------------------------------------------------------------------
347
348    def setGamma(self, value, update_widget = True, update_metadata = True):
349        if value != self._gamma:
350            self._gamma = value
351
352            if update_widget:
353                block = self._gamma_widget.blockSignals(True)
354                self._gamma_widget.setValue(self._gamma)
355                self._gamma_widget.blockSignals(block)
356
357            if update_metadata:
358                mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
359                self._display_filter.setMetadata('Gamma', self._gamma)
360                mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
361
362            self._rebuildDisplayFilter()
363
364            ocio.printMessage(ocio.MessageType.DEBUG, 'Changed gamma to \'%s\'' % self._gamma)
365
366    #-----------------------------------------------------------------------------------------
367
368    def updateLUTSize(self):
369        ocio.printMessage(ocio.MessageType.DEBUG, 'Updating LUT size...')
370
371        # Rebuild the LUT filter.
372        if self._lut_sampler_name is not None:
373            self._lut_filter.deleteTexture(self._lut_sampler_name)
374            self._lut_sampler_name = None
375
376        self._lut_filter_cache_id  = None
377        self._lut_texture_cache_id = None
378
379        if not self._rebuildLUTFilter(lut_path = '' if self._lut_file_list.isEmpty() else self._lut_file_list.at(0),
380                                      force_shader_build = True):
381            self.resetLUT()
382
383        # Rebuild the display filter.
384        if self._display_sampler_name is not None:
385            self._display_filter.deleteTexture(self._display_sampler_name)
386            self._display_sampler_name = None
387
388        self._display_filter_cache_id  = None
389        self._display_texture_cache_id = None
390
391        self._rebuildDisplayFilter()
392
393    #-----------------------------------------------------------------------------------------
394
395    def updateFStopCenter(self):
396        ocio.printMessage(ocio.MessageType.DEBUG, 'Updating f-stop center...')
397
398        fstop = ocio.convertGainToFStop(self._gain)
399        self._updateFStopWidgetText(fstop)
400
401    #-----------------------------------------------------------------------------------------
402    # Widgets:
403    #-----------------------------------------------------------------------------------------
404
405    def _buildWidgets(self):
406        action_list = list()
407
408        self._toggle_color_management_action = self._addAction(
409            '/Mari/OpenColorIO/&Toggle Color Management',
410            'mari.system._ocio_toolbar.toolbar._toggleColorManagement()',
411            'ColorManager.png',
412            'Toggle on/off color management',
413            'Toggle color management')
414        self._toggle_color_management_action.setCheckable(True)
415        self._toggle_color_management_action.setChecked(ocio.enabled_default)
416        action_list.append('/Mari/OpenColorIO/&Toggle Color Management')
417
418        self._select_config_action = self._addAction(
419            '/Mari/OpenColorIO/&Select Config',
420            'mari.system._ocio_toolbar.toolbar.selectConfig()',
421            'LoadColorConfig.png',
422            'Select color space configuration file',
423            'Select config')
424        action_list.append('/Mari/OpenColorIO/&Select Config')
425
426        self._select_lut_action = self._addAction(
427            '/Mari/OpenColorIO/&Select LUT',
428            'mari.system._ocio_toolbar.toolbar.selectLUT()',
429            'LoadLookupTable.png',
430            'Select LUT file',
431            'Select LUT')
432        action_list.append('/Mari/OpenColorIO/&Select LUT')
433
434        self._clear_lut_action = self._addAction(
435            '/Mari/OpenColorIO/&Clear LUT',
436            'mari.system._ocio_toolbar.toolbar._clearLUT()',
437            'ClearLookupTable.png',
438            'Clear current LUT',
439            'Clear LUT')
440        action_list.append('/Mari/OpenColorIO/&Clear LUT')
441
442        mari.app.deleteToolBar('Color Space')
443        self._toolbar = mari.app.createToolBar('Color Space', True)
444        self._toolbar.addActionList(action_list, False)
445        self._toolbar.setLockedSlot(True)
446        self._toolbar.setSpacing(TOOLBAR_SPACING)
447
448        self._toolbar.insertSeparator('/Mari/OpenColorIO/&Select LUT')
449
450        # Extrapolate:
451        self._toolbar.addWidget(QtGui.QLabel('Extrapolate'))
452        self._lut_extrapolate_widget = QtGui.QCheckBox()
453        self._lut_extrapolate_widget.setToolTip('Extrapolate if outside LUT range');
454        self._lut_extrapolate_widget.setChecked(self._lut_extrapolate)
455        self._lut_extrapolate_widget.connect(
456            QtCore.SIGNAL('toggled(bool)'),
457            lambda value: self.setExtrapolateEnabled(value = value, update_widget = False, update_metadata = True))
458        self._toolbar.addWidget(self._lut_extrapolate_widget)
459
460        self._toolbar.addSeparator()
461
462        color_spaces = [color_space.getName() for color_space in self._config.getColorSpaces()]
463
464        # Color-Space:
465        self._color_space_widget = self._addComboBox(
466            'Input Color Space',
467            color_spaces,
468            self._color_space,
469            ocio.color_space_default,
470            lambda value: self.setColorSpace(value = value, update_widget = False, update_metadata = True))
471        self._color_space = self._color_space_widget.currentText
472
473        # Display:
474        self._display_widget = self._addComboBox(
475            'Display Device',
476            self._config.getDisplays(),
477            self._display,
478            ocio.display_default,
479            lambda value: self.setDisplay(value = value, update_widget = False, update_metadata = True))
480        self._display = self._display_widget.currentText
481
482        # View:
483        self._view_widget = self._addComboBox(
484            'View Transform',
485            self._config.getViews(self._display),
486            self._view,
487            ocio.view_default,
488            lambda value: self.setView(value = value, update_widget = False, update_metadata = True))
489        self._view = self._view_widget.currentText
490
491        # Swizzle:
492        self._swizzle_widget = self._addComboBox(
493            'Component',
494            ocio.SWIZZLE_TYPES,
495            self._swizzle,
496            ocio.swizzle_default,
497            lambda value: self.setSwizzle(value = value, update_widget = False, update_metadata = True))
498        self._swizzle = self._swizzle_widget.currentText
499
500        # Gain Group:
501        group_widget, layout = self._addWidgetGroup()
502        group_widget.setMaximumWidth(GAIN_GROUP_MAX_WIDTH)
503
504        layout.addWidget(QtGui.QLabel('Gain'))
505
506        # F-Stop:
507        subgroup_widget = QtGui.QWidget()
508        layout.addWidget(subgroup_widget)
509
510        sublayout = QtGui.QHBoxLayout()
511        sublayout.setSpacing(0)
512        sublayout.setMargin(0)
513        subgroup_widget.setLayout(sublayout)
514
515        exposure     = ocio.convertGainToExposure(self._gain)
516        fstop        = ocio.convertExposureToFStop(exposure)
517        scale        = (exposure - ocio.EXPOSURE_MIN) / ocio.EXPOSURE_DELTA
518        widget_max   = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.FSTOP_STEP_SIZE))
519        widget_value = scale * widget_max
520        self._fstop_widget = mari.LineEdit()
521        self._fstop_widget.setRange(widget_max)
522        self._fstop_widget.setMaximumWidth(FSTOP_MAX_WIDTH)
523        self._fstop_widget.setReadOnly(True)
524        self._updateFStopWidgetText(fstop)
525        self._fstop_widget.setValue(widget_value)
526        mari.utils.connect(self._fstop_widget.movedMouse, self._fstopMovedMouse)
527        self._fstop_widget.addToLayout(sublayout)
528
529        self._fstop_decrement_widget = self._addSmallButtom(
530            sublayout,
531            '-',
532            'Decrease gain 1/2 stop',
533            lambda: self.setGain(ocio.convertExposureToGain(ocio.convertGainToExposure(self._gain) - 0.5)))
534        self._fstop_increment_widget = self._addSmallButtom(
535            sublayout,
536            '+',
537            'Increase gain 1/2 stop',
538            lambda: self.setGain(ocio.convertExposureToGain(ocio.convertGainToExposure(self._gain) + 0.5)))
539
540        ocio.registerLUTSizeChanged(self.updateLUTSize)
541        ocio.registerFStopCenterChanged(self.updateFStopCenter)
542
543        # Gain:
544        subgroup_widget = QtGui.QWidget()
545        layout.addWidget(subgroup_widget)
546
547        sublayout = QtGui.QHBoxLayout()
548        sublayout.setSpacing(3)
549        sublayout.setMargin(0)
550        subgroup_widget.setLayout(sublayout)
551
552        widget_max   = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.EXPOSURE_STEP_SIZE))
553        widget_value = scale * widget_max
554        self._gain_widget = mari.LineEdit()
555        self._gain_widget.setRange(widget_max)
556        self._gain_widget.addFloatValidator(ocio.GAIN_MIN, ocio.GAIN_MAX, ocio.GAIN_PRECISION)
557        self._gain_widget.setMaximumWidth(GAIN_MAX_WIDTH)
558        self._updateGainWidgetText()
559        self._gain_widget.setValue(widget_value)
560        mari.utils.connect(
561            self._gain_widget.lostFocus,
562            lambda: self.setGain(max(min(float(self._gain_widget.text()), ocio.GAIN_MAX), ocio.GAIN_MIN)))
563        mari.utils.connect(self._gain_widget.movedMouse, self._gainMovedMouse)
564        self._gain_widget.addToLayout(sublayout)
565
566        # Exposure:
567        self._exposure_widget = QtGui.QSlider()
568        self._exposure_widget.orientation = 1
569        self._exposure_widget.setMaximum(widget_max)
570        self._exposure_widget.setValue(widget_value)
571        self._exposure_widget.setMinimumWidth(EXPOSURE_MAX_WIDTH)
572        self._exposure_widget.setMaximumWidth(EXPOSURE_MAX_WIDTH)
573        mari.utils.connect(self._exposure_widget.valueChanged, self._exposureChanged)
574        sublayout.addWidget(self._exposure_widget)
575
576        self._gain_reset_widget = self._addSmallButtom(
577            layout,
578            'R',
579            'Reset gain to default',
580            lambda: self.setGain(value = ocio.GAIN_RESET, update_widget = True, update_metadata = True))
581
582        # Gamma:
583        group_widget, layout = self._addWidgetGroup()
584        group_widget.setMaximumWidth(GAMMA_MAX_WIDTH)
585
586        layout.addWidget(QtGui.QLabel('Gamma'))
587
588        self._gamma_widget = mari.FloatSlider()
589        self._gamma_widget.setRange(ocio.GAMMA_MIN, ocio.GAMMA_MAX)
590        self._gamma_widget.setStepSize(ocio.GAMMA_STEP_SIZE)
591        self._gamma_widget.setPrecision(ocio.GAMMA_PRECISION)
592        self._gamma_widget.setValue(self._gamma)
593        mari.utils.connect(
594            self._gamma_widget.valueChanged,
595            lambda value: self.setGamma(value = value, update_widget = False, update_metadata = True))
596        self._gamma_widget.addToLayout(layout)
597
598        self._gamma_reset_widget = self._addSmallButtom(
599            layout,
600            'R',
601            'Reset gamma to default',
602            lambda: self.setGamma(value = ocio.GAMMA_RESET, update_widget = True, update_metadata = True))
603
604    #-----------------------------------------------------------------------------------------
605
606    def _updateDisplayWidgets(self):
607        color_spaces = [color_space.getName() for color_space in self._config.getColorSpaces()]
608
609        self._updateComboBox(self._color_space_widget, color_spaces, self._color_space, ocio.color_space_default)
610        self._color_space = self._color_space_widget.currentText
611
612        self._updateComboBox(self._display_widget, self._config.getDisplays(), self._display, ocio.display_default)
613        self._display = self._display_widget.currentText
614
615        self._updateComboBox(self._view_widget, self._config.getViews(self._display), self._view, ocio.view_default)
616        self._view = self._view_widget.currentText
617
618        self._updateComboBox(self._swizzle_widget, ocio.SWIZZLE_TYPES, self._swizzle, ocio.swizzle_default)
619        self._swizzle = self._swizzle_widget.currentText
620
621        self._updateGainWidgets()
622
623        self._gamma_widget.setValue(self._gamma)
624
625    #-----------------------------------------------------------------------------------------
626
627    def _enableWidgets(self, enable):
628        self._select_config_action.setEnabled(enable)
629        self._select_lut_action.setEnabled(enable)
630        lut_enable = enable and not self._lut_file_list.isEmpty()
631        self._clear_lut_action.setEnabled(lut_enable)
632        self._lut_extrapolate_widget.setEnabled(lut_enable)
633
634        self._color_space_widget.setEnabled(enable)
635        self._display_widget.setEnabled(enable)
636        self._view_widget.setEnabled(enable)
637        self._swizzle_widget.setEnabled(enable)
638
639        self._fstop_widget.setEnabled(enable)
640        self._fstop_decrement_widget.setEnabled(enable)
641        self._fstop_increment_widget.setEnabled(enable)
642        self._gain_widget.setEnabled(enable)
643        self._exposure_widget.setEnabled(enable)
644        self._gain_reset_widget.setEnabled(enable)
645
646        self._gamma_widget.setEnabled(enable)
647        self._gamma_reset_widget.setEnabled(enable)
648
649    #-----------------------------------------------------------------------------------------
650
651    def _addAction(self, identifier, command, icon_filename, tip, whats_this):
652        action = mari.actions.find(identifier)
653        if action is None:
654            action = mari.actions.create(identifier, command)
655
656            icon_path = mari.resources.path(mari.resources.ICONS) + '/' + icon_filename
657            action.setIconPath(icon_path)
658
659            action.setStatusTip(tip)
660            action.setToolTip(tip)
661            action.setWhatsThis(whats_this)
662
663        return action
664
665    #-----------------------------------------------------------------------------------------
666
667    def _addWidgetGroup(self):
668        group_widget = QtGui.QWidget()
669        self._toolbar.addWidget(group_widget)
670
671        layout = QtGui.QHBoxLayout()
672        layout.setSpacing(1)
673        layout.setMargin(1)
674        group_widget.setLayout(layout)
675
676        return (group_widget, layout)
677
678    #-----------------------------------------------------------------------------------------
679
680    def _addComboBox(self, label, items, value, default, value_changed, *args):
681        group_widget, layout = self._addWidgetGroup()
682
683        layout.addWidget(QtGui.QLabel(label))
684
685        widget = QtGui.QComboBox()
686        self._updateComboBox(widget, items, value, default)
687        widget.connect(QtCore.SIGNAL('currentIndexChanged(const QString &)'), value_changed)
688        layout.addWidget(widget)
689
690        return widget
691
692    #-----------------------------------------------------------------------------------------
693
694    def _updateComboBox(self, widget, items, value, default):
695        block = widget.blockSignals(True)
696
697        widget.clear()
698        for item in items:
699            widget.addItem(item)
700
701        if items.count(value) != 0:
702            widget.setCurrentIndex(items.index(value))
703        elif items.count(default) != 0:
704            widget.setCurrentIndex(items.index(default))
705
706        widget.blockSignals(block)
707
708    #-----------------------------------------------------------------------------------------
709
710    def _addSmallButtom(self, layout, label, tool_tip, value_changed, *args):
711        widget = QtGui.QPushButton(label);
712        widget.setToolTip(tool_tip);
713        widget.setFixedHeight(16);
714        widget.setFixedWidth(16);
715        widget.connect(QtCore.SIGNAL('released()'), value_changed)
716        layout.addWidget(widget);
717
718        return widget
719
720    #-----------------------------------------------------------------------------------------
721
722    def _convertFStopWidgetValueToGain(self, value):
723        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.FSTOP_STEP_SIZE))
724        scale      = float(value) / float(widget_max)
725        exposure   = ocio.EXPOSURE_MIN + scale * ocio.EXPOSURE_DELTA
726        return ocio.convertExposureToGain(exposure)
727
728    #-----------------------------------------------------------------------------------------
729
730    def _convertExposureWidgetValueToGain(self, value):
731        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.EXPOSURE_STEP_SIZE))
732        scale      = float(value) / float(widget_max)
733        exposure   = ocio.EXPOSURE_MIN + scale * ocio.EXPOSURE_DELTA
734        return ocio.convertExposureToGain(exposure)
735
736    #-----------------------------------------------------------------------------------------
737
738    def _updateFStopWidgetText(self, fstop):
739        block = self._fstop_widget.blockSignals(True)
740        if fstop < 10.0:
741            # Floor the value to one decimal place and only display the decimal point if necessary
742            text = '%f' % fstop
743            index = text.index('.')
744            if text[index + 1] == '0':
745                text = text[:index]
746            else:
747                text = text[:index + 2]
748            self._fstop_widget.setText('f/%s' % text)
749        else:
750            self._fstop_widget.setText('f/%d' % int(fstop))
751        self._fstop_widget.blockSignals(block)
752
753    #-----------------------------------------------------------------------------------------
754
755    def _updateGainWidgetText(self):
756        block = self._gain_widget.blockSignals(True)
757        self._gain_widget.setText(('%.' + ('%d' % ocio.GAIN_PRECISION) + 'f') % self._gain)
758        self._gain_widget.home(False)
759        self._gain_widget.blockSignals(block)
760
761    #-----------------------------------------------------------------------------------------
762
763    def _updateGainWidgets(self):
764        exposure = ocio.convertGainToExposure(self._gain)
765        fstop    = ocio.convertExposureToFStop(exposure)
766        self._updateFStopWidgetText(fstop)
767
768        scale        = (exposure - ocio.EXPOSURE_MIN) / ocio.EXPOSURE_DELTA
769        widget_max   = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.FSTOP_STEP_SIZE))
770        widget_value = int(round(scale * float(widget_max)))
771        block = self._fstop_widget.blockSignals(True)
772        self._fstop_widget.setValue(widget_value)
773        self._fstop_widget.blockSignals(block)
774
775        self._updateGainWidgetText()
776
777        widget_max   = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.EXPOSURE_STEP_SIZE))
778        widget_value = int(round(scale * float(widget_max)))
779        block = self._gain_widget.blockSignals(True)
780        self._gain_widget.setValue(widget_value)
781        self._gain_widget.blockSignals(block)
782        block = self._exposure_widget.blockSignals(True)
783        self._exposure_widget.setValue(widget_value)
784        self._exposure_widget.blockSignals(block)
785
786    #-----------------------------------------------------------------------------------------
787
788    def _toggleColorManagement(self):
789        enabled = self.isColorManagementEnabled()
790        mari.gl_render.setPostProcessingEnabled(enabled)
791        self._enableWidgets(enabled)
792        ocio.printMessage(ocio.MessageType.DEBUG, 'Toggled color management to \'%s\'' % ('on' if enabled else 'off'))
793
794    #-----------------------------------------------------------------------------------------
795
796    def _clearLUT(self):
797        self.setLUTPath('')
798        ocio.printMessage(ocio.MessageType.DEBUG, 'Cleared lut')
799
800    #-----------------------------------------------------------------------------------------
801
802    def _fstopMovedMouse(self, value):
803        self.setGain(self._convertFStopWidgetValueToGain(float(value)), False)
804
805        exposure = ocio.convertGainToExposure(self._gain)
806        fstop    = ocio.convertExposureToFStop(exposure)
807        self._updateFStopWidgetText(fstop)
808
809        self._updateGainWidgetText()
810
811        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.EXPOSURE_STEP_SIZE))
812        scale      = (exposure - ocio.EXPOSURE_MIN) / ocio.EXPOSURE_DELTA
813        value      = int(round(scale * float(widget_max)))
814        self._gain_widget.setValue(value)
815
816        value = max(min(value, widget_max), 0)
817        self._exposure_widget.setValue(value)
818
819    #-----------------------------------------------------------------------------------------
820
821    def _gainMovedMouse(self, value):
822        self.setGain(self._convertExposureWidgetValueToGain(float(value)), False)
823
824        self._updateGainWidgetText()
825
826        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.EXPOSURE_STEP_SIZE))
827        value      = max(min(value, widget_max), 0)
828        self._exposure_widget.setValue(value)
829
830        exposure = ocio.convertGainToExposure(self._gain)
831        fstop    = ocio.convertExposureToFStop(exposure)
832        self._updateFStopWidgetText(fstop)
833
834        scale      = (exposure - ocio.EXPOSURE_MIN) / ocio.EXPOSURE_DELTA
835        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.FSTOP_STEP_SIZE))
836        value      = int(round(scale * float(widget_max)))
837        self._fstop_widget.setValue(value)
838
839    #-----------------------------------------------------------------------------------------
840
841    def _exposureChanged(self, value):
842        self.setGain(value = self._convertExposureWidgetValueToGain(float(value)),
843                     update_widget = False,
844                     update_metadata = True)
845
846        self._updateGainWidgetText()
847
848        self._gain_widget.setValue(value)
849
850        exposure = ocio.convertGainToExposure(self._gain)
851        fstop    = ocio.convertExposureToFStop(exposure)
852        self._updateFStopWidgetText(fstop)
853
854        scale      = (exposure - ocio.EXPOSURE_MIN) / ocio.EXPOSURE_DELTA
855        widget_max = int(math.ceil(ocio.EXPOSURE_DELTA / ocio.FSTOP_STEP_SIZE))
856        value      = int(round(scale * float(widget_max)))
857        self._fstop_widget.setValue(value)
858
859    #-----------------------------------------------------------------------------------------
860    # Metadata:
861    #-----------------------------------------------------------------------------------------
862
863    def _buildMetadata(self):
864        # LUT:
865        # ---
866        mari.utils.connect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
867        self._updateLUTMetadata()
868
869        flags = self._lut_filter.METADATA_VISIBLE | self._lut_filter.METADATA_EDITABLE
870        self._lut_filter.setMetadataFlags('File', flags)
871
872        self._lut_filter.setMetadataFlags('Extrapolate', flags)
873
874        # Display:
875        # -------
876        mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
877        self._updateDisplayMetadata()
878
879        self._display_filter.setMetadataDisplayName('ConfigPath', 'Configuration File')
880        flags = self._display_filter.METADATA_VISIBLE | self._display_filter.METADATA_EDITABLE
881        self._display_filter.setMetadataFlags('ConfigPath', flags)
882
883        self._display_filter.setMetadataDisplayName('InputColorSpace', 'Input Color Space')
884        self._display_filter.setMetadataFlags('InputColorSpace', flags)
885
886        self._display_filter.setMetadataDisplayName('Display', 'Display Device')
887        self._display_filter.setMetadataFlags('Display', flags)
888
889        self._display_filter.setMetadataDisplayName('View', 'View Transform')
890        self._display_filter.setMetadataFlags('View', flags)
891
892        self._display_filter.setMetadataDisplayName('Swizzle', 'Component')
893        self._display_filter.setMetadataFlags('Swizzle', flags)
894
895        self._display_filter.setMetadataDefault('Gain', ocio.GAIN_RESET)
896        self._display_filter.setMetadataRange('Gain', ocio.GAIN_MIN, ocio.GAIN_MAX)
897        self._display_filter.setMetadataStep('Gain', ocio.GAIN_STEP_SIZE)
898        self._display_filter.setMetadataFlags('Gain', flags)
899
900        self._display_filter.setMetadataDefault('Gamma', ocio.GAMMA_RESET)
901        self._display_filter.setMetadataRange('Gamma', ocio.GAMMA_MIN, ocio.GAMMA_MAX)
902        self._display_filter.setMetadataStep('Gamma', ocio.GAMMA_STEP_SIZE)
903        self._display_filter.setMetadataFlags('Gamma', flags)
904
905    #-----------------------------------------------------------------------------------------
906
907    def _updateLUTMetadata(self):
908        mari.utils.disconnect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
909
910        self._lut_filter.setMetadata('File', self._lut_file_list)
911
912        self._lut_filter.setMetadata('Extrapolate', self._lut_extrapolate)
913
914        mari.utils.connect(self._lut_filter.metadataValueChanged, lutMetadataValueChanged)
915
916    #-----------------------------------------------------------------------------------------
917
918    def _updateDisplayMetadata(self):
919        mari.utils.disconnect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
920
921        self._display_filter.setMetadata('ConfigPath', self._config_file_list)
922
923        color_spaces = [color_space.getName() for color_space in self._config.getColorSpaces()]
924
925        self._display_filter.setMetadata('InputColorSpace', self._color_space)
926        self._display_filter.setMetadataItemList('InputColorSpace', color_spaces)
927
928        self._display_filter.setMetadata('Display', self._display)
929        self._display_filter.setMetadataItemList('Display', self._config.getDisplays())
930
931        self._display_filter.setMetadata('View', self._view)
932        self._display_filter.setMetadataItemList('View', self._config.getViews(self._display))
933
934        self._display_filter.setMetadata('Swizzle', self._swizzle)
935        self._display_filter.setMetadataItemList('Swizzle', ocio.SWIZZLE_TYPES)
936
937        self._display_filter.setMetadata('Gain', self._gain)
938
939        self._display_filter.setMetadata('Gamma', self._gain)
940
941        mari.utils.connect(self._display_filter.metadataValueChanged, displayMetadataValueChanged)
942
943
944    #-----------------------------------------------------------------------------------------
945    # External Connections:
946    #-----------------------------------------------------------------------------------------
947
948    def _openedProject(self, project):
949        ocio.printMessage(ocio.MessageType.DEBUG, 'Loading settings for project \'%s\'' % project.name())
950
951        # Load the settings stored as metadata on the project...
952
953        # General:
954        # -------
955
956        self._toggle_color_management_action.setEnabled(True)
957        self._toggle_color_management_action.setChecked(project.metadata('ColorEnabled') if project.hasMetadata('ColorEnabled') else ocio.enabled_default)
958
959        # Enable/disable color management (MUST be done after modifications to 'self._toggle_color_management_action'.
960        mari.gl_render.setPostProcessingEnabled(self.isColorManagementEnabled())
961
962        filter_collection = None
963        if project.hasMetadata('ColorProfile'):
964            # *** IMPORTANT *** The post filter collection used to be called 'OpenColorIO' but was renamed to hide the
965            # fact we use OpenColorIO from our users. So as a temporary workaround we need to check for the old filter
966            # collection correct for it.
967            name = project.metadata('ColorProfile')
968            if name == 'OpenColorIO':
969                name = 'Color Space'
970            filter_collection = mari.gl_render.findPostFilterCollection(name)
971
972        # Default the color management filter stack if the working one doesn't exist.
973        if filter_collection is None:
974            filter_collection = mari.gl_render.findPostFilterCollection(ocio.profile_default)
975
976        mari.gl_render.setPostFilterCollection(filter_collection)
977
978        # LUT:
979        # ---
980
981        lut_extrapolate = project.metadata('OcioLutExtrapolate') if project.hasMetadata('OcioLutExtrapolate') else ocio.lut_extrapolate_default
982        force_shader_build = lut_extrapolate != self._lut_extrapolate
983        self._lut_extrapolate = lut_extrapolate
984        self._lut_extrapolate_widget.setChecked(self._lut_extrapolate)
985
986        if project.hasMetadata('OcioLutPath'):
987            lut_path = ocio.buildLoadPath(project.metadata('OcioLutPath'))
988            if not self.setLUTPath(value = lut_path, update_metadata = True, force_shader_build = force_shader_build):
989                self.resetLUT()
990        else:
991            self.resetLUT()
992
993        # Display:
994        # -------
995
996        self._color_space = project.metadata(  'OcioColorSpace') if project.hasMetadata('OcioColorSpace') else ocio.color_space_default
997        self._display     = project.metadata(     'OcioDisplay') if project.hasMetadata(   'OcioDisplay') else ocio.display_default
998        self._view        = project.metadata(        'OcioView') if project.hasMetadata(      'OcioView') else ocio.view_default
999        self._swizzle     = project.metadata(     'OcioSwizzle') if project.hasMetadata(   'OcioSwizzle') else ocio.swizzle_default
1000        self._gain        = max(min(project.metadata('OcioGain'),
1001                                    ocio.GAIN_MAX),
1002                                ocio.GAIN_MIN)                   if project.hasMetadata(      'OcioGain') else ocio.gain_default
1003        self._gamma       = project.metadata(       'OcioGamma') if project.hasMetadata(     'OcioGamma') else ocio.gamma_default
1004
1005        # Attempt to load a configuration file...
1006        self._config_file_list.clear()
1007        self._config = None
1008
1009        # 1. Environment variable.
1010        config_path = os.getenv('OCIO')
1011        if config_path is not None:
1012            self.setConfigPath(config_path)
1013
1014        # 2. Project setting.
1015        if self._config is None and project.hasMetadata('OcioConfigPath'):
1016            self.setConfigPath(ocio.buildLoadPath(project.metadata('OcioConfigPath')))
1017
1018        # 3. Use the default if nothing was found.
1019        if self._config is None:
1020            self._config_file_list = mari.FileList(ocio.config_file_list_default)
1021            self._config           = ocio.config_default
1022
1023            self._updateDisplayWidgets()
1024            self._rebuildDisplayFilter()
1025
1026        self._enableWidgets(filter_collection.name() == 'Color Space' and self._toggle_color_management_action.isChecked())
1027        self._updateLUTMetadata()
1028        self._updateDisplayMetadata()
1029
1030        self._printLog()
1031
1032    #-----------------------------------------------------------------------------------------
1033
1034    def _aboutToSaveProject(self, project):
1035        ocio.printMessage(ocio.MessageType.DEBUG, 'Saving settings for project \'%s\'' % project.name())
1036
1037        # Store the settings as metadata on the project.
1038        project.setMetadata(      'ColorEnabled', self.isColorManagementEnabled())
1039        filter_collection = mari.gl_render.currentPostFilterCollection()
1040        if filter_collection is not None:
1041            project.setMetadata(  'ColorProfile', filter_collection.name())
1042        project.setMetadata('OcioLutExtrapolate', self._lut_extrapolate)
1043        project.setMetadata(       'OcioLutPath', '' if self._lut_file_list.isEmpty() else ocio.buildSavePath(self._lut_file_list.at(0)))
1044        if os.getenv('OCIO') is None:
1045            project.setMetadata('OcioConfigPath', '' if self._config_file_list.isEmpty() else ocio.buildSavePath(self._config_file_list.at(0)))
1046        project.setMetadata(    'OcioColorSpace', self._color_space)
1047        project.setMetadata(       'OcioDisplay', self._display)
1048        project.setMetadata(          'OcioView', self._view)
1049        project.setMetadata(       'OcioSwizzle', self._swizzle)
1050        project.setMetadata(          'OcioGain', self._gain)
1051        project.setMetadata(         'OcioGamma', self._gamma)
1052
1053    #-----------------------------------------------------------------------------------------
1054
1055    def _closedProject(self):
1056        self._toggle_color_management_action.setEnabled(False)
1057        self._enableWidgets(False)
1058
1059    #-----------------------------------------------------------------------------------------
1060
1061    def _toolBarsCreated(self):
1062        # Things like deleting Mari's configuration file and reseting the layout to the default will destroy the toolbar
1063        # so we need to detect if this is the case and rebuild it!
1064        toolbar = mari.app.findToolBar('Color Space')
1065        if toolbar is None:
1066            ocio.printMessage(ocio.MessageType.DEBUG, 'Rebuilding missing toolbar...')
1067            self._buildWidgets()
1068
1069    #-----------------------------------------------------------------------------------------
1070
1071    def _postProcessingEnabled(self, enabled):
1072        self._toggle_color_management_action.setChecked(enabled)
1073
1074        # Only enable or disable UI if we have a current project.
1075        current_project = mari.projects.current()
1076        if current_project is not None:
1077            self._enableWidgets(enabled)
1078
1079    #-----------------------------------------------------------------------------------------
1080
1081    def _setCurrentPostFilterCollection(self):
1082        # Only enable or disable UI if we have a current project.
1083        current_project = mari.projects.current()
1084        if current_project is not None:
1085            filter_collection = mari.gl_render.currentPostFilterCollection()
1086            if filter_collection is None or filter_collection.name() != 'Color Space':
1087                ocio.printMessage(ocio.MessageType.DEBUG, 'Disabling OpenColorIO')
1088                self._enableWidgets(False)
1089            else:
1090                ocio.printMessage(ocio.MessageType.DEBUG, 'Enabling OpenColorIO')
1091                self._enableWidgets(True)
1092
1093    #-----------------------------------------------------------------------------------------
1094    # Filter:
1095    #-----------------------------------------------------------------------------------------
1096
1097    def _rebuildLUTFilter(self, lut_path, force_shader_build = False):
1098        if lut_path == '':
1099            self._lut_filter.setDefinitionsSnippet('')
1100            self._lut_filter.setBodySnippet('')
1101
1102            if self._lut_sampler_name is not None:
1103                self._lut_filter.deleteTexture(self._lut_sampler_name)
1104                self._lut_sampler_name = None
1105
1106            self._lut_filter_cache_id  = None
1107            self._lut_texture_cache_id = None
1108
1109        else:
1110            # There is a chance this is a bad file so we need to guard against it.
1111            try:
1112                self._lut_filter_cache_id, self._lut_texture_cache_id, self._lut_sampler_name = ocio.buildLUTFilter(
1113                    self._config,
1114                    lut_path,
1115                    self._lut_filter,
1116                    self._lut_filter_cache_id,
1117                    self._lut_texture_cache_id,
1118                    self._lut_extrapolate,
1119                    force_shader_build)
1120
1121            except Exception, e:
1122                message = 'Failed to load LUT file \'%s\' due to \'%s\'' % (lut_path, e)
1123                ocio.printMessage(ocio.MessageType.ERROR, '%s' % message)
1124                if not mari.app.inTerminalMode():
1125                    mari.utils.misc.message(message, 'Color Space', 1024, 2)
1126
1127                return False
1128
1129        ocio.printMessage(ocio.MessageType.DEBUG, 'Changed LUT to \'%s\'' % lut_path)
1130
1131        return True
1132
1133    #-----------------------------------------------------------------------------------------
1134
1135    def _rebuildDisplayFilter(self):
1136        display_transform = ocio.PyOpenColorIO.DisplayTransform()
1137        display_transform.setInputColorSpaceName(self._color_space)
1138
1139        if hasattr(display_transform, 'setDisplay'):
1140            # OCIO 1.0+
1141            display_transform.setDisplay(self._display)
1142            display_transform.setView(self._view)
1143        else:
1144            # OCIO 0.8.X
1145            display_color_space = self._config.getDisplayColorSpaceName(self._display, self._view)
1146            display_transform.setDisplayColorSpaceName(display_color_space)
1147
1148        # Add the channel sizzle.
1149        luma_coefs = self._config.getDefaultLumaCoefs()
1150        mtx, offset = ocio.PyOpenColorIO.MatrixTransform.View(ocio.SWIZZLE_VALUES[self._swizzle], luma_coefs)
1151
1152        transform = ocio.PyOpenColorIO.MatrixTransform()
1153        transform.setValue(mtx, offset)
1154        display_transform.setChannelView(transform)
1155
1156        # Add the linear gain.
1157        transform = ocio.PyOpenColorIO.CDLTransform()
1158        transform.setSlope((self._gain, self._gain, self._gain))
1159        display_transform.setLinearCC(transform)
1160
1161        # Add the post-display CC.
1162        transform = ocio.PyOpenColorIO.ExponentTransform()
1163        transform.setValue([1.0 / max(1e-6, v) for v in (self._gamma, self._gamma, self._gamma, self._gamma)])
1164        display_transform.setDisplayCC(transform)
1165
1166        processor = self._config.getProcessor(display_transform)
1167
1168        self._display_filter_cache_id, self._display_texture_cache_id, self._display_sampler_name = ocio.buildProcessorFilter(
1169            processor,
1170            self._display_filter,
1171            self._display_filter_cache_id,
1172            self._display_texture_cache_id)
1173
1174        current_canvas = mari.canvases.current()
1175        if current_canvas is not None:
1176            current_canvas.repaint()
1177
1178    #-----------------------------------------------------------------------------------------
1179    # Debugging:
1180    #-----------------------------------------------------------------------------------------
1181
1182    def _printLog(self):
1183        ocio.printMessage(    ocio.MessageType.INFO, '==============================================================')
1184        ocio.printMessage(    ocio.MessageType.INFO, 'Configuration:')
1185        ocio.printMessage(    ocio.MessageType.INFO, '==============================================================')
1186        ocio.printMessage(    ocio.MessageType.INFO, '     Enabled: %s; Default: %s'             % (mari.gl_render.isPostProcessingEnabled(),
1187                                                                                                    ocio.enabled_default))
1188        filter_collection = mari.gl_render.currentPostFilterCollection()
1189        if filter_collection is not None:
1190            ocio.printMessage(ocio.MessageType.INFO, '     Profile: %s; Default: %s'             % (filter_collection.name(),
1191                                                                                                    ocio.profile_default))
1192        else:
1193            ocio.printMessage(ocio.MessageType.INFO, '     Profile: None; Default: %s'           % (ocio.profile_default))
1194        ocio.printMessage(    ocio.MessageType.INFO, '    LUT Path: %s; Default: %s'             % ('' if self._lut_file_list.isEmpty() else self._lut_file_list.at(0),
1195                                                                                                    '' if ocio.lut_file_list_default.isEmpty() else ocio.lut_file_list_default.at(0)))
1196        ocio.printMessage(    ocio.MessageType.INFO, ' Extrapolate: %s; Default: %s'             % (self._lut_extrapolate,
1197                                                                                                    ocio.lut_extrapolate_default))
1198        ocio.printMessage(    ocio.MessageType.INFO, ' Config Path: %s; Default: %s'             % ('' if self._config_file_list.isEmpty() else self._config_file_list.at(0),
1199                                                                                                    '' if ocio.config_file_list_default.isEmpty() else ocio.config_file_list_default.at(0)))
1200        ocio.printMessage(    ocio.MessageType.INFO, ' Color Space: %s; Default: %s'             % (self._color_space,
1201                                                                                                    ocio.color_space_default))
1202        ocio.printMessage(    ocio.MessageType.INFO, '     Display: %s; Default: %s'             % (self._display,
1203                                                                                                    ocio.display_default))
1204        ocio.printMessage(    ocio.MessageType.INFO, '        View: %s; Default: %s'             % (self._view,
1205                                                                                                    ocio.view_default))
1206        ocio.printMessage(    ocio.MessageType.INFO, '     Swizzle: %s; Default: %s'             % (self._swizzle,
1207                                                                                                    ocio.swizzle_default))
1208        ocio.printMessage(    ocio.MessageType.INFO, '      F-Stop: %f; Default: %f; Center: %f' % (ocio.convertGainToFStop(self._gain),
1209                                                                                                    ocio.convertGainToFStop(ocio.gain_default),
1210                                                                                                    ocio.fstop_center))
1211        ocio.printMessage(    ocio.MessageType.INFO, '        Gain: %f; Default: %f'             % (self._gain,
1212                                                                                                    ocio.gain_default))
1213        ocio.printMessage(    ocio.MessageType.INFO, '       Gamma: %f; Default: %f'             % (self._gamma,
1214                                                                                                    ocio.gamma_default))
1215        ocio.printMessage(    ocio.MessageType.INFO, '==============================================================')
1216
1217##############################################################################################
1218# The following functions CAN'T be part of the toolbar class as a potential bug in PythonQt
1219# causes the disconnect function to fail
1220
1221def lutMetadataValueChanged(name, value):
1222    global toolbar
1223
1224    ocio.printMessage(ocio.MessageType.DEBUG, 'LUT metadata \'%s\' changed to \'%s\'' % (name, value))
1225
1226    if name == 'File':
1227        toolbar.setLUTPath(value = '' if value.isEmpty() else value.at(0),
1228                           update_metadata = False,
1229                           force_shader_build = False)
1230
1231    elif name == 'Extrapolate':
1232        toolbar.setExtrapolateEnabled(value = value, update_widget = True, update_metadata = False)
1233
1234#-----------------------------------------------------------------------------------------
1235
1236def displayMetadataValueChanged(name, value):
1237    global toolbar
1238
1239    ocio.printMessage(ocio.MessageType.DEBUG, 'Display metadata \'%s\' changed to \'%s\'' % (name, value))
1240
1241    if name == 'ConfigPath':
1242        toolbar.setConfigPath(value = '' if value.isEmpty() else value.at(0), update_metadata = False)
1243
1244    elif name == 'InputColorSpace':
1245        toolbar.setColorSpace(value = value, update_widget = True, update_metadata = False)
1246
1247    elif name == 'Display':
1248        toolbar.setDisplay(value = value, update_widget = True, update_metadata = False)
1249
1250    elif name == 'View':
1251        toolbar.setView(value = value, update_widget = True, update_metadata = False)
1252
1253    elif name == 'Swizzle':
1254        toolbar.setSwizzle(value = value, update_widget = True, update_metadata = False)
1255
1256    elif name == 'Gain':
1257        toolbar.setGain(value = value, update_widget = True, update_metadata = False)
1258
1259    elif name == 'Gamma':
1260        toolbar.setGamma(value = value, update_widget = True, update_metadata = False)
1261
1262##############################################################################################
1263
1264if mari.app.isRunning():
1265    if not hasattr(mari.gl_render, 'createPostFilterCollection'):
1266        ocio.printMessage(ocio.MessageType.ERROR, 'This version of Mari does not support the mari.gl_render.createPostFilterCollection API')
1267
1268    else:
1269        if ocio.config_default is not None:
1270            toolbar = OcioToolBar()
1271
1272        else:
1273            # Destroy the OCIO post filter collection if present to prevent the user trying to use it.
1274            filter_collection = mari.gl_render.findPostFilterCollection('Color Space')
1275            if filter_collection is not None:
1276                mari.gl_render.deletePostFilterCollection(filter_collection)
1277
1278            # Destroy the toolbar to prevent the user trying to use it.
1279            mari.app.deleteToolBar('Color Space')
1280