1 #include "iwa_corridorgradientfx.h"
2 
3 #include "trop.h"
4 #include "tparamuiconcept.h"
5 #include "tspectrumparam.h"
6 #include "gradients.h"
7 
8 #include <QPolygonF>
9 
10 #include <array>
11 #include <algorithm>
12 
13 //------------------------------------------------------------
14 
Iwa_CorridorGradientFx()15 Iwa_CorridorGradientFx::Iwa_CorridorGradientFx()
16     : m_shape(new TIntEnumParam(0, "Quadrangle"))
17     , m_innerColor(TPixel32::White)
18     , m_outerColor(TPixel32::Black)
19     , m_curveType(new TIntEnumParam()) {
20   for (int inout = 0; inout < 2; inout++) {
21     double size           = (inout == 0) ? 50. : 400.;
22     std::string inout_str = (inout == 0) ? "_in" : "_out";
23 
24     for (int c = 0; c < 4; c++) {
25       Qt::Corner corner = (Qt::Corner)c;
26       TPointD basePos(1, 1);
27       if (corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner)
28         basePos.x *= -1;
29       if (corner == Qt::BottomLeftCorner || corner == Qt::BottomRightCorner)
30         basePos.y *= -1;
31 
32       m_points[inout][corner] = basePos * size;
33 
34       m_points[inout][corner]->getX()->setMeasureName("fxLength");
35       m_points[inout][corner]->getY()->setMeasureName("fxLength");
36 
37       std::string TB_str =
38           (corner == Qt::TopLeftCorner || corner == Qt::TopRightCorner)
39               ? "top"
40               : "bottom";
41       std::string LR_str =
42           (corner == Qt::TopLeftCorner || corner == Qt::BottomLeftCorner)
43               ? "_left"
44               : "_right";
45 
46       bindParam(this, TB_str + LR_str + inout_str, m_points[inout][corner]);
47     }
48   }
49 
50   m_shape->addItem(1, "Circle");
51   bindParam(this, "shape", m_shape);
52 
53   m_curveType->addItem(EaseInOut, "Ease In-Out");
54   m_curveType->addItem(Linear, "Linear");
55   m_curveType->addItem(EaseIn, "Ease In");
56   m_curveType->addItem(EaseOut, "Ease Out");
57   m_curveType->setDefaultValue(Linear);
58   m_curveType->setValue(Linear);
59   bindParam(this, "curveType", m_curveType);
60 
61   bindParam(this, "inner_color", m_innerColor);
62   bindParam(this, "outer_color", m_outerColor);
63 }
64 
65 //------------------------------------------------------------
66 
doGetBBox(double frame,TRectD & bBox,const TRenderSettings & ri)67 bool Iwa_CorridorGradientFx::doGetBBox(double frame, TRectD &bBox,
68                                        const TRenderSettings &ri) {
69   bBox = TConsts::infiniteRectD;
70   return true;
71 }
72 
73 //------------------------------------------------------------
74 
75 namespace {
76 
toQPointF(const TPointD & p)77 QPointF toQPointF(const TPointD &p) { return QPointF(p.x, p.y); }
78 
WedgeProduct(const TPointD v,const TPointD w)79 double WedgeProduct(const TPointD v, const TPointD w) {
80   return v.x * w.y - v.y * w.x;
81 }
82 
83 struct BilinearParam {
84   TPointD p0, b1, b2, b3;
85 };
86 
87 //------------------------------------------------------------
88 
getFactor(const TPointD & p,const BilinearParam & param,const GradientCurveType type)89 double getFactor(const TPointD &p, const BilinearParam &param,
90                  const GradientCurveType type) {
91   double t;
92   TPointD q = p - param.p0;
93   // Set up quadratic formula
94   float A = WedgeProduct(param.b2, param.b3);
95   float B = WedgeProduct(param.b3, q) - WedgeProduct(param.b1, param.b2);
96   float C = WedgeProduct(param.b1, q);
97 
98   // Solve for v
99   if (std::abs(A) < 0.001) {
100     // Linear form
101     t = -C / B;
102   } else {
103     // Quadratic form
104     float discrim = B * B - 4 * A * C;
105     t             = 0.5 * (-B - std::sqrt(discrim)) / A;
106   }
107   double factor;
108   switch (type) {
109   case Linear:
110     factor = t;
111     break;
112   case EaseIn:
113     factor = t * t;
114     break;
115   case EaseOut:
116     factor = 1.0 - (1.0 - t) * (1.0 - t);
117     break;
118   case EaseInOut:
119   default:
120     factor = (-2 * t + 3) * (t * t);
121     break;
122   }
123   return factor;
124 }
125 
126 //------------------------------------------------------------
127 
128 template <typename RASTER, typename PIXEL>
doQuadrangleT(RASTER ras,TDimensionI dim,TPointD pos[2][4],const TSpectrumT<PIXEL> & spectrum,GradientCurveType type)129 void doQuadrangleT(RASTER ras, TDimensionI dim, TPointD pos[2][4],
130                    const TSpectrumT<PIXEL> &spectrum, GradientCurveType type) {
131   auto buildPolygon = [&](QPolygonF &pol, Qt::Corner c1, Qt::Corner c2) {
132     pol << toQPointF(pos[0][(int)c1]) << toQPointF(pos[1][(int)c1])
133         << toQPointF(pos[1][(int)c2]) << toQPointF(pos[0][(int)c2]);
134   };
135 
136   auto buildBilinearParam = [&](BilinearParam &bp, Qt::Corner c1,
137                                 Qt::Corner c2) {
138     bp.p0 = pos[0][(int)c1];
139     bp.b1 = pos[0][(int)c2] - pos[0][(int)c1];
140     bp.b2 = pos[1][(int)c1] - pos[0][(int)c1];
141     bp.b3 =
142         pos[0][(int)c1] - pos[0][(int)c2] - pos[1][(int)c1] + pos[1][(int)c2];
143   };
144 
145   std::array<QPolygonF, 4> polygons;
146   std::array<BilinearParam, 4> params;
147 
148   // Top
149   buildPolygon(polygons[0], Qt::TopLeftCorner, Qt::TopRightCorner);
150   buildBilinearParam(params[0], Qt::TopLeftCorner, Qt::TopRightCorner);
151   // Left
152   buildPolygon(polygons[1], Qt::BottomLeftCorner, Qt::TopLeftCorner);
153   buildBilinearParam(params[1], Qt::BottomLeftCorner, Qt::TopLeftCorner);
154   // Bottom
155   buildPolygon(polygons[2], Qt::BottomRightCorner, Qt::BottomLeftCorner);
156   buildBilinearParam(params[2], Qt::BottomRightCorner, Qt::BottomLeftCorner);
157   // Right
158   buildPolygon(polygons[3], Qt::TopRightCorner, Qt::BottomRightCorner);
159   buildBilinearParam(params[3], Qt::TopRightCorner, Qt::BottomRightCorner);
160 
161   QPolygonF innerPolygon;
162   innerPolygon << toQPointF(pos[0][Qt::TopLeftCorner])
163                << toQPointF(pos[0][Qt::TopRightCorner])
164                << toQPointF(pos[0][Qt::BottomRightCorner])
165                << toQPointF(pos[0][Qt::BottomLeftCorner]);
166 
167   ras->lock();
168   for (int j = 0; j < ras->getLy(); j++) {
169     PIXEL *pix    = ras->pixels(j);
170     PIXEL *endPix = pix + ras->getLx();
171     int i         = 0;
172     while (pix < endPix) {
173       QPointF p(i, j);
174 
175       double factor;
176       bool found = false;
177       for (int edge = 0; edge < 4; edge++) {
178         if (polygons[edge].containsPoint(p, Qt::WindingFill)) {
179           factor = getFactor(TPointD(i, j), params.at(edge), type);
180           found  = true;
181           break;
182         }
183       }
184       if (!found) {
185         if (innerPolygon.containsPoint(p, Qt::WindingFill))
186           factor = 0.0;
187         else
188           factor = 1.0;
189       }
190 
191       *pix++ = spectrum.getPremultipliedValue(factor);
192       i++;
193     }
194   }
195   ras->unlock();
196 }
197 
198 //------------------------------------------------------------
199 
200 template <typename RASTER, typename PIXEL>
doCircleT(RASTER ras,TDimensionI dim,TPointD pos[2][4],const TSpectrumT<PIXEL> & spectrum,GradientCurveType type)201 void doCircleT(RASTER ras, TDimensionI dim, TPointD pos[2][4],
202                const TSpectrumT<PIXEL> &spectrum, GradientCurveType type) {
203   auto lerp = [](TPointD p1, TPointD p2, double f) {
204     return p1 * (1 - f) + p2 * f;
205   };
206   auto bilinearPos = [&](TPointD uv, int inout) {
207     return lerp(lerp(pos[inout][Qt::BottomLeftCorner],
208                      pos[inout][Qt::BottomRightCorner], uv.x),
209                 lerp(pos[inout][Qt::TopLeftCorner],
210                      pos[inout][Qt::TopRightCorner], uv.x),
211                 uv.y);
212   };
213 
214   const int DIVNUM = 36;
215 
216   std::array<TPointD, DIVNUM> innerPos;
217   std::array<TPointD, DIVNUM> outerPos;
218   double tmpRadius = std::sqrt(2.0) / 2.0;
219   for (int div = 0; div < DIVNUM; div++) {
220     double angle = 2.0 * M_PI * (double)div / (double)DIVNUM;
221     // circle position in uv coordinate
222     TPointD uv(tmpRadius * std::cos(angle) + 0.5,
223                tmpRadius * std::sin(angle) + 0.5);
224     // compute inner and outer circle positions by bilinear interpolation
225     // using uv coordinate values.
226     innerPos[div] = bilinearPos(uv, 0);
227     outerPos[div] = bilinearPos(uv, 1);
228   }
229 
230   // - - - - - - - -
231 
232   auto buildPolygon = [&](QPolygonF &pol, int id1, int id2) {
233     pol << toQPointF(innerPos[id2]) << toQPointF(outerPos[id2])
234         << toQPointF(outerPos[id1]) << toQPointF(innerPos[id1]);
235   };
236 
237   auto buildBilinearParam = [&](BilinearParam &bp, int id1, int id2) {
238     bp.p0 = innerPos[id2];
239     bp.b1 = innerPos[id1] - innerPos[id2];
240     bp.b2 = outerPos[id2] - innerPos[id2];
241     bp.b3 = innerPos[id2] - innerPos[id1] - outerPos[id2] + outerPos[id1];
242   };
243   std::array<QPolygonF, DIVNUM> polygons;
244   std::array<BilinearParam, DIVNUM> params;
245   QPolygonF innerPolygon;
246   for (int div = 0; div < DIVNUM; div++) {
247     int next_div = (div == DIVNUM - 1) ? 0 : div + 1;
248     // create polygon and bilinear parameters for each piece surrounding the
249     // circle
250     buildPolygon(polygons[div], div, next_div);
251     buildBilinearParam(params[div], div, next_div);
252     // create inner circle polygon
253     innerPolygon << toQPointF(innerPos[div]);
254   }
255 
256   // - - - ok, ready to render
257 
258   ras->lock();
259 
260   for (int j = 0; j < ras->getLy(); j++) {
261     PIXEL *pix    = ras->pixels(j);
262     PIXEL *endPix = pix + ras->getLx();
263     int i         = 0;
264     while (pix < endPix) {
265       QPointF p(i, j);
266       double factor;
267       bool found = false;
268       for (int div = 0; div < DIVNUM; div++) {
269         // check if the point is inside of the surrounding pieces
270         if (polygons[div].containsPoint(p, Qt::WindingFill)) {
271           // compute factor by invert bilinear interpolation
272           factor = getFactor(TPointD(i, j), params.at(div), type);
273           found  = true;
274           break;
275         }
276       }
277       if (!found) {
278         if (innerPolygon.containsPoint(p, Qt::WindingFill))
279           factor = 0.0;
280         else
281           factor = 1.0;
282       }
283 
284       *pix++ = spectrum.getPremultipliedValue(factor);
285 
286       i++;
287     }
288   }
289   ras->unlock();
290 }
291 
292 };  // namespace
293 
294 //------------------------------------------------------------
295 
doCompute(TTile & tile,double frame,const TRenderSettings & ri)296 void Iwa_CorridorGradientFx::doCompute(TTile &tile, double frame,
297                                        const TRenderSettings &ri) {
298   if (!((TRaster32P)tile.getRaster()) && !((TRaster64P)tile.getRaster())) {
299     throw TRopException("unsupported input pixel type");
300   }
301 
302   // convert shape position to render region coordinate
303   TPointD pos[2][4];
304   TAffine aff = ri.m_affine;
305   TDimensionI dimOut(tile.getRaster()->getLx(), tile.getRaster()->getLy());
306   TPointD dimOffset((float)dimOut.lx / 2.0f, (float)dimOut.ly / 2.0f);
307   for (int inout = 0; inout < 2; inout++) {
308     for (int c = 0; c < 4; c++) {
309       TPointD _point = m_points[inout][c]->getValue(frame);
310       pos[inout][c]  = aff * _point -
311                       (tile.m_pos + tile.getRaster()->getCenterD()) + dimOffset;
312     }
313   }
314 
315   std::vector<TSpectrum::ColorKey> colors = {
316       TSpectrum::ColorKey(0, m_innerColor->getValue(frame)),
317       TSpectrum::ColorKey(1, m_outerColor->getValue(frame))};
318   TSpectrumParamP m_colors = TSpectrumParamP(colors);
319 
320   tile.getRaster()->clear();
321   TRaster32P outRas32 = (TRaster32P)tile.getRaster();
322   TRaster64P outRas64 = (TRaster64P)tile.getRaster();
323   if (m_shape->getValue() == 0) {  // Quadrangle
324     if (outRas32)
325       doQuadrangleT<TRaster32P, TPixel32>(
326           outRas32, dimOut, pos, m_colors->getValue(frame),
327           (GradientCurveType)m_curveType->getValue());
328     else if (outRas64)
329       doQuadrangleT<TRaster64P, TPixel64>(
330           outRas64, dimOut, pos, m_colors->getValue64(frame),
331           (GradientCurveType)m_curveType->getValue());
332   } else {  // m_shape == 1 : Circle
333     if (outRas32)
334       doCircleT<TRaster32P, TPixel32>(
335           outRas32, dimOut, pos, m_colors->getValue(frame),
336           (GradientCurveType)m_curveType->getValue());
337     else if (outRas64)
338       doCircleT<TRaster64P, TPixel64>(
339           outRas64, dimOut, pos, m_colors->getValue64(frame),
340           (GradientCurveType)m_curveType->getValue());
341   }
342 }
343 
344 //------------------------------------------------------------
345 
getParamUIs(TParamUIConcept * & concepts,int & length)346 void Iwa_CorridorGradientFx::getParamUIs(TParamUIConcept *&concepts,
347                                          int &length) {
348   concepts = new TParamUIConcept[length = 6];
349 
350   int vectorUiIdOffset = 2;
351 
352   std::array<Qt::Corner, 4> loopIds{Qt::TopLeftCorner, Qt::TopRightCorner,
353                                     Qt::BottomRightCorner,
354                                     Qt::BottomLeftCorner};
355 
356   for (int inout = 0; inout < 2; inout++) {
357     concepts[inout].m_type = TParamUIConcept::QUAD;
358 
359     for (int c = 0; c < 4; c++) {
360       Qt::Corner corner = loopIds[c];
361 
362       // quad ui
363       concepts[inout].m_params.push_back(m_points[inout][(int)corner]);
364       concepts[inout].m_label = (inout == 0) ? " In" : " Out";
365 
366       // vector ui
367       if (inout == 0)
368         concepts[vectorUiIdOffset + (int)corner].m_type =
369             TParamUIConcept::VECTOR;
370       concepts[vectorUiIdOffset + (int)corner].m_params.push_back(
371           m_points[inout][(int)corner]);
372     }
373   }
374 }
375 
376 //------------------------------------------------------------
377 
378 FX_PLUGIN_IDENTIFIER(Iwa_CorridorGradientFx, "iwa_CorridorGradientFx");
379