diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..cc5e99a
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,24 @@
+## Description
+
+Explain what, why and the though process leading to the changes
+
+link to issue if relevant.
+
+### code changes
+- point form description of changes
+
+## Type of change
+
+is this a major or a minor or patch update ?
+
+## How Has This Been Tested?
+
+Describe operating system and Nuke versions
+
+## Checklist:
+
+- [ ] My code follows the style guidelines of this project
+- [ ] I have performed a self-review of my own code
+- [ ] I have commented my code, particularly in hard-to-understand areas
+- [ ] I have made corresponding changes to the documentation
+- [ ] My changes generate no new warnings
diff --git a/documentation/CMakeLists.txt b/documentation/CMakeLists.txt
index dead310..f30a41a 100644
--- a/documentation/CMakeLists.txt
+++ b/documentation/CMakeLists.txt
@@ -13,8 +13,8 @@ add_custom_target(documentation ALL
add_custom_target(documentation_package
DEPENDS documentation
COMMENT "creating documentation package for ${PROJECT_PACKAGE_NAME}"
- COMMAND ${CMAKE_COMMAND} -E tar "cfvz" "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}_documentation.tgz" "${CMAKE_CURRENT_BINARY_DIR}/documentation"
- COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}_documentation.tgz" "../"
+ COMMAND ${CMAKE_COMMAND} -E tar "cfvz" "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}_documentation.tar.gz" "${CMAKE_CURRENT_BINARY_DIR}/documentation"
+ COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_NAME}-${LAYER_ALCHEMY_VERSION_STRING}_documentation.tar.gz" "../"
)
install(
diff --git a/documentation/docs/GradeBeauty.md b/documentation/docs/GradeBeauty.md
index 424cd79..d099695 100644
--- a/documentation/docs/GradeBeauty.md
+++ b/documentation/docs/GradeBeauty.md
@@ -22,8 +22,8 @@
Lighters can use it with the light_group [LayerSet](core.md#layersets) to :
-* Tweak light group values and animations, (flicker matching)
-* Use two nodes, one in _multiply_ mode, and one in _stops_ mode to adhere to the exposure/gain decoupling
+* Tweak light group values or animations, (flicker matching)
+* Use two nodes, one in _multiply_ mode, and one in _stops_ mode to mimic to the exposure/gain decoupling
* Send the values back to their cg application !
Compositors:
@@ -42,6 +42,7 @@ Compositors:
| target_layer | enumeration | selects which layer to pre-subtract layers from (if enabled) and add the modified layers to |
| math_type | enumeration | selects the color knob preference
| subtract | bool | controls pre-subtracting the [LayerSet](core.md#layersets) from the target layer |
+| black_clamp | bool | clamp negative values from all output layers |
| reset values | button | resets all color knobs to their defaults |
## [PixelIop](https://learn.foundry.com/nuke/developers/11.3/ndkdevguide/2d/pixeliops.html) Knobs
@@ -61,7 +62,7 @@ Compositors:
### subtract
The purpose of the subtract knob is to make sure that the output will always match the beauty render, even if
-some layers are missing, yet included in the beauty render.
+some aov layers are missing in the beauty render.
This can happen, so it is enabled by default.
@@ -70,9 +71,24 @@ This can happen, so it is enabled by default.
| enabled | the additive sum of the chosen [LayerSet](core.md#layersets) is subtracted from the target layer before recombining with this node's modifications |
this means any difference between the target layer and the render layers is kept in the final output
| disabled | bypasses aov/target layer pre-subtraction | _when this is disabled, it replaces the target layer with the result_
+!!! info ""
+
+ If enabled and you completely remove layers, it's possible that you get negative values when
+ completely removing aovs AND subtracting. In this scenario, enable black_clamp
+
+### black_clamp
+The purpose of the black_clamp knob is to make sure that the output pixels always are alaways positive.
+
+| value | what it does | notes |
+| ----- | ------------ | ----- |
+| enabled | no negative values are passed to the output | in the odd case that you need to stop negative values this will do the job
+| disabled | bypasses aov/target layer negative clamping |
+
## Arnold 5 LayerSets
+LayerAlchemy includes arnold configurations by default
+
Arnold can separate the beauty render components in various ways.
The following type are natively supported with the [default](https://docs.arnoldrenderer.com/display/A5AFMUG/AOVs#AOVs-AOVs) aov naming :
diff --git a/documentation/docs/GradeBeautyLayer.md b/documentation/docs/GradeBeautyLayer.md
new file mode 100644
index 0000000..ec9dc02
--- /dev/null
+++ b/documentation/docs/GradeBeautyLayer.md
@@ -0,0 +1,22 @@
+# GradeBeautyLayer
+
+!!! info ""
+
+ GradeBeautyLayer provides a simple way to specifically grade a cg layer and replace it in the beauty
+
+#### Order of operations :
+- input layer subtracted from the target layer
+- layer is modified
+- modified source layer is added to the target layer
+
+
+
+
+## Knob reference
+
+| knob name | type | what it does |
+| --------- | ---- | ------------
+| source_layer | enumeration | layer to to grade |
+| target_layer | enumeration | layer to subtract and add the modied source_layer to |
+| reset values | button | resets all color knobs to their defaults |
+
diff --git a/documentation/docs/GradeBeautyLayerSet.md b/documentation/docs/GradeBeautyLayerSet.md
index 9bd3ed1..0a9f7ba 100644
--- a/documentation/docs/GradeBeautyLayerSet.md
+++ b/documentation/docs/GradeBeautyLayerSet.md
@@ -5,11 +5,6 @@
GradeBeautyLayerSet provides a simple way to specifically grade multiple cg layers using a [LayerSet](core
.md#layersets)
- it's a cross between :
-
- - [GradeLayerSet](GradeLayerSet.md)
- - [GradeBeauty](GradeBeauty.md)
- - [FlattenLayerSet](FlattenLayerSet.md)
Image processing math is exactly like the Nuke Grade node except that, you can grade multiple layers at the
@@ -30,5 +25,5 @@ same time
| mode name | what it does |
| --------- | ------------ |
-| copy | outputs only the addition of modified layers to the target layer |
+| copy | outputs only the modified layers to the target layer (added together)|
| add | this first subtracts all layers from the target layer, then adds each of them back
diff --git a/documentation/docs/about.md b/documentation/docs/about.md
index 7a8057c..b9057dc 100644
--- a/documentation/docs/about.md
+++ b/documentation/docs/about.md
@@ -4,10 +4,13 @@
| ---- | ---- | ----- | -------- |
| Sébastien Jacob | author, designer | [email](mailto:sebjacobvfx@gmail.com) | [LinkedIn](https://www.linkedin.com/in/s%C3%A9bastien-jacob-3b05112/)
-!!! info "special thanks 👍, for their help in kick starting the very first builds goes to :"
+# special thanks 👍
- - Jean-Christophe Morin
- - Gregory Starck
+- Charles Fleche
+- Christian Morin
+- Mathieu Dupuis
+- Jean-Christophe Morin
+- Gregory Starck
# Contributing
diff --git a/documentation/docs/core.md b/documentation/docs/core.md
index 6b99afd..35c9988 100644
--- a/documentation/docs/core.md
+++ b/documentation/docs/core.md
@@ -22,9 +22,8 @@ as they will will be referenced throughout the documentation
- limited mostly by the callback system to trigger behaviors.
- cannot be cloned in Nuke.
- - internally, the algorithm is spread out across multiple nodes that must be managed.
- - lots of knob setting python code, and workarounds are required.
- - no direct image processing possible.
+ - internally, the algorithm is spread out across multiple Nuke nodes that must be managed.
+ - lots python and expression code, workarounds are required.
- some knobs are c++ only.
- In general, the fewer the nodes, the faster the comp.
diff --git a/documentation/docs/media/parameters/GradeBeauty.png b/documentation/docs/media/parameters/GradeBeauty.png
index 4d57eb6..975b69f 100644
Binary files a/documentation/docs/media/parameters/GradeBeauty.png and b/documentation/docs/media/parameters/GradeBeauty.png differ
diff --git a/documentation/docs/media/parameters/GradeBeautyLayer.png b/documentation/docs/media/parameters/GradeBeautyLayer.png
new file mode 100644
index 0000000..738c4a1
Binary files /dev/null and b/documentation/docs/media/parameters/GradeBeautyLayer.png differ
diff --git a/documentation/docs/media/parameters/GradeBeauty_uncollapsed.png b/documentation/docs/media/parameters/GradeBeauty_uncollapsed.png
index 0071fc3..7b7ef50 100644
Binary files a/documentation/docs/media/parameters/GradeBeauty_uncollapsed.png and b/documentation/docs/media/parameters/GradeBeauty_uncollapsed.png differ
diff --git a/documentation/docs/tools.md b/documentation/docs/tools.md
index 3bf7d2e..4947562 100644
--- a/documentation/docs/tools.md
+++ b/documentation/docs/tools.md
@@ -11,13 +11,23 @@ This also runs at Nuke startup to make sure the config files are ok
ConfigTester 😷
-Simple executable to test if a yaml file can be loaded and a LayerMap object can be constructed
+Simple executable to validate yaml files
+
+LayerAlchemy 0.9.0
+https://github.com/sebjacob/LayerAlchemy
-Example usage: ConfigTester /path/to/config.yaml
+Example usage:
+
+ConfigTester --config /path/to/config.yaml
+ConfigTester --config /path/to/config1.yaml /path/to/config2.yaml
+Usage: ./ConfigTester [options]
+Options:
+ --config List of layer names to test (Required)
+ --quiet disable terminal output, return code only
```
```bash
-./ConfigTester $LAYER_ALCHEMY_LAYER_CONFIG
+./ConfigTester --config $LAYER_ALCHEMY_LAYER_CONFIG
✅ LayerAlchemy : valid configuration file /path/to/layers.yaml
```
diff --git a/documentation/mkdocs.yml b/documentation/mkdocs.yml
index 545bbd3..1965c3c 100644
--- a/documentation/mkdocs.yml
+++ b/documentation/mkdocs.yml
@@ -17,6 +17,7 @@ nav :
- Nuke plugins :
- GradeBeauty.md
- GradeBeautyLayerSet.md
+ - GradeBeautyLayer.md
- GradeLayerSet.md
- FlattenLayerSet.md
- RemoveLayerSet.md
diff --git a/icons/GradeBeautyLayer.png b/icons/GradeBeautyLayer.png
new file mode 100644
index 0000000..2fd3dc0
Binary files /dev/null and b/icons/GradeBeautyLayer.png differ
diff --git a/include/nuke/LayerSet.h b/include/nuke/LayerSet.h
index 88179de..c1c8c04 100644
--- a/include/nuke/LayerSet.h
+++ b/include/nuke/LayerSet.h
@@ -30,8 +30,12 @@ namespace LayerSet {
namespace Utilities {
void hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::ChannelSet channels, DD::Image::Row& toRow);
float* hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::Channel channel, DD::Image::Row& toRow);
+ // centralized pixel engine code for Grade type plugins
+ void gradeChannelPixelEngine(const DD::Image::Row& in, int y, int x, int r, DD::Image::ChannelSet& channels, DD::Image::Row& aRow, float* A, float* B, float* G, bool reverse, bool clampBlack, bool clampWhite);
// use to validate if a target layer the user selects is within the required color ranges
void validateTargetLayerColorIndex(DD::Image::Op* t_op, const DD::Image::ChannelSet& targetLayer, unsigned minIndex, unsigned maxIndex);
+ // test float value for pow functions, NDK states that linux behaves badly for very large or very small exponent values.
+ float validateGammaValue(const float& gammaValue);
} // End namespace Utilities
namespace Knobs {
diff --git a/include/version.h b/include/version.h
index c2bd15b..0585cf9 100644
--- a/include/version.h
+++ b/include/version.h
@@ -1,6 +1,6 @@
#pragma once
#define LAYER_ALCHEMY_VERSION_MAJOR 0
-#define LAYER_ALCHEMY_VERSION_MINOR 8
+#define LAYER_ALCHEMY_VERSION_MINOR 9
#define LAYER_ALCHEMY_VERSION_PATCH 0
#include
diff --git a/python/nuke/init.py b/python/nuke/init.py
index cc49929..c216cee 100644
--- a/python/nuke/init.py
+++ b/python/nuke/init.py
@@ -4,5 +4,6 @@
import layer_alchemy.callbacks
if layer_alchemy.utilities.nukeVersionCompatible():
+ layer_alchemy.utilities.validateConfigFileEnvironmentVariables()
layer_alchemy.utilities.pluginAddPaths()
layer_alchemy.callbacks.setupCallbacks()
diff --git a/python/nuke/layer_alchemy/callbacks.py b/python/nuke/layer_alchemy/callbacks.py
index c61b91e..3e36743 100644
--- a/python/nuke/layer_alchemy/callbacks.py
+++ b/python/nuke/layer_alchemy/callbacks.py
@@ -1,61 +1,41 @@
"""LayerAlchemy callback module"""
-import os
import nuke
-import nukescripts
-import utilities
import constants
def setupCallbacks():
"""
Utility function to add all callbacks for plugins in this suite.
- Adds knobChanged and autolabel callbacks
"""
- for pluginName in constants.LAYER_ALCHEMY_PLUGINS.keys():
- nuke.addKnobChanged(
- lambda: knobChangedCommon(nuke.thisNode(), nuke.thisKnob()),
- nodeClass=pluginName
- )
- nuke.addAutolabel(autolabel, nodeClass=pluginName)
+ for pluginName in constants.LAYER_ALCHEMY_PLUGIN_NAMES:
+ nuke.addAutolabel(_autolabel, nodeClass=pluginName)
+ pass
-def knobChangedCommon(node, knob):
- """
- Common knobChanged function for plugins in this suite
- :param node: the Nuke node object
- :type node: :class:`nuke.Node`
- :param knob: the Nuke knob object
- :type knob: :class:`nuke.Knob`
- """
- if knob.name() == 'docButton':
- documentationIndex = utilities.getDocumentationIndexPath()
- if not documentationIndex:
- message = 'Local documentation is unavailable, please visit :\n\n{website}'.format(
- website=constants.LAYER_ALCHEMY_URL)
- nuke.message(message)
- return
- pluginDocFileName = '{0}.{1}'.format(node.Class(), 'html')
- htmlFile = os.path.join(os.path.dirname(documentationIndex), pluginDocFileName)
- outputPath = htmlFile if os.path.isfile(htmlFile) else documentationIndex
- nukescripts.start(outputPath)
-
-
-def autolabel():
+def _autolabel():
"""
Common autolabel function for plugins in this suite
+ :return: the formatted text to use as a label
+ :rtype: str
"""
node = nuke.thisNode()
nodeName = node.name()
- layerSetKnob = node.knob('layer_set')
- index = int(layerSetKnob.getValue())
- layerSetName = layerSetKnob.enumName(index)
+ text = []
+ for knobName in ('layer_set', 'channels', 'maskChannelInput', 'unpremult'):
+ knob = node.knob(knobName)
+ if knob:
+ index = int(knob.getValue())
+ value = knob.enumName(index) if isinstance(knob, nuke.Enumeration_Knob) else knob.value()
+ if value and value != 'none':
+ text.append(value)
+
node.knob('indicators').setValue(_getIndicatorValue(node))
- if layerSetName:
- return '{name}\n({layerSet})'.format(name=nodeName, layerSet=layerSetName)
+ if text:
+ return '{name}\n({layers})'.format(name=nodeName, layers=' / '.join(text))
else:
return nodeName
@@ -71,8 +51,11 @@ def _getIndicatorValue(node):
indicators = 0
knobs = node.allKnobs()
mixKnob = node.knob('mix')
+ maskKnob = node.knob('maskChannelInput')
if mixKnob and mixKnob.value() != 1:
indicators += 16
+ if maskKnob and maskKnob.getValue() != 0:
+ indicators += 4
if node.clones():
indicators += 8
if any(knob.isAnimated() for knob in knobs):
diff --git a/python/nuke/layer_alchemy/constants.py b/python/nuke/layer_alchemy/constants.py
index 013ad79..ff47f8f 100644
--- a/python/nuke/layer_alchemy/constants.py
+++ b/python/nuke/layer_alchemy/constants.py
@@ -2,22 +2,23 @@
import os
-LAYER_ALCHEMY_PLUGINS = { # The name of the plugin and its icon name
- 'GradeBeauty': 'GradeBeauty.png',
- 'GradeBeautyLayerSet': 'GradeBeautyLayerSet.png',
- 'FlattenLayerSet': 'FlattenLayerSet.png',
- 'RemoveLayerSet': 'RemoveLayerSet.png',
- 'MultiplyLayerSet': 'MultiplyLayerSet.png',
- 'GradeLayerSet': 'GradeLayerSet.png',
-}
+LAYER_ALCHEMY_URL = 'https://github.com/sebjacob/LayerAlchemy'
+
+LAYER_ALCHEMY_PLUGIN_NAMES = [
+ 'GradeBeauty',
+ 'GradeBeautyLayerSet',
+ 'GradeBeautyLayer',
+ 'GradeLayerSet',
+ 'MultiplyLayerSet',
+ 'RemoveLayerSet',
+ 'FlattenLayerSet'
+]
LAYER_ALCHEMY_CONFIGS_DICT = {
'LAYER_ALCHEMY_LAYER_CONFIG': 'layers.yaml',
'LAYER_ALCHEMY_CHANNEL_CONFIG': 'channels.yaml'
}
-LAYER_ALCHEMY_URL = 'https://github.com/sebjacob/LayerAlchemy'
-
_thisDir = os.path.dirname(os.path.realpath(__file__))
_layerAlchemyNukeDir = os.path.abspath(os.path.join(_thisDir, '..'))
diff --git a/python/nuke/layer_alchemy/documentation.py b/python/nuke/layer_alchemy/documentation.py
new file mode 100644
index 0000000..b026bbb
--- /dev/null
+++ b/python/nuke/layer_alchemy/documentation.py
@@ -0,0 +1,79 @@
+"""shared documentation/help module for LayerAlchemy"""
+
+import os
+
+import nuke
+
+import constants
+import utilities
+
+if nuke.GUI:
+ if nuke.NUKE_VERSION_MAJOR > 10:
+ from PySide2.QtWebEngineWidgets import QWebEngineView as qWebview
+ from PySide2.QtCore import QUrl
+ from PySide2.QtWidgets import QApplication
+ else:
+ from PySide.QtWebKit import QWebView as qWebview
+ from PySide.QtCore import QUrl
+ from PySide.QtGui import QApplication
+
+
+def documentationPath(node=None):
+ """
+ find an absolute path to the project documentation, or the html file for the chosen node
+ :param node: the Nuke node object
+ :type node: :class:`nuke.Node`
+ :return: absolute html file path
+ :rtype: str
+ :raises ValueError if absolutely no html file can be found
+ """
+ documentationIndexPath = utilities.getDocumentationIndexPath()
+ htmlFile = documentationIndexPath
+ if not documentationIndexPath or not os.path.exists(documentationIndexPath):
+ message = 'Local documentation is unavailable, please visit :\n\n{website}'.format(
+ website=constants.LAYER_ALCHEMY_URL)
+ if nuke.GUI:
+ nuke.message(message)
+ raise ValueError(message)
+ if node:
+ pluginDocFileName = '{0}.html'.format(node.Class())
+ htmlFile = os.path.join(os.path.dirname(documentationIndexPath), pluginDocFileName)
+ if not os.path.isfile(htmlFile):
+ htmlFile = documentationIndexPath
+ return htmlFile
+
+
+def documentationWebViewWidget(documentationPath):
+ """
+ Creates a PySide web view Widget
+ :param str documentationPath: the absolute path to the html file to display
+ :return: a PySide web view widget set up with the local documentation
+ :rtype: PySide2.QtWebEngineWidgets.QWebEngineView
+ """
+ webView = qWebview()
+ webView.setWindowTitle('LayerAlchemy Documentation')
+ qUrl = QUrl.fromLocalFile(documentationPath)
+ webView.load(qUrl)
+ screenGeo = QApplication.desktop().geometry()
+ webView.setMinimumHeight(screenGeo.height() / 2)
+ center = screenGeo.center()
+ webView.move(center - webView.rect().center())
+ return webView
+
+
+def displayDocumentation(node=None):
+ """
+ Wrapper function to display documentation in Nuke as a PySide2.QtWebEngineWidgets.QWebEngineView
+ Due to a bug in Nuke 11, the documentation will be displayed using the web browser
+ https://support.foundry.com/hc/en-us/articles/360000148684-Q100379-Importing-PySide2-QtWebEngine-into-Nuke-11
+ :param node: the Nuke node object
+ :type node: :class:`nuke.Node`
+ :return: a PySide web view widget set up with the local documentation
+ :rtype: PySide2.QtWebEngineWidgets.QWebEngineView
+ """
+ htmlFile = documentationPath(node)
+ if nuke.env['NukeVersionMajor'] == 11:
+ import nukescripts
+ nukescripts.start(htmlFile)
+ else:
+ return documentationWebViewWidget(htmlFile)
diff --git a/python/nuke/layer_alchemy/utilities.py b/python/nuke/layer_alchemy/utilities.py
index b6a0556..264446b 100644
--- a/python/nuke/layer_alchemy/utilities.py
+++ b/python/nuke/layer_alchemy/utilities.py
@@ -10,10 +10,8 @@
def pluginAddPaths():
"""
- This validates that configuration files are present and valid
- and adds the icon and plugin directories to Nuke's pluginPath
+ adds the icon and plugin directories to Nuke's pluginPath
"""
- validateConfigFileEnvironmentVariables()
nuke.pluginAddPath(constants.LAYER_ALCHEMY_ICON_DIR)
nuke.pluginAddPath(_getPluginDirForCurrentNukeVersion())
@@ -67,9 +65,9 @@ def _validateConfigFile(configFilePath):
"""
if not os.path.isfile(configFilePath):
raise ValueError('missing configuration file')
- error = subprocess.call([constants.LAYER_ALCHEMY_CONFIGTESTER_BIN, configFilePath])
- if error:
- raise ValueError('invalid configuration file')
+ return subprocess.call(
+ [constants.LAYER_ALCHEMY_CONFIGTESTER_BIN, '--config', configFilePath, '--quiet']
+ )
def validateConfigFileEnvironmentVariables():
@@ -83,4 +81,6 @@ def validateConfigFileEnvironmentVariables():
if not configFile:
configFile = os.path.join(constants.LAYER_ALCHEMY_CONFIGS_DIR, baseName)
os.environ[envVarName] = configFile
- _validateConfigFile(configFile)
+ error = _validateConfigFile(configFile)
+ if error:
+ raise ValueError('invalid configuration file {}'.format(configFile))
diff --git a/python/nuke/menu.py b/python/nuke/menu.py
index c2b0bba..e69978e 100644
--- a/python/nuke/menu.py
+++ b/python/nuke/menu.py
@@ -2,22 +2,50 @@
import nuke
-import layer_alchemy.constants
import layer_alchemy.utilities
if layer_alchemy.utilities.nukeVersionCompatible():
toolbar = nuke.menu("Nodes")
menu = toolbar.addMenu("LayerAlchemy", icon="layer_alchemy.png", index=-1)
- for pluginName, icon in sorted(layer_alchemy.constants.LAYER_ALCHEMY_PLUGINS.items()):
- menu.addCommand(name=pluginName,
- command="nuke.createNode('{}')".format(pluginName),
- icon=icon)
+ menu.addCommand(name='GradeBeauty',
+ command="nuke.createNode('GradeBeauty')",
+ icon="GradeBeauty.png"
+ )
+ menu.addCommand(name='GradeBeautyLayerSet',
+ command="nuke.createNode('GradeBeautyLayerSet')",
+ icon="GradeBeautyLayerSet.png"
+ )
+ menu.addCommand(name='GradeBeautyLayer',
+ command="nuke.createNode('GradeBeautyLayer')",
+ icon="GradeBeautyLayer.png"
+ )
+ menu.addSeparator()
+ menu.addCommand(name='GradeLayerSet',
+ command="nuke.createNode('GradeLayerSet')",
+ icon="GradeLayerSet.png"
+ )
+ menu.addCommand(name='MultiplyLayerSet',
+ command="nuke.createNode('MultiplyLayerSet')",
+ icon="MultiplyLayerSet.png"
+ )
+ menu.addSeparator()
+ menu.addCommand(name='FlattenLayerSet',
+ command="nuke.createNode('FlattenLayerSet')",
+ icon="FlattenLayerSet.png"
+ )
+ menu.addCommand(name='RemoveLayerSet',
+ command="nuke.createNode('RemoveLayerSet')",
+ icon="RemoveLayerSet.png"
+ )
menu.addSeparator()
menu.addCommand(name="documentation",
- command=("import layer_alchemy;import nuke;import nukescripts;"
- "nukescripts.start(layer_alchemy.utilities.getDocumentationIndexPath())"),
+ command=(
+ "import layer_alchemy.documentation\n"
+ "webview = layer_alchemy.documentation.displayDocumentation(node=None)\n"
+ "if webview:\n"
+ " webview.show()"),
icon="documentation.png"
)
diff --git a/src/ConfigTester.cpp b/src/ConfigTester.cpp
index 0fcdd63..56dca75 100644
--- a/src/ConfigTester.cpp
+++ b/src/ConfigTester.cpp
@@ -3,11 +3,13 @@
* usage example: ConfigTester /path/to/config.yaml
*/
-#include
#include
#include
+#include "argparse.h"
+
#include "LayerSetCore.h"
+#include "version.h"
static const string greenText = "\x1B[92m";
static const string redText = "\x1B[31m";
@@ -16,39 +18,76 @@ static const string emojiOk = "\xE2\x9C\x85 ";
static const string emojiMedical = "\xF0\x9F\x98\xB7 ";
static const string emojiError = "\xE2\x9D\x97 ";
-int main(int argc, char** argv) {
- if (argc <= 1) {
- string usage =
- "\nConfigTester " + emojiMedical + "\n\n"
- "Simple executable to test if a yaml file can be loaded "
- "and a LayerMap object can be constructed\n\n"
- "Example usage: ConfigTester /path/to/config.yaml\n";
- std::cout << usage << std::endl;
- return 1;
- }
- if (argc >= 3) {
- std::cerr << emojiError << redText << "One config at a time!" << std::endl << endColor;
- return 1;
+static const std::string DESCRIPTION = "Simple executable to validate yaml files";
+static const string LAYER_ALCHEMY_PROJECT_URL = "https://github.com/sebjacob/LayerAlchemy";
+
+static const string HEADER =
+ "\nConfigTester " + emojiMedical + "\n\n" + DESCRIPTION + "\n\n"
+ "LayerAlchemy " + LAYER_ALCHEMY_VERSION_STRING + "\n" +
+ LAYER_ALCHEMY_PROJECT_URL + "\n\n"
+ "Example usage: \n\nConfigTester --config /path/to/config.yaml\n"
+ "ConfigTester --config /path/to/config1.yaml /path/to/config2.yaml";
+
+
+void logException(const char* filePath, const std::exception& e)
+{
+ std::cerr << emojiError << redText << "[ERROR] LayerAlchemy : " << filePath << e.what() << std::endl << endColor;
+}
+
+int main(int argc, const char* argv[])
+{
+ ArgumentParser parser(DESCRIPTION);
+ parser.add_argument("--config", "List of layer names to test", true);
+ parser.add_argument("--quiet", "disable terminal output, return code only", false);
+
+ try
+ {
+ parser.parse(argc, argv);
}
- string strFilePath = reinterpret_cast (argv[1]);
- std::ifstream inputFile(strFilePath);
- if (!inputFile || opendir(strFilePath.c_str())) {
- std::cerr << emojiError << redText <<
- "LayerAlchemy ERROR : not a file : " << strFilePath
- << std::endl << endColor;
- return 1;
+ catch (const ArgumentParser::ArgumentNotFound &ex)
+ {
+ std::cout << HEADER << std::endl;
+ parser.print_help();
+
+ std::cout << ex.what() << std::endl;
+ return 0;
}
- try {
- LayerMap testLayerMap = LayerMap(loadConfigToMap(strFilePath));
+ if (parser.is_help())
+ return 0;
+
+ auto configs = parser.getv("config");
+ bool quiet = parser.get("quiet");
+
+ for(auto it = configs.begin(); it != configs.end(); ++it)
+ {
+ auto configFilePath = it->c_str();
+ std::ifstream inputFile(configFilePath);
- } catch (const std::exception& e) {
- std::cerr << emojiError << redText <<
- "LayerAlchemy ERROR : configuration file " << strFilePath << " " << e.what()
+ if (!inputFile || opendir(configFilePath))
+ {
+ if (!quiet)
+ {
+ logException(configFilePath, std::invalid_argument(" is not a file"));
+ }
+ return 1;
+ }
+ try
+ {
+ LayerMap testLayerMap = LayerMap(loadConfigToMap(configFilePath));
+ if (!quiet)
+ {
+ std::cout << greenText << emojiOk << "LayerAlchemy : valid configuration file " << configFilePath
<< std::endl << endColor;
- return 1;
+ }
+ }
+ catch (const std::exception& e)
+ {
+ if (!quiet)
+ {
+ logException(configFilePath, e);
+ }
+ return 1;
+ }
}
- std::cout << greenText << emojiOk <<
- "LayerAlchemy : valid configuration file " << strFilePath
- << std::endl << endColor;
return 0;
}
diff --git a/src/nuke/CMakeLists.txt b/src/nuke/CMakeLists.txt
index a457119..70016b6 100644
--- a/src/nuke/CMakeLists.txt
+++ b/src/nuke/CMakeLists.txt
@@ -72,6 +72,9 @@ list(APPEND NUKE_PLUGINS GradeBeauty)
add_library(GradeBeautyLayerSet SHARED ${PROJECT_NUKE_SRC_DIR}/GradeBeautyLayerSet.cpp)
list(APPEND NUKE_PLUGINS GradeBeautyLayerSet)
+add_library(GradeBeautyLayer SHARED ${PROJECT_NUKE_SRC_DIR}/GradeBeautyLayer.cpp)
+list(APPEND NUKE_PLUGINS GradeBeautyLayer)
+
foreach(PLUGIN ${NUKE_PLUGINS})
set_target_properties(${PLUGIN} PROPERTIES PREFIX "")
target_link_libraries(${PLUGIN} ${LAYERSET_LIBS} ${NUKE_LAYERSET_LIBS} ${LIB_DDIMAGE})
diff --git a/src/nuke/FlattenLayerSet.cpp b/src/nuke/FlattenLayerSet.cpp
index cdf90ba..9b31d22 100644
--- a/src/nuke/FlattenLayerSet.cpp
+++ b/src/nuke/FlattenLayerSet.cpp
@@ -102,6 +102,7 @@ void FlattenLayerSet::_validate(bool for_real)
updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, excludeLayerFilter);
}
set_out_channels(activeChannelSet());
+ info_.turn_on(m_targetLayer);
}
void FlattenLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out)
diff --git a/src/nuke/GradeBeauty.cpp b/src/nuke/GradeBeauty.cpp
index 53d9f46..7eba485 100644
--- a/src/nuke/GradeBeauty.cpp
+++ b/src/nuke/GradeBeauty.cpp
@@ -1,4 +1,5 @@
-#include "math.h"
+#include
+#include
#include
#include
@@ -149,8 +150,8 @@ class GradeBeautyValueMap {
return ptrValueMap.find(knobName)->second[0]; // the first index if where the layer pointer is.
}
- // computes, for a given layer name, the total value to multiply the pixels with at a specific color index for any given
- // math mode
+ // computes, for a given layer name, the total value to multiply the pixels with
+ // at a specific color index for any given math mode
float getLayerMultiplier(const string& layerName, const int& colorIndex, const int& mode) const
{
vector values = layerValuePointersForIndex(layerName, colorIndex);
@@ -188,8 +189,10 @@ class GradeBeautyValueMap {
class GradeBeauty : public DD::Image::PixelIop {
private:
+ bool m_firstRun {true};
LayerAlchemy::LayerSetKnob::LayerSetKnobData m_lsKnobData;
int m_mathMode {GRADE_BEAUTY_MATH_MODE::STOPS};
+ bool m_clampBlack {true};
bool m_beautyDiff {true};
ChannelSet m_targetLayer {Mask_RGB};
GradeBeautyValueMap m_valueMap;
@@ -226,6 +229,7 @@ class GradeBeauty : public DD::Image::PixelIop {
void channelPixelEngine(const Row&, int, int, int, ChannelMask, Row&);
// pixel engine function when the target layer is requested to render
void beautyPixelEngine(const Row&, int y, int x, int r, ChannelSet&, Row&);
+ GradeBeauty* firstGradeBeauty();
};
@@ -244,6 +248,11 @@ const Iop::Description GradeBeauty::description(
build
);
+GradeBeauty* GradeBeauty::firstGradeBeauty()
+{
+ return static_cast( this->firstOp() );
+}
+
void GradeBeauty::in_channels(int input_number, ChannelSet& mask) const
{
mask += ChannelMask(activeChannelSet());
@@ -262,7 +271,6 @@ ChannelSet GradeBeauty::activeChannelSet() const
return outChans;
}
-
void GradeBeauty::_validate(bool for_real)
{
copy_info(); // this copies the input info to the output
@@ -280,29 +288,35 @@ void GradeBeauty::_validate(bool for_real)
calculateLayerValues(m_lsKnobData.m_selectedChannels, m_valueMap);
set_out_channels(activeChannelSet());
+ info_.turn_on(m_targetLayer);
}
void GradeBeauty::channelPixelEngine(const Row& in, int y, int x, int r, ChannelMask channels, Row& aRow)
{
- map aovPtrIdxMap;
+ map aovFloatPtrChannelMap;
foreach(channel, channels)
{
LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow);
- aovPtrIdxMap[channel] = aRow.writable(channel);
+ aovFloatPtrChannelMap[channel] = aRow.writable(channel);
}
foreach(channel, channels)
{
unsigned chanIdx = colourIndex(channel);
string layerName = getLayerName(channel);
- float* outAovValue = aovPtrIdxMap[channel];
+ float* outAovValue = aovFloatPtrChannelMap[channel];
const float* inAovValue = in[channel];
float multValue = m_valueMap.multipliers[channel];
for (unsigned X = x; X < r; X++) {
float origValue = inAovValue[X];
- outAovValue[X] = origValue * multValue;
+ if (m_clampBlack)
+ {
+ outAovValue[X] = std::max(0.0f, (origValue * multValue));
+ } else {
+ outAovValue[X] = origValue * multValue;
+ }
}
}
}
@@ -313,8 +327,8 @@ void GradeBeauty::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelS
ChannelSet aovs = m_lsKnobData.m_selectedChannels.intersection(channels);
map btyPtrIdxMap;
- map aovPtrIdxMap;
- map aovInPtrIdxMap;
+ map aovFloatPtrChannelMap;
+ map aovConstFloatPtrChannelMap;
foreach(channel, bty) {
unsigned chanIdx = colourIndex(channel);
@@ -331,8 +345,8 @@ void GradeBeauty::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelS
}
foreach(channel, aovs) {
- aovPtrIdxMap[channel] = aRow.writable(channel);
- aovInPtrIdxMap[channel] = in[channel];
+ aovFloatPtrChannelMap[channel] = aRow.writable(channel);
+ aovConstFloatPtrChannelMap[channel] = in[channel];
}
for (const auto& kvp : btyPtrIdxMap)
@@ -348,8 +362,8 @@ void GradeBeauty::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelS
continue;
}
float* aRowBty = btyPtrIdxMap[aovChanIdx];
- float* aRowAov = aovPtrIdxMap[aov];
- const float* inAov = aovInPtrIdxMap[aov];
+ float* aRowAov = aovFloatPtrChannelMap[aov];
+ const float* inAov = aovConstFloatPtrChannelMap[aov];
for (int X = x; X < r; X++)
{
@@ -360,7 +374,8 @@ void GradeBeauty::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelS
float aovInPixel = inAov[X];
btyPixel -= aovInPixel;
}
- aRowBty[X] = btyPixel + aovPixel;
+ float result = btyPixel + aovPixel;
+ aRowBty[X] = m_clampBlack ? std::max(0.0f, result) : result;
}
}
}
@@ -375,9 +390,9 @@ void GradeBeauty::pixel_engine(const Row& in, int y, int x, int r, ChannelMask c
bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size();
if (isTargetLayer)
{
- channelPixelEngine(in, y, x, r, activeChannels, aRow);
+ channelPixelEngine(in, y, x, r, m_lsKnobData.m_selectedChannels, aRow);
beautyPixelEngine(in, y, x, r, activeChannels, aRow);
- }
+ }
else
{
channelPixelEngine(in, y, x, r, inChannels, aRow);
@@ -417,30 +432,40 @@ void GradeBeauty::knobs(Knob_Callback f)
"(this means any difference between the target layer "
"and the render layers is kept in the final output)
");
+ Bool_knob(f, &m_clampBlack, "black_clamp", "black clamp");
+ Tooltip(f,
+ "enabled : clamp negative values from all output layers
"
+ "disabled : negative values permitted
");
+
Divider(f, 0); // separates master from the rest
Knob* masterKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(MASTER_KNOB_NAME), MASTER_KNOB_NAME, true);
Tooltip(f, "this knob contributes to each layer");
- if (!colorKnobVectorComplete) {
+ if (!colorKnobVectorComplete)
+ {
m_valueMap.addColorKnob(masterKnob);
}
Divider(f, 0); // separates master from the rest
- for (auto iterLayerName = layers::nonShading.begin(); iterLayerName != layers::nonShading.end(); iterLayerName++) {
+ for (auto iterLayerName = layers::nonShading.begin(); iterLayerName != layers::nonShading.end(); iterLayerName++)
+ {
Knob* aovKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(*iterLayerName), *iterLayerName, false);
Tooltip(f, "applies to this layer or to layers in this layer set");
- if (!colorKnobVectorComplete) {
+ if (!colorKnobVectorComplete)
+ {
m_valueMap.addColorKnob(aovKnob);
}
}
BeginClosedGroup(f, "shading_group", "beauty shading layers");
SetFlags(f, Knob::HIDDEN);
- for (auto iterLayerName = layers::shading.begin(); iterLayerName != layers::shading.end(); iterLayerName++) {
+ for (auto iterLayerName = layers::shading.begin(); iterLayerName != layers::shading.end(); iterLayerName++)
+ {
Knob* aovKnob = createColorKnob(f, m_valueMap.getLayerFloatPointer(*iterLayerName), *iterLayerName, false);
Tooltip(f, "apply to this layer only");
- if (!colorKnobVectorComplete) {
+ if (!colorKnobVectorComplete)
+ {
m_valueMap.addColorKnob(aovKnob);
}
}
@@ -455,23 +480,27 @@ void GradeBeauty::knobs(Knob_Callback f)
EndToolbar(f);
}
-int GradeBeauty::knob_changed(Knob* k) {
- if (k->is("math_type")) {
+int GradeBeauty::knob_changed(Knob* k)
+{
+ if (k->is("math_type"))
+ {
setKnobRanges(m_mathMode, true);
setKnobDefaultValue(this);
- return 1;
}
- if (k == &DD::Image::Knob::inputChange) {
+ if (k == &DD::Image::Knob::inputChange)
+ {
_validate(true);
}
return 1;
}
-bool GradeBeauty::colorKnobsPopulated() const {
+bool GradeBeauty::colorKnobsPopulated() const
+{
return (m_valueMap.m_colorKnobs.size() >= (categories::all.size() + 1));
}
-Knob* GradeBeauty::createColorKnob(Knob_Callback f, float* valueStore, const string& name, const bool& visible) {
+Knob* GradeBeauty::createColorKnob(Knob_Callback f, float* valueStore, const string& name, const bool& visible)
+{
const char* knobName = name.c_str();
Knob* colorKnob = Color_knob(f, valueStore, IRange(COLOR_KNOB_RANGES[this->m_mathMode][0], COLOR_KNOB_RANGES[m_mathMode][1]), knobName, knobName);
SetFlags(f, Knob::LOG_SLIDER);
@@ -482,9 +511,11 @@ Knob* GradeBeauty::createColorKnob(Knob_Callback f, float* valueStore, const str
return colorKnob;
}
-void GradeBeauty::setKnobRanges(const int& modeValue, const bool& reset) {
+void GradeBeauty::setKnobRanges(const int& modeValue, const bool& reset)
+{
const char* script = (modeValue == 0 ? "{0}" : "{1}");
- for (auto iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++) {
+ for (auto iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++)
+ {
if (reset) {
(*iterKnob)->from_script(script);
}
@@ -492,49 +523,54 @@ void GradeBeauty::setKnobRanges(const int& modeValue, const bool& reset) {
}
}
-void GradeBeauty::setKnobVisibility() {
+void GradeBeauty::setKnobVisibility()
+{
bool isBeautyShading = LayerAlchemy::LayerSetKnob::getLayerSetKnobEnumString(this) == categories::shading;
auto layerNames = LayerAlchemy::LayerSet::getLayerNames(m_lsKnobData.m_selectedChannels);
LayerMap categorized = LayerAlchemy::layerCollection.categorizeLayers(layerNames, categorizeType::pub);
- for (vector::const_iterator iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++) {
+ for (vector::const_iterator iterKnob = m_valueMap.m_colorKnobs.begin(); iterKnob != m_valueMap.m_colorKnobs.end(); iterKnob++)
+ {
Knob* colorKnob = *iterKnob;
string knobName = colorKnob->name();
bool isGlobalLayer = categorized.contains(knobName);
bool contains = categorized.isMember("all", knobName);
-
if (knobName == MASTER_KNOB_NAME)
{
contains = true;
- }
+ }
else if (isBeautyShading && isGlobalLayer && categorized.contains(knobName))
{
contains = true;
}
- if (contains) {
+ if (contains)
+ {
colorKnob->clear_flag(Knob::DO_NOT_WRITE);
colorKnob->set_flag(Knob::ALWAYS_SAVE);
- // since hidden knobs can be revealed, make sure the values are correct
- if (m_valueMap.isDefault(colorKnob->name(), 1.0f) && m_mathMode == GRADE_BEAUTY_MATH_MODE::MULTIPLY && !colorKnob->is_animated()) {
- colorKnob->from_script("{1}");
- }
- } else {
+ } else // this is run on knobs that are not visible to the user
+ {
colorKnob->clear_flag(Knob::ALWAYS_SAVE);
colorKnob->set_flag(Knob::DO_NOT_WRITE);
+ if (firstGradeBeauty()->m_firstRun)
+ {
+ colorKnob->set_value(m_mathMode); // make sure the unsaved knobs are the correct default
+ }
}
- //printf("setKnobVisibility: knob name is %s and visible is %d is global %d \n", knobName.c_str(), contains, isGlobalLayer);
colorKnob->visible(contains);
}
knob("shading_group")->visible(isBeautyShading);
+ firstGradeBeauty()->m_firstRun = false;
}
-void GradeBeauty::setKnobDefaultValue(DD::Image::Op* nukeOpPtr) {
+void GradeBeauty::setKnobDefaultValue(DD::Image::Op* nukeOpPtr)
+{
nukeOpPtr->script_command(DEFAULT_VALUE_PYSCRIPT, true, false);
}
-void GradeBeauty::calculateLayerValues(const DD::Image::ChannelSet& channels, GradeBeautyValueMap& valueMap) {
+void GradeBeauty::calculateLayerValues(const DD::Image::ChannelSet& channels, GradeBeautyValueMap& valueMap)
+{
foreach(channel, channels) {
int chanIdx = colourIndex(channel);
string layerName = getLayerName(channel);
diff --git a/src/nuke/GradeBeautyLayer.cpp b/src/nuke/GradeBeautyLayer.cpp
new file mode 100644
index 0000000..07d2a56
--- /dev/null
+++ b/src/nuke/GradeBeautyLayer.cpp
@@ -0,0 +1,276 @@
+#include "math.h"
+
+#include
+#include
+
+#include "LayerSet.h"
+#include "LayerSetKnob.h"
+#include "GradeBeautyLayerSet.cpp"
+
+
+namespace GradeBeautyLayer {
+
+const char* const HELP =
+ "Grade node for cg multichannel beauty aovs
"
+ "order of operations is : \n\n"
+ "1 - subtract source layer from the target layer\n"
+ "2 - perform grade modification to the source layer\n"
+ "3 - add the modified source layer to the target layer\n";
+
+static const char* const layerNames[] = {
+ " ", 0
+};
+
+using namespace DD::Image;
+
+static const StrVecType all = {
+ "beauty_direct_indirect", "beauty_shading_global", "light_group", "beauty_shading"
+};
+static const CategorizeFilter CategorizeFilterAllBeauty(all, CategorizeFilter::modes::INCLUDE);
+
+class GradeBeautyLayer : public PixelIop {
+
+private:
+ float blackpoint[3]{0.0f, 0.0f, 0.0f};
+ float whitepoint[3]{1.0f, 1.0f, 1.0f};
+ float lift[3]{0.0f, 0.0f, 0.0f};
+ float gain[3]{1.0f, 1.0f, 1.0f};
+ float offset[3]{0.0f, 0.0f, 0.0f};
+ float multiply[3]{1.0f, 1.0f, 1.0f};
+ float gamma[3]{1.0f, 1.0f, 1.0f};
+ bool reverse{false};
+ bool clampBlack{true};
+ bool clampWhite{false};
+ ChannelSet m_targetLayer{Mask_RGB};
+ ChannelSet m_sourceLayer{Mask_None};
+ ChannelSet m_selectedLayers;
+
+ // intermediate grade algorithm storage
+ float A[3]{0.0f, 0.0f, 0.0f};
+ float B[3]{0.0f, 0.0f, 0.0f};
+ float G[3]{0.0f, 0.0f, 0.0f};
+
+public:
+ void knobs(Knob_Callback);
+ void _validate(bool for_real);
+ bool pass_transform() const {return true;}
+ void in_channels(int, ChannelSet& channels) const;
+ void pixel_engine(const Row&, int, int, int, ChannelMask, Row&);
+ int knob_changed(Knob*);
+ const char* Class() const {return description.name;}
+ const char* node_help() const {return HELP;}
+ static const Iop::Description description;
+ // channel set that contains all channels that are modified by the node
+ ChannelSet activeChannelSet() const;
+ // pixel engine function when anything but the target layer is requested to render
+ void channelPixelEngine(const Row&, int, int, int, ChannelSet&, Row&);
+ // pixel engine functon when the target layer is requested to render
+ void beautyPixelEngine(const Row&, int y, int x, int r, ChannelSet&, Row&);
+ // This function calculates and stores the grade algorithm's intermediate calculations
+ bool precomputeValues();
+ GradeBeautyLayer(Node* node);
+ ~GradeBeautyLayer();
+};
+
+GradeBeautyLayer::GradeBeautyLayer(Node* node) : PixelIop(node)
+{
+ precomputeValues();
+}
+
+static Op* build(Node* node)
+{
+ return (new NukeWrapper(new GradeBeautyLayer(node)))->noChannels()->mixLuminance();
+}
+
+GradeBeautyLayer::~GradeBeautyLayer() {}
+
+const Iop::Description GradeBeautyLayer::description(
+ "GradeBeautyLayer",
+ "LayerAlchemy/GradeBeautyLayer",
+ build
+);
+
+ChannelSet GradeBeautyLayer::activeChannelSet() const
+{
+ ChannelSet outChans = ChannelSet(m_targetLayer + m_sourceLayer);
+ return outChans;
+}
+
+void GradeBeautyLayer::in_channels(int input_number, ChannelSet& mask) const {
+ mask += activeChannelSet();
+}
+
+bool GradeBeautyLayer::precomputeValues() {
+ bool changeZero = false;
+ for (unsigned int chanIdx = 0; chanIdx < 3; chanIdx++) {
+ float a = whitepoint[chanIdx] - blackpoint[chanIdx];
+ a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f;
+ a *= multiply[chanIdx];
+ float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a;
+ float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]);
+ A[chanIdx] = a;
+ B[chanIdx] = b;
+ G[chanIdx] = g;
+ if (a != 1.0f || b != 0.0f || g != 1.0f)
+ {
+ if (b)
+ {
+ changeZero = true;
+ }
+ }
+ }
+ return changeZero;
+}
+
+void GradeBeautyLayer::_validate(bool for_real) {
+ copy_info(); // this copies the input info to the output
+ bool changeZero = precomputeValues();
+ info_.black_outside(!changeZero);
+ ChannelSet inChannels = info_.channels();
+ LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2);
+ m_selectedLayers = activeChannelSet();
+ set_out_channels(m_selectedLayers);
+ info_.turn_on(m_targetLayer);
+}
+void GradeBeautyLayer::channelPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow)
+{
+ LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite);
+}
+void GradeBeautyLayer::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow)
+{
+ ChannelSet bty = m_targetLayer.intersection(channels);
+ ChannelSet aovs = m_sourceLayer.intersection(channels);
+
+ map btyPtrIdxMap;
+ map aovPtrIdxMap;
+ map aovInPtrIdxMap;
+
+ foreach(channel, bty) {
+ unsigned chanIdx = colourIndex(channel);
+ float* rowBtyChan;
+ LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow);
+ rowBtyChan = aRow.writable(channel);
+ btyPtrIdxMap[chanIdx] = rowBtyChan;
+
+ }
+ foreach(channel, aovs) {
+ aovPtrIdxMap[channel] = aRow.writable(channel);
+ aovInPtrIdxMap[channel] = in[channel];
+ }
+
+ for (const auto& kvp : btyPtrIdxMap)
+ {
+ unsigned btyChanIdx = kvp.first;
+ float* aRowBty = kvp.second;
+
+ foreach(aov, aovs)
+ {
+ unsigned aovChanIdx = colourIndex(aov);
+ if (btyChanIdx != aovChanIdx)
+ {
+ continue;
+ }
+
+ float* aRowBty = btyPtrIdxMap[aovChanIdx];
+ float* aRowAov = aovPtrIdxMap[aov];
+ const float* inAov = aovInPtrIdxMap[aov];
+ for (int X = x; X < r; X++)
+ {
+ float aovPixel = aRowAov[X];
+ float btyPixel = aRowBty[X];
+ float aovInPixel = inAov[X];
+ btyPixel -= aovInPixel;
+ float resultPixel = btyPixel + aovPixel;
+ aRowBty[X] = btyPixel + aovPixel;
+ }
+ }
+ // clamp
+ if (clampWhite || clampBlack) {
+ for (int X = x; X < r; X++)
+ {
+ float btyPixel = aRowBty[X];
+
+ if (btyPixel < 0.0f && clampBlack)
+ {
+ btyPixel = 0.0f;
+ }
+ else if (btyPixel > 1.0f && clampWhite)
+ {
+ btyPixel = 1.0f;
+ }
+ aRowBty[X] = btyPixel;
+ }
+ }
+ }
+}
+
+void GradeBeautyLayer::pixel_engine(const Row& in, int y, int x, int r, ChannelMask channels, Row& out) {
+ ChannelSet inChannels = ChannelSet(channels);
+ ChannelSet activeChannels = m_selectedLayers;
+ Row aRow(x, r);
+ bool isTargetLayer = m_targetLayer.intersection(inChannels).size() == m_targetLayer.size();
+
+ if (isTargetLayer)
+ {
+ LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, m_sourceLayer, aRow, A, B, G, reverse, clampBlack, clampWhite);
+ beautyPixelEngine(in, y, x, r, activeChannels, aRow);
+ }
+ else
+ {
+ LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, inChannels, aRow, A, B, G, reverse, clampBlack, clampWhite);
+ }
+ LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out);
+}
+
+void GradeBeautyLayer::knobs(Knob_Callback f) {
+ ChannelSet_knob(f, &m_sourceLayer, "channels", "source layer");
+ Tooltip(f,
+ "Order of operations :
"
+ " 1 - source_layer subtracted from target_layer
"
+ " 2 - source_layer is graded
"
+ " 2 - graded source_layer is added to target_layer
"
+ );
+ SetFlags(f, Knob::NO_ALPHA_PULLDOWN);
+ SetFlags(f, Knob::NO_CHECKMARKS);
+
+ LayerAlchemy::Knobs::createDocumentationButton(f);
+ LayerAlchemy::Knobs::createColorKnobResetButton(f);
+ LayerAlchemy::Knobs::createVersionTextKnob(f);
+ Divider(f, 0); // separates layer set knobs from the rest
+
+ Input_ChannelMask_knob(f, &m_targetLayer, 0, "target layer");
+ SetFlags(f, Knob::NO_ALPHA_PULLDOWN);
+ Tooltip(f, "Selects which layer to pre-subtract layers from (if enabled) and offset the modified layers to
");
+ SetFlags(f, Knob::EXPAND_TO_CONTENTS);
+
+ Divider(f, 0); // separates layer set knobs from the rest
+
+ Color_knob(f, blackpoint, IRange(-1, 1), "blackpoint");
+ Tooltip(f, "This color is turned into black");
+ Color_knob(f, whitepoint, IRange(0, 4), "whitepoint");
+ Tooltip(f, "This color is turned into white");
+ Color_knob(f, lift, IRange(-1, 1), "lift", "lift");
+ Tooltip(f, "Black is turned into this color");
+ Color_knob(f, gain, IRange(0, 4), "gain", "gain");
+ Tooltip(f, "White is turned into this color");
+ Color_knob(f, multiply, IRange(0, 4), "multiply");
+ Tooltip(f, "Constant to multiply result by");
+ Color_knob(f, offset, IRange(-1, 1), "offset", "offset");
+ Tooltip(f, "Constant to offset to result (raises both black & white, unlike lift)");
+ Color_knob(f, gamma, IRange(.2, 5), "gamma");
+ Tooltip(f, "Gamma correction applied to final result");
+ Newline(f, " ");
+ Bool_knob(f, &reverse, "reverse");
+ Tooltip(f, "Invert the math to undo the correction");
+ Bool_knob(f, &clampBlack, "clampBlack", "black clamp");
+ Tooltip(f, "Output that is less than zero is changed to zero");
+ Bool_knob(f, &clampWhite, "clampWhite", "white clamp");
+ Tooltip(f, "Output that is greater than 1 is changed to 1");
+
+ Divider(f, 0); // separates NukeWrapper knobs created after this
+}
+
+int GradeBeautyLayer::knob_changed(Knob* k) {
+ return 1;
+}
+} // End namespace GradeBeautyLayer
diff --git a/src/nuke/GradeBeautyLayerSet.cpp b/src/nuke/GradeBeautyLayerSet.cpp
index 3402d4e..03f8a90 100644
--- a/src/nuke/GradeBeautyLayerSet.cpp
+++ b/src/nuke/GradeBeautyLayerSet.cpp
@@ -23,24 +23,6 @@ static const char* const HELP =
" * more info with the documentation button"
;
-// patch for linux alphas because the pow function behaves badly
-// for very large or very small exponent values.
-static bool LINUX = false;
-#ifdef __alpha
-LINUX = true;
-#endif
-
-float validateGammaValue(const float& gammaValue) {
- if (LINUX) {
- if (gammaValue < 0.008f) {
- return 0.0f;
- } else if (gammaValue > 125.0f) {
- return 125.0f;
- }
- }
- return gammaValue;
-}
-
enum operationModes {
ADD = 0, COPY
};
@@ -90,7 +72,7 @@ class GradeBeautyLayerSet : public PixelIop {
static const Iop::Description description;
// This function calculates and stores the grade algorithm's intermediate calculations
- void precomputeValues();
+ bool precomputeValues();
GradeBeautyLayerSet(Node* node);
~GradeBeautyLayerSet();
// channel set that contains all channels that are modified by the node
@@ -144,31 +126,32 @@ void GradeBeautyLayerSet::in_channels(int input_number, ChannelSet& mask) const
mask += activeChannelSet();
}
-void GradeBeautyLayerSet::precomputeValues() {
- for (int chanIdx = 0; chanIdx < 3; chanIdx++) {
+bool GradeBeautyLayerSet::precomputeValues() {
+ bool changeZero = false;
+ for (unsigned int chanIdx = 0; chanIdx < 3; chanIdx++) {
float a = whitepoint[chanIdx] - blackpoint[chanIdx];
a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f;
a *= multiply[chanIdx];
float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a;
- float g = validateGammaValue(gamma[chanIdx]);
+ float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]);
A[chanIdx] = a;
B[chanIdx] = b;
G[chanIdx] = g;
- }
-}
-
-void GradeBeautyLayerSet::_validate(bool for_real) {
- bool changeZero = false;
- precomputeValues();
- for (int chanIdx = 0; chanIdx <= 3; chanIdx++) {
- if (A[chanIdx] != 1 || B[chanIdx] || gamma[chanIdx] != 1.0f) {
- if (B[chanIdx]) {
+ if (a != 1.0f || b != 0.0f || g != 1.0f)
+ {
+ if (b)
+ {
changeZero = true;
}
}
}
- info_.black_outside(!changeZero);
+ return changeZero;
+}
+
+void GradeBeautyLayerSet::_validate(bool for_real) {
copy_info(); // this copies the input info to the output
+ bool changeZero = precomputeValues();
+ info_.black_outside(!changeZero);
ChannelSet inChannels = info_.channels();
LayerAlchemy::Utilities::validateTargetLayerColorIndex(this, m_targetLayer, 0, 2);
@@ -176,99 +159,11 @@ void GradeBeautyLayerSet::_validate(bool for_real) {
updateLayerSetKnob(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels, CategorizeFilterAllBeauty);
}
set_out_channels(activeChannelSet());
+ info_.turn_on(m_targetLayer);
}
void GradeBeautyLayerSet::channelPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow)
{
- map aovPtrIdxMap;
- foreach(channel, channels)
- {
- LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow);
- aovPtrIdxMap[channel] = aRow.writable(channel);
- }
-
- foreach(channel, channels) {
- unsigned chanIdx = colourIndex(channel);
- const float* inAovValue = in[channel];
- float* outAovValue = aovPtrIdxMap[channel];
-
- float _A = A[chanIdx];
- float _B = B[chanIdx];
- float _G = G[chanIdx];
-
- for (int X = x; X < r; X++)
- {
- float outPixel = inAovValue[X];
-
- if (!reverse) {
- if (_A != 1.0f || _B) {
- outPixel *= _A;
- outPixel += _B;
- }
- if (clampWhite || clampBlack) {
- if (outPixel < 0.0f && clampBlack) { // clamp black
- outPixel = 0.0f;
- }
- if (outPixel > 1.0f && clampWhite) { // clamp white
- outPixel = 1.0f;
- }
- }
- if (_G <= 0) {
- if (outPixel < 1.0f) {
- outPixel = 0.0f;
- } else if (outPixel > 1.0f) {
- outPixel = INFINITY;
- }
- } else if (_G != 1.0f) {
- float power = 1.0f / _G;
- if (LINUX & (outPixel <= 1e-6f && power > 1.0f)) {
- outPixel = 0.0f;
- } else if (outPixel < 1) {
- outPixel = powf(outPixel, power);
- } else {
- outPixel = (1.0f + outPixel - 1.0f) * power;
- }
- }
- }
- if (reverse) { // Reverse gamma:
- if (_G <= 0) {
- outPixel = outPixel > 0.0f ? 1.0f : 0.0f;
- }
- if (_G != 1.0f) {
- if (LINUX & (outPixel <= 1e-6f && _G > 1.0f)) {
- outPixel = 0.0f;
- } else if (outPixel < 1.0f) {
- outPixel = powf(outPixel, _G);
- } else {
- outPixel = 1.0f + (outPixel - 1.0f) * _G;
- }
- }
- // Reverse the linear part:
- if (_A != 1.0f || _B) {
- float b = _B;
- float a = _A;
- if (a) {
- a = 1 / a;
- } else {
- a = 1.0f;
- }
- b = -b * a;
- outPixel = (outPixel * a) + b;
- }
- }
- // clamp
- if (clampWhite || clampBlack) {
- if (outPixel < 0.0f && clampBlack)
- {
- outPixel = 0.0f;
- }
- else if (outPixel > 1.0f && clampWhite)
- {
- outPixel = 1.0f;
- }
- }
- outAovValue[X] = outPixel;
- }
- }
+ LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite);
}
void GradeBeautyLayerSet::beautyPixelEngine(const Row& in, int y, int x, int r, ChannelSet& channels, Row& aRow)
{
@@ -356,7 +251,7 @@ void GradeBeautyLayerSet::pixel_engine(const Row& in, int y, int x, int r, Chann
if (isTargetLayer)
{
- channelPixelEngine(in, y, x, r, activeChannels, aRow);
+ channelPixelEngine(in, y, x, r, m_lsKnobData.m_selectedChannels, aRow);
beautyPixelEngine(in, y, x, r, activeChannels, aRow);
}
else
diff --git a/src/nuke/GradeLayerSet.cpp b/src/nuke/GradeLayerSet.cpp
index 9d9ec51..4e16a97 100644
--- a/src/nuke/GradeLayerSet.cpp
+++ b/src/nuke/GradeLayerSet.cpp
@@ -20,25 +20,6 @@ const char* const HELP =
"invert the grade. This will do the opposite gamma correction followed by the "
"opposite linear ramp.";
-// patch for linux alphas because the pow function behaves badly
-// for very large or very small exponent values.
-static bool LINUX = false;
-#ifdef __alpha
-LINUX = true;
-#endif
-
-float validateGammaValue(const float& gammaValue)
-{
- if (LINUX) {
- if (gammaValue < 0.008f) {
- return 0.0f;
- } else if (gammaValue > 125.0f) {
- return 125.0f;
- }
- }
- return gammaValue;
-}
-
class GradeLayerSet : public PixelIop {
private:
@@ -70,7 +51,7 @@ class GradeLayerSet : public PixelIop {
GradeLayerSet(Node* node);
~GradeLayerSet();
// This function calculates and stores the grade algorithm's intermediate calculations
- void precomputeValues();
+ bool precomputeValues();
// channel set that contains all channels that are modified by the node
ChannelSet activeChannelSet() const {return ChannelSet(m_lsKnobData.m_selectedChannels);}
};
@@ -93,12 +74,9 @@ void GradeLayerSet::in_channels(int input, ChannelSet& mask) const {}
void GradeLayerSet::_validate(bool for_real)
{
- bool change_zero = false;
- precomputeValues();
- if (change_zero) {
- info_.black_outside(false);
- }
copy_info(); // this copies the input info to the output
+ bool changeZero = precomputeValues();
+ info_.black_outside(!changeZero);
ChannelSet inChannels = info_.channels();
if (validateLayerSetKnobUpdate(this, m_lsKnobData, LayerAlchemy::layerCollection, inChannels)) {
@@ -106,114 +84,34 @@ void GradeLayerSet::_validate(bool for_real)
}
set_out_channels(activeChannelSet());
}
-void GradeLayerSet::precomputeValues()
-{
- for (int chanIdx = 0; chanIdx < 3; chanIdx++) {
+
+bool GradeLayerSet::precomputeValues() {
+ bool changeZero = false;
+ for (unsigned int chanIdx = 0; chanIdx < 4; chanIdx++) {
float a = whitepoint[chanIdx] - blackpoint[chanIdx];
a = a ? (gain[chanIdx] - lift[chanIdx]) / a : 10000.0f;
a *= multiply[chanIdx];
float b = offset[chanIdx] + lift[chanIdx] - blackpoint[chanIdx] * a;
- float g = validateGammaValue(gamma[chanIdx]);
+ float g = LayerAlchemy::Utilities::validateGammaValue(gamma[chanIdx]);
A[chanIdx] = a;
B[chanIdx] = b;
G[chanIdx] = g;
+ if (a != 1.0f || b != 0.0f || g != 1.0f)
+ {
+ if (b)
+ {
+ changeZero = true;
+ }
+ }
}
+ return changeZero;
}
void GradeLayerSet::pixel_engine(const Row& in, int y, int x, int r, ChannelMask inChannels, Row& out)
{
Row aRow(x, r);
-
- map aovPtrIdxMap;
- foreach(channel, inChannels)
- {
- LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow);
- aovPtrIdxMap[channel] = aRow.writable(channel);
- }
-
- foreach(channel, inChannels) {
- unsigned chanIdx = colourIndex(channel);
- const float* inAovValue = in[channel];
- float* outAovValue = aovPtrIdxMap[channel];
-
- float _A = A[chanIdx];
- float _B = B[chanIdx];
- float _G = G[chanIdx];
-
- for (int X = x; X < r; X++)
- {
- float outPixel = inAovValue[X];
-
- if (!reverse) {
- if (_A != 1.0f || _B) {
- outPixel *= _A;
- outPixel += _B;
- }
- if (clampWhite || clampBlack) {
- if (outPixel < 0.0f && clampBlack) { // clamp black
- outPixel = 0.0f;
- }
- if (outPixel > 1.0f && clampWhite) { // clamp white
- outPixel = 1.0f;
- }
- }
- if (_G <= 0) {
- if (outPixel < 1.0f) {
- outPixel = 0.0f;
- } else if (outPixel > 1.0f) {
- outPixel = INFINITY;
- }
- } else if (_G != 1.0f) {
- float power = 1.0f / _G;
- if (LINUX & (outPixel <= 1e-6f && power > 1.0f)) {
- outPixel = 0.0f;
- } else if (outPixel < 1) {
- outPixel = powf(outPixel, power);
- } else {
- outPixel = (1.0f + outPixel - 1.0f) * power;
- }
- }
- }
- if (reverse) { // Reverse gamma:
- if (_G <= 0) {
- outPixel = outPixel > 0.0f ? 1.0f : 0.0f;
- }
- if (_G != 1.0f) {
- if (LINUX & (outPixel <= 1e-6f && _G > 1.0f)) {
- outPixel = 0.0f;
- } else if (outPixel < 1.0f) {
- outPixel = powf(outPixel, _G);
- } else {
- outPixel = 1.0f + (outPixel - 1.0f) * _G;
- }
- }
- // Reverse the linear part:
- if (_A != 1.0f || _B) {
- float b = _B;
- float a = _A;
- if (a) {
- a = 1 / a;
- } else {
- a = 1.0f;
- }
- b = -b * a;
- outPixel = (outPixel * a) + b;
- }
- }
- // clamp
- if (clampWhite || clampBlack) {
- if (outPixel < 0.0f && clampBlack)
- {
- outPixel = 0.0f;
- }
- else if (outPixel > 1.0f && clampWhite)
- {
- outPixel = 1.0f;
- }
- }
- outAovValue[X] = outPixel;
- }
- }
+ ChannelSet channels = ChannelSet(inChannels);
+ LayerAlchemy::Utilities::gradeChannelPixelEngine(in, y, x, r, channels, aRow, A, B, G, reverse, clampBlack, clampWhite);
LayerAlchemy::Utilities::hard_copy(aRow, x, r, inChannels, out);
}
diff --git a/src/nuke/LayerSet.cpp b/src/nuke/LayerSet.cpp
index b7f5d7a..315b435 100644
--- a/src/nuke/LayerSet.cpp
+++ b/src/nuke/LayerSet.cpp
@@ -72,8 +72,14 @@ namespace Knobs {
DD::Image::Knob* createDocumentationButton(DD::Image::Knob_Callback& f)
{
- DD::Image::Knob* docButton = Button(f, "docButton", "documentation");
- Tooltip(f, "This will launch the default browser and load the included plugin documentation
");
+ const char* docButtonScript =
+ "import layer_alchemy.documentation\n"
+ "qtWidget = layer_alchemy.documentation.displayDocumentation(node=nuke.thisNode())\n"
+ "if qtWidget:\n"
+ " qtWidget.show()";
+
+ DD::Image::Knob* docButton = PyScript_knob(f, docButtonScript, "documentation");
+ Tooltip(f, "This will display the included plugin documentation
");
return docButton;
}
@@ -136,6 +142,128 @@ void hard_copy(const DD::Image::Row& fromRow, int x, int r, DD::Image::ChannelSe
hard_copy(fromRow, x, r, channel, toRow);
}
}
+
+void gradeChannelPixelEngine(const DD::Image::Row& in, int y, int x, int r, DD::Image::ChannelSet& channels, DD::Image::Row& aRow, float* A, float* B, float* G, bool reverse, bool clampBlack, bool clampWhite)
+{
+ // patch for linux alphas because the pow function behaves badly
+ // for very large or very small exponent values.
+ static bool LINUX = false;
+ #ifdef __alpha
+ LINUX = true;
+ #endif
+
+
+ map aovPtrIdxMap;
+ foreach(channel, channels)
+ {
+ LayerAlchemy::Utilities::hard_copy(in, x, r, channel, aRow);
+ aovPtrIdxMap[channel] = aRow.writable(channel);
+ }
+
+ foreach(channel, channels) {
+ unsigned chanIdx = colourIndex(channel);
+ const float* inAovValue = in[channel];
+ float* outAovValue = aovPtrIdxMap[channel];
+
+ float _A = A[chanIdx];
+ float _B = B[chanIdx];
+ float _G = G[chanIdx];
+
+ for (int X = x; X < r; X++)
+ {
+ float outPixel = inAovValue[X];
+
+ if (!reverse) {
+ if (_A != 1.0f || _B) {
+ outPixel *= _A;
+ outPixel += _B;
+ }
+ if (clampWhite || clampBlack) {
+ if (outPixel < 0.0f && clampBlack) { // clamp black
+ outPixel = 0.0f;
+ }
+ if (outPixel > 1.0f && clampWhite) { // clamp white
+ outPixel = 1.0f;
+ }
+ }
+ if (_G <= 0) {
+ if (outPixel < 1.0f) {
+ outPixel = 0.0f;
+ } else if (outPixel > 1.0f) {
+ outPixel = INFINITY;
+ }
+ } else if (_G != 1.0f) {
+ float power = 1.0f / _G;
+ if (LINUX & (outPixel <= 1e-6f && power > 1.0f)) {
+ outPixel = 0.0f;
+ } else if (outPixel < 1) {
+ outPixel = powf(outPixel, power);
+ } else {
+ outPixel = (1.0f + outPixel - 1.0f) * power;
+ }
+ }
+ }
+ if (reverse) { // Reverse gamma:
+ if (_G <= 0) {
+ outPixel = outPixel > 0.0f ? 1.0f : 0.0f;
+ }
+ if (_G != 1.0f) {
+ if (LINUX & (outPixel <= 1e-6f && _G > 1.0f)) {
+ outPixel = 0.0f;
+ } else if (outPixel < 1.0f) {
+ outPixel = powf(outPixel, _G);
+ } else {
+ outPixel = 1.0f + (outPixel - 1.0f) * _G;
+ }
+ }
+ // Reverse the linear part:
+ if (_A != 1.0f || _B) {
+ float b = _B;
+ float a = _A;
+ if (a) {
+ a = 1 / a;
+ } else {
+ a = 1.0f;
+ }
+ b = -b * a;
+ outPixel = (outPixel * a) + b;
+ }
+ }
+ // clamp
+ if (clampWhite || clampBlack) {
+ if (outPixel < 0.0f && clampBlack)
+ {
+ outPixel = 0.0f;
+ }
+ else if (outPixel > 1.0f && clampWhite)
+ {
+ outPixel = 1.0f;
+ }
+ }
+ outAovValue[X] = outPixel;
+ }
+ }
+}
+
+float validateGammaValue(const float& gammaValue)
+{
+ static bool LINUX = false;
+ #ifdef __alpha
+ LINUX = true;
+ #endif
+ if (LINUX)
+ {
+ if (gammaValue < 0.008f)
+ {
+ return 0.0f;
+ }
+ else if (gammaValue > 125.0f)
+ {
+ return 125.0f;
+ }
+ }
+ return gammaValue;
+}
} // End namespace Utilities
} // End namespace LayerAlchemy
diff --git a/src/nuke/LayerSetKnob.cpp b/src/nuke/LayerSetKnob.cpp
index 7a9fc3b..28cb474 100644
--- a/src/nuke/LayerSetKnob.cpp
+++ b/src/nuke/LayerSetKnob.cpp
@@ -16,7 +16,7 @@ LayerSetKnobData::~LayerSetKnobData()
DD::Image::Knob* LayerSetKnob(DD::Image::Knob_Callback& f, LayerSetKnobData& pLayerSetKnobData)
{
DD::Image::Knob* layerSetKnob = DD::Image::Enumeration_knob(
- f, &pLayerSetKnobData.selectedLayerSetIndex, pLayerSetKnobData.items, "layer_set", "layer set");
+ f, &pLayerSetKnobData.selectedLayerSetIndex, pLayerSetKnobData.items, LAYER_SET_KNOB_NAME, "layer set");
Tooltip(f, "This selects a specific layer set for processing in this node");
SetFlags(f, DD::Image::Knob::SAVE_MENU);
SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);