tahoma2d/toonz/sources/stdfx/iwa_bokeh_util.cpp
luz paz 35e409e926 fix various typos
Found via `codespell -q 3 -S *.ts,thirdparty, -L appy,ba,inbetween,inout,pevent,possibile,upto`
2021-08-31 11:10:50 -04:00

1902 lines
68 KiB
C++

#include "iwa_bokeh_util.h"
#include "trop.h"
#include <array>
#include <QSet>
#include <QMap>
#include <QReadWriteLock>
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 <typename T>
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<TRasterGR8P>& rasterList,
QList<kiss_fftnd_cfg>& 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 <typename RASTER, typename PIXEL>
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<TRaster32P, TPixel32>(ras32, m_kissfft_comp_in, dim);
else if (ras64)
setLayerRaster<TRaster64P, TPixel64>(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<TRaster32P, TPixel32>(
const TRaster32P srcRas, double4* dstMem, TDimensionI dim);
template void BokehUtils::setSourceRaster<TRaster64P, TPixel64>(
const TRaster64P srcRas, double4* dstMem, TDimensionI dim);
template <typename RASTER, typename PIXEL>
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<TRaster32P, TPixel32>(
const TRaster32P srcRas, unsigned char* dstMem, TDimensionI dim);
template void BokehUtils::setDepthRaster<TRaster64P, TPixel64>(
const TRaster64P srcRas, unsigned char* dstMem, TDimensionI dim);
template <typename RASTER, typename PIXEL>
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<double>& segmentDepth_main,
QVector<double>& segmentDepth_sub, const double focusDepth,
int distancePrecision, double nearDepth, double farDepth) {
QSet<int> segmentValues;
// histogram parameters
struct HISTO {
int pix_amount;
int belongingSegmentValue; // value to which temporary segmented
int segmentId;
int segmentId_sub;
};
std::array<HISTO, 256> 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
// priority, 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
// priority, 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<int> 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<unsigned char>(
&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 / result.w, layerHardnessRatio) * result.w;
result.y = std::pow(result.y / result.w, layerHardnessRatio) * result.w;
result.z = std::pow(result.z / result.w, layerHardnessRatio) * result.w;
}
// 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<TRaster32P, TPixel32>(ras32, layer_buff,
dimOut);
else if (ras64)
BokehUtils::setSourceRaster<TRaster64P, TPixel64>(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<TRaster32P, TPixel32>(
double4* src, const TRaster32P dstRas, TDimensionI& dim, int2 margin);
template void BokehUtils::setOutputRaster<TRaster64P, TPixel64>(
double4* src, const TRaster64P dstRas, TDimensionI& dim, int2 margin);
template <typename RASTER, typename PIXEL>
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<LayerValue>& layerValues,
QMap<int, unsigned char*>& 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<TRasterGR8P> rasterList;
QList<kiss_fftnd_cfg> planList;
kiss_fft_cpx* kissfft_comp_iris;
double* alpha_bokeh = nullptr;
double4* result = nullptr;
rasterList.append(
allocateRasterAndLock<kiss_fft_cpx>(&kissfft_comp_iris, dimOut));
rasterList.append(allocateRasterAndLock<double>(&alpha_bokeh, dimOut));
rasterList.append(allocateRasterAndLock<double4>(&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<kiss_fft_cpx>(
&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<TRaster32P, TPixel32>(result, outRas32, dimOut,
outMargin);
else if (outRas64)
BokehUtils::setOutputRaster<TRaster64P, TPixel64>(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<TRasterGR8P> rasterList;
QList<kiss_fftnd_cfg> planList;
// source image
double4* source_buff;
rasterList.append(allocateRasterAndLock<double4>(&source_buff, dimOut));
TRaster32P ras32 = (TRaster32P)layer.sourceTile->getRaster();
TRaster64P ras64 = (TRaster64P)layer.sourceTile->getRaster();
lock.lockForRead();
if (ras32)
BokehUtils::setSourceRaster<TRaster32P, TPixel32>(ras32, source_buff,
dimOut);
else if (ras64)
BokehUtils::setSourceRaster<TRaster64P, TPixel64>(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<unsigned char>(&indexMap_main_buff, dimOut));
rasterList.append(
allocateRasterAndLock<unsigned char>(&indexMap_sub_buff, dimOut));
rasterList.append(allocateRasterAndLock<double>(&mainSub_ratio_buff, dimOut));
QVector<double> segmentDepth_main;
QVector<double> 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<double4>(&layer_buff, dimOut));
kiss_fft_cpx* kissfft_comp_iris_before;
rasterList.append(
allocateRasterAndLock<kiss_fft_cpx>(&kissfft_comp_iris_before, dimOut));
// alpha channel
kiss_fft_cpx* fftcpx_alpha_before;
kiss_fft_cpx* fftcpx_alpha;
rasterList.append(
allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_alpha_before, dimOut));
rasterList.append(allocateRasterAndLock<kiss_fft_cpx>(&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<kiss_fft_cpx>(&fftcpx_r_before, dimOut));
rasterList.append(
allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_g_before, dimOut));
rasterList.append(
allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_b_before, dimOut));
rasterList.append(allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_r, dimOut));
rasterList.append(allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_g, dimOut));
rasterList.append(allocateRasterAndLock<kiss_fft_cpx>(&fftcpx_b, dimOut));
// for accumulating result image
double4* result_main_buff;
double4* result_sub_buff;
rasterList.append(allocateRasterAndLock<double4>(&result_main_buff, dimOut));
rasterList.append(allocateRasterAndLock<double4>(&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<double> 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);
}