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 ¶m,
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