1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-misc <https://github.com/devernay/openfx-misc>,
3  * Copyright (C) 2015 INRIA
4  *
5  * openfx-misc is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-misc is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-misc.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX DenoiseSharpen plugin.
21  */
22 
23 /*
24    TODO:
25    - add "Luminance Blend [0.7]" and "Chrominance Blend [1.0]" settings to YCbCr and Lab, which is like "mix", but only on luminance or chrominance.
26    - bandlets (see fig 25 of http://www.cmap.polytechnique.fr/~mallat/papiers/07-NumerAlgo-MallatPeyre-BandletsReview.pdf )
27    - edge-aware version
28    - estimate a per-intensity noise gain, based on analysis of the HH1 subband, in conjunction with the first smoothed level. analyze the noise in one channel only (luminance or green).
29    is this is film, analyze at 9 values from 0.1 to 1.0, with a geometric progression, thus x = 0.1*a^i for i = 0..8, whith a = (1/0.1)^(1./8) = 1.13314845307
30    if this is digital, use an arithmetic progression, x=0.1 + 0.1*i
31 
32    Notes on edge-aware version:
33 
34    - multiply side weights (which are 1 for now) by exp(-||c_i(p)-c_i(q)||^2/(2sigma_r^2) and normalize the total weight
35    - the problem is that the filtering order (column, then rows, or rows, then columns) has a strong influence on the result. Check
36    - can be color or luminance difference
37 
38    - Hanika et al. test several values of sigma_r and
39 
40    - In the context of denoising by bilateral filtering,
41    Liu et al. [41] show that adapting the range parameter σr to estimates of the local noise level yields
42    more satisfying results. The authors recommend a linear dependence:
43    σ_r = 1.95 σ_n, where σ_n is the local noise level estimate.
44    [41]  C. Liu, W. T. Freeman, R. Szeliski, and S. Kang, “Noise estimation from a single image,” in Proceedings of the Conference on IEEE Computer Vision and Pattern Recognition, volume 1, pp. 901–908, 2006.
45 
46    - as an estimate of sigma_n, we can use the
47  */
48 
49 #define kUseMultithread // define to use the multithread suite
50 //#define DEBUG_STDOUT // output debugging messages on stdout (for Resolve)
51 
52 #if defined(_OPENMP)
53 #include <omp.h>
54 #define _GLIBCXX_PARALLEL // enable libstdc++ parallel STL algorithm (eg nth_element, sort...)
55 #endif
56 #include <cmath>
57 #include <cfloat> // DBL_MAX
58 #include <algorithm> // max
59 #ifdef DEBUG_STDOUT
60 #include <iostream>
61 #define DBG(x) (x)
62 #else
63 #define DBG(x) (void)0
64 #endif
65 
66 #include "ofxsMaskMix.h"
67 #include "ofxsCoords.h"
68 #include "ofxsMacros.h"
69 #include "ofxsLut.h"
70 #include "ofxsRectangleInteract.h"
71 #include "ofxsThreadSuite.h"
72 #include "ofxsMultiThread.h"
73 #include "ofxsCopier.h"
74 #ifdef OFX_USE_MULTITHREAD_MUTEX
75 namespace {
76 typedef MultiThread::Mutex Mutex;
77 typedef MultiThread::AutoMutex AutoMutex;
78 }
79 #else
80 // some OFX hosts do not have mutex handling in the MT-Suite (e.g. Sony Catalyst Edit)
81 // prefer using the fast mutex by Marcus Geelnard http://tinythreadpp.bitsnbites.eu/
82 #include "fast_mutex.h"
83 namespace {
84 typedef tthread::fast_mutex Mutex;
85 typedef OFX::MultiThread::AutoMutexT<tthread::fast_mutex> AutoMutex;
86 }
87 #endif
88 
89 #ifndef M_LN2
90 #define M_LN2       0.693147180559945309417232121458176568  /* loge(2)        */
91 #endif
92 
93 using namespace OFX;
94 #ifdef DEBUG_STDOUT
95 using std::cout;
96 using std::endl;
97 #endif
98 
99 OFXS_NAMESPACE_ANONYMOUS_ENTER
100 
101 #define kPluginName "DenoiseSharpen"
102 #define kPluginGrouping "Filter"
103 #define kPluginDescriptionShort \
104     "Denoise and/or sharpen images using wavelet-based algorithms.\n" \
105     "\n" \
106     "## Description\n" \
107     "\n" \
108     "This plugin allows the separate denoising of image channels in multiple color spaces using wavelets, using the BayesShrink algorithm, and can also sharpen the image details.\n" \
109     "\n" \
110     "Noise levels for each channel may be either set manually, or analyzed from the image data in each wavelet subband using the MAD (median absolute deviation) estimator.\n" \
111     "Noise analysis is based on the assuption that the noise is Gaussian and additive (it is not intensity-dependent). If there is speckle or salt-and-pepper noise in the images, the Median or SmoothPatchBased filters may be more appropriate.\n" \
112     "The color model specifies the channels and the transforms used. Noise levels have to be re-adjusted or re-analyzed when changing the color model.\n" \
113     "\n" \
114     "## Basic Usage\n" \
115     "\n" \
116     "The input image should be in linear RGB.\n" \
117     "\n" \
118     "For most footage, the effect works best by keeping the default Y'CbCr color model. The color models are made to work with Rec.709 data, but DenoiseSharpen will still work if the input is in another colorspace, as long as the input is linear RGB:\n" \
119     "\n" \
120     "- The Y'CbCr color model uses the Rec.709 opto-electronic transfer function to convert from RGB to R'G'B' and the the Rec.709 primaries to convert from R'G'B' to Y'CbCr.\n" \
121     "- The L * a * b color model uses the Rec.709 RGB primaries to convert from RGB to L * a * b.\n" \
122     "- The R'G'B' color model uses the Rec.709 opto-electronic transfer function to convert from RGB to R'G'B'.\n" \
123     "- The RGB color model (linear) makes no assumption about the RGB color space, and works directly on the RGB components, assuming additive noise. This is the only option if the noisy source contains negative values. If, say, the noise is known to be multiplicative, one can convert the images to Log before denoising, use this option, and convert back to linear after denoising.\n" \
124     "- The Alpha channel, if processed, is always considered to be linear.\n" \
125     "\n" \
126     "The simplest way to use this plugin is to leave the noise analysis area to the whole image, and click \"Analyze Noise Levels\". Once the analysis is done, \"Lock Noise Analysis\" is checked in order to avoid modifying the essential parameters by mistake.\n" \
127     "\n" \
128     "If the image has many textured areas, it may be preferable to select an analysis area with flat colors, free from any details, shadows or hightlights, to avoid considering texture as noise. The AnalysisMask input can be used to mask the analysis, if the rectangular area is not appropriate. Any non-zero pixels in the mask are taken into account. A good option for the AnalysisMask would be to take the inverse of the output of an edge detector and clamp it correctly so that all pixels near the edges have a value of zero..\n" \
129     "\n" \
130     "If the sequence to be denoised does not have enough flat areas, you can also connect a reference footage with the same kind of noise to the AnalysisSource input: that source will be used for the analysis only. If no source with flat areas is available, and noise analysis can only be performed on areas which also contain details, it is often preferable to disable very low, low, and sometimes medium frequencies in the \"Frequency Tuning\" parameters group, or at least to lower their gain, since they may be misestimated by the noise analysis process.\n" \
131     "If the noise is IID (independent and identically distributed), such as digital sensor noise, only \"Denoise High Frequencies\" should be checked. If the noise has some grain (i.e. it commes from lossy compression of noisy images by a camera, or it is scanned film), then you may want to enable medium frequencies as well. If low and very low frequencies are enabled, but the analysis area is not a flat zone, the signal itself (i.e. the noise-free image) could be considered as noise, and the result may exhibit low contrast and blur.\n" \
132     "\n" \
133     "To check what details have been kept after denoising, you can raise the Sharpen Amount to something like 10, and then adjust the Noise Level Gain to get the desired denoising amount, until no noise is left and only image details remain in the sharpened image. You can then reset the Sharpen Amount to zero, unless you actually want to enhance the contrast of your denoised footage.\n" \
134     "\n" \
135     "You can also check what was actually removed from the original image by selecting the \"Noise\" Output mode (instead of \"Result\"). If too many image details are visible in the noise, noise parameters may need to be tuned.\n"
136 
137 
138 #ifdef _OPENMP
139 #define kPluginDescription kPluginDescriptionShort "\nThis plugin was compiled with OpenMP support."
140 #else
141 #define kPluginDescription kPluginDescriptionShort
142 #endif
143 #define kPluginIdentifier "net.sf.openfx.DenoiseSharpen"
144 // History:
145 // version 1.0: initial version
146 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
147 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
148 
149 #define kSupportsTiles 1
150 #define kSupportsMultiResolution 1
151 #define kSupportsRenderScale 1
152 #define kSupportsMultipleClipPARs false
153 #define kSupportsMultipleClipDepths false
154 #define kRenderThreadSafety eRenderFullySafe
155 
156 #define kClipSourceHint "The footage to be denoised. If nothing is connected to the AnalysisSource input, this is also used for noise analysis."
157 #define kClipMaskHint "An optional image to use as a mask. By default, the effect is limited to the non-black areas of the mask."
158 #define kClipAnalysisSource "AnalysisSource"
159 #define kClipAnalysisSourceHint "An optional noise source. If connected, this is used instead of the Source input for the noise analysis. This is used to analyse noise from some footage by apply it on another footage, in case the footage to be denoised does not have enough flat areas."
160 #define kClipAnalysisMask "AnalysisMask"
161 #define kClipAnalysisMaskHint "An optional mask for the analysis area. This mask is intersected with the Analysis Rectangle. Non-zero pixels are taken into account in the noise analysis phase."
162 
163 #ifdef OFX_EXTENSIONS_NATRON
164 #define kParamProcessR kNatronOfxParamProcessR
165 #define kParamProcessRLabel kNatronOfxParamProcessRLabel
166 #define kParamProcessRHint kNatronOfxParamProcessRHint
167 #define kParamProcessG kNatronOfxParamProcessG
168 #define kParamProcessGLabel kNatronOfxParamProcessGLabel
169 #define kParamProcessGHint kNatronOfxParamProcessGHint
170 #define kParamProcessB kNatronOfxParamProcessB
171 #define kParamProcessBLabel kNatronOfxParamProcessBLabel
172 #define kParamProcessBHint kNatronOfxParamProcessBHint
173 #define kParamProcessA kNatronOfxParamProcessA
174 #define kParamProcessALabel kNatronOfxParamProcessALabel
175 #define kParamProcessAHint kNatronOfxParamProcessAHint
176 #else
177 #define kParamProcessR      "processR"
178 #define kParamProcessRLabel "R"
179 #define kParamProcessRHint  "Process red component."
180 #define kParamProcessG      "processG"
181 #define kParamProcessGLabel "G"
182 #define kParamProcessGHint  "Process green component."
183 #define kParamProcessB      "processB"
184 #define kParamProcessBLabel "B"
185 #define kParamProcessBHint  "Process blue component."
186 #define kParamProcessA      "processA"
187 #define kParamProcessALabel "A"
188 #define kParamProcessAHint  "Process alpha component."
189 #endif
190 
191 #define kParamOutputMode "outputMode"
192 #define kParamOutputModeLabel "Output"
193 #define kParamOutputModeHint "Select which image is output when analysis is locked. When analysis is not locked, the effect does nothing (the output is the source image)."
194 #define kParamOutputModeOptionResult "Result", "The result of denoising and sharpening the Source image.", "result"
195 #define kParamOutputModeOptionNoise "Noise", "An image containing what would be added to the image to denoise it. If 'Denoise Amount' is zero, this image should be black. Only noise should be visible in this image. If you can see a lot of picture detail in the noise output, it means the current settings are denoising too hard and remove too much of the image, which leads to a smoothed result. Try to lower the noise levels or the noise level gain.", "noise"
196 #define kParamOutputModeOptionSharpen "Sharpen", "An image containing what would be added to the image to sharpen it. If 'Sharpen Amount' is zero, this image should be black. Only image details should be visible in this image. If you can see a lot of noise in the sharpen output, it means the current settings are denoising not enough, which leads to a noisy result. Try to raise the noise levels or the noise level gain.", "sharpen"
197 enum OutputModeEnum
198 {
199     eOutputModeResult = 0,
200     eOutputModeNoise,
201     eOutputModeSharpen,
202 };
203 
204 #define kParamColorModel "colorModel"
205 #define kParamColorModelLabel "Color Model"
206 #define kParamColorModelHint "The colorspace where denoising is performed. These colorspaces assume that input and output use the Rec.709/sRGB chromaticities and the D65 illuminant, but should tolerate other input colorspaces (the output colorspace will always be the same as the input colorspace). Noise levels are reset when the color model is changed."
207 #define kParamColorModelOptionYCbCr "Y'CbCr(A)", "The YCbCr color model has one luminance channel (Y) which contains most of the detail information of an image (such as brightness and contrast) and two chroma channels (Cb = blueness, Cr = reddness) that hold the color information. Note that this choice drastically affects the result. Uses the Rec.709 opto-electronic transfer function to convert from RGB to R'G'B' and the the Rec.709 primaries to convert from R'G'B' to Y'CbCr.", "ycbcr"
208 #define kParamColorModelOptionLab "CIE L*a*b(A)", "CIE L*a*b* is a color model in which chrominance is separated from lightness and color distances are perceptually uniform. Note that this choice drastically affects the result. Uses the Rec.709 primaries to convert from RGB to L*a*b.", "cielab"
209 #define kParamColorModelOptionRGB "R'G'B'(A)", "The R'G'B' color model (gamma-corrected RGB) separates an image into channels of red, green, and blue. Note that this choice drastically affects the result. Uses the Rec.709 opto-electronic transfer function to convert from RGB to R'G'B'.", "gammargb"
210 #define kParamColorModelOptionLinearRGB "RGB(A)", "The Linear RGB color model processes the raw linear components. Usually a bad choice, except when denoising non-color data (e.g. depth or motion vectors). No assumption is made about the RGB color space.", "linearrgb"
211 enum ColorModelEnum
212 {
213     eColorModelYCbCr = 0,
214     eColorModelLab,
215     eColorModelRGB,
216     eColorModelLinearRGB,
217     eColorModelAny, // used for channelLabel()
218 };
219 
220 #define kGroupAnalysis "analysis"
221 #define kGroupAnalysisLabel "Analysis"
222 #define kParamAnalysisLock "analysisLock"
223 #define kParamAnalysisLockLabel "Lock Analysis and Apply"
224 #define kParamAnalysisLockHint "Lock all noise analysis parameters and apply denoising. When the analysis is not locked, the source image is output."
225 #define kParamB3 "useB3Spline"
226 #define kParamB3Label "B3 Spline Interpolation"
227 #define kParamB3Hint "For wavelet decomposition, use a 5x5 filter based on B3 spline interpolation rather than a 3x3 Lagrange linear filter. Noise levels are reset when this setting is changed. The influence of this parameter is minimal, and it should not be changed."
228 #define kParamAnalysisFrame "analysisFrame"
229 #define kParamAnalysisFrameLabel "Analysis Frame"
230 #define kParamAnalysisFrameHint "The frame number where the noise levels were analyzed."
231 
232 #define kGroupNoiseLevels "noiseLevels"
233 #define kGroupNoiseLevelsLabel "Noise Levels"
234 #define kParamNoiseLevelHint "Adjusts the noise variance of the selected channel for the given noise frequency. May be estimated from image data by pressing the \"Analyze Noise\" button."
235 #define kParamNoiseLevelMax 0.05 // noise level is at most 1/sqrt(12) ~=0.29 (stddev of a uniform distribution between 0 and 1)
236 #define kParamYLRNoiseLevel "ylrNoiseLevel"
237 #define kParamYLRNoiseLevelLabel "Y/L/R Level"
238 #define kParamYNoiseLevelLabel "Y Level"
239 #define kParamLNoiseLevelLabel "L Level"
240 #define kParamRNoiseLevelLabel "R Level"
241 #define kParamCbAGNoiseLevel "cbagNoiseLevel"
242 #define kParamCbAGNoiseLevelLabel "Cb/A/G Level"
243 #define kParamCbNoiseLevelLabel "Cb Level"
244 #define kParamANoiseLevelLabel "A Level"
245 #define kParamGNoiseLevelLabel "G Level"
246 #define kParamCrBBNoiseLevel "crbbNoiseLevel"
247 #define kParamCrBBNoiseLevelLabel "Cr/B/B Level"
248 #define kParamCrNoiseLevelLabel "Cr Level"
249 #define kParamBNoiseLevelLabel "B Level"
250 #define kParamAlphaNoiseLevel "alphaNoiseLevel"
251 #define kParamAlphaNoiseLevelLabel "Alpha Level"
252 #define kParamHigh "High"
253 #define kParamNoiseLevelHighLabel " (High)"
254 #define kParamMedium "Medium"
255 #define kParamNoiseLevelMediumLabel " (Medium)"
256 #define kParamLow "Low"
257 #define kParamNoiseLevelLowLabel " (Low)"
258 #define kParamVeryLow "VeryLow"
259 #define kParamNoiseLevelVeryLowLabel " (Very Low)"
260 #define kParamAnalyzeNoiseLevels "analyzeNoiseLevels"
261 #define kParamAnalyzeNoiseLevelsLabel "Analyze Noise Levels"
262 #define kParamAnalyzeNoiseLevelsHint "Computes the noise levels from the current frame and current color model. To use the same settings for the whole sequence, analyze a frame that is representative of the sequence. If a mask is set, it is used to compute the noise levels from areas where the mask is non-zero. If there are keyframes on the noise level parameters, this sets a keyframe at the current frame. The noise levels can then be fine-tuned."
263 
264 #define kParamNoiseLevelGain "noiseLevelGain"
265 #define kParamNoiseLevelGainLabel "Noise Level Gain"
266 #define kParamNoiseLevelGainHint "Global gain to apply to the noise level thresholds. 0 means no denoising, 1 means use the estimated thresholds multiplied by the per-frequency gain and the channel gain. The default value (1.0) is rather conservative (it does not destroy any kind of signal). Values around 1.1 or 1.2 usually give more pleasing results."
267 
268 #define kParamDenoiseAmount "denoiseAmount"
269 #define kParamDenoiseAmountLabel "Denoise Amount"
270 #define kParamDenoiseAmountHint "The amount of denoising to apply. 0 means no denoising (which may be useful to sharpen without denoising), between 0 and 1 does a soft thresholding of below the thresholds, thus keeping some noise, and 1 applies the threshold strictly and removes everything below the thresholds. This should be used only if you want to keep some noise, for example for noise matching. This value is multiplied by the per-channel amount se in the 'Channel Tuning' group. Remember that the thresholds are multiplied by the per-frequency gain, the channel gain, and the Noise Level Gain first."
271 
272 #define kGroupTuning "freqTuning"
273 #define kGroupTuningLabel "Frequency Tuning"
274 #define kParamEnable "enableFreq"
275 #define kParamGain "gainFreq"
276 #define kParamEnableHighLabel "Denoise High Frequencies"
277 #define kParamEnableHighHint "Check to enable the high frequency noise level thresholds. It is recommended to always leave this checked."
278 #define kParamGainHighLabel "High Gain"
279 #define kParamGainHighHint "Gain to apply to the high frequency noise level thresholds. 0 means no denoising, 1 means use the estimated thresholds multiplied by the channel Gain and the Noise Level Gain."
280 #define kParamEnableMediumLabel "Denoise Medium Frequencies"
281 #define kParamEnableMediumHint "Check to enable the medium frequency noise level thresholds. Can be disabled if the analysis area contains high frequency texture, or if the the noise is known to be IID (independent and identically distributed), for example if this is only sensor noise and lossless compression is used, and not grain or compression noise."
282 #define kParamGainMediumLabel "Medium Gain"
283 #define kParamGainMediumHint "Gain to apply to the medium frequency noise level thresholds. 0 means no denoising, 1 means use the estimated thresholds multiplied by the channel Gain and the Noise Level Gain."
284 #define kParamEnableLowLabel "Denoise Low Frequencies"
285 #define kParamEnableLowHint "Check to enable the low frequency noise level thresholds. Must be disabled if the analysis area contains texture, or if the noise is known to be IID (independent and identically distributed), for example if this is only sensor noise and lossless compression is used, and not grain or compression noise."
286 #define kParamGainLowLabel "Low Gain"
287 #define kParamGainLowHint "Gain to apply to the low frequency noise level thresholds. 0 means no denoising, 1 means use the estimated thresholds multiplied by the channel Gain and the Noise Level Gain."
288 #define kParamEnableVeryLowLabel "Denoise Very Low Frequencies"
289 #define kParamEnableVeryLowHint "Check to enable the very low frequency noise level thresholds. Can be disabled in most cases. Must be disabled if the analysis area contains texture, or if the noise is known to be IID (independent and identically distributed), for example if this is only sensor noise and lossless compression is used, and not grain or compression noise."
290 #define kParamGainVeryLowLabel "Very Low Gain"
291 #define kParamGainVeryLowHint "Gain to apply to the very low frequency noise level thresholds. 0 means no denoising, 1 means use the estimated thresholds multiplied by the channel Gain and the global Noise Level Gain."
292 
293 #define kParamAdaptiveRadius "adaptiveRadius"
294 #define kParamAdaptiveRadiusLabel "Adaptive Radius"
295 #define kParamAdaptiveRadiusHint "Radius of the window where the signal level is analyzed at each scale. If zero, the signal level is computed from the whole image, which may excessively blur the edges if the image has many flat color areas. A reasonable value should to be in the range 2-4."
296 #define kParamAdaptiveRadiusDefault 4
297 
298 #define kGroupChannelTuning "channelTuning"
299 #define kGroupChannelTuningLabel "Channel Tuning"
300 #define kParamChannelGainHint "Gain to apply to the thresholds for this channel. 0 means no denoising, 1 means use the estimated thresholds multiplied by the per-frequency gain and the global Noise Level Gain."
301 #define kParamYLRGain "ylrGain"
302 #define kParamYLRGainLabel "Y/L/R Gain"
303 #define kParamYGainLabel "Y Gain"
304 #define kParamLGainLabel "L Gain"
305 #define kParamRGainLabel "R Gain"
306 #define kParamCbAGGain "cbagGain"
307 #define kParamCbAGGainLabel "Cb/A/G Gain"
308 #define kParamCbGainLabel "Cb Gain"
309 #define kParamAGainLabel "A Gain"
310 #define kParamGGainLabel "G Gain"
311 #define kParamCrBBGain "crbbGain"
312 #define kParamCrBBGainLabel "Cr/B/B Gain"
313 #define kParamCrGainLabel "Cr Gain"
314 #define kParamBGainLabel "B Gain"
315 #define kParamAlphaGain "alphaGain"
316 #define kParamAlphaGainLabel "Alpha Gain"
317 
318 #define kParamAmountHint "The amount of denoising to apply to the specified channel. 0 means no denoising, between 0 and 1 does a soft thresholding of below the thresholds, thus keeping some noise, and 1 applies the threshold strictly and removes everything below the thresholds. This should be used only if you want to keep some noise, for example for noise matching. This value is multiplied by the global Denoise Amount. Remember that the thresholds are multiplied by the per-frequency gain, the channel gain, and the Noise Level Gain first."
319 #define kParamYLRAmount "ylrAmount"
320 #define kParamYLRAmountLabel "Y/L/R Amount"
321 #define kParamYAmountLabel "Y Amount"
322 #define kParamLAmountLabel "L Amount"
323 #define kParamRAmountLabel "R Amount"
324 #define kParamCbAGAmount "cbagAmount"
325 #define kParamCbAGAmountLabel "Cb/A/G Amount"
326 #define kParamCbAmountLabel "Cb Amount"
327 #define kParamAAmountLabel "A Amount"
328 #define kParamGAmountLabel "G Amount"
329 #define kParamCrBBAmount "crbbAmount"
330 #define kParamCrBBAmountLabel "Cr/B/B Amount"
331 #define kParamCrAmountLabel "Cr Amount"
332 #define kParamBAmountLabel "B Amount"
333 #define kParamAlphaAmount "alphaAmount"
334 #define kParamAlphaAmountLabel "Alpha Amount"
335 
336 
337 #define kGroupSharpen "sharpen"
338 #define kGroupSharpenLabel "Sharpen"
339 
340 #define kParamSharpenAmount "sharpenAmount"
341 #define kParamSharpenAmountLabel "Sharpen Amount"
342 #define kParamSharpenAmountHint "Adjusts the amount of sharpening applied. Be careful that only components that are above the noise levels are enhanced, so the noise level gain parameters are very important for proper sharpening. For example, if 'Noise Level Gain' is set to zero (0), then noise is sharpened as well as signal. If the 'Noise Level Gain' is set to one (1), only signal is sharpened. In order to sharpen without denoising, set the 'Denoise Amount' parameter to zero (0)."
343 
344 // see setup() for the difference between this and the GIMP wavelet sharpen's radius
345 #define kParamSharpenSize "sharpenSize"
346 #define kParamSharpenSizeLabel "Sharpen Size"
347 #define kParamSharpenSizeHint "Adjusts the size of the sharpening. For very unsharp images it is recommended to use higher values. Default is 10."
348 
349 #define kParamSharpenLuminance "sharpenLuminance"
350 #define kParamSharpenLuminanceLabel "Sharpen Y Only"
351 #define kParamSharpenLuminanceHint "Sharpens luminance only (if colormodel is R'G'B', sharpen only RGB). This avoids color artifacts to appear. Colour sharpness in natural images is not critical for the human eye."
352 
353 #define kParamPremultChanged "premultChanged"
354 
355 // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
356 #define kParamDefaultsNormalised "defaultsNormalised"
357 
358 #define kLevelMax 4 // 7 // maximum level for denoising
359 
360 #define kProgressAnalysis // define to enable progress for analysis
361 //#define kProgressRender // define to enable progress for render
362 
363 
364 #ifdef kProgressAnalysis
365 #define progressStartAnalysis(x) progressStart(x)
366 #define progressUpdateAnalysis(x) progressUpdate(x)
367 #define progressEndAnalysis() progressEnd()
368 #else
369 #define progressStartAnalysis(x) unused(x)
370 #define progressUpdateAnalysis(x) unused(x)
371 #define progressEndAnalysis() ( (void)0 )
372 #endif
373 
374 #ifdef kProgressRender
375 #define progressStartRender(x) progressStart(x)
376 #define progressUpdateRender(x) progressUpdate(x)
377 #define progressEndRender() progressEnd()
378 #else
379 #define progressStartRender(x) unused(x)
380 #define progressUpdateRender(x) unused(x)
381 #define progressEndRender() ( (void)0 )
382 #endif
383 
384 static bool gHostSupportsDefaultCoordinateSystem = true; // for kParamDefaultsNormalised
385 
386 // those are the noise levels on HHi subands that correspond to a
387 // Gaussian noise, with the dcraw "a trous" wavelets.
388 // dcraw's version:
389 //static const float noise[] = { 0.8002,   0.2735,   0.1202,   0.0585,    0.0291,    0.0152,    0.0080,     0.0044 };
390 // my version (use a NoiseCImg with sigma=1 on input, and uncomment the printf below to get stdev
391 //static const float noise[] = { 0.800519, 0.272892, 0.119716, 0.0577944, 0.0285969, 0.0143022, 0.00723830, 0.00372276 };
392 //static const float noise[] = { 0.800635, 0.272677, 0.119736, 0.0578772, 0.0288094, 0.0143987, 0.00715343, 0.00360457 };
393 //static const float noise[] = { 0.800487, 0.272707, 0.11954,  0.0575443, 0.0285203, 0.0142214, 0.00711241, 0.00362163 };
394 //static const float noise[] = { 0.800539, 0.273004, 0.119987, 0.0578018, 0.0285823, 0.0143751, 0.00710835, 0.00360699 };
395 //static const float noise[] = { 0.800521, 0.272831, 0.119881, 0.0578049, 0.0287941, 0.0144411, 0.00739661, 0.00370236 };
396 //static const float noise[] = { 0.800543, 0.272880, 0.119764, 0.0577759, 0.0285594, 0.0143134, 0.00717619, 0.00366561 };
397 //static const float noise[] = { 0.800370, 0.272859, 0.119750, 0.0577506, 0.0285429, 0.0144341, 0.00733049, 0.00362141 };
398 static const float noise[] = { 0.8005f,   0.2729f,   0.1197f,   0.0578f,    0.0286f,    0.0144f,    0.0073f,     0.0037f };
399 
400 // for B3 Splines, the noise levels are different
401 //static const float noise_b3[] = { 0.890983, 0.200605, 0.0855252, 0.0412078, 0.0204200, 0.0104461, 0.00657528, 0.00447530 };
402 //static const float noise_b3[] = { 0.890774, 0.200587, 0.0855374, 0.0411216, 0.0205889, 0.0104974, 0.00661727, 0.00445607 };
403 //static const float noise_b3[] = { 0.890663, 0.200599, 0.0854052, 0.0412852, 0.0207739, 0.0104784, 0.00634701, 0.00447869  };
404 //static const float noise_b3[] = { 0.890611, 0.200791, 0.0856202, 0.0412572, 0.0206385, 0.0103060, 0.00653794, 0.00458579  };
405 //static const float noise_b3[] = { 0.890800, 0.200619, 0.0856033, 0.0412239, 0.0206324, 0.0104488, 0.00664716, 0.00440302  };
406 //static const float noise_b3[] = { 0.890912, 0.200739, 0.0856778, 0.0412566, 0.0205922, 0.0103516, 0.00650336, 0.00445504  };
407 static const float noise_b3[] = { 0.8908f,   0.2007f,   0.0855f,    0.0412f,    0.0206f,    0.0104f,    0.0065f,     0.0045f  };
408 
409 #if defined(_OPENMP)
410 #define abort_test() if ( !omp_get_thread_num() && abort() ) { throwSuiteStatusException(kOfxStatFailed); }
411 #define abort_test_loop() if ( abort() ) { if ( !omp_get_thread_num() ) {throwSuiteStatusException(kOfxStatFailed);} \
412                                            else { continue;} \
413 }
414 #else
415 #define abort_test() if ( abort() ) { throwSuiteStatusException(kOfxStatFailed); }
416 #define abort_test_loop() abort_test()
417 #endif
418 
419 static Color::LutManager<Mutex>* gLutManager;
420 
421 template<typename T>
422 static inline void
unused(const T &)423 unused(const T&) {}
424 
425 static
426 const char*
fToParam(unsigned f)427 fToParam(unsigned f)
428 {
429     switch (f) {
430     case 0:
431 
432         return kParamHigh;
433     case 1:
434 
435         return kParamMedium;
436     case 2:
437 
438         return kParamLow;
439     case 3:
440 
441         return kParamVeryLow;
442     default:
443 
444         return "";
445     }
446 }
447 
448 static
449 const char*
fToLabel(unsigned f)450 fToLabel(unsigned f)
451 {
452     switch (f) {
453     case 0:
454 
455         return kParamNoiseLevelHighLabel;
456     case 1:
457 
458         return kParamNoiseLevelMediumLabel;
459     case 2:
460 
461         return kParamNoiseLevelLowLabel;
462     case 3:
463 
464         return kParamNoiseLevelVeryLowLabel;
465     default:
466 
467         return "";
468     }
469 }
470 
471 static
472 std::string
channelParam(unsigned c,unsigned f)473 channelParam(unsigned c,
474              unsigned f)
475 {
476     const char* fstr = fToParam(f);
477 
478     if (c == 3) {
479     }
480     switch (c) {
481     case 0:
482 
483         return std::string(kParamYLRNoiseLevel) + fstr;
484     case 1:
485 
486         return std::string(kParamCbAGNoiseLevel) + fstr;
487     case 2:
488 
489         return std::string(kParamCrBBNoiseLevel) + fstr;
490     case 3:
491 
492         return std::string(kParamAlphaNoiseLevel) + fstr;
493     default:
494         break;
495     }
496     assert(false);
497 
498     return std::string();
499 }
500 
501 static
502 std::string
enableParam(unsigned f)503 enableParam(unsigned f)
504 {
505     const char* fstr = fToParam(f);
506 
507     return std::string(kParamEnable) + fstr;
508 }
509 
510 static
511 std::string
gainParam(unsigned f)512 gainParam(unsigned f)
513 {
514     const char* fstr = fToParam(f);
515 
516     return std::string(kParamGain) + fstr;
517 }
518 
519 static
520 std::string
channelLabel(ColorModelEnum e,unsigned c,unsigned f)521 channelLabel(ColorModelEnum e,
522              unsigned c,
523              unsigned f)
524 {
525     const char* fstr = fToLabel(f);
526 
527     if (c == 3) {
528         return std::string(kParamAlphaNoiseLevelLabel) + fstr;
529     }
530     switch (e) {
531     case eColorModelYCbCr:
532         switch (c) {
533         case 0:
534 
535             return std::string(kParamYNoiseLevelLabel) + fstr;
536         case 1:
537 
538             return std::string(kParamCbNoiseLevelLabel) + fstr;
539         case 2:
540 
541             return std::string(kParamCrNoiseLevelLabel) + fstr;
542         default:
543             break;
544         }
545         break;
546 
547     case eColorModelLab:
548         switch (c) {
549         case 0:
550 
551             return std::string(kParamLNoiseLevelLabel) + fstr;
552         case 1:
553 
554             return std::string(kParamANoiseLevelLabel) + fstr;
555         case 2:
556 
557             return std::string(kParamBNoiseLevelLabel) + fstr;
558         default:
559             break;
560         }
561         break;
562 
563     case eColorModelRGB:
564     case eColorModelLinearRGB:
565         switch (c) {
566         case 0:
567 
568             return std::string(kParamRNoiseLevelLabel) + fstr;
569         case 1:
570 
571             return std::string(kParamGNoiseLevelLabel) + fstr;
572         case 2:
573 
574             return std::string(kParamBNoiseLevelLabel) + fstr;
575         default:
576             break;
577         }
578         break;
579 
580     case eColorModelAny:
581         switch (c) {
582         case 0:
583 
584             return std::string(kParamYLRNoiseLevelLabel) + fstr;
585         case 1:
586 
587             return std::string(kParamCbAGNoiseLevelLabel) + fstr;
588         case 2:
589 
590             return std::string(kParamCrBBNoiseLevelLabel) + fstr;
591         default:
592             break;
593         }
594         break;
595     } // switch
596     assert(false);
597 
598     return std::string();
599 } // channelLabel
600 
601 static
602 const char*
amountLabel(ColorModelEnum e,unsigned c)603 amountLabel(ColorModelEnum e,
604             unsigned c)
605 {
606     if (c == 3) {
607         return kParamAlphaAmountLabel;
608     }
609     switch (e) {
610     case eColorModelYCbCr:
611         switch (c) {
612         case 0:
613 
614             return kParamYAmountLabel;
615         case 1:
616 
617             return kParamCbAmountLabel;
618         case 2:
619 
620             return kParamCrAmountLabel;
621         default:
622             break;
623         }
624         break;
625 
626     case eColorModelLab:
627         switch (c) {
628         case 0:
629 
630             return kParamLAmountLabel;
631         case 1:
632 
633             return kParamAAmountLabel;
634         case 2:
635 
636             return kParamBAmountLabel;
637         default:
638             break;
639         }
640         break;
641 
642     case eColorModelRGB:
643     case eColorModelLinearRGB:
644         switch (c) {
645         case 0:
646 
647             return kParamRAmountLabel;
648         case 1:
649 
650             return kParamGAmountLabel;
651         case 2:
652 
653             return kParamBAmountLabel;
654         default:
655             break;
656         }
657         break;
658 
659     case eColorModelAny:
660         switch (c) {
661         case 0:
662 
663             return kParamYLRAmountLabel;
664         case 1:
665 
666             return kParamCbAGAmountLabel;
667         case 2:
668 
669             return kParamCrBBAmountLabel;
670         default:
671             break;
672         }
673         break;
674     } // switch
675     assert(false);
676 
677     return "";
678 } // amountLabel
679 
680 static
681 const char*
channelGainLabel(ColorModelEnum e,unsigned c)682 channelGainLabel(ColorModelEnum e,
683                  unsigned c)
684 {
685     if (c == 3) {
686         return kParamAlphaGainLabel;
687     }
688     switch (e) {
689     case eColorModelYCbCr:
690         switch (c) {
691         case 0:
692 
693             return kParamYGainLabel;
694         case 1:
695 
696             return kParamCbGainLabel;
697         case 2:
698 
699             return kParamCrGainLabel;
700         default:
701             break;
702         }
703         break;
704 
705     case eColorModelLab:
706         switch (c) {
707         case 0:
708 
709             return kParamLGainLabel;
710         case 1:
711 
712             return kParamAGainLabel;
713         case 2:
714 
715             return kParamBGainLabel;
716         default:
717             break;
718         }
719         break;
720 
721     case eColorModelRGB:
722     case eColorModelLinearRGB:
723         switch (c) {
724         case 0:
725 
726             return kParamRGainLabel;
727         case 1:
728 
729             return kParamGGainLabel;
730         case 2:
731 
732             return kParamBGainLabel;
733         default:
734             break;
735         }
736         break;
737 
738     case eColorModelAny:
739         switch (c) {
740         case 0:
741 
742             return kParamYLRGainLabel;
743         case 1:
744 
745             return kParamCbAGGainLabel;
746         case 2:
747 
748             return kParamCrBBGainLabel;
749         default:
750             break;
751         }
752         break;
753     } // switch
754     assert(false);
755 
756     return "";
757 } // channelGainLabel
758 
759 ////////////////////////////////////////////////////////////////////////////////
760 /** @brief The plugin that does our work */
761 class DenoiseSharpenPlugin
762     : public ImageEffect
763 {
764     struct Params;
765 
766 public:
767 
768     /** @brief ctor */
DenoiseSharpenPlugin(OfxImageEffectHandle handle)769     DenoiseSharpenPlugin(OfxImageEffectHandle handle)
770         : ImageEffect(handle)
771         , _lut( gLutManager->Rec709Lut() ) // TODO: work in different colorspaces
772         , _dstClip(NULL)
773         , _srcClip(NULL)
774         , _maskClip(NULL)
775         , _analysisSrcClip(NULL)
776         , _analysisMaskClip(NULL)
777         , _processR(NULL)
778         , _processG(NULL)
779         , _processB(NULL)
780         , _processA(NULL)
781         , _colorModel(NULL)
782         , _premult(NULL)
783         , _premultChannel(NULL)
784         , _mix(NULL)
785         , _maskApply(NULL)
786         , _maskInvert(NULL)
787         , _premultChanged(NULL)
788     {
789         _dstClip = fetchClip(kOfxImageEffectOutputClipName);
790         assert( _dstClip && (_dstClip->getPixelComponents() == ePixelComponentRGB ||
791                              _dstClip->getPixelComponents() == ePixelComponentRGBA ||
792                              _dstClip->getPixelComponents() == ePixelComponentAlpha) );
793         _srcClip = getContext() == eContextGenerator ? NULL : fetchClip(kOfxImageEffectSimpleSourceClipName);
794         assert( (!_srcClip && getContext() == eContextGenerator) ||
795                 ( _srcClip && (_srcClip->getPixelComponents() == ePixelComponentRGB ||
796                                _srcClip->getPixelComponents() == ePixelComponentRGBA ||
797                                _srcClip->getPixelComponents() == ePixelComponentAlpha) ) );
798         _maskClip = fetchClip(getContext() == eContextPaint ? "Brush" : "Mask");
799         assert(!_maskClip || _maskClip->getPixelComponents() == ePixelComponentAlpha);
800         _analysisSrcClip = fetchClip(kClipAnalysisSource);
801         assert( ( _analysisSrcClip && (_analysisSrcClip->getPixelComponents() == ePixelComponentRGB ||
802                                        _analysisSrcClip->getPixelComponents() == ePixelComponentRGBA ||
803                                        _analysisSrcClip->getPixelComponents() == ePixelComponentAlpha) ) );
804         _analysisMaskClip = fetchClip(kClipAnalysisMask);
805         assert(!_analysisMaskClip || _analysisMaskClip->getPixelComponents() == ePixelComponentAlpha);
806 
807         _premult = fetchBooleanParam(kParamPremult);
808         _premultChannel = fetchChoiceParam(kParamPremultChannel);
809         assert(_premult && _premultChannel);
810         _mix = fetchDoubleParam(kParamMix);
811         _maskApply = ( ofxsMaskIsAlwaysConnected( OFX::getImageEffectHostDescription() ) && paramExists(kParamMaskApply) ) ? fetchBooleanParam(kParamMaskApply) : 0;
812         _maskInvert = fetchBooleanParam(kParamMaskInvert);
813         assert(_mix && _maskInvert);
814 
815         _processR = fetchBooleanParam(kParamProcessR);
816         _processG = fetchBooleanParam(kParamProcessG);
817         _processB = fetchBooleanParam(kParamProcessB);
818         _processA = fetchBooleanParam(kParamProcessA);
819         assert(_processR && _processG && _processB && _processA);
820 
821 
822         // fetch noise parameters
823         _outputMode = fetchChoiceParam(kParamOutputMode);
824         _colorModel = fetchChoiceParam(kParamColorModel);
825         _analysisLock = fetchBooleanParam(kParamAnalysisLock);
826         _btmLeft = fetchDouble2DParam(kParamRectangleInteractBtmLeft);
827         _size = fetchDouble2DParam(kParamRectangleInteractSize);
828         _analysisFrame = fetchIntParam(kParamAnalysisFrame);
829         _analyze = fetchPushButtonParam(kParamAnalyzeNoiseLevels);
830 
831         // noise levels
832         for (unsigned f = 0; f < 4; ++f) {
833             for (unsigned c = 0; c < 4; ++c) {
834                 _noiseLevel[c][f] = fetchDoubleParam( channelParam(c, f) );
835             }
836         }
837 
838         _adaptiveRadius = fetchIntParam(kParamAdaptiveRadius);
839 
840         _noiseLevelGain = fetchDoubleParam(kParamNoiseLevelGain);
841 
842         _denoiseAmount = fetchDoubleParam(kParamDenoiseAmount);
843 
844         // frequency tuning
845         for (unsigned int f = 0; f < 4; ++f) {
846             _enableFreq[f] = fetchBooleanParam( enableParam(f) );
847             _gainFreq[f] = fetchDoubleParam( gainParam(f) );
848         }
849 
850         // channel tuning
851         for (unsigned c = 0; c < 4; ++c) {
852             _channelGain[c] = fetchDoubleParam( ( c == 0 ? kParamYLRGain :
853                                                   ( c == 1 ? kParamCbAGGain :
854                                                     (c == 2 ? kParamCrBBGain :
855                                                      kParamAlphaGain) ) ) );
856             _amount[c] = fetchDoubleParam( ( c == 0 ? kParamYLRAmount :
857                                              ( c == 1 ? kParamCbAGAmount :
858                                                (c == 2 ? kParamCrBBAmount :
859                                                 kParamAlphaAmount) ) ) );
860         }
861 
862         // sharpen
863         _sharpenAmount = fetchDoubleParam(kParamSharpenAmount);
864         _sharpenSize = fetchDoubleParam(kParamSharpenSize);
865         _sharpenLuminance = fetchBooleanParam(kParamSharpenLuminance);
866 
867         _premultChanged = fetchBooleanParam(kParamPremultChanged);
868         assert(_premultChanged);
869 
870         _b3 = fetchBooleanParam(kParamB3);
871 
872         // honor kParamDefaultsNormalised
873         if ( paramExists(kParamDefaultsNormalised) ) {
874             // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
875             // handle these ourselves!
876             BooleanParam* param = fetchBooleanParam(kParamDefaultsNormalised);
877             assert(param);
878             bool normalised = param->getValue();
879             if (normalised) {
880                 OfxPointD size = getProjectExtent();
881                 OfxPointD origin = getProjectOffset();
882                 OfxPointD p;
883                 // we must denormalise all parameters for which setDefaultCoordinateSystem(eCoordinatesNormalised) couldn't be done
884                 beginEditBlock(kParamDefaultsNormalised);
885                 p = _btmLeft->getValue();
886                 _btmLeft->setValue(p.x * size.x + origin.x, p.y * size.y + origin.y);
887                 p = _size->getValue();
888                 _size->setValue(p.x * size.x, p.y * size.y);
889                 param->setValue(false);
890                 endEditBlock();
891             }
892         }
893 
894         // finally
895         syncPrivateData();
896     }
897 
898 private:
899     /* Override the render */
900     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
901 
902     template<int nComponents>
903     void renderForComponents(const RenderArguments &args);
904 
905     template <class PIX, int nComponents, int maxValue>
906     void renderForBitDepth(const RenderArguments &args);
907 
908     void setup(const RenderArguments &args,
909                auto_ptr<const Image>& src,
910                auto_ptr<Image>& dst,
911                auto_ptr<const Image>& mask,
912                Params& p);
913 
914     // override the roi call
915     virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;
916     virtual bool isIdentity(const IsIdentityArguments &args, Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE FINAL;
917 
918     /** @brief called when a clip has just been changed in some way (a rewire maybe) */
919     virtual void changedClip(const InstanceChangedArgs &args, const std::string &clipName) OVERRIDE FINAL;
920     virtual void changedParam(const InstanceChangedArgs &args, const std::string &paramName) OVERRIDE FINAL;
921 
922     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)923     virtual void syncPrivateData(void) OVERRIDE FINAL
924     {
925         // update the channel labels
926         updateLabels();
927         updateSecret();
928         analysisLock();
929     }
930 
931     void analyzeNoiseLevels(const InstanceChangedArgs &args);
932 
933     template<int nComponents>
934     void analyzeNoiseLevelsForComponents(const InstanceChangedArgs &args);
935 
936     template <class PIX, int nComponents, int maxValue>
937     void analyzeNoiseLevelsForBitDepth(const InstanceChangedArgs &args);
938 
939     void updateLabels();
940 
941     void updateSecret();
942 
943     void wavelet_denoise(float *fimg[3], //!< fimg[0] is the channel to process with intensities between 0. and 1., of size iwidth*iheight, fimg[1] and fimg[2] are working space images of the same size
944                          unsigned int iwidth, //!< width of the image
945                          unsigned int iheight, //!< height of the image
946                          bool b3,
947                          const double noiselevels[4], //!< noise levels for high/medium/low/very low frequencies
948                          int adaptiveRadius,
949                          double denoise_amount, //!< amount parameter
950                          double sharpen_amount, //!< constrast boost amount
951                          double sharpen_radius, //!< contrast boost radius
952                          int startLevel,
953                          float a, // progress amount at start
954                          float b); // progress increment
955 
956     void sigma_mad(float *fimg[2], //!< fimg[0] is the channel to process with intensities between 0. and 1., of size iwidth*iheight, fimg[1] is a working space image of the same size
957                    bool *bimgmask,
958                    unsigned int iwidth, //!< width of the image
959                    unsigned int iheight, //!< height of the image
960                    bool b3,
961                    double noiselevels[4], //!< output: the sigma for each frequency
962                    float a, //!< progress amount at start
963                    float b); //!< progress increment
964 
analysisLock()965     void analysisLock()
966     {
967         bool locked = _analysisLock->getValue();
968 
969         // unlock the output mode
970         _outputMode->setEnabled( locked );
971         // lock the color model
972         _colorModel->setEnabled( !locked );
973         _b3->setEnabled( !locked );
974         // disable the interact
975         _btmLeft->setEnabled( !locked );
976         _size->setEnabled( !locked );
977         // lock the noise levels
978         for (unsigned f = 0; f < 4; ++f) {
979             for (unsigned c = 0; c < 4; ++c) {
980                 _noiseLevel[c][f]->setEnabled( !locked );
981             }
982         }
983         _analyze->setEnabled( !locked );
984     }
985 
986 private:
987     struct Params
988     {
989         bool doMasking;
990         bool maskInvert;
991         bool analysisLock;
992         bool premult;
993         int premultChannel;
994         double mix;
995         OutputModeEnum outputMode;
996         ColorModelEnum colorModel;
997         bool b3;
998         int startLevel;
999         bool process[4];
1000         double noiseLevel[4][4]; // first index: channel second index: frequency
1001         int adaptiveRadius;
1002         double denoise_amount[4];
1003         double sharpen_amount[4];
1004         double sharpen_radius;
1005         OfxRectI srcWindow;
1006 
ParamsDenoiseSharpenPlugin::Params1007         Params()
1008             : doMasking(false)
1009             , maskInvert(false)
1010             , analysisLock(false)
1011             , premult(false)
1012             , premultChannel(3)
1013             , mix(1.)
1014             , outputMode(eOutputModeResult)
1015             , colorModel(eColorModelYCbCr)
1016             , b3(false)
1017             , startLevel(0)
1018             , adaptiveRadius(0)
1019             , sharpen_radius(0.5)
1020         {
1021             for (unsigned int c = 0; c < 4; ++c) {
1022                 process[c] = true;
1023                 for (unsigned int f = 0; f < 4; ++f) {
1024                     noiseLevel[c][f] = 0.;
1025                 }
1026                 denoise_amount[c] = 0.;
1027                 sharpen_amount[c] = 0.;
1028             }
1029             srcWindow.x1 = srcWindow.x2 = srcWindow.y1 = srcWindow.y2 = 0;
1030         }
1031     };
1032 
1033     const Color::Lut* _lut;
1034 
1035     // do not need to delete these, the ImageEffect is managing them for us
1036     Clip *_dstClip;
1037     Clip *_srcClip;
1038     Clip *_maskClip;
1039     Clip *_analysisSrcClip;
1040     Clip *_analysisMaskClip;
1041     BooleanParam* _processR;
1042     BooleanParam* _processG;
1043     BooleanParam* _processB;
1044     BooleanParam* _processA;
1045     ChoiceParam* _outputMode;
1046     ChoiceParam* _colorModel;
1047     BooleanParam* _analysisLock;
1048     Double2DParam* _btmLeft;
1049     Double2DParam* _size;
1050     IntParam* _analysisFrame;
1051     PushButtonParam* _analyze;
1052     DoubleParam* _noiseLevel[4][4];
1053     IntParam* _adaptiveRadius;
1054     DoubleParam* _noiseLevelGain;
1055     DoubleParam* _denoiseAmount;
1056     BooleanParam* _enableFreq[4];
1057     DoubleParam* _gainFreq[4];
1058     DoubleParam* _channelGain[4];
1059     DoubleParam* _amount[4];
1060     DoubleParam* _sharpenAmount;
1061     DoubleParam* _sharpenSize;
1062     BooleanParam* _sharpenLuminance;
1063     BooleanParam* _premult;
1064     ChoiceParam* _premultChannel;
1065     DoubleParam* _mix;
1066     BooleanParam* _maskApply;
1067     BooleanParam* _maskInvert;
1068     BooleanParam* _premultChanged; // set to true the first time the user connects src
1069     BooleanParam* _b3;
1070 };
1071 
1072 // compute the maximum level used in wavelet_denoise (not the number of levels)
1073 inline
1074 int
startLevelFromRenderScale(const OfxPointD & renderScale)1075 startLevelFromRenderScale(const OfxPointD& renderScale)
1076 {
1077     double s = std::min(renderScale.x, renderScale.y);
1078 
1079     assert(0. < s && s <= 1.);
1080     int retval = -(int)std::floor(std::log(s) / M_LN2);
1081     assert(retval >= 0);
1082 
1083     return retval;
1084 }
1085 
1086 // functions hat_transform and wavelet_denoise are from LibRaw 0.17.2 (LGPL 2.1) with local modifications.
1087 
1088 // h = (0.25,0.5,0.25) linear Lagrange interpolation, with mirroring at the edges.
1089 // could be made edge-aware, maybe?
1090 // - https://www.darktable.org/wp-content/uploads/2011/11/hdl11_talk.pdf
1091 // - https://jo.dreggn.org/home/2011_atrous.pdf
1092 // for the edge-avoiding a trous, just multiply the side coefficients by
1093 // exp(-(dist2/(2.f*sigma_r*sigma_r)));
1094 // where dist2 is the squared color distance with the center, and sigma_r = 0.1
1095 static
1096 void
hat_transform_linear(float * temp,const float * base,int st,int size,int sc)1097 hat_transform_linear (float *temp, //!< output vector
1098                       const float *base, //!< input vector
1099                       int st, //!< input stride (1 for line, iwidth for column)
1100                       int size, //!< vector size
1101                       int sc) //!< scale
1102 {
1103     assert(sc - 1 + sc < size);
1104     int i;
1105     for (i = 0; i < sc; ++i) {
1106         temp[i] = (2 * base[st * i] + base[st * (sc - i)] + base[st * (i + sc)]) / 4;
1107     }
1108     for (; i + sc < size; ++i) {
1109         temp[i] = (2 * base[st * i] + base[st * (i - sc)] + base[st * (i + sc)]) / 4;
1110     }
1111     for (; i < size; ++i) {
1112         temp[i] = (2 * base[st * i] + base[st * (i - sc)] + base[st * ( 2 * size - 2 - (i + sc) )]) / 4;
1113     }
1114 }
1115 
1116 // h = (1/16, 1/4, 3/8, 1/4, 1/16) (Murtagh F.:  Multiscale transform methods in data analysis)
1117 static
1118 void
hat_transform_b3(float * temp,const float * base,int st,int size,int sc)1119 hat_transform_b3 (float *temp, //!< output vector
1120                   const float *base, //!< input vector
1121                   int st, //!< input stride (1 for line, iwidth for column)
1122                   int size, //!< vector size
1123                   int sc) //!< scale
1124 {
1125     assert(2 * sc - 1 + 2 * sc < size);
1126     int i;
1127     for (i = 0; i < sc; ++i) {
1128         temp[i] = (6 * base[st * i] + 4 * base[st * (sc - i)] + 4 * base[st * (i + sc)] + 1 * base[st * (2 * sc - i)] + 1 * base[st * (i + 2 * sc)]) / 16;
1129     }
1130     for (; i < 2 * sc; ++i) {
1131         temp[i] = (6 * base[st * i] + 4 * base[st * (i - sc)] + 4 * base[st * (i + sc)] + 1 * base[st * (2 * sc - i)] + 1 * base[st * (i + 2 * sc)]) / 16;
1132     }
1133     for (; i + 2 * sc < size; ++i) {
1134         temp[i] = (6 * base[st * i] + 4 * base[st * (i - sc)] + 4 * base[st * (i + sc)] + 1 * base[st * (i - 2 * sc)] + 1 * base[st * (i + 2 * sc)]) / 16;
1135     }
1136     for (; i + sc < size; ++i) {
1137         temp[i] = (6 * base[st * i] + 4 * base[st * (i - sc)] + 4 * base[st * (i + sc)] + 1 * base[st * (i - 2 * sc)] + 1 * base[st * ( 2 * size - 2 - (i + 2 * sc) )]) / 16;
1138     }
1139     for (; i < size; ++i) {
1140         temp[i] = (6 * base[st * i] + 4 * base[st * (i - sc)] + 4 * base[st * ( 2 * size - 2 - (i + sc) )] + 1 * base[st * (i - 2 * sc)] + 1 * base[st * ( 2 * size - 2 - (i + 2 * sc) )]) / 16;
1141     }
1142 }
1143 
1144 static
1145 void
hat_transform(float * temp,const float * base,int st,int size,bool b3,int sc)1146 hat_transform (float *temp, //!< output vector
1147                const float *base, //!< input vector
1148                int st, //!< input stride (1 for line, iwidth for column)
1149                int size, //!< vector size
1150                bool b3,
1151                int sc) //!< scale
1152 {
1153     if (b3) {
1154         hat_transform_b3(temp, base, st, size, sc);
1155     } else {
1156         hat_transform_linear(temp, base, st, size, sc);
1157     }
1158 }
1159 
1160 #ifdef kUseMultithread
1161 
1162 // multithread processing classes for various stages of the algorithm
1163 template<bool rows>
1164 class ProcessRowsColsBase
1165     : public MultiThread::Processor
1166 {
1167 public:
ProcessRowsColsBase(ImageEffect & instance,float * fimg_hpass,float * fimg_lpass,unsigned int iwidth,unsigned int iheight,bool b3,int sc)1168     ProcessRowsColsBase(ImageEffect &instance,
1169                         float* fimg_hpass,
1170                         float* fimg_lpass,
1171                         unsigned int iwidth,
1172                         unsigned int iheight,
1173                         bool b3,
1174                         int sc) // 1 << lev
1175         : _effect(instance)
1176         , _fimg_hpass(fimg_hpass)
1177         , _fimg_lpass(fimg_lpass)
1178         , _iwidth(iwidth)
1179         , _iheight(iheight)
1180         , _b3(b3)
1181         , _sc(sc)
1182     {
1183         assert(_fimg_hpass && _fimg_lpass && _iwidth > 0 && _iheight > 0 && sc > 0);
1184     }
1185 
1186     /** @brief called to process everything */
process(void)1187     void process(void)
1188     {
1189         // make sure there are at least 4096 pixels per CPU and at least 1 line par CPU
1190         unsigned int nCPUs = ( std::min(rows ? _iwidth : _iheight, 4096u) * (rows ? _iheight : _iwidth) ) / 4096u;
1191 
1192         // make sure the number of CPUs is valid (and use at least 1 CPU)
1193         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1194 
1195         // call the base multi threading code, should put a pre & post thread calls in too
1196         multiThread(nCPUs);
1197     }
1198 
1199 protected:
1200     ImageEffect &_effect;      /**< @brief effect to render with */
1201     float * const _fimg_hpass;
1202     float * const _fimg_lpass;
1203     unsigned int const _iwidth;
1204     unsigned int const _iheight;
1205     bool const _b3;
1206     int const _sc;
1207 };
1208 
1209 class SmoothRows
1210     : public ProcessRowsColsBase<true>
1211 {
1212 public:
SmoothRows(ImageEffect & instance,float * fimg_hpass,float * fimg_lpass,unsigned int iwidth,unsigned int iheight,bool b3,int sc)1213     SmoothRows(ImageEffect &instance,
1214                float* fimg_hpass,
1215                float* fimg_lpass,
1216                unsigned int iwidth,
1217                unsigned int iheight,
1218                bool b3,
1219                int sc) // 1 << lev
1220         : ProcessRowsColsBase<true>(instance, fimg_hpass, fimg_lpass, iwidth, iheight, b3, sc)
1221     {
1222     }
1223 
1224 private:
1225     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1226     virtual void multiThreadFunction(unsigned int threadID,
1227                                      unsigned int nThreads) OVERRIDE FINAL
1228     {
1229         int row_begin = 0;
1230         int row_end = 0;
1231 
1232         MultiThread::getThreadRange(threadID, nThreads, 0, _iheight, &row_begin, &row_end);
1233         if (row_end <= row_begin) {
1234             return;
1235         }
1236         std::vector<float> temp(_iwidth);
1237         for (int row = row_begin; row < row_end; ++row) {
1238             if ( _effect.abort() ) {
1239                 return;
1240             }
1241             hat_transform (&temp[0], _fimg_hpass + row * _iwidth, 1, _iwidth, _b3, _sc);
1242             for (unsigned int col = 0; col < _iwidth; ++col) {
1243                 unsigned int i = row * _iwidth + col;
1244                 _fimg_lpass[i] = temp[col];
1245             }
1246         }
1247     }
1248 };
1249 
1250 class SmoothColsSumSq
1251     : public ProcessRowsColsBase<false>
1252 {
1253 public:
SmoothColsSumSq(ImageEffect & instance,float * fimg_hpass,float * fimg_lpass,unsigned int iwidth,unsigned int iheight,bool b3,int sc,double * sumsq)1254     SmoothColsSumSq(ImageEffect &instance,
1255                     float* fimg_hpass,
1256                     float* fimg_lpass,
1257                     unsigned int iwidth,
1258                     unsigned int iheight,
1259                     bool b3,
1260                     int sc, // 1 << lev
1261                     double* sumsq)
1262         : ProcessRowsColsBase<false>(instance, fimg_hpass, fimg_lpass, iwidth, iheight, b3, sc)
1263         , _sumsq(sumsq)
1264     {
1265     }
1266 
1267 private:
1268     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1269     virtual void multiThreadFunction(unsigned int threadID,
1270                                      unsigned int nThreads) OVERRIDE FINAL
1271     {
1272         int col_begin = 0;
1273         int col_end = 0;
1274 
1275         MultiThread::getThreadRange(threadID, nThreads, 0, _iwidth, &col_begin, &col_end);
1276         if (col_end <= col_begin) {
1277             return;
1278         }
1279         std::vector<float> temp(_iheight);
1280         for (int col = col_begin; col < col_end; ++col) {
1281             if ( _effect.abort() ) {
1282                 return;
1283             }
1284             hat_transform (&temp[0], _fimg_lpass + col, _iwidth, _iheight, _b3, _sc);
1285             double sumsqrow = 0.;
1286             for (unsigned int row = 0; row < _iheight; ++row) {
1287                 unsigned int i = row * _iwidth + col;
1288                 _fimg_lpass[i] = temp[row];
1289                 // compute band-pass image as: (smoothed at this lev)-(smoothed at next lev)
1290                 _fimg_hpass[i] -= _fimg_lpass[i];
1291                 sumsqrow += _fimg_hpass[i] * _fimg_hpass[i];
1292             }
1293             {
1294                 AutoMutex l(&_sumsq_mutex);
1295                 *_sumsq += sumsqrow;
1296             }
1297         }
1298     }
1299 
1300     Mutex _sumsq_mutex;
1301     double *_sumsq;
1302 };
1303 
1304 
1305 class SmoothCols
1306     : public ProcessRowsColsBase<false>
1307 {
1308 public:
SmoothCols(ImageEffect & instance,float * fimg_hpass,float * fimg_lpass,unsigned int iwidth,unsigned int iheight,bool b3,int sc)1309     SmoothCols(ImageEffect &instance,
1310                float* fimg_hpass,
1311                float* fimg_lpass,
1312                unsigned int iwidth,
1313                unsigned int iheight,
1314                bool b3,
1315                int sc) // 1 << lev
1316         : ProcessRowsColsBase<false>(instance, fimg_hpass, fimg_lpass, iwidth, iheight, b3, sc)
1317     {
1318     }
1319 
1320 private:
1321     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1322     virtual void multiThreadFunction(unsigned int threadID,
1323                                      unsigned int nThreads) OVERRIDE FINAL
1324     {
1325         int col_begin = 0;
1326         int col_end = 0;
1327 
1328         MultiThread::getThreadRange(threadID, nThreads, 0, _iwidth, &col_begin, &col_end);
1329         if (col_end <= col_begin) {
1330             return;
1331         }
1332         std::vector<float> temp(_iheight);
1333         for (int col = col_begin; col < col_end; ++col) {
1334             if ( _effect.abort() ) {
1335                 return;
1336             }
1337             hat_transform (&temp[0], _fimg_lpass + col, _iwidth, _iheight, _b3, _sc);
1338             for (unsigned int row = 0; row < _iheight; ++row) {
1339                 unsigned int i = row * _iwidth + col;
1340                 _fimg_lpass[i] = temp[row];
1341                 // compute band-pass image as: (smoothed at this lev)-(smoothed at next lev)
1342                 _fimg_hpass[i] -= _fimg_lpass[i];
1343             }
1344         }
1345     }
1346 };
1347 
1348 class ApplyThreshold
1349     : public MultiThread::Processor
1350 {
1351 public:
ApplyThreshold(ImageEffect & instance,float * fimg_hpass,float * fimg_0,unsigned int size,float thold,double denoise_amount,double beta)1352     ApplyThreshold(ImageEffect &instance,
1353                    float* fimg_hpass,
1354                    float* fimg_0,
1355                    unsigned int size,
1356                    float thold,
1357                    double denoise_amount,
1358                    double beta)
1359         : _effect(instance)
1360         , _fimg_hpass(fimg_hpass)
1361         , _fimg_0(fimg_0)
1362         , _size(size)
1363         , _thold(thold)
1364         , _denoise_amount(denoise_amount)
1365         , _beta(beta)
1366     {
1367         assert(_fimg_hpass && _size > 0);
1368     }
1369 
1370     /** @brief called to process everything */
process(void)1371     void process(void)
1372     {
1373         // make sure there are at least 4096 pixels per CPU
1374         unsigned int nCPUs = _size / 4096u;
1375 
1376         // make sure the number of CPUs is valid (and use at least 1 CPU)
1377         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1378 
1379         // call the base multi threading code, should put a pre & post thread calls in too
1380         multiThread(nCPUs);
1381     }
1382 
1383 private:
1384     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1385     virtual void multiThreadFunction(unsigned int threadID,
1386                                      unsigned int nThreads) OVERRIDE FINAL
1387     {
1388         int i_begin = 0;
1389         int i_end = 0;
1390 
1391         MultiThread::getThreadRange(threadID, nThreads, 0, _size, &i_begin, &i_end);
1392         if (i_end <= i_begin) {
1393             return;
1394         }
1395         if ( _effect.abort() ) {
1396             return;
1397         }
1398         for (int i = i_begin; i < i_end; ++i) {
1399             float fimg_denoised = _fimg_hpass[i];
1400 
1401             // apply smooth threshold
1402             if (_fimg_hpass[i] < -_thold) {
1403                 _fimg_hpass[i] += _thold * _denoise_amount;
1404                 fimg_denoised += _thold;
1405             } else if (_fimg_hpass[i] >  _thold) {
1406                 _fimg_hpass[i] -= _thold * _denoise_amount;
1407                 fimg_denoised -= _thold;
1408             } else {
1409                 _fimg_hpass[i] *= 1. - _denoise_amount;
1410                 fimg_denoised = 0.;
1411             }
1412             // add the denoised band to the final image
1413             if (_fimg_0) { // if (hpass != 0)
1414                 // note: local contrast boost could be applied here, by multiplying fimg[hpass][i] by a factor beta
1415                 // GIMP's wavelet sharpen uses beta = amount * exp (-(lev - radius) * (lev - radius) / 1.5)
1416 
1417                 _fimg_0[i] += _fimg_hpass[i] + _beta * fimg_denoised;
1418             }
1419         }
1420     }
1421 
1422 private:
1423     ImageEffect &_effect;      /**< @brief effect to render with */
1424     float * const _fimg_hpass;
1425     float * const _fimg_0;
1426     unsigned int const _size;
1427     float const _thold;
1428     double const _denoise_amount;
1429     double const _beta;
1430 };
1431 
1432 
1433 class AddLowPass
1434     : public MultiThread::Processor
1435 {
1436 public:
AddLowPass(ImageEffect & instance,float * fimg_0,float * fimg_lpass,unsigned int size)1437     AddLowPass(ImageEffect &instance,
1438                float* fimg_0,
1439                float* fimg_lpass,
1440                unsigned int size)
1441         : _effect(instance)
1442         , _fimg_0(fimg_0)
1443         , _fimg_lpass(fimg_lpass)
1444         , _size(size)
1445     {
1446         assert(_fimg_0 && _fimg_lpass && _size > 0);
1447     }
1448 
1449     /** @brief called to process everything */
process(void)1450     void process(void)
1451     {
1452         // make sure there are at least 4096 pixels per CPU
1453         unsigned int nCPUs = _size / 4096u;
1454 
1455         // make sure the number of CPUs is valid (and use at least 1 CPU)
1456         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1457 
1458         // call the base multi threading code, should put a pre & post thread calls in too
1459         multiThread(nCPUs);
1460     }
1461 
1462 private:
1463     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1464     virtual void multiThreadFunction(unsigned int threadID,
1465                                      unsigned int nThreads) OVERRIDE FINAL
1466     {
1467         int i_begin = 0;
1468         int i_end = 0;
1469 
1470         MultiThread::getThreadRange(threadID, nThreads, 0, _size, &i_begin, &i_end);
1471         if (i_end <= i_begin) {
1472             return;
1473         }
1474         if ( _effect.abort() ) {
1475             return;
1476         }
1477         for (int i = i_begin; i < i_end; ++i) {
1478             _fimg_0[i] += _fimg_lpass[i];
1479         }
1480     }
1481 
1482 private:
1483     ImageEffect &_effect;      /**< @brief effect to render with */
1484     float * const _fimg_0;
1485     float * const _fimg_lpass;
1486     unsigned int const _size;
1487 };
1488 
1489 
1490 // integral images computation
1491 
1492 class IntegralRows
1493     : public MultiThread::Processor
1494 {
1495 public:
IntegralRows(ImageEffect & instance,float const * fimg,float * fimgsumsqrow,unsigned int iwidth,unsigned int iheight)1496     IntegralRows(ImageEffect &instance,
1497                  float const* fimg, // img
1498                  //float* fimgsumrow, // sum along rows
1499                  float* fimgsumsqrow, // sum along rows
1500                  unsigned int iwidth,
1501                  unsigned int iheight) // 1 << lev
1502         : _effect(instance)
1503         , _fimg(fimg)
1504         //, _fimgsumrow(fimgsumrow)
1505         , _fimgsumsqrow(fimgsumsqrow)
1506         , _iwidth(iwidth)
1507         , _iheight(iheight)
1508     {
1509         assert(_fimg && /*_fimgsumrow &&*/ _fimgsumsqrow && _iwidth > 0 && _iheight > 0);
1510     }
1511 
1512     /** @brief called to process everything */
process(void)1513     void process(void)
1514     {
1515         // make sure there are at least 4096 pixels per CPU and at least 1 line par CPU
1516         unsigned int nCPUs = ( std::min(_iwidth, 4096u) * _iheight ) / 4096u;
1517 
1518         // make sure the number of CPUs is valid (and use at least 1 CPU)
1519         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1520 
1521         // call the base multi threading code, should put a pre & post thread calls in too
1522         multiThread(nCPUs);
1523     }
1524 
1525 private:
1526     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1527     virtual void multiThreadFunction(unsigned int threadID,
1528                                      unsigned int nThreads) OVERRIDE FINAL
1529     {
1530         int row_begin = 0;
1531         int row_end = 0;
1532 
1533         MultiThread::getThreadRange(threadID, nThreads, 0, _iheight, &row_begin, &row_end);
1534         if (row_end <= row_begin) {
1535             return;
1536         }
1537         for (int row = row_begin; row < row_end; ++row) {
1538             if ( _effect.abort() ) {
1539                 return;
1540             }
1541             //float prev = 0.;
1542             float prevsq = 0.;
1543             for (unsigned int col = 0; col < _iwidth; ++col) {
1544                 unsigned int i = row * _iwidth + col;
1545                 //prev += _fimg[i];
1546                 //_fimgsumrow[i] = prev;
1547                 prevsq += _fimg[i] * _fimg[i];
1548                 _fimgsumsqrow[i] = prevsq;
1549             }
1550         }
1551     }
1552 
1553 private:
1554     ImageEffect &_effect;      /**< @brief effect to render with */
1555     float const * const _fimg;
1556     //float * const _fimgsumrow;
1557     float * const _fimgsumsqrow;
1558     unsigned int const _iwidth;
1559     unsigned int const _iheight;
1560 };
1561 
1562 class IntegralCols
1563     : public MultiThread::Processor
1564 {
1565 public:
IntegralCols(ImageEffect & instance,float const * fimgsumrow,float * fimgsum,unsigned int iwidth,unsigned int iheight)1566     IntegralCols(ImageEffect &instance,
1567                  float const* fimgsumrow, // sum along rows
1568                  float* fimgsum, // integral image
1569                  unsigned int iwidth,
1570                  unsigned int iheight) // 1 << lev
1571         : _effect(instance)
1572         , _fimgsumrow(fimgsumrow)
1573         , _fimgsum(fimgsum)
1574         , _iwidth(iwidth)
1575         , _iheight(iheight)
1576     {
1577         assert(_fimgsumrow && _fimgsum && _iwidth > 0 && _iheight > 0);
1578     }
1579 
1580     /** @brief called to process everything */
process(void)1581     void process(void)
1582     {
1583         // make sure there are at least 4096 pixels per CPU and at least 1 column per CPU
1584         unsigned int nCPUs = ( std::min(_iheight, 4096u) * _iwidth ) / 4096u;
1585 
1586         // make sure the number of CPUs is valid (and use at least 1 CPU)
1587         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1588 
1589         // call the base multi threading code, should put a pre & post thread calls in too
1590         multiThread(nCPUs);
1591     }
1592 
1593 private:
1594     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1595     virtual void multiThreadFunction(unsigned int threadID,
1596                                      unsigned int nThreads) OVERRIDE FINAL
1597     {
1598         int col_begin = 0;
1599         int col_end = 0;
1600 
1601         MultiThread::getThreadRange(threadID, nThreads, 0, _iwidth, &col_begin, &col_end);
1602         if (col_end <= col_begin) {
1603             return;
1604         }
1605         for (int col = col_begin; col < col_end; ++col) {
1606             if ( _effect.abort() ) {
1607                 return;
1608             }
1609             float prev = 0.;
1610             for (unsigned int row = 0; row < _iheight; ++row) {
1611                 unsigned int i = row * _iwidth + col;
1612                 prev += _fimgsumrow[i];
1613                 _fimgsum[i] = prev;
1614             }
1615         }
1616     }
1617 
1618 private:
1619     ImageEffect &_effect;      /**< @brief effect to render with */
1620     float const * const _fimgsumrow;
1621     float * const _fimgsum;
1622     unsigned int const _iwidth;
1623     unsigned int const _iheight;
1624 };
1625 
1626 class ApplyThresholdAdaptive
1627     : public MultiThread::Processor
1628 {
1629 public:
ApplyThresholdAdaptive(ImageEffect & instance,float * fimg_hpass,float * fimg_0,float * fimg_sat,unsigned int iwidth,unsigned int iheight,int adaptiveRadiusPixel,double sigma_n_i_sq,double denoise_amount,double beta)1630     ApplyThresholdAdaptive(ImageEffect &instance,
1631                            float* fimg_hpass,
1632                            float* fimg_0,
1633                            float* fimg_sat, // summed area table of squared values
1634                            unsigned int iwidth,
1635                            unsigned int iheight,
1636                            int adaptiveRadiusPixel,
1637                            double sigma_n_i_sq,
1638                            double denoise_amount,
1639                            double beta)
1640         : _effect(instance)
1641         , _fimg_hpass(fimg_hpass)
1642         , _fimg_0(fimg_0)
1643         , _fimg_sat(fimg_sat)
1644         , _iwidth(iwidth)
1645         , _iheight(iheight)
1646         , _adaptiveRadiusPixel(adaptiveRadiusPixel)
1647         , _sigma_n_i_sq(sigma_n_i_sq)
1648         , _denoise_amount(denoise_amount)
1649         , _beta(beta)
1650     {
1651         assert(_fimg_hpass && _fimg_sat && _iwidth > 0 && _iheight > 0);
1652     }
1653 
1654     /** @brief called to process everything */
process(void)1655     void process(void)
1656     {
1657         // make sure there are at least 4096 pixels per CPU and at least 1 line par CPU
1658         unsigned int nCPUs = ( std::min(_iwidth, 4096u) * _iheight ) / 4096u;
1659 
1660         // make sure the number of CPUs is valid (and use at least 1 CPU)
1661         nCPUs = std::max( 1u, std::min( nCPUs, MultiThread::getNumCPUs() ) );
1662 
1663         // call the base multi threading code, should put a pre & post thread calls in too
1664         multiThread(nCPUs);
1665     }
1666 
1667 private:
1668     /** @brief function that will be called in each thread. ID is from 0..nThreads-1 nThreads are the number of threads it is being run over */
multiThreadFunction(unsigned int threadID,unsigned int nThreads)1669     virtual void multiThreadFunction(unsigned int threadID,
1670                                      unsigned int nThreads) OVERRIDE FINAL
1671     {
1672         int row_begin = 0;
1673         int row_end = 0;
1674 
1675         MultiThread::getThreadRange(threadID, nThreads, 0, _iheight, &row_begin, &row_end);
1676         if (row_end <= row_begin) {
1677             return;
1678         }
1679         for (int row = row_begin; row < row_end; ++row) {
1680             if ( _effect.abort() ) {
1681                 return;
1682             }
1683             // summed area table (sat) rows
1684             int row_sat_up = std::max(row - 1 - _adaptiveRadiusPixel, -1);
1685             int row_sat_down = std::min(row + _adaptiveRadiusPixel, (int)_iheight - 1);
1686             int row_sat_size = row_sat_down - row_sat_up;
1687             for (unsigned int col = 0; col < _iwidth; ++col) {
1688                 int col_sat_left = std::max( (int)col - 1 - _adaptiveRadiusPixel, -1 );
1689                 int col_sat_right = std::min( (int)col + _adaptiveRadiusPixel, (int)_iwidth - 1 );
1690                 int col_sat_size = col_sat_right - col_sat_left;
1691                 double sumsq = ( _fimg_sat[row_sat_down * _iwidth + col_sat_right]
1692                                  - (row_sat_up >= 0 ? _fimg_sat[row_sat_up * _iwidth + col_sat_right] : 0.)
1693                                  - (col_sat_left >= 0 ? _fimg_sat[row_sat_down * _iwidth + col_sat_left] : 0.)
1694                                  + ( (row_sat_up >= 0 && col_sat_left >= 0) ? _fimg_sat[row_sat_up * _iwidth + col_sat_left] : 0. ) );
1695                 int sumsqsize = row_sat_size * col_sat_size;
1696                 unsigned int i = row * _iwidth + col;
1697                 float fimg_denoised = _fimg_hpass[i];
1698 
1699                 // apply smooth threshold
1700                 float thold = _sigma_n_i_sq / std::sqrt( std::max(1e-30, sumsq / sumsqsize - _sigma_n_i_sq) );
1701 
1702                 if (_fimg_hpass[i] < -thold) {
1703                     _fimg_hpass[i] += thold * _denoise_amount;
1704                     fimg_denoised += thold;
1705                 } else if (_fimg_hpass[i] >  thold) {
1706                     _fimg_hpass[i] -= thold * _denoise_amount;
1707                     fimg_denoised -= thold;
1708                 } else {
1709                     _fimg_hpass[i] *= 1. - _denoise_amount;
1710                     fimg_denoised = 0.;
1711                 }
1712                 // add the denoised band to the final image
1713                 if (_fimg_0) { // if (hpass != 0)
1714                     // note: local contrast boost could be applied here, by multiplying fimg[hpass][i] by a factor beta
1715                     // GIMP's wavelet sharpen uses beta = amount * exp (-(lev - radius) * (lev - radius) / 1.5)
1716 
1717                     _fimg_0[i] += _fimg_hpass[i] + _beta * fimg_denoised;
1718                 }
1719             }
1720         }
1721     } // multiThreadFunction
1722 
1723 private:
1724     ImageEffect &_effect;      /**< @brief effect to render with */
1725     float * const _fimg_hpass;
1726     float * const _fimg_0;
1727     float * const _fimg_sat;
1728     unsigned int const _iwidth;
1729     unsigned int const _iheight;
1730     int _adaptiveRadiusPixel;
1731     double _sigma_n_i_sq;
1732     double const _denoise_amount;
1733     double const _beta;
1734 };
1735 
1736 
1737 #endif // ifdef kUseMultithread
1738 
1739 
1740 // "A trous" algorithm with a linear interpolation filter.
1741 // from dcraw/UFRaw/LibRaw, with enhancements from GIMP wavelet denoise
1742 // https://sourceforge.net/p/ufraw/mailman/message/24069162/
1743 void
wavelet_denoise(float * fimg[4],unsigned int iwidth,unsigned int iheight,bool b3,const double noiselevels[4],int adaptiveRadius,double denoise_amount,double sharpen_amount,double sharpen_radius,int startLevel,float a,float b)1744 DenoiseSharpenPlugin::wavelet_denoise(float *fimg[4], //!< fimg[0] is the channel to process with intensities between 0. and 1., of size iwidth*iheight, fimg[1] and fimg[2] are working space images of the same size, fimg[3] is a working image of the same size used when adaptiveRadius > 0
1745                                       unsigned int iwidth, //!< width of the image
1746                                       unsigned int iheight, //!< height of the image
1747                                       bool b3,
1748                                       const double noiselevels[4], //!< noise levels for high/medium/low/very low frequencies
1749                                       int adaptiveRadius,
1750                                       double denoise_amount, //!< amount parameter
1751                                       double sharpen_amount, //!< constrast boost amount
1752                                       double sharpen_radius, //!< contrast boost radius
1753                                       int startLevel,
1754                                       float a, // progress amount at start
1755                                       float b) // progress increment
1756 {
1757     //
1758     // BayesShrink (as describred in <https://jo.dreggn.org/home/2011_atrous.pdf>):
1759     // compute sigma_n using the MAD (median absolute deviation at the finest level:
1760     // sigma_n = median(|d_0|)/0.6745 (could be computed in an analysis step from the first detail subband)
1761     // The soft shrinkage threshold is
1762     // T = \sigma_{n,i}^2 / \sqrt{max(0,\sigma_{y,i}^2 - \sigma_{n,i}^2)}
1763     // with
1764     // \sigma_{y,i}^2 = 1/N \sum{p} d_i(p)^2 (standard deviation of the signal with the noise for this detail subband)
1765     // \sigma_{n,i} = \sigma_n . 2^{-i} (standard deviation of the noise)
1766     //
1767     // S. G. Chang, Bin Yu and M. Vetterli, "Adaptive wavelet thresholding for image denoising and compression," in IEEE Transactions on Image Processing, vol. 9, no. 9, pp. 1532-1546, Sep 2000. doi: 10.1109/83.862633
1768     // http://www.csee.wvu.edu/~xinl/courses/ee565/TIP2000.pdf
1769 
1770 
1771     // a single channel of the original image is in fimg[0],
1772     // with intensities between 0 and 1.
1773     // The channel is from either gamma-compressed R'G'B', CIE L*a*b,
1774     // or gamma-compressed Y'CbCr.
1775     // fimg[1] and fimg[2] are used as temporary images.
1776     // at each outer iteration (for lev= 0 to 4):
1777     // 1. the hpass image (initialy the image itself) is smoothed using
1778     //    hat_transform, and the result is put in image lpass.
1779     // 2. image lpass is subtracted from hpass, so that lpass contains
1780     //    a smoothed image, and hpass contains the details at level lev
1781     //    (which may be positive r negative). The original image is
1782     //    destroyed, but can be reconstructed as hpass+lpass.
1783     // 3. a threshold thold is computed as:
1784     //    5.0 / (1 << 6) * exp (-2.6 * sqrt (lev + 1)) * 0.8002 / exp (-2.6)
1785     //    This a priori noise threshold is only used to exclude noisy values
1786     //    from the statistics computed hereafter.
1787     // 4. the standard deviation of pixels of hpass that are outside of
1788     //    [-thold,thold] is computed, classified by the intensity range of the
1789     //    smoothed image lpass:
1790     //    0: ]-oo,0.2], 1: ]0.2,0.4], 2: ]0.4,0.6], 3: ]0.6,0.8], 4: ]0.8,+oo[
1791     //    This means that is a given pixel of lpass is between 0.6 and 0.8,
1792     //    the corresponding pixel of hpass is added to the statitics for sdev[3].
1793     // 5. for each pixel of hpass, a threshold is computed, depending on the
1794     //    intensity range r of lpass as: thold = threshold*sdev[r]
1795     //    (threshold is an external parameter between 0 and 10).
1796     //    Depending on the value of this pixel:
1797     //    - between -thold and thold, it is multiplied by low (low is the
1798     //      softwess parameter between 0. and 1. which drives how much noise
1799     //      should be kept).
1800     //    - below -thold , thold*(1-low) is added
1801     //    - above thold , thold*(1-low) is subtracted
1802     // 6. if lev is more than 0, hpass is added to the level-0
1803     //    high-pass image fimg[0]
1804     // 7. hpass is then set to the smoothed (lpass image), and lpass is set to
1805     //    the other temporary image.
1806 
1807     int maxLevel = kLevelMax - startLevel;
1808 
1809     if (maxLevel < 0) {
1810         return;
1811     }
1812 
1813     if ( ( ( (noiselevels[0] <= 0.) && (noiselevels[1] <= 0.) && (noiselevels[2] <= 0.) && (noiselevels[3] <= 0.) ) || (denoise_amount <= 0.) ) && (sharpen_amount <= 0.) ) {
1814         return;
1815     }
1816 
1817     const unsigned int size = iheight * iwidth;
1818     int hpass = 0;
1819     int lpass;
1820     for (int lev = 0; lev <= maxLevel; lev++) {
1821         abort_test();
1822         if (b != 0) {
1823             progressUpdateRender( a + b * lev / (maxLevel + 1.) );
1824         }
1825         lpass = ( (lev & 1) + 1 );
1826 
1827         // smooth fimg[hpass], result is in fimg[lpass]:
1828         // a- smooth rows, result is in fimg[lpass]
1829 #ifdef kUseMultithread
1830         {
1831             SmoothRows processor(*this, fimg[hpass], fimg[lpass], iwidth, iheight, b3, 1 << lev);
1832             processor.process();
1833         }
1834 #else
1835         // SmoothRows
1836 #       ifdef _OPENMP
1837 #       pragma omp parallel for
1838 #       endif
1839         for (unsigned int row = 0; row < iheight; ++row) {
1840             abort_test_loop();
1841             float* temp = new float[iwidth];
1842             hat_transform (temp, fimg[hpass] + row * iwidth, 1, iwidth, b3, 1 << lev);
1843             for (unsigned int col = 0; col < iwidth; ++col) {
1844                 unsigned int i = row * iwidth + col;
1845                 fimg[lpass][i] = temp[col];
1846             }
1847             delete [] temp;
1848         }
1849 #endif
1850         abort_test();
1851         if (b != 0) {
1852             progressUpdateRender( a + b * (lev + 0.25) / (maxLevel + 1.) );
1853         }
1854 
1855         // b- smooth cols, result is in fimg[lpass]
1856         // compute HHi + its variance
1857         double sumsq = 0.;
1858         unsigned int sumsqsize = 0;
1859 #ifdef kUseMultithread
1860         if (adaptiveRadius <= 0) {
1861             SmoothColsSumSq processor(*this, fimg[hpass], fimg[lpass], iwidth, iheight, b3, 1 << lev, &sumsq);
1862             processor.process();
1863             sumsqsize = size;
1864         } else {
1865             SmoothCols processor(*this, fimg[hpass], fimg[lpass], iwidth, iheight, b3, 1 << lev);
1866             processor.process();
1867         }
1868 #else // !kUseMultithread
1869         if (adaptiveRadius <= 0) {
1870             // SmoothColsSumSq
1871 #           ifdef _OPENMP
1872 #           pragma omp parallel for reduction (+:sumsq)
1873 #           endif
1874             for (unsigned int col = 0; col < iwidth; ++col) {
1875                 abort_test_loop();
1876                 float* temp = new float[iheight];
1877                 hat_transform (temp, fimg[lpass] + col, iwidth, iheight, b3, 1 << lev);
1878                 double sumsqrow = 0.;
1879                 for (unsigned int row = 0; row < iheight; ++row) {
1880                     unsigned int i = row * iwidth + col;
1881                     fimg[lpass][i] = temp[row];
1882                     // compute band-pass image as: (smoothed at this lev)-(smoothed at next lev)
1883                     fimg[hpass][i] -= fimg[lpass][i];
1884                     sumsqrow += fimg[hpass][i] * fimg[hpass][i];
1885                 }
1886                 sumsq += sumsqrow;
1887                 delete [] temp;
1888             }
1889             sumsqsize = size;
1890         } else {
1891             // SmoothCols
1892 #           ifdef _OPENMP
1893 #           pragma omp parallel for reduction (+:sumsq)
1894 #           endif
1895             for (unsigned int col = 0; col < iwidth; ++col) {
1896                 abort_test_loop();
1897                 float* temp = new float[iheight];
1898                 hat_transform (temp, fimg[lpass] + col, iwidth, iheight, b3, 1 << lev);
1899                 for (unsigned int row = 0; row < iheight; ++row) {
1900                     unsigned int i = row * iwidth + col;
1901                     fimg[lpass][i] = temp[row];
1902                     // compute band-pass image as: (smoothed at this lev)-(smoothed at next lev)
1903                     fimg[hpass][i] -= fimg[lpass][i];
1904                 }
1905                 delete [] temp;
1906             }
1907         }
1908 #endif // !kUseMultithread
1909         abort_test();
1910         if (b != 0) {
1911             progressUpdateRender( a + b * (lev + 0.5) / (maxLevel + 1.) );
1912         }
1913 
1914 
1915         // threshold
1916         // The soft shrinkage threshold is
1917         // T = \sigma_{n,i}^2 / \sqrt{max(0,\sigma_{y,i}^2 - \sigma_{n,i}^2)}
1918         // with
1919         // \sigma_{y,i}^2 = 1/N \sum{p} d_i(p)^2 (standard deviation of the signal with the noise for this detail subband)
1920         // \sigma_{n,i} = \sigma_n . 2^{-i} (standard deviation of the noise)
1921 
1922         // The following corresponds to <https://jo.dreggn.org/home/2011_atrous.pdf>:
1923         //double sigma_n_i = ( noiselevel * noise[0] / ( 1 << (lev + startLevel) ) );
1924         // The following uses levels obtained by filtering an actual Gaussian noise:
1925         double sigma_n_i_sq = 0;
1926         // sum up the noise from different frequencies
1927         for (unsigned f = 0; f < 4; ++f) {
1928             if (lev + startLevel >= (int)f) {
1929                 double k = b3 ? noise_b3[lev + startLevel] : noise[lev + startLevel];
1930                 double sigma_n_i = noiselevels[f] * k;
1931                 sigma_n_i_sq += sigma_n_i * sigma_n_i;
1932             }
1933         }
1934 
1935         // uncomment to check the values of the noise[] array
1936         //printf("width=%u level=%u stdev=%g sigma_n_i=%g\n", iwidth, lev, std::sqrt(sumsq / sumsqsize), std::sqrt(sigma_n_i_sq));
1937 
1938         // sharpen
1939         double beta = 0.;
1940         if (sharpen_amount > 0.) {
1941             beta = sharpen_amount * exp (-( (lev + startLevel) - sharpen_radius ) * ( (lev + startLevel) - sharpen_radius ) / 1.5);
1942         }
1943 
1944         if (adaptiveRadius <= 0) {
1945             assert(sumsqsize > 0);
1946             // use the signal level computed from the whole image
1947             float thold = sigma_n_i_sq / std::sqrt( std::max(1e-30, sumsq / sumsqsize - sigma_n_i_sq) );
1948 
1949 #ifdef kUseMultithread
1950             {
1951                 ApplyThreshold processor(*this, fimg[hpass], hpass ? fimg[0] : NULL, size, thold, denoise_amount, beta);
1952                 processor.process();
1953             }
1954 #else
1955             // ApplyThreshold
1956 #           ifdef _OPENMP
1957 #           pragma omp parallel for
1958 #           endif
1959             for (unsigned int i = 0; i < size; ++i) {
1960                 float fimg_denoised = fimg[hpass][i];
1961 
1962                 // apply smooth threshold
1963                 if (fimg[hpass][i] < -thold) {
1964                     fimg[hpass][i] += thold * denoise_amount;
1965                     fimg_denoised += _thold;
1966                 } else if (fimg[hpass][i] >  thold) {
1967                     fimg[hpass][i] -= thold * denoise_amount;
1968                     fimg_denoised -= _thold;
1969                 } else {
1970                     fimg[hpass][i] *= 1. - denoise_amount;
1971                     fimg_denoised = 0.;
1972                 }
1973                 // add the denoised band to the final image
1974                 if (hpass != 0) {
1975                     // note: local contrast boost could be applied here, by multiplying fimg[hpass][i] by a factor beta
1976                     // GIMP's wavelet sharpen uses beta = amount * exp (-(lev - radius) * (lev - radius) / 1.5)
1977 
1978                     fimg[0][i] += fimg[hpass][i] + beta * fimg_denoised;
1979                 }
1980             }
1981 #endif
1982         } else { // adaptiveRadius > 0
1983             // use the local image level
1984             assert(fimg[3] != NULL);
1985             int adaptiveRadiusPixel = ( adaptiveRadius + (b3 ? 2 : 1) ) * (1 << lev);
1986 #ifdef kUseMultithread
1987             {
1988                 IntegralRows processor(*this, fimg[hpass], fimg[3], iwidth, iheight);
1989                 processor.process();
1990             }
1991             {
1992                 IntegralCols processor(*this, fimg[3], fimg[3], iwidth, iheight);
1993                 processor.process();
1994             }
1995 
1996             {
1997                 ApplyThresholdAdaptive processor(*this, fimg[hpass], hpass ? fimg[0] : NULL, fimg[3], iwidth, iheight, adaptiveRadiusPixel, sigma_n_i_sq, denoise_amount, beta);
1998                 processor.process();
1999             }
2000 #else
2001             float *fimg_sat = fimg[3];
2002             // IntegralRows
2003 #           ifdef _OPENMP
2004 #           pragma omp parallel for
2005 #           endif
2006             for (unsigned int row = 0; row < iheight; ++row) {
2007                 abort_test_loop();
2008                 //float prev = 0.;
2009                 float prevsq = 0.;
2010                 for (unsigned int col = 0; col < iwidth; ++col) {
2011                     unsigned int i = row * iwidth + col;
2012                     //prev += fimg[hpass];
2013                     //_fimgsumrow[i] = prev;
2014                     prevsq += fimg[hpass][i] * fimg[hpass][i];
2015                     fimg_sat[i] = prevsq;
2016                 }
2017             }
2018             // IntegralCols
2019 #           ifdef _OPENMP
2020 #           pragma omp parallel for
2021 #           endif
2022             for (unsigned int col = 0; col < iwidth; ++col) {
2023                 abort_test_loop();
2024                 float prev = 0.;
2025                 for (unsigned int row = 0; row < iheight; ++row) {
2026                     unsigned int i = row * iwidth + col;
2027                     prev += fimg_sat[i];
2028                     fimg_sat[i] = prev;
2029                 }
2030             }
2031             // ApplyThresholdAdaptive
2032 #           ifdef _OPENMP
2033 #           pragma omp parallel for
2034 #           endif
2035             for (unsigned int row = 0; row < iheight; ++row) {
2036                 abort_test_loop();
2037                 // summed area table (sat) rows
2038                 int row_sat_up = std::max( (int)row - 1 - adaptiveRadiusPixel, -1 );
2039                 int row_sat_down = std::min( (int)row + adaptiveRadiusPixel, (int)iheight - 1 );
2040                 int row_sat_size = row_sat_down - row_sat_up;
2041                 for (unsigned int col = 0; col < iwidth; ++col) {
2042                     int col_sat_left = std::max( (int)col - 1 - adaptiveRadiusPixel, -1 );
2043                     int col_sat_right = std::min( (int)col + adaptiveRadiusPixel, (int)iwidth - 1 );
2044                     int col_sat_size = col_sat_right - col_sat_left;
2045                     double sumsq = ( fimg_sat[row_sat_down * iwidth + col_sat_right]
2046                                      - (row_sat_up >= 0 ? fimg_sat[row_sat_up * iwidth + col_sat_right] : 0.)
2047                                      - (col_sat_left >= 0 ? fimg_sat[row_sat_down * iwidth + col_sat_left] : 0.)
2048                                      + ( (row_sat_up >= 0 && col_sat_left >= 0) ? fimg_sat[row_sat_up * iwidth + col_sat_left] : 0. ) );
2049                     int sumsqsize = row_sat_size * col_sat_size;
2050                     unsigned int i = row * iwidth + col;
2051                     float fimg_denoised = fimg[hpass][i];
2052 
2053                     // apply smooth threshold
2054                     float thold = sigma_n_i_sq / std::sqrt( std::max(1e-30, sumsq / sumsqsize - sigma_n_i_sq) );
2055 
2056                     if (fimg[hpass][i] < -thold) {
2057                         fimg[hpass][i] += thold * denoise_amount;
2058                         fimg_denoised += thold;
2059                     } else if (fimg[hpass][i] >  thold) {
2060                         fimg[hpass][i] -= thold * denoise_amount;
2061                         fimg_denoised -= thold;
2062                     } else {
2063                         fimg[hpass][i] *= 1. - denoise_amount;
2064                         fimg_denoised = 0.;
2065                     }
2066                     // add the denoised band to the final image
2067                     if (hpass != 0) {
2068                         // note: local contrast boost could be applied here, by multiplying fimg[hpass][i] by a factor beta
2069                         // GIMP's wavelet sharpen uses beta = amount * exp (-(lev - radius) * (lev - radius) / 1.5)
2070 
2071                         fimg[0][i] += fimg[hpass][i] + beta * fimg_denoised;
2072                     }
2073                 }
2074             }
2075 #endif // ifdef kUseMultithread
2076         }
2077         hpass = lpass;
2078     } // for(lev)
2079 
2080     abort_test();
2081     // add the last smoothed image to the image
2082 #ifdef kUseMultithread
2083     {
2084         AddLowPass processor(*this, fimg[0], fimg[lpass], size);
2085         processor.process();
2086     }
2087 #else
2088 #   ifdef _OPENMP
2089 #   pragma omp parallel for
2090 #   endif
2091     for (unsigned int i = 0; i < size; ++i) {
2092         fimg[0][i] += fimg[lpass][i];
2093     }
2094 #endif
2095 } // wavelet_denoise
2096 
2097 void
sigma_mad(float * fimg[4],bool * bimgmask,unsigned int iwidth,unsigned int iheight,bool b3,double noiselevels[4],float a,float b)2098 DenoiseSharpenPlugin::sigma_mad(float *fimg[4], //!< fimg[0] is the channel to process with intensities between 0. and 1., of size iwidth*iheight, fimg[1-3] are working space images of the same size
2099                                 bool *bimgmask,
2100                                 unsigned int iwidth, //!< width of the image
2101                                 unsigned int iheight, //!< height of the image
2102                                 bool b3,
2103                                 double noiselevels[4], //!< output: the sigma for each frequency
2104                                 float a, // progress amount at start
2105                                 float b) // progress increment
2106 {
2107     // compute sigma_n using the MAD (median absolute deviation at the finest level:
2108     // sigma_n = median(|d_0|)/0.6745 (could be computed in an analysis step from the first detail subband)
2109 
2110     const unsigned int size = iheight * iwidth;
2111     const int maxLevel = 3;
2112     double noiselevel_prev_fullres = 0.;
2113     int hpass = 0;
2114     int lpass;
2115 
2116     for (int lev = 0; lev <= maxLevel; lev++) {
2117         abort_test();
2118         if (b != 0) {
2119             progressUpdateAnalysis( a + b * lev / (maxLevel + 1.) );
2120         }
2121         lpass = ( (lev & 1) + 1 );
2122 
2123         // smooth fimg[hpass], result is in fimg[lpass]:
2124         // a- smooth rows, result is in fimg[lpass]
2125 #ifdef _OPENMP
2126 #pragma omp parallel for
2127 #endif
2128         for (unsigned int row = 0; row < iheight; ++row) {
2129             float* temp = new float[iwidth];
2130             abort_test_loop();
2131             hat_transform (temp, fimg[hpass] + row * iwidth, 1, iwidth, b3, 1 << lev);
2132             for (unsigned int col = 0; col < iwidth; ++col) {
2133                 unsigned int i = row * iwidth + col;
2134                 fimg[lpass][i] = temp[col];
2135             }
2136             delete [] temp;
2137         }
2138         abort_test();
2139         if (b != 0) {
2140             progressUpdateAnalysis( a + b * (lev + 0.25) / (maxLevel + 1.) );
2141         }
2142 
2143         // b- smooth cols, result is in fimg[lpass]
2144         // compute HHlev
2145 #ifdef _OPENMP
2146 #pragma omp parallel for
2147 #endif
2148         for (unsigned int col = 0; col < iwidth; ++col) {
2149             float* temp = new float[iheight];
2150             abort_test_loop();
2151             hat_transform (temp, fimg[lpass] + col, iwidth, iheight, b3, 1 << lev);
2152             for (unsigned int row = 0; row < iheight; ++row) {
2153                 unsigned int i = row * iwidth + col;
2154                 fimg[lpass][i] = temp[row];
2155                 // compute band-pass image as: (smoothed at this lev)-(smoothed at next lev)
2156                 fimg[hpass][i] -= fimg[lpass][i];
2157             }
2158             delete [] temp;
2159         }
2160         abort_test();
2161         if (b != 0) {
2162             progressUpdateAnalysis( a + b * (lev + 0.5) / (maxLevel + 1.) );
2163         }
2164         // take the absolute value to compute MAD, and extract points within the mask
2165         unsigned int n;
2166         if (bimgmask) {
2167             n = 0;
2168             for (unsigned int i = 0; i < size; ++i) {
2169                 if (bimgmask[i]) {
2170                     fimg[3][n] = std::abs(fimg[hpass][i]);
2171                     ++n;
2172                 }
2173             }
2174         } else {
2175             for (unsigned int i = 0; i < size; ++i) {
2176                 fimg[3][i] = std::abs(fimg[hpass][i]);
2177             }
2178             n = size;
2179         }
2180         abort_test();
2181         if (n != 0) {
2182             std::nth_element(&fimg[3][0], &fimg[3][n / 2], &fimg[3][n]);
2183         }
2184 
2185         double sigma_this = (n == 0) ? 0. : fimg[3][n / 2] / 0.6745;
2186         // compute the sigma at image resolution
2187         double k = b3 ? noise_b3[lev] : noise[lev];
2188         double sigma_fullres = sigma_this / k;
2189         if (noiselevel_prev_fullres <= 0.) {
2190             noiselevels[lev] = sigma_fullres;
2191             noiselevel_prev_fullres = sigma_fullres;
2192         } else if (sigma_fullres > noiselevel_prev_fullres) {
2193             // subtract the contribution from previous levels
2194             noiselevels[lev] = std::sqrt(sigma_fullres * sigma_fullres - noiselevel_prev_fullres * noiselevel_prev_fullres);
2195             noiselevel_prev_fullres = sigma_fullres;
2196         } else {
2197             noiselevels[lev] = 0.;
2198             // cumulated noiselevel is unchanged
2199             //noiselevel_prev_fullres = noiselevel_prev_fullres;
2200         }
2201         hpass = lpass;
2202     }
2203 } // DenoiseSharpenPlugin::sigma_mad
2204 
2205 ////////////////////////////////////////////////////////////////////////////////
2206 /** @brief render for the filter */
2207 
2208 ////////////////////////////////////////////////////////////////////////////////
2209 // basic plugin render function, just a skelington to instantiate templates from
2210 // the overridden render function
2211 void
render(const RenderArguments & args)2212 DenoiseSharpenPlugin::render(const RenderArguments &args)
2213 {
2214 #ifdef _OPENMP
2215     // set the number of OpenMP threads to a reasonable value
2216     // (but remember that the OpenMP threads are not counted my the multithread suite)
2217     omp_set_num_threads( std::max(1u, MultiThread::getNumCPUs() ) );
2218 #endif
2219     DBG(cout << "render! with " << MultiThread::getNumCPUs() << " CPUs\n");
2220 
2221     progressStartRender(kPluginName " (render)");
2222 
2223     // instantiate the render code based on the pixel depth of the dst clip
2224     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
2225 
2226     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
2227     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
2228     assert(dstComponents == ePixelComponentRGBA || dstComponents == ePixelComponentRGB /*|| dstComponents == ePixelComponentXY*/ || dstComponents == ePixelComponentAlpha);
2229     // do the rendering
2230     switch (dstComponents) {
2231     case ePixelComponentRGBA:
2232         renderForComponents<4>(args);
2233         break;
2234     case ePixelComponentRGB:
2235         renderForComponents<3>(args);
2236         break;
2237 #ifdef OFX_EXTENSIONS_NATRON
2238     //case ePixelComponentXY:
2239     //    renderForComponents<2>(args);
2240     //    break;
2241 #endif
2242     case ePixelComponentAlpha:
2243         renderForComponents<1>(args);
2244         break;
2245     default:
2246         DBG(std::cout << "components usupported\n");
2247         throwSuiteStatusException(kOfxStatErrUnsupported);
2248         break;
2249     } // switch
2250     progressEndRender();
2251 
2252     DBG(cout << "render! OK\n");
2253 }
2254 
2255 template<int nComponents>
2256 void
renderForComponents(const RenderArguments & args)2257 DenoiseSharpenPlugin::renderForComponents(const RenderArguments &args)
2258 {
2259     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
2260 
2261     switch (dstBitDepth) {
2262     case eBitDepthUByte:
2263         renderForBitDepth<unsigned char, nComponents, 255>(args);
2264         break;
2265 
2266     case eBitDepthUShort:
2267         renderForBitDepth<unsigned short, nComponents, 65535>(args);
2268         break;
2269 
2270     case eBitDepthFloat:
2271         renderForBitDepth<float, nComponents, 1>(args);
2272         break;
2273     default:
2274         DBG(cout << "depth usupported\n");
2275         throwSuiteStatusException(kOfxStatErrUnsupported);
2276     }
2277 }
2278 
2279 static int
borderSize(int adaptiveRadius,bool b3,int nlevels)2280 borderSize(int adaptiveRadius,
2281            bool b3,
2282            int nlevels)
2283 {
2284     // hat_transform gets the pixel at x+-(1<<maxLev), which is computex from x+-(1<<(maxLev-1)), etc...
2285 
2286     // We thus need pixels at x +- (1<<(maxLev+1))-1
2287     return ( adaptiveRadius + (b3 ? 2 : 1) ) * (1 << nlevels) - 1;
2288 }
2289 
2290 void
setup(const RenderArguments & args,auto_ptr<const Image> & src,auto_ptr<Image> & dst,auto_ptr<const Image> & mask,Params & p)2291 DenoiseSharpenPlugin::setup(const RenderArguments &args,
2292                             auto_ptr<const Image>& src,
2293                             auto_ptr<Image>& dst,
2294                             auto_ptr<const Image>& mask,
2295                             Params& p)
2296 {
2297     const double time = args.time;
2298 
2299     dst.reset( _dstClip->fetchImage(time) );
2300 
2301     if ( !dst.get() ) {
2302         throwSuiteStatusException(kOfxStatFailed);
2303     }
2304     BitDepthEnum dstBitDepth    = dst->getPixelDepth();
2305     PixelComponentEnum dstComponents  = dst->getPixelComponents();
2306     if ( ( dstBitDepth != _dstClip->getPixelDepth() ) ||
2307          ( dstComponents != _dstClip->getPixelComponents() ) ) {
2308         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong depth or components");
2309         throwSuiteStatusException(kOfxStatFailed);
2310     }
2311     if ( (dst->getRenderScale().x != args.renderScale.x) ||
2312          ( dst->getRenderScale().y != args.renderScale.y) ||
2313          ( ( dst->getField() != eFieldNone) /* for DaVinci Resolve */ && ( dst->getField() != args.fieldToRender) ) ) {
2314         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
2315         throwSuiteStatusException(kOfxStatFailed);
2316     }
2317     src.reset( ( _srcClip && _srcClip->isConnected() ) ?
2318                _srcClip->fetchImage(time) : 0 );
2319     if ( !src.get() ) {
2320         throwSuiteStatusException(kOfxStatFailed);
2321     }
2322     if ( src.get() ) {
2323         if ( (src->getRenderScale().x != args.renderScale.x) ||
2324              ( src->getRenderScale().y != args.renderScale.y) ||
2325              ( ( src->getField() != eFieldNone) /* for DaVinci Resolve */ && ( src->getField() != args.fieldToRender) ) ) {
2326             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
2327             throwSuiteStatusException(kOfxStatFailed);
2328         }
2329         BitDepthEnum srcBitDepth      = src->getPixelDepth();
2330         PixelComponentEnum srcComponents = src->getPixelComponents();
2331         if ( (srcBitDepth != dstBitDepth) || (srcComponents != dstComponents) ) {
2332             throwSuiteStatusException(kOfxStatErrImageFormat);
2333         }
2334     }
2335     p.doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
2336     mask.reset(p.doMasking ? _maskClip->fetchImage(time) : 0);
2337     if ( mask.get() ) {
2338         if ( (mask->getRenderScale().x != args.renderScale.x) ||
2339              ( mask->getRenderScale().y != args.renderScale.y) ||
2340              ( ( mask->getField() != eFieldNone) /* for DaVinci Resolve */ && ( mask->getField() != args.fieldToRender) ) ) {
2341             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
2342             throwSuiteStatusException(kOfxStatFailed);
2343         }
2344     }
2345     clearPersistentMessage();
2346 
2347     p.maskInvert = false;
2348     if (p.doMasking) {
2349         _maskInvert->getValueAtTime(time, p.maskInvert);
2350     }
2351 
2352     // fetch parameter values
2353     p.analysisLock = _analysisLock->getValueAtTime(time);
2354     if ( !p.analysisLock ) {
2355         // all we have to do is copy pixels
2356         DBG(cout << "render called although analysis not locked and isidentity=true\n");
2357         copyPixels(*this,
2358                    args.renderWindow,
2359                    src.get(),
2360                    dst.get());
2361         return;
2362 
2363     }
2364     p.premult = _premult->getValueAtTime(time);
2365     p.premultChannel = _premultChannel->getValueAtTime(time);
2366     p.mix = _mix->getValueAtTime(time);
2367 
2368     p.process[0] = _processR->getValueAtTime(time);
2369     p.process[1] = _processG->getValueAtTime(time);
2370     p.process[2] = _processB->getValueAtTime(time);
2371     p.process[3] = _processA->getValueAtTime(time);
2372 
2373     p.outputMode = (OutputModeEnum)_outputMode->getValueAtTime(time);
2374     p.colorModel = (ColorModelEnum)_colorModel->getValueAtTime(time);
2375     p.b3 = _b3->getValueAtTime(time);
2376     p.startLevel = startLevelFromRenderScale(args.renderScale);
2377     p.adaptiveRadius = _adaptiveRadius->getValueAtTime(time);
2378 
2379     double noiseLevelGain = _noiseLevelGain->getValueAtTime(time);
2380     double gainFreq[4];
2381     for (unsigned int f = 0; f < 4; ++f) {
2382         if ( _enableFreq[f]->getValueAtTime(time) ) {
2383             gainFreq[f] = noiseLevelGain * _gainFreq[f]->getValueAtTime(time);
2384         } else {
2385             gainFreq[f] = 0;
2386         }
2387     }
2388 
2389     double denoiseAmount = _denoiseAmount->getValueAtTime(time);
2390     for (unsigned int c = 0; c < 4; ++c) {
2391         double channelGain = _channelGain[c]->getValueAtTime(time);
2392         for (unsigned int f = 0; f < 4; ++f) {
2393             p.noiseLevel[c][f] = channelGain * gainFreq[f] * _noiseLevel[c][f]->getValueAtTime(time);
2394         }
2395         p.denoise_amount[c] = (p.outputMode == eOutputModeSharpen) ? 0. : denoiseAmount * _amount[c]->getValueAtTime(time);
2396     }
2397     p.sharpen_amount[0] = (p.outputMode == eOutputModeNoise) ? 0. : _sharpenAmount->getValueAtTime(time);
2398     double sharpenSize = _sharpenSize->getValueAtTime(time);
2399     // The GIMP's wavelet sharpen uses a sharpen radius parameter which is counter-intuitive
2400     // and points to a level number. We convert from the Sharpen Size (simililar to the size in the
2401     // Laplacian or Sharpen plugins) to the radius using the following heuristic formula (radius=0 seems to correspond to size=8)
2402     p.sharpen_radius = std::log(sharpenSize) / M_LN2 - 3.; // log(8)/log(2) = 3.
2403     bool sharpenLuminance = _sharpenLuminance->getValueAtTime(time);
2404 
2405     if (!sharpenLuminance) {
2406         p.sharpen_amount[1] = p.sharpen_amount[2] = p.sharpen_amount[3] = p.sharpen_amount[0];
2407     } else if ( (p.colorModel == eColorModelRGB) || (p.colorModel == eColorModelLinearRGB) ) {
2408         p.sharpen_amount[1] = p.sharpen_amount[2] = p.sharpen_amount[0]; // cannot sharpen luminance only
2409     }
2410 
2411     if ( (p.colorModel == eColorModelRGB) || (p.colorModel == eColorModelLinearRGB) ) {
2412         for (int c = 0; c < 3; ++c) {
2413             p.process[c] = p.process[c] && ( ((p.noiseLevel[c][0] > 0 ||
2414                                                p.noiseLevel[c][1] > 0 ||
2415                                                p.noiseLevel[c][2] > 0 ||
2416                                                p.noiseLevel[c][3] > 0) && p.denoise_amount[c] > 0.) || p.sharpen_amount[c] > 0. );
2417         }
2418     } else {
2419         bool processcolor = false;
2420         for (int c = 0; c < 3; ++c) {
2421             processcolor = processcolor || ( ((p.noiseLevel[c][0] > 0 ||
2422                                                p.noiseLevel[c][1] > 0 ||
2423                                                p.noiseLevel[c][2] > 0 ||
2424                                                p.noiseLevel[c][3] > 0) && p.denoise_amount[c] > 0.) || p.sharpen_amount[c] > 0. );
2425         }
2426         for (int c = 0; c < 3; ++c) {
2427             p.process[c] = p.process[c] && processcolor;
2428         }
2429     }
2430     p.process[3] = p.process[3] && ( ((p.noiseLevel[3][0] > 0 ||
2431                                        p.noiseLevel[3][1] > 0 ||
2432                                        p.noiseLevel[3][2] > 0 ||
2433                                        p.noiseLevel[3][3] > 0) && p.denoise_amount[3] > 0.) || p.sharpen_amount[3] > 0. );
2434 
2435     // compute the number of levels (max is 4, which adds 1<<4 = 16 pixels on each side)
2436     int maxLev = std::max( 0, kLevelMax - startLevelFromRenderScale(args.renderScale) );
2437     int border = borderSize(p.adaptiveRadius, p.b3, maxLev + 1);
2438     p.srcWindow.x1 = args.renderWindow.x1 - border;
2439     p.srcWindow.y1 = args.renderWindow.y1 - border;
2440     p.srcWindow.x2 = args.renderWindow.x2 + border;
2441     p.srcWindow.y2 = args.renderWindow.y2 + border;
2442 
2443     // intersect with srcBounds
2444     bool nonempty = Coords::rectIntersection(p.srcWindow, src->getBounds(), &p.srcWindow);
2445     unused(nonempty);
2446 } // DenoiseSharpenPlugin::setup
2447 
2448 template <class PIX, int nComponents, int maxValue>
2449 void
renderForBitDepth(const RenderArguments & args)2450 DenoiseSharpenPlugin::renderForBitDepth(const RenderArguments &args)
2451 {
2452     auto_ptr<const Image> src;
2453     auto_ptr<Image> dst;
2454     auto_ptr<const Image> mask;
2455     Params p;
2456 
2457     setup(args, src, dst, mask, p);
2458     if ( !p.analysisLock ) {
2459         // we copied pixels to dst already
2460         return;
2461     }
2462 
2463     const OfxRectI& procWindow = args.renderWindow;
2464 
2465 
2466     // temporary buffers: one for each channel plus 2 for processing
2467     unsigned int iwidth = p.srcWindow.x2 - p.srcWindow.x1;
2468     unsigned int iheight = p.srcWindow.y2 - p.srcWindow.y1;
2469     unsigned int isize = iwidth * iheight;
2470     auto_ptr<ImageMemory> tmpData( new ImageMemory(sizeof(float) * isize * ( nComponents + 2 + ( (p.adaptiveRadius > 0) ? 1 : 0 ) ), this) );
2471     float* tmpPixelData = tmpData.get() ? (float*)tmpData->lock() : NULL;
2472     float* fimgcolor[3] = { NULL, NULL, NULL };
2473     float* fimgalpha = NULL;
2474     float *fimgtmp[3] = { NULL, NULL, NULL };
2475     fimgcolor[0] = (nComponents != 1) ? tmpPixelData : NULL;
2476     fimgcolor[1] = (nComponents != 1) ? tmpPixelData + isize : NULL;
2477     fimgcolor[2] = (nComponents != 1) ? tmpPixelData + 2 * isize : NULL;
2478     fimgalpha = (nComponents == 1) ? tmpPixelData : ( (nComponents == 4) ? tmpPixelData + 3 * isize : NULL );
2479     fimgtmp[0] = tmpPixelData + nComponents * isize;
2480     fimgtmp[1] = tmpPixelData + (nComponents + 1) * isize;
2481     if (p.adaptiveRadius > 0) {
2482         fimgtmp[2] = tmpPixelData + (nComponents + 2) * isize;
2483     }
2484     // - extract the color components and convert them to the appropriate color model
2485     //
2486 #ifdef _OPENMP
2487 #pragma omp parallel for
2488 #endif
2489     for (int y = p.srcWindow.y1; y < p.srcWindow.y2; y++) {
2490         abort_test_loop();
2491 
2492         for (int x = p.srcWindow.x1; x < p.srcWindow.x2; x++) {
2493             const PIX *srcPix = (const PIX *)  (src.get() ? src->getPixelAddress(x, y) : 0);
2494             float unpPix[4] = {0., 0., 0., 0.};
2495             ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, p.premult, p.premultChannel);
2496             unsigned int pix = (x - p.srcWindow.x1) + (y - p.srcWindow.y1) * iwidth;
2497             // convert to the appropriate color model and store in tmpPixelData
2498             if ( (nComponents != 1) && (p.process[0] || p.process[1] || p.process[2]) ) {
2499                 if (p.colorModel == eColorModelLab) {
2500                     if (sizeof(PIX) == 1) {
2501                         // convert to linear
2502                         for (int c = 0; c < 3; ++c) {
2503                             unpPix[c] = _lut->fromColorSpaceFloatToLinearFloat(unpPix[c]);
2504                         }
2505                     }
2506                     Color::rgb709_to_lab(unpPix[0], unpPix[1], unpPix[2], &unpPix[0], &unpPix[1], &unpPix[2]);
2507                     // bring each component in the 0..1 range
2508                     //unpPix[0] = unpPix[0] / 116.0 + 0 * 16 * 27 / 24389.0;
2509                     //unpPix[1] = unpPix[1] / 500.0 / 2.0 + 0.5;
2510                     //unpPix[2] = unpPix[2] / 200.0 / 2.2 + 0.5;
2511                 } else {
2512                     if (p.colorModel != eColorModelLinearRGB) {
2513                         if (sizeof(PIX) != 1) {
2514                             // convert to rec709
2515                             for (int c = 0; c < 3; ++c) {
2516                                 unpPix[c] = _lut->toColorSpaceFloatFromLinearFloat(unpPix[c]);
2517                             }
2518                         }
2519                     }
2520 
2521                     if (p.colorModel == eColorModelYCbCr) {
2522                         Color::rgb_to_ypbpr709(unpPix[0], unpPix[1], unpPix[2], &unpPix[0], &unpPix[1], &unpPix[2]);
2523                         // bring to the 0-1 range
2524                         //unpPix[1] += 0.5;
2525                         //unpPix[2] += 0.5;
2526                     }
2527                 }
2528                 // store in tmpPixelData
2529                 for (int c = 0; c < 3; ++c) {
2530                     if (!( (p.colorModel == eColorModelRGB) || (p.colorModel == eColorModelLinearRGB) ) || p.process[c]) {
2531                         fimgcolor[c][pix] = unpPix[c];
2532                     }
2533                 }
2534             }
2535             if (nComponents != 3) {
2536                 assert(fimgalpha);
2537                 fimgalpha[pix] = unpPix[3];
2538             }
2539         }
2540     }
2541 
2542     // denoise
2543 
2544     if ( (nComponents != 1) && (p.process[0] || p.process[1] || p.process[2]) ) {
2545         // process color channels
2546         for (int c = 0; c < 3; ++c) {
2547             if (!( (p.colorModel == eColorModelRGB) || (p.colorModel == eColorModelLinearRGB) ) || p.process[c]) {
2548                 assert(fimgcolor[c]);
2549                 float* fimg[4] = { fimgcolor[c], fimgtmp[0], fimgtmp[1], (p.adaptiveRadius > 0) ? fimgtmp[2] : NULL};
2550                 abort_test();
2551                 wavelet_denoise(fimg, iwidth, iheight, p.b3, p.noiseLevel[c], p.adaptiveRadius, p.denoise_amount[c], p.sharpen_amount[c], p.sharpen_radius, p.startLevel, (float)c / nComponents, 1.f / nComponents);
2552             }
2553         }
2554     }
2555     if ( (nComponents != 3) && p.process[3] ) {
2556         assert(fimgalpha);
2557         // process alpha
2558         float* fimg[4] = { fimgalpha, fimgtmp[0], fimgtmp[1], (p.adaptiveRadius > 0) ? fimgtmp[2] : NULL };
2559         abort_test();
2560         wavelet_denoise(fimg, iwidth, iheight, p.b3, p.noiseLevel[3], p.adaptiveRadius, p.denoise_amount[3], p.sharpen_amount[3], p.sharpen_radius, p.startLevel, (float)(nComponents - 1) / nComponents, 1.f / nComponents);
2561     }
2562 
2563     // store back into the result
2564 
2565 #ifdef _OPENMP
2566 #pragma omp parallel for
2567 #endif
2568     for (int y = procWindow.y1; y < procWindow.y2; y++) {
2569         abort_test_loop();
2570 
2571         PIX *dstPix = (PIX *) dst->getPixelAddress(procWindow.x1, y);
2572         for (int x = procWindow.x1; x < procWindow.x2; x++) {
2573             const PIX *srcPix = (const PIX *)  (src.get() ? src->getPixelAddress(x, y) : 0);
2574             unsigned int pix = (x - p.srcWindow.x1) + (y - p.srcWindow.y1) * iwidth;
2575             float tmpPix[4] = {0., 0., 0., 1.};
2576             // get values from tmpPixelData
2577             if (nComponents != 3) {
2578                 assert(fimgalpha);
2579                 tmpPix[3] = fimgalpha[pix];
2580             }
2581             if (nComponents != 1) {
2582                 // store in tmpPixelData
2583                 for (int c = 0; c < 3; ++c) {
2584                     tmpPix[c] = fimgcolor[c][pix];
2585                 }
2586 
2587                 if (p.colorModel == eColorModelLab) {
2588                     // back from 0..1 range to normal Lab
2589                     //tmpPix[0] = (tmpPix[0] - 0 * 16 * 27 / 24389.0) * 116;
2590                     //tmpPix[1] = (tmpPix[1] - 0.5) * 500 * 2;
2591                     //tmpPix[2] = (tmpPix[2] - 0.5) * 200 * 2.2;
2592 
2593                     Color::lab_to_rgb709(tmpPix[0], tmpPix[1], tmpPix[2], &tmpPix[0], &tmpPix[1], &tmpPix[2]);
2594                     if (sizeof(PIX) == 1.) {
2595                         // convert from linear
2596                         for (int c = 0; c < 3; ++c) {
2597                             tmpPix[c] = _lut->toColorSpaceFloatFromLinearFloat(tmpPix[c]);
2598                         }
2599                     }
2600                 } else {
2601                     if (p.colorModel == eColorModelYCbCr) {
2602                         // bring from 0..1 to the -0.5-0.5 range
2603                         //tmpPix[1] -= 0.5;
2604                         //tmpPix[2] -= 0.5;
2605                         Color::ypbpr_to_rgb709(tmpPix[0], tmpPix[1], tmpPix[2], &tmpPix[0], &tmpPix[1], &tmpPix[2]);
2606                     }
2607                     if (p.colorModel != eColorModelLinearRGB) {
2608                         if (sizeof(PIX) != 1) {
2609                             // convert from rec709
2610                             for (int c = 0; c < 3; ++c) {
2611                                 tmpPix[c] = _lut->fromColorSpaceFloatToLinearFloat(tmpPix[c]);
2612                             }
2613                         }
2614                     }
2615                 }
2616             }
2617 
2618             ofxsPremultMaskMixPix<PIX, nComponents, maxValue, true>(tmpPix, p.premult, p.premultChannel, x, y, srcPix, p.doMasking, mask.get(), p.mix, p.maskInvert, dstPix);
2619             if ( (p.outputMode == eOutputModeNoise) || (p.outputMode == eOutputModeSharpen) ) {
2620                 // if Output=Noise or Output=Sharpen, the unchecked channels should be zero on output
2621                 if (srcPix) {
2622                     for (int c = 0; c < nComponents; ++c) {
2623                         dstPix[c] = p.process[c] ? (dstPix[c] - srcPix[c]) : 0;
2624                     }
2625                 }
2626             } else {
2627                 // copy back original values from unprocessed channels
2628                 if (nComponents == 1) {
2629                     if (!p.process[3]) {
2630                         dstPix[0] = srcPix ? srcPix[0] : PIX();
2631                     }
2632                 } else if ( (nComponents == 3) || (nComponents == 4) ) {
2633                     for (int c = 0; c < 3; ++c) {
2634                         if (!p.process[c]) {
2635                             dstPix[c] = srcPix ? srcPix[c] : PIX();
2636                         }
2637                     }
2638                     if ( !p.process[3] && (nComponents == 4) ) {
2639                         dstPix[3] = srcPix ? srcPix[3] : PIX();
2640                     }
2641                 }
2642             }
2643             // increment the dst pixel
2644             dstPix += nComponents;
2645         }
2646     }
2647 } // DenoiseSharpenPlugin::renderForBitDepth
2648 
2649 // override the roi call
2650 // Required if the plugin requires a region from the inputs which is different from the rendered region of the output.
2651 // (this is the case here)
2652 void
getRegionsOfInterest(const RegionsOfInterestArguments & args,RegionOfInterestSetter & rois)2653 DenoiseSharpenPlugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
2654                                            RegionOfInterestSetter &rois)
2655 {
2656     const double time = args.time;
2657 
2658     if (!_srcClip || !_srcClip->isConnected()) {
2659         return;
2660     }
2661     const OfxRectD srcRod = _srcClip->getRegionOfDefinition(time);
2662     if ( Coords::rectIsEmpty(srcRod) || Coords::rectIsEmpty(args.regionOfInterest) ) {
2663         return;
2664     }
2665 
2666     int adaptiveRadius = _adaptiveRadius->getValueAtTime(time);
2667     if (adaptiveRadius <= 0) {
2668         // requires the full image to compute standard deviation of the signal
2669         rois.setRegionOfInterest(*_srcClip, srcRod);
2670 
2671         return;
2672     }
2673     bool b3 = _b3->getValueAtTime(time);
2674     double par = _srcClip->getPixelAspectRatio();
2675     OfxRectI roiPixel;
2676     Coords::toPixelEnclosing(args.regionOfInterest, args.renderScale, par, &roiPixel);
2677     int levels = kLevelMax - startLevelFromRenderScale(args.renderScale);
2678     int radiusPixel = borderSize(adaptiveRadius, b3, levels);
2679     roiPixel.x1 -= radiusPixel;
2680     roiPixel.x2 += radiusPixel;
2681     roiPixel.y1 -= radiusPixel;
2682     roiPixel.y2 += radiusPixel;
2683 #ifndef NDEBUG
2684     int sc = 1 << levels;
2685     if (b3) {
2686         assert( (2 * sc - 1 + 2 * sc) < (roiPixel.x2 - roiPixel.x1) );
2687         assert( (2 * sc - 1 + 2 * sc) < (roiPixel.y2 - roiPixel.y1) );
2688     } else {
2689         assert( sc - 1 + sc < (roiPixel.x2 - roiPixel.x1) );
2690         assert( sc - 1 + sc < (roiPixel.y2 - roiPixel.y1) );
2691     }
2692 #endif
2693     OfxRectD roi;
2694     Coords::toCanonical(roiPixel, args.renderScale, par, &roi);
2695 
2696     Coords::rectIntersection<OfxRectD>(roi, srcRod, &roi);
2697     rois.setRegionOfInterest(*_srcClip, roi);
2698 
2699     // if analysis is locked, we do not need the analysis inputs
2700     if ( _analysisLock->getValueAtTime(time) ) {
2701         OfxRectD emptyRoi = {0., 0., 0., 0.};
2702         rois.setRegionOfInterest(*_analysisSrcClip, emptyRoi);
2703         rois.setRegionOfInterest(*_analysisMaskClip, emptyRoi);
2704     }
2705 }
2706 
2707 bool
isIdentity(const IsIdentityArguments & args,Clip * & identityClip,double &,int &,std::string &)2708 DenoiseSharpenPlugin::isIdentity(const IsIdentityArguments &args,
2709                                  Clip * &identityClip,
2710                                  double & /*identityTime*/
2711                                  , int& /*view*/, std::string& /*plane*/)
2712 {
2713     DBG(cout << "isIdentity!\n");
2714 
2715     const double time = args.time;
2716 
2717     if (kLevelMax - startLevelFromRenderScale(args.renderScale) < 0) {
2718         // renderScale is too low for denoising
2719         identityClip = _srcClip;
2720 
2721         return true;
2722     }
2723 
2724     if ( !_analysisLock->getValue() ) {
2725         // analysis not locked, always return source image
2726         identityClip = _srcClip;
2727 
2728         return true;
2729     }
2730 
2731     double mix = _mix->getValueAtTime(time);
2732 
2733     if (mix == 0. /*|| (!processR && !processG && !processB && !processA)*/) {
2734         identityClip = _srcClip;
2735 
2736         return true;
2737     }
2738 
2739     bool processR = _processR->getValueAtTime(time);
2740     bool processG = _processG->getValueAtTime(time);
2741     bool processB = _processB->getValueAtTime(time);
2742     bool processA = _processA->getValueAtTime(time);
2743     if (!processR && !processG && !processB && !processA) {
2744         identityClip = _srcClip;
2745 
2746         return true;
2747     }
2748 
2749     // which plugin parameter values give identity?
2750 
2751     if ( ( (OutputModeEnum)_outputMode->getValueAtTime(time) == eOutputModeNoise ) ||
2752          ( (OutputModeEnum)_outputMode->getValueAtTime(time) == eOutputModeSharpen ) ) {
2753         return false;
2754     }
2755 
2756     if ( processA && !( (_noiseLevel[3][0]->getValueAtTime(time) <= 0.) &&
2757                         ( _noiseLevel[3][1]->getValueAtTime(time) <= 0.) &&
2758                         ( _noiseLevel[3][2]->getValueAtTime(time) <= 0.) &&
2759                         ( _noiseLevel[3][3]->getValueAtTime(time) <= 0.) ) ) {
2760         return false;
2761     }
2762 
2763     ColorModelEnum colorModel = (ColorModelEnum)_colorModel->getValueAtTime(time);
2764     double noiseLevelGain = _noiseLevelGain->getValueAtTime(time);
2765     double gainFreq[4];;
2766     for (unsigned int f = 0; f < 4; ++f) {
2767         if ( _enableFreq[f]->getValueAtTime(time) ) {
2768             gainFreq[f] = noiseLevelGain * _gainFreq[f]->getValueAtTime(time);
2769         } else {
2770             gainFreq[f] = 0;
2771         }
2772     }
2773     double denoiseAmount = _denoiseAmount->getValueAtTime(time);
2774     bool denoise[4];
2775     for (unsigned int c = 0; c < 4; ++c) {
2776         denoise[c] = false;
2777         double denoise_amount = _amount[c]->getValueAtTime(time) * denoiseAmount;
2778         for (unsigned int f = 0; f < 4; ++f) {
2779             double noiseLevel = gainFreq[f] * _noiseLevel[c][f]->getValueAtTime(time);
2780             denoise[c] |= (noiseLevel > 0. && denoise_amount > 0.);
2781         }
2782     }
2783     double sharpenAmount = _sharpenAmount->getValueAtTime(time);
2784     if ( (noiseLevelGain <= 0.) &&
2785          (sharpenAmount <= 0.) ) {
2786         identityClip = _srcClip;
2787 
2788         return true;
2789     } else if ( ( (colorModel == eColorModelRGB) || (colorModel == eColorModelLinearRGB) ) &&
2790                 (!processR || !denoise[0]) &&
2791                 (!processG || !denoise[1]) &&
2792                 (!processR || !denoise[2]) &&
2793                 (!processA || !denoise[3]) &&
2794                 (sharpenAmount <= 0.) ) {
2795         identityClip = _srcClip;
2796 
2797         return true;
2798     } else if ( ( (!processR && !processG && !processB) ||
2799                   ( !denoise[0] &&
2800                     !denoise[1] &&
2801                     !denoise[2] ) ) &&
2802                 (!processA || !denoise[3]) &&
2803                 (sharpenAmount <= 0.) ) {
2804         identityClip = _srcClip;
2805 
2806         return true;
2807     }
2808 
2809     bool doMasking = ( ( !_maskApply || _maskApply->getValueAtTime(time) ) && _maskClip && _maskClip->isConnected() );
2810     if (doMasking) {
2811         bool maskInvert;
2812         _maskInvert->getValueAtTime(time, maskInvert);
2813         if (!maskInvert) {
2814             OfxRectI maskRoD;
2815             Coords::toPixelEnclosing(_maskClip->getRegionOfDefinition(time), args.renderScale, _maskClip->getPixelAspectRatio(), &maskRoD);
2816             // effect is identity if the renderWindow doesn't intersect the mask RoD
2817             if ( !Coords::rectIntersection<OfxRectI>(args.renderWindow, maskRoD, 0) ) {
2818                 identityClip = _srcClip;
2819 
2820                 return true;
2821             }
2822         }
2823     }
2824 
2825     DBG(cout << "isIdentity! false\n");
2826 
2827     return false;
2828 } // DenoiseSharpenPlugin::isIdentity
2829 
2830 void
changedClip(const InstanceChangedArgs & args,const std::string & clipName)2831 DenoiseSharpenPlugin::changedClip(const InstanceChangedArgs &args,
2832                                   const std::string &clipName)
2833 {
2834     DBG(cout << "changedClip!\n");
2835 
2836     if ( (clipName == kOfxImageEffectSimpleSourceClipName) &&
2837          _srcClip && _srcClip->isConnected() &&
2838          !_premultChanged->getValue() &&
2839          ( args.reason == eChangeUserEdit) ) {
2840         if (_srcClip->getPixelComponents() != ePixelComponentRGBA) {
2841             _premult->setValue(false);
2842         } else {
2843             switch ( _srcClip->getPreMultiplication() ) {
2844             case eImageOpaque:
2845                 _premult->setValue(false);
2846                 break;
2847             case eImagePreMultiplied:
2848                 _premult->setValue(true);
2849                 break;
2850             case eImageUnPreMultiplied:
2851                 _premult->setValue(false);
2852                 break;
2853             }
2854         }
2855     }
2856     DBG(cout << "changedClip OK!\n");
2857 }
2858 
2859 void
changedParam(const InstanceChangedArgs & args,const std::string & paramName)2860 DenoiseSharpenPlugin::changedParam(const InstanceChangedArgs &args,
2861                                    const std::string &paramName)
2862 {
2863     const double time = args.time;
2864 
2865     if ( ( (paramName == kParamProcessR) ||
2866            ( paramName == kParamProcessG) ||
2867            ( paramName == kParamProcessB) ||
2868            ( paramName == kParamProcessA) ) && (args.reason == eChangeUserEdit) ) {
2869         updateSecret();
2870     } else if ( (paramName == kParamPremult) && (args.reason == eChangeUserEdit) ) {
2871         _premultChanged->setValue(true);
2872     } else if ( (paramName == kParamColorModel) || (paramName == kParamB3) ) {
2873         updateLabels();
2874         if (args.reason == eChangeUserEdit) {
2875             beginEditBlock(kParamColorModel);
2876             for (unsigned int c = 0; c < 4; ++c) {
2877                 for (unsigned int f = 0; f < 4; ++f) {
2878                     _noiseLevel[c][f]->setValue(0.);
2879                 }
2880             }
2881             endEditBlock();
2882         }
2883     } else if (paramName == kParamAnalysisLock) {
2884         analysisLock();
2885     } else if (paramName == kParamAnalyzeNoiseLevels) {
2886         analyzeNoiseLevels(args);
2887     } else if (paramName == kParamAdaptiveRadius) {
2888         // if adaptiveRadius <= 0, we need to render the whole image anyway, so disable tiles support
2889         int adaptiveRadius = _adaptiveRadius->getValueAtTime(time);
2890         setSupportsTiles(adaptiveRadius > 0);
2891     }
2892 }
2893 
2894 void
analyzeNoiseLevels(const InstanceChangedArgs & args)2895 DenoiseSharpenPlugin::analyzeNoiseLevels(const InstanceChangedArgs &args)
2896 {
2897     DBG(cout << "analysis!\n");
2898 
2899     assert(args.renderScale.x == 1. && args.renderScale.y == 1.);
2900 
2901     progressStartAnalysis(kPluginName " (noise analysis)");
2902     beginEditBlock(kParamAnalyzeNoiseLevels);
2903 
2904     // instantiate the render code based on the pixel depth of the dst clip
2905     PixelComponentEnum dstComponents  = _dstClip->getPixelComponents();
2906 
2907     assert( !_analysisLock->getValue() );
2908 
2909 #ifdef _OPENMP
2910     // set the number of OpenMP threads to a reasonable value
2911     // (but remember that the OpenMP threads are not counted my the multithread suite)
2912     omp_set_num_threads( std::max(1u, MultiThread::getNumCPUs() ) );
2913 #endif
2914 
2915     assert( kSupportsMultipleClipPARs   || !_srcClip || _srcClip->getPixelAspectRatio() == _dstClip->getPixelAspectRatio() );
2916     assert( kSupportsMultipleClipDepths || !_srcClip || _srcClip->getPixelDepth()       == _dstClip->getPixelDepth() );
2917     assert(dstComponents == ePixelComponentRGBA || dstComponents == ePixelComponentRGB /*|| dstComponents == ePixelComponentXY*/ || dstComponents == ePixelComponentAlpha);
2918     // do the rendering
2919     switch (dstComponents) {
2920     case ePixelComponentRGBA:
2921         analyzeNoiseLevelsForComponents<4>(args);
2922         break;
2923     case ePixelComponentRGB:
2924         analyzeNoiseLevelsForComponents<3>(args);
2925         break;
2926 #ifdef OFX_EXTENSIONS_NATRON
2927     //case ePixelComponentXY:
2928     //    renderForComponents<2>(args);
2929     //    break;
2930 #endif
2931     case ePixelComponentAlpha:
2932         analyzeNoiseLevelsForComponents<1>(args);
2933         break;
2934     default:
2935 #ifdef DEBUG_STDOUT
2936         std::cout << "components usupported\n";
2937 #endif
2938         throwSuiteStatusException(kOfxStatErrUnsupported);
2939         break;
2940     } // switch
2941     _analysisFrame->setValue( (int)args.time );
2942 
2943     // lock values
2944     _analysisLock->setValue(true);
2945     endEditBlock();
2946     progressEndAnalysis();
2947 
2948     DBG(cout << "analysis! OK\n");
2949 }
2950 
2951 template<int nComponents>
2952 void
analyzeNoiseLevelsForComponents(const InstanceChangedArgs & args)2953 DenoiseSharpenPlugin::analyzeNoiseLevelsForComponents(const InstanceChangedArgs &args)
2954 {
2955     BitDepthEnum dstBitDepth    = _dstClip->getPixelDepth();
2956 
2957     switch (dstBitDepth) {
2958     case eBitDepthUByte:
2959         analyzeNoiseLevelsForBitDepth<unsigned char, nComponents, 255>(args);
2960         break;
2961 
2962     case eBitDepthUShort:
2963         analyzeNoiseLevelsForBitDepth<unsigned short, nComponents, 65535>(args);
2964         break;
2965 
2966     case eBitDepthFloat:
2967         analyzeNoiseLevelsForBitDepth<float, nComponents, 1>(args);
2968         break;
2969     default:
2970 #ifdef DEBUG_STDOUT
2971         std::cout << "depth usupported\n";
2972 #endif
2973         throwSuiteStatusException(kOfxStatErrUnsupported);
2974     }
2975 }
2976 
2977 template <class PIX, int nComponents, int maxValue>
2978 void
analyzeNoiseLevelsForBitDepth(const InstanceChangedArgs & args)2979 DenoiseSharpenPlugin::analyzeNoiseLevelsForBitDepth(const InstanceChangedArgs &args)
2980 {
2981     assert(args.renderScale.x == 1. && args.renderScale.y == 1.);
2982     const double time = args.time;
2983 
2984     auto_ptr<const Image> src;
2985     auto_ptr<const Image> mask;
2986 
2987     if ( _analysisSrcClip && _analysisSrcClip->isConnected() ) {
2988         src.reset( _analysisSrcClip->fetchImage(time) );
2989     } else {
2990         src.reset( ( _srcClip && _srcClip->isConnected() ) ?
2991                    _srcClip->fetchImage(time) : 0 );
2992     }
2993     if ( src.get() ) {
2994         if ( (src->getRenderScale().x != args.renderScale.x) ||
2995              ( src->getRenderScale().y != args.renderScale.y) ) {
2996             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale");
2997             throwSuiteStatusException(kOfxStatFailed);
2998         }
2999     }
3000     bool doMasking = _analysisMaskClip && _analysisMaskClip->isConnected();
3001     mask.reset(doMasking ? _analysisMaskClip->fetchImage(time) : 0);
3002     if ( mask.get() ) {
3003         if ( (mask->getRenderScale().x != args.renderScale.x) ||
3004              ( mask->getRenderScale().y != args.renderScale.y) ) {
3005             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale");
3006             throwSuiteStatusException(kOfxStatFailed);
3007         }
3008     }
3009     if ( !src.get() ) {
3010         setPersistentMessage(Message::eMessageError, "", "No Source image to analyze");
3011         throwSuiteStatusException(kOfxStatFailed);
3012     }
3013 
3014     bool maskInvert = doMasking ? _maskInvert->getValueAtTime(time) : false;
3015     bool premult = _premult->getValueAtTime(time);
3016     int premultChannel = _premultChannel->getValueAtTime(time);
3017     ColorModelEnum colorModel = (ColorModelEnum)_colorModel->getValueAtTime(time);
3018     bool b3 = _b3->getValueAtTime(time);
3019     OfxRectD cropRect;
3020     _btmLeft->getValueAtTime(time, cropRect.x1, cropRect.y1);
3021     double w, h;
3022     _size->getValueAtTime(time, w, h);
3023     cropRect.x2 = cropRect.x1 + w;
3024     cropRect.y2 = cropRect.y1 + h;
3025 
3026     OfxRectI cropRectI;
3027     cropRectI.x1 = std::ceil(cropRect.x1);
3028     cropRectI.x2 = std::floor(cropRect.x2);
3029     cropRectI.y1 = std::ceil(cropRect.y1);
3030     cropRectI.y2 = std::floor(cropRect.y2);
3031 
3032     OfxRectI srcWindow;
3033     bool intersect = Coords::rectIntersection(src->getBounds(), cropRectI, &srcWindow);
3034     if ( !intersect || ( (srcWindow.x2 - srcWindow.x1) < 80 ) || ( (srcWindow.y2 - srcWindow.y1) < 80 ) ) {
3035         setPersistentMessage(Message::eMessageError, "", "The analysis window must be at least 80x80 pixels.");
3036         throwSuiteStatusException(kOfxStatFailed);
3037     }
3038     clearPersistentMessage();
3039 
3040     // temporary buffers: one for each channel plus 2 for processing
3041     unsigned int iwidth = srcWindow.x2 - srcWindow.x1;
3042     unsigned int iheight = srcWindow.y2 - srcWindow.y1;
3043     unsigned int isize = iwidth * iheight;
3044     auto_ptr<ImageMemory> tmpData( new ImageMemory(sizeof(float) * isize * (nComponents + 3), this) );
3045     float* tmpPixelData = (float*)tmpData->lock();
3046     float* fimgcolor[3] = { NULL, NULL, NULL };
3047     float* fimgalpha = NULL;
3048     float *fimgtmp[3] = { NULL, NULL, NULL };
3049     fimgcolor[0] = (nComponents != 1) ? tmpPixelData : NULL;
3050     fimgcolor[1] = (nComponents != 1) ? tmpPixelData + isize : NULL;
3051     fimgcolor[2] = (nComponents != 1) ? tmpPixelData + 2 * isize : NULL;
3052     fimgalpha = (nComponents == 1) ? tmpPixelData : ( (nComponents == 4) ? tmpPixelData + 3 * isize : NULL );
3053     fimgtmp[0] = tmpPixelData + nComponents * isize;
3054     fimgtmp[1] = tmpPixelData + (nComponents + 1) * isize;
3055     fimgtmp[2] = tmpPixelData + (nComponents + 2) * isize;
3056     auto_ptr<ImageMemory> maskData( doMasking ? new ImageMemory(sizeof(bool) * isize, this) : NULL );
3057     bool* bimgmask = doMasking ? (bool*)maskData->lock() : NULL;
3058 
3059 
3060     // - extract the color components and convert them to the appropriate color model
3061     //
3062 #ifdef _OPENMP
3063 #pragma omp parallel for
3064 #endif
3065     for (int y = srcWindow.y1; y < srcWindow.y2; y++) {
3066         abort_test_loop();
3067 
3068         for (int x = srcWindow.x1; x < srcWindow.x2; x++) {
3069             const PIX *srcPix = (const PIX *)  (src.get() ? src->getPixelAddress(x, y) : 0);
3070             float unpPix[4] = {0., 0., 0., 0.};
3071             ofxsUnPremult<PIX, nComponents, maxValue>(srcPix, unpPix, premult, premultChannel);
3072             unsigned int pix = (x - srcWindow.x1) + (y - srcWindow.y1) * iwidth;
3073             // convert to the appropriate color model and store in tmpPixelData
3074             if (nComponents != 1) {
3075                 if (colorModel == eColorModelLab) {
3076                     if (sizeof(PIX) == 1) {
3077                         // convert to linear
3078                         for (int c = 0; c < 3; ++c) {
3079                             unpPix[c] = _lut->fromColorSpaceFloatToLinearFloat(unpPix[c]);
3080                         }
3081                     }
3082                     Color::rgb709_to_lab(unpPix[0], unpPix[1], unpPix[2], &unpPix[0], &unpPix[1], &unpPix[2]);
3083                     // bring each component in the 0..1 range
3084                     //unpPix[0] = unpPix[0] / 116.0 + 0 * 16 * 27 / 24389.0;
3085                     //unpPix[1] = unpPix[1] / 500.0 / 2.0 + 0.5;
3086                     //unpPix[2] = unpPix[2] / 200.0 / 2.2 + 0.5;
3087                 } else {
3088                     if (colorModel != eColorModelLinearRGB) {
3089                         if (sizeof(PIX) != 1) {
3090                             // convert to rec709
3091                             for (int c = 0; c < 3; ++c) {
3092                                 unpPix[c] = _lut->toColorSpaceFloatFromLinearFloat(unpPix[c]);
3093                             }
3094                         }
3095                     }
3096                     if (colorModel == eColorModelYCbCr) {
3097                         Color::rgb_to_ypbpr709(unpPix[0], unpPix[1], unpPix[2], &unpPix[0], &unpPix[1], &unpPix[2]);
3098                         // bring to the 0-1 range
3099                         //unpPix[1] += 0.5;
3100                         //unpPix[2] += 0.5;
3101                     }
3102                 }
3103                 // store in tmpPixelData
3104                 for (int c = 0; c < 3; ++c) {
3105                     fimgcolor[c][pix] = unpPix[c];
3106                 }
3107             }
3108             if (nComponents != 3) {
3109                 assert(fimgalpha);
3110                 fimgalpha[pix] = unpPix[3];
3111             }
3112             if (doMasking) {
3113                 assert(bimgmask);
3114                 const PIX *maskPix = (const PIX *)  (mask.get() ? mask->getPixelAddress(x, y) : 0);
3115                 bool mask = maskPix ? (*maskPix != 0) : false;
3116                 bimgmask[pix] = maskInvert ? !mask : mask;
3117             }
3118         }
3119     }
3120 
3121     // set noise levels
3122 
3123     if (nComponents != 1) {
3124         // process color channels
3125         for (int c = 0; c < 3; ++c) {
3126             assert(fimgcolor[c]);
3127             float* fimg[4] = { fimgcolor[c], fimgtmp[0], fimgtmp[1], fimgtmp[2] };
3128             double sigma_n[4];
3129             sigma_mad(fimg, bimgmask, iwidth, iheight, b3, sigma_n, (float)c / nComponents, 1.f / nComponents);
3130             for (unsigned f = 0; f < 4; ++f) {
3131                 _noiseLevel[c][f]->setValue(sigma_n[f]);
3132             }
3133         }
3134     }
3135     if (nComponents != 3) {
3136         assert(fimgalpha);
3137         // process alpha
3138         float* fimg[4] = { fimgalpha, fimgtmp[0], fimgtmp[1], fimgtmp[2] };
3139         double sigma_n[4];
3140         sigma_mad(fimg, bimgmask, iwidth, iheight, b3, sigma_n, (float)(nComponents - 1) / nComponents, 1.f / nComponents);
3141         for (unsigned f = 0; f < 4; ++f) {
3142             _noiseLevel[3][f]->setValue(sigma_n[f]);
3143         }
3144     }
3145 } // DenoiseSharpenPlugin::analyzeNoiseLevelsForBitDepth
3146 
3147 void
updateLabels()3148 DenoiseSharpenPlugin::updateLabels()
3149 {
3150     ColorModelEnum colorModel = (ColorModelEnum)_colorModel->getValue();
3151 
3152     for (unsigned c = 0; c < 4; ++c) {
3153         for (unsigned f = 0; f < 4; ++f) {
3154             _noiseLevel[c][f]->setLabel( channelLabel(colorModel, c, f) );
3155         }
3156         _channelGain[c]->setLabel( channelGainLabel(colorModel, c) );
3157         _amount[c]->setLabel( amountLabel(colorModel, c) );
3158     }
3159 }
3160 
3161 void
updateSecret()3162 DenoiseSharpenPlugin::updateSecret()
3163 {
3164     bool process[4];
3165 
3166     process[0] = _processR->getValue();
3167     process[1] = _processG->getValue();
3168     process[2] = _processB->getValue();
3169     process[3] = _processA->getValue();
3170 
3171     ColorModelEnum colorModel = (ColorModelEnum)_colorModel->getValue();
3172     if ( (colorModel == eColorModelYCbCr) || (colorModel == eColorModelLab) ) {
3173         bool processColor = process[0] || process[1] || process[2];
3174         process[0] = process[1] = process[2] = processColor;
3175     }
3176     for (unsigned c = 0; c < 4; ++c) {
3177         for (unsigned f = 0; f < 4; ++f) {
3178             _noiseLevel[c][f]->setIsSecretAndDisabled(!process[c]);
3179         }
3180         _channelGain[c]->setIsSecretAndDisabled(!process[c]);
3181         _amount[c]->setIsSecretAndDisabled(!process[c]);
3182     }
3183 }
3184 
3185 class DenoiseSharpenOverlayDescriptor
3186     : public DefaultEffectOverlayDescriptor<DenoiseSharpenOverlayDescriptor, RectangleInteract>
3187 {
3188 };
3189 
3190 mDeclarePluginFactory(DenoiseSharpenPluginFactory, { gLutManager = new Color::LutManager<Mutex>; ofxsThreadSuiteCheck(); }, { delete gLutManager; });
3191 
3192 void
describe(ImageEffectDescriptor & desc)3193 DenoiseSharpenPluginFactory::describe(ImageEffectDescriptor &desc)
3194 {
3195     DBG(cout << "describe!\n");
3196 
3197     // basic labels
3198     desc.setLabel(kPluginName);
3199     desc.setPluginGrouping(kPluginGrouping);
3200     desc.setPluginDescription(kPluginDescription);
3201     desc.setDescriptionIsMarkdown(true);
3202 
3203     desc.addSupportedContext(eContextFilter);
3204     desc.addSupportedContext(eContextGeneral);
3205     desc.addSupportedContext(eContextPaint);
3206     desc.addSupportedBitDepth(eBitDepthUByte);
3207     desc.addSupportedBitDepth(eBitDepthUShort);
3208     desc.addSupportedBitDepth(eBitDepthFloat);
3209 
3210     // set a few flags
3211     desc.setSingleInstance(false);
3212     desc.setHostFrameThreading(false);
3213     desc.setSupportsMultiResolution(kSupportsMultiResolution);
3214     // For hosts that don't support setting kSupportsTiles on the plugin instance (it appeared
3215     // in OFX 1.4, see <https://groups.google.com/d/msg/ofxa-members/MgvKUWlMljg/LoJeGgWZRDcJ>),
3216     // the plugin descriptor has this property set to false.
3217     desc.setSupportsTiles(false);
3218     desc.setTemporalClipAccess(false);
3219     desc.setRenderTwiceAlways(false);
3220     desc.setSupportsMultipleClipPARs(kSupportsMultipleClipPARs);
3221     desc.setSupportsMultipleClipDepths(kSupportsMultipleClipDepths);
3222     desc.setRenderThreadSafety(kRenderThreadSafety);
3223     desc.setOverlayInteractDescriptor(new DenoiseSharpenOverlayDescriptor);
3224 
3225 #ifdef OFX_EXTENSIONS_NATRON
3226     desc.setChannelSelector(ePixelComponentNone); // we have our own channel selector
3227 #endif
3228     DBG(cout << "describe! OK\n");
3229 }
3230 
3231 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)3232 DenoiseSharpenPluginFactory::describeInContext(ImageEffectDescriptor &desc,
3233                                                ContextEnum context)
3234 {
3235     DBG(cout << "describeInContext!\n");
3236 
3237     // Source clip only in the filter context
3238     // create the mandated source clip
3239     ClipDescriptor *srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName);
3240 
3241     srcClip->setHint(kClipSourceHint);
3242     srcClip->addSupportedComponent(ePixelComponentRGBA);
3243     srcClip->addSupportedComponent(ePixelComponentRGB);
3244 #ifdef OFX_EXTENSIONS_NATRON
3245     //srcClip->addSupportedComponent(ePixelComponentXY);
3246 #endif
3247     srcClip->addSupportedComponent(ePixelComponentAlpha);
3248     srcClip->setTemporalClipAccess(false);
3249     srcClip->setSupportsTiles(kSupportsTiles);
3250     srcClip->setIsMask(false);
3251 
3252     // create the mandated output clip
3253     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
3254     dstClip->addSupportedComponent(ePixelComponentRGBA);
3255     dstClip->addSupportedComponent(ePixelComponentRGB);
3256 #ifdef OFX_EXTENSIONS_NATRON
3257     //dstClip->addSupportedComponent(ePixelComponentXY);
3258 #endif
3259     dstClip->addSupportedComponent(ePixelComponentAlpha);
3260     dstClip->setSupportsTiles(kSupportsTiles);
3261 
3262     ClipDescriptor *maskClip = (context == eContextPaint) ? desc.defineClip("Brush") : desc.defineClip("Mask");
3263     maskClip->setHint(kClipMaskHint);
3264     maskClip->addSupportedComponent(ePixelComponentAlpha);
3265     maskClip->setTemporalClipAccess(false);
3266     if (context != eContextPaint) {
3267         maskClip->setOptional(true);
3268     }
3269     maskClip->setSupportsTiles(kSupportsTiles);
3270     maskClip->setIsMask(true);
3271 
3272     ClipDescriptor *analysisSrcClip = desc.defineClip(kClipAnalysisSource);
3273     analysisSrcClip->setHint(kClipAnalysisSourceHint);
3274     analysisSrcClip->addSupportedComponent(ePixelComponentRGBA);
3275     analysisSrcClip->addSupportedComponent(ePixelComponentRGB);
3276 #ifdef OFX_EXTENSIONS_NATRON
3277     //analysisSrcClip->addSupportedComponent(ePixelComponentXY);
3278 #endif
3279     analysisSrcClip->addSupportedComponent(ePixelComponentAlpha);
3280     analysisSrcClip->setTemporalClipAccess(false);
3281     analysisSrcClip->setOptional(true);
3282     analysisSrcClip->setSupportsTiles(kSupportsTiles);
3283     analysisSrcClip->setIsMask(false);
3284 
3285     ClipDescriptor *analysisMaskClip = desc.defineClip(kClipAnalysisMask);
3286     analysisMaskClip->setHint(kClipAnalysisMaskHint);
3287     analysisMaskClip->addSupportedComponent(ePixelComponentAlpha);
3288     analysisMaskClip->setTemporalClipAccess(false);
3289     analysisMaskClip->setOptional(true);
3290     analysisMaskClip->setSupportsTiles(kSupportsTiles);
3291     analysisMaskClip->setIsMask(true);
3292 
3293     // make some pages and to things in
3294     PageParamDescriptor *page = desc.definePageParam("Controls");
3295     const GroupParamDescriptor* group = NULL;
3296 
3297     {
3298         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessR);
3299         param->setLabel(kParamProcessRLabel);
3300         param->setHint(kParamProcessRHint);
3301         param->setDefault(true);
3302         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3303         if (group) {
3304             // coverity[dead_error_line]
3305             param->setParent(*group);
3306         }
3307         if (page) {
3308             page->addChild(*param);
3309         }
3310     }
3311     {
3312         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessG);
3313         param->setLabel(kParamProcessGLabel);
3314         param->setHint(kParamProcessGHint);
3315         param->setDefault(true);
3316         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3317         if (group) {
3318             // coverity[dead_error_line]
3319             param->setParent(*group);
3320         }
3321         if (page) {
3322             page->addChild(*param);
3323         }
3324     }
3325     {
3326         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessB);
3327         param->setLabel(kParamProcessBLabel);
3328         param->setHint(kParamProcessBHint);
3329         param->setDefault(true);
3330         param->setLayoutHint(eLayoutHintNoNewLine, 1);
3331         if (group) {
3332             // coverity[dead_error_line]
3333             param->setParent(*group);
3334         }
3335         if (page) {
3336             page->addChild(*param);
3337         }
3338     }
3339     {
3340         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamProcessA);
3341         param->setLabel(kParamProcessALabel);
3342         param->setHint(kParamProcessAHint);
3343         param->setDefault(false);
3344         if (group) {
3345             // coverity[dead_error_line]
3346             param->setParent(*group);
3347         }
3348         if (page) {
3349             page->addChild(*param);
3350         }
3351     }
3352 
3353     // describe plugin params
3354     {
3355         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamOutputMode);
3356         param->setLabel(kParamOutputModeLabel);
3357         param->setHint(kParamOutputModeHint);
3358         param->setAnimates(false);
3359         assert(param->getNOptions() == (int)eOutputModeResult);
3360         param->appendOption(kParamOutputModeOptionResult);
3361         assert(param->getNOptions() == (int)eOutputModeNoise);
3362         param->appendOption(kParamOutputModeOptionNoise);
3363         assert(param->getNOptions() == (int)eOutputModeSharpen);
3364         param->appendOption(kParamOutputModeOptionSharpen);
3365         param->setDefault( (int)eOutputModeResult );
3366         if (group) {
3367             // coverity[dead_error_line]
3368             param->setParent(*group);
3369         }
3370         if (page) {
3371             page->addChild(*param);
3372         }
3373     }
3374 
3375     {
3376         ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamColorModel);
3377         param->setLabel(kParamColorModelLabel);
3378         param->setHint(kParamColorModelHint);
3379         param->setAnimates(false);
3380         assert(param->getNOptions() == (int)eColorModelYCbCr);
3381         param->appendOption(kParamColorModelOptionYCbCr);
3382         assert(param->getNOptions() == (int)eColorModelLab);
3383         param->appendOption(kParamColorModelOptionLab);
3384         assert(param->getNOptions() == (int)eColorModelRGB);
3385         param->appendOption(kParamColorModelOptionRGB);
3386         assert(param->getNOptions() == (int)eColorModelLinearRGB);
3387         param->appendOption(kParamColorModelOptionLinearRGB);
3388         param->setDefault( (int)eColorModelYCbCr );
3389         if (group) {
3390             // coverity[dead_error_line]
3391             param->setParent(*group);
3392         }
3393         if (page) {
3394             page->addChild(*param);
3395         }
3396     }
3397 
3398 
3399     {
3400         GroupParamDescriptor* group = desc.defineGroupParam(kGroupAnalysis);
3401         if (group) {
3402             group->setLabel(kGroupAnalysisLabel);
3403             //group->setHint(kGroupAnalysisHint);
3404             group->setEnabled(true);
3405             if (page) {
3406                 page->addChild(*group);
3407             }
3408         }
3409 
3410 
3411         // analysisLock
3412         {
3413             BooleanParamDescriptor* param = desc.defineBooleanParam(kParamAnalysisLock);
3414             param->setLabel(kParamAnalysisLockLabel);
3415             param->setHint(kParamAnalysisLockHint);
3416             param->setDefault(false);
3417             param->setEvaluateOnChange(true); // changes the output mode
3418             param->setAnimates(false);
3419             if (group) {
3420                 param->setParent(*group);
3421             }
3422             if (page) {
3423                 page->addChild(*param);
3424             }
3425         }
3426         // btmLeft
3427         {
3428             Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractBtmLeft);
3429             param->setLabel(kParamRectangleInteractBtmLeftLabel);
3430             param->setDoubleType(eDoubleTypeXYAbsolute);
3431             if ( param->supportsDefaultCoordinateSystem() ) {
3432                 param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
3433             } else {
3434                 gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
3435             }
3436             param->setDefault(0.1, 0.1);
3437             param->setRange(-DBL_MAX, -DBL_MAX, DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
3438             param->setDisplayRange(-10000, -10000, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
3439             param->setIncrement(1.);
3440             param->setHint("Coordinates of the bottom left corner of the analysis rectangle. This rectangle is intersected with the AnalysisMask input, if connected.");
3441             param->setDigits(0);
3442             param->setEvaluateOnChange(false);
3443             param->setAnimates(false);
3444             if (group) {
3445                 param->setParent(*group);
3446             }
3447             if (page) {
3448                 page->addChild(*param);
3449             }
3450         }
3451 
3452         // size
3453         {
3454             Double2DParamDescriptor* param = desc.defineDouble2DParam(kParamRectangleInteractSize);
3455             param->setLabel(kParamRectangleInteractSizeLabel);
3456             param->setDoubleType(eDoubleTypeXY);
3457             if ( param->supportsDefaultCoordinateSystem() ) {
3458                 param->setDefaultCoordinateSystem(eCoordinatesNormalised); // no need of kParamDefaultsNormalised
3459             } else {
3460                 gHostSupportsDefaultCoordinateSystem = false; // no multithread here, see kParamDefaultsNormalised
3461             }
3462             param->setDefault(0.8, 0.8);
3463             param->setRange(0., 0., DBL_MAX, DBL_MAX); // Resolve requires range and display range or values are clamped to (-1,1)
3464             param->setDisplayRange(0, 0, 10000, 10000); // Resolve requires display range or values are clamped to (-1,1)
3465             param->setIncrement(1.);
3466             param->setDimensionLabels(kParamRectangleInteractSizeDim1, kParamRectangleInteractSizeDim2);
3467             param->setHint("Width and height of the analysis rectangle. This rectangle is intersected with the AnalysisMask input, if connected.");
3468             param->setIncrement(1.);
3469             param->setDigits(0);
3470             param->setEvaluateOnChange(false);
3471             param->setAnimates(false);
3472             if (group) {
3473                 param->setParent(*group);
3474             }
3475             if (page) {
3476                 page->addChild(*param);
3477             }
3478         }
3479         {
3480             BooleanParamDescriptor* param = desc.defineBooleanParam(kParamB3);
3481             param->setLabel(kParamB3Label);
3482             param->setHint(kParamB3Hint);
3483             param->setDefault(true);
3484             param->setAnimates(false);
3485             if (group) {
3486                 // coverity[dead_error_line]
3487                 param->setParent(*group);
3488             }
3489             if (page) {
3490                 page->addChild(*param);
3491             }
3492         }
3493         {
3494             IntParamDescriptor* param = desc.defineIntParam(kParamAnalysisFrame);
3495             param->setLabel(kParamAnalysisFrameLabel);
3496             param->setHint(kParamAnalysisFrameHint);
3497             param->setEnabled(false);
3498             param->setAnimates(false);
3499             param->setEvaluateOnChange(false);
3500             param->setDefault(-1);
3501             if (group) {
3502                 param->setParent(*group);
3503             }
3504             if (page) {
3505                 page->addChild(*param);
3506             }
3507         }
3508 
3509         {
3510             PushButtonParamDescriptor* param = desc.definePushButtonParam(kParamAnalyzeNoiseLevels);
3511             param->setLabel(kParamAnalyzeNoiseLevelsLabel);
3512             param->setHint(kParamAnalyzeNoiseLevelsHint);
3513             if (group) {
3514                 param->setParent(*group);
3515             }
3516             if (page) {
3517                 page->addChild(*param);
3518             }
3519         }
3520     }
3521 
3522     {
3523         GroupParamDescriptor* group = desc.defineGroupParam(kGroupNoiseLevels);
3524         if (group) {
3525             group->setLabel(kGroupNoiseLevelsLabel);
3526             //group->setHint(kGroupNoiseLevelsHint);
3527             group->setOpen(false);
3528             group->setEnabled(true);
3529             if (page) {
3530                 page->addChild(*group);
3531             }
3532         }
3533 
3534         for (unsigned f = 0; f < 4; ++f) {
3535             for (unsigned c = 0; c < 4; ++c) {
3536                 DoubleParamDescriptor* param = desc.defineDoubleParam( channelParam(c, f) );
3537                 param->setLabel( channelLabel(eColorModelAny, c, f) );
3538                 param->setHint(kParamNoiseLevelHint);
3539                 param->setRange(0, DBL_MAX);
3540                 param->setDisplayRange(0, kParamNoiseLevelMax);
3541                 param->setAnimates(true);
3542                 if (group) {
3543                     param->setParent(*group);
3544                 }
3545                 if (page) {
3546                     page->addChild(*param);
3547                 }
3548             }
3549         }
3550     }
3551     {
3552         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamNoiseLevelGain);
3553         param->setLabel(kParamNoiseLevelGainLabel);
3554         param->setHint(kParamNoiseLevelGainHint);
3555         param->setRange(0, DBL_MAX);
3556         param->setDisplayRange(0, 2.);
3557         param->setDefault(1.);
3558         param->setAnimates(true);
3559         if (group) {
3560             // coverity[dead_error_line]
3561             param->setParent(*group);
3562         }
3563         if (page) {
3564             page->addChild(*param);
3565         }
3566     }
3567     {
3568         DoubleParamDescriptor* param = desc.defineDoubleParam(kParamDenoiseAmount);
3569         param->setLabel(kParamDenoiseAmountLabel);
3570         param->setHint(kParamDenoiseAmountHint);
3571         param->setRange(0, 1.);
3572         param->setDisplayRange(0, 1.);
3573         param->setDefault(1.);
3574         param->setAnimates(true);
3575         if (group) {
3576             // coverity[dead_error_line]
3577             param->setParent(*group);
3578         }
3579         if (page) {
3580             page->addChild(*param);
3581         }
3582     }
3583     {
3584         GroupParamDescriptor* group = desc.defineGroupParam(kGroupTuning);
3585         if (group) {
3586             group->setLabel(kGroupTuningLabel);
3587             //group->setHint(kGroupTuningHint);
3588             group->setOpen(false);
3589             group->setEnabled(true);
3590             if (page) {
3591                 page->addChild(*group);
3592             }
3593         }
3594 
3595         for (unsigned int f = 0; f < 4; ++f) {
3596             {
3597                 BooleanParamDescriptor* param = desc.defineBooleanParam( enableParam(f) );
3598                 param->setLabel( f == 0 ? kParamEnableHighLabel :
3599                                  ( f == 1 ? kParamEnableMediumLabel :
3600                                    (f == 2 ? kParamEnableLowLabel :
3601                                     kParamEnableVeryLowLabel) ) );
3602                 param->setHint( f == 0 ? kParamEnableHighHint :
3603                                 ( f == 1 ? kParamEnableMediumHint :
3604                                   (f == 2 ? kParamEnableLowHint :
3605                                    kParamEnableVeryLowHint) ) );
3606                 param->setDefault(true);
3607                 param->setAnimates(false);
3608                 if (group) {
3609                     param->setParent(*group);
3610                 }
3611                 if (page) {
3612                     page->addChild(*param);
3613                 }
3614             }
3615             {
3616                 DoubleParamDescriptor* param = desc.defineDoubleParam( gainParam(f) );
3617                 param->setLabel( f == 0 ? kParamGainHighLabel :
3618                                  ( f == 1 ? kParamGainMediumLabel :
3619                                    (f == 2 ? kParamGainLowLabel :
3620                                     kParamGainVeryLowLabel) ) );
3621                 param->setHint( f == 0 ? kParamGainHighHint :
3622                                 ( f == 1 ? kParamGainMediumHint :
3623                                   (f == 2 ? kParamGainLowHint :
3624                                    kParamGainVeryLowHint) ) );
3625                 param->setRange(0, DBL_MAX);
3626                 param->setDisplayRange(0, 10.);
3627                 param->setDefault(1.);
3628                 param->setAnimates(true);
3629                 if (group) {
3630                     param->setParent(*group);
3631                 }
3632                 if (page) {
3633                     page->addChild(*param);
3634                 }
3635             }
3636         }
3637         {
3638             IntParamDescriptor* param = desc.defineIntParam(kParamAdaptiveRadius);
3639             param->setLabel(kParamAdaptiveRadiusLabel);
3640             param->setHint(kParamAdaptiveRadiusHint);
3641             param->setRange(0, 10);
3642             param->setDisplayRange(0, 10);
3643             param->setDefault(kParamAdaptiveRadiusDefault);
3644             param->setAnimates(false);
3645             if (group) {
3646                 // coverity[dead_error_line]
3647                 param->setParent(*group);
3648             }
3649             if (page) {
3650                 page->addChild(*param);
3651             }
3652         }
3653     }
3654     {
3655         GroupParamDescriptor* group = desc.defineGroupParam(kGroupChannelTuning);
3656         if (group) {
3657             group->setLabel(kGroupChannelTuningLabel);
3658             //group->setHint(kGroupChannelTuningHint);
3659             group->setOpen(false);
3660             group->setEnabled(true);
3661             if (page) {
3662                 page->addChild(*group);
3663             }
3664         }
3665 
3666         for (unsigned c = 0; c < 4; ++c) {
3667             {
3668                 DoubleParamDescriptor* param = desc.defineDoubleParam( c == 0 ? kParamYLRGain :
3669                                                                        ( c == 1 ? kParamCbAGGain :
3670                                                                          (c == 2 ? kParamCrBBGain :
3671                                                                           kParamAlphaGain) ) );
3672                 param->setLabel( channelGainLabel(eColorModelAny, c) );
3673                 param->setHint(kParamChannelGainHint);
3674                 param->setRange(0, DBL_MAX);
3675                 param->setDisplayRange(0, 10.);
3676                 param->setDefault(1.);
3677                 param->setAnimates(true);
3678                 if (group) {
3679                     param->setParent(*group);
3680                 }
3681                 if (page) {
3682                     page->addChild(*param);
3683                 }
3684             }
3685             {
3686                 DoubleParamDescriptor* param = desc.defineDoubleParam( c == 0 ? kParamYLRAmount :
3687                                                                        ( c == 1 ? kParamCbAGAmount :
3688                                                                          (c == 2 ? kParamCrBBAmount :
3689                                                                           kParamAlphaAmount) ) );
3690                 param->setLabel( amountLabel(eColorModelAny, c) );
3691                 param->setHint(kParamAmountHint);
3692                 param->setRange(0, 1.);
3693                 param->setDisplayRange(0, 1.);
3694                 param->setDefault(1.);
3695                 param->setAnimates(true);
3696                 if (group) {
3697                     param->setParent(*group);
3698                 }
3699                 if (page) {
3700                     page->addChild(*param);
3701                 }
3702             }
3703         }
3704     }
3705 
3706     {
3707         GroupParamDescriptor* group = desc.defineGroupParam(kGroupSharpen);
3708         if (group) {
3709             group->setLabel(kGroupSharpenLabel);
3710             //group->setHint(kGroupSettingsHint);
3711             group->setEnabled(true);
3712             group->setOpen(false);
3713             if (page) {
3714                 page->addChild(*group);
3715             }
3716         }
3717 
3718 
3719         {
3720             DoubleParamDescriptor* param = desc.defineDoubleParam(kParamSharpenAmount);
3721             param->setLabel(kParamSharpenAmountLabel);
3722             param->setHint(kParamSharpenAmountHint);
3723             param->setRange(0, DBL_MAX);
3724             param->setDisplayRange(0, 10);
3725             param->setDefault(0.);
3726             param->setAnimates(true);
3727             if (group) {
3728                 param->setParent(*group);
3729             }
3730             if (page) {
3731                 page->addChild(*param);
3732             }
3733         }
3734 
3735         {
3736             DoubleParamDescriptor* param = desc.defineDoubleParam(kParamSharpenSize);
3737             param->setLabel(kParamSharpenSizeLabel);
3738             param->setHint(kParamSharpenSizeHint);
3739             param->setRange(0, DBL_MAX);
3740             param->setDisplayRange(8, 32.);
3741             param->setDefault(10.);
3742             param->setAnimates(true);
3743             if (group) {
3744                 param->setParent(*group);
3745             }
3746             if (page) {
3747                 page->addChild(*param);
3748             }
3749         }
3750 
3751         {
3752             BooleanParamDescriptor* param = desc.defineBooleanParam(kParamSharpenLuminance);
3753             param->setLabel(kParamSharpenLuminanceLabel);
3754             param->setHint(kParamSharpenLuminanceHint);
3755             param->setDefault(true);
3756             if (group) {
3757                 param->setParent(*group);
3758             }
3759             if (page) {
3760                 page->addChild(*param);
3761             }
3762         }
3763     }
3764 
3765     ofxsPremultDescribeParams(desc, page);
3766     ofxsMaskMixDescribeParams(desc, page);
3767 
3768     {
3769         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamPremultChanged);
3770         param->setDefault(false);
3771         param->setIsSecretAndDisabled(true);
3772         param->setAnimates(false);
3773         param->setEvaluateOnChange(false);
3774         if (group) {
3775             // coverity[dead_error_line]
3776             param->setParent(*group);
3777         }
3778         if (page) {
3779             page->addChild(*param);
3780         }
3781     }
3782 
3783     // Some hosts (e.g. Resolve) may not support normalized defaults (setDefaultCoordinateSystem(eCoordinatesNormalised))
3784     if (!gHostSupportsDefaultCoordinateSystem) {
3785         BooleanParamDescriptor* param = desc.defineBooleanParam(kParamDefaultsNormalised);
3786         param->setDefault(true);
3787         param->setEvaluateOnChange(false);
3788         param->setIsSecretAndDisabled(true);
3789         param->setIsPersistent(true);
3790         param->setAnimates(false);
3791         if (page) {
3792             page->addChild(*param);
3793         }
3794     }
3795 
3796     DBG(cout << "describeInContext! OK\n");
3797 } // DenoiseSharpenPluginFactory::describeInContext
3798 
3799 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)3800 DenoiseSharpenPluginFactory::createInstance(OfxImageEffectHandle handle,
3801                                             ContextEnum /*context*/)
3802 {
3803     return new DenoiseSharpenPlugin(handle);
3804 }
3805 
3806 static DenoiseSharpenPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
3807 mRegisterPluginFactoryInstance(p)
3808 
3809 OFXS_NAMESPACE_ANONYMOUS_EXIT
3810