diff --git a/stuff/config/current.txt b/stuff/config/current.txt index 237e241..70c485b 100644 --- a/stuff/config/current.txt +++ b/stuff/config/current.txt @@ -1183,6 +1183,8 @@ <item>"STD_iwa_SoapBubbleFx.GGamma" "G Gamma" </item> <item>"STD_iwa_SoapBubbleFx.BGamma" "B Gamma" </item> <item>"STD_iwa_SoapBubbleFx.binarizeThresold" "Threshold" </item> + <item>"STD_iwa_SoapBubbleFx.multiSource" "Multiple Bubbles in Shape Image" </item> + <item>"STD_iwa_SoapBubbleFx.maskCenter" "Mask Center of the Bubble" </item> <item>"STD_iwa_SoapBubbleFx.shapeAspectRatio" "Shape Aspect Ratio" </item> <item>"STD_iwa_SoapBubbleFx.blurRadius" "Blur Radius" </item> <item>"STD_iwa_SoapBubbleFx.blurPower" "Power" </item> diff --git a/stuff/profiles/layouts/fxs/STD_iwa_SoapBubbleFx.xml b/stuff/profiles/layouts/fxs/STD_iwa_SoapBubbleFx.xml index 232753d..5803332 100644 --- a/stuff/profiles/layouts/fxs/STD_iwa_SoapBubbleFx.xml +++ b/stuff/profiles/layouts/fxs/STD_iwa_SoapBubbleFx.xml @@ -13,6 +13,8 @@ <vbox> <separator label="Shape"/> <control>binarizeThresold</control> + <control>multiSource</control> + <control>maskCenter</control> <control>shapeAspectRatio</control> <control>blurRadius</control> <control>blurPower</control> diff --git a/toonz/sources/stdfx/iwa_soapbubblefx.cpp b/toonz/sources/stdfx/iwa_soapbubblefx.cpp index 24249f6..27b9395 100644 --- a/toonz/sources/stdfx/iwa_soapbubblefx.cpp +++ b/toonz/sources/stdfx/iwa_soapbubblefx.cpp @@ -67,6 +67,8 @@ Iwa_SoapBubbleFx::Iwa_SoapBubbleFx() , m_shape_aspect_ratio(1.0) , m_blur_radius(5.0) , m_blur_power(0.5) + , m_multi_source(false) + , m_mask_center(false) , m_normal_sample_distance(1) , m_noise_sub_depth(3) , m_noise_resolution_s(18.0) @@ -85,6 +87,8 @@ Iwa_SoapBubbleFx::Iwa_SoapBubbleFx() bindParam(this, "shapeAspectRatio", m_shape_aspect_ratio); bindParam(this, "blurRadius", m_blur_radius); bindParam(this, "blurPower", m_blur_power); + bindParam(this, "multiSource", m_multi_source); + bindParam(this, "maskCenter", m_mask_center); bindParam(this, "normalSampleDistance", m_normal_sample_distance); bindParam(this, "noiseSubDepth", m_noise_sub_depth); bindParam(this, "noiseResolutionS", m_noise_resolution_s); @@ -118,23 +122,37 @@ void Iwa_SoapBubbleFx::doCompute(TTile& tile, double frame, TDimensionI dim(tile.getRaster()->getLx(), tile.getRaster()->getLy()); TRectD bBox(tile.m_pos, TPointD(dim.lx, dim.ly)); + QList<TRasterGR8P> allocatedRasList; /* soap bubble color map */ TRasterGR8P bubbleColor_ras(sizeof(float3) * 256 * 256, 1); bubbleColor_ras->lock(); + allocatedRasList.append(bubbleColor_ras); float3* bubbleColor_p = (float3*)bubbleColor_ras->getRawData(); calcBubbleMap(bubbleColor_p, frame, true); + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + /* depth map */ TRasterGR8P depth_map_ras(sizeof(float) * dim.lx * dim.ly, 1); depth_map_ras->lock(); + allocatedRasList.append(depth_map_ras); float* depth_map_p = (float*)depth_map_ras->getRawData(); + /* alpha map */ + TRasterGR8P alpha_map_ras(sizeof(float) * dim.lx * dim.ly, 1); + alpha_map_ras->lock(); + allocatedRasList.append(alpha_map_ras); + float* alpha_map_p = (float*)alpha_map_ras->getRawData(); + /* if the depth image is connected, use it */ if (m_depth.isConnected()) { TTile depth_tile; m_depth->allocateAndCompute(depth_tile, bBox.getP00(), dim, tile.getRaster(), frame, settings); + + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + TRasterP depthRas = depth_tile.getRaster(); depthRas->lock(); @@ -142,9 +160,11 @@ void Iwa_SoapBubbleFx::doCompute(TTile& tile, double frame, TRaster64P depthRas64 = (TRaster64P)depthRas; { if (depthRas32) - convertToBrightness<TRaster32P, TPixel32>(depthRas32, depth_map_p, dim); + convertToBrightness<TRaster32P, TPixel32>(depthRas32, depth_map_p, + alpha_map_p, dim); else if (depthRas64) - convertToBrightness<TRaster64P, TPixel64>(depthRas64, depth_map_p, dim); + convertToBrightness<TRaster64P, TPixel64>(depthRas64, depth_map_p, + alpha_map_p, dim); } depthRas->unlock(); } @@ -157,46 +177,60 @@ void Iwa_SoapBubbleFx::doCompute(TTile& tile, double frame, m_shape->allocateAndCompute(shape_tile, bBox.getP00(), dim, tmp, frame, settings); } - processShape(frame, shape_tile, depth_map_p, dim, settings); + + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + + processShape(frame, shape_tile, depth_map_p, alpha_map_p, dim, settings); } + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + /* compute the thickness input and temporarily store to the tile */ m_input->compute(tile, frame, settings); + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + TRasterGR8P thickness_map_ras(sizeof(float) * dim.lx * dim.ly, 1); thickness_map_ras->lock(); + allocatedRasList.append(thickness_map_ras); float* thickness_map_p = (float*)thickness_map_ras->getRawData(); TRasterP thicknessRas = tile.getRaster(); TRaster32P ras32 = (TRaster32P)thicknessRas; TRaster64P ras64 = (TRaster64P)thicknessRas; { if (ras32) - convertToBrightness<TRaster32P, TPixel32>(ras32, thickness_map_p, dim); + convertToBrightness<TRaster32P, TPixel32>(ras32, thickness_map_p, nullptr, + dim); else if (ras64) - convertToBrightness<TRaster64P, TPixel64>(ras64, thickness_map_p, dim); + convertToBrightness<TRaster64P, TPixel64>(ras64, thickness_map_p, nullptr, + dim); } + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + /* process noise */ processNoise(thickness_map_p, depth_map_p, dim, frame, settings); + if (checkCancelAndReleaseRaster(allocatedRasList, tile, settings)) return; + if (ras32) convertToRaster<TRaster32P, TPixel32>(ras32, thickness_map_p, depth_map_p, - dim, bubbleColor_p); + alpha_map_p, dim, bubbleColor_p); else if (ras64) convertToRaster<TRaster64P, TPixel64>(ras64, thickness_map_p, depth_map_p, - dim, bubbleColor_p); + alpha_map_p, dim, bubbleColor_p); - thickness_map_ras->unlock(); - depth_map_ras->unlock(); - bubbleColor_ras->unlock(); + for (int i = 0; i < allocatedRasList.size(); i++) + allocatedRasList.at(i)->unlock(); } //------------------------------------ template <typename RASTER, typename PIXEL> void Iwa_SoapBubbleFx::convertToBrightness(const RASTER srcRas, float* dst, - TDimensionI dim) { - float* dst_p = dst; + float* alpha, TDimensionI dim) { + float* dst_p = dst; + float* alpha_p = alpha; for (int j = 0; j < dim.ly; j++) { PIXEL* pix = srcRas->pixels(j); for (int i = 0; i < dim.lx; i++, dst_p++, pix++) { @@ -205,6 +239,10 @@ void Iwa_SoapBubbleFx::convertToBrightness(const RASTER srcRas, float* dst, float b = (float)pix->b / (float)PIXEL::maxChannelValue; /* brightness */ *dst_p = 0.298912f * r + 0.586611f * g + 0.114478f * b; + if (alpha) { + *alpha_p = (float)pix->m / (float)PIXEL::maxChannelValue; + alpha_p++; + } } } } @@ -213,16 +251,20 @@ void Iwa_SoapBubbleFx::convertToBrightness(const RASTER srcRas, float* dst, template <typename RASTER, typename PIXEL> void Iwa_SoapBubbleFx::convertToRaster(const RASTER ras, float* thickness_map_p, - float* depth_map_p, TDimensionI dim, - float3* bubbleColor_p) { + float* depth_map_p, float* alpha_map_p, + TDimensionI dim, float3* bubbleColor_p) { float* depth_p = depth_map_p; float* thickness_p = thickness_map_p; + float* alpha_p = alpha_map_p; for (int j = 0; j < dim.ly; j++) { PIXEL* pix = ras->pixels(j); - for (int i = 0; i < dim.lx; i++, depth_p++, thickness_p++, pix++) { - float alpha = (float)pix->m / PIXEL::maxChannelValue; - if (alpha == 0.0f) /* no change for the transparent pixels */ + for (int i = 0; i < dim.lx; + i++, depth_p++, thickness_p++, alpha_p++, pix++) { + float alpha = (*alpha_p) * (float)pix->m / (float)PIXEL::maxChannelValue; + if (alpha == 0.0f) { /* no change for the transparent pixels */ + pix->m = (typename PIXEL::Channel)0; continue; + } float coordinate[2]; coordinate[0] = 256.0f * std::min(1.0f, *depth_p); @@ -265,15 +307,19 @@ void Iwa_SoapBubbleFx::convertToRaster(const RASTER ras, float* thickness_map_p, nColors[3] * interp_ratio[0] * interp_ratio[1]; /* clamp */ - float val = color.x * (float)PIXEL::maxChannelValue + 0.5f; + float val = alpha * (float)PIXEL::maxChannelValue + 0.5f; + pix->m = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue) + ? (float)PIXEL::maxChannelValue + : val); + val = alpha * color.x * (float)PIXEL::maxChannelValue + 0.5f; pix->r = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue) ? (float)PIXEL::maxChannelValue : val); - val = color.y * (float)PIXEL::maxChannelValue + 0.5f; + val = alpha * color.y * (float)PIXEL::maxChannelValue + 0.5f; pix->g = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue) ? (float)PIXEL::maxChannelValue : val); - val = color.z * (float)PIXEL::maxChannelValue + 0.5f; + val = alpha * color.z * (float)PIXEL::maxChannelValue + 0.5f; pix->b = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue) ? (float)PIXEL::maxChannelValue : val); @@ -284,15 +330,16 @@ void Iwa_SoapBubbleFx::convertToRaster(const RASTER ras, float* thickness_map_p, //------------------------------------ void Iwa_SoapBubbleFx::processShape(double frame, TTile& shape_tile, - float* depth_map_p, TDimensionI dim, + float* depth_map_p, float* alpha_map_p, + TDimensionI dim, const TRenderSettings& settings) { TRaster32P shapeRas = shape_tile.getRaster(); shapeRas->lock(); /* binarize the shape image */ - TRasterGR8P binarized_ras(sizeof(char) * dim.lx * dim.ly, 1); + TRasterGR8P binarized_ras(sizeof(USHORT) * dim.lx * dim.ly, 1); binarized_ras->lock(); - char* binarized_p = (char*)binarized_ras->getRawData(); + USHORT* binarized_p = (USHORT*)binarized_ras->getRawData(); TRasterGR8P distance_ras(sizeof(float) * dim.lx * dim.ly, 1); distance_ras->lock(); @@ -300,11 +347,27 @@ void Iwa_SoapBubbleFx::processShape(double frame, TTile& shape_tile, float binarize_thres = (float)m_binarize_threshold->getValue(frame); - do_binarize(shapeRas, binarized_p, binarize_thres, distance_p, dim); + int regionCount = do_binarize(shapeRas, binarized_p, binarize_thres, + distance_p, alpha_map_p, dim); shapeRas->unlock(); - do_distance_transform(distance_p, dim, frame); + if (settings.m_isCanceled && *settings.m_isCanceled) { + binarized_ras->unlock(); + distance_ras->unlock(); + return; + } + + do_distance_transform(distance_p, binarized_p, regionCount, dim, frame); + + if (settings.m_isCanceled && *settings.m_isCanceled) { + binarized_ras->unlock(); + distance_ras->unlock(); + return; + } + + if (m_mask_center->getValue()) + applyDistanceToAlpha(distance_p, alpha_map_p, dim); /* create blur filter */ float blur_radius = (float)m_blur_radius->getValue(frame) * @@ -315,7 +378,7 @@ void Iwa_SoapBubbleFx::processShape(double frame, TTile& shape_tile, float power = (float)m_blur_power->getValue(frame); float* tmp_depth = depth_map_p; float* tmp_dist = distance_p; - char* bin_p = binarized_p; + USHORT* bin_p = binarized_p; for (int i = 0; i < dim.lx * dim.ly; i++, tmp_depth++, tmp_dist++, bin_p++) { if (*bin_p == 0) @@ -336,9 +399,16 @@ void Iwa_SoapBubbleFx::processShape(double frame, TTile& shape_tile, do_createBlurFilter(blur_filter_p, blur_filter_size, blur_radius); + if (settings.m_isCanceled && *settings.m_isCanceled) { + blur_filter_ras->unlock(); + binarized_ras->unlock(); + distance_ras->unlock(); + return; + } + /* blur filtering, normarize & power */ do_applyFilter(depth_map_p, dim, distance_p, binarized_p, blur_filter_p, - blur_filter_size, frame); + blur_filter_size, frame, settings); distance_ras->unlock(); binarized_ras->unlock(); @@ -347,19 +417,77 @@ void Iwa_SoapBubbleFx::processShape(double frame, TTile& shape_tile, //------------------------------------ -void Iwa_SoapBubbleFx::do_binarize(TRaster32P srcRas, char* dst_p, float thres, - float* distance_p, TDimensionI dim) { +int Iwa_SoapBubbleFx::do_binarize(TRaster32P srcRas, USHORT* dst_p, float thres, + float* distance_p, float* alpha_map_p, + TDimensionI dim) { TPixel32::Channel channelThres = (TPixel32::Channel)(thres * (float)TPixel32::maxChannelValue); - char* tmp_p = dst_p; + USHORT* tmp_p = dst_p; float* tmp_dist = distance_p; + float* alpha_p = alpha_map_p; for (int j = 0; j < dim.ly; j++) { TPixel32* pix = srcRas->pixels(j); - for (int i = 0; i < dim.lx; i++, pix++, tmp_p++, tmp_dist++) { + for (int i = 0; i < dim.lx; i++, pix++, tmp_p++, tmp_dist++, alpha_p++) { (*tmp_p) = (pix->m > channelThres) ? 1 : 0; (*tmp_dist) = (*tmp_p == 1) ? INF : 0.0f; + *alpha_p = (float)pix->m / (float)TPixel32::maxChannelValue; } } + + // label regions when multi bubble option is on + if (!m_multi_source->getValue()) return 1; + + QList<int> lut; + for (int i = 0; i < 65536; i++) lut.append(i); + tmp_p = dst_p; + int regionCount = 0; + for (int j = 0; j < dim.ly; j++) { + for (int i = 0; i < dim.lx; i++, tmp_p++) { + if ((*tmp_p) == 1) { + int up = (j == 0) ? 0 : *(tmp_p - dim.lx); + int left = (i == 0) ? 0 : *(tmp_p - 1); + assert(up >= 0 && left >= 0); + if (!up && !left) { + if (regionCount < 65535) regionCount++; + (*tmp_p) = regionCount; + } else if (up && !left) + (*tmp_p) = up; + else if (!up && left) + (*tmp_p) = left; + else if (up == left) + (*tmp_p) = up; + else if (up > left) { + (*tmp_p) = left; + lut[up] = left; + } else { + (*tmp_p) = up; + lut[left] = up; + } + } + } + } + + // organize lut + QList<int> convIndex; + int currentIndex = 0; + for (int i = 0; i < 65536; i++) { + if (lut.at(i) == i) { + lut[i] = currentIndex; + currentIndex++; + } else + convIndex.append(i); + } + for (int i = 0; i < convIndex.count(); i++) + lut[convIndex.at(i)] = lut.at(lut.at(convIndex.at(i))); + + // apply lut + tmp_p = dst_p; + for (int j = 0; j < dim.ly; j++) { + for (int i = 0; i < dim.lx; i++, tmp_p++) { + (*tmp_p) = lut[*tmp_p]; + } + } + return regionCount; } //------------------------------------ @@ -392,16 +520,17 @@ void Iwa_SoapBubbleFx::do_createBlurFilter(float* dst_p, int size, //------------------------------------ void Iwa_SoapBubbleFx::do_applyFilter(float* depth_map_p, TDimensionI dim, - float* distance_p, char* binarized_p, + float* distance_p, USHORT* binarized_p, float* blur_filter_p, - int blur_filter_size, double frame) { + int blur_filter_size, double frame, + const TRenderSettings& settings) { float power = (float)m_blur_power->getValue(frame); memset(depth_map_p, 0, sizeof(float) * dim.lx * dim.ly); int fil_margin = (blur_filter_size - 1) / 2; float* dst_p = depth_map_p; - char* bin_p = binarized_p; + USHORT* bin_p = binarized_p; for (int j = 0; j < dim.ly; j++) { for (int i = 0; i < dim.lx; i++, dst_p++, bin_p++) { if (*bin_p == 0) continue; @@ -421,6 +550,7 @@ void Iwa_SoapBubbleFx::do_applyFilter(float* depth_map_p, TDimensionI dim, /* power the value */ *dst_p = 1.0f - std::pow(*dst_p, power); } + if (settings.m_isCanceled && *settings.m_isCanceled) return; } } @@ -662,13 +792,15 @@ void Iwa_SoapBubbleFx::add_noise(float* thickness_map_p, float* depth_map_p, } //------------------------------------ -void Iwa_SoapBubbleFx::do_distance_transform(float* dst_p, TDimensionI dim, +void Iwa_SoapBubbleFx::do_distance_transform(float* dst_p, USHORT* binarized_p, + int regionCount, TDimensionI dim, double frame) { float ar = (float)m_shape_aspect_ratio->getValue(frame); float* f = new float[std::max(dim.lx, dim.ly)]; - float max_val = 0.0f; + QList<float> max_val; + for (int r = 0; r <= regionCount; r++) max_val.append(0.0f); float* tmp_dst = dst_p; /* transform along rows */ @@ -694,20 +826,46 @@ void Iwa_SoapBubbleFx::do_distance_transform(float* dst_p, TDimensionI dim, dt(f, dim.ly, ar); /* ar : taking account of the aspect ratio of the shape */ for (int j = 0; j < dim.ly; j++) { - dst_p[j * dim.lx + i] = d[j]; - if (d[j] > max_val) max_val = d[j]; + dst_p[j * dim.lx + i] = d[j]; + int regionId = binarized_p[j * dim.lx + i]; + if (d[j] > max_val[regionId]) max_val[regionId] = d[j]; } delete[] d; } tmp_dst = dst_p; - max_val = std::sqrt(max_val); + for (int r = 0; r <= regionCount; r++) max_val[r] = std::sqrt(max_val[r]); /* square root and normalize */ - for (int i = 0; i < dim.lx * dim.ly; i++, *tmp_dst++) { - *tmp_dst = std::sqrt(*tmp_dst) / max_val; + USHORT* region_p = binarized_p; + for (int i = 0; i < dim.lx * dim.ly; i++, *tmp_dst++, region_p++) { + if (max_val[*region_p] > 0) + *tmp_dst = std::sqrt(*tmp_dst) / max_val[*region_p]; } } +//------------------------------------ + +bool Iwa_SoapBubbleFx::checkCancelAndReleaseRaster( + const QList<TRasterGR8P>& allocatedRasList, TTile& tile, + const TRenderSettings& settings) { + if (settings.m_isCanceled && *settings.m_isCanceled) { + for (int i = 0; i < allocatedRasList.size(); i++) + allocatedRasList.at(i)->unlock(); + tile.getRaster()->clear(); + return true; + } else + return false; +} +//------------------------------------ + +void Iwa_SoapBubbleFx::applyDistanceToAlpha(float* distance_p, + float* alpha_map_p, + TDimensionI dim) { + float* d_p = distance_p; + float* a_p = alpha_map_p; + for (int i = 0; i < dim.lx * dim.ly; i++, d_p++, a_p++) + (*a_p) *= 1.0f - (*d_p); +} //============================================================================== diff --git a/toonz/sources/stdfx/iwa_soapbubblefx.h b/toonz/sources/stdfx/iwa_soapbubblefx.h index 2b7acb1..7e9aa3d 100644 --- a/toonz/sources/stdfx/iwa_soapbubblefx.h +++ b/toonz/sources/stdfx/iwa_soapbubblefx.h @@ -26,6 +26,8 @@ protected: TDoubleParamP m_shape_aspect_ratio; TDoubleParamP m_blur_radius; TDoubleParamP m_blur_power; + TBoolParamP m_multi_source; + TBoolParamP m_mask_center; // noise parameters TIntParamP m_normal_sample_distance; @@ -38,24 +40,27 @@ protected: TDoubleParamP m_noise_thickness_mix_ratio; template <typename RASTER, typename PIXEL> - void convertToBrightness(const RASTER srcRas, float* dst, TDimensionI dim); + void convertToBrightness(const RASTER srcRas, float* dst, float* alpha, + TDimensionI dim); template <typename RASTER, typename PIXEL> void convertToRaster(const RASTER ras, float* thickness_map_p, - float* depth_map_p, TDimensionI dim, + float* depth_map_p, float* alpha_map_p, TDimensionI dim, float3* bubbleColor_p); void processShape(double frame, TTile& shape_tile, float* depth_map_p, - TDimensionI dim, const TRenderSettings& settings); + float* alpha_map_p, TDimensionI dim, + const TRenderSettings& settings); - void do_binarize(TRaster32P srcRas, char* dst_p, float thres, - float* distance_p, TDimensionI dim); + int do_binarize(TRaster32P srcRas, USHORT* dst_p, float thres, + float* distance_p, float* alpha_map_p, TDimensionI dim); void do_createBlurFilter(float* dst_p, int size, float radius); void do_applyFilter(float* depth_map_p, TDimensionI dim, float* distace_p, - char* binarized_p, float* blur_filter_p, - int blur_filter_size, double frame); + USHORT* binarized_p, float* blur_filter_p, + int blur_filter_size, double frame, + const TRenderSettings& settings); void processNoise(float* thickness_map_p, float* depth_map_p, TDimensionI dim, double frame, const TRenderSettings& settings); @@ -76,7 +81,15 @@ protected: float* noise_map_p, float noise_thickness_mix_ratio, float noise_depth_mix_ratio); - void do_distance_transform(float* dst_p, TDimensionI dim, double frame); + void do_distance_transform(float* dst_p, USHORT* binarized_p, int regionCount, + TDimensionI dim, double frame); + + // if the rendering process is canceled, release raster and return TRUE + bool checkCancelAndReleaseRaster(const QList<TRasterGR8P>&, TTile&, + const TRenderSettings&); + + void applyDistanceToAlpha(float* distance_p, float* alpha_map_p, + TDimensionI dim); public: Iwa_SoapBubbleFx();