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