#include "iwa_corridorgradientfx.h"
#include "trop.h"
#include "tparamuiconcept.h"
#include "tspectrumparam.h"
#include "gradients.h"
#include <QPolygonF>
#include <array>
#include <algorithm>
//------------------------------------------------------------
Iwa_CorridorGradientFx::Iwa_CorridorGradientFx()
: m_shape(new TIntEnumParam(0, "Quadrangle"))
, m_innerColor(TPixel32::White)
, m_outerColor(TPixel32::Black)
, m_curveType(new TIntEnumParam()) {
for (int inout = 0; inout < 2; inout++) {
double size = (inout == 0) ? 50. : 400.;
std::string inout_str = (inout == 0) ? "_in" : "_out";
for (int c = 0; c < 4; c++) {
Qt::Corner corner = (Qt::Corner)c;
TPointD basePos(1, 1);
if (corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner)
basePos.x *= -1;
if (corner == Qt::BottomLeftCorner || corner == Qt::BottomRightCorner)
basePos.y *= -1;
m_points[inout][corner] = basePos * size;
m_points[inout][corner]->getX()->setMeasureName("fxLength");
m_points[inout][corner]->getY()->setMeasureName("fxLength");
std::string TB_str =
(corner == Qt::TopLeftCorner || corner == Qt::TopRightCorner)
? "top"
: "bottom";
std::string LR_str =
(corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner)
? "_left"
: "_right";
bindParam(this, TB_str + LR_str + inout_str, m_points[inout][corner]);
}
}
m_shape->addItem(1, "Circle");
bindParam(this, "shape", m_shape);
m_curveType->addItem(EaseInOut, "Ease In-Out");
m_curveType->addItem(Linear, "Linear");
m_curveType->addItem(EaseIn, "Ease In");
m_curveType->addItem(EaseOut, "Ease Out");
m_curveType->setDefaultValue(Linear);
m_curveType->setValue(Linear);
bindParam(this, "curveType", m_curveType);
bindParam(this, "inner_color", m_innerColor);
bindParam(this, "outer_color", m_outerColor);
enableComputeInFloat(true);
}
//------------------------------------------------------------
bool Iwa_CorridorGradientFx::doGetBBox(double frame, TRectD &bBox,
const TRenderSettings &ri) {
bBox = TConsts::infiniteRectD;
return true;
}
//------------------------------------------------------------
namespace {
QPointF toQPointF(const TPointD &p) { return QPointF(p.x, p.y); }
double WedgeProduct(const TPointD v, const TPointD w) {
return v.x * w.y - v.y * w.x;
}
struct BilinearParam {
TPointD p0, b1, b2, b3;
};
//------------------------------------------------------------
double getFactor(const TPointD &p, const BilinearParam ¶m,
const GradientCurveType type) {
double t;
TPointD q = p - param.p0;
// Set up quadratic formula
float A = WedgeProduct(param.b2, param.b3);
float B = WedgeProduct(param.b3, q) - WedgeProduct(param.b1, param.b2);
float C = WedgeProduct(param.b1, q);
// Solve for v
if (std::abs(A) < 0.001) {
// Linear form
t = -C / B;
} else {
// Quadratic form
float discrim = B * B - 4 * A * C;
t = 0.5 * (-B - std::sqrt(discrim)) / A;
}
double factor;
switch (type) {
case Linear:
factor = t;
break;
case EaseIn:
factor = t * t;
break;
case EaseOut:
factor = 1.0 - (1.0 - t) * (1.0 - t);
break;
case EaseInOut:
default:
factor = (-2 * t + 3) * (t * t);
break;
}
return factor;
}
//------------------------------------------------------------
template <typename RASTER, typename PIXEL>
void doQuadrangleT(RASTER ras, TDimensionI dim, TPointD pos[2][4],
const TSpectrumT<PIXEL> &spectrum, GradientCurveType type) {
auto buildPolygon = [&](QPolygonF &pol, Qt::Corner c1, Qt::Corner c2) {
pol << toQPointF(pos[0][(int)c1]) << toQPointF(pos[1][(int)c1])
<< toQPointF(pos[1][(int)c2]) << toQPointF(pos[0][(int)c2]);
};
auto buildBilinearParam = [&](BilinearParam &bp, Qt::Corner c1,
Qt::Corner c2) {
bp.p0 = pos[0][(int)c1];
bp.b1 = pos[0][(int)c2] - pos[0][(int)c1];
bp.b2 = pos[1][(int)c1] - pos[0][(int)c1];
bp.b3 =
pos[0][(int)c1] - pos[0][(int)c2] - pos[1][(int)c1] + pos[1][(int)c2];
};
std::array<QPolygonF, 4> polygons;
std::array<BilinearParam, 4> params;
// Top
buildPolygon(polygons[0], Qt::TopLeftCorner, Qt::TopRightCorner);
buildBilinearParam(params[0], Qt::TopLeftCorner, Qt::TopRightCorner);
// Left
buildPolygon(polygons[1], Qt::BottomLeftCorner, Qt::TopLeftCorner);
buildBilinearParam(params[1], Qt::BottomLeftCorner, Qt::TopLeftCorner);
// Bottom
buildPolygon(polygons[2], Qt::BottomRightCorner, Qt::BottomLeftCorner);
buildBilinearParam(params[2], Qt::BottomRightCorner, Qt::BottomLeftCorner);
// Right
buildPolygon(polygons[3], Qt::TopRightCorner, Qt::BottomRightCorner);
buildBilinearParam(params[3], Qt::TopRightCorner, Qt::BottomRightCorner);
QPolygonF innerPolygon;
innerPolygon << toQPointF(pos[0][Qt::TopLeftCorner])
<< toQPointF(pos[0][Qt::TopRightCorner])
<< toQPointF(pos[0][Qt::BottomRightCorner])
<< toQPointF(pos[0][Qt::BottomLeftCorner]);
ras->lock();
for (int j = 0; j < ras->getLy(); j++) {
PIXEL *pix = ras->pixels(j);
PIXEL *endPix = pix + ras->getLx();
int i = 0;
while (pix < endPix) {
QPointF p(i, j);
double factor;
bool found = false;
for (int edge = 0; edge < 4; edge++) {
if (polygons[edge].containsPoint(p, Qt::WindingFill)) {
factor = getFactor(TPointD(i, j), params.at(edge), type);
found = true;
break;
}
}
if (!found) {
if (innerPolygon.containsPoint(p, Qt::WindingFill))
factor = 0.0;
else
factor = 1.0;
}
*pix++ = spectrum.getPremultipliedValue(factor);
i++;
}
}
ras->unlock();
}
//------------------------------------------------------------
template <typename RASTER, typename PIXEL>
void doCircleT(RASTER ras, TDimensionI dim, TPointD pos[2][4],
const TSpectrumT<PIXEL> &spectrum, GradientCurveType type) {
auto lerp = [](TPointD p1, TPointD p2, double f) {
return p1 * (1 - f) + p2 * f;
};
auto bilinearPos = [&](TPointD uv, int inout) {
return lerp(lerp(pos[inout][Qt::BottomLeftCorner],
pos[inout][Qt::BottomRightCorner], uv.x),
lerp(pos[inout][Qt::TopLeftCorner],
pos[inout][Qt::TopRightCorner], uv.x),
uv.y);
};
const int DIVNUM = 36;
std::array<TPointD, DIVNUM> innerPos;
std::array<TPointD, DIVNUM> outerPos;
double tmpRadius = std::sqrt(2.0) / 2.0;
for (int div = 0; div < DIVNUM; div++) {
double angle = 2.0 * M_PI * (double)div / (double)DIVNUM;
// circle position in uv coordinate
TPointD uv(tmpRadius * std::cos(angle) + 0.5,
tmpRadius * std::sin(angle) + 0.5);
// compute inner and outer circle positions by bilinear interpolation
// using uv coordinate values.
innerPos[div] = bilinearPos(uv, 0);
outerPos[div] = bilinearPos(uv, 1);
}
// - - - - - - - -
auto buildPolygon = [&](QPolygonF &pol, int id1, int id2) {
pol << toQPointF(innerPos[id2]) << toQPointF(outerPos[id2])
<< toQPointF(outerPos[id1]) << toQPointF(innerPos[id1]);
};
auto buildBilinearParam = [&](BilinearParam &bp, int id1, int id2) {
bp.p0 = innerPos[id2];
bp.b1 = innerPos[id1] - innerPos[id2];
bp.b2 = outerPos[id2] - innerPos[id2];
bp.b3 = innerPos[id2] - innerPos[id1] - outerPos[id2] + outerPos[id1];
};
std::array<QPolygonF, DIVNUM> polygons;
std::array<BilinearParam, DIVNUM> params;
QPolygonF innerPolygon;
for (int div = 0; div < DIVNUM; div++) {
int next_div = (div == DIVNUM - 1) ? 0 : div + 1;
// create polygon and bilinear parameters for each piece surrounding the
// circle
buildPolygon(polygons[div], div, next_div);
buildBilinearParam(params[div], div, next_div);
// create inner circle polygon
innerPolygon << toQPointF(innerPos[div]);
}
// - - - ok, ready to render
ras->lock();
for (int j = 0; j < ras->getLy(); j++) {
PIXEL *pix = ras->pixels(j);
PIXEL *endPix = pix + ras->getLx();
int i = 0;
while (pix < endPix) {
QPointF p(i, j);
double factor;
bool found = false;
for (int div = 0; div < DIVNUM; div++) {
// check if the point is inside of the surrounding pieces
if (polygons[div].containsPoint(p, Qt::WindingFill)) {
// compute factor by invert bilinear interpolation
factor = getFactor(TPointD(i, j), params.at(div), type);
found = true;
break;
}
}
if (!found) {
if (innerPolygon.containsPoint(p, Qt::WindingFill))
factor = 0.0;
else
factor = 1.0;
}
*pix++ = spectrum.getPremultipliedValue(factor);
i++;
}
}
ras->unlock();
}
}; // namespace
//------------------------------------------------------------
void Iwa_CorridorGradientFx::doCompute(TTile &tile, double frame,
const TRenderSettings &ri) {
if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster()) &&
!((TRasterFP)tile.getRaster())) {
throw TRopException("unsupported input pixel type");
}
// convert shape position to render region coordinate
TPointD pos[2][4];
TAffine aff = ri.m_affine;
TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy());
TPointD dimOffset((float)dimOut.lx / 2.0f, (float)dimOut.ly / 2.0f);
for (int inout = 0; inout < 2; inout++) {
for (int c = 0; c < 4; c++) {
TPointD _point = m_points[inout][c]->getValue(frame);
pos[inout][c] = aff * _point -
(tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset;
}
}
std::vector<TSpectrum::ColorKey> colors = {
TSpectrum::ColorKey(0, m_innerColor->getValue(frame)),
TSpectrum::ColorKey(1, m_outerColor->getValue(frame))};
TSpectrumParamP m_colors = TSpectrumParamP(colors);
tile.getRaster()->clear();
TRaster32P outRas32 = (TRaster32P)tile.getRaster();
TRaster64P outRas64 = (TRaster64P)tile.getRaster();
TRasterFP outRasF = (TRasterFP)tile.getRaster();
if (m_shape->getValue() == 0) { // Quadrangle
if (outRas32)
doQuadrangleT<TRaster32P, TPixel32>(
outRas32, dimOut, pos, m_colors->getValue(frame),
(GradientCurveType)m_curveType->getValue());
else if (outRas64)
doQuadrangleT<TRaster64P, TPixel64>(
outRas64, dimOut, pos, m_colors->getValue64(frame),
(GradientCurveType)m_curveType->getValue());
else if (outRasF)
doQuadrangleT<TRasterFP, TPixelF>(
outRasF, dimOut, pos, m_colors->getValueF(frame),
(GradientCurveType)m_curveType->getValue());
} else { // m_shape == 1 : Circle
if (outRas32)
doCircleT<TRaster32P, TPixel32>(
outRas32, dimOut, pos, m_colors->getValue(frame),
(GradientCurveType)m_curveType->getValue());
else if (outRas64)
doCircleT<TRaster64P, TPixel64>(
outRas64, dimOut, pos, m_colors->getValue64(frame),
(GradientCurveType)m_curveType->getValue());
else if (outRasF)
doCircleT<TRasterFP, TPixelF>(outRasF, dimOut, pos,
m_colors->getValueF(frame),
(GradientCurveType)m_curveType->getValue());
}
}
//------------------------------------------------------------
void Iwa_CorridorGradientFx::getParamUIs(TParamUIConcept *&concepts,
int &length) {
concepts = new TParamUIConcept[length = 6];
int vectorUiIdOffset = 2;
std::array<Qt::Corner, 4> loopIds{Qt::TopLeftCorner, Qt::TopRightCorner,
Qt::BottomRightCorner,
Qt::BottomLeftCorner};
for (int inout = 0; inout < 2; inout++) {
concepts[inout].m_type = TParamUIConcept::QUAD;
for (int c = 0; c < 4; c++) {
Qt::Corner corner = loopIds[c];
// quad ui
concepts[inout].m_params.push_back(m_points[inout][(int)corner]);
concepts[inout].m_label = (inout == 0) ? " In" : " Out";
// vector ui
if (inout == 0)
concepts[vectorUiIdOffset + (int)corner].m_type =
TParamUIConcept::VECTOR;
concepts[vectorUiIdOffset + (int)corner].m_params.push_back(
m_points[inout][(int)corner]);
}
}
}
//------------------------------------------------------------
FX_PLUGIN_IDENTIFIER(Iwa_CorridorGradientFx, "iwa_CorridorGradientFx");