diff --git a/stuff/config/current.txt b/stuff/config/current.txt
index d9701fa..0841a8a 100644
--- a/stuff/config/current.txt
+++ b/stuff/config/current.txt
@@ -1341,6 +1341,12 @@
- "STD_iwa_FractalNoiseFx.dynamicIntensity" "Dynamic Intensity"
- "STD_iwa_FractalNoiseFx.alphaRendering" "Alpha Rendering"
+ - "STD_iwa_BloomFx" "Bloom Iwa"
+ - "STD_iwa_BloomFx.gamma" "Gamma"
+ - "STD_iwa_BloomFx.gain" "Gain"
+ - "STD_iwa_BloomFx.size" "Size"
+ - "STD_iwa_BloomFx.alpha_rendering" "Alpha Rendering"
+
- STD_iwa_TiledParticlesFx "Tiled Particles Iwa"
diff --git a/stuff/profiles/layouts/fxs/STD_iwa_BloomFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_BloomFx.xml
new file mode 100644
index 0000000..ae3daec
--- /dev/null
+++ b/stuff/profiles/layouts/fxs/STD_iwa_BloomFx.xml
@@ -0,0 +1,8 @@
+
+
+ gamma
+ gain
+ size
+ alpha_rendering
+
+
diff --git a/stuff/profiles/layouts/fxs/fxs.lst b/stuff/profiles/layouts/fxs/fxs.lst
index 15e9a3a..a191d2b 100644
--- a/stuff/profiles/layouts/fxs/fxs.lst
+++ b/stuff/profiles/layouts/fxs/fxs.lst
@@ -125,6 +125,7 @@
STD_iwa_SoapBubbleFx
STD_targetSpotFx
STD_iwa_GlareFx
+ STD_iwa_BloomFx
STD_erodeDilateFx
diff --git a/toonz/sources/stdfx/CMakeLists.txt b/toonz/sources/stdfx/CMakeLists.txt
index 1c1e308..2ce671b 100644
--- a/toonz/sources/stdfx/CMakeLists.txt
+++ b/toonz/sources/stdfx/CMakeLists.txt
@@ -83,6 +83,10 @@ set(HEADERS
iwa_fractalnoisefx.h
)
+if(OpenCV_FOUND)
+ list(APPEND HEADERS iwa_bloomfx.h)
+endif()
+
set(SOURCES
adjustlevelsfx.cpp
artcontourfx.cpp
@@ -270,6 +274,10 @@ set(SOURCES
iwa_fractalnoisefx.cpp
)
+if(OpenCV_FOUND)
+ list(APPEND SOURCES iwa_bloomfx.cpp)
+endif()
+
set(OBJCSOURCES
)
@@ -306,3 +314,7 @@ include_directories(
_find_toonz_library(TNZLIBS "tnzcore;tnzbase;toonzlib")
target_link_libraries(tnzstdfx Qt5::Core Qt5::Gui Qt5::OpenGL ${GL_LIB} ${GLEW_LIB} ${TNZLIBS} ${PTHREAD_LIBRARY})
+
+if(OpenCV_FOUND)
+ target_link_libraries(tnzstdfx ${OpenCV_LIBS})
+endif()
diff --git a/toonz/sources/stdfx/iwa_bloomfx.cpp b/toonz/sources/stdfx/iwa_bloomfx.cpp
new file mode 100644
index 0000000..7b0e199
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bloomfx.cpp
@@ -0,0 +1,299 @@
+#include "iwa_bloomfx.h"
+
+#include "tparamuiconcept.h"
+
+#include
+#include
+
+namespace {
+// convert sRGB color space to power space
+template
+inline T to_linear_color_space(T nonlinear_color, T exposure, T gamma) {
+ return std::pow(nonlinear_color, gamma) / exposure;
+}
+// convert power space to sRGB color space
+template
+inline T to_nonlinear_color_space(T linear_color, T exposure, T gamma) {
+ return std::pow(linear_color * exposure, T(1) / gamma);
+}
+template
+const T &clamp(const T &v, const T &lo, const T &hi) {
+ assert(!(hi < lo));
+ return (v < lo) ? lo : (hi < v) ? hi : v;
+}
+
+void blurByRotate(cv::Mat &mat) {
+ double angle = 45.0;
+
+ int size = std::ceil(std::sqrt(mat.cols * mat.cols + mat.rows * mat.rows));
+ int width = ((size - mat.cols) % 2 == 0) ? size : size + 1;
+ int height = ((size - mat.rows) % 2 == 0) ? size : size + 1;
+
+ cv::Point2f center((mat.cols - 1) / 2.0, (mat.rows - 1) / 2.0);
+
+ cv::Mat rot = cv::getRotationMatrix2D(center, angle, 1.0);
+ rot.at(0, 2) += (width - mat.cols) / 2.0;
+ rot.at(1, 2) += (height - mat.rows) / 2.0;
+
+ cv::Mat tmp;
+ cv::warpAffine(mat, tmp, rot, cv::Size(width, height));
+
+ center = cv::Point2f((width - 1) / 2.0, (height - 1) / 2.0);
+ rot = cv::getRotationMatrix2D(center, -angle, 1.0);
+ rot.at(0, 2) += (mat.cols - width) / 2.0;
+ rot.at(1, 2) += (mat.rows - height) / 2.0;
+
+ cv::warpAffine(tmp, mat, rot, mat.size());
+}
+
+} // namespace
+
+//--------------------------------------------
+// Iwa_BloomFx
+//--------------------------------------------
+Iwa_BloomFx::Iwa_BloomFx()
+ : m_gamma(2.2), m_gain(2.0), m_size(100.0), m_alpha_rendering(false) {
+ addInputPort("Source", m_source);
+ bindParam(this, "gamma", m_gamma);
+ bindParam(this, "gain", m_gain);
+ bindParam(this, "size", m_size);
+ bindParam(this, "alpha_rendering", m_alpha_rendering);
+
+ m_gamma->setValueRange(0.1, 5.0);
+ m_gain->setValueRange(0.1, 10.0);
+ m_size->setValueRange(0.1, 1024.0);
+
+ m_size->setMeasureName("fxLength");
+}
+//------------------------------------------------
+
+double Iwa_BloomFx::getSizePixelAmount(const double val, const TAffine affine) {
+ /*--- Convert to vector --- */
+ TPointD vect;
+ vect.x = val;
+ 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);
+}
+//------------------------------------------------
+
+template
+void Iwa_BloomFx::setSourceTileToMat(const RASTER ras, cv::Mat &imgMat,
+ const double gamma) {
+ double maxi = static_cast(PIXEL::maxChannelValue); // 255or65535
+ for (int j = 0; j < ras->getLy(); j++) {
+ const PIXEL *pix = ras->pixels(j);
+ cv::Vec3f *mat_p = imgMat.ptr(j);
+ for (int i = 0; i < ras->getLx(); i++, pix++, mat_p++) {
+ double pix_a = static_cast(pix->m) / maxi;
+ if (pix_a <= 0.0) {
+ *mat_p = cv::Vec3f(0, 0, 0);
+ continue;
+ }
+ double bgra[3];
+ bgra[0] = static_cast(pix->b) / maxi;
+ bgra[1] = static_cast(pix->g) / maxi;
+ bgra[2] = static_cast(pix->r) / maxi;
+ for (int c = 0; c < 3; c++) {
+ // assuming that the source image is premultiplied
+ bgra[c] = to_linear_color_space(bgra[c] / pix_a, 1.0, gamma) * pix_a;
+ }
+ *mat_p = cv::Vec3f(bgra[0], bgra[1], bgra[2]);
+ }
+ }
+}
+//------------------------------------------------
+template
+void Iwa_BloomFx::setMatToOutput(const RASTER ras, const RASTER srcRas,
+ cv::Mat &ingMat, const double gamma,
+ const double gain, const bool withAlpha,
+ const int margin) {
+ double maxi = static_cast(PIXEL::maxChannelValue); // 255or65535
+ for (int j = 0; j < ras->getLy(); j++) {
+ cv::Vec3f const *mat_p = ingMat.ptr(j);
+ PIXEL *pix = ras->pixels(j);
+ PIXEL *srcPix = srcRas->pixels(j + margin) + margin;
+
+ for (int i = 0; i < ras->getLx(); i++, pix++, srcPix++, mat_p++) {
+ double nonlinear_b =
+ to_nonlinear_color_space((double)(*mat_p)[0] * gain, 1.0, gamma);
+ double nonlinear_g =
+ to_nonlinear_color_space((double)(*mat_p)[1] * gain, 1.0, gamma);
+ double nonlinear_r =
+ to_nonlinear_color_space((double)(*mat_p)[2] * gain, 1.0, gamma);
+
+ nonlinear_b = clamp(nonlinear_b, 0.0, 1.0);
+ nonlinear_g = clamp(nonlinear_g, 0.0, 1.0);
+ nonlinear_r = clamp(nonlinear_r, 0.0, 1.0);
+
+ pix->r = (typename PIXEL::Channel)(nonlinear_r * (maxi + 0.999999));
+ pix->g = (typename PIXEL::Channel)(nonlinear_g * (maxi + 0.999999));
+ pix->b = (typename PIXEL::Channel)(nonlinear_b * (maxi + 0.999999));
+ if (withAlpha) {
+ double chan_a =
+ std::max(std::max(nonlinear_b, nonlinear_g), nonlinear_r);
+ pix->m = std::max((typename PIXEL::Channel)(chan_a * (maxi + 0.999999)),
+ srcPix->m);
+ } else
+ pix->m = (typename PIXEL::Channel)(PIXEL::maxChannelValue);
+ }
+ }
+}
+
+//------------------------------------------------
+
+void Iwa_BloomFx::doCompute(TTile &tile, double frame,
+ const TRenderSettings &settings) {
+ // If the source is not connected, then do nothing
+ if (!m_source.isConnected()) {
+ tile.getRaster()->clear();
+ return;
+ }
+ // obtain parameters
+ double gamma = m_gamma->getValue(frame);
+ double gain = m_gain->getValue(frame);
+ double size = getSizePixelAmount(m_size->getValue(frame), settings.m_affine);
+ bool withAlpha = m_alpha_rendering->getValue();
+
+ int margin = static_cast(std::ceil(size));
+ TRectD _rect(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
+ tile.getRaster()->getLy()));
+ _rect = _rect.enlarge(static_cast(margin));
+ TDimensionI dimSrc(static_cast(_rect.getLx() + 0.5),
+ static_cast(_rect.getLy() + 0.5));
+
+ // obtain the source tile
+ TTile sourceTile;
+ m_source->allocateAndCompute(sourceTile, _rect.getP00(), dimSrc,
+ tile.getRaster(), frame, settings);
+
+ // set the source image to cvMat, converting to linear color space
+ cv::Mat imgMat(cv::Size(dimSrc.lx, dimSrc.ly), CV_32FC3);
+ TRaster32P ras32 = tile.getRaster();
+ TRaster64P ras64 = tile.getRaster();
+ if (ras32)
+ setSourceTileToMat(sourceTile.getRaster(), imgMat,
+ gamma);
+ else if (ras64)
+ setSourceTileToMat(sourceTile.getRaster(), imgMat,
+ gamma);
+
+ // compute size and intensity ratios of resampled layers
+ // resample size is reduced from the specified size, taking into account
+ // that the gaussian blur (x 2) and the blur by rotation resampling (x sqrt2)
+ double no_blur_size = size / (2 * 1.5);
+ // find the mimimum "power of 2" value which is the same as or larger than the
+ // filter size
+ int level = 1;
+ double power_of_2 = 1.0;
+ while (1) {
+ if (power_of_2 >= no_blur_size) break;
+ level++;
+ power_of_2 *= 2.0;
+ }
+ // store the size of resampled layers
+ QVector sizes;
+ double tmp_filterSize = no_blur_size;
+ double width = static_cast(imgMat.size().width);
+ double height = static_cast(imgMat.size().height);
+ for (int lvl = 0; lvl < level - 1; lvl++) {
+ int tmp_w = static_cast(std::ceil(width / tmp_filterSize));
+ int tmp_h = static_cast(std::ceil(height / tmp_filterSize));
+ sizes.push_front(cv::Size(tmp_w, tmp_h));
+ tmp_filterSize *= 0.5;
+ }
+ sizes.push_front(imgMat.size());
+
+ // the filter is based on the nearest power-of-2 sized one with an adjustment
+ // reducing the sizes and increasing the intensity with this ratio
+ double ratio = power_of_2 / no_blur_size;
+ // base filter sizes will be 1, 2, 4, ... 2^(level-1)
+ // intensity of the filter with sizes > 2
+ double intensity_all = power_of_2 / (power_of_2 * 2.0 - 1.0);
+ // intensity of the filter with size 1, so that the amount of the filter at
+ // the center point is always 1.0
+ double intensity_front = 1.0 - (1.0 - intensity_all) * ratio;
+
+ std::vector dst(level);
+ cv::Size const ksize(3, 3);
+
+ cv::Mat tmp;
+ int i;
+ // for each level of filter (from larger to smaller)
+ for (i = 0; i < level;) {
+ // scaling down the size
+ if (i) {
+ cv::resize(imgMat, tmp, sizes[i], 0.0, 0.0, cv::INTER_AREA);
+ imgMat = tmp;
+ }
+ // gaussian blur
+ cv::GaussianBlur(imgMat, dst[i], ksize, 0.0);
+ ++i;
+ }
+ // for each level of filter (from smaller to larger)
+ for (--i; i > 0; --i) {
+ // scaling up the size
+ cv::resize(dst[i], tmp, dst[i - 1].size());
+ // blur by rotational resampling in order to reduce box-shaped artifact
+ blurByRotate(tmp);
+ // add to the upper resampled image
+ if (i > 1)
+ dst[i - 1] += tmp;
+ else
+ imgMat = dst[0] * intensity_front + tmp * intensity_all;
+ }
+
+ // get the subimage without margin
+ cv::Rect roi(cv::Point(margin, margin),
+ cv::Size(tile.getRaster()->getLx(), tile.getRaster()->getLy()));
+ imgMat = imgMat(roi);
+
+ // set the result to the tile, converting to rgb channel values
+ if (ras32)
+ setMatToOutput(tile.getRaster(),
+ sourceTile.getRaster(), imgMat, gamma,
+ gain, withAlpha, margin);
+ else if (ras64)
+ setMatToOutput(tile.getRaster(),
+ sourceTile.getRaster(), imgMat, gamma,
+ gain, withAlpha, margin);
+}
+//------------------------------------------------
+
+bool Iwa_BloomFx::doGetBBox(double frame, TRectD &bBox,
+ const TRenderSettings &info) {
+ if (!m_source.isConnected()) {
+ bBox = TRectD();
+ return false;
+ }
+ bool ret = m_source->doGetBBox(frame, bBox, info);
+ int margin = static_cast(
+ std::ceil(getSizePixelAmount(m_size->getValue(frame), info.m_affine)));
+ if (margin > 0) {
+ bBox = bBox.enlarge(static_cast(margin));
+ }
+ return ret;
+}
+//------------------------------------------------
+
+bool Iwa_BloomFx::canHandle(const TRenderSettings &info, double frame) {
+ return false;
+}
+//------------------------------------------------
+
+void Iwa_BloomFx::getParamUIs(TParamUIConcept *&concepts, int &length) {
+ concepts = new TParamUIConcept[length = 1];
+
+ concepts[0].m_type = TParamUIConcept::RADIUS;
+ concepts[0].m_label = "Size";
+ concepts[0].m_params.push_back(m_size);
+}
+//------------------------------------------------
+
+FX_PLUGIN_IDENTIFIER(Iwa_BloomFx, "iwa_BloomFx")
\ No newline at end of file
diff --git a/toonz/sources/stdfx/iwa_bloomfx.h b/toonz/sources/stdfx/iwa_bloomfx.h
new file mode 100644
index 0000000..bc5f274
--- /dev/null
+++ b/toonz/sources/stdfx/iwa_bloomfx.h
@@ -0,0 +1,49 @@
+#pragma once
+
+/*------------
+ Iwa_BloomFx
+ Based on the LightBloom plugin fx by Dwango, with modifications as follows:
+ - parameters are reduced & simplified to gamma, gain and size
+ - reduced the block noise appeared especially when the gamma is with higher
+value
+ - enabled to render alpha gradation
+ - enabled to adjust size with the viewer gadget
+--------------*/
+
+#ifndef IWA_BLOOMFX_H
+#define IWA_BLOOMFX_H
+
+#include "stdfx.h"
+#include "tfxparam.h"
+#include
+
+class Iwa_BloomFx : public TStandardRasterFx {
+ FX_PLUGIN_DECLARATION(Iwa_BloomFx)
+protected:
+ TRasterFxPort m_source;
+ TDoubleParamP m_gamma;
+ TDoubleParamP m_gain;
+ TDoubleParamP m_size;
+ TBoolParamP m_alpha_rendering;
+
+ double getSizePixelAmount(const double val, const TAffine affine);
+ template
+ void setSourceTileToMat(const RASTER ras, cv::Mat &ingMat,
+ const double gamma);
+
+ template
+ void setMatToOutput(const RASTER ras, const RASTER srcRas, cv::Mat &ingMat,
+ const double gamma, const double gain,
+ const bool withAlpha, const int margin);
+
+public:
+ Iwa_BloomFx();
+ 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 getParamUIs(TParamUIConcept *&concepts, int &length) override;
+};
+
+#endif
\ No newline at end of file