tahoma2d/toonz/sources/stdfx/localblurfx.cpp

431 lines
14 KiB
C++
Raw Normal View History

2016-03-19 06:57:51 +13:00
#include "stdfx.h"
#include "tfxparam.h"
#include "trop.h"
#include "tdoubleparam.h"
#include "trasterfx.h"
/* (Daniele)
2016-06-15 18:43:10 +12:00
NOTE: Current LocalBlurFx is effectively flawed. Following implementation
relies on the idea that
the blurring filter is separable and therefore applicable on rows and
2016-06-15 18:43:10 +12:00
columns, in sequence.
It actually is not. It can be easily verified applying the fx on a
chessboard with a strong
blur intensity. The squares will be cast vertically towards blurred
regions.
Originally, this was a sub-optimal O(lx * ly * blur) algorithm. The
following (still separated)
2016-03-19 06:57:51 +13:00
is O(lx * ly) using precomputed sums in an additional line.
2016-06-15 18:43:10 +12:00
The 'correct' algorithm could be implemented again as O(lx * ly * blur),
with additional O(lx * blur)
2016-03-19 06:57:51 +13:00
memory usage with precomputed sums along (blur) rows.
2016-06-15 18:43:10 +12:00
Pixels would have to be filtered one by one, but the horizontal
filtering per pixel convolution row
2016-03-19 06:57:51 +13:00
would take O(1). Thus it would be O(blur) (column filtering) per pixel.
*/
//********************************************************************************
// Local namespace stuff
//********************************************************************************
2016-06-15 18:43:10 +12:00
namespace {
2016-03-19 06:57:51 +13:00
struct Sums {
2016-06-15 18:43:10 +12:00
std::unique_ptr<TUINT64[]>
m_sumsIX_r; //!< m_sumsIX1[i+1] = m_sumsIX1[i] + i * pix.r
std::unique_ptr<TUINT64[]> m_sumsIX_g;
std::unique_ptr<TUINT64[]> m_sumsIX_b;
std::unique_ptr<TUINT64[]> m_sumsIX_m;
std::unique_ptr<TUINT64[]> m_sumsX_r; //!< m_sumsX[i+1] = m_sumsX[i] + pix.r
std::unique_ptr<TUINT64[]> m_sumsX_g;
std::unique_ptr<TUINT64[]> m_sumsX_b;
std::unique_ptr<TUINT64[]> m_sumsX_m;
Sums(int length)
: m_sumsIX_r(new TUINT64[length + 1])
, m_sumsIX_g(new TUINT64[length + 1])
, m_sumsIX_b(new TUINT64[length + 1])
, m_sumsIX_m(new TUINT64[length + 1])
, m_sumsX_r(new TUINT64[length + 1])
, m_sumsX_g(new TUINT64[length + 1])
, m_sumsX_b(new TUINT64[length + 1])
, m_sumsX_m(new TUINT64[length + 1]) {}
template <typename Pix>
void build(Pix *line, int wrap, int n) {
++n;
m_sumsIX_r[0] = m_sumsX_r[0] = 0;
m_sumsIX_g[0] = m_sumsX_g[0] = 0;
m_sumsIX_b[0] = m_sumsX_b[0] = 0;
m_sumsIX_m[0] = m_sumsX_m[0] = 0;
Pix *pix;
int i, i_1;
for (pix = line, i_1 = 0, i = 1; i < n; pix += wrap, i_1 = i++) {
m_sumsIX_r[i] = m_sumsIX_r[i_1] + i * pix->r;
m_sumsIX_g[i] = m_sumsIX_g[i_1] + i * pix->g;
m_sumsIX_b[i] = m_sumsIX_b[i_1] + i * pix->b;
m_sumsIX_m[i] = m_sumsIX_m[i_1] + i * pix->m;
m_sumsX_r[i] = m_sumsX_r[i_1] + pix->r;
m_sumsX_g[i] = m_sumsX_g[i_1] + pix->g;
m_sumsX_b[i] = m_sumsX_b[i_1] + pix->b;
m_sumsX_m[i] = m_sumsX_m[i_1] + pix->m;
}
}
2016-03-19 06:57:51 +13:00
};
//----------------------------------------------------------------------------
template <typename Pix, typename Grey>
2016-06-15 18:43:10 +12:00
void filterLine(Pix *lineIn, int wrapIn, Grey *lineGr, int wrapGr, Pix *lineOut,
int wrapOut, int length, double blurFactor, Sums &sums) {
// Build temporary sums
sums.build(lineIn, wrapIn, length);
// Declare vars
double blur, kLeft, kRight, cLeft, cRight;
double blurI;
// Perform line filtering
Pix *pixIn, *pixOut;
Grey *pixGr;
int i, iLeft, iRight;
++length;
for (i = 1, pixIn = lineIn, pixGr = lineGr, pixOut = lineOut; i < length;
++i, pixIn += wrapIn, pixGr += wrapGr, pixOut += wrapOut) {
blur = pixGr->value * blurFactor; // A table of factors should be made -
// since we have a finite
if (blur > 0.0) {
blur +=
0.5; /*-- 0.5足すのは、注目ピクセルの半径分。例えばBlur0.5は、
0.5
--*/
blurI = (double)tfloor(blur);
double amount = blur + (2 * blur - blurI - 1) * blurI;
double dR = 1.0 / amount;
double ini = (blur - blurI) / amount;
kLeft = dR;
cLeft = ini - dR * (i - blurI);
kRight = -dR;
cRight = blur / amount + dR * i;
// NOTE: The normalization factor with blur (not integer) would be:
// 1.0 / (1 + 2 * blurI - (blurI / blur) * (blurI + 1))
// -- could be done using a factors table
iLeft = std::max(i - tfloor(blur) - 1, 0);
iRight = std::min(i + tfloor(blur), length - 1);
pixOut->r =
troundp(kLeft * (sums.m_sumsIX_r[i] - sums.m_sumsIX_r[iLeft]) +
kRight * (sums.m_sumsIX_r[iRight] - sums.m_sumsIX_r[i]) +
cLeft * (sums.m_sumsX_r[i] - sums.m_sumsX_r[iLeft]) +
cRight * (sums.m_sumsX_r[iRight] - sums.m_sumsX_r[i]));
pixOut->g =
troundp(kLeft * (sums.m_sumsIX_g[i] - sums.m_sumsIX_g[iLeft]) +
kRight * (sums.m_sumsIX_g[iRight] - sums.m_sumsIX_g[i]) +
cLeft * (sums.m_sumsX_g[i] - sums.m_sumsX_g[iLeft]) +
cRight * (sums.m_sumsX_g[iRight] - sums.m_sumsX_g[i]));
pixOut->b =
troundp(kLeft * (sums.m_sumsIX_b[i] - sums.m_sumsIX_b[iLeft]) +
kRight * (sums.m_sumsIX_b[iRight] - sums.m_sumsIX_b[i]) +
cLeft * (sums.m_sumsX_b[i] - sums.m_sumsX_b[iLeft]) +
cRight * (sums.m_sumsX_b[iRight] - sums.m_sumsX_b[i]));
pixOut->m =
troundp(kLeft * (sums.m_sumsIX_m[i] - sums.m_sumsIX_m[iLeft]) +
kRight * (sums.m_sumsIX_m[iRight] - sums.m_sumsIX_m[i]) +
cLeft * (sums.m_sumsX_m[i] - sums.m_sumsX_m[iLeft]) +
cRight * (sums.m_sumsX_m[iRight] - sums.m_sumsX_m[i]));
} else
*pixOut = *pixIn;
}
2016-03-19 06:57:51 +13:00
}
//----------------------------------------------------------------------------
template <typename Pix, typename Grey>
2016-06-15 18:43:10 +12:00
void doLocalBlur(TRasterPT<Pix> rin, TRasterPT<Pix> rcontrol,
TRasterPT<Pix> rout, double blur, const TPoint &displacement) {
assert(rin->getLx() == rcontrol->getLx() &&
rin->getLy() == rcontrol->getLy());
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double blurFactor = blur / Grey::maxChannelValue;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
int x, y, inLx, inLy, outLx, outLy, wrapIn, wrapOut, wrapC;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
inLx = rin->getLx();
inLy = rin->getLy();
wrapIn = rin->getWrap();
outLx = rout->getLx();
outLy = rout->getLy();
wrapOut = rout->getWrap();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Convert the control raster to grey values (this avoids the overhead of
// performing the pixel-to-value
// conversion twice, once per line filtering)
TRasterPT<Grey> rcontrolGrey(rcontrol->getLx(), rcontrol->getLy());
TRop::convert(rcontrolGrey, rcontrol);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
wrapC = rcontrolGrey->getWrap();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
Pix *lineIn, *bufIn;
Grey *lineC, *bufC;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Filter rin. The output filtering is still stored in rin.
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
rin->lock();
rcontrolGrey->lock();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
bufIn = rin->pixels(0);
bufC = rcontrolGrey->pixels(0);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
{
Sums sums(inLx);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
for (y = 0, lineIn = bufIn, lineC = bufC; y < inLy;
++y, lineIn += wrapIn, lineC += wrapC) {
// Filter row
filterLine(lineIn, 1, lineC, 1, lineIn, 1, inLx, blurFactor, sums);
}
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
{
Sums sums(inLy);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
for (x = 0, lineIn = bufIn, lineC = bufC; x < inLx;
++x, ++lineIn, ++lineC) {
// Filter column
filterLine(lineIn, wrapIn, lineC, wrapC, lineIn, wrapIn, inLy, blurFactor,
sums);
}
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
rin->unlock();
rcontrolGrey->unlock();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
// Copy the interesting part of rin to rout
TRect rectOut(rout->getBounds() - displacement);
TRect rectIn(rin->getBounds() + displacement);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TRop::copy(rout->extract(rectIn), rin->extract(rectOut));
2016-03-19 06:57:51 +13:00
}
2016-06-15 18:43:10 +12:00
} // namespace
2016-03-19 06:57:51 +13:00
//********************************************************************************
// LocalBlurFx implementation
//********************************************************************************
class LocalBlurFx final : public TStandardRasterFx {
2016-06-15 18:43:10 +12:00
FX_PLUGIN_DECLARATION(LocalBlurFx)
2016-03-19 06:57:51 +13:00
protected:
2016-06-15 18:43:10 +12:00
TRasterFxPort m_up, m_ref;
TDoubleParamP m_value;
2016-03-19 06:57:51 +13:00
public:
2016-06-15 18:43:10 +12:00
LocalBlurFx() : m_value(20) {
m_value->setMeasureName("fxLength");
addInputPort("Source", m_up);
addInputPort("Reference", m_ref);
bindParam(this, "value", m_value);
m_value->setValueRange(0, (std::numeric_limits<double>::max)());
}
~LocalBlurFx() {}
2016-06-19 20:06:29 +12:00
bool canHandle(const TRenderSettings &info, double frame) override {
2016-06-15 18:43:10 +12:00
return (isAlmostIsotropic(info.m_affine) || m_value->getValue(frame) == 0);
}
2016-06-20 14:23:05 +12:00
bool doGetBBox(double frame, TRectD &bBox,
const TRenderSettings &info) override {
2016-06-15 18:43:10 +12:00
if (m_up.isConnected()) {
bool ret = m_up->doGetBBox(frame, bBox, info);
double blur = fabs(m_value->getValue(frame));
int blurI = tceil(blur);
bBox = bBox.enlarge(blurI);
return ret;
} else {
bBox = TRectD();
return false;
}
}
void enlarge(const TRectD &bbox, TRectD &requestedRect, int blur);
2016-06-20 14:23:05 +12:00
void doDryCompute(TRectD &rect, double frame,
const TRenderSettings &info) override;
void doCompute(TTile &tile, double frame,
const TRenderSettings &info) override;
2016-06-15 18:43:10 +12:00
int getMemoryRequirement(const TRectD &rect, double frame,
2016-06-19 20:06:29 +12:00
const TRenderSettings &info) override;
2016-03-19 06:57:51 +13:00
};
//-------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void LocalBlurFx::enlarge(const TRectD &bbox, TRectD &requestedRect, int blur) {
// See BlurFx: this is a faithful replica
if (bbox.isEmpty() || requestedRect.isEmpty()) {
requestedRect.empty();
return;
}
TRectD enlargedBBox(bbox.enlarge(blur));
TRectD enlargedOut(requestedRect.enlarge(blur));
TPointD originalP00(requestedRect.getP00());
requestedRect = (enlargedOut * bbox) + (enlargedBBox * requestedRect);
requestedRect -= originalP00;
requestedRect.x0 = tfloor(requestedRect.x0);
requestedRect.y0 = tfloor(requestedRect.y0);
requestedRect.x1 = tceil(requestedRect.x1);
requestedRect.y1 = tceil(requestedRect.y1);
requestedRect += originalP00;
2016-03-19 06:57:51 +13:00
}
//-------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void LocalBlurFx::doDryCompute(TRectD &rectOut, double frame,
const TRenderSettings &info) {
// Mimics the doCompute() without actual computation.
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TTile refTile;
bool isUp = m_up.isConnected();
bool isDown = m_ref.isConnected();
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (!isUp) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (!isDown) {
m_up->dryCompute(rectOut, frame, info);
return;
}
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
double blur =
fabs(m_value->getValue(frame) * sqrt(fabs(info.m_affine.det())));
int blurI = tceil(blur);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TRectD bboxIn;
if (!m_up->getBBox(frame, bboxIn, info) || rectOut.isEmpty()) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
TRectD rectIn(rectOut);
enlarge(bboxIn, rectIn, blurI);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
if (rectIn.isEmpty()) return;
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
m_up->dryCompute(rectOut, frame, info);
m_ref->dryCompute(rectOut, frame, info);
2016-03-19 06:57:51 +13:00
}
//-------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
void LocalBlurFx::doCompute(TTile &tile, double frame,
const TRenderSettings &info) {
TTile refTile;
bool isUp = m_up.isConnected();
bool isDown = m_ref.isConnected();
if (!isUp) return;
if (!isDown) {
m_up->compute(tile, frame, info);
return;
}
// Generic case
assert(isAlmostIsotropic(info.m_affine));
double blur =
fabs(m_value->getValue(frame) * sqrt(fabs(info.m_affine.det())));
int blurI = tceil(blur);
// Get the requested tile's geometry
TRectD rectOut(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
tile.getRaster()->getLy()));
// Retrieve the input interesting geometry - and ensure that something
// actually has
// to be computed
TRectD bboxIn;
if (!m_up->getBBox(frame, bboxIn, info) || rectOut.isEmpty()) return;
TRectD rectIn(rectOut);
enlarge(bboxIn, rectIn, blurI);
if (rectIn.isEmpty()) return;
// Finally, allocate and compute the blur argument
TTile tileIn;
m_up->allocateAndCompute(tileIn, rectIn.getP00(),
TDimension(rectIn.getLx(), rectIn.getLy()),
tile.getRaster(), frame, info);
TTile tileRef;
m_ref->allocateAndCompute(tileRef, rectIn.getP00(),
TDimension(rectIn.getLx(), rectIn.getLy()),
tile.getRaster(), frame, info);
// Perform Local Blur
TRasterP inRas(tileIn.getRaster());
TRasterP refRas(tileRef.getRaster());
TRasterP outRas(tile.getRaster());
TRaster32P in32(inRas);
TRaster32P ref32(refRas);
TRaster32P out32(outRas);
TPoint displacement(convert(
rectIn.getP00() - tile.m_pos)); // inTile position relative to (out)tile
// The difference already has integer coordinates due to enlarge()
if (in32 && ref32 && out32)
doLocalBlur<TPixelRGBM32, TPixelGR8>(in32, ref32, out32, blur,
displacement);
else {
TRaster64P in64(inRas);
TRaster64P ref64(refRas);
TRaster64P out64(outRas);
if (in64 && ref64 && out64)
doLocalBlur<TPixelRGBM64, TPixelGR16>(in64, ref64, out64, blur,
displacement);
else
throw TException("LocalBlurFx: unsupported raster type");
}
2016-03-19 06:57:51 +13:00
}
//-------------------------------------------------------------------
2016-06-15 18:43:10 +12:00
int LocalBlurFx::getMemoryRequirement(const TRectD &rect, double frame,
const TRenderSettings &info) {
double blur =
fabs(m_value->getValue(frame) * sqrt(fabs(info.m_affine.det())));
int blurI = tceil(blur);
2016-03-19 06:57:51 +13:00
2016-06-15 18:43:10 +12:00
return 2 * TRasterFx::memorySize(rect.enlarge(blurI), info.m_bpp);
2016-03-19 06:57:51 +13:00
}
//-------------------------------------------------------------------
FX_PLUGIN_IDENTIFIER(LocalBlurFx, "localBlurFx")