diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index 93464b2f..ef3f35f3 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -1290,21 +1290,16 @@
- "STD_iwa_BokehFx.on_focus_distance" "On-Focus Distance"
- "STD_iwa_BokehFx.bokeh_amount" "Bokeh Amount"
- "STD_iwa_BokehFx.hardness" "Hardness"
- - "STD_iwa_BokehFx.premultiply1" "Layer1 Premultiply"
- - "STD_iwa_BokehFx.distance1" "Layer1 Distance"
- - "STD_iwa_BokehFx.bokeh_adjustment1" "Layer1 Bokeh Adjustment"
- - "STD_iwa_BokehFx.premultiply2" "Layer2 Premultiply"
- - "STD_iwa_BokehFx.distance2" "Layer2 Distance"
- - "STD_iwa_BokehFx.bokeh_adjustment2" "Layer2 Bokeh Adjustment"
- - "STD_iwa_BokehFx.premultiply3" "Layer3 Premultiply"
- - "STD_iwa_BokehFx.distance3" "Layer3 Distance"
- - "STD_iwa_BokehFx.bokeh_adjustment3" "Layer3 Bokeh Adjustment"
- - "STD_iwa_BokehFx.premultiply4" "Layer4 Premultiply"
- - "STD_iwa_BokehFx.distance4" "Layer4 Distance"
- - "STD_iwa_BokehFx.bokeh_adjustment4" "Layer4 Bokeh Adjustment"
- - "STD_iwa_BokehFx.premultiply5" "Layer5 Premultiply"
- - "STD_iwa_BokehFx.distance5" "Layer5 Distance"
- - "STD_iwa_BokehFx.bokeh_adjustment5" "Layer5 Bokeh Adjustment"
+ - "STD_iwa_BokehFx.distance1" "Source1 Distance"
+ - "STD_iwa_BokehFx.bokeh_adjustment1" "Source1 Bokeh Adjustment"
+ - "STD_iwa_BokehFx.distance2" "Source2 Distance"
+ - "STD_iwa_BokehFx.bokeh_adjustment2" "Source2 Bokeh Adjustment"
+ - "STD_iwa_BokehFx.distance3" "Source3 Distance"
+ - "STD_iwa_BokehFx.bokeh_adjustment3" "Source3 Bokeh Adjustment"
+ - "STD_iwa_BokehFx.distance4" "Source4 Distance"
+ - "STD_iwa_BokehFx.bokeh_adjustment4" "Source4 Bokeh Adjustment"
+ - "STD_iwa_BokehFx.distance5" "Source5 Distance"
+ - "STD_iwa_BokehFx.bokeh_adjustment5" "Source5 Bokeh Adjustment"
- "STD_iwa_BokehRefFx" "Bokeh Ref Iwa"
- "STD_iwa_BokehRefFx.on_focus_distance" "On-Focus Distance"
@@ -1314,6 +1309,42 @@
- "STD_iwa_BokehRefFx.fill_gap" "Fill Gap"
- "STD_iwa_BokehRefFx.fill_gap_with_median_filter" "Use Median Filter"
+ - "STD_iwa_BokehAdvancedFx" "Bokeh Advanced Iwa"
+ - "STD_iwa_BokehAdvancedFx.on_focus_distance" "On-Focus Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_amount" "Bokeh Amount"
+ - "STD_iwa_BokehAdvancedFx.masterHardness" "Master Hardness"
+ - "STD_iwa_BokehAdvancedFx.hardnessPerSource" "Hardness per Source"
+
+ - "STD_iwa_BokehAdvancedFx.distance1" "Source1 Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_adjustment1" "Source1 Bokeh Adjustment"
+ - "STD_iwa_BokehAdvancedFx.hardness1" "Source1 Hardness"
+ - "STD_iwa_BokehAdvancedFx.depth_ref1" "Depth Image"
+ - "STD_iwa_BokehAdvancedFx.depthRange1" "Source1 Depth Range"
+
+ - "STD_iwa_BokehAdvancedFx.distance2" "Source2 Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_adjustment2" "Source2 Bokeh Adjustment"
+ - "STD_iwa_BokehAdvancedFx.hardness2" "Source2 Hardness"
+ - "STD_iwa_BokehAdvancedFx.depth_ref2" "Depth Image"
+ - "STD_iwa_BokehAdvancedFx.depthRange2" "Source2 Depth Range"
+
+ - "STD_iwa_BokehAdvancedFx.distance3" "Source3 Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_adjustment3" "Source3 Bokeh Adjustment"
+ - "STD_iwa_BokehAdvancedFx.hardness3" "Source3 Hardness"
+ - "STD_iwa_BokehAdvancedFx.depth_ref3" "Depth Image"
+ - "STD_iwa_BokehAdvancedFx.depthRange3" "Source3 Depth Range"
+
+ - "STD_iwa_BokehAdvancedFx.distance4" "Source4 Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_adjustment4" "Source4 Bokeh Adjustment"
+ - "STD_iwa_BokehAdvancedFx.hardness4" "Source4 Hardness"
+ - "STD_iwa_BokehAdvancedFx.depth_ref4" "Depth Image"
+ - "STD_iwa_BokehAdvancedFx.depthRange4" "Source4 Depth Range"
+
+ - "STD_iwa_BokehAdvancedFx.distance5" "Source5 Distance"
+ - "STD_iwa_BokehAdvancedFx.bokeh_adjustment5" "Source5 Bokeh Adjustment"
+ - "STD_iwa_BokehAdvancedFx.hardness5" "Source5 Hardness"
+ - "STD_iwa_BokehAdvancedFx.depth_ref5" "Depth Image"
+ - "STD_iwa_BokehAdvancedFx.depthRange5" "Source5 Depth Range"
+
- "STD_iwa_TimeCodeFx" "TimeCode Iwa"
- "STD_iwa_TimeCodeFx.displayType" "Display Type"
- "STD_iwa_TimeCodeFx.frameRate" "Frame Rate"
diff --git a/stuff/doc/BokehAdvancedIwa.html b/stuff/doc/BokehAdvancedIwa.html
new file mode 100644
index 00000000..dbb4e436
--- /dev/null
+++ b/stuff/doc/BokehAdvancedIwa.html
@@ -0,0 +1,104 @@
+
+
+
+ Bokeh Advanced Iwa
+
+
+ Bokeh Iwa
+
+● Overview
+This effect reproduces a camera lens blur. The RGB values of each layer will be converted
+to exposure values, then blurred using the Iris shape, and finally composed together.
+Unlike the Bokeh Iwa Fx, this fx can use depth reference images
+for dividing layer into several sub-layers within specified range of depth.
+Also, this fx can apply individual hardness value for each source for adjusting bokeh brightness.
+To achieve a faster processing time, a Fourier transformation is used to process the filter.
+
+● Input Port
+
+- Iris : Connects the image to define the shape of the Iris. The image luminance
+values will be used for the effect. If no image is connected to the Iris port, no calculation
+will take place. It's possible to connect 8bit or 16bit RGBA images.
+ - Source [1~5] : Connects images for the layers. The order in which they are
+connected here doesn't define the layer stacking order. If nothing is connected to any
+Source port, no calculation will be performed.
+ - Depth [1~] : Connect the Depth reference image for the layer to be divided into sub-layers.
+ The brightness of each pixel corresponds to the depth.
+ The higher (brighter) the value, the farther away from the camera.
+
+
+● Parameters
+Common
+
+- On-Focus Distance : If there's a layer or sub-layer in this position, it will look on focus and it will be
+composed normally. 0 represents the camera position. (Range 0.0-1.0)
+ - Bokeh Amount : Maximum size of blur (in scene units). When the focus position and the
+layer position are 1.0 away from each other and Bokeh Adjustment is 1, the Iris image is
+enlarged until the width of this image reaches this value.
+ - Master Hardness : The gamma value of the film. Used to convert between RGB value and
+exposure. This is equivalent to the increase in RGB value (0.0 to 1.0) when the exposure is
+increased 10 times. The lower the value, the more the highlights are emphasized.
+(Range 0.05-3.0)
+ - Hardness per Source : Specify whether to use individual hardness value instead of the master hardness for converting the layer RGB values to exposures.
+
+Sources
+
+- Distance : The distance of the layer from the camera. The layer stacking order is
+automatically sorted according to this value. (Range 0.0-1.0)
+ - Bokeh Adjustment : Bokeh size correction value. The size of the bokeh is multiplied by N,
+keeping the order of layer stacking. If this value is 0, the layers will be composited normally
+without blurring regardless of the distance of the layer. (Range 0.0-2.0)
+ - Hardness : Individual hardness used for converting this layer's RGB values to exposure.
+
- Depth Image : Specifies the Depth port number. If some available Depth port is set, this layer will be divided into sub-layers at different depths in the specified range.
+
- Depth Range : Specifies the range of depth where the sub-layers will be distributed.
+
+
+● Notes
+
+- Separating a layer into sub-layers is just like the Bokeh Ref Iwa Fx except that the following parameters are hard-coded:
+
+ - Distance Precision = 10
+
- Fill Gap = ON
+
- Use Median Filter = OFF
+
+ - When rendering multiple frames, the Iris and the layers images, must be present within
+the range of all frames to be rendered.
+ - This effect uses a lot of RAM.
+
+
+● License Information
+
+- This effect uses an open source library called Kiss FFT for the Fourier transform.
+
+
+
+This is the BSD-style license for the KissFFT.
+
+Copyright (c) 2003-2010 Mark Borgerding
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that
+the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the author nor the names of any contributors may be used to endorse or promote products derived
+from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+
\ No newline at end of file
diff --git a/stuff/doc/BokehIwa.html b/stuff/doc/BokehIwa.html
index b824316d..6913ecd2 100644
--- a/stuff/doc/BokehIwa.html
+++ b/stuff/doc/BokehIwa.html
@@ -34,10 +34,8 @@ exposure. This is equivalent to the increase in RGB value (0.0 to 1.0) when the
increased 10 times. The lower the value, the more the highlights are emphasized.
(Range 0.05-3.0)
-Layers
+Sources
-- Premultiply : Check this box when connecting unpremultiplied material such as the ones
-from DigiBook, etc. directly to this effect.
- Distance : The distance of the layer from the camera. The layer stacking order is
automatically sorted according to this value. (Range 0.0-1.0)
- Bokeh Adjustment : Bokeh size correction value. The size of the bokeh is multiplied by N,
diff --git a/stuff/doc/BokehRefIwa.html b/stuff/doc/BokehRefIwa.html
index 9bc5cf7a..e7144031 100644
--- a/stuff/doc/BokehRefIwa.html
+++ b/stuff/doc/BokehRefIwa.html
@@ -12,7 +12,7 @@ based on the gradation of the Depth reference image, and the RGB values of each
layer are converted into exposure values, blurred using the Iris shape, and combined.
Higher speed is achieved by using the discrete Fourier transform. Before blurring,
a process of extending the parts of each layer that is hidden in the foreground with a
-Median filter is performed.
+Median filter can be performed.
● Input Port
@@ -39,7 +39,10 @@ is increased 10 times. The lower the value, the more the highlights are emphasiz
- Distance Precision : The number of divisions of the depth reference image. The larger
the value, the finer the gradation of the blur size, but the slower the processing.
(Range 3-128)
-
+ - Fill Gap : Specifies whether to extend the further segmented layer pixels in order to fill gap.
+
- Use Median Filter : Specifies whether to use median filter for the extension.
+ This option is recommended when the depth is distributed discretely.
+
● Notes
diff --git a/stuff/doc/img/fx_iwa_bokeh_advanced.png b/stuff/doc/img/fx_iwa_bokeh_advanced.png
new file mode 100644
index 00000000..03322691
Binary files /dev/null and b/stuff/doc/img/fx_iwa_bokeh_advanced.png differ
diff --git a/stuff/doc/日本語/BokehAdvancedIwa.html b/stuff/doc/日本語/BokehAdvancedIwa.html
new file mode 100644
index 00000000..05a0a557
--- /dev/null
+++ b/stuff/doc/日本語/BokehAdvancedIwa.html
@@ -0,0 +1,74 @@
+
+
+
+ Bokeh Fx Iwa
+
+
+ Bokeh Advanced Iwa
+
+● 概要
+レンズのボケを再現するエフェクトです。各レイヤのRGB値を露光値に変換して、絞り形状でボカし、合成します。
+Bokeh Iwa Fxと異なり、このエフェクトは任意のレイヤーを深度参照画像を用いてサブレイヤーに分割することができます。また、各レイヤーに異なるHardnessの値を用いて、ボケの明るさを調整することができます。
+フィルタ処理にフーリエ変換を用いて高速化を図っています。
+
+● 入力ポート
+
+- Iris : 絞り画像を接続します。入力された画像の輝度値がフィルタに用いられます。Irisポートに何も接続されていない場合は、計算が行われません。RGBA8bit又はRGBA16bit画像が入力できます。
+
- Source[1~5] : レイヤー画像を接続します。ここでの接続の順番は、レイヤーの重ね順に影響しません。全てのSourceポートに何も接続されていない場合は、計算が行われません。
+
- Depth[1~] : サブレイヤーに分割したいレイヤーがあるとき、深度参照画像を接続します。
+
+
+● パラメータ
+共通パラメータ
+
+- On-Focus Distance : フォーカス位置。この位置にレイヤーがあると、
+そのレイヤーはボカされず、通常合成されます。0がカメラ位置です。(範囲 0.0~1.0)
+
- Bokeh Amount : ボケの最大サイズ(単位 Unit)。フォーカス位置とレイヤー位置が 1.0 離れていて、Bokeh Adjustmentが 1 のとき、絞り画像の横幅がこの値になるまで拡大されて用いられます。
+
- Master Hardness : フィルムのガンマ値。RGB値と露光量の変換に用います。露光量が10倍増えた時の、RGB値(0.0~1.0)の増加量に相当します。この値が小さいほど、ハイライトが強調されます。(範囲 0.05~3.0)
+
- Hardness per Source : ONのとき、各レイヤーのRGB値を露光値に変換するとき、個別のHardnessの値を用いることができます。
+
+レイヤー毎のパラメータ
+
+- Distance : レイヤーのカメラからの距離。この値に合わせ、レイヤーの重なる順序が自動的にソートされます。(範囲 0.0~1.0)
+
- Bokeh Adjustment : ボケサイズの補正値。レイヤーの重なる順序はそのままに、ボケのサイズが N 倍されます。この値が 0 なら、どの距離にレイヤーを置いても、ボケずに通常合成されます。(範囲 0.0~2.0)
+
- Hardness : 各レイヤーのRGB値を露光値に変換するときに用いる個々のHardness値。(Hardness per SourceがONのとき有効)
+
- Depth Image : 深度参照画像を接続したポート番号を指定します。有効なポート番号が指定されているとき、このレイヤーはサブレイヤーに分割され、Depth Rangeで指定された奥行きの範囲に分布します。
+
- Depth Range : レイヤーをサブレイヤーに分割するときの奥行の範囲を指定します。
+
+
+● 注意点
+
+- レイヤーを深度参照画像を用いてサブレイヤーに分割するのは、Bokeh Ref Iwa Fxの挙動とほぼ同じです。ただし、下記のパラメータは固定となっています:
+
+ - Distance Precision = 10
+
- Fill Gap = ON
+
- Use Median Filter = OFF
+
+ - 複数フレームをレンダリングする際、レイヤーだけでなくIrisの素材も、レンダリングされるすべてのフレームの範囲に入っている必要があります。
+
- メモリを多く使います。
+
+
+● ライセンス情報
+
+- このエフェクトは、フーリエ変換にKiss FFTというオープンソース・ライブラリを用いています。
+
+
+
+This is the BSD-style license for the KissFFT.
+
+Copyright (c) 2003-2010 Mark Borgerding
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ * Neither the author nor the names of any contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+
+
\ No newline at end of file
diff --git a/stuff/doc/日本語/img/fx_iwa_bokeh_advanced.png b/stuff/doc/日本語/img/fx_iwa_bokeh_advanced.png
new file mode 100644
index 00000000..03322691
Binary files /dev/null and b/stuff/doc/日本語/img/fx_iwa_bokeh_advanced.png differ
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_BokehAdvancedFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_BokehAdvancedFx.xml
new file mode 100644
index 00000000..473f9e48
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_BokehAdvancedFx.xml
@@ -0,0 +1,60 @@
+
+
+
+ on_focus_distance
+ bokeh_amount
+ masterHardness
+ hardnessPerSource
+
+
+
+ distance1
+ bokeh_adjustment1
+ hardness1
+ depth_ref1
+ depthRange1
+
+
+
+ distance2
+ bokeh_adjustment2
+ hardness2
+ depth_ref2
+ depthRange2
+
+
+
+ distance3
+ bokeh_adjustment3
+ hardness3
+ depth_ref3
+ depthRange3
+
+
+
+ distance4
+ bokeh_adjustment4
+ hardness4
+ depth_ref4
+ depthRange4
+
+
+
+ distance5
+ bokeh_adjustment5
+ hardness5
+ depth_ref5
+ depthRange5
+
+
+
+ hardnessPerSource
+ hardness1
+ hardness2
+ hardness3
+ hardness4
+ hardness5
+
+
+
+
\ No newline at end of file
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_BokehFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_BokehFx.xml
index c2548748..e5c7a8cc 100644
--- a/stuff/profiles/layouts/fxs/STD_iwa_BokehFx.xml
+++ b/stuff/profiles/layouts/fxs/STD_iwa_BokehFx.xml
@@ -6,37 +6,32 @@
hardness
-
+
- premultiply1
distance1
bokeh_adjustment1
-
+
- premultiply2
distance2
bokeh_adjustment2
-
+
- premultiply3
distance3
bokeh_adjustment3
-
+
- premultiply4
distance4
bokeh_adjustment4
-
+
- premultiply5
distance5
bokeh_adjustment5
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_BokehRefFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_BokehRefFx.xml
index 027792c4..6df0b34f 100644
--- a/stuff/profiles/layouts/fxs/STD_iwa_BokehRefFx.xml
+++ b/stuff/profiles/layouts/fxs/STD_iwa_BokehRefFx.xml
@@ -7,6 +7,10 @@
distance_precision
fill_gap
fill_gap_with_median_filter
-
+
+
+ fill_gap
+ fill_gap_with_median_filter
+
diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst
index aab063e7..cb844793 100644
--- a/stuff/profiles/layouts/fxs/fxs.lst
+++ b/stuff/profiles/layouts/fxs/fxs.lst
@@ -22,6 +22,7 @@
STD_inoSpinBlurFx
STD_iwa_BokehFx
STD_iwa_BokehRefFx
+ STD_iwa_BokehAdvancedFx
STD_freeDistortFx
diff --git a/toonz/sources/include/toonzqt/paramfield.h b/toonz/sources/include/toonzqt/paramfield.h
index 1fca2dc1..f20848f4 100644
--- a/toonz/sources/include/toonzqt/paramfield.h
+++ b/toonz/sources/include/toonzqt/paramfield.h
@@ -388,7 +388,7 @@ public:
void updateField(double value) override;
- QSize getPreferedSize() override { return QSize(260, 28); }
+ QSize getPreferedSize() override { return QSize(260, 26); }
void setPrecision(int precision) override;
protected slots:
@@ -534,11 +534,14 @@ signals:
class DVAPI ModeSensitiveBox final : public QWidget {
Q_OBJECT
QList m_modes;
+ int m_currentMode;
public:
ModeSensitiveBox(QWidget *parent, ModeChangerParamField *modeChanger,
QList modes);
+ ModeSensitiveBox(QWidget *parent, QCheckBox *checkBox);
QList modes() { return m_modes; }
+ bool isActive() { return m_modes.contains(m_currentMode); }
protected slots:
void onModeChanged(int mode);
};
@@ -612,7 +615,7 @@ public:
int frame) override;
void update(int frame) override;
- QSize getPreferedSize() override { return QSize(50, 20); }
+ QSize getPreferedSize() override { return QSize(50, 19); }
protected slots:
void onChange(bool isDragging = false);
diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt
index 33e010fe..47ec7067 100644
--- a/toonz/sources/stdfx/CMakeLists.txt
+++ b/toonz/sources/stdfx/CMakeLists.txt
@@ -83,6 +83,8 @@ set(HEADERS
iwa_fractalnoisefx.h
iwa_rainbow_intensity.h
iwa_rainbowfx.h
+ iwa_bokeh_advancedfx.h
+ iwa_bokeh_util.h
)
if(OpenCV_FOUND)
@@ -275,6 +277,8 @@ set(SOURCES
iwa_glarefx.cpp
iwa_fractalnoisefx.cpp
iwa_rainbowfx.cpp
+ iwa_bokeh_advancedfx.cpp
+ iwa_bokeh_util.cpp
)
if(OpenCV_FOUND)
diff --git a/toonz/sources/stdfx/iwa_bokeh_advancedfx.cpp b/toonz/sources/stdfx/iwa_bokeh_advancedfx.cpp
new file mode 100644
index 00000000..5ff077dd
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bokeh_advancedfx.cpp
@@ -0,0 +1,321 @@
+#include "iwa_bokeh_advancedfx.h"
+
+#include "trop.h"
+#include "trasterfx.h"
+#include "trasterimage.h"
+
+#include
+#include
+#include
+#include
+#include
+
+namespace {
+QReadWriteLock lock;
+QMutex fx_mutex;
+bool isFurtherLayer(const QPair val1,
+ const QPair val2) {
+ // if the layers are at the same depth, then put the layer with smaller index
+ // above
+ if (val1.second == val2.second) return val1.first > val2.first;
+ return val1.second > val2.second;
+}
+
+template
+TRasterGR8P allocateRasterAndLock(T** buf, TDimensionI dim) {
+ TRasterGR8P ras(dim.lx * sizeof(T), dim.ly);
+ ras->lock();
+ *buf = (T*)ras->getRawData();
+ return ras;
+}
+
+// release all registered raster memories and free all fft plans
+void releaseAllRastersAndPlans(QList& rasterList,
+ QList& planList) {
+ for (int r = 0; r < rasterList.size(); r++) rasterList.at(r)->unlock();
+ for (int p = 0; p < planList.size(); p++) kiss_fft_free(planList.at(p));
+}
+
+}; // namespace
+
+//--------------------------------------------
+// sort source images
+QList Iwa_BokehAdvancedFx::getSortedSourceIndices(double frame) {
+ QList> usedSourceList;
+
+ // gather connected sources
+ for (int i = 0; i < LAYER_NUM; i++) {
+ if (m_layerParams[i].m_source.isConnected())
+ usedSourceList.push_back(
+ QPair(i, m_layerParams[i].m_distance->getValue(frame)));
+ }
+
+ if (usedSourceList.empty()) return QList();
+
+ // sort by distance
+ std::sort(usedSourceList.begin(), usedSourceList.end(), isFurtherLayer);
+
+ QList indicesList;
+ for (int i = 0; i < usedSourceList.size(); i++) {
+ indicesList.push_back(usedSourceList.at(i).first);
+ }
+
+ return indicesList;
+}
+
+//--------------------------------------------
+// Compute the bokeh size for each layer. The source tile will be enlarged by
+// the largest size of them.
+QMap Iwa_BokehAdvancedFx::getIrisSizes(
+ const double frame, const QList sourceIndices,
+ const double bokehPixelAmount, double& maxIrisSize) {
+ // create a list of available reference image ports
+ QList availableCtrPorts;
+ for (int i = 0; i < getInputPortCount(); ++i) {
+ QString portName = QString::fromStdString(getInputPortName(i));
+ if (portName.startsWith("Depth") && getInputPort(i)->isConnected())
+ availableCtrPorts.push_back(portName.remove(0, 5).toInt());
+ }
+
+ double focus = m_onFocusDistance->getValue(frame);
+ double max = 0.0;
+ QMap irisSizes;
+ for (int s = 0; s < sourceIndices.size(); s++) {
+ int index = sourceIndices.at(s);
+
+ double layer = m_layerParams[index].m_distance->getValue(frame);
+ double adjust = m_layerParams[index].m_bokehAdjustment->getValue(frame);
+
+ double irisSize;
+ // In case there is no available reference image
+ if (m_layerParams[index].m_depth_ref->getValue() == 0 ||
+ !availableCtrPorts.contains(
+ m_layerParams[index].m_depth_ref->getValue())) {
+ irisSize = (focus - layer) * bokehPixelAmount * adjust;
+ }
+ // in case using the reference image
+ else {
+ double refRangeHalf =
+ m_layerParams[index].m_depthRange->getValue(frame) * 0.5;
+ double nearToFocus = focus - layer + refRangeHalf;
+ double farToFocus = focus - layer - refRangeHalf;
+ // take further point from the focus distance
+ if (std::abs(nearToFocus) > std::abs(farToFocus))
+ irisSize = nearToFocus * bokehPixelAmount * adjust;
+ else
+ irisSize = farToFocus * bokehPixelAmount * adjust;
+ }
+
+ irisSizes[index] = irisSize;
+
+ // update the maximum
+ if (max < std::abs(irisSize)) max = std::abs(irisSize);
+ }
+ maxIrisSize = max;
+
+ return irisSizes;
+}
+
+//-----------------------------------------------------
+// return true if the control image is available and used
+bool Iwa_BokehAdvancedFx::portIsUsed(int portIndex) {
+ for (int layer = 0; layer < LAYER_NUM; layer++) {
+ if (m_layerParams[layer].m_source.isConnected() &&
+ m_layerParams[layer].m_depth_ref->getValue() == portIndex)
+ return true;
+ }
+ return false;
+}
+
+//--------------------------------------------
+Iwa_BokehAdvancedFx::Iwa_BokehAdvancedFx()
+ : m_hardnessPerSource(false), m_control("Depth") {
+ // Bind common parameters
+ bindParam(this, "on_focus_distance", m_onFocusDistance, false);
+ bindParam(this, "bokeh_amount", m_bokehAmount, false);
+ bindParam(this, "masterHardness", m_hardness, false);
+ bindParam(this, "hardnessPerSource", m_hardnessPerSource, false);
+
+ // Bind layer parameters
+ for (int layer = 0; layer < LAYER_NUM; layer++) {
+ m_layerParams[layer].m_distance = TDoubleParamP(0.5);
+ m_layerParams[layer].m_bokehAdjustment = TDoubleParamP(1);
+
+ m_layerParams[layer].m_hardness = TDoubleParamP(0.3);
+ m_layerParams[layer].m_depth_ref = TIntParamP(0);
+ m_layerParams[layer].m_depthRange = TDoubleParamP(1.0);
+
+ std::string str = QString("Source%1").arg(layer + 1).toStdString();
+ addInputPort(str, m_layerParams[layer].m_source);
+ bindParam(this, QString("distance%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_distance);
+ bindParam(this, QString("bokeh_adjustment%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_bokehAdjustment);
+
+ bindParam(this, QString("hardness%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_hardness);
+ bindParam(this, QString("depth_ref%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_depth_ref);
+ bindParam(this, QString("depthRange%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_depthRange);
+
+ m_layerParams[layer].m_distance->setValueRange(0.0, 1.0);
+ m_layerParams[layer].m_bokehAdjustment->setValueRange(0.0, 2.0);
+
+ m_layerParams[layer].m_hardness->setValueRange(0.05, 3.0);
+ m_layerParams[layer].m_depthRange->setValueRange(0.0, 1.0);
+ }
+
+ addInputPort("Depth1", new TRasterFxPort, 0);
+}
+
+//--------------------------------------------
+void Iwa_BokehAdvancedFx::doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) {
+ // If the iris is not connected, then do nothing
+ if (!m_iris.isConnected()) {
+ tile.getRaster()->clear();
+ return;
+ }
+ // If none of the source ports is connected, then do nothing
+ bool sourceIsConnected = false;
+ for (int i = 0; i < LAYER_NUM; i++) {
+ if (m_layerParams[i].m_source.isConnected()) {
+ sourceIsConnected = true;
+ break;
+ }
+ }
+ if (!sourceIsConnected) {
+ tile.getRaster()->clear();
+ return;
+ }
+
+ // Sort source layers by distance
+ QList sourceIndices = getSortedSourceIndices(frame);
+
+ // Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
+ double bokehPixelAmount = BokehUtils::getBokehPixelAmount(
+ m_bokehAmount->getValue(frame), settings.m_affine);
+
+ // Compute the bokeh size for each layer. The source tile will be enlarged by
+ // the largest size of them.
+ double maxIrisSize;
+ QMap irisSizes =
+ getIrisSizes(frame, sourceIndices, bokehPixelAmount, maxIrisSize);
+
+ int margin = (int)std::ceil(maxIrisSize * 0.5);
+
+ TRectD _rectOut(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
+ tile.getRaster()->getLy()));
+ _rectOut = _rectOut.enlarge(static_cast(margin));
+
+ TDimensionI dimOut(static_cast(_rectOut.getLx() + 0.5),
+ static_cast(_rectOut.getLy() + 0.5));
+
+ // Enlarge the size to the "fast size" for kissfft which has no factors other
+ // than 2,3, or 5.
+ if (dimOut.lx < 10000 && dimOut.ly < 10000) {
+ int new_x = kiss_fft_next_fast_size(dimOut.lx);
+ int new_y = kiss_fft_next_fast_size(dimOut.ly);
+ // margin should be integer
+ while ((new_x - dimOut.lx) % 2 != 0)
+ new_x = kiss_fft_next_fast_size(new_x + 1);
+ while ((new_y - dimOut.ly) % 2 != 0)
+ new_y = kiss_fft_next_fast_size(new_y + 1);
+
+ _rectOut = _rectOut.enlarge(static_cast(new_x - dimOut.lx) / 2.0,
+ static_cast(new_y - dimOut.ly) / 2.0);
+
+ dimOut.lx = new_x;
+ dimOut.ly = new_y;
+ }
+
+ // compute the input tiles
+ QMap sourceTiles;
+ for (auto index : sourceIndices) {
+ TTile* layerTile = new TTile();
+ m_layerParams[index].m_source->allocateAndCompute(
+ *layerTile, _rectOut.getP00(), dimOut, tile.getRaster(), frame,
+ settings);
+ sourceTiles[index] = layerTile;
+ }
+
+ // obtain pixel size of original iris image
+ TRectD irisBBox;
+ m_iris->getBBox(frame, irisBBox, settings);
+ // compute the iris tile
+ TTile irisTile;
+ m_iris->allocateAndCompute(
+ irisTile, irisBBox.getP00(),
+ TDimension(static_cast(irisBBox.getLx() + 0.5),
+ static_cast(irisBBox.getLy() + 0.5)),
+ tile.getRaster(), frame, settings);
+
+ // compute the reference image
+ std::vector ctrl_rasters; // to be stored in uchar
+ QMap
+ ctrls; // container of [port number, reference image buffer in uchar]
+ for (int i = 0; i < getInputPortCount(); ++i) {
+ QString portName = QString::fromStdString(getInputPortName(i));
+ if (portName.startsWith("Depth") && getInputPort(i)->isConnected()) {
+ int portIndex = portName.remove(0, 5).toInt();
+ if (portIsUsed(portIndex)) {
+ TTile tmpTile;
+ TRasterFxPort* tmpCtrl = dynamic_cast(getInputPort(i));
+ (*tmpCtrl)->allocateAndCompute(tmpTile, _rectOut.getP00(), dimOut,
+ tile.getRaster(), frame, settings);
+ TRasterGR8P ctrlRas(dimOut.lx, dimOut.ly);
+ ctrlRas->lock();
+ unsigned char* ctrl_mem = (unsigned char*)ctrlRas->getRawData();
+ TRaster32P ras32 = (TRaster32P)tmpTile.getRaster();
+ TRaster64P ras64 = (TRaster64P)tmpTile.getRaster();
+ lock.lockForRead();
+ if (ras32)
+ BokehUtils::setDepthRaster(ras32, ctrl_mem,
+ dimOut);
+ else if (ras64)
+ BokehUtils::setDepthRaster(ras64, ctrl_mem,
+ dimOut);
+ lock.unlock();
+ ctrl_rasters.push_back(ctrlRas);
+ ctrls[portIndex] = ctrl_mem;
+ }
+ }
+ }
+
+ double masterHardness = m_hardness->getValue(frame);
+
+ QList layerValues;
+ for (auto index : sourceIndices) {
+ LayerValue layerValue;
+ layerValue.sourceTile = sourceTiles[index];
+ layerValue.premultiply =
+ false; // assuming input images are always premultiplied
+ layerValue.layerHardness =
+ (m_hardnessPerSource->getValue())
+ ? m_layerParams[index].m_hardness->getValue(frame)
+ : masterHardness;
+ layerValue.depth_ref = m_layerParams[index].m_depth_ref->getValue();
+ layerValue.irisSize = irisSizes.value(index);
+ layerValue.distance = m_layerParams[index].m_distance->getValue(frame);
+ layerValue.bokehAdjustment =
+ m_layerParams[index].m_bokehAdjustment->getValue(frame);
+ layerValue.depthRange = m_layerParams[index].m_depthRange->getValue(frame);
+ layerValue.distancePrecision = 10;
+ layerValue.fillGap = true;
+ layerValue.doMedian = false;
+
+ layerValues.append(layerValue);
+ }
+
+ Iwa_BokehCommonFx::doFx(tile, frame, settings, bokehPixelAmount, margin,
+ dimOut, irisBBox, irisTile, layerValues, ctrls);
+
+ // release control image buffers
+ for (int r = 0; r < ctrl_rasters.size(); r++) ctrl_rasters[r]->unlock();
+
+ qDeleteAll(sourceTiles);
+}
+
+//--------------------------------------------
+FX_PLUGIN_IDENTIFIER(Iwa_BokehAdvancedFx, "iwa_BokehAdvancedFx")
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bokeh_advancedfx.h b/toonz/sources/stdfx/iwa_bokeh_advancedfx.h
new file mode 100644
index 00000000..1272a0d9
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bokeh_advancedfx.h
@@ -0,0 +1,70 @@
+#pragma once
+//----------------------------------------
+// Iwa_BokehAdvancedFx
+// Advanced version of Bokeh Fx Iwa
+// - Enabled to set different hardness for each layer
+// - Enabled to apply the depth referene image for each layer
+//----------------------------------------
+
+#ifndef IWA_BOKEH_ADVANCED_H
+#define IWA_BOKEH_ADVANCED_H
+
+#include "stdfx.h"
+#include "tfxparam.h"
+#include "traster.h"
+
+#include "kiss_fft.h"
+#include "tools/kiss_fftnd.h"
+#include "iwa_bokeh_util.h"
+
+#include
+#include
+
+const int LAYER_NUM = 5;
+
+//------------------------------------
+
+class Iwa_BokehAdvancedFx : public Iwa_BokehCommonFx {
+ FX_PLUGIN_DECLARATION(Iwa_BokehAdvancedFx)
+
+protected:
+ TFxPortDG m_control;
+
+ TBoolParamP m_hardnessPerSource; // switch between layer and master hardness
+
+ struct LAYERPARAM {
+ TRasterFxPort m_source;
+ TDoubleParamP m_distance; // The layer distance from the camera (0-1)
+ TDoubleParamP m_bokehAdjustment; // Factor for adjusting distance (= focal
+ // distance - layer distance) (0-2.0)
+ TDoubleParamP m_hardness; // film gamma for each layer
+ TIntParamP m_depth_ref; // port index of depth reference image
+ TDoubleParamP m_depthRange; // distance range varies depends on the
+ // brightness of the reference image (0-1)
+ };
+ std::array m_layerParams;
+
+ // sort source images
+ QList getSortedSourceIndices(double frame);
+ // Compute the bokeh size for each layer. The source tile will be enlarged by
+ // the largest size of them.
+ QMap getIrisSizes(const double frame,
+ const QList sourceIndices,
+ const double bokehPixelAmount,
+ double& maxIrisSize);
+ // return true if the control image is available and used
+ bool portIsUsed(int portIndex);
+
+public:
+ Iwa_BokehAdvancedFx();
+
+ void doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) override;
+
+ int dynamicPortGroupsCount() const override { return 1; }
+ const TFxPortDG* dynamicPortGroup(int g) const override {
+ return (g == 0) ? &m_control : 0;
+ }
+};
+
+#endif
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bokeh_util.cpp b/toonz/sources/stdfx/iwa_bokeh_util.cpp
new file mode 100644
index 00000000..485e96e2
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bokeh_util.cpp
@@ -0,0 +1,1902 @@
+
+#include "iwa_bokeh_util.h"
+
+#include "trop.h"
+#include
+
+#include
+#include
+#include
+
+namespace {
+QReadWriteLock lock;
+QMutex mutex;
+
+// modify fft coordinate to normal
+inline int getCoord(int index, int lx, int ly) {
+ int i = index % lx;
+ int j = index / lx;
+
+ int cx = i - lx / 2;
+ int cy = j - ly / 2;
+
+ if (cx < 0) cx += lx;
+ if (cy < 0) cy += ly;
+
+ return cy * lx + cx;
+}
+
+// RGB value <--> Exposure
+inline double valueToExposure(double value, double filmGamma) {
+ double logVal = (value - 0.5) / filmGamma;
+ return std::pow(10.0, logVal);
+}
+inline double exposureToValue(double exposure, double filmGamma) {
+ return std::log10(exposure) * filmGamma + 0.5;
+}
+
+inline double clamp01(double val) {
+ return (val < 0.0) ? 0.0 : ((val > 1.0) ? 1.0 : val);
+}
+
+template
+TRasterGR8P allocateRasterAndLock(T** buf, TDimensionI dim) {
+ TRasterGR8P ras(dim.lx * sizeof(T), dim.ly);
+ ras->lock();
+ *buf = (T*)ras->getRawData();
+ return ras;
+}
+
+// release all registered raster memories and free all fft plans
+void releaseAllRastersAndPlans(QList& rasterList,
+ QList& planList) {
+ for (int r = 0; r < rasterList.size(); r++) rasterList.at(r)->unlock();
+ for (int p = 0; p < planList.size(); p++) kiss_fft_free(planList.at(p));
+}
+} // namespace
+
+//--------------------------------------------
+// thread for computing bokeh of each channel
+//--------------------------------------------
+
+BokehUtils::MyThread::MyThread(Channel channel, TRasterP layerTileRas,
+ double4* result, double* alpha_bokeh,
+ kiss_fft_cpx* kissfft_comp_iris,
+ double layerHardness, double masterHardness,
+ bool doLightenComp)
+ : m_channel(channel)
+ , m_layerTileRas(layerTileRas)
+ , m_result(result)
+ , m_alpha_bokeh(alpha_bokeh)
+ , m_kissfft_comp_iris(kissfft_comp_iris)
+ , m_layerHardness(layerHardness)
+ , m_masterHardness(masterHardness)
+ , m_finished(false)
+ , m_kissfft_comp_in(0)
+ , m_kissfft_comp_out(0)
+ , m_isTerminated(false)
+ , m_doLightenComp(doLightenComp) {
+ if (m_masterHardness == 0.0) m_masterHardness = m_layerHardness;
+}
+
+bool BokehUtils::MyThread::init() {
+ // obtain the input image size
+ int lx, ly;
+ lx = m_layerTileRas->getSize().lx;
+ ly = m_layerTileRas->getSize().ly;
+
+ // memory allocation for input
+ m_kissfft_comp_in_ras = TRasterGR8P(lx * sizeof(kiss_fft_cpx), ly);
+ m_kissfft_comp_in_ras->lock();
+ m_kissfft_comp_in = (kiss_fft_cpx*)m_kissfft_comp_in_ras->getRawData();
+
+ // allocation check
+ if (m_kissfft_comp_in == 0) return false;
+
+ // cancel check
+ if (m_isTerminated) {
+ m_kissfft_comp_in_ras->unlock();
+ return false;
+ }
+
+ // memory allocation for output
+ m_kissfft_comp_out_ras = TRasterGR8P(lx * sizeof(kiss_fft_cpx), ly);
+ m_kissfft_comp_out_ras->lock();
+ m_kissfft_comp_out = (kiss_fft_cpx*)m_kissfft_comp_out_ras->getRawData();
+
+ // allocation check
+ if (m_kissfft_comp_out == 0) {
+ m_kissfft_comp_in_ras->unlock();
+ m_kissfft_comp_in = 0;
+ return false;
+ }
+
+ // cancel check
+ if (m_isTerminated) {
+ m_kissfft_comp_in_ras->unlock();
+ m_kissfft_comp_in = 0;
+ m_kissfft_comp_out_ras->unlock();
+ m_kissfft_comp_out = 0;
+ return false;
+ }
+
+ // create the forward FFT plan
+ int dims[2] = {ly, lx};
+ int ndims = 2;
+ m_kissfft_plan_fwd = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
+ // allocation and cancel check
+ if (m_kissfft_plan_fwd == NULL || m_isTerminated) {
+ m_kissfft_comp_in_ras->unlock();
+ m_kissfft_comp_in = 0;
+ m_kissfft_comp_out_ras->unlock();
+ m_kissfft_comp_out = 0;
+ return false;
+ }
+
+ // create the backward FFT plan
+ m_kissfft_plan_bkwd = kiss_fftnd_alloc(dims, ndims, true, 0, 0);
+ // allocation and cancel check
+ if (m_kissfft_plan_bkwd == NULL || m_isTerminated) {
+ m_kissfft_comp_in_ras->unlock();
+ m_kissfft_comp_in = 0;
+ m_kissfft_comp_out_ras->unlock();
+ m_kissfft_comp_out = 0;
+ kiss_fft_free(m_kissfft_plan_fwd);
+ m_kissfft_plan_fwd = NULL;
+ return false;
+ }
+
+ // return true if all the initializations are done
+ return true;
+}
+
+//------------------------------------------------------------
+// convert layer RGB to exposure
+// multiply alpha
+// set to kiss_fft_cpx
+
+template
+void BokehUtils::MyThread::setLayerRaster(const RASTER srcRas,
+ kiss_fft_cpx* dstMem,
+ TDimensionI dim) {
+ for (int j = 0; j < dim.ly; j++) {
+ PIXEL* pix = srcRas->pixels(j);
+ for (int i = 0; i < dim.lx; i++, pix++) {
+ if (pix->m != 0) {
+ double val = (m_channel == Red) ? (double)pix->r
+ : (m_channel == Green) ? (double)pix->g
+ : (double)pix->b;
+ // multiply the exposure by alpha channel value
+ dstMem[j * dim.lx + i].r =
+ valueToExposure(val / (double)PIXEL::maxChannelValue,
+ m_layerHardness) *
+ ((double)pix->m / (double)PIXEL::maxChannelValue);
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------
+
+void BokehUtils::MyThread::run() {
+ // get the source image size
+ TDimensionI dim = m_layerTileRas->getSize();
+ // int lx,ly;
+ int lx = m_layerTileRas->getSize().lx;
+ int ly = m_layerTileRas->getSize().ly;
+
+ // initialize
+ for (int i = 0; i < dim.lx * dim.ly; i++) {
+ m_kissfft_comp_in[i].r = 0.0; // real part
+ m_kissfft_comp_in[i].i = 0.0; // imaginary part
+ }
+
+ TRaster32P ras32 = (TRaster32P)m_layerTileRas;
+ TRaster64P ras64 = (TRaster64P)m_layerTileRas;
+ // Prepare data for FFT.
+ // Convert the RGB values to the exposure, then multiply it by the alpha
+ // channel value
+ {
+ lock.lockForRead();
+ if (ras32)
+ setLayerRaster(ras32, m_kissfft_comp_in, dim);
+ else if (ras64)
+ setLayerRaster(ras64, m_kissfft_comp_in, dim);
+ else {
+ lock.unlock();
+ return;
+ }
+
+ lock.unlock();
+ }
+
+ if (checkTerminationAndCleanupThread()) return;
+
+ kiss_fftnd(m_kissfft_plan_fwd, m_kissfft_comp_in, m_kissfft_comp_out);
+ kiss_fft_free(m_kissfft_plan_fwd); // we don't need this plan anymore
+ m_kissfft_plan_fwd = NULL;
+
+ if (checkTerminationAndCleanupThread()) return;
+
+ // Filtering. Multiply by the iris FFT data
+ {
+ for (int i = 0; i < lx * ly; i++) {
+ float re, im;
+ re = m_kissfft_comp_out[i].r * m_kissfft_comp_iris[i].r -
+ m_kissfft_comp_out[i].i * m_kissfft_comp_iris[i].i;
+ im = m_kissfft_comp_out[i].r * m_kissfft_comp_iris[i].i +
+ m_kissfft_comp_iris[i].r * m_kissfft_comp_out[i].i;
+ m_kissfft_comp_out[i].r = re;
+ m_kissfft_comp_out[i].i = im;
+ }
+ }
+
+ if (checkTerminationAndCleanupThread()) return;
+
+ kiss_fftnd(m_kissfft_plan_bkwd, m_kissfft_comp_out,
+ m_kissfft_comp_in); // Backward FFT
+ kiss_fft_free(m_kissfft_plan_bkwd); // we don't need this plan anymore
+ m_kissfft_plan_bkwd = NULL;
+
+ // In the backward FFT above, "m_kissfft_comp_out" is used as input and
+ // "m_kissfft_comp_in" as output.
+ // So we don't need "m_kissfft_comp_out" anymore.
+ m_kissfft_comp_out_ras->unlock();
+ m_kissfft_comp_out = 0;
+
+ if (checkTerminationAndCleanupThread()) return;
+
+ {
+ QMutexLocker locker(&mutex);
+
+ double* alp_p = m_alpha_bokeh;
+ double4* res_p = m_result;
+ for (int i = 0; i < dim.lx * dim.ly; i++, alp_p++, res_p++) {
+ if ((*alp_p) < 0.00001) continue;
+
+ double exposure =
+ (double)m_kissfft_comp_in[getCoord(i, dim.lx, dim.ly)].r /
+ (double)(dim.lx * dim.ly);
+
+ // convert to layer hardness
+ if (m_masterHardness != m_layerHardness)
+ exposure =
+ std::pow(exposure / (*alp_p), m_layerHardness / m_masterHardness) *
+ (*alp_p);
+
+ double* res = (m_channel == Red) ? (&((*res_p).x))
+ : (m_channel == Green) ? (&((*res_p).y))
+ : (&((*res_p).z));
+
+ // composite exposure
+ if ((*alp_p) >= 1.0 || (*res) == 0.0)
+ (*res) = exposure;
+ else {
+ (*res) *= 1.0 - (*alp_p);
+ (*res) += exposure;
+ }
+
+ // over compoite alpha
+ if (m_channel == Red) {
+ if ((*res_p).w < 1.0) {
+ if ((*alp_p) > 1.0)
+ (*res_p).w = 1.0;
+ else
+ (*res_p).w = (*alp_p) + (*res_p).w * (1.0 - (*alp_p));
+ }
+ }
+ //---
+ }
+ }
+
+ m_kissfft_comp_in_ras->unlock();
+ m_kissfft_comp_in = 0;
+
+ m_finished = true;
+}
+
+bool BokehUtils::MyThread::checkTerminationAndCleanupThread() {
+ if (!m_isTerminated) return false;
+
+ if (m_kissfft_comp_in) m_kissfft_comp_in_ras->unlock();
+ if (m_kissfft_comp_out) m_kissfft_comp_out_ras->unlock();
+
+ if (m_kissfft_plan_fwd) kiss_fft_free(m_kissfft_plan_fwd);
+ if (m_kissfft_plan_bkwd) kiss_fft_free(m_kissfft_plan_bkwd);
+
+ m_finished = true;
+ return true;
+}
+
+//------------------------------------
+BokehUtils::BokehRefThread::BokehRefThread(
+ int channel, kiss_fft_cpx* fftcpx_channel_before,
+ kiss_fft_cpx* fftcpx_channel, kiss_fft_cpx* fftcpx_alpha,
+ kiss_fft_cpx* fftcpx_iris, double4* result_buff,
+ kiss_fftnd_cfg kissfft_plan_fwd, kiss_fftnd_cfg kissfft_plan_bkwd,
+ TDimensionI& dim)
+ : m_channel(channel)
+ , m_fftcpx_channel_before(fftcpx_channel_before)
+ , m_fftcpx_channel(fftcpx_channel)
+ , m_fftcpx_alpha(fftcpx_alpha)
+ , m_fftcpx_iris(fftcpx_iris)
+ , m_result_buff(result_buff)
+ , m_kissfft_plan_fwd(kissfft_plan_fwd)
+ , m_kissfft_plan_bkwd(kissfft_plan_bkwd)
+ , m_dim(dim)
+ , m_finished(false)
+ , m_isTerminated(false) {}
+
+//------------------------------------
+
+void BokehUtils::BokehRefThread::run() {
+ // execute channel fft
+ kiss_fftnd(m_kissfft_plan_fwd, m_fftcpx_channel_before, m_fftcpx_channel);
+
+ // cancel check
+ if (m_isTerminated) {
+ m_finished = true;
+ return;
+ }
+
+ int size = m_dim.lx * m_dim.ly;
+
+ // multiply filter
+ for (int i = 0; i < size; i++) {
+ float re, im;
+ re = m_fftcpx_channel[i].r * m_fftcpx_iris[i].r -
+ m_fftcpx_channel[i].i * m_fftcpx_iris[i].i;
+ im = m_fftcpx_channel[i].r * m_fftcpx_iris[i].i +
+ m_fftcpx_iris[i].r * m_fftcpx_channel[i].i;
+ m_fftcpx_channel[i].r = re;
+ m_fftcpx_channel[i].i = im;
+ }
+ // execute invert fft
+ kiss_fftnd(m_kissfft_plan_bkwd, m_fftcpx_channel, m_fftcpx_channel_before);
+
+ // cancel check
+ if (m_isTerminated) {
+ m_finished = true;
+ return;
+ }
+
+ // pixels with smaller index : normal composite exposure value
+ // pixels with the same or larger index : replace exposure value
+ double4* result_p = m_result_buff;
+ for (int i = 0; i < size; i++, result_p++) {
+ // modify fft coordinate to normal
+ int coord = getCoord(i, m_dim.lx, m_dim.ly);
+
+ double alpha = (double)m_fftcpx_alpha[coord].r / (double)size;
+ // ignore transpalent pixels
+ if (alpha < 0.00001) continue;
+
+ double exposure = (double)m_fftcpx_channel_before[coord].r / (double)size;
+
+ // in case of using upper layer at all
+ if (alpha >= 1.0 || (m_channel == 0 && (*result_p).x == 0.0) ||
+ (m_channel == 1 && (*result_p).y == 0.0) ||
+ (m_channel == 2 && (*result_p).z == 0.0)) {
+ // set exposure
+ if (m_channel == 0) // R
+ (*result_p).x = exposure;
+ else if (m_channel == 1) // G
+ (*result_p).y = exposure;
+ else // B
+ (*result_p).z = exposure;
+ }
+ // in case of compositing both layers
+ else {
+ if (m_channel == 0) // R
+ {
+ (*result_p).x *= 1.0 - alpha;
+ (*result_p).x += exposure;
+ } else if (m_channel == 1) // G
+ {
+ (*result_p).y *= 1.0 - alpha;
+ (*result_p).y += exposure;
+ } else // B
+ {
+ (*result_p).z *= 1.0 - alpha;
+ (*result_p).z += exposure;
+ }
+ }
+ }
+ m_finished = true;
+}
+
+//------------------------------------------------------------
+
+//------------------------------------------------------------
+// normalize the source raster image to 0-1 and set to dstMem
+// returns true if the source is (seems to be) premultiplied
+//------------------------------------------------------------
+template void BokehUtils::setSourceRaster(
+ const TRaster32P srcRas, double4* dstMem, TDimensionI dim);
+template void BokehUtils::setSourceRaster(
+ const TRaster64P srcRas, double4* dstMem, TDimensionI dim);
+
+template
+void BokehUtils::setSourceRaster(const RASTER srcRas, double4* dstMem,
+ TDimensionI dim) {
+ double4* chann_p = dstMem;
+ for (int j = 0; j < dim.ly; j++) {
+ PIXEL* pix = srcRas->pixels(j);
+ for (int i = 0; i < dim.lx; i++, pix++, chann_p++) {
+ (*chann_p).x = (double)pix->r / (double)PIXEL::maxChannelValue;
+ (*chann_p).y = (double)pix->g / (double)PIXEL::maxChannelValue;
+ (*chann_p).z = (double)pix->b / (double)PIXEL::maxChannelValue;
+ (*chann_p).w = (double)pix->m / (double)PIXEL::maxChannelValue;
+ }
+ }
+}
+
+//------------------------------------------------------------
+// normalize brightness of the depth reference image to unsigned char
+// and store into detMem
+//------------------------------------------------------------
+template void BokehUtils::setDepthRaster(
+ const TRaster32P srcRas, unsigned char* dstMem, TDimensionI dim);
+template void BokehUtils::setDepthRaster(
+ const TRaster64P srcRas, unsigned char* dstMem, TDimensionI dim);
+
+template
+void BokehUtils::setDepthRaster(const RASTER srcRas, unsigned char* dstMem,
+ TDimensionI dim) {
+ unsigned char* depth_p = dstMem;
+ for (int j = 0; j < dim.ly; j++) {
+ PIXEL* pix = srcRas->pixels(j);
+ for (int i = 0; i < dim.lx; i++, pix++, depth_p++) {
+ // normalize brightness to 0-1
+ double val = ((double)pix->r * 0.3 + (double)pix->g * 0.59 +
+ (double)pix->b * 0.11) /
+ (double)PIXEL::maxChannelValue;
+ // convert to unsigned char
+ (*depth_p) = (unsigned char)(val * (double)UCHAR_MAX + 0.5);
+ }
+ }
+}
+
+//------------------------------------------------------------
+// create the depth index map
+//------------------------------------------------------------
+void BokehUtils::defineSegemntDepth(
+ const unsigned char* indexMap_main, const unsigned char* indexMap_sub,
+ const double* mainSub_ratio, const unsigned char* depth_buff,
+ const TDimensionI& dimOut, QVector& segmentDepth_main,
+ QVector& segmentDepth_sub, const double focusDepth,
+ int distancePrecision, double nearDepth, double farDepth) {
+ QSet segmentValues;
+
+ // histogram parameters
+ struct HISTO {
+ int pix_amount;
+ int belongingSegmentValue; // value to which temporary segmented
+ int segmentId;
+ int segmentId_sub;
+ };
+ std::array histo;
+
+ // initialize
+ for (int h = 0; h < 256; h++) {
+ histo[h].pix_amount = 0;
+ histo[h].belongingSegmentValue = -1;
+ histo[h].segmentId = -1;
+ }
+
+ int size = dimOut.lx * dimOut.ly;
+
+ // max and min
+ int minHisto = (int)UCHAR_MAX;
+ int maxHisto = 0;
+
+ unsigned char* depth_p = (unsigned char*)depth_buff;
+ for (int i = 0; i < size; i++, depth_p++) {
+ histo[(int)*depth_p].pix_amount++;
+ // update max and min
+ if ((int)*depth_p < minHisto) minHisto = (int)*depth_p;
+ if ((int)*depth_p > maxHisto) maxHisto = (int)*depth_p;
+ }
+
+ // the maximum and the minimum depth become the segment layers
+ segmentValues.insert(minHisto);
+ segmentValues.insert(maxHisto);
+
+ // The nearest depth in the histogram
+ double minDepth =
+ (farDepth - nearDepth) * (double)minHisto / 255.0 + nearDepth;
+ // The furthest depth in the histogram
+ double maxDepth =
+ (farDepth - nearDepth) * (double)maxHisto / 255.0 + nearDepth;
+ // focus depth becomes the segment layer as well
+ if (minDepth < focusDepth && focusDepth < maxDepth)
+ segmentValues.insert(std::round(
+ (double)UCHAR_MAX * (focusDepth - nearDepth) / (farDepth - nearDepth)));
+
+ // set the initial segmentation for each depth value
+ for (int h = 0; h < 256; h++) {
+ for (int seg = 0; seg < segmentValues.size(); seg++) {
+ // set the segment
+ if (histo[h].belongingSegmentValue == -1) {
+ histo[h].belongingSegmentValue = segmentValues.values().at(seg);
+ continue;
+ }
+ // error amount at the current registered layers
+ int tmpError = std::abs(h - histo[h].belongingSegmentValue);
+ if (tmpError == 0) break;
+ // new error amount
+ int newError = std::abs(h - segmentValues.values().at(seg));
+ // compare the two and update
+ if (newError < tmpError)
+ histo[h].belongingSegmentValue = segmentValues.values().at(seg);
+ }
+ }
+
+ // add the segment layers to the distance precision value
+ while (segmentValues.size() < distancePrecision) {
+ // add a new segment at the value which will reduce the error amount in
+ // maximum
+ double tmpMaxErrorMod = 0;
+ int tmpBestNewSegVal;
+ bool newSegFound = false;
+ for (int h = minHisto + 1; h < maxHisto; h++) {
+ // if it is already set as the segment, continue
+ if (histo[h].belongingSegmentValue == h) continue;
+
+ double errorModAmount = 0;
+ // estimate how much the error will be reduced if the current h becomes
+ // segment
+ for (int i = minHisto + 1; i < maxHisto; i++) {
+ // compare the current segment value and h and take the nearest value
+ // if h is near (from i), then accumulate the estimated error reduction
+ // amount
+ if (std::abs(i - histo[i].belongingSegmentValue) >
+ std::abs(i - h)) // the current segment value has
+ // proirity, if the distance is the same
+ errorModAmount +=
+ (std::abs(i - histo[i].belongingSegmentValue) - std::abs(i - h)) *
+ histo[i].pix_amount;
+ }
+
+ // if h will reduce the error, update the candidate segment value
+ if (errorModAmount > tmpMaxErrorMod) {
+ tmpMaxErrorMod = errorModAmount;
+ tmpBestNewSegVal = h;
+ newSegFound = true;
+ }
+ }
+
+ if (!newSegFound) break;
+
+ // register tmpBestNewSegVal to the segment values list
+ segmentValues.insert(tmpBestNewSegVal);
+
+ // update belongingSegmentValue
+ for (int h = minHisto + 1; h < maxHisto; h++) {
+ // compare the current segment value and h and take the nearest value
+ // if tmpBestNewSegVal is near (from h), then update the
+ // belongingSegmentValue
+ if (std::abs(h - histo[h].belongingSegmentValue) >
+ std::abs(h - tmpBestNewSegVal)) // the current segment value has
+ // proirity, if the distance is the same
+ histo[h].belongingSegmentValue = tmpBestNewSegVal;
+ }
+ }
+
+ // set indices from the farthest and create the index table for each depth
+ // value
+ QVector segValVec;
+ int tmpSegVal = -1;
+ int tmpSegId = -1;
+ for (int h = 255; h >= 0; h--) {
+ if (histo[h].belongingSegmentValue != tmpSegVal) {
+ segmentDepth_main.push_back((double)histo[h].belongingSegmentValue /
+ (double)UCHAR_MAX);
+ tmpSegVal = histo[h].belongingSegmentValue;
+ tmpSegId++;
+ segValVec.push_back(tmpSegVal);
+ }
+ histo[h].segmentId = tmpSegId;
+ }
+
+ // "sub" depth segment value list for interporation
+ for (int d = 0; d < segmentDepth_main.size() - 1; d++)
+ segmentDepth_sub.push_back(
+ (segmentDepth_main.at(d) + segmentDepth_main.at(d + 1)) / 2.0);
+
+ // create the "sub" index table for each depth value
+ tmpSegId = 0;
+ for (int seg = 0; seg < segValVec.size() - 1; seg++) {
+ int hMax = (seg == 0) ? 255 : segValVec.at(seg);
+ int hMin = (seg == segValVec.size() - 2) ? 0 : segValVec.at(seg + 1) + 1;
+ for (int h = hMax; h >= hMin; h--) histo[h].segmentId_sub = tmpSegId;
+ tmpSegId++;
+ }
+
+ // convert the depth value to the segment index by using the index table
+ depth_p = (unsigned char*)depth_buff;
+ unsigned char* main_p = (unsigned char*)indexMap_main;
+ unsigned char* sub_p = (unsigned char*)indexMap_sub;
+ // mainSub_ratio represents the composition ratio of the image with "main"
+ // separation.
+ double* ratio_p = (double*)mainSub_ratio;
+ for (int i = 0; i < size; i++, depth_p++, main_p++, sub_p++, ratio_p++) {
+ *main_p = (unsigned char)histo[(int)*depth_p].segmentId;
+ *sub_p = (unsigned char)histo[(int)*depth_p].segmentId_sub;
+
+ double depth = (double)*depth_p / (double)UCHAR_MAX;
+ double main_segDepth = segmentDepth_main.at(*main_p);
+ double sub_segDepth = segmentDepth_sub.at(*sub_p);
+
+ if (main_segDepth == sub_segDepth)
+ *ratio_p = 1.0;
+ else {
+ *ratio_p = 1.0 - (main_segDepth - depth) / (main_segDepth - sub_segDepth);
+ *ratio_p = clamp01(*ratio_p);
+ }
+ }
+}
+
+//--------------------------------------------
+// convert source image value rgb -> exposure
+//--------------------------------------------
+void BokehUtils::convertRGBToExposure(const double4* source_buff, int size,
+ double filmGamma) {
+ double4* source_p = (double4*)source_buff;
+ for (int i = 0; i < size; i++, source_p++) {
+ // continue if alpha channel is 0
+ if ((*source_p).w == 0.0) {
+ (*source_p).x = 0.0;
+ (*source_p).y = 0.0;
+ (*source_p).z = 0.0;
+ continue;
+ }
+
+ // RGB value -> exposure
+ (*source_p).x = valueToExposure((*source_p).x, filmGamma);
+ (*source_p).y = valueToExposure((*source_p).y, filmGamma);
+ (*source_p).z = valueToExposure((*source_p).z, filmGamma);
+
+ // multiply with alpha channel
+ (*source_p).x *= (*source_p).w;
+ (*source_p).y *= (*source_p).w;
+ (*source_p).z *= (*source_p).w;
+ }
+}
+
+//--------------------------------------------
+// convert result image value exposure -> rgb
+//--------------------------------------------
+void BokehUtils::convertExposureToRGB(const double4* result_buff, int size,
+ double filmGamma) {
+ double4* res_p = (double4*)result_buff;
+ for (int i = 0; i < size; i++, res_p++) {
+ (*res_p).x = clamp01(exposureToValue((*res_p).x, filmGamma));
+ (*res_p).y = clamp01(exposureToValue((*res_p).y, filmGamma));
+ (*res_p).z = clamp01(exposureToValue((*res_p).z, filmGamma));
+ }
+}
+
+//-----------------------------------------------------
+// obtain iris size from the depth value
+//-----------------------------------------------------
+double BokehUtils::calcIrisSize(
+ const double depth, // relative depth where near depth is set to 0 and far
+ // depth is 1
+ const double bokehPixelAmount, const double onFocusDistance,
+ const double bokehAdjustment, double nearDepth,
+ double farDepth) // actual depth of black and white reference pixels
+{
+ double realDepth = nearDepth + (farDepth - nearDepth) * depth;
+ return ((double)onFocusDistance - realDepth) * bokehPixelAmount *
+ bokehAdjustment;
+}
+
+namespace {
+//--------------------------------------------
+// apply single median filter
+//--------------------------------------------
+void doSingleMedian(const double4* source_buff,
+ const double4* segment_layer_buff,
+ const unsigned char* indexMap_mainSub, int index, int lx,
+ int ly, const unsigned char* generation_buff, int curGen) {
+ double4* source_p = (double4*)source_buff;
+ double4* layer_p = (double4*)segment_layer_buff;
+ unsigned char* indexMap_p = (unsigned char*)indexMap_mainSub;
+ unsigned char* gen_p = (unsigned char*)generation_buff;
+ for (int posY = 0; posY < ly; posY++) {
+ for (int posX = 0; posX < lx;
+ posX++, source_p++, layer_p++, indexMap_p++, gen_p++) {
+ // continue if the current pixel is at the same or far depth
+ if ((int)(*indexMap_p) <= index) continue;
+ // continue if the current pixel is already extended
+ if ((*gen_p) > 0) continue;
+
+ // check out the neighbor pixels. store brightness in neighbor[x].w
+ double4 neighbor[8];
+ int neighbor_amount = 0;
+ for (int ky = posY - 1; ky <= posY + 1; ky++) {
+ for (int kx = posX - 1; kx <= posX + 1; kx++) {
+ // skip the current pixel itself
+ if (kx == posX && ky == posY) continue;
+ // border condition
+ if (ky < 0 || ky >= ly || kx < 0 || kx >= lx) continue;
+ // index in the buffer
+ int neighborId = ky * lx + kx;
+
+ if ((int)indexMap_mainSub[neighborId] !=
+ index && // pixels from the original image can be used as
+ // neighbors
+ (generation_buff[neighborId] == 0 || // pixels which is not yet
+ // be extended cannot be
+ // used as neighbors
+ generation_buff[neighborId] == curGen)) // pixels which is
+ // extended in the
+ // current median
+ // generation cannot be
+ // used as neighbors
+ continue;
+ // compute brightness (actually, it is "pseudo" brightness
+ // since the source buffer is already converted to exposure)
+ double brightness = source_buff[neighborId].x * 0.3 +
+ source_buff[neighborId].y * 0.59 +
+ source_buff[neighborId].z * 0.11;
+ // insert with sorting
+ int ins_index;
+ for (ins_index = 0; ins_index < neighbor_amount; ins_index++) {
+ if (neighbor[ins_index].w < brightness) break;
+ }
+ // displace neighbor values from neighbor_amount-1 to ins_index
+ for (int k = neighbor_amount - 1; k >= ins_index; k--) {
+ neighbor[k + 1].x = neighbor[k].x;
+ neighbor[k + 1].y = neighbor[k].y;
+ neighbor[k + 1].z = neighbor[k].z;
+ neighbor[k + 1].w = neighbor[k].w;
+ }
+ // set the neighbor value
+ neighbor[ins_index].x = source_buff[neighborId].x;
+ neighbor[ins_index].y = source_buff[neighborId].y;
+ neighbor[ins_index].z = source_buff[neighborId].z;
+ neighbor[ins_index].w = brightness;
+
+ // increment the count
+ neighbor_amount++;
+ }
+ }
+
+ // If there is no neighbor pixles available, continue
+ if (neighbor_amount == 0) continue;
+
+ // switch the behavior when there are even number of neighbors
+ bool flag = ((posX + posY) % 2 == 0);
+ // pick up the medium index
+ int pickIndex = (flag)
+ ? (int)std::floor((double)(neighbor_amount - 1) / 2.0)
+ : (int)std::ceil((double)(neighbor_amount - 1) / 2.0);
+
+ // set the medium pixel values
+ (*layer_p).x = neighbor[pickIndex].x;
+ (*layer_p).y = neighbor[pickIndex].y;
+ (*layer_p).z = neighbor[pickIndex].z;
+ (*layer_p).w = (*source_p).w;
+
+ // set the generation
+ (*gen_p) = (unsigned char)curGen;
+ }
+ }
+}
+
+void doSingleExtend(const double4* source_buff,
+ const double4* segment_layer_buff,
+ const unsigned char* indexMap_mainSub, int index, int lx,
+ int ly, const unsigned char* generation_buff, int curGen) {
+ double4* source_p = (double4*)source_buff;
+ double4* layer_p = (double4*)segment_layer_buff;
+ unsigned char* indexMap_p = (unsigned char*)indexMap_mainSub;
+ unsigned char* gen_p = (unsigned char*)generation_buff;
+ for (int posY = 0; posY < ly; posY++) {
+ for (int posX = 0; posX < lx;
+ posX++, source_p++, layer_p++, indexMap_p++, gen_p++) {
+ // continue if the current pixel is at the same or far depth
+ if ((int)(*indexMap_p) <= index) continue;
+ // continue if the current pixel is already extended
+ if ((*gen_p) > 0) continue;
+
+ // check out the neighbor pixels. store brightness in neighbor[x].w
+ double4 neighbor[8];
+ bool neighbor_found = false;
+ for (int ky = posY - 1; ky <= posY + 1; ky++) {
+ for (int kx = posX - 1; kx <= posX + 1; kx++) {
+ // skip the current pixel itself
+ if (kx == posX && ky == posY) continue;
+ // border condition
+ if (ky < 0 || ky >= ly || kx < 0 || kx >= lx) continue;
+ // index in the buffer
+ int neighborId = ky * lx + kx;
+
+ if ((int)indexMap_mainSub[neighborId] !=
+ index && // pixels from the original image can be used as
+ // neighbors
+ (generation_buff[neighborId] == 0 || // pixels which is not yet
+ // be extended cannot be
+ // used as neighbors
+ generation_buff[neighborId] == curGen)) // pixels which is
+ // extended in the
+ // current median
+ // generation cannot be
+ // used as neighbors
+ continue;
+ // increment the count
+ neighbor_found = true;
+ break;
+ }
+ if (neighbor_found) break;
+ }
+
+ // If there is no neighbor pixles available, continue
+ if (!neighbor_found) continue;
+
+ // set the medium pixel values
+ (*layer_p).x = (*source_p).x;
+ (*layer_p).y = (*source_p).y;
+ (*layer_p).z = (*source_p).z;
+ (*layer_p).w = (*source_p).w;
+
+ // set the generation
+ (*gen_p) = (unsigned char)curGen;
+ }
+ }
+}
+} // namespace
+
+//--------------------------------------------
+// generate the segment layer source at the current depth
+// considering fillGap and doMedian options
+//--------------------------------------------
+void BokehUtils::retrieveLayer(const double4* source_buff,
+ const double4* segment_layer_buff,
+ const unsigned char* indexMap_mainSub, int index,
+ int lx, int ly, bool fillGap, bool doMedian,
+ int margin) {
+ // only when fillGap is ON and doMedian is OFF,
+ // fill the region where will be behind of the near layers
+ // bool fill = (fillGap && !doMedian);
+ // retrieve the regions with the current depth
+ double4* source_p = (double4*)source_buff;
+ double4* layer_p = (double4*)segment_layer_buff;
+ unsigned char* indexMap_p = (unsigned char*)indexMap_mainSub;
+ for (int i = 0; i < lx * ly; i++, source_p++, layer_p++, indexMap_p++) {
+ // continue if the current pixel is at the far layer
+ // consider the fill flag if the current pixel is at the near layer
+ // if ((int)(*indexMap_p) < index || (!fill && (int)(*indexMap_p) > index))
+ if ((int)(*indexMap_p) != index) continue;
+
+ // copy pixel values
+ (*layer_p).x = (*source_p).x;
+ (*layer_p).y = (*source_p).y;
+ (*layer_p).z = (*source_p).z;
+ (*layer_p).w = (*source_p).w;
+ }
+
+ if (!fillGap && !doMedian) return;
+ if (margin == 0) return;
+
+ // extend pixels by using median filter
+ unsigned char* generation_buff;
+ TRasterGR8P generation_buff_ras = allocateRasterAndLock(
+ &generation_buff, TDimensionI(lx, ly));
+
+ // extend (margin * 2) pixels in order to enough cover when two adjacent
+ // layers are both blurred in the maximum radius
+ for (int gen = 0; gen < margin * 2; gen++) {
+ if (doMedian)
+ // apply single median filter
+ doSingleMedian(source_buff, segment_layer_buff, indexMap_mainSub, index,
+ lx, ly, generation_buff, gen + 1);
+ else
+ // put the source pixel as-is
+ doSingleExtend(source_buff, segment_layer_buff, indexMap_mainSub, index,
+ lx, ly, generation_buff, gen + 1);
+ }
+
+ generation_buff_ras->unlock();
+}
+
+//--------------------------------------------
+// normal-composite the layer as is, without filtering
+//--------------------------------------------
+void BokehUtils::compositeAsIs(const double4* segment_layer_buff,
+ const double4* result_buff_mainSub, int size) {
+ double4* layer_p = (double4*)segment_layer_buff;
+ double4* result_p = (double4*)result_buff_mainSub;
+ for (int i = 0; i < size; i++, layer_p++, result_p++) {
+ // in case the pixel is full opac
+ if ((*layer_p).w == 1.0f) {
+ (*result_p).x = (*layer_p).x;
+ (*result_p).y = (*layer_p).y;
+ (*result_p).z = (*layer_p).z;
+ (*result_p).w = 1.0f;
+ continue;
+ }
+ // in case the pixel is full transparent
+ else if ((*layer_p).w == 0.0f)
+ continue;
+ // in case the pixel is semi-transparent, do normal composite
+ else {
+ (*result_p).x = (*layer_p).x + (*result_p).x * (1.0 - (*layer_p).w);
+ (*result_p).y = (*layer_p).y + (*result_p).y * (1.0 - (*layer_p).w);
+ (*result_p).z = (*layer_p).z + (*result_p).z * (1.0 - (*layer_p).w);
+ (*result_p).w = (*layer_p).w + (*result_p).w * (1.0 - (*layer_p).w);
+ }
+ }
+}
+
+//------------------------------------------------
+// Resize / flip the iris image according to the size ratio.
+// Normalize the brightness of the iris image.
+// Enlarge the iris to the output size.
+void BokehUtils::convertIris(const double irisSize,
+ kiss_fft_cpx* kissfft_comp_iris_before,
+ const TDimensionI& dimOut, const TRectD& irisBBox,
+ const TTile& irisTile) {
+ // the original size of iris image
+ double2 irisOrgSize = {irisBBox.getLx(), irisBBox.getLy()};
+
+ // Get the size ratio of iris based on width. The ratio can be negative value.
+ double irisSizeResampleRatio = irisSize / irisOrgSize.x;
+
+ // Create the raster for resized iris
+ double2 resizedIrisSize = {std::abs(irisSizeResampleRatio) * irisOrgSize.x,
+ std::abs(irisSizeResampleRatio) * irisOrgSize.y};
+ // add 1 pixel margins to all sides
+ int2 filterSize = {int(std::ceil(resizedIrisSize.x)) + 2,
+ int(std::ceil(resizedIrisSize.y)) + 2};
+
+ TPointD resizeOffset((double)filterSize.x - resizedIrisSize.x,
+ (double)filterSize.y - resizedIrisSize.y);
+ // Add some adjustment in order to absorb the difference of the cases when the
+ // iris size is odd and even numbers.
+ // Try to set the center of the iris to the center of the screen
+ if ((dimOut.lx - filterSize.x) % 2 == 1) filterSize.x++;
+ if ((dimOut.ly - filterSize.y) % 2 == 1) filterSize.y++;
+
+ // Terminate if the filter size becomes bigger than the output size.
+ if (filterSize.x > dimOut.lx || filterSize.y > dimOut.ly) {
+ std::cout
+ << "Error: The iris filter size becomes larger than the source size!"
+ << std::endl;
+ return;
+ }
+
+ TRaster64P resizedIris(TDimension(filterSize.x, filterSize.y));
+
+ // Add some adjustment in order to absorb the 0.5 translation to be done in
+ // resample()
+ TAffine aff;
+ TPointD affOffset(0.5, 0.5);
+ affOffset += TPointD((dimOut.lx % 2 == 1) ? 0.5 : 0.0,
+ (dimOut.ly % 2 == 1) ? 0.5 : 0.0);
+
+ aff = TTranslation(resizedIris->getCenterD() + affOffset);
+ aff *= TScale(irisSizeResampleRatio);
+ aff *= TTranslation(-(irisTile.getRaster()->getCenterD() + affOffset));
+
+ // resample the iris
+ TRop::resample(resizedIris, irisTile.getRaster(), aff);
+
+ // accumulated value
+ float irisValAmount = 0.0f;
+
+ int iris_j = 0;
+ // Initialize
+ for (int i = 0; i < dimOut.lx * dimOut.ly; i++) {
+ kissfft_comp_iris_before[i].r = 0.0f;
+ kissfft_comp_iris_before[i].i = 0.0f;
+ }
+ for (int j = (dimOut.ly - filterSize.y) / 2; iris_j < filterSize.y;
+ j++, iris_j++) {
+ TPixel64* pix = resizedIris->pixels(iris_j);
+ int iris_i = 0;
+ for (int i = (dimOut.lx - filterSize.x) / 2; iris_i < filterSize.x;
+ i++, iris_i++) {
+ // Value = 0.3R 0.59G 0.11B
+ kissfft_comp_iris_before[j * dimOut.lx + i].r =
+ ((float)pix->r * 0.3f + (float)pix->g * 0.59f +
+ (float)pix->b * 0.11f) /
+ (float)USHRT_MAX;
+ irisValAmount += kissfft_comp_iris_before[j * dimOut.lx + i].r;
+ pix++;
+ }
+ }
+
+ // Normalize value
+ for (int i = 0; i < dimOut.lx * dimOut.ly; i++) {
+ kissfft_comp_iris_before[i].r /= irisValAmount;
+ }
+}
+
+//--------------------------------------------
+// retrieve segment layer image for each channel
+//--------------------------------------------
+void BokehUtils::retrieveChannel(const double4* segment_layer_buff, // src
+ kiss_fft_cpx* fftcpx_r_before, // dst
+ kiss_fft_cpx* fftcpx_g_before, // dst
+ kiss_fft_cpx* fftcpx_b_before, // dst
+ kiss_fft_cpx* fftcpx_a_before, // dst
+ int size) {
+ double4* layer_p = (double4*)segment_layer_buff;
+ for (int i = 0; i < size; i++, layer_p++) {
+ fftcpx_r_before[i].r = (*layer_p).x;
+ fftcpx_g_before[i].r = (*layer_p).y;
+ fftcpx_b_before[i].r = (*layer_p).z;
+ fftcpx_a_before[i].r = (*layer_p).w;
+ }
+}
+
+//--------------------------------------------
+// multiply filter on channel
+//--------------------------------------------
+void BokehUtils::multiplyFilter(kiss_fft_cpx* fftcpx_channel, // dst
+ kiss_fft_cpx* fftcpx_iris, // filter
+ int size) {
+ for (int i = 0; i < size; i++) {
+ float re, im;
+ re = fftcpx_channel[i].r * fftcpx_iris[i].r -
+ fftcpx_channel[i].i * fftcpx_iris[i].i;
+ im = fftcpx_channel[i].r * fftcpx_iris[i].i +
+ fftcpx_channel[i].i * fftcpx_iris[i].r;
+
+ fftcpx_channel[i].r = re;
+ fftcpx_channel[i].i = im;
+ }
+}
+
+//--------------------------------------------
+// normal comosite the alpha channel
+//--------------------------------------------
+void BokehUtils::compositeAlpha(const double4* result_buff, // dst
+ const kiss_fft_cpx* fftcpx_alpha, // alpha
+ int lx, int ly) {
+ int size = lx * ly;
+ double4* result_p = (double4*)result_buff;
+ for (int i = 0; i < size; i++, result_p++) {
+ // modify fft coordinate to normal
+ double alpha = (double)fftcpx_alpha[getCoord(i, lx, ly)].r / (double)size;
+ alpha = clamp01(alpha);
+ (*result_p).w = alpha + ((*result_p).w * (1.0 - alpha));
+ }
+}
+
+//--------------------------------------------
+// interpolate main and sub exposures
+// set to result
+//--------------------------------------------
+void BokehUtils::interpolateExposureAndConvertToRGB(
+ const double4* result_main_buff, // result1
+ const double4* result_sub_buff, // result2
+ const double* mainSub_ratio, // ratio
+ const double4* result_buff, // dst
+ int size, double layerHardnessRatio) {
+ double4* resultMain_p = (double4*)result_main_buff;
+ double4* resultSub_p = (double4*)result_sub_buff;
+ double* ratio_p = (double*)mainSub_ratio;
+ double4* out_p = (double4*)result_buff;
+ for (int i = 0; i < size;
+ i++, resultMain_p++, resultSub_p++, ratio_p++, out_p++) {
+ // interpolate main and sub exposures
+ double4 result;
+
+ result.x =
+ (*resultMain_p).x * (*ratio_p) + (*resultSub_p).x * (1.0 - (*ratio_p));
+ result.y =
+ (*resultMain_p).y * (*ratio_p) + (*resultSub_p).y * (1.0 - (*ratio_p));
+ result.z =
+ (*resultMain_p).z * (*ratio_p) + (*resultSub_p).z * (1.0 - (*ratio_p));
+ result.w =
+ (*resultMain_p).w * (*ratio_p) + (*resultSub_p).w * (1.0 - (*ratio_p));
+
+ // continue for transparent pixel
+ if (result.w == 0.0) {
+ continue;
+ }
+
+ // convert exposure by layer hardness
+ if (layerHardnessRatio != 1.0) {
+ result.x = std::pow(result.x, layerHardnessRatio);
+ result.y = std::pow(result.y, layerHardnessRatio);
+ result.z = std::pow(result.z, layerHardnessRatio);
+ }
+
+ // in case the result is replaced by the upper layer pixel
+ if (result.w >= 1.0f) {
+ (*out_p).x = result.x;
+ (*out_p).y = result.y;
+ (*out_p).z = result.z;
+ }
+ // in case the layers will be composed
+ else {
+ (*out_p).x = (*out_p).x * (1.0 - result.w) + result.x;
+ (*out_p).y = (*out_p).y * (1.0 - result.w) + result.y;
+ (*out_p).z = (*out_p).z * (1.0 - result.w) + result.z;
+ }
+
+ (*out_p).w = (*out_p).w * (1.0 - result.w) + result.w;
+ }
+}
+
+//--------------------------------------------
+//"Over" composite the layer to the output exposure.
+void BokehUtils::compositLayerAsIs(TTile& layerTile, double4* result,
+ TDimensionI& dimOut, double filmGamma) {
+ double4* layer_buff;
+ TRasterGR8P layer_buff_ras(dimOut.lx * sizeof(double4), dimOut.ly);
+ layer_buff_ras->lock();
+ layer_buff = (double4*)layer_buff_ras->getRawData();
+
+ TRaster32P ras32 = (TRaster32P)layerTile.getRaster();
+ TRaster64P ras64 = (TRaster64P)layerTile.getRaster();
+ lock.lockForRead();
+ if (ras32)
+ BokehUtils::setSourceRaster(ras32, layer_buff,
+ dimOut);
+ else if (ras64)
+ BokehUtils::setSourceRaster(ras64, layer_buff,
+ dimOut);
+ lock.unlock();
+
+ double4* lay_p = layer_buff;
+ double4* res_p = result;
+ for (int i = 0; i < dimOut.lx * dimOut.ly; i++, lay_p++, res_p++) {
+ if ((*lay_p).w <= 0.0)
+ continue;
+ else if ((*lay_p).w < 1.0) {
+ // composite exposure
+ (*res_p).x = valueToExposure((*lay_p).x, filmGamma) * (*lay_p).w +
+ (*res_p).x * (1.0 - (*lay_p).w);
+ (*res_p).y = valueToExposure((*lay_p).y, filmGamma) * (*lay_p).w +
+ (*res_p).y * (1.0 - (*lay_p).w);
+ (*res_p).z = valueToExposure((*lay_p).z, filmGamma) * (*lay_p).w +
+ (*res_p).z * (1.0 - (*lay_p).w);
+ // over composite alpha
+ (*res_p).w = (*lay_p).w + ((*res_p).w * (1.0 - (*lay_p).w));
+ (*res_p).w = clamp01((*res_p).w);
+ } else // replace by upper layer
+ {
+ (*res_p).x = valueToExposure((*lay_p).x, filmGamma);
+ (*res_p).y = valueToExposure((*lay_p).y, filmGamma);
+ (*res_p).z = valueToExposure((*lay_p).z, filmGamma);
+ (*res_p).w = 1.0;
+ }
+ }
+
+ layer_buff_ras->unlock();
+}
+
+//--------------------------------------------
+// Do FFT the alpha channel.
+// Forward FFT -> Multiply by the iris data -> Backward FFT
+void BokehUtils::calcAlfaChannelBokeh(kiss_fft_cpx* kissfft_comp_iris,
+ TTile& layerTile, double* alpha_bokeh) {
+ // Obtain the source size
+ int lx, ly;
+ lx = layerTile.getRaster()->getSize().lx;
+ ly = layerTile.getRaster()->getSize().ly;
+
+ // Allocate the FFT data
+ kiss_fft_cpx *kissfft_comp_in, *kissfft_comp_out;
+
+ TRasterGR8P kissfft_comp_in_ras(lx * sizeof(kiss_fft_cpx), ly);
+ kissfft_comp_in_ras->lock();
+ kissfft_comp_in = (kiss_fft_cpx*)kissfft_comp_in_ras->getRawData();
+ TRasterGR8P kissfft_comp_out_ras(lx * sizeof(kiss_fft_cpx), ly);
+ kissfft_comp_out_ras->lock();
+ kissfft_comp_out = (kiss_fft_cpx*)kissfft_comp_out_ras->getRawData();
+
+ // Initialize the FFT data
+ for (int i = 0; i < lx * ly; i++) {
+ kissfft_comp_in[i].r = 0.0; // real part
+ kissfft_comp_in[i].i = 0.0; // imaginary part
+ }
+
+ TRaster32P ras32 = (TRaster32P)layerTile.getRaster();
+ TRaster64P ras64 = (TRaster64P)layerTile.getRaster();
+ if (ras32) {
+ for (int j = 0; j < ly; j++) {
+ TPixel32* pix = ras32->pixels(j);
+ for (int i = 0; i < lx; i++) {
+ kissfft_comp_in[j * lx + i].r = (double)pix->m / (double)UCHAR_MAX;
+ pix++;
+ }
+ }
+ } else if (ras64) {
+ for (int j = 0; j < ly; j++) {
+ TPixel64* pix = ras64->pixels(j);
+ for (int i = 0; i < lx; i++) {
+ kissfft_comp_in[j * lx + i].r = (double)pix->m / (double)USHRT_MAX;
+ pix++;
+ }
+ }
+ } else
+ return;
+
+ int dims[2] = {ly, lx};
+ int ndims = 2;
+ kiss_fftnd_cfg plan_fwd = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
+ kiss_fftnd(plan_fwd, kissfft_comp_in, kissfft_comp_out);
+ kiss_fft_free(plan_fwd); // we don't need this plan anymore
+
+ // Filtering. Multiply by the iris FFT data
+ for (int i = 0; i < lx * ly; i++) {
+ float re, im;
+ re = kissfft_comp_out[i].r * kissfft_comp_iris[i].r -
+ kissfft_comp_out[i].i * kissfft_comp_iris[i].i;
+ im = kissfft_comp_out[i].r * kissfft_comp_iris[i].i +
+ kissfft_comp_iris[i].r * kissfft_comp_out[i].i;
+ kissfft_comp_out[i].r = re;
+ kissfft_comp_out[i].i = im;
+ }
+
+ kiss_fftnd_cfg plan_bkwd = kiss_fftnd_alloc(dims, ndims, true, 0, 0);
+ kiss_fftnd(plan_bkwd, kissfft_comp_out, kissfft_comp_in); // Backward FFT
+ kiss_fft_free(plan_bkwd); // we don't need this plan anymore
+
+ // In the backward FFT above, "kissfft_comp_out" is used as input and
+ // "kissfft_comp_in" as output.
+ // So we don't need "kissfft_comp_out" anymore.
+ kissfft_comp_out_ras->unlock();
+
+ // store to the buffer
+ double* alp_p = alpha_bokeh;
+ for (int j = 0; j < ly; j++) {
+ for (int i = 0; i < lx; i++, alp_p++) {
+ (*alp_p) = (double)kissfft_comp_in[getCoord(j * lx + i, lx, ly)].r /
+ (double)(lx * ly);
+ (*alp_p) = clamp01(*alp_p);
+ }
+ }
+
+ kissfft_comp_in_ras->unlock();
+}
+
+//-----------------------------------------------------
+// convert to channel value and set to output
+
+template void BokehUtils::setOutputRaster(
+ double4* src, const TRaster32P dstRas, TDimensionI& dim, int2 margin);
+template void BokehUtils::setOutputRaster(
+ double4* src, const TRaster64P dstRas, TDimensionI& dim, int2 margin);
+
+template
+void BokehUtils::setOutputRaster(double4* src, const RASTER dstRas,
+ TDimensionI& dim, int2 margin) {
+ double4* src_p = src + (margin.y * dim.lx);
+
+ for (int j = 0; j < dstRas->getLy(); j++) {
+ PIXEL* outPix = dstRas->pixels(j);
+ src_p += margin.x;
+ for (int i = 0; i < dstRas->getLx(); i++, outPix++, src_p++) {
+ double val;
+ val = (*src_p).x * (double)PIXEL::maxChannelValue + 0.5;
+ outPix->r =
+ (typename PIXEL::Channel)((val > (double)PIXEL::maxChannelValue)
+ ? (double)PIXEL::maxChannelValue
+ : val);
+ val = (*src_p).y * (double)PIXEL::maxChannelValue + 0.5;
+ outPix->g =
+ (typename PIXEL::Channel)((val > (double)PIXEL::maxChannelValue)
+ ? (double)PIXEL::maxChannelValue
+ : val);
+ val = (*src_p).z * (double)PIXEL::maxChannelValue + 0.5;
+ outPix->b =
+ (typename PIXEL::Channel)((val > (double)PIXEL::maxChannelValue)
+ ? (double)PIXEL::maxChannelValue
+ : val);
+ val = (*src_p).w * (double)PIXEL::maxChannelValue + 0.5;
+ outPix->m =
+ (typename PIXEL::Channel)((val > (double)PIXEL::maxChannelValue)
+ ? (double)PIXEL::maxChannelValue
+ : val);
+ }
+ src_p += margin.x;
+ }
+}
+
+//-----------------------------------------------------
+// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
+double BokehUtils::getBokehPixelAmount(const double bokehAmount,
+ const TAffine affine) {
+ /*--- Convert to vector --- */
+ TPointD vect(bokehAmount, 0.0);
+ /*--- Apply geometrical transformation ---*/
+ // For the following lines I referred to lines 586-592 of
+ // sources/stdfx/motionblurfx.cpp
+ TAffine aff(affine);
+ aff.a13 = aff.a23 = 0; /* ignore translation */
+ vect = aff * vect;
+ /*--- return the length of the vector ---*/
+ return std::sqrt(vect.x * vect.x + vect.y * vect.y);
+}
+
+//-----------------------------------------------------
+
+Iwa_BokehCommonFx::Iwa_BokehCommonFx()
+ : m_onFocusDistance(0.5), m_bokehAmount(30.0), m_hardness(0.3) {
+ addInputPort("Iris", m_iris);
+
+ // Set the ranges of common parameters
+ m_onFocusDistance->setValueRange(0.0, 10.);
+ m_bokehAmount->setValueRange(0.0, 300.0);
+ m_bokehAmount->setMeasureName("fxLength");
+ m_hardness->setValueRange(0.05, 3.0);
+}
+
+//--------------------------------------------
+bool Iwa_BokehCommonFx::doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) {
+ bBox = TConsts::infiniteRectD;
+ return true;
+}
+
+//--------------------------------------------
+bool Iwa_BokehCommonFx::canHandle(const TRenderSettings& info, double frame) {
+ return false;
+}
+
+//--------------------------------------------
+void Iwa_BokehCommonFx::doFx(TTile& tile, double frame,
+ const TRenderSettings& settings,
+ double bokehPixelAmount, int margin,
+ TDimensionI& dimOut, TRectD& irisBBox,
+ TTile& irisTile, QList& layerValues,
+ QMap& ctrls) {
+ // This fx is relatively heavy so the multi thread computation is introduced.
+ // Lock the mutex here in order to prevent multiple rendering tasks run at the
+ // same time.
+ // QMutexLocker fx_locker(&fx_mutex);
+
+ QList rasterList;
+ QList planList;
+
+ kiss_fft_cpx* kissfft_comp_iris;
+ double* alpha_bokeh = nullptr;
+ double4* result = nullptr;
+ rasterList.append(
+ allocateRasterAndLock(&kissfft_comp_iris, dimOut));
+ rasterList.append(allocateRasterAndLock(&alpha_bokeh, dimOut));
+ rasterList.append(allocateRasterAndLock(&result, dimOut));
+
+ double4 zero = {0.0, 0.0, 0.0, 0.0};
+ // initialize
+ std::fill_n(result, dimOut.lx * dimOut.ly, zero);
+
+ double masterHardness = m_hardness->getValue(frame);
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ // compute layers from further to nearer
+ for (int i = 0; i < layerValues.size(); i++) {
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ LayerValue layer = layerValues.at(i);
+
+ //-------------------
+
+ // separate layer by the reference image
+ int ctrlIndex = layer.depth_ref;
+ if (ctrlIndex > 0 && ctrls.contains(ctrlIndex) && ctrls[ctrlIndex] != 0) {
+ TTile* layerTile = layer.sourceTile;
+ if (!layer.premultiply) TRop::depremultiply(layerTile->getRaster());
+
+ doBokehRef(result, frame, settings, bokehPixelAmount, margin, dimOut,
+ irisBBox, irisTile, kissfft_comp_iris, layer,
+ ctrls[ctrlIndex]);
+
+ continue;
+ }
+
+ //-------------------
+
+ double layerHardness = layer.layerHardness;
+ // The iris size of the current layer
+ double irisSize = layer.irisSize;
+
+ // in case the layer is at the focus
+ if (-1.0 <= irisSize && 1.0 >= irisSize) {
+ TTile* layerTile = layer.sourceTile;
+ if (!layer.premultiply) TRop::depremultiply(layerTile->getRaster());
+ BokehUtils::compositLayerAsIs(*layerTile, result, dimOut, masterHardness);
+ // Continue to the next layer
+ continue;
+ }
+
+ {
+ // prepare for iris FFT
+ kiss_fft_cpx* kissfft_comp_iris_before;
+ rasterList.append(allocateRasterAndLock(
+ &kissfft_comp_iris_before, dimOut));
+ // Resize / flip the iris image according to the size ratio.
+ // Normalize the brightness of the iris image.
+ // Enlarge the iris to the output size.
+ BokehUtils::convertIris(irisSize, kissfft_comp_iris_before, dimOut,
+ irisBBox, irisTile);
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ // Create the FFT plan for the iris image.
+ kiss_fftnd_cfg iris_kissfft_plan;
+ while (1) {
+ int dims[2] = {dimOut.ly, dimOut.lx};
+ int ndims = 2;
+ iris_kissfft_plan = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
+ if (iris_kissfft_plan != NULL) break;
+ }
+ // Do FFT the iris image.
+ kiss_fftnd(iris_kissfft_plan, kissfft_comp_iris_before,
+ kissfft_comp_iris);
+ kiss_fft_free(iris_kissfft_plan);
+ // release the iris buffer
+ rasterList.takeLast()->unlock();
+ }
+
+ // Up to here, FFT-ed iris data is stored in kissfft_comp_iris
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ //- - - - - - - - - - - - -
+ // Prepare the layer rasters
+ TTile* layerTile = layer.sourceTile;
+
+ if (!layer.premultiply) TRop::depremultiply(layerTile->getRaster());
+
+ //- - - - - - - - - - - - -
+ // Do FFT the alpha channel.
+ // Forward FFT -> Multiply by the iris data -> Backward FFT
+ BokehUtils::calcAlfaChannelBokeh(kissfft_comp_iris, *layerTile,
+ alpha_bokeh);
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ BokehUtils::MyThread threadR(
+ BokehUtils::MyThread::Red, layerTile->getRaster(), result, alpha_bokeh,
+ kissfft_comp_iris, layerHardness, masterHardness);
+ BokehUtils::MyThread threadG(
+ BokehUtils::MyThread::Green, layerTile->getRaster(), result,
+ alpha_bokeh, kissfft_comp_iris, layerHardness, masterHardness);
+ BokehUtils::MyThread threadB(
+ BokehUtils::MyThread::Blue, layerTile->getRaster(), result, alpha_bokeh,
+ kissfft_comp_iris, layerHardness, masterHardness);
+
+ // If you set this flag to true, the fx will be forced to compute in single
+ // thread.
+ // Under some specific condition (such as calling from single-threaded
+ // tcomposer)
+ // we may need to use this flag... For now, I'll keep this option unused.
+ // TODO: investigate this.
+ bool renderInSingleThread = false;
+
+ // Start the thread when the initialization is done.
+ // Red channel
+ int waitCount = 0;
+ while (1) {
+ // cancel check
+ if ((settings.m_isCanceled && *settings.m_isCanceled) ||
+ waitCount >= 20) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ if (threadR.init()) {
+ if (renderInSingleThread)
+ threadR.run();
+ else
+ threadR.start();
+ break;
+ }
+ QThread::msleep(500);
+ waitCount++;
+ }
+
+ waitCount = 0;
+ while (1) {
+ if ((settings.m_isCanceled && *settings.m_isCanceled) ||
+ waitCount >= 20) // 10 seconds
+ {
+ if (!threadR.isFinished()) threadR.terminateThread();
+ while (!threadR.isFinished()) {
+ }
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ if (threadG.init()) {
+ if (renderInSingleThread)
+ threadG.run();
+ else
+ threadG.start();
+ break;
+ }
+ QThread::msleep(500);
+ waitCount++;
+ }
+
+ waitCount = 0;
+ while (1) {
+ if ((settings.m_isCanceled && *settings.m_isCanceled) ||
+ waitCount >= 20) // 10 seconds
+ {
+ if (!threadR.isFinished()) threadR.terminateThread();
+ if (!threadG.isFinished()) threadG.terminateThread();
+ while (!threadR.isFinished() || !threadG.isFinished()) {
+ }
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ if (threadB.init()) {
+ if (renderInSingleThread)
+ threadB.run();
+ else
+ threadB.start();
+ break;
+ }
+ QThread::msleep(500);
+ waitCount++;
+ }
+
+ /*
+ * What is done in the thread for each RGB channel:
+ * - Convert channel value -> Exposure
+ * - Multiply by alpha channel
+ * - Forward FFT
+ * - Multiply by the iris FFT data
+ * - Backward FFT
+ * - Convert Exposure -> channel value
+ */
+
+ waitCount = 0;
+ while (1) {
+ if ((settings.m_isCanceled && *settings.m_isCanceled) ||
+ waitCount >= 2000) // 100 second timeout
+ {
+ if (!threadR.isFinished()) threadR.terminateThread();
+ if (!threadG.isFinished()) threadG.terminateThread();
+ if (!threadB.isFinished()) threadB.terminateThread();
+ while (!threadR.isFinished() || !threadG.isFinished() ||
+ !threadB.isFinished()) {
+ }
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ if (threadR.isFinished() && threadG.isFinished() && threadB.isFinished())
+ break;
+ QThread::msleep(50);
+ waitCount++;
+ }
+ }
+
+ // convert result image value exposure -> rgb
+ BokehUtils::convertExposureToRGB(result, dimOut.lx * dimOut.ly,
+ masterHardness);
+
+ // clear result raster
+ tile.getRaster()->clear();
+ TRaster32P outRas32 = (TRaster32P)tile.getRaster();
+ TRaster64P outRas64 = (TRaster64P)tile.getRaster();
+
+ int2 outMargin = {(dimOut.lx - tile.getRaster()->getSize().lx) / 2,
+ (dimOut.ly - tile.getRaster()->getSize().ly) / 2};
+
+ lock.lockForWrite();
+ if (outRas32)
+ BokehUtils::setOutputRaster(result, outRas32, dimOut,
+ outMargin);
+ else if (outRas64)
+ BokehUtils::setOutputRaster(result, outRas64, dimOut,
+ outMargin);
+ lock.unlock();
+
+ releaseAllRastersAndPlans(rasterList, planList);
+}
+
+void Iwa_BokehCommonFx::doBokehRef(double4* result, double frame,
+ const TRenderSettings& settings,
+ double bokehPixelAmount, int margin,
+ TDimensionI& dimOut, TRectD& irisBBox,
+ TTile& irisTile,
+ kiss_fft_cpx* kissfft_comp_iris,
+ LayerValue layer, unsigned char* ctrl) {
+ QList rasterList;
+ QList planList;
+ // source image
+ double4* source_buff;
+ rasterList.append(allocateRasterAndLock(&source_buff, dimOut));
+
+ TRaster32P ras32 = (TRaster32P)layer.sourceTile->getRaster();
+ TRaster64P ras64 = (TRaster64P)layer.sourceTile->getRaster();
+ lock.lockForRead();
+ if (ras32)
+ BokehUtils::setSourceRaster(ras32, source_buff,
+ dimOut);
+ else if (ras64)
+ BokehUtils::setSourceRaster(ras64, source_buff,
+ dimOut);
+ lock.unlock();
+
+ // create the index map, which indicates which layer each pixel belongs to
+ // make two separations and interporate the results in order to avoid
+ // artifacts appear at the layer border
+ unsigned char* indexMap_main_buff;
+ unsigned char* indexMap_sub_buff;
+ double* mainSub_ratio_buff;
+
+ rasterList.append(
+ allocateRasterAndLock(&indexMap_main_buff, dimOut));
+ rasterList.append(
+ allocateRasterAndLock(&indexMap_sub_buff, dimOut));
+ rasterList.append(allocateRasterAndLock(&mainSub_ratio_buff, dimOut));
+
+ QVector segmentDepth_main;
+ QVector segmentDepth_sub;
+
+ double layerDitance = layer.distance;
+ double nearDepth = layerDitance - layer.depthRange * layerDitance;
+ double farDepth = nearDepth + layer.depthRange;
+
+ int distancePrecision = layer.distancePrecision;
+
+ // create the depth index map
+ BokehUtils::defineSegemntDepth(
+ indexMap_main_buff, indexMap_sub_buff, mainSub_ratio_buff, ctrl, dimOut,
+ segmentDepth_main, segmentDepth_sub, m_onFocusDistance->getValue(frame),
+ distancePrecision, nearDepth, farDepth);
+
+ int size = dimOut.lx * dimOut.ly;
+
+ double4* layer_buff;
+ rasterList.append(allocateRasterAndLock(&layer_buff, dimOut));
+
+ kiss_fft_cpx* kissfft_comp_iris_before;
+ rasterList.append(
+ allocateRasterAndLock(&kissfft_comp_iris_before, dimOut));
+
+ // alpha channel
+ kiss_fft_cpx* fftcpx_alpha_before;
+ kiss_fft_cpx* fftcpx_alpha;
+ rasterList.append(
+ allocateRasterAndLock(&fftcpx_alpha_before, dimOut));
+ rasterList.append(allocateRasterAndLock(&fftcpx_alpha, dimOut));
+
+ // RGB channels
+ kiss_fft_cpx* fftcpx_r_before;
+ kiss_fft_cpx* fftcpx_g_before;
+ kiss_fft_cpx* fftcpx_b_before;
+ kiss_fft_cpx* fftcpx_r;
+ kiss_fft_cpx* fftcpx_g;
+ kiss_fft_cpx* fftcpx_b;
+ rasterList.append(
+ allocateRasterAndLock(&fftcpx_r_before, dimOut));
+ rasterList.append(
+ allocateRasterAndLock(&fftcpx_g_before, dimOut));
+ rasterList.append(
+ allocateRasterAndLock(&fftcpx_b_before, dimOut));
+ rasterList.append(allocateRasterAndLock(&fftcpx_r, dimOut));
+ rasterList.append(allocateRasterAndLock(&fftcpx_g, dimOut));
+ rasterList.append(allocateRasterAndLock(&fftcpx_b, dimOut));
+
+ // for accumulating result image
+ double4* result_main_buff;
+ double4* result_sub_buff;
+ rasterList.append(allocateRasterAndLock(&result_main_buff, dimOut));
+ rasterList.append(allocateRasterAndLock(&result_sub_buff, dimOut));
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ // fft plans
+ int dims[2] = {dimOut.ly, dimOut.lx};
+ kiss_fftnd_cfg kissfft_plan_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
+ planList.append(kissfft_plan_fwd);
+ planList.append(kissfft_plan_bkwd);
+
+ kiss_fftnd_cfg kissfft_plan_r_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_r_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_g_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_g_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_b_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
+ kiss_fftnd_cfg kissfft_plan_b_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
+ planList.append(kissfft_plan_r_fwd);
+ planList.append(kissfft_plan_r_bkwd);
+ planList.append(kissfft_plan_g_fwd);
+ planList.append(kissfft_plan_g_bkwd);
+ planList.append(kissfft_plan_b_fwd);
+ planList.append(kissfft_plan_b_bkwd);
+
+ // initialize result memory
+ memset(result_main_buff, 0, sizeof(double4) * size);
+ memset(result_sub_buff, 0, sizeof(double4) * size);
+
+ double masterHardness = (double)m_hardness->getValue(frame);
+ double layerHardness = layer.layerHardness;
+
+ // convert source image value rgb -> exposure
+ // note that premultiplied source image is already unpremultiplied before this
+ // function
+ BokehUtils::convertRGBToExposure(source_buff, size, layerHardness);
+
+ double focus = m_onFocusDistance->getValue(frame);
+ double adjust = layer.bokehAdjustment;
+
+ for (int mainSub = 0; mainSub < 2; mainSub++) {
+ double4* result_buff_mainSub;
+ QVector segmentDepth_mainSub;
+ unsigned char* indexMap_mainSub;
+ if (mainSub == 0) {
+ result_buff_mainSub = result_main_buff;
+ segmentDepth_mainSub = segmentDepth_main;
+ indexMap_mainSub = indexMap_main_buff;
+ } else {
+ result_buff_mainSub = result_sub_buff;
+ segmentDepth_mainSub = segmentDepth_sub;
+ indexMap_mainSub = indexMap_sub_buff;
+ }
+
+ // compute from further to nearer
+ for (int index = 0; index < segmentDepth_mainSub.size(); index++) {
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ // compute iris size
+ double irisSize = BokehUtils::calcIrisSize(segmentDepth_mainSub.at(index),
+ bokehPixelAmount, focus,
+ adjust, nearDepth, farDepth);
+
+ memset(layer_buff, 0, sizeof(double4) * size);
+ BokehUtils::retrieveLayer(
+ source_buff, layer_buff, indexMap_mainSub, index, dimOut.lx,
+ dimOut.ly, layer.fillGap, layer.doMedian,
+ (index == segmentDepth_mainSub.size() - 1) ? 0 : margin);
+
+ // in case the current segment is at the focus
+ if (-1.0 <= irisSize && 1.0 >= irisSize) {
+ BokehUtils::compositeAsIs(layer_buff, result_buff_mainSub, size);
+ continue;
+ }
+
+ // Resize / flip the iris image according to the size ratio.
+ // Normalize the brightness of the iris image.
+ // Enlarge the iris to the output size.
+ BokehUtils::convertIris(irisSize, kissfft_comp_iris_before, dimOut,
+ irisBBox, irisTile);
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ // Do FFT the iris image.
+ kiss_fftnd(kissfft_plan_fwd, kissfft_comp_iris_before, kissfft_comp_iris);
+
+ // initialize alpha
+ memset(fftcpx_alpha_before, 0, sizeof(kiss_fft_cpx) * size);
+ // initialize channels
+ memset(fftcpx_r_before, 0, sizeof(kiss_fft_cpx) * size);
+ memset(fftcpx_g_before, 0, sizeof(kiss_fft_cpx) * size);
+ memset(fftcpx_b_before, 0, sizeof(kiss_fft_cpx) * size);
+
+ // retrieve segment layer image for each channel
+ BokehUtils::retrieveChannel(layer_buff, // src
+ fftcpx_r_before, // dst
+ fftcpx_g_before, // dst
+ fftcpx_b_before, // dst
+ fftcpx_alpha_before, // dst
+ size);
+
+ // forward fft of alpha channel
+ kiss_fftnd(kissfft_plan_fwd, fftcpx_alpha_before, fftcpx_alpha);
+
+ // multiply filter on alpha
+ BokehUtils::multiplyFilter(fftcpx_alpha, // dst
+ kissfft_comp_iris, // filter
+ size);
+
+ // inverse fft the alpha channel
+ // note that the result is multiplied by the image size
+ kiss_fftnd(kissfft_plan_bkwd, fftcpx_alpha, fftcpx_alpha_before);
+
+ // over composite the alpha channel
+ BokehUtils::compositeAlpha(result_buff_mainSub, // dst
+ fftcpx_alpha_before, // alpha
+ dimOut.lx, dimOut.ly);
+
+ // create worker threads
+ BokehUtils::BokehRefThread threadR(
+ 0, fftcpx_r_before, fftcpx_r, fftcpx_alpha_before, kissfft_comp_iris,
+ result_buff_mainSub, kissfft_plan_r_fwd, kissfft_plan_r_bkwd, dimOut);
+ BokehUtils::BokehRefThread threadG(
+ 1, fftcpx_g_before, fftcpx_g, fftcpx_alpha_before, kissfft_comp_iris,
+ result_buff_mainSub, kissfft_plan_g_fwd, kissfft_plan_g_bkwd, dimOut);
+ BokehUtils::BokehRefThread threadB(
+ 2, fftcpx_b_before, fftcpx_b, fftcpx_alpha_before, kissfft_comp_iris,
+ result_buff_mainSub, kissfft_plan_b_fwd, kissfft_plan_b_bkwd, dimOut);
+
+ // If you set this flag to true, the fx will be forced to compute in
+ // single thread.
+ // Under some specific condition (such as calling from single-threaded
+ // tcomposer)
+ // we may need to use this flag... For now, I'll keep this option unused.
+ // TODO: investigate this.
+ bool renderInSingleThread = false;
+
+ if (renderInSingleThread) {
+ threadR.run();
+ threadG.run();
+ threadB.run();
+ } else {
+ threadR.start();
+ threadG.start();
+ threadB.start();
+ int waitCount = 0;
+ while (1) {
+ if ((settings.m_isCanceled && *settings.m_isCanceled) ||
+ waitCount >= 2000) // 100 second timeout
+ {
+ if (!threadR.isFinished()) threadR.terminateThread();
+ if (!threadG.isFinished()) threadG.terminateThread();
+ if (!threadB.isFinished()) threadB.terminateThread();
+ while (!threadR.isFinished() || !threadG.isFinished() ||
+ !threadB.isFinished()) {
+ }
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+ if (threadR.isFinished() && threadG.isFinished() &&
+ threadB.isFinished())
+ break;
+ QThread::msleep(50);
+ waitCount++;
+ }
+ }
+
+ } // for each segment
+ } // main and sub
+
+ // cancel check
+ if (settings.m_isCanceled && *settings.m_isCanceled) {
+ releaseAllRastersAndPlans(rasterList, planList);
+ return;
+ }
+
+ BokehUtils::interpolateExposureAndConvertToRGB(
+ result_main_buff, // result1
+ result_sub_buff, // result2
+ mainSub_ratio_buff, // ratio
+ result, // dst
+ size, layerHardness / masterHardness);
+
+ // release rasters and plans
+ releaseAllRastersAndPlans(rasterList, planList);
+}
diff --git a/toonz/sources/stdfx/iwa_bokeh_util.h b/toonz/sources/stdfx/iwa_bokeh_util.h
new file mode 100644
index 00000000..6740d1ea
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bokeh_util.h
@@ -0,0 +1,267 @@
+#pragma once
+
+#ifndef IWA_BOKEH_UTIL_H
+#define IWA_BOKEH_UTIL_H
+
+#include "tgeometry.h"
+#include "traster.h"
+#include "kiss_fft.h"
+#include "tools/kiss_fftnd.h"
+#include "ttile.h"
+#include "stdfx.h"
+#include "tfxparam.h"
+
+#include
+#include
+
+struct double4 {
+ double x, y, z, w;
+};
+
+struct double2 {
+ double x, y;
+};
+
+struct int2 {
+ int x, y;
+};
+
+namespace BokehUtils {
+
+//------------------------------------
+
+class MyThread : public QThread {
+public:
+ enum Channel { Red = 0, Green, Blue };
+
+private:
+ int m_channel;
+
+ volatile bool m_finished;
+
+ TRasterP m_layerTileRas;
+ double4* m_result;
+ double* m_alpha_bokeh;
+
+ kiss_fft_cpx* m_kissfft_comp_iris;
+
+ double m_layerHardness;
+ double m_masterHardness;
+
+ TRasterGR8P m_kissfft_comp_in_ras, m_kissfft_comp_out_ras;
+ kiss_fft_cpx *m_kissfft_comp_in, *m_kissfft_comp_out;
+ kiss_fftnd_cfg m_kissfft_plan_fwd, m_kissfft_plan_bkwd;
+
+ bool m_isTerminated;
+
+ // not used for now
+ bool m_doLightenComp;
+
+public:
+ MyThread(Channel channel, TRasterP layerTileRas, double4* result,
+ double* alpha_bokeh, kiss_fft_cpx* kissfft_comp_iris,
+ double layerHardness, double masterHardness = 0.0,
+ bool doLightenComp = false); // not used for now
+
+ // Convert the pixels from RGB values to exposures and multiply it by alpha
+ // channel value.
+ // Store the results in the real part of kiss_fft_cpx.
+ template
+ void setLayerRaster(const RASTER srcRas, kiss_fft_cpx* dstMem,
+ TDimensionI dim);
+
+ void run();
+
+ bool isFinished() { return m_finished; }
+
+ //ƒƒ‚ƒŠŠm•Û
+ bool init();
+
+ void terminateThread() { m_isTerminated = true; }
+
+ bool checkTerminationAndCleanupThread();
+};
+
+//------------------------------------
+
+class BokehRefThread : public QThread {
+ int m_channel;
+ volatile bool m_finished;
+
+ kiss_fft_cpx* m_fftcpx_channel_before;
+ kiss_fft_cpx* m_fftcpx_channel;
+ kiss_fft_cpx* m_fftcpx_alpha;
+ kiss_fft_cpx* m_fftcpx_iris;
+ double4* m_result_buff;
+
+ kiss_fftnd_cfg m_kissfft_plan_fwd, m_kissfft_plan_bkwd;
+
+ TDimensionI m_dim;
+ bool m_isTerminated;
+
+public:
+ BokehRefThread(int channel, kiss_fft_cpx* fftcpx_channel_before,
+ kiss_fft_cpx* fftcpx_channel, kiss_fft_cpx* fftcpx_alpha,
+ kiss_fft_cpx* fftcpx_iris, double4* result_buff,
+ kiss_fftnd_cfg kissfft_plan_fwd,
+ kiss_fftnd_cfg kissfft_plan_bkwd, TDimensionI& dim);
+
+ void run() override;
+
+ bool isFinished() { return m_finished; }
+ void terminateThread() { m_isTerminated = true; }
+};
+
+//------------------------------------
+
+// normalize the source raster image to 0-1 and set to dstMem
+// returns true if the source is (seems to be) premultiplied
+template
+void setSourceRaster(const RASTER srcRas, double4* dstMem, TDimensionI dim);
+
+// normalize brightness of the depth reference image to unsigned char
+// and store into dstMem
+template
+void setDepthRaster(const RASTER srcRas, unsigned char* dstMem,
+ TDimensionI dim);
+
+// create the depth index map
+void defineSegemntDepth(
+ const unsigned char* indexMap_main, const unsigned char* indexMap_sub,
+ const double* mainSub_ratio, const unsigned char* depth_host,
+ const TDimensionI& dimOut, QVector& segmentDepth_main,
+ QVector& segmentDepth_sub, const double focusDepth,
+ int distancePrecision = 10, double nearDepth = 0.0, double farDepth = 1.0);
+
+// convert source image value rgb -> exposure
+void convertRGBToExposure(const double4* source_buff, int size,
+ double filmGamma);
+
+// convert result image value exposure -> rgb
+void convertExposureToRGB(const double4* result_buff, int size,
+ double filmGamma);
+
+// obtain iris size from the depth value
+double calcIrisSize(const double depth, const double bokehPixelAmount,
+ const double onFocusDistance,
+ const double bokehAdjustment = 1.0, double nearDepth = 0.0,
+ double farDepth = 1.0);
+
+// generate the segment layer source at the current depth
+// considering fillGap and doMedian options
+void retrieveLayer(const double4* source_buff,
+ const double4* segment_layer_buff,
+ const unsigned char* indexMap_mainSub, int index, int lx,
+ int ly, bool fillGap = true, bool doMedian = false,
+ int margin = 0);
+
+// normal-composite the layer as is, without filtering
+void compositeAsIs(const double4* segment_layer_buff,
+ const double4* result_buff_mainSub, int size);
+
+// Resize / flip the iris image according to the size ratio.
+// Normalize the brightness of the iris image.
+// Enlarge the iris to the output size.
+void convertIris(const double irisSize, kiss_fft_cpx* kissfft_comp_iris_before,
+ const TDimensionI& dimOut, const TRectD& irisBBox,
+ const TTile& irisTile);
+
+// retrieve segment layer image for each channel
+void retrieveChannel(const double4* segment_layer_buff, // src
+ kiss_fft_cpx* fftcpx_r_before, // dst
+ kiss_fft_cpx* fftcpx_g_before, // dst
+ kiss_fft_cpx* fftcpx_b_before, // dst
+ kiss_fft_cpx* fftcpx_a_before, // dst
+ int size);
+
+// multiply filter on channel
+void multiplyFilter(kiss_fft_cpx* fftcpx_channel, // dst
+ kiss_fft_cpx* fftcpx_iris, // filter
+ int size);
+
+// normal comosite the alpha channel
+void compositeAlpha(const double4* result_buff, // dst
+ const kiss_fft_cpx* fftcpx_alpha, // alpha
+ int lx, int ly);
+
+// interpolate main and sub exposures
+// set to result
+void interpolateExposureAndConvertToRGB(
+ const double4* result_main_buff, // result1
+ const double4* result_sub_buff, // result2
+ const double* mainSub_ratio, // ratio
+ const double4* result_buff, // dst
+ int size, double layerHardnessRatio = 1.0);
+
+//"Over" composite the layer to the output exposure.
+void compositLayerAsIs(TTile& layerTile, double4* result, TDimensionI& dimOut,
+ double filmGamma);
+
+// Do FFT the alpha channel.
+// Forward FFT -> Multiply by the iris data -> Backward FFT
+void calcAlfaChannelBokeh(kiss_fft_cpx* kissfft_comp_iris, TTile& layerTile,
+ double* alpha_bokeh);
+
+// convert to channel value and set to output
+template
+void setOutputRaster(double4* src, const RASTER dstRas, TDimensionI& dim,
+ int2 margin);
+
+// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
+double getBokehPixelAmount(const double bokehAmount, const TAffine affine);
+} // namespace BokehUtils
+
+//-----------------------------------------------------
+
+class Iwa_BokehCommonFx : public TStandardRasterFx {
+protected:
+ TRasterFxPort m_iris;
+ TDoubleParamP m_onFocusDistance; // Focus Distance (0-1)
+ TDoubleParamP m_bokehAmount; // The maximum bokeh size. The size of bokeh at
+ // the layer separated by 1.0 from the focal
+ // position
+ TDoubleParamP m_hardness; // Film gamma
+
+ struct LayerValue {
+ TTile* sourceTile;
+ // set to false if the input image is already premultiplied.
+ // this parameter is now always false (assuming input images are always
+ // premultiplied). the value is left to keep backward compatibility
+ bool premultiply;
+ double layerHardness;
+ int depth_ref;
+
+ double irisSize;
+
+ double distance;
+ double bokehAdjustment;
+ double depthRange;
+ int distancePrecision;
+ bool fillGap;
+ bool doMedian;
+ };
+
+ void doFx(TTile& tile, double frame, const TRenderSettings& settings,
+ double bokehPixelAmount, int margin, TDimensionI& dimOut,
+ TRectD& irisBBox, TTile& irisTile, QList& layerValues,
+ QMap& ctrls);
+
+ void doBokehRef(double4* result, double frame,
+ const TRenderSettings& settings, double bokehPixelAmount,
+ int margin, TDimensionI& dimOut, TRectD& irisBBox,
+ TTile& irisTile, kiss_fft_cpx* kissfft_comp_iris,
+ LayerValue layer, unsigned char* ctrl);
+
+public:
+ Iwa_BokehCommonFx();
+
+ void doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) override = 0;
+
+ bool doGetBBox(double frame, TRectD& bBox,
+ const TRenderSettings& info) final override;
+
+ bool canHandle(const TRenderSettings& info, double frame) final override;
+};
+
+#endif
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bokehfx.cpp b/toonz/sources/stdfx/iwa_bokehfx.cpp
index 1b856502..c9616999 100644
--- a/toonz/sources/stdfx/iwa_bokehfx.cpp
+++ b/toonz/sources/stdfx/iwa_bokehfx.cpp
@@ -15,387 +15,60 @@
namespace {
QReadWriteLock lock;
-QMutex mutex, fx_mutex;
+QMutex fx_mutex;
-bool isFurtherLayer(const QPair val1,
- const QPair val2) {
+bool isFurtherLayer(const QPair val1,
+ const QPair val2) {
+ // if the layers are at the same depth, then put the layer with smaller index
+ // above
+ if (val1.second == val2.second) return val1.first > val2.first;
return val1.second > val2.second;
}
-// FFT coordinate -> Normal corrdinate
-inline int getCoord(int i, int j, int lx, int ly) {
- int cx = i - lx / 2;
- int cy = j - ly / 2;
-
- if (cx < 0) cx += lx;
- if (cy < 0) cy += ly;
-
- return cy * lx + cx;
+template
+TRasterGR8P allocateRasterAndLock(T** buf, TDimensionI dim) {
+ TRasterGR8P ras(dim.lx * sizeof(T), dim.ly);
+ ras->lock();
+ *buf = (T*)ras->getRawData();
+ return ras;
+}
+// release all registered raster memories and free all fft plans
+void releaseAllRastersAndPlans(QList& rasterList,
+ QList& planList) {
+ for (int r = 0; r < rasterList.size(); r++) rasterList.at(r)->unlock();
+ for (int p = 0; p < planList.size(); p++) kiss_fft_free(planList.at(p));
}
-// RGB value <--> Exposure
-inline float valueToExposure(float value, float filmGamma) {
- float logVal = (value - 0.5) / filmGamma;
- return pow(10, logVal);
-}
-inline float exposureToValue(float exposure, float filmGamma) {
- return log10(exposure) * filmGamma + 0.5;
-}
}; // namespace
-//--------------------------------------------
-// Threads used for FFT computation for each RGB channel
-//--------------------------------------------
-
-MyThread::MyThread(Channel channel, TRasterP layerTileRas, TRasterP outTileRas,
- TRasterP tmpAlphaRas, kiss_fft_cpx* kissfft_comp_iris,
- float filmGamma,
- bool doLightenComp) // not used for now
- : m_channel(channel)
- , m_layerTileRas(layerTileRas)
- , m_outTileRas(outTileRas)
- , m_tmpAlphaRas(tmpAlphaRas)
- , m_kissfft_comp_iris(kissfft_comp_iris)
- , m_filmGamma(filmGamma)
- , m_finished(false)
- , m_kissfft_comp_in(0)
- , m_kissfft_comp_out(0)
- , m_isTerminated(false)
- , m_doLightenComp(doLightenComp) // not used for now
-{}
-
-bool MyThread::init() {
- // get the source size
- int lx, ly;
- lx = m_layerTileRas->getSize().lx;
- ly = m_layerTileRas->getSize().ly;
-
- // memory allocation for input
- m_kissfft_comp_in_ras = TRasterGR8P(lx * sizeof(kiss_fft_cpx), ly);
- m_kissfft_comp_in_ras->lock();
- m_kissfft_comp_in = (kiss_fft_cpx*)m_kissfft_comp_in_ras->getRawData();
-
- // allocation check
- if (m_kissfft_comp_in == 0) return false;
-
- // cancel check
- if (m_isTerminated) {
- m_kissfft_comp_in_ras->unlock();
- return false;
- }
-
- // memory allocation for output
- m_kissfft_comp_out_ras = TRasterGR8P(lx * sizeof(kiss_fft_cpx), ly);
- m_kissfft_comp_out_ras->lock();
- m_kissfft_comp_out = (kiss_fft_cpx*)m_kissfft_comp_out_ras->getRawData();
-
- // allocation check
- if (m_kissfft_comp_out == 0) {
- m_kissfft_comp_in_ras->unlock();
- m_kissfft_comp_in = 0;
- return false;
- }
-
- // cancel check
- if (m_isTerminated) {
- m_kissfft_comp_in_ras->unlock();
- m_kissfft_comp_in = 0;
- m_kissfft_comp_out_ras->unlock();
- m_kissfft_comp_out = 0;
- return false;
- }
-
- // create the forward FFT plan
- int dims[2] = {ly, lx};
- int ndims = 2;
- m_kissfft_plan_fwd = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
- // allocation and cancel check
- if (m_kissfft_plan_fwd == NULL || m_isTerminated) {
- m_kissfft_comp_in_ras->unlock();
- m_kissfft_comp_in = 0;
- m_kissfft_comp_out_ras->unlock();
- m_kissfft_comp_out = 0;
- return false;
- }
-
- // create the backward FFT plan
- m_kissfft_plan_bkwd = kiss_fftnd_alloc(dims, ndims, true, 0, 0);
- // allocation and cancel check
- if (m_kissfft_plan_bkwd == NULL || m_isTerminated) {
- m_kissfft_comp_in_ras->unlock();
- m_kissfft_comp_in = 0;
- m_kissfft_comp_out_ras->unlock();
- m_kissfft_comp_out = 0;
- kiss_fft_free(m_kissfft_plan_fwd);
- m_kissfft_plan_fwd = NULL;
- return false;
- }
-
- // return true if all the initializations are done
- return true;
-}
-
-//------------------------------------------------------------
-// Convert the pixels from RGB values to exposures and multiply it by alpha
-// channel value.
-// Store the results in the real part of kiss_fft_cpx.
-//------------------------------------------------------------
-template
-void MyThread::setLayerRaster(const RASTER srcRas, kiss_fft_cpx* dstMem,
- TDimensionI dim) {
- for (int j = 0; j < dim.ly; j++) {
- PIXEL* pix = srcRas->pixels(j);
- for (int i = 0; i < dim.lx; i++, pix++) {
- if (pix->m != 0) {
- float val = (m_channel == Red)
- ? (float)pix->r
- : (m_channel == Green) ? (float)pix->g : (float)pix->b;
- // multiply the exposure by alpha channel value
- dstMem[j * dim.lx + i].r =
- valueToExposure(val / (float)PIXEL::maxChannelValue) *
- ((float)pix->m / (float)PIXEL::maxChannelValue);
- }
- }
- }
-}
-
-//------------------------------------------------------------
-// Composite the bokeh layer to the result
-//------------------------------------------------------------
-template
-void MyThread::compositLayerToTile(const RASTER layerRas,
- const RASTER outTileRas,
- const A_RASTER alphaRas, TDimensionI dim,
- int2 margin) {
- int j = margin.y;
- for (int out_j = 0; out_j < outTileRas->getLy(); j++, out_j++) {
- PIXEL* outPix = outTileRas->pixels(out_j);
- A_PIXEL* alphaPix = alphaRas->pixels(j);
-
- alphaPix += margin.x;
-
- int i = margin.x;
- for (int out_i = 0; out_i < outTileRas->getLx(); i++, out_i++) {
- // If the layer pixel is transparent, keep the result pizel as-is.
- float alpha = (float)alphaPix->value / (float)PIXEL::maxChannelValue;
- if (alpha == 0.0f) {
- alphaPix++;
- outPix++;
- continue;
- }
- // Composite the upper layer exposure with the bottom layers. Then,
- // convert the exposure to RGB values.
- typename PIXEL::Channel dnVal =
- (m_channel == Red) ? outPix->r
- : (m_channel == Green) ? outPix->g : outPix->b;
-
- float exposure;
- double val;
- if (alpha == 1.0 || dnVal == 0.0) {
- exposure = (m_kissfft_comp_in[getCoord(i, j, dim.lx, dim.ly)].r /
- (dim.lx * dim.ly));
- val = exposureToValue(exposure) * (float)PIXEL::maxChannelValue + 0.5f;
- } else {
- exposure =
- (m_kissfft_comp_in[getCoord(i, j, dim.lx, dim.ly)].r /
- (dim.lx * dim.ly)) +
- valueToExposure((float)dnVal / (float)PIXEL::maxChannelValue) *
- (1 - alpha);
- val = exposureToValue(exposure) * (float)PIXEL::maxChannelValue + 0.5f;
- // not used for now
- if (m_doLightenComp) val = std::max(val, (double)dnVal);
- }
-
- // clamp
- if (val < 0.0)
- val = 0.0;
- else if (val > (float)PIXEL::maxChannelValue)
- val = (float)PIXEL::maxChannelValue;
-
- switch (m_channel) {
- case Red:
- outPix->r = (typename PIXEL::Channel)val;
- //"over" composite the alpha channel here
- if (outPix->m != A_PIXEL::maxChannelValue) {
- if (alphaPix->value == A_PIXEL::maxChannelValue)
- outPix->m = A_PIXEL::maxChannelValue;
- else
- outPix->m =
- alphaPix->value +
- (typename A_PIXEL::Channel)(
- (float)outPix->m *
- (float)(A_PIXEL::maxChannelValue - alphaPix->value) /
- (float)A_PIXEL::maxChannelValue);
- }
- break;
- case Green:
- outPix->g = (typename PIXEL::Channel)val;
- break;
- case Blue:
- outPix->b = (typename PIXEL::Channel)val;
- break;
- }
-
- alphaPix++;
- outPix++;
- }
- }
-}
-
-//------------------------------------------------------------
-
-void MyThread::run() {
- // get the source image size
- TDimensionI dim = m_layerTileRas->getSize();
-
- int2 margin = {(dim.lx - m_outTileRas->getSize().lx) / 2,
- (dim.ly - m_outTileRas->getSize().ly) / 2};
-
- // initialize
- for (int i = 0; i < dim.lx * dim.ly; i++) {
- m_kissfft_comp_in[i].r = 0.0; // real part
- m_kissfft_comp_in[i].i = 0.0; // imaginary part
- }
-
- TRaster32P ras32 = (TRaster32P)m_layerTileRas;
- TRaster64P ras64 = (TRaster64P)m_layerTileRas;
- // Prepare data for FFT.
- // Convert the RGB values to the exposure, then multiply it by the alpha
- // channel value
- {
- lock.lockForRead();
- if (ras32)
- setLayerRaster(ras32, m_kissfft_comp_in, dim);
- else if (ras64)
- setLayerRaster(ras64, m_kissfft_comp_in, dim);
- else {
- lock.unlock();
- return;
- }
-
- lock.unlock();
- }
-
- if (checkTerminationAndCleanupThread()) return;
-
- kiss_fftnd(m_kissfft_plan_fwd, m_kissfft_comp_in, m_kissfft_comp_out);
- kiss_fft_free(m_kissfft_plan_fwd); // we don't need this plan anymore
- m_kissfft_plan_fwd = NULL;
-
- if (checkTerminationAndCleanupThread()) return;
-
- // Filtering. Multiply by the iris FFT data
- {
- for (int i = 0; i < dim.lx * dim.ly; i++) {
- float re, im;
- re = m_kissfft_comp_out[i].r * m_kissfft_comp_iris[i].r -
- m_kissfft_comp_out[i].i * m_kissfft_comp_iris[i].i;
- im = m_kissfft_comp_out[i].r * m_kissfft_comp_iris[i].i +
- m_kissfft_comp_iris[i].r * m_kissfft_comp_out[i].i;
- m_kissfft_comp_out[i].r = re;
- m_kissfft_comp_out[i].i = im;
- }
- }
-
- if (checkTerminationAndCleanupThread()) return;
-
- kiss_fftnd(m_kissfft_plan_bkwd, m_kissfft_comp_out,
- m_kissfft_comp_in); // Backward FFT
- kiss_fft_free(m_kissfft_plan_bkwd); // we don't need this plan anymore
- m_kissfft_plan_bkwd = NULL;
-
- // In the backward FFT above, "m_kissfft_comp_out" is used as input and
- // "m_kissfft_comp_in" as output.
- // So we don't need "m_kissfft_comp_out" anymore.
- m_kissfft_comp_out_ras->unlock();
- m_kissfft_comp_out = 0;
-
- if (checkTerminationAndCleanupThread()) return;
-
- {
- QMutexLocker locker(&mutex);
-
- TRaster32P ras32 = (TRaster32P)m_layerTileRas;
- TRaster64P ras64 = (TRaster64P)m_layerTileRas;
-
- if (ras32) {
- compositLayerToTile(
- ras32, (TRaster32P)m_outTileRas, (TRasterGR8P)m_tmpAlphaRas, dim,
- margin);
- } else if (ras64) {
- compositLayerToTile(
- ras64, (TRaster64P)m_outTileRas, (TRasterGR16P)m_tmpAlphaRas, dim,
- margin);
- } else {
- lock.unlock();
- return;
- }
- }
-
- // Now we don't need "m_kissfft_comp_in" anymore.
- m_kissfft_comp_in_ras->unlock();
- m_kissfft_comp_in = 0;
-
- m_finished = true;
-}
-
-// RGB value <--> Exposure
-inline float MyThread::valueToExposure(float value) {
- float logVal = (value - 0.5) / m_filmGamma;
- return pow(10, logVal);
-}
-inline float MyThread::exposureToValue(float exposure) {
- return log10(exposure) * m_filmGamma + 0.5;
-}
-
-// Release the raster memory and FFT plans on cancel rendering.
-bool MyThread::checkTerminationAndCleanupThread() {
- if (!m_isTerminated) return false;
-
- if (m_kissfft_comp_in) m_kissfft_comp_in_ras->unlock();
- if (m_kissfft_comp_out) m_kissfft_comp_out_ras->unlock();
- if (m_kissfft_plan_fwd) kiss_fft_free(m_kissfft_plan_fwd);
- if (m_kissfft_plan_bkwd) kiss_fft_free(m_kissfft_plan_bkwd);
-
- m_finished = true;
- return true;
-}
-
//--------------------------------------------
// Iwa_BokehFx
//--------------------------------------------
-Iwa_BokehFx::Iwa_BokehFx()
- : m_onFocusDistance(0.5), m_bokehAmount(30.0), m_hardness(0.3) {
+Iwa_BokehFx::Iwa_BokehFx() {
// Bind the common parameters
- addInputPort("Iris", m_iris);
bindParam(this, "on_focus_distance", m_onFocusDistance, false);
bindParam(this, "bokeh_amount", m_bokehAmount, false);
bindParam(this, "hardness", m_hardness, false);
- // Set the ranges of common parameters
- m_onFocusDistance->setValueRange(0, 1);
- m_bokehAmount->setValueRange(0, 300);
- m_bokehAmount->setMeasureName("fxLength");
- m_hardness->setValueRange(0.05, 3.0);
-
// Bind the layer parameters
for (int layer = 0; layer < LAYER_NUM; layer++) {
- m_layerParams[layer].m_premultiply = TBoolParamP(false);
m_layerParams[layer].m_distance = TDoubleParamP(0.5);
- m_layerParams[layer].m_bokehAdjustment = TDoubleParamP(1);
+ m_layerParams[layer].m_bokehAdjustment = TDoubleParamP(1.0);
+ // The premultiply option is not displayed anymore for simplicity
+ m_layerParams[layer].m_premultiply = TBoolParamP(false);
std::string str = QString("Source%1").arg(layer + 1).toStdString();
addInputPort(str, m_layerParams[layer].m_source);
- bindParam(this, QString("premultiply%1").arg(layer + 1).toStdString(),
- m_layerParams[layer].m_premultiply, false);
bindParam(this, QString("distance%1").arg(layer + 1).toStdString(),
m_layerParams[layer].m_distance, false);
bindParam(this, QString("bokeh_adjustment%1").arg(layer + 1).toStdString(),
m_layerParams[layer].m_bokehAdjustment, false);
+ bindParam(this, QString("premultiply%1").arg(layer + 1).toStdString(),
+ m_layerParams[layer].m_premultiply, false);
- m_layerParams[layer].m_distance->setValueRange(0, 1);
- m_layerParams[layer].m_bokehAdjustment->setValueRange(0, 2);
+ m_layerParams[layer].m_distance->setValueRange(0.0, 1.0);
+ m_layerParams[layer].m_bokehAdjustment->setValueRange(0.0, 2.0);
}
}
@@ -423,12 +96,13 @@ void Iwa_BokehFx::doCompute(TTile& tile, double frame,
QList sourceIndices = getSortedSourceIndices(frame);
// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
- float bokehPixelAmount = getBokehPixelAmount(frame, settings.m_affine);
+ double bokehPixelAmount = BokehUtils::getBokehPixelAmount(
+ m_bokehAmount->getValue(frame), settings.m_affine);
// Compute the bokeh size for each layer. The source tile will be enlarged by
// the largest size of them.
- float maxIrisSize;
- QVector irisSizes =
+ double maxIrisSize;
+ QMap irisSizes =
getIrisSizes(frame, sourceIndices, bokehPixelAmount, maxIrisSize);
int margin = tceil(maxIrisSize / 2.0);
@@ -462,24 +136,11 @@ void Iwa_BokehFx::doCompute(TTile& tile, double frame,
//----------------------------
// Compute the input tiles first
QMap sourceTiles;
- for (int i = 0; i < sourceIndices.size(); i++) {
- int index = sourceIndices.at(i);
- float irisSize = irisSizes.at(i);
-
+ for (auto index : sourceIndices) {
TTile* layerTile = new TTile();
- // Layer to be composited as-is
- if (-1.0 <= irisSize && 1.0 >= irisSize) {
- m_layerParams[index].m_source->allocateAndCompute(
- *layerTile, tile.m_pos,
- TDimension(tile.getRaster()->getLx(), tile.getRaster()->getLy()),
- tile.getRaster(), frame, settings);
- }
- // Layer to be off-focused
- else {
- m_layerParams[index].m_source->allocateAndCompute(
- *layerTile, _rectOut.getP00(), dimOut, tile.getRaster(), frame,
- settings);
- }
+ m_layerParams[index].m_source->allocateAndCompute(
+ *layerTile, _rectOut.getP00(), dimOut, tile.getRaster(), frame,
+ settings);
sourceTiles[index] = layerTile;
}
@@ -494,284 +155,39 @@ void Iwa_BokehFx::doCompute(TTile& tile, double frame,
static_cast(irisBBox.getLy() + 0.5)),
tile.getRaster(), frame, settings);
- // This fx is relatively heavy so the multi thread computation is introduced.
- // Lock the mutex here in order to prevent multiple rendering tasks run at the
- // same time.
- QMutexLocker fx_locker(&fx_mutex);
+ double masterHardness = m_hardness->getValue(frame);
- kiss_fft_cpx* kissfft_comp_iris;
- // create the iris data for FFT (in the same size as the source tile)
- TRasterGR8P kissfft_comp_iris_ras(dimOut.lx * sizeof(kiss_fft_cpx),
- dimOut.ly);
- kissfft_comp_iris_ras->lock();
- kissfft_comp_iris = (kiss_fft_cpx*)kissfft_comp_iris_ras->getRawData();
+ QMap ctrls;
- // obtain the film gamma
- double filmGamma = m_hardness->getValue(frame);
-
- // clear the raster memory
- tile.getRaster()->clear();
- TRaster32P raster32 = tile.getRaster();
- if (raster32)
- raster32->fill(TPixel32::Transparent);
- else {
- TRaster64P ras64 = tile.getRaster();
- if (ras64) ras64->fill(TPixel64::Transparent);
+ QList layerValues;
+ for (auto index : sourceIndices) {
+ LayerValue layerValue;
+ layerValue.sourceTile = sourceTiles[index];
+ layerValue.premultiply =
+ m_layerParams[index].m_premultiply->getValue() ? 1 : 0;
+ layerValue.layerHardness = masterHardness;
+ layerValue.depth_ref = 0;
+ layerValue.irisSize = irisSizes.value(index);
+ layerValue.distance = m_layerParams[index].m_distance->getValue(frame);
+ layerValue.bokehAdjustment =
+ m_layerParams[index].m_bokehAdjustment->getValue(frame);
+ layerValues.append(layerValue);
}
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- return;
- }
-
- // Compute from from the most distant layer
- for (int i = 0; i < sourceIndices.size(); i++) {
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- return;
- }
-
- int index = sourceIndices.at(i);
-
- // The iris size of the current layer
- float irisSize = irisSizes.at(i);
-
- // If the size of iris is less than 1 (i.e. the layer is at focal position),
- // composite the layer as-is.
- if (-1.0 <= irisSize && 1.0 >= irisSize) {
- //"Over" composite the layer to the output raster.
- TTile* layerTile = sourceTiles.value(index);
- compositLayerAsIs(tile, *layerTile, frame, settings, index);
- sourceTiles.remove(index);
- // Continue to the next layer
- continue;
- }
-
- {
- // Create the Iris image for FFT
- kiss_fft_cpx* kissfft_comp_iris_before;
- TRasterGR8P kissfft_comp_iris_before_ras(dimOut.lx * sizeof(kiss_fft_cpx),
- dimOut.ly);
- kissfft_comp_iris_before_ras->lock();
- kissfft_comp_iris_before =
- (kiss_fft_cpx*)kissfft_comp_iris_before_ras->getRawData();
- // Resize / flip the iris image according to the size ratio.
- // Normalize the brightness of the iris image.
- // Enlarge the iris to the output size.
- convertIris(irisSize, kissfft_comp_iris_before, dimOut, irisBBox,
- irisTile);
-
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- kissfft_comp_iris_ras->unlock();
- kissfft_comp_iris_before_ras->unlock();
- tile.getRaster()->clear();
- return;
- }
-
- // Create the FFT plan for the iris image.
- kiss_fftnd_cfg iris_kissfft_plan;
- while (1) {
- int dims[2] = {dimOut.ly, dimOut.lx};
- int ndims = 2;
- iris_kissfft_plan = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
- if (iris_kissfft_plan != NULL) break;
- }
- // Do FFT the iris image.
- kiss_fftnd(iris_kissfft_plan, kissfft_comp_iris_before,
- kissfft_comp_iris);
- kiss_fft_free(iris_kissfft_plan);
- kissfft_comp_iris_before_ras->unlock();
- }
-
- // Up to here, FFT-ed iris data is stored in kissfft_comp_iris
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- return;
- }
- // Prepare the layer rasters
- TTile* layerTile = sourceTiles.value(index);
- // Unpremultiply the source if needed
- if (!m_layerParams[index].m_premultiply->getValue())
- TRop::depremultiply(layerTile->getRaster());
- // Create the raster memory for storing alpha channel
- TRasterP tmpAlphaRas;
- {
- TRaster32P ras32(tile.getRaster());
- TRaster64P ras64(tile.getRaster());
- if (ras32)
- tmpAlphaRas = TRasterGR8P(dimOut);
- else if (ras64)
- tmpAlphaRas = TRasterGR16P(dimOut);
- }
- tmpAlphaRas->lock();
-
- // Do FFT the alpha channel.
- // Forward FFT -> Multiply by the iris data -> Backward FFT
- calcAlfaChannelBokeh(kissfft_comp_iris, *layerTile, tmpAlphaRas);
-
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- tmpAlphaRas->unlock();
- return;
- }
-
- // Create the threads for RGB channels
- MyThread threadR(MyThread::Red, layerTile->getRaster(), tile.getRaster(),
- tmpAlphaRas, kissfft_comp_iris, filmGamma);
- MyThread threadG(MyThread::Green, layerTile->getRaster(), tile.getRaster(),
- tmpAlphaRas, kissfft_comp_iris, filmGamma);
- MyThread threadB(MyThread::Blue, layerTile->getRaster(), tile.getRaster(),
- tmpAlphaRas, kissfft_comp_iris, filmGamma);
-
- // If you set this flag to true, the fx will be forced to compute in single
- // thread.
- // Under some specific condition (such as calling from single-threaded
- // tcomposer)
- // we may need to use this flag... For now, I'll keep this option unused.
- // TODO: investigate this.
- bool renderInSingleThread = false;
-
- // Start the thread when the initialization is done.
- // Red channel
- int waitCount = 0;
- while (1) {
- // cancel & timeout check
- if ((settings.m_isCanceled && *settings.m_isCanceled) ||
- waitCount >= 20) // 10 second timeout
- {
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- tmpAlphaRas->unlock();
- return;
- }
- if (threadR.init()) {
- if (renderInSingleThread)
- threadR.run();
- else
- threadR.start();
- break;
- }
- QThread::msleep(500);
- waitCount++;
- }
- // Green channel
- waitCount = 0;
- while (1) {
- // cancel & timeout check
- if ((settings.m_isCanceled && *settings.m_isCanceled) ||
- waitCount >= 20) // 10 second timeout
- {
- if (!threadR.isFinished()) threadR.terminateThread();
- while (!threadR.isFinished()) {
- }
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- tmpAlphaRas->unlock();
- return;
- }
- if (threadG.init()) {
- if (renderInSingleThread)
- threadG.run();
- else
- threadG.start();
- break;
- }
- QThread::msleep(500);
- waitCount++;
- }
- // Blue channel
- waitCount = 0;
- while (1) {
- // cancel & timeout check
- if ((settings.m_isCanceled && *settings.m_isCanceled) ||
- waitCount >= 20) // 10 second timeout
- {
- if (!threadR.isFinished()) threadR.terminateThread();
- if (!threadG.isFinished()) threadG.terminateThread();
- while (!threadR.isFinished() || !threadG.isFinished()) {
- }
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- tmpAlphaRas->unlock();
- return;
- }
- if (threadB.init()) {
- if (renderInSingleThread)
- threadB.run();
- else
- threadB.start();
- break;
- }
- QThread::msleep(500);
- waitCount++;
- }
-
- /*
- * What is done in the thread for each RGB channel:
- * - Convert channel value -> Exposure
- * - Multiply by alpha channel
- * - Forward FFT
- * - Multiply by the iris FFT data
- * - Backward FFT
- * - Convert Exposure -> channel value
- */
-
- waitCount = 0;
- while (1) {
- // cancel & timeout check
- if ((settings.m_isCanceled && *settings.m_isCanceled) ||
- waitCount >= 2000) // 100 second timeout
- {
- if (!threadR.isFinished()) threadR.terminateThread();
- if (!threadG.isFinished()) threadG.terminateThread();
- if (!threadB.isFinished()) threadB.terminateThread();
- while (!threadR.isFinished() || !threadG.isFinished() ||
- !threadB.isFinished()) {
- }
- kissfft_comp_iris_ras->unlock();
- tile.getRaster()->clear();
- tmpAlphaRas->unlock();
- return;
- }
- if (threadR.isFinished() && threadG.isFinished() && threadB.isFinished())
- break;
- QThread::msleep(50);
- waitCount++;
- }
- tmpAlphaRas->unlock();
- sourceTiles.remove(index);
- }
-
- kissfft_comp_iris_ras->unlock();
-}
-
-bool Iwa_BokehFx::doGetBBox(double frame, TRectD& bBox,
- const TRenderSettings& info) {
- bBox = TConsts::infiniteRectD;
- return true;
-}
-
-bool Iwa_BokehFx::canHandle(const TRenderSettings& info, double frame) {
- return false;
+ Iwa_BokehCommonFx::doFx(tile, frame, settings, bokehPixelAmount, margin,
+ dimOut, irisBBox, irisTile, layerValues, ctrls);
+ qDeleteAll(sourceTiles);
}
// Sort the layers by distances
QList Iwa_BokehFx::getSortedSourceIndices(double frame) {
- QList> usedSourceList;
+ QList> usedSourceList;
// Gather the source layers connected to the ports
for (int i = 0; i < LAYER_NUM; i++) {
if (m_layerParams[i].m_source.isConnected())
usedSourceList.push_back(
- QPair(i, m_layerParams[i].m_distance->getValue(frame)));
+ QPair(i, m_layerParams[i].m_distance->getValue(frame)));
}
if (usedSourceList.empty()) return QList();
@@ -787,254 +203,28 @@ QList Iwa_BokehFx::getSortedSourceIndices(double frame) {
return indicesList;
}
-// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
-float Iwa_BokehFx::getBokehPixelAmount(const double frame,
- const TAffine affine) {
- /*--- Convert to vector --- */
- TPointD vect;
- vect.x = m_bokehAmount->getValue(frame);
- vect.y = 0.0;
- /*--- Apply geometrical transformation ---*/
- // For the following lines I referred to lines 586-592 of
- // sources/stdfx/motionblurfx.cpp
- TAffine aff(affine);
- aff.a13 = aff.a23 = 0; /* ignore translation */
- vect = aff * vect;
- /*--- return the length of the vector ---*/
- return sqrt(vect.x * vect.x + vect.y * vect.y);
-}
-
// Compute the bokeh size for each layer. The source tile will be enlarged by
// the largest size of them.
-QVector Iwa_BokehFx::getIrisSizes(const double frame,
- const QList sourceIndices,
- const float bokehPixelAmount,
- float& maxIrisSize) {
- float max = 0.0;
- QVector irisSizes;
+QMap Iwa_BokehFx::getIrisSizes(const double frame,
+ const QList sourceIndices,
+ const double bokehPixelAmount,
+ double& maxIrisSize) {
+ double max = 0.0;
+ QMap irisSizes;
for (int s = 0; s < sourceIndices.size(); s++) {
- int index = sourceIndices.at(s);
- float irisSize = (m_onFocusDistance->getValue(frame) -
- m_layerParams[index].m_distance->getValue(frame)) *
- bokehPixelAmount *
- m_layerParams[index].m_bokehAdjustment->getValue(frame);
- irisSizes.push_back(irisSize);
+ int index = sourceIndices.at(s);
+ double irisSize = (m_onFocusDistance->getValue(frame) -
+ m_layerParams[index].m_distance->getValue(frame)) *
+ bokehPixelAmount *
+ m_layerParams[index].m_bokehAdjustment->getValue(frame);
+ irisSizes[index] = irisSize;
// Update the maximum size
- if (max < fabs(irisSize)) max = fabs(irisSize);
+ if (max < std::abs(irisSize)) max = std::abs(irisSize);
}
maxIrisSize = max;
return irisSizes;
}
-//"Over" composite the layer to the output raster.
-void Iwa_BokehFx::compositLayerAsIs(TTile& tile, TTile& layerTile,
- const double frame,
- const TRenderSettings& settings,
- const int index) {
- // Premultiply the source if needed
- if (m_layerParams[index].m_premultiply->getValue())
- TRop::premultiply(layerTile.getRaster());
-
- TRop::over(tile.getRaster(), layerTile.getRaster());
-}
-
-//------------------------------------------------
-// Resize / flip the iris image according to the size ratio.
-// Normalize the brightness of the iris image.
-// Enlarge the iris to the output size.
-void Iwa_BokehFx::convertIris(const float irisSize,
- kiss_fft_cpx* kissfft_comp_iris_before,
- const TDimensionI& dimOut, const TRectD& irisBBox,
- const TTile& irisTile) {
- // the original size of iris image
- double2 irisOrgSize = {irisBBox.getLx(), irisBBox.getLy()};
-
- // Get the size ratio of iris based on width. The ratio can be negative value.
- double irisSizeResampleRatio = irisSize / irisOrgSize.x;
-
- // Create the raster for resized iris
- double2 resizedIrisSize = {std::abs(irisSizeResampleRatio) * irisOrgSize.x,
- std::abs(irisSizeResampleRatio) * irisOrgSize.y};
- // add 1 pixel margins to all sides
- int2 filterSize = {int(std::ceil(resizedIrisSize.x)) + 2,
- int(std::ceil(resizedIrisSize.y)) + 2};
-
- TPointD resizeOffset((double)filterSize.x - resizedIrisSize.x,
- (double)filterSize.y - resizedIrisSize.y);
- // Add some adjustment in order to absorb the difference of the cases when the
- // iris size is odd and even numbers.
- // Try to set the center of the iris to the center of the screen
- if ((dimOut.lx - filterSize.x) % 2 == 1) filterSize.x++;
- if ((dimOut.ly - filterSize.y) % 2 == 1) filterSize.y++;
-
- // Terminate if the filter size becomes bigger than the output size.
- if (filterSize.x > dimOut.lx || filterSize.y > dimOut.ly) {
- std::cout
- << "Error: The iris filter size becomes larger than the source size!"
- << std::endl;
- return;
- }
-
- TRaster64P resizedIris(TDimension(filterSize.x, filterSize.y));
-
- // Add some adjustment in order to absorb the 0.5 translation to be done in
- // resample()
- TAffine aff;
- TPointD affOffset(0.5, 0.5);
- affOffset += TPointD((dimOut.lx % 2 == 1) ? 0.5 : 0.0,
- (dimOut.ly % 2 == 1) ? 0.5 : 0.0);
-
- aff = TTranslation(resizedIris->getCenterD() + affOffset);
- aff *= TScale(irisSizeResampleRatio);
- aff *= TTranslation(-(irisTile.getRaster()->getCenterD() + affOffset));
-
- // resample the iris
- TRop::resample(resizedIris, irisTile.getRaster(), aff);
-
- // accumulated value
- float irisValAmount = 0.0;
-
- int iris_j = 0;
- // Initialize
- for (int i = 0; i < dimOut.lx * dimOut.ly; i++) {
- kissfft_comp_iris_before[i].r = 0.0;
- kissfft_comp_iris_before[i].i = 0.0;
- }
- for (int j = (dimOut.ly - filterSize.y) / 2; iris_j < filterSize.y;
- j++, iris_j++) {
- TPixel64* pix = resizedIris->pixels(iris_j);
- int iris_i = 0;
- for (int i = (dimOut.lx - filterSize.x) / 2; iris_i < filterSize.x;
- i++, iris_i++) {
- // Value = 0.3R 0.59G 0.11B
- kissfft_comp_iris_before[j * dimOut.lx + i].r =
- ((float)pix->r * 0.3f + (float)pix->g * 0.59f +
- (float)pix->b * 0.11f) /
- (float)USHRT_MAX;
- irisValAmount += kissfft_comp_iris_before[j * dimOut.lx + i].r;
- pix++;
- }
- }
-
- // Normalize value
- for (int i = 0; i < dimOut.lx * dimOut.ly; i++) {
- kissfft_comp_iris_before[i].r /= irisValAmount;
- }
-}
-
-// Do FFT the alpha channel.
-// Forward FFT -> Multiply by the iris data -> Backward FFT
-void Iwa_BokehFx::calcAlfaChannelBokeh(kiss_fft_cpx* kissfft_comp_iris,
- TTile& layerTile, TRasterP tmpAlphaRas) {
- // Obtain the source size
- int lx, ly;
- lx = layerTile.getRaster()->getSize().lx;
- ly = layerTile.getRaster()->getSize().ly;
-
- // Allocate the FFT data
- kiss_fft_cpx *kissfft_comp_in, *kissfft_comp_out;
-
- TRasterGR8P kissfft_comp_in_ras(lx * sizeof(kiss_fft_cpx), ly);
- kissfft_comp_in_ras->lock();
- kissfft_comp_in = (kiss_fft_cpx*)kissfft_comp_in_ras->getRawData();
- TRasterGR8P kissfft_comp_out_ras(lx * sizeof(kiss_fft_cpx), ly);
- kissfft_comp_out_ras->lock();
- kissfft_comp_out = (kiss_fft_cpx*)kissfft_comp_out_ras->getRawData();
-
- // Initialize the FFT data
- for (int i = 0; i < lx * ly; i++) {
- kissfft_comp_in[i].r = 0.0; // real part
- kissfft_comp_in[i].i = 0.0; // imaginary part
- }
-
- TRaster32P ras32 = (TRaster32P)layerTile.getRaster();
- TRaster64P ras64 = (TRaster64P)layerTile.getRaster();
- if (ras32) {
- for (int j = 0; j < ly; j++) {
- TPixel32* pix = ras32->pixels(j);
- for (int i = 0; i < lx; i++) {
- kissfft_comp_in[j * lx + i].r = (float)pix->m / (float)UCHAR_MAX;
- pix++;
- }
- }
- } else if (ras64) {
- for (int j = 0; j < ly; j++) {
- TPixel64* pix = ras64->pixels(j);
- for (int i = 0; i < lx; i++) {
- kissfft_comp_in[j * lx + i].r = (float)pix->m / (float)USHRT_MAX;
- pix++;
- }
- }
- } else
- return;
-
- int dims[2] = {ly, lx};
- int ndims = 2;
- kiss_fftnd_cfg plan_fwd = kiss_fftnd_alloc(dims, ndims, false, 0, 0);
- kiss_fftnd(plan_fwd, kissfft_comp_in, kissfft_comp_out);
- kiss_fft_free(plan_fwd); // we don't need this plan anymore
-
- // Filtering. Multiply by the iris FFT data
- for (int i = 0; i < lx * ly; i++) {
- float re, im;
- re = kissfft_comp_out[i].r * kissfft_comp_iris[i].r -
- kissfft_comp_out[i].i * kissfft_comp_iris[i].i;
- im = kissfft_comp_out[i].r * kissfft_comp_iris[i].i +
- kissfft_comp_iris[i].r * kissfft_comp_out[i].i;
- kissfft_comp_out[i].r = re;
- kissfft_comp_out[i].i = im;
- }
-
- kiss_fftnd_cfg plan_bkwd = kiss_fftnd_alloc(dims, ndims, true, 0, 0);
- kiss_fftnd(plan_bkwd, kissfft_comp_out, kissfft_comp_in); // Backward FFT
- kiss_fft_free(plan_bkwd); // we don't need this plan anymore
-
- // In the backward FFT above, "kissfft_comp_out" is used as input and
- // "kissfft_comp_in" as output.
- // So we don't need "kissfft_comp_out" anymore.
- kissfft_comp_out_ras->unlock();
-
- // Store the result into the alpha channel of layer tile
- if (ras32) {
- TRasterGR8P alphaRas8(tmpAlphaRas);
- for (int j = 0; j < ly; j++) {
- TPixelGR8* pix = alphaRas8->pixels(j);
- for (int i = 0; i < lx; i++) {
- float val =
- kissfft_comp_in[getCoord(i, j, lx, ly)].r / (lx * ly) * 256.0;
- if (val < 0.0)
- val = 0.0;
- else if (val > 255.0)
- val = 255.0;
-
- pix->value = (unsigned char)val;
-
- pix++;
- }
- }
- } else if (ras64) {
- TRasterGR16P alphaRas16(tmpAlphaRas);
- for (int j = 0; j < ly; j++) {
- TPixelGR16* pix = alphaRas16->pixels(j);
- for (int i = 0; i < lx; i++) {
- float val =
- kissfft_comp_in[getCoord(i, j, lx, ly)].r / (lx * ly) * 65536.0;
- if (val < 0.0)
- val = 0.0;
- else if (val > 65535.0)
- val = 65535.0;
-
- pix->value = (unsigned short)val;
-
- pix++;
- }
- }
- } else
- return;
-
- kissfft_comp_in_ras->unlock();
-}
-
FX_PLUGIN_IDENTIFIER(Iwa_BokehFx, "iwa_BokehFx")
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bokehfx.h b/toonz/sources/stdfx/iwa_bokehfx.h
index 9d3b6f78..04f2c450 100644
--- a/toonz/sources/stdfx/iwa_bokehfx.h
+++ b/toonz/sources/stdfx/iwa_bokehfx.h
@@ -21,89 +21,13 @@ distributed with a 3-clause BSD-style license.
#include
#include "tools/kiss_fftnd.h"
+#include "iwa_bokeh_util.h"
const int LAYER_NUM = 5;
-struct double2 {
- double x, y;
-};
-struct int2 {
- int x, y;
-};
-
-class MyThread : public QThread {
-public:
- enum Channel { Red = 0, Green, Blue };
-
-private:
- Channel m_channel;
-
- volatile bool m_finished;
-
- TRasterP m_layerTileRas;
- TRasterP m_outTileRas;
- TRasterP m_tmpAlphaRas;
-
- kiss_fft_cpx *m_kissfft_comp_iris;
-
- float m_filmGamma; // keep the film gamma in each thread as it is refered so
- // often
-
- TRasterGR8P m_kissfft_comp_in_ras, m_kissfft_comp_out_ras;
- kiss_fft_cpx *m_kissfft_comp_in, *m_kissfft_comp_out;
- kiss_fftnd_cfg m_kissfft_plan_fwd, m_kissfft_plan_bkwd;
-
- bool m_isTerminated;
-
- // not used for now
- bool m_doLightenComp;
-
-public:
- MyThread(Channel channel, TRasterP layerTileRas, TRasterP outTileRas,
- TRasterP tmpAlphaRas, kiss_fft_cpx *kissfft_comp_iris,
- float m_filmGamma,
- bool doLightenComp = false); // not used for now
-
- // Convert the pixels from RGB values to exposures and multiply it by alpha
- // channel value.
- // Store the results in the real part of kiss_fft_cpx.
- template
- void setLayerRaster(const RASTER srcRas, kiss_fft_cpx *dstMem,
- TDimensionI dim);
-
- // Composite the bokeh layer to the result
- template
- void compositLayerToTile(const RASTER layerRas, const RASTER outTileRas,
- const A_RASTER alphaRas, TDimensionI dim,
- int2 margin);
- void run();
-
- bool isFinished() { return m_finished; }
-
- // RGB value <--> Exposure
- float valueToExposure(float value);
- float exposureToValue(float exposure);
-
- // memory allocation
- bool init();
-
- void terminateThread() { m_isTerminated = true; }
-
- bool checkTerminationAndCleanupThread();
-};
-
-class Iwa_BokehFx : public TStandardRasterFx {
+class Iwa_BokehFx : public Iwa_BokehCommonFx {
FX_PLUGIN_DECLARATION(Iwa_BokehFx)
-protected:
- TRasterFxPort m_iris;
- TDoubleParamP m_onFocusDistance; // Focus Distance (0-1)
- TDoubleParamP m_bokehAmount; // The maximum bokeh size. The size of bokeh at
- // the layer separated by 1.0 from the focal
- // position
- TDoubleParamP m_hardness; // Film gamma
-
struct LAYERPARAM {
TRasterFxPort m_source;
TBoolParamP m_premultiply;
@@ -114,36 +38,17 @@ protected:
// Sort source layers by distance
QList getSortedSourceIndices(double frame);
- // Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
- float getBokehPixelAmount(const double frame, const TAffine affine);
// Compute the bokeh size for each layer. The source tile will be enlarged by
// the largest size of them.
- QVector getIrisSizes(const double frame,
- const QList sourceIndices,
- const float bokehPixelAmount, float &maxIrisSize);
- //"Over" composite the layer to the output raster.
- void compositLayerAsIs(TTile &tile, TTile &layerTile, const double frame,
- const TRenderSettings &settings, const int index);
- // Resize / flip the iris image according to the size ratio.
- // Normalize the brightness of the iris image.
- // Enlarge the iris to the output size.
- void convertIris(const float irisSize, kiss_fft_cpx *kissfft_comp_iris_before,
- const TDimensionI &dimOut, const TRectD &irisBBox,
- const TTile &irisTile);
-
- // Do FFT the alpha channel.
- // Forward FFT -> Multiply by the iris data -> Backward FFT
- void calcAlfaChannelBokeh(kiss_fft_cpx *kissfft_comp_iris, TTile &layerTile,
- TRasterP tmpAlphaRas);
+ QMap getIrisSizes(const double frame,
+ const QList sourceIndices,
+ const double bokehPixelAmount,
+ double &maxIrisSize);
public:
Iwa_BokehFx();
void doCompute(TTile &tile, double frame, const TRenderSettings &settings) override;
-
- bool doGetBBox(double frame, TRectD &bBox, const TRenderSettings &info) override;
-
- bool canHandle(const TRenderSettings &info, double frame) override;
};
#endif
diff --git a/toonz/sources/stdfx/iwa_bokehreffx.cpp b/toonz/sources/stdfx/iwa_bokehreffx.cpp
index f2e2f86e..079c0a37 100644
--- a/toonz/sources/stdfx/iwa_bokehreffx.cpp
+++ b/toonz/sources/stdfx/iwa_bokehreffx.cpp
@@ -4,11 +4,12 @@
#include
#include
+#include
#include
namespace {
-QMutex fx_mutex;
QReadWriteLock lock;
+QMutex fx_mutex;
template
TRasterGR8P allocateRasterAndLock(T** buf, TDimensionI dim) {
@@ -18,20 +19,6 @@ TRasterGR8P allocateRasterAndLock(T** buf, TDimensionI dim) {
return ras;
}
-// modify fft coordinate to normal
-inline int getCoord(int index, int lx, int ly) {
- int i = index % lx;
- int j = index / lx;
-
- int cx = i - lx / 2;
- int cy = j - ly / 2;
-
- if (cx < 0) cx += lx;
- if (cy < 0) cy += ly;
-
- return cy * lx + cx;
-}
-
// release all registered raster memories
void releaseAllRasters(QList& rasterList) {
for (int r = 0; r < rasterList.size(); r++) rasterList.at(r)->unlock();
@@ -45,152 +32,6 @@ void releaseAllRastersAndPlans(QList& rasterList,
}
}; // namespace
-//------------------------------------
-BokehRefThread::BokehRefThread(int channel, kiss_fft_cpx* fftcpx_channel_before,
- kiss_fft_cpx* fftcpx_channel,
- kiss_fft_cpx* fftcpx_alpha,
- kiss_fft_cpx* fftcpx_iris, float4* result_buff,
- kiss_fftnd_cfg kissfft_plan_fwd,
- kiss_fftnd_cfg kissfft_plan_bkwd,
- TDimensionI& dim)
- : m_channel(channel)
- , m_fftcpx_channel_before(fftcpx_channel_before)
- , m_fftcpx_channel(fftcpx_channel)
- , m_fftcpx_alpha(fftcpx_alpha)
- , m_fftcpx_iris(fftcpx_iris)
- , m_result_buff(result_buff)
- , m_kissfft_plan_fwd(kissfft_plan_fwd)
- , m_kissfft_plan_bkwd(kissfft_plan_bkwd)
- , m_dim(dim)
- , m_finished(false)
- , m_isTerminated(false) {}
-
-//------------------------------------
-
-void BokehRefThread::run() {
- // execute channel fft
- kiss_fftnd(m_kissfft_plan_fwd, m_fftcpx_channel_before, m_fftcpx_channel);
-
- // cancel check
- if (m_isTerminated) {
- m_finished = true;
- return;
- }
-
- int size = m_dim.lx * m_dim.ly;
-
- // multiply filter
- for (int i = 0; i < size; i++) {
- float re, im;
- re = m_fftcpx_channel[i].r * m_fftcpx_iris[i].r -
- m_fftcpx_channel[i].i * m_fftcpx_iris[i].i;
- im = m_fftcpx_channel[i].r * m_fftcpx_iris[i].i +
- m_fftcpx_iris[i].r * m_fftcpx_channel[i].i;
- m_fftcpx_channel[i].r = re;
- m_fftcpx_channel[i].i = im;
- }
- // execute invert fft
- kiss_fftnd(m_kissfft_plan_bkwd, m_fftcpx_channel, m_fftcpx_channel_before);
-
- // cancel check
- if (m_isTerminated) {
- m_finished = true;
- return;
- }
-
- // normal composite exposure value
- float4* result_p = m_result_buff;
- for (int i = 0; i < size; i++, result_p++) {
- // modify fft coordinate to normal
- int coord = getCoord(i, m_dim.lx, m_dim.ly);
-
- float alpha = m_fftcpx_alpha[coord].r / (float)size;
- // ignore transpalent pixels
- if (alpha == 0.0f) continue;
-
- float exposure = m_fftcpx_channel_before[coord].r / (float)size;
-
- // in case of using upper layer at all
- if (alpha >= 1.0f || (m_channel == 0 && (*result_p).x == 0.0f) ||
- (m_channel == 1 && (*result_p).y == 0.0f) ||
- (m_channel == 2 && (*result_p).z == 0.0f)) {
- // set exposure
- if (m_channel == 0) // R
- (*result_p).x = exposure;
- else if (m_channel == 1) // G
- (*result_p).y = exposure;
- else // B
- (*result_p).z = exposure;
- }
- // in case of compositing both layers
- else {
- if (m_channel == 0) // R
- {
- (*result_p).x *= 1.0f - alpha;
- (*result_p).x += exposure;
- } else if (m_channel == 1) // G
- {
- (*result_p).y *= 1.0f - alpha;
- (*result_p).y += exposure;
- } else // B
- {
- (*result_p).z *= 1.0f - alpha;
- (*result_p).z += exposure;
- }
- }
- }
- m_finished = true;
-}
-
-//============================================================
-
-//------------------------------------------------------------
-// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
-// DONE
-float Iwa_BokehRefFx::getBokehPixelAmount(const double frame,
- const TAffine affine) {
- // Convert to vector
- TPointD vect;
- vect.x = m_bokehAmount->getValue(frame);
- vect.y = 0.0;
- // Apply geometrical transformation
- TAffine aff(affine);
- aff.a13 = aff.a23 = 0; // ignore translation
- vect = aff * vect;
- // return the length of the vector
- return sqrtf((float)(vect.x * vect.x + vect.y * vect.y));
-}
-
-//------------------------------------------------------------
-// normalize the source raster image to 0-1 and set to dstMem
-// returns true if the source is (seems to be) premultiplied
-//------------------------------------------------------------
-template
-bool Iwa_BokehRefFx::setSourceRaster(const RASTER srcRas, float4* dstMem,
- TDimensionI dim) {
- bool isPremultiplied = true;
-
- float4* chann_p = dstMem;
- for (int j = 0; j < dim.ly; j++) {
- PIXEL* pix = srcRas->pixels(j);
- for (int i = 0; i < dim.lx; i++, pix++, chann_p++) {
- (*chann_p).x = (float)pix->r / (float)PIXEL::maxChannelValue;
- (*chann_p).y = (float)pix->g / (float)PIXEL::maxChannelValue;
- (*chann_p).z = (float)pix->b / (float)PIXEL::maxChannelValue;
- (*chann_p).w = (float)pix->m / (float)PIXEL::maxChannelValue;
-
- // if there is at least one pixel of which any of RGB channels has
- // higher value than alpha channel, the source image can be jusged
- // as NON-premultiplied.
- if (isPremultiplied &&
- ((*chann_p).x > (*chann_p).w || (*chann_p).y > (*chann_p).w ||
- (*chann_p).z > (*chann_p).w))
- isPremultiplied = false;
- }
- }
- return isPremultiplied;
-}
-
//------------------------------------------------------------
// normalize brightness of the depth reference image to unsigned char
// and store into detMem
@@ -203,11 +44,11 @@ void Iwa_BokehRefFx::setDepthRaster(const RASTER srcRas, unsigned char* dstMem,
PIXEL* pix = srcRas->pixels(j);
for (int i = 0; i < dim.lx; i++, pix++, depth_p++) {
// normalize brightness to 0-1
- float val = ((float)pix->r * 0.3f + (float)pix->g * 0.59f +
- (float)pix->b * 0.11f) /
- (float)PIXEL::maxChannelValue;
+ double val = ((double)pix->r * 0.3 + (double)pix->g * 0.59 +
+ (double)pix->b * 0.11) /
+ (double)PIXEL::maxChannelValue;
// convert to unsigned char
- (*depth_p) = (unsigned char)(val * (float)UCHAR_MAX + 0.5f);
+ (*depth_p) = (unsigned char)(val * (double)UCHAR_MAX + 0.5);
}
}
}
@@ -221,646 +62,18 @@ void Iwa_BokehRefFx::setDepthRasterGray(const RASTER srcRas,
PIXEL* pix = srcRas->pixels(j);
for (int i = 0; i < dim.lx; i++, pix++, depth_p++) {
// normalize brightness to 0-1
- float val = (float)pix->value / (float)PIXEL::maxChannelValue;
+ double val = (double)pix->value / (double)PIXEL::maxChannelValue;
// convert to unsigned char
- (*depth_p) = (unsigned char)(val * (float)UCHAR_MAX + 0.5f);
+ (*depth_p) = (unsigned char)(val * (double)UCHAR_MAX + 0.5);
}
}
}
-//------------------------------------------------------------
-// create the depth index map based on the histogram
-//------------------------------------------------------------
-void Iwa_BokehRefFx::defineSegemntDepth(
- const unsigned char* indexMap_main, const unsigned char* indexMap_sub,
- const float* mainSub_ratio, const unsigned char* depth_buff,
- const TDimensionI& dimOut, const double frame,
- QVector& segmentDepth_main, QVector& segmentDepth_sub) {
- QSet segmentValues;
-
- // histogram parameters
- struct HISTO {
- int pix_amount;
- int belongingSegmentValue; // value to which temporary segmented
- int segmentId;
- int segmentId_sub;
- } histo[256];
-
- // initialize
- for (int h = 0; h < 256; h++) {
- histo[h].pix_amount = 0;
- histo[h].belongingSegmentValue = -1;
- histo[h].segmentId = -1;
- }
-
- int size = dimOut.lx * dimOut.ly;
-
- // max and min
- int minHisto = (int)UCHAR_MAX;
- int maxHisto = 0;
-
- unsigned char* depth_p = (unsigned char*)depth_buff;
- for (int i = 0; i < size; i++, depth_p++) {
- histo[(int)*depth_p].pix_amount++;
- // update max and min
- if ((int)*depth_p < minHisto) minHisto = (int)*depth_p;
- if ((int)*depth_p > maxHisto) maxHisto = (int)*depth_p;
- }
-
- // the maximum and the minimum depth become the segment layers
- segmentValues.insert(minHisto);
- segmentValues.insert(maxHisto);
-
- // focus depth becomes the segment layer as well
- int focusVal = (int)(m_onFocusDistance->getValue(frame) * (double)UCHAR_MAX);
- if (minHisto < focusVal && focusVal < maxHisto)
- segmentValues.insert(focusVal);
-
- // set the initial segmentation for each depth value
- for (int h = 0; h < 256; h++) {
- for (int seg = 0; seg < segmentValues.size(); seg++) {
- // set the segment
- if (histo[h].belongingSegmentValue == -1) {
- histo[h].belongingSegmentValue = segmentValues.values().at(seg);
- continue;
- }
- // error amount at the current registered layers
- int tmpError = std::abs(h - histo[h].belongingSegmentValue);
- if (tmpError == 0) break;
- // new error amount
- int newError = std::abs(h - segmentValues.values().at(seg));
- // compare the two and update
- if (newError < tmpError)
- histo[h].belongingSegmentValue = segmentValues.values().at(seg);
- }
- }
-
- // add the segment layers to the distance precision value
- while (segmentValues.size() < m_distancePrecision->getValue()) {
- // add a new segment at the value which will reduce the error amount in
- // maximum
- double tmpMaxErrorMod = 0;
- int tmpBestNewSegVal;
- bool newSegFound = false;
- for (int h = minHisto + 1; h < maxHisto; h++) {
- // if it is already set as the segment, continue
- if (histo[h].belongingSegmentValue == h) continue;
-
- double errorModAmount = 0;
- // estimate how much the error will be reduced if the current h becomes
- // segment
- for (int i = minHisto + 1; i < maxHisto; i++) {
- // compare the current segment value and h and take the nearest value
- // if h is near (from i), then accumulate the estimated error reduction
- // amount
- if (std::abs(i - histo[i].belongingSegmentValue) >
- std::abs(i - h)) // the current segment value has
- // proirity, if the distance is the same
- errorModAmount +=
- (std::abs(i - histo[i].belongingSegmentValue) - std::abs(i - h)) *
- histo[i].pix_amount;
- }
-
- // if h will reduce the error, update the candidate segment value
- if (errorModAmount > tmpMaxErrorMod) {
- tmpMaxErrorMod = errorModAmount;
- tmpBestNewSegVal = h;
- newSegFound = true;
- }
- }
-
- if (!newSegFound) break;
-
- // register tmpBestNewSegVal to the segment values list
- segmentValues.insert(tmpBestNewSegVal);
- // std::cout << "insert " << tmpBestNewSegVal << std::endl;
-
- // update belongingSegmentValue
- for (int h = minHisto + 1; h < maxHisto; h++) {
- // compare the current segment value and h and take the nearest value
- // if tmpBestNewSegVal is near (from h), then update the
- // belongingSegmentValue
- if (std::abs(h - histo[h].belongingSegmentValue) >
- std::abs(h - tmpBestNewSegVal)) // the current segment value has
- // proirity, if the distance is the same
- histo[h].belongingSegmentValue = tmpBestNewSegVal;
- }
- }
-
- // set indices from the farthest and create the index table for each depth
- // value
- QVector segValVec;
- int tmpSegVal = -1;
- int tmpSegId = -1;
- for (int h = 255; h >= 0; h--) {
- if (histo[h].belongingSegmentValue != tmpSegVal) {
- segmentDepth_main.push_back((float)histo[h].belongingSegmentValue /
- (float)UCHAR_MAX);
- tmpSegVal = histo[h].belongingSegmentValue;
- tmpSegId++;
- segValVec.push_back(tmpSegVal);
- }
- histo[h].segmentId = tmpSegId;
- }
-
- // "sub" depth segment value list for interporation
- for (int d = 0; d < segmentDepth_main.size() - 1; d++)
- segmentDepth_sub.push_back(
- (segmentDepth_main.at(d) + segmentDepth_main.at(d + 1)) / 2.0f);
-
- // create the "sub" index table for each depth value
- tmpSegId = 0;
- for (int seg = 0; seg < segValVec.size() - 1; seg++) {
- int hMax = (seg == 0) ? 255 : segValVec.at(seg);
- int hMin = (seg == segValVec.size() - 2) ? 0 : segValVec.at(seg + 1) + 1;
- for (int h = hMax; h >= hMin; h--) histo[h].segmentId_sub = tmpSegId;
- tmpSegId++;
- }
-
- // convert the depth value to the segment index by using the index table
- depth_p = (unsigned char*)depth_buff;
- unsigned char* main_p = (unsigned char*)indexMap_main;
- unsigned char* sub_p = (unsigned char*)indexMap_sub;
- // mainSub_ratio represents the composition ratio of the image with "main"
- // separation.
- float* ratio_p = (float*)mainSub_ratio;
- for (int i = 0; i < size; i++, depth_p++, main_p++, sub_p++, ratio_p++) {
- *main_p = (unsigned char)histo[(int)*depth_p].segmentId;
- *sub_p = (unsigned char)histo[(int)*depth_p].segmentId_sub;
-
- float depth = (float)*depth_p / (float)UCHAR_MAX;
- float main_segDepth = segmentDepth_main.at(*main_p);
- float sub_segDepth = segmentDepth_sub.at(*sub_p);
-
- if (main_segDepth == sub_segDepth)
- *ratio_p = 1.0f;
- else {
- *ratio_p =
- 1.0f - (main_segDepth - depth) / (main_segDepth - sub_segDepth);
- if (*ratio_p > 1.0f) *ratio_p = 1.0f;
- if (*ratio_p < 0.0f) *ratio_p = 0.0f;
- }
- }
-}
-
-//------------------------------------------------------------
-// set the result
-//------------------------------------------------------------
-template
-void Iwa_BokehRefFx::setOutputRaster(float4* srcMem, const RASTER dstRas,
- TDimensionI dim, TDimensionI margin) {
- int out_j = 0;
- for (int j = margin.ly; j < dstRas->getLy() + margin.ly; j++, out_j++) {
- PIXEL* pix = dstRas->pixels(out_j);
- float4* chan_p = srcMem;
- chan_p += j * dim.lx + margin.lx;
- for (int i = 0; i < dstRas->getLx(); i++, pix++, chan_p++) {
- float val;
- val = (*chan_p).x * (float)PIXEL::maxChannelValue + 0.5f;
- pix->r = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
- ? (float)PIXEL::maxChannelValue
- : val);
- val = (*chan_p).y * (float)PIXEL::maxChannelValue + 0.5f;
- pix->g = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
- ? (float)PIXEL::maxChannelValue
- : val);
- val = (*chan_p).z * (float)PIXEL::maxChannelValue + 0.5f;
- pix->b = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
- ? (float)PIXEL::maxChannelValue
- : val);
- val = (*chan_p).w * (float)PIXEL::maxChannelValue + 0.5f;
- pix->m = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
- ? (float)PIXEL::maxChannelValue
- : val);
- }
- }
-}
-
-//------------------------------------------------------------
-// obtain iris size from the depth value
-//------------------------------------------------------------
-inline float Iwa_BokehRefFx::calcIrisSize(const float depth,
- const float bokehPixelAmount,
- const double onFocusDistance) {
- return ((float)onFocusDistance - depth) * bokehPixelAmount;
-}
-
-//--------------------------------------------
-// resize/invert the iris according to the size ratio
-// normalize the brightness
-// resize to the output size
-//--------------------------------------------
-void Iwa_BokehRefFx::convertIris(const float irisSize, const TRectD& irisBBox,
- const TTile& irisTile,
- const TDimensionI& dimOut,
- kiss_fft_cpx* fftcpx_iris_before) {
- // original size of the iris image
- TDimensionD irisOrgSize = irisBBox.getSize();
-
- // obtain size ratio based on width
- double irisSizeResampleRatio = irisSize / irisOrgSize.lx;
-
- // create the raster for resizing
- TDimensionD resizedIrisSize(std::abs(irisSizeResampleRatio) * irisOrgSize.lx,
- std::abs(irisSizeResampleRatio) * irisOrgSize.ly);
- // add 1 pixel margins to all sides
- TDimensionI filterSize((int)std::ceil(resizedIrisSize.lx) + 2,
- (int)std::ceil(resizedIrisSize.ly) + 2);
- TPointD resizeOffset((double)filterSize.lx - resizedIrisSize.lx,
- (double)filterSize.ly - resizedIrisSize.ly);
-
- // iris shape must be exactly at the center of the image
- if ((dimOut.lx - filterSize.lx) % 2 == 1) filterSize.lx++;
- if ((dimOut.ly - filterSize.ly) % 2 == 1) filterSize.ly++;
-
- // if the filter size becomes larger than the output size, return
- if (filterSize.lx > dimOut.lx || filterSize.ly > dimOut.ly) {
- std::cout
- << "Error: The iris filter size becomes larger than the source size!"
- << std::endl;
- return;
- }
-
- TRaster64P resizedIris(filterSize);
-
- // offset
- TAffine aff;
- TPointD affOffset(0.5, 0.5);
- affOffset += TPointD((dimOut.lx % 2 == 1) ? 0.5 : 0.0,
- (dimOut.ly % 2 == 1) ? 0.5 : 0.0);
-
- aff = TTranslation(resizedIris->getCenterD() + affOffset);
- aff *= TScale(irisSizeResampleRatio);
- aff *= TTranslation(-(irisTile.getRaster()->getCenterD() + affOffset));
-
- // resample the iris
- TRop::resample(resizedIris, irisTile.getRaster(), aff);
-
- // sum of the value
- float irisValAmount = 0.0;
-
- int iris_j = 0;
- // initialize
- for (int i = 0; i < dimOut.lx * dimOut.ly; i++) {
- fftcpx_iris_before[i].r = 0.0;
- fftcpx_iris_before[i].i = 0.0;
- }
- for (int j = (dimOut.ly - filterSize.ly) / 2; iris_j < filterSize.ly;
- j++, iris_j++) {
- TPixel64* pix = resizedIris->pixels(iris_j);
- int iris_i = 0;
- for (int i = (dimOut.lx - filterSize.lx) / 2; iris_i < filterSize.lx;
- i++, iris_i++) {
- // Value = 0.3R 0.59G 0.11B
- fftcpx_iris_before[j * dimOut.lx + i].r =
- ((float)pix->r * 0.3f + (float)pix->g * 0.59f +
- (float)pix->b * 0.11f) /
- (float)USHRT_MAX;
- irisValAmount += fftcpx_iris_before[j * dimOut.lx + i].r;
- pix++;
- }
- }
-
- // Normalize value
- for (int i = 0; i < dimOut.lx * dimOut.ly; i++)
- fftcpx_iris_before[i].r /= irisValAmount;
-}
-
-//--------------------------------------------
-// convert source image value rgb -> exposure
-//--------------------------------------------
-void Iwa_BokehRefFx::convertRGBToExposure(const float4* source_buff, int size,
- float filmGamma,
- bool sourceIsPremultiplied) {
- float4* source_p = (float4*)source_buff;
- for (int i = 0; i < size; i++, source_p++) {
- // continue if alpha channel is 0
- if ((*source_p).w == 0.0f) {
- (*source_p).x = 0.0f;
- (*source_p).y = 0.0f;
- (*source_p).z = 0.0f;
- continue;
- }
-
- // unmultiply for premultiplied source
- // this will not be applied to non-premultiplied image such as "DigiBook"
- // (digital overlay)
- if (sourceIsPremultiplied) {
- // unpremultiply
- (*source_p).x /= (*source_p).w;
- (*source_p).y /= (*source_p).w;
- (*source_p).z /= (*source_p).w;
- }
-
- // RGB value -> exposure
- (*source_p).x = pow(10, ((*source_p).x - 0.5f) / filmGamma);
- (*source_p).y = pow(10, ((*source_p).y - 0.5f) / filmGamma);
- (*source_p).z = pow(10, ((*source_p).z - 0.5f) / filmGamma);
-
- // multiply with alpha channel
- (*source_p).x *= (*source_p).w;
- (*source_p).y *= (*source_p).w;
- (*source_p).z *= (*source_p).w;
- }
-}
-
-//--------------------------------------------
-// generate the segment layer source at the current depth
-// considering fillGap and doMedian options
-//--------------------------------------------
-void Iwa_BokehRefFx::retrieveLayer(const float4* source_buff,
- const float4* segment_layer_buff,
- const unsigned char* indexMap_mainSub,
- int index, int lx, int ly, bool fillGap,
- bool doMedian, int margin) {
- // only when fillGap is ON and doMedian is OFF,
- // fill the region where will be behind of the near layers
- bool fill = (fillGap && !doMedian);
- // retrieve the regions with the current depth
- float4* source_p = (float4*)source_buff;
- float4* layer_p = (float4*)segment_layer_buff;
- unsigned char* indexMap_p = (unsigned char*)indexMap_mainSub;
- for (int i = 0; i < lx * ly; i++, source_p++, layer_p++, indexMap_p++) {
- // continue if the current pixel is at the far layer
- // consider the fill flag if the current pixel is at the near layer
- if ((int)(*indexMap_p) < index || (!fill && (int)(*indexMap_p) > index))
- continue;
-
- // copy pixel values
- (*layer_p).x = (*source_p).x;
- (*layer_p).y = (*source_p).y;
- (*layer_p).z = (*source_p).z;
- (*layer_p).w = (*source_p).w;
- }
-
- if (!fillGap || !doMedian) return;
- if (margin == 0) return;
-
- // extend pixels by using median filter
- unsigned char* generation_buff;
- TRasterGR8P generation_buff_ras = allocateRasterAndLock(
- &generation_buff, TDimensionI(lx, ly));
-
- // extend (margin * 2) pixels in order to enough cover when two adjacent
- // layers are both blurred in the maximum radius
- for (int gen = 0; gen < margin * 2; gen++) {
- // apply single median filter
- doSingleMedian(source_buff, segment_layer_buff, indexMap_mainSub, index, lx,
- ly, generation_buff, gen + 1);
- }
-
- generation_buff_ras->unlock();
-}
-
-//--------------------------------------------
-// apply single median filter
-//--------------------------------------------
-void Iwa_BokehRefFx::doSingleMedian(const float4* source_buff,
- const float4* segment_layer_buff,
- const unsigned char* indexMap_mainSub,
- int index, int lx, int ly,
- const unsigned char* generation_buff,
- int curGen) {
- float4* source_p = (float4*)source_buff;
- float4* layer_p = (float4*)segment_layer_buff;
- unsigned char* indexMap_p = (unsigned char*)indexMap_mainSub;
- unsigned char* gen_p = (unsigned char*)generation_buff;
- for (int posY = 0; posY < ly; posY++) {
- for (int posX = 0; posX < lx;
- posX++, source_p++, layer_p++, indexMap_p++, gen_p++) {
- // continue if the current pixel is at the same or far depth
- if ((int)(*indexMap_p) <= index) continue;
- // continue if the current pixel is already extended
- if ((*gen_p) > 0) continue;
-
- // check out the neighbor pixels. store brightness in neighbor[x].w
- float4 neighbor[8];
- int neighbor_amount = 0;
- for (int ky = posY - 1; ky <= posY + 1; ky++) {
- for (int kx = posX - 1; kx <= posX + 1; kx++) {
- // skip the current pixel itself
- if (kx == posX && ky == posY) continue;
- // border condition
- if (ky < 0 || ky >= ly || kx < 0 || kx >= lx) continue;
- // index in the buffer
- int neighborId = ky * lx + kx;
-
- if ((int)indexMap_mainSub[neighborId] !=
- index && // pixels from the original image can be used as
- // neighbors
- (generation_buff[neighborId] == 0 || // pixels which is not yet
- // be extended cannot be
- // used as neighbors
- generation_buff[neighborId] == curGen)) // pixels which is
- // extended in the
- // current median
- // generation cannot be
- // used as neighbors
- continue;
- // compute brightness (actually, it is "pseudo" brightness
- // since the source buffer is already converted to exposure)
- float brightness = source_buff[neighborId].x * 0.3f +
- source_buff[neighborId].y * 0.59f +
- source_buff[neighborId].z * 0.11f;
- // insert with sorting
- int ins_index;
- for (ins_index = 0; ins_index < neighbor_amount; ins_index++) {
- if (neighbor[ins_index].w < brightness) break;
- }
- // displace neighbor values from neighbor_amount-1 to ins_index
- for (int k = neighbor_amount - 1; k >= ins_index; k--) {
- neighbor[k + 1].x = neighbor[k].x;
- neighbor[k + 1].y = neighbor[k].y;
- neighbor[k + 1].z = neighbor[k].z;
- neighbor[k + 1].w = neighbor[k].w;
- }
- // set the neighbor value
- neighbor[ins_index].x = source_buff[neighborId].x;
- neighbor[ins_index].y = source_buff[neighborId].y;
- neighbor[ins_index].z = source_buff[neighborId].z;
- neighbor[ins_index].w = brightness;
-
- // increment the count
- neighbor_amount++;
- }
- }
-
- // If there is no neighbor pixles available, continue
- if (neighbor_amount == 0) continue;
-
- // switch the behavior when there are even number of neighbors
- bool flag = ((posX + posY) % 2 == 0);
- // pick up the medium index
- int pickIndex = (flag)
- ? (int)std::floor((float)(neighbor_amount - 1) / 2.0f)
- : (int)std::ceil((float)(neighbor_amount - 1) / 2.0f);
-
- // set the medium pixel values
- (*layer_p).x = neighbor[pickIndex].x;
- (*layer_p).y = neighbor[pickIndex].y;
- (*layer_p).z = neighbor[pickIndex].z;
- (*layer_p).w = (*source_p).w;
-
- // set the generation
- (*gen_p) = (unsigned char)curGen;
- }
- }
-}
-
-//--------------------------------------------
-// normal-composite the layer as is, without filtering
-//--------------------------------------------
-void Iwa_BokehRefFx::compositeAsIs(const float4* segment_layer_buff,
- const float4* result_buff_mainSub,
- int size) {
- float4* layer_p = (float4*)segment_layer_buff;
- float4* result_p = (float4*)result_buff_mainSub;
- for (int i = 0; i < size; i++, layer_p++, result_p++) {
- // in case the pixel is full opac
- if ((*layer_p).w == 1.0f) {
- (*result_p).x = (*layer_p).x;
- (*result_p).y = (*layer_p).y;
- (*result_p).z = (*layer_p).z;
- (*result_p).w = 1.0f;
- continue;
- }
- // in case the pixel is full transparent
- else if ((*layer_p).w == 0.0f)
- continue;
- // in case the pixel is semi-transparent, do normal composite
- else {
- (*result_p).x = (*layer_p).x + (*result_p).x * (1.0f - (*layer_p).w);
- (*result_p).y = (*layer_p).y + (*result_p).y * (1.0f - (*layer_p).w);
- (*result_p).z = (*layer_p).z + (*result_p).z * (1.0f - (*layer_p).w);
- (*result_p).w = (*layer_p).w + (*result_p).w * (1.0f - (*layer_p).w);
- }
- }
-}
-
-//--------------------------------------------
-// retrieve segment layer image for each channel
-//--------------------------------------------
-void Iwa_BokehRefFx::retrieveChannel(const float4* segment_layer_buff, // src
- kiss_fft_cpx* fftcpx_r_before, // dst
- kiss_fft_cpx* fftcpx_g_before, // dst
- kiss_fft_cpx* fftcpx_b_before, // dst
- kiss_fft_cpx* fftcpx_a_before, // dst
- int size) {
- float4* layer_p = (float4*)segment_layer_buff;
- for (int i = 0; i < size; i++, layer_p++) {
- fftcpx_r_before[i].r = (*layer_p).x;
- fftcpx_g_before[i].r = (*layer_p).y;
- fftcpx_b_before[i].r = (*layer_p).z;
- fftcpx_a_before[i].r = (*layer_p).w;
- }
-}
-
-//--------------------------------------------
-// multiply filter on channel
-//--------------------------------------------
-void Iwa_BokehRefFx::multiplyFilter(kiss_fft_cpx* fftcpx_channel, // dst
- kiss_fft_cpx* fftcpx_iris, // filter
- int size) {
- for (int i = 0; i < size; i++) {
- float re, im;
- re = fftcpx_channel[i].r * fftcpx_iris[i].r -
- fftcpx_channel[i].i * fftcpx_iris[i].i;
- im = fftcpx_channel[i].r * fftcpx_iris[i].i +
- fftcpx_channel[i].i * fftcpx_iris[i].r;
-
- fftcpx_channel[i].r = re;
- fftcpx_channel[i].i = im;
- }
-}
-
-//--------------------------------------------
-// normal comosite the alpha channel
-//--------------------------------------------
-void Iwa_BokehRefFx::compositeAlpha(const float4* result_buff, // dst
- const kiss_fft_cpx* fftcpx_alpha, // alpha
- int lx, int ly) {
- int size = lx * ly;
- float4* result_p = (float4*)result_buff;
- for (int i = 0; i < size; i++, result_p++) {
- // modify fft coordinate to normal
- float alpha = fftcpx_alpha[getCoord(i, lx, ly)].r / (float)size;
-
- if ((*result_p).w < 1.0f) {
- if (alpha >= 1.0f)
- (*result_p).w = 1.0f;
- else
- (*result_p).w = alpha + ((*result_p).w * (1.0f - alpha));
- }
- }
-}
-
-//--------------------------------------------
-// interpolate main and sub exposures
-// convert exposure -> value (0-1)
-// set to the result
-//--------------------------------------------
-void Iwa_BokehRefFx::interpolateExposureAndConvertToRGB(
- const float4* result_main_buff, // result1
- const float4* result_sub_buff, // result2
- const float* mainSub_ratio, // ratio
- float filmGamma,
- const float4* source_buff, // dst
- int size) {
- float4* resultMain_p = (float4*)result_main_buff;
- float4* resultSub_p = (float4*)result_sub_buff;
- float* ratio_p = (float*)mainSub_ratio;
- float4* out_p = (float4*)source_buff;
- for (int i = 0; i < size;
- i++, resultMain_p++, resultSub_p++, ratio_p++, out_p++) {
- // interpolate main and sub exposures
- float4 result;
-
- result.x =
- (*resultMain_p).x * (*ratio_p) + (*resultSub_p).x * (1.0f - (*ratio_p));
- result.y =
- (*resultMain_p).y * (*ratio_p) + (*resultSub_p).y * (1.0f - (*ratio_p));
- result.z =
- (*resultMain_p).z * (*ratio_p) + (*resultSub_p).z * (1.0f - (*ratio_p));
- result.w =
- (*resultMain_p).w * (*ratio_p) + (*resultSub_p).w * (1.0f - (*ratio_p));
-
- (*out_p).w = result.w;
-
- // convert exposure -> value (0-1)
-
- // continue for transparent pixel
- if (result.w == 0.0f) {
- (*out_p).x = 0.0f;
- (*out_p).y = 0.0f;
- (*out_p).z = 0.0f;
- continue;
- }
-
- // convert Exposure to value
- result.x = log10(result.x) * filmGamma + 0.5f;
- result.y = log10(result.y) * filmGamma + 0.5f;
- result.z = log10(result.z) * filmGamma + 0.5f;
-
- (*out_p).x =
- (result.x > 1.0f) ? 1.0f : ((result.x < 0.0f) ? 0.0f : result.x);
- (*out_p).y =
- (result.y > 1.0f) ? 1.0f : ((result.y < 0.0f) ? 0.0f : result.y);
- (*out_p).z =
- (result.z > 1.0f) ? 1.0f : ((result.z < 0.0f) ? 0.0f : result.z);
- }
-}
-
//--------------------------------------------
Iwa_BokehRefFx::Iwa_BokehRefFx()
- : m_onFocusDistance(0.5)
- , m_bokehAmount(30.0)
- , m_hardness(0.3)
- , m_distancePrecision(10)
- , m_fillGap(true)
- , m_doMedian(true) {
+ : m_distancePrecision(10), m_fillGap(true), m_doMedian(true) {
// Bind parameters
- addInputPort("Iris", m_iris);
addInputPort("Source", m_source);
addInputPort("Depth", m_depth);
@@ -871,11 +84,6 @@ Iwa_BokehRefFx::Iwa_BokehRefFx()
bindParam(this, "fill_gap", m_fillGap, false);
bindParam(this, "fill_gap_with_median_filter", m_doMedian, false);
- // Set the ranges of parameters
- m_onFocusDistance->setValueRange(0, 1);
- m_bokehAmount->setValueRange(0, 300);
- m_bokehAmount->setMeasureName("fxLength");
- m_hardness->setValueRange(0.05, 20.0);
m_distancePrecision->setValueRange(3, 128);
}
@@ -893,16 +101,17 @@ void Iwa_BokehRefFx::doCompute(TTile& tile, double frame,
QList rasterList;
// Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
- float bokehPixelAmount = getBokehPixelAmount(frame, settings.m_affine);
+ double bokehPixelAmount = BokehUtils::getBokehPixelAmount(
+ m_bokehAmount->getValue(frame), settings.m_affine);
// Obtain the larger size of bokeh between the nearest (black) point and
// the farthest (white) point, based on the focus distance.
double onFocusDistance = m_onFocusDistance->getValue(frame);
- float maxIrisSize =
+ double maxIrisSize =
bokehPixelAmount * std::max((1.0 - onFocusDistance), onFocusDistance);
int margin =
- (maxIrisSize > 1.0f) ? (int)(std::ceil((maxIrisSize - 1.0f) / 2.0f)) : 0;
+ (maxIrisSize > 1.0f) ? (int)(std::ceil((maxIrisSize - 1.0) / 2.0)) : 0;
// Range of computation
TRectD rectOut(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
@@ -932,105 +141,16 @@ void Iwa_BokehRefFx::doCompute(TTile& tile, double frame,
// - - - Compute the input tiles - - -
- // Automatically judge whether the source image is premultiplied or not
- bool isPremultiplied;
-
// source image buffer
- float4* source_buff;
- rasterList.append(allocateRasterAndLock(&source_buff, dimOut));
+ // double4* source_buff;
+ // rasterList.append(allocateRasterAndLock(&source_buff, dimOut));
- {
- // source tile is used only in this focus.
- // normalized source image data is stored in source_buff.
- TTile sourceTile;
- m_source->allocateAndCompute(sourceTile, rectOut.getP00(), dimOut,
- tile.getRaster(), frame, settings);
- // normalize the tile image to 0-1 and set to source_buff
- TRaster32P ras32 = (TRaster32P)sourceTile.getRaster();
- TRaster64P ras64 = (TRaster64P)sourceTile.getRaster();
- lock.lockForRead();
- if (ras32)
- isPremultiplied =
- setSourceRaster(ras32, source_buff, dimOut);
- else if (ras64)
- isPremultiplied =
- setSourceRaster(ras64, source_buff, dimOut);
- lock.unlock();
- }
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- tile.getRaster()->clear();
- return;
- }
-
- // create the index map, which indicates which layer each pixel belongs to
- // make two separations and interporate the results in order to avoid
- // artifacts appear at the layer border
- unsigned char* indexMap_main;
- unsigned char* indexMap_sub;
- rasterList.append(
- allocateRasterAndLock(&indexMap_main, dimOut));
- rasterList.append(
- allocateRasterAndLock(&indexMap_sub, dimOut));
-
- // interporation ratio between two results
- float* mainSub_ratio;
- rasterList.append(allocateRasterAndLock(&mainSub_ratio, dimOut));
-
- // - - - depth segmentation - - -
- QVector segmentDepth_main;
- QVector segmentDepth_sub;
- {
- // depth image stored in 256 levels
- unsigned char* depth_buff;
- TRasterGR8P depth_buff_ras =
- allocateRasterAndLock(&depth_buff, dimOut);
- {
- TTile depthTile;
- m_depth->allocateAndCompute(depthTile, rectOut.getP00(), dimOut,
- tile.getRaster(), frame, settings);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- depth_buff_ras->unlock();
- tile.getRaster()->clear();
- return;
- }
-
- // normalize brightness of the depth reference image to unsigned char
- // and store into depth_buff
- TRasterGR8P rasGR8 = (TRasterGR8P)depthTile.getRaster();
- TRasterGR16P rasGR16 = (TRasterGR16P)depthTile.getRaster();
- TRaster32P ras32 = (TRaster32P)depthTile.getRaster();
- TRaster64P ras64 = (TRaster64P)depthTile.getRaster();
- lock.lockForRead();
- if (rasGR8)
- setDepthRasterGray(rasGR8, depth_buff, dimOut);
- else if (rasGR16)
- setDepthRasterGray(rasGR16, depth_buff,
- dimOut);
- else if (ras32)
- setDepthRaster(ras32, depth_buff, dimOut);
- else if (ras64)
- setDepthRaster(ras64, depth_buff, dimOut);
- lock.unlock();
- }
- // create the depth index map
- defineSegemntDepth(indexMap_main, indexMap_sub, mainSub_ratio, depth_buff,
- dimOut, frame, segmentDepth_main, segmentDepth_sub);
- // depth image is not needed anymore. release it
- depth_buff_ras->unlock();
- }
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- tile.getRaster()->clear();
- return;
- }
+ LayerValue layerValue;
+ // source tile is used only in this focus.
+ // normalized source image data is stored in source_buff.
+ layerValue.sourceTile = new TTile();
+ m_source->allocateAndCompute(*layerValue.sourceTile, rectOut.getP00(), dimOut,
+ tile.getRaster(), frame, settings);
// - - - iris image - - -
// Get the original size of Iris image
@@ -1051,363 +171,62 @@ void Iwa_BokehRefFx::doCompute(TTile& tile, double frame,
return;
}
- // for now only CPU computation is supported
- // when introduced GPGPU capability, computation will be separated here
- doCompute_CPU(frame, settings, bokehPixelAmount, maxIrisSize, margin, dimOut,
- source_buff, indexMap_main, indexMap_sub, mainSub_ratio,
- segmentDepth_main, segmentDepth_sub, irisTile, irisBBox,
- isPremultiplied);
+ // compute the reference image
+ std::vector ctrl_rasters; // to be stored in uchar
+ QMap
+ ctrls; // container of [port number, reference image buffer in uchar]
+ unsigned char* depth_buff;
+ rasterList.append(allocateRasterAndLock(&depth_buff, dimOut));
+ {
+ TTile depthTile;
+ m_depth->allocateAndCompute(depthTile, rectOut.getP00(), dimOut,
+ tile.getRaster(), frame, settings);
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- tile.getRaster()->clear();
- return;
- }
-
- TDimensionI actualMargin((dimOut.lx - tile.getRaster()->getSize().lx) / 2,
- (dimOut.ly - tile.getRaster()->getSize().ly) / 2);
- // clear the tile raster
- tile.getRaster()->clear();
- TRaster32P outRas32 = (TRaster32P)tile.getRaster();
- TRaster64P outRas64 = (TRaster64P)tile.getRaster();
- lock.lockForWrite();
- if (outRas32)
- setOutputRaster(source_buff, outRas32, dimOut,
- actualMargin);
- else if (outRas64)
- setOutputRaster(source_buff, outRas64, dimOut,
- actualMargin);
- lock.unlock();
-
- // release all rasters
- releaseAllRasters(rasterList);
-}
-
-//--------------------------------------------
-
-void Iwa_BokehRefFx::doCompute_CPU(
- const double frame, const TRenderSettings& settings, float bokehPixelAmount,
- float maxIrisSize, int margin, TDimensionI& dimOut, float4* source_buff,
- unsigned char* indexMap_main, unsigned char* indexMap_sub,
- float* mainSub_ratio, QVector& segmentDepth_main,
- QVector& segmentDepth_sub, TTile& irisTile, TRectD& irisBBox,
- bool sourceIsPremultiplied) {
- QList rasterList;
- QList planList;
-
- // This fx is relatively heavy so the multi thread computation is introduced.
- // Lock the mutex here in order to prevent multiple rendering tasks run at the
- // same time.
- QMutexLocker fx_locker(&fx_mutex);
-
- // - - - memory allocation for FFT - - -
-
- // iris image
- kiss_fft_cpx* fftcpx_iris_before;
- kiss_fft_cpx* fftcpx_iris;
- rasterList.append(
- allocateRasterAndLock(&fftcpx_iris_before, dimOut));
- rasterList.append(allocateRasterAndLock(&fftcpx_iris, dimOut));
-
- // segment layers
- float4* segment_layer_buff;
- rasterList.append(allocateRasterAndLock(&segment_layer_buff, dimOut));
-
- // alpha channel
- kiss_fft_cpx* fftcpx_alpha_before;
- kiss_fft_cpx* fftcpx_alpha;
- rasterList.append(
- allocateRasterAndLock(&fftcpx_alpha_before, dimOut));
- rasterList.append(allocateRasterAndLock(&fftcpx_alpha, dimOut));
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- return;
- }
-
- // RGB channels
- kiss_fft_cpx* fftcpx_r_before;
- kiss_fft_cpx* fftcpx_g_before;
- kiss_fft_cpx* fftcpx_b_before;
- kiss_fft_cpx* fftcpx_r;
- kiss_fft_cpx* fftcpx_g;
- kiss_fft_cpx* fftcpx_b;
- rasterList.append(
- allocateRasterAndLock(&fftcpx_r_before, dimOut));
- rasterList.append(
- allocateRasterAndLock(&fftcpx_g_before, dimOut));
- rasterList.append(
- allocateRasterAndLock(&fftcpx_b_before, dimOut));
- rasterList.append(allocateRasterAndLock(&fftcpx_r, dimOut));
- rasterList.append(allocateRasterAndLock(&fftcpx_g, dimOut));
- rasterList.append(allocateRasterAndLock(&fftcpx_b, dimOut));
-
- // for accumulating result image
- float4* result_main_buff;
- float4* result_sub_buff;
- rasterList.append(allocateRasterAndLock(&result_main_buff, dimOut));
- rasterList.append(allocateRasterAndLock(&result_sub_buff, dimOut));
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRasters(rasterList);
- return;
- }
-
- // fft plans
- int dims[2] = {dimOut.ly, dimOut.lx};
- kiss_fftnd_cfg kissfft_plan_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
- kiss_fftnd_cfg kissfft_plan_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
- planList.append(kissfft_plan_fwd);
- planList.append(kissfft_plan_bkwd);
-
- kiss_fftnd_cfg kissfft_plan_r_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
- kiss_fftnd_cfg kissfft_plan_r_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
- kiss_fftnd_cfg kissfft_plan_g_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
- kiss_fftnd_cfg kissfft_plan_g_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
- kiss_fftnd_cfg kissfft_plan_b_fwd = kiss_fftnd_alloc(dims, 2, false, 0, 0);
- kiss_fftnd_cfg kissfft_plan_b_bkwd = kiss_fftnd_alloc(dims, 2, true, 0, 0);
- planList.append(kissfft_plan_r_fwd);
- planList.append(kissfft_plan_r_bkwd);
- planList.append(kissfft_plan_g_fwd);
- planList.append(kissfft_plan_g_bkwd);
- planList.append(kissfft_plan_b_fwd);
- planList.append(kissfft_plan_b_bkwd);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- int size = dimOut.lx * dimOut.ly;
-
- // initialize result memory
- memset(result_main_buff, 0, sizeof(float4) * size);
- memset(result_sub_buff, 0, sizeof(float4) * size);
-
- // obtain parameters
- float filmGamma = (float)m_hardness->getValue(frame);
- bool fillGap = m_fillGap->getValue();
- bool doMedian = m_doMedian->getValue();
-
- // convert source image value rgb -> exposure
- convertRGBToExposure(source_buff, size, filmGamma, sourceIsPremultiplied);
-
- // compute twice (main and sub) for interpolation
- for (int mainSub = 0; mainSub < 2; mainSub++) {
// cancel check
if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
+ releaseAllRasters(rasterList);
+ tile.getRaster()->clear();
return;
}
- float4* result_buff_mainSub;
- QVector segmentDepth_mainSub;
- unsigned char* indexMap_mainSub;
- if (mainSub == 0) // main process
- {
- result_buff_mainSub = result_main_buff;
- segmentDepth_mainSub = segmentDepth_main;
- indexMap_mainSub = indexMap_main;
- } else // sub process
- {
- result_buff_mainSub = result_sub_buff;
- segmentDepth_mainSub = segmentDepth_sub;
- indexMap_mainSub = indexMap_sub;
- }
-
- // Compute from the most distant segment layer
- for (int index = 0; index < segmentDepth_mainSub.size(); index++) {
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // obtain iris size from the depth value
- float irisSize =
- calcIrisSize(segmentDepth_mainSub.at(index), bokehPixelAmount,
- m_onFocusDistance->getValue(frame));
-
- // initialize the layer memory
- memset(segment_layer_buff, 0, sizeof(float4) * size);
- // generate the segment layer source at the current depth
- // considering fillGap and doMedian options
- retrieveLayer(source_buff, segment_layer_buff, indexMap_mainSub, index,
- dimOut.lx, dimOut.ly, fillGap, doMedian,
- (index == segmentDepth_mainSub.size() - 1) ? 0 : margin);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // if the layer is at (almost) the focus position
- if (-1.0 <= irisSize && 1.0 >= irisSize) {
- // normal-composite the layer as is, without filtering
- compositeAsIs(segment_layer_buff, result_buff_mainSub, size);
- // continue to next layer
- continue;
- }
-
- // resize the iris image
-
- // resize/invert the iris according to the size ratio
- // normalize the brightness
- // resize to the output size
- convertIris(irisSize, irisBBox, irisTile, dimOut, fftcpx_iris_before);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // Do FFT the iris image.
- kiss_fftnd(kissfft_plan_fwd, fftcpx_iris_before, fftcpx_iris);
- // fftwf_execute_dft(fftw_plan_fwd_r, iris_host, iris_host);
-
- // initialize alpha
- memset(fftcpx_alpha_before, 0, sizeof(kiss_fft_cpx) * size);
- // initialize channels
- memset(fftcpx_r_before, 0, sizeof(kiss_fft_cpx) * size);
- memset(fftcpx_g_before, 0, sizeof(kiss_fft_cpx) * size);
- memset(fftcpx_b_before, 0, sizeof(kiss_fft_cpx) * size);
-
- // retrieve segment layer image for each channel
- retrieveChannel(segment_layer_buff, // src
- fftcpx_r_before, // dst
- fftcpx_g_before, // dst
- fftcpx_b_before, // dst
- fftcpx_alpha_before, // dst
- size);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // forward fft of alpha channel
- kiss_fftnd(kissfft_plan_fwd, fftcpx_alpha_before, fftcpx_alpha);
- // fftwf_execute_dft(fftw_plan_fwd_r, alphaBokeh_host, alphaBokeh_host);
-
- // multiply filter on alpha
- multiplyFilter(fftcpx_alpha, // dst
- fftcpx_iris, // filter
- size);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // inverse fft the alpha channel
- // note that the result is multiplied by the image size
- kiss_fftnd(kissfft_plan_bkwd, fftcpx_alpha, fftcpx_alpha_before);
- // fftwf_execute_dft(fftw_plan_bkwd_r, alphaBokeh_host, alphaBokeh_host);
-
- // normal composite the alpha channel
- compositeAlpha(result_buff_mainSub, // dst
- fftcpx_alpha_before, // alpha
- dimOut.lx, dimOut.ly);
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
-
- // create worker threads
- BokehRefThread threadR(0, fftcpx_r_before, fftcpx_r, fftcpx_alpha_before,
- fftcpx_iris, result_buff_mainSub,
- kissfft_plan_r_fwd, kissfft_plan_r_bkwd, dimOut);
- BokehRefThread threadG(1, fftcpx_g_before, fftcpx_g, fftcpx_alpha_before,
- fftcpx_iris, result_buff_mainSub,
- kissfft_plan_g_fwd, kissfft_plan_g_bkwd, dimOut);
- BokehRefThread threadB(2, fftcpx_b_before, fftcpx_b, fftcpx_alpha_before,
- fftcpx_iris, result_buff_mainSub,
- kissfft_plan_b_fwd, kissfft_plan_b_bkwd, dimOut);
-
- // If you set this flag to true, the fx will be forced to compute in
- // single
- // thread.
- // Under some specific condition (such as calling from single-threaded
- // tcomposer)
- // we may need to use this flag... For now, I'll keep this option unused.
- // TODO: investigate this.
- bool renderInSingleThread = false;
-
- if (renderInSingleThread) {
- threadR.run();
- threadG.run();
- threadB.run();
- } else {
- threadR.start();
- threadG.start();
- threadB.start();
- int waitCount = 0;
- while (1) {
- if ((settings.m_isCanceled && *settings.m_isCanceled) ||
- waitCount >= 2000) // 100 second timeout
- {
- if (!threadR.isFinished()) threadR.terminateThread();
- if (!threadG.isFinished()) threadG.terminateThread();
- if (!threadB.isFinished()) threadB.terminateThread();
- while (!threadR.isFinished() || !threadG.isFinished() ||
- !threadB.isFinished()) {
- }
- releaseAllRastersAndPlans(rasterList, planList);
- return;
- }
- if (threadR.isFinished() && threadG.isFinished() &&
- threadB.isFinished())
- break;
- QThread::msleep(50);
- waitCount++;
- }
- }
-
- } // for each layer
- } // for main and sub
-
- // cancel check
- if (settings.m_isCanceled && *settings.m_isCanceled) {
- releaseAllRastersAndPlans(rasterList, planList);
- return;
+ // normalize brightness of the depth reference image to unsigned char
+ // and store into depth_buff
+ TRasterGR8P rasGR8 = (TRasterGR8P)depthTile.getRaster();
+ TRasterGR16P rasGR16 = (TRasterGR16P)depthTile.getRaster();
+ TRaster32P ras32 = (TRaster32P)depthTile.getRaster();
+ TRaster64P ras64 = (TRaster64P)depthTile.getRaster();
+ lock.lockForRead();
+ if (rasGR8)
+ setDepthRasterGray(rasGR8, depth_buff, dimOut);
+ else if (rasGR16)
+ setDepthRasterGray(rasGR16, depth_buff, dimOut);
+ else if (ras32)
+ BokehUtils::setDepthRaster(ras32, depth_buff,
+ dimOut);
+ else if (ras64)
+ BokehUtils::setDepthRaster(ras64, depth_buff,
+ dimOut);
+ lock.unlock();
}
+ ctrls[1] = depth_buff;
- // interpolate main and sub exposures
- // convert exposure -> RGB (0-1)
- // set to the result
- interpolateExposureAndConvertToRGB(result_main_buff, // result1
- result_sub_buff, // result2
- mainSub_ratio, // ratio
- filmGamma,
- source_buff, // dst
- size);
+ layerValue.premultiply = 2; // auto
+ layerValue.layerHardness = m_hardness->getValue(frame);
+ layerValue.depth_ref = 1;
+ layerValue.distance = 0.5;
+ layerValue.bokehAdjustment = 1.0;
+ layerValue.depthRange = 1.0;
+ layerValue.distancePrecision = m_distancePrecision->getValue();
+ layerValue.fillGap = m_fillGap->getValue();
+ layerValue.doMedian = m_doMedian->getValue();
+ QList layerValues;
+ layerValues.append(layerValue);
- // release rasters and plans
- releaseAllRastersAndPlans(rasterList, planList);
-}
+ Iwa_BokehCommonFx::doFx(tile, frame, settings, bokehPixelAmount, margin,
+ dimOut, irisBBox, irisTile, layerValues, ctrls);
-//--------------------------------------------
-
-bool Iwa_BokehRefFx::doGetBBox(double frame, TRectD& bBox,
- const TRenderSettings& info) {
- bBox = TConsts::infiniteRectD;
- return true;
-}
-
-//--------------------------------------------
-
-bool Iwa_BokehRefFx::canHandle(const TRenderSettings& info, double frame) {
- return false;
+ releaseAllRasters(rasterList);
+ delete layerValue.sourceTile;
}
FX_PLUGIN_IDENTIFIER(Iwa_BokehRefFx, "iwa_BokehRefFx")
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bokehreffx.h b/toonz/sources/stdfx/iwa_bokehreffx.h
index 88b99e76..653a2251 100644
--- a/toonz/sources/stdfx/iwa_bokehreffx.h
+++ b/toonz/sources/stdfx/iwa_bokehreffx.h
@@ -21,56 +21,17 @@ distributed with a 3-clause BSD-style license.
#include
#include "tools/kiss_fftnd.h"
-
-struct float4 {
- float x, y, z, w;
-};
+#include "iwa_bokeh_util.h"
//------------------------------------
-class BokehRefThread : public QThread {
- int m_channel;
- volatile bool m_finished;
-
- kiss_fft_cpx* m_fftcpx_channel_before;
- kiss_fft_cpx* m_fftcpx_channel;
- kiss_fft_cpx* m_fftcpx_alpha;
- kiss_fft_cpx* m_fftcpx_iris;
- float4* m_result_buff;
-
- kiss_fftnd_cfg m_kissfft_plan_fwd, m_kissfft_plan_bkwd;
-
- TDimensionI m_dim;
- bool m_isTerminated;
-
-public:
- BokehRefThread(int channel, kiss_fft_cpx* fftcpx_channel_before,
- kiss_fft_cpx* fftcpx_channel, kiss_fft_cpx* fftcpx_alpha,
- kiss_fft_cpx* fftcpx_iris, float4* result_buff,
- kiss_fftnd_cfg kissfft_plan_fwd,
- kiss_fftnd_cfg kissfft_plan_bkwd, TDimensionI& dim);
-
- void run() override;
-
- bool isFinished() { return m_finished; }
- void terminateThread() { m_isTerminated = true; }
-};
-
-//------------------------------------
-
-class Iwa_BokehRefFx : public TStandardRasterFx {
+class Iwa_BokehRefFx : public Iwa_BokehCommonFx {
FX_PLUGIN_DECLARATION(Iwa_BokehRefFx)
protected:
- TRasterFxPort m_iris; // iris image
TRasterFxPort m_source; // source image
TRasterFxPort m_depth; // depth reference image
- TDoubleParamP m_onFocusDistance; // Focus Distance (0-1)
- TDoubleParamP m_bokehAmount; // The maximum bokeh size. The size of bokeh at
- // the layer separated by 1.0 from the focal
- // position
- TDoubleParamP m_hardness; // Film gamma
TIntParamP m_distancePrecision; // Separation of depth image
TBoolParamP m_fillGap; // Toggles whether to extend pixels behind the front
@@ -81,14 +42,6 @@ protected:
TBoolParamP m_doMedian; // (Effective only when the Fill Gap option is ON)
// Toggles whether to use Median Filter for extending the pixels.
- // Get the pixel size of bokehAmount ( referenced ino_blur.cpp )
- float getBokehPixelAmount(const double frame, const TAffine affine);
-
- // normalize the source raster image to 0-1 and set to dstMem
- // returns true if the source is (seems to be) premultiplied
- template
- bool setSourceRaster(const RASTER srcRas, float4* dstMem, TDimensionI dim);
-
// normalize brightness of the depth reference image to unsigned char
// and store into dstMem
template
@@ -98,97 +51,11 @@ protected:
void setDepthRasterGray(const RASTER srcRas, unsigned char* dstMem,
TDimensionI dim);
- // create the depth index map
- void defineSegemntDepth(const unsigned char* indexMap_main,
- const unsigned char* indexMap_sub,
- const float* mainSub_ratio,
- const unsigned char* depth_buff,
- const TDimensionI& dimOut, const double frame,
- QVector& segmentDepth_main,
- QVector& segmentDepth_sub);
-
- // set the result
- template
- void setOutputRaster(float4* srcMem, const RASTER dstRas, TDimensionI dim,
- TDimensionI margin);
-
- // obtain iris size from the depth value
- float calcIrisSize(const float depth, const float bokehPixelAmount,
- const double onFocusDistance);
-
- // resize/invert the iris according to the size ratio
- // normalize the brightness
- // resize to the output size
- void convertIris(const float irisSize, const TRectD& irisBBox,
- const TTile& irisTile, const TDimensionI& enlargedDim,
- kiss_fft_cpx* fftcpx_iris_before);
-
- // convert source image value rgb -> exposure
- void convertRGBToExposure(const float4* source_buff, int size,
- float filmGamma, bool sourceIsPremultiplied);
-
- // generate the segment layer source at the current depth
- // considering fillGap and doMedian options
- void retrieveLayer(const float4* source_buff,
- const float4* segment_layer_buff,
- const unsigned char* indexMap_mainSub, int index, int lx,
- int ly, bool fillGap, bool doMedian, int margin);
-
- // apply single median filter
- void doSingleMedian(const float4* source_buff,
- const float4* segment_layer_buff,
- const unsigned char* indexMap_mainSub, int index, int lx,
- int ly, const unsigned char* generation_buff, int curGen);
-
- // normal-composite the layer as is, without filtering
- void compositeAsIs(const float4* segment_layer_buff,
- const float4* result_buff_mainSub, int size);
-
- // retrieve segment layer image for each channel
- void retrieveChannel(const float4* segment_layer_buff, // src
- kiss_fft_cpx* fftcpx_r_before, // dst
- kiss_fft_cpx* fftcpx_g_before, // dst
- kiss_fft_cpx* fftcpx_b_before, // dst
- kiss_fft_cpx* fftcpx_a_before, // dst
- int size);
-
- // multiply filter on channel
- void multiplyFilter(kiss_fft_cpx* fftcpx_channel, // dst
- kiss_fft_cpx* fftcpx_iris, // filter
- int size);
-
- // normal comosite the alpha channel
- void compositeAlpha(const float4* result_buff, // dst
- const kiss_fft_cpx* fftcpx_alpha, // alpha
- int lx, int ly);
-
- // interpolate main and sub exposures
- // convert exposure -> RGB (0-1)
- // set to the result
- void interpolateExposureAndConvertToRGB(
- const float4* result_main_buff, // result1
- const float4* result_sub_buff, // result2
- const float* mainSub_ratio, // ratio
- float filmGamma,
- const float4* source_buff, // dst
- int size);
-
public:
Iwa_BokehRefFx();
- void doCompute(TTile& tile, double frame, const TRenderSettings& settings) override;
-
- bool doGetBBox(double frame, TRectD& bBox, const TRenderSettings& info) override;
-
- bool canHandle(const TRenderSettings& info, double frame) override;
-
- void doCompute_CPU(const double frame, const TRenderSettings& settings,
- float bokehPixelAmount, float maxIrisSize, int margin,
- TDimensionI& dimOut, float4* source_buff,
- unsigned char* indexMap_main, unsigned char* indexMap_sub,
- float* mainSub_ratio, QVector& segmentDepth_main,
- QVector& segmentDepth_sub, TTile& irisTile,
- TRectD& irisBBox, bool sourceIsPremultiplied);
+ void doCompute(TTile& tile, double frame,
+ const TRenderSettings& settings) override;
};
#endif
diff --git a/toonz/sources/toonzqt/fxsettings.cpp b/toonz/sources/toonzqt/fxsettings.cpp
index d1362534..6a175d21 100644
--- a/toonz/sources/toonzqt/fxsettings.cpp
+++ b/toonz/sources/toonzqt/fxsettings.cpp
@@ -43,6 +43,8 @@
#include
#include
+#include
+#include
using namespace DVGui;
@@ -249,7 +251,6 @@ void ParamsPage::setPageField(TIStream &is, const TFxP &fx, bool isVertical) {
if (shrinkStr != "" || modeSensitiveStr != "") {
QWidget *tmpWidget;
if (shrinkStr != "") {
- tmpWidget = new QWidget(this);
shrink = QString::fromStdString(shrinkStr).toInt();
std::string label = is.getTagAttribute("label");
QCheckBox *checkBox = new QCheckBox(this);
@@ -262,9 +263,7 @@ void ParamsPage::setPageField(TIStream &is, const TFxP &fx, bool isVertical) {
int currentRow = m_mainLayout->rowCount();
m_mainLayout->addLayout(sepLay, currentRow, 0, 1, 2);
m_mainLayout->setRowStretch(currentRow, 0);
- //--- signal-slot connection
- connect(checkBox, SIGNAL(toggled(bool)), tmpWidget,
- SLOT(setVisible(bool)));
+ tmpWidget = new ModeSensitiveBox(this, checkBox);
checkBox->setChecked(shrink == 1);
tmpWidget->setVisible(shrink == 1);
} else { // modeSensitiveStr != ""
@@ -421,7 +420,7 @@ void ParamsPage::setPageField(TIStream &is, const TFxP &fx, bool isVertical) {
throw TException("unexpected tag " + tagName);
}
/*-- 表示コントロールをconnect --*/
- if (controller_bpf) {
+ if (controller_bpf && (!on_items.isEmpty() || !off_items.isEmpty())) {
/*-- ラベルとWidgetを両方表示/非表示 --*/
for (int i = 0; i < on_items.size(); i++) {
connect(controller_bpf, SIGNAL(toggled(bool)), on_items[i],
@@ -433,6 +432,8 @@ void ParamsPage::setPageField(TIStream &is, const TFxP &fx, bool isVertical) {
SLOT(setHidden(bool)));
off_items[i]->show();
}
+ connect(controller_bpf, SIGNAL(toggled(bool)), this,
+ SIGNAL(preferredPageSizeChanged()));
} else
std::cout << "controller_bpf NOT found!" << std::endl;
} else
@@ -448,11 +449,8 @@ void ParamsPage::setPageField(TIStream &is, const TFxP &fx, bool isVertical) {
void ParamsPage::setPageSpace() {
if (m_fields.count() != 0) {
- QWidget *spaceWidget = new QWidget();
int currentRow = m_mainLayout->rowCount();
- m_mainLayout->addWidget(spaceWidget, currentRow, 0, 1, 2);
-
for (int i = 0; i < currentRow; i++) m_mainLayout->setRowStretch(i, 0);
m_mainLayout->setRowStretch(currentRow, 1);
}
@@ -636,29 +634,25 @@ void updateMaximumPageSize(QGridLayout *layout, int &maxLabelWidth,
}
}
+ int itemCount = 0;
/*-- Widget側の最適な縦サイズおよび横幅の最大値を得る --*/
- QMap heightsByMode;
- int maxModeHeight = 0;
for (int r = 0; r < layout->rowCount(); r++) {
/*-- Column1にある可能性のあるもの:ParamField, Histogram, Layout,
* RgbLinkButtons --*/
QLayoutItem *item = layout->itemAtPosition(r, 1);
- if (!item) continue;
+ if (!item || (item->widget() && item->widget()->isHidden())) continue;
ModeSensitiveBox *box = dynamic_cast(item->widget());
if (box) {
+ if (!box->isActive()) continue;
// if (box->isHidden()) continue;
QGridLayout *innerLay = dynamic_cast(box->layout());
if (!innerLay) continue;
int tmpHeight = 0;
updateMaximumPageSize(innerLay, maxLabelWidth, maxWidgetWidth, tmpHeight);
- for (int mode : box->modes()) {
- heightsByMode[mode] += tmpHeight;
- maxModeHeight = std::max(maxModeHeight, heightsByMode[mode]);
- }
+ fieldsHeight += tmpHeight;
- // attempt to align the label column
innerLay->setColumnMinimumWidth(0, maxLabelWidth);
continue;
}
@@ -666,10 +660,10 @@ void updateMaximumPageSize(QGridLayout *layout, int &maxLabelWidth,
QSize itemSize = getItemSize(item);
if (maxWidgetWidth < itemSize.width()) maxWidgetWidth = itemSize.width();
fieldsHeight += itemSize.height();
+ itemCount++;
}
- if (maxModeHeight > 0) fieldsHeight += maxModeHeight;
- if (layout->rowCount() > 1) fieldsHeight += (layout->rowCount() - 1) * 10;
+ if (itemCount >= 1) fieldsHeight += itemCount * 10;
}
}; // namespace
@@ -1585,10 +1579,17 @@ void FxSettings::onViewModeChanged(QAction *triggeredAct) {
//-----------------------------------------------------------------------------
void FxSettings::onPreferredSizeChanged(QSize pvBestSize) {
+ DockWidget *popup = dynamic_cast(parentWidget());
+ if (!popup || !popup->isFloating()) return;
+
QSize popupBestSize = pvBestSize;
+ static int maximumHeight =
+ (QGuiApplication::primaryScreen()->geometry().height()) * 0.9;
+
// Set minimum size, just in case
- popupBestSize.setHeight(std::max(popupBestSize.height(), 85));
+ popupBestSize.setHeight(
+ std::min(std::max(popupBestSize.height(), 85), maximumHeight));
popupBestSize.setWidth(std::max(popupBestSize.width(), 390));
if (m_toolBar->isVisible()) {
@@ -1597,13 +1598,10 @@ void FxSettings::onPreferredSizeChanged(QSize pvBestSize) {
std::max(popupBestSize.width(), m_viewer->width() + 13));
}
- DockWidget *popup = dynamic_cast(parentWidget());
- if (popup && popup->isFloating()) {
- QRect geom = popup->geometry();
- geom.setSize(popupBestSize);
- popup->setGeometry(geom);
- popup->update();
- }
+ QRect geom = popup->geometry();
+ geom.setSize(popupBestSize);
+ popup->setGeometry(geom);
+ popup->update();
}
//-----------------------------------------------------------------------------
diff --git a/toonz/sources/toonzqt/paramfield.cpp b/toonz/sources/toonzqt/paramfield.cpp
index e14447e6..bdde43d4 100644
--- a/toonz/sources/toonzqt/paramfield.cpp
+++ b/toonz/sources/toonzqt/paramfield.cpp
@@ -1304,14 +1304,30 @@ ModeSensitiveBox::ModeSensitiveBox(QWidget *parent,
ModeChangerParamField *modeChanger,
QList modes)
: QWidget(parent), m_modes(modes) {
+ m_currentMode = m_modes.first();
connect(modeChanger, SIGNAL(modeChanged(int)), this,
SLOT(onModeChanged(int)));
}
+ModeSensitiveBox::ModeSensitiveBox(QWidget *parent, QCheckBox *checkBox)
+ : QWidget(parent) {
+ m_modes << 1;
+ connect(
+ checkBox, &QCheckBox::stateChanged, this,
+ [=]() { onModeChanged(checkBox->isChecked() ? 1 : 0); },
+ Qt::AutoConnection);
+}
+
//-----------------------------------------------------------------------------
void ModeSensitiveBox::onModeChanged(int modeValue) {
- setVisible(m_modes.contains(modeValue));
+ bool wasVisible = isVisible();
+ m_currentMode = modeValue;
+ if (wasVisible == m_modes.contains(modeValue)) return;
+ setVisible(!wasVisible);
+
+ ParamsPage *paramsPage = dynamic_cast(parentWidget());
+ if (paramsPage) emit paramsPage->preferredPageSizeChanged();
}
//=============================================================================