1 /**
2  * OpenColorIO LogConvert Iop.
3  */
4 
5 #include "OCIOLogConvert.h"
6 
7 namespace OCIO = OCIO_NAMESPACE;
8 
9 #include <string>
10 #include <sstream>
11 #include <stdexcept>
12 
13 #include <DDImage/Channel.h>
14 #include <DDImage/PixelIop.h>
15 #include <DDImage/NukeWrapper.h>
16 #include <DDImage/Row.h>
17 #include <DDImage/Knobs.h>
18 
19 const char* OCIOLogConvert::modes[] = {
20     "log to lin", "lin to log", 0
21 };
22 
OCIOLogConvert(Node * n)23 OCIOLogConvert::OCIOLogConvert(Node *n) : DD::Image::PixelIop(n)
24 {
25     modeindex = 0;
26 }
27 
~OCIOLogConvert()28 OCIOLogConvert::~OCIOLogConvert()
29 {
30 
31 }
32 
knobs(DD::Image::Knob_Callback f)33 void OCIOLogConvert::knobs(DD::Image::Knob_Callback f)
34 {
35 
36     Enumeration_knob(f, &modeindex, modes, "operation", "operation");
37     DD::Image::SetFlags(f, DD::Image::Knob::ALWAYS_SAVE);
38 }
39 
_validate(bool for_real)40 void OCIOLogConvert::_validate(bool for_real)
41 {
42     try
43     {
44         OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig();
45 
46         const char * src = 0;
47         const char * dst = 0;
48 
49         if(modeindex == 0)
50         {
51             src = OCIO::ROLE_COMPOSITING_LOG;
52             dst = OCIO::ROLE_SCENE_LINEAR;
53         }
54         else
55         {
56             src = OCIO::ROLE_SCENE_LINEAR;
57             dst = OCIO::ROLE_COMPOSITING_LOG;
58         }
59 
60         processor = config->getProcessor(src, dst);
61     }
62     catch(OCIO::Exception &e)
63     {
64         error(e.what());
65         return;
66     }
67 
68     if(processor->isNoOp())
69     {
70         set_out_channels(DD::Image::Mask_None); // prevents engine() from being called
71     } else {
72         set_out_channels(DD::Image::Mask_All);
73     }
74 
75     DD::Image::PixelIop::_validate(for_real);
76 }
77 
78 // Note that this is copied by others (OCIODisplay)
in_channels(int,DD::Image::ChannelSet & mask) const79 void OCIOLogConvert::in_channels(int /* n unused */, DD::Image::ChannelSet& mask) const
80 {
81     DD::Image::ChannelSet done;
82     foreach(c, mask)
83     {
84         if (DD::Image::colourIndex(c) < 3 && !(done & c))
85         {
86             done.addBrothers(c, 3);
87         }
88     }
89     mask += done;
90 }
91 
92 // See Saturation::pixel_engine for a well-commented example.
93 // Note that this is copied by others (OCIODisplay)
pixel_engine(const DD::Image::Row & in,int,int rowX,int rowXBound,DD::Image::ChannelMask outputChannels,DD::Image::Row & out)94 void OCIOLogConvert::pixel_engine(
95     const DD::Image::Row& in,
96     int /* rowY unused */, int rowX, int rowXBound,
97     DD::Image::ChannelMask outputChannels,
98     DD::Image::Row& out)
99 {
100     int rowWidth = rowXBound - rowX;
101 
102     DD::Image::ChannelSet done;
103     foreach (requestedChannel, outputChannels)
104     {
105         // Skip channels which had their trios processed already,
106         if (done & requestedChannel)
107         {
108             continue;
109         }
110 
111         // Pass through channels which are not selected for processing
112         // and non-rgb channels.
113         if (colourIndex(requestedChannel) >= 3)
114         {
115             out.copy(in, requestedChannel, rowX, rowXBound);
116             continue;
117         }
118 
119         DD::Image::Channel rChannel = DD::Image::brother(requestedChannel, 0);
120         DD::Image::Channel gChannel = DD::Image::brother(requestedChannel, 1);
121         DD::Image::Channel bChannel = DD::Image::brother(requestedChannel, 2);
122 
123         done += rChannel;
124         done += gChannel;
125         done += bChannel;
126 
127         const float *rIn = in[rChannel] + rowX;
128         const float *gIn = in[gChannel] + rowX;
129         const float *bIn = in[bChannel] + rowX;
130 
131         float *rOut = out.writable(rChannel) + rowX;
132         float *gOut = out.writable(gChannel) + rowX;
133         float *bOut = out.writable(bChannel) + rowX;
134 
135         // OCIO modifies in-place
136         // Note: xOut can equal xIn in some circumstances, such as when the
137         // 'Black' (throwaway) scanline is uses. We thus must guard memcpy,
138         // which does not allow for overlapping regions.
139         if (rOut != rIn) memcpy(rOut, rIn, sizeof(float)*rowWidth);
140         if (gOut != gIn) memcpy(gOut, gIn, sizeof(float)*rowWidth);
141         if (bOut != bIn) memcpy(bOut, bIn, sizeof(float)*rowWidth);
142 
143         try
144         {
145             OCIO::PlanarImageDesc img(rOut, gOut, bOut, NULL, rowWidth, /*height*/ 1);
146             processor->apply(img);
147         }
148         catch(OCIO::Exception &e)
149         {
150             error(e.what());
151         }
152     }
153 }
154 
155 const DD::Image::Op::Description OCIOLogConvert::description("OCIOLogConvert", build);
156 
Class() const157 const char* OCIOLogConvert::Class() const
158 {
159     return description.name;
160 }
161 
displayName() const162 const char* OCIOLogConvert::displayName() const
163 {
164     return description.name;
165 }
166 
node_help() const167 const char* OCIOLogConvert::node_help() const
168 {
169     // TODO more detailed help text
170     return "Use OpenColorIO to convert from SCENE_LINEAR to COMPOSITING_LOG (or back).";
171 }
172 
173 
build(Node * node)174 DD::Image::Op* build(Node *node)
175 {
176     DD::Image::NukeWrapper *op = new DD::Image::NukeWrapper(new OCIOLogConvert(node));
177     op->channels(DD::Image::Mask_RGB);
178     return op;
179 }
180