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 ¶mName) 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 ¶mName)
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