1 /*------------------------------------
2 Iwa_BarrelDistortFx
3 Generates the barrel/pincushion distort.
4 Based on an approximated model for radial distortion
5 by Fitzgibbon, 2001
6 ------------------------------------*/
7 
8 #include "stdfx.h"
9 #include "tfxparam.h"
10 #include "tparamset.h"
11 #include "tparamuiconcept.h"
12 
13 #include <QVector2D>
14 #include <QPointF>
15 
16 namespace {
17 struct float4 {
18   float x, y, z, w;
19 };
20 /*------------------------------------------------------------
21 read the source image, normalize to 0 - 1
22 ------------------------------------------------------------*/
23 template <typename RASTER, typename PIXEL>
setSourceRaster(const RASTER srcRas,float4 * dstMem,TDimensionI dim)24 void setSourceRaster(const RASTER srcRas, float4 *dstMem, TDimensionI dim) {
25   float4 *chann_p = dstMem;
26   for (int j = 0; j < dim.ly; j++) {
27     PIXEL *pix = srcRas->pixels(j);
28     for (int i = 0; i < dim.lx; i++, pix++, chann_p++) {
29       (*chann_p).x = (float)pix->r / (float)PIXEL::maxChannelValue;
30       (*chann_p).y = (float)pix->g / (float)PIXEL::maxChannelValue;
31       (*chann_p).z = (float)pix->b / (float)PIXEL::maxChannelValue;
32       (*chann_p).w = (float)pix->m / (float)PIXEL::maxChannelValue;
33     }
34   }
35 }
36 
37 /*------------------------------------------------------------
38 convert the result to channel value and store to the output raster
39 ------------------------------------------------------------*/
40 template <typename RASTER, typename PIXEL>
setOutputRaster(float4 * srcMem,const RASTER dstRas)41 void setOutputRaster(float4 *srcMem, const RASTER dstRas) {
42   typename PIXEL::Channel halfChan =
43       (typename PIXEL::Channel)(PIXEL::maxChannelValue / 2);
44 
45   dstRas->fill(PIXEL::Transparent);
46 
47   float4 *chan_p = srcMem;
48   for (int j = 0; j < dstRas->getLy(); j++) {
49     PIXEL *pix = dstRas->pixels(j);
50     for (int i = 0; i < dstRas->getLx(); i++, chan_p++, pix++) {
51       float val;
52       val    = (*chan_p).x * (float)PIXEL::maxChannelValue + 0.5f;
53       pix->r = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
54                                              ? (float)PIXEL::maxChannelValue
55                                              : val);
56       val    = (*chan_p).y * (float)PIXEL::maxChannelValue + 0.5f;
57       pix->g = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
58                                              ? (float)PIXEL::maxChannelValue
59                                              : val);
60       val    = (*chan_p).z * (float)PIXEL::maxChannelValue + 0.5f;
61       pix->b = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
62                                              ? (float)PIXEL::maxChannelValue
63                                              : val);
64       val    = (*chan_p).w * (float)PIXEL::maxChannelValue + 0.5f;
65       pix->m = (typename PIXEL::Channel)((val > (float)PIXEL::maxChannelValue)
66                                              ? (float)PIXEL::maxChannelValue
67                                              : val);
68     }
69   }
70 }
71 
getSource_CPU(float4 * source_host,TDimensionI & dim,int pos_x,int pos_y)72 float4 getSource_CPU(float4 *source_host, TDimensionI &dim, int pos_x,
73                      int pos_y) {
74   if (pos_x < 0 || pos_x >= dim.lx || pos_y < 0 || pos_y >= dim.ly)
75     return float4{0.0f, 0.0f, 0.0f, 0.0f};
76 
77   return source_host[pos_y * dim.lx + pos_x];
78 }
79 
interp_CPU(float4 val1,float4 val2,float ratio)80 float4 interp_CPU(float4 val1, float4 val2, float ratio) {
81   return float4{(1.0f - ratio) * val1.x + ratio * val2.x,
82                 (1.0f - ratio) * val1.y + ratio * val2.y,
83                 (1.0f - ratio) * val1.z + ratio * val2.z,
84                 (1.0f - ratio) * val1.w + ratio * val2.w};
85 }
adjustExposure(float source,float distance,float amount,float gamma,float midpoint)86 float adjustExposure(float source, float distance, float amount, float gamma,
87                      float midpoint) {
88   float scale = (distance < midpoint)
89                     ? 0.0f
90                     : amount * (distance - midpoint) / (1.0f - midpoint);
91 
92   float ret = powf(10, (source - 0.5f) * gamma);
93 
94   ret *= powf(10, scale);
95 
96   ret = log10f(ret) / gamma + 0.5f;
97 
98   return (ret > 1.0f) ? 1.0f : ((ret < 0.0f) ? 0.0f : ret);
99 }
100 };
101 
102 class Iwa_BarrelDistortFx final : public TStandardRasterFx {
103   FX_PLUGIN_DECLARATION(Iwa_BarrelDistortFx)
104 
105   TRasterFxPort m_source;
106   TPointParamP m_point;
107   TDoubleParamP m_distortion;
108   TDoubleParamP m_distortionAspect;
109   TDoubleParamP m_precision;
110   TDoubleParamP m_chromaticAberration;
111   TDoubleParamP m_vignetteAmount;
112   TDoubleParamP m_vignetteGamma;
113   TDoubleParamP m_vignetteMidpoint;
114   TDoubleParamP m_scale;
115 
116 public:
Iwa_BarrelDistortFx()117   Iwa_BarrelDistortFx()
118       : m_point(TPointD(0.0, 0.0))
119       , m_distortion(0.0)
120       , m_distortionAspect(1.0)
121       , m_precision(1.0)
122       , m_chromaticAberration(0.0)
123       , m_vignetteAmount(0.0)
124       , m_vignetteGamma(1.0)
125       , m_vignetteMidpoint(0.5)
126       , m_scale(1.0) {
127     m_point->getX()->setMeasureName("fxLength");
128     m_point->getY()->setMeasureName("fxLength");
129     bindParam(this, "center", m_point);
130     bindParam(this, "distortion", m_distortion);
131     bindParam(this, "distortionAspect", m_distortionAspect);
132     bindParam(this, "precision", m_precision);
133     bindParam(this, "chromaticAberration", m_chromaticAberration);
134     bindParam(this, "vignetteAmount", m_vignetteAmount);
135     bindParam(this, "vignetteGamma", m_vignetteGamma);
136     bindParam(this, "vignetteMidpoint", m_vignetteMidpoint);
137     bindParam(this, "scale", m_scale);
138 
139     addInputPort("Source", m_source);
140     m_distortion->setValueRange(-2.0, 2.0);
141     m_distortionAspect->setValueRange(0.2, 5.0);
142     m_precision->setValueRange(1.0, 3.0);
143     m_chromaticAberration->setValueRange(-0.1, 0.1);
144     m_vignetteAmount->setValueRange(-1.0, 1.0);
145     m_vignetteGamma->setValueRange(0.05, 20.0);
146     m_vignetteMidpoint->setValueRange(0.0, 1.0);
147     m_scale->setValueRange(0.1, 2.0);
148   }
149 
~Iwa_BarrelDistortFx()150   ~Iwa_BarrelDistortFx(){};
151 
doGetBBox(double frame,TRectD & bBox,const TRenderSettings & info)152   bool doGetBBox(double frame, TRectD &bBox,
153                  const TRenderSettings &info) override {
154     if (m_source.isConnected()) {
155       bool ret      = m_source->doGetBBox(frame, bBox, info);
156       if (ret) bBox = TConsts::infiniteRectD;
157       return ret;
158     }
159     return false;
160   }
161 
162   void doCompute(TTile &tile, double frame, const TRenderSettings &) override;
163 
164   void doCompute_CPU(TPointD &point, TPointD &sourcePoint, float dist,
165                      float distAspect, float4 *source_host, float4 *result_host,
166                      TRectD &rectOut, TDimensionI &sourceDim, TPointD &offset,
167                      float precision, double vignetteAmount,
168                      double vignetteGamma, double vignetteMidpoint, float scale,
169                      const TRenderSettings &ri);
170 
171   void doCompute_chroma_CPU(TPointD &point, TPointD &sourcePoint, float dist,
172                             float distAspect, float chroma, float4 *source_host,
173                             float4 *result_host, TRectD &rectOut,
174                             TDimensionI &sourceDim, TPointD &offset,
175                             float precision, double vignetteAmount,
176                             double vignetteGamma, double vignetteMidpoint,
177                             float scale, const TRenderSettings &ri);
178 
canHandle(const TRenderSettings & info,double frame)179   bool canHandle(const TRenderSettings &info, double frame) override {
180     return false;
181   }
182 
getParamUIs(TParamUIConcept * & concepts,int & length)183   void getParamUIs(TParamUIConcept *&concepts, int &length) override {
184     concepts = new TParamUIConcept[length = 1];
185 
186     concepts[0].m_type  = TParamUIConcept::POINT;
187     concepts[0].m_label = "Center";
188     concepts[0].m_params.push_back(m_point);
189   }
190 };
191 
192 //------------------------------------------------------------------
193 
doCompute(TTile & tile,double frame,const TRenderSettings & ri)194 void Iwa_BarrelDistortFx::doCompute(TTile &tile, double frame,
195                                     const TRenderSettings &ri) {
196   if (!m_source.isConnected()) return;
197 
198   TPointD point = m_point->getValue(frame);
199   TAffine aff   = ri.m_affine;
200   // convert the coordinate to with origin at bottom-left corner of the camera
201   point = aff * point +
202           TPointD(ri.m_cameraBox.getLx() / 2.0, ri.m_cameraBox.getLy() / 2.0);
203   double dist       = m_distortion->getValue(frame);
204   double distAspect = m_distortionAspect->getValue(frame);
205   double scale      = m_scale->getValue(frame);
206 
207   TRectD rectOut(tile.m_pos, TDimensionD(tile.getRaster()->getLx(),
208                                          tile.getRaster()->getLy()));
209   TDimensionI outDim(rectOut.getLx(), rectOut.getLy());
210 
211   float precision = m_precision->getValue(frame);
212   TRenderSettings source_ri(ri);
213   TPointD sourcePoint(point);
214   if (precision > 1.0) {
215     source_ri.m_affine *= TScale(precision, precision);
216     sourcePoint = source_ri.m_affine * m_point->getValue(frame) +
217                   TPointD(source_ri.m_cameraBox.getLx() / 2.0,
218                           source_ri.m_cameraBox.getLy() / 2.0);
219   }
220 
221   TRectD sourceBBox;
222   m_source->getBBox(frame, sourceBBox, source_ri);
223   TDimensionI sourceDim;
224   if (sourceBBox == TConsts::infiniteRectD) {
225     TPointD tileOffset = tile.m_pos + tile.getRaster()->getCenterD();
226     sourceBBox         = TRectD(TPointD(source_ri.m_cameraBox.x0 + tileOffset.x,
227                                 source_ri.m_cameraBox.y0 + tileOffset.y),
228                         TDimensionD(source_ri.m_cameraBox.getLx(),
229                                     source_ri.m_cameraBox.getLy()));
230   }
231   sourceDim.lx = std::ceil(sourceBBox.getLx());
232   sourceDim.ly = std::ceil(sourceBBox.getLy());
233 
234   TTile sourceTile;
235   m_source->allocateAndCompute(sourceTile, sourceBBox.getP00(), sourceDim,
236                                tile.getRaster(), frame, source_ri);
237 
238   float4 *source_host;
239   TRasterGR8P source_host_ras(sourceDim.lx * sizeof(float4), sourceDim.ly);
240   source_host_ras->lock();
241   source_host = (float4 *)source_host_ras->getRawData();
242 
243   TRaster32P ras32 = (TRaster32P)sourceTile.getRaster();
244   TRaster64P ras64 = (TRaster64P)sourceTile.getRaster();
245   if (ras32)
246     setSourceRaster<TRaster32P, TPixel32>(ras32, source_host, sourceDim);
247   else if (ras64)
248     setSourceRaster<TRaster64P, TPixel64>(ras64, source_host, sourceDim);
249 
250   TRasterGR8P result_host_ras(outDim.lx * sizeof(float4), outDim.ly);
251 
252   // memory to store the result
253   float4 *result_host;
254   result_host_ras->lock();
255   result_host = (float4 *)result_host_ras->getRawData();
256 
257   TPointD offset = sourceTile.m_pos + TPointD(ri.m_cameraBox.getLx() / 2.0,
258                                               ri.m_cameraBox.getLy() / 2.0);
259 
260   double vignetteAmount   = m_vignetteAmount->getValue(frame);
261   double vignetteGamma    = m_vignetteGamma->getValue(frame);
262   double vignetteMidpoint = m_vignetteMidpoint->getValue(frame);
263 
264   double chroma = m_chromaticAberration->getValue(frame);
265   if (areAlmostEqual(chroma, 0.0))
266     doCompute_CPU(point, sourcePoint, dist, distAspect, source_host,
267                   result_host, rectOut, sourceDim, offset, precision,
268                   vignetteAmount, vignetteGamma, vignetteMidpoint, scale, ri);
269   else
270     doCompute_chroma_CPU(point, sourcePoint, dist, distAspect, chroma,
271                          source_host, result_host, rectOut, sourceDim, offset,
272                          precision, vignetteAmount, vignetteGamma,
273                          vignetteMidpoint, scale, ri);
274 
275   source_host_ras->unlock();
276 
277   // convert the result to channel value and store to the output raster
278   TRaster32P outRas32 = (TRaster32P)tile.getRaster();
279   TRaster64P outRas64 = (TRaster64P)tile.getRaster();
280   if (outRas32)
281     setOutputRaster<TRaster32P, TPixel32>(result_host, outRas32);
282   else if (outRas64)
283     setOutputRaster<TRaster64P, TPixel64>(result_host, outRas64);
284 
285   result_host_ras->unlock();
286 }
287 //------------------------------------------------------------------
288 
doCompute_CPU(TPointD & point,TPointD & sourcePoint,float dist,float distAspect,float4 * source_host,float4 * result_host,TRectD & rectOut,TDimensionI & sourceDim,TPointD & offset,float precision,double vignetteAmount,double vignetteGamma,double vignetteMidpoint,float scale,const TRenderSettings & ri)289 void Iwa_BarrelDistortFx::doCompute_CPU(
290     TPointD &point, TPointD &sourcePoint, float dist, float distAspect,
291     float4 *source_host, float4 *result_host, TRectD &rectOut,
292     TDimensionI &sourceDim, TPointD &offset, float precision,
293     double vignetteAmount, double vignetteGamma, double vignetteMidpoint,
294     float scale, const TRenderSettings &ri) {
295   float4 *result_p = result_host;
296   float squaredSize =
297       (float)(ri.m_cameraBox.getLx() * ri.m_cameraBox.getLy()) * 0.25f;
298 
299   QPointF offsetCenter(sourcePoint.x - offset.x, sourcePoint.y - offset.y);
300 
301   bool doVignette = !areAlmostEqual(vignetteAmount, 0.0);
302 
303   // Only the case of barrel distortion (dist < 0) , decrease the sample
304   // position.
305   // It will enlarge the result image.
306   // Such adjustment can be seen in Lens Correction feature of PhotoShop.
307   float sizeAdjust = (dist < 0.0f) ? (1.0f + dist) : 1.0f;
308 
309   QVector2D distAspectVec(1, 1);
310   if (distAspect > 0.0f && distAspect != 1.0f) {
311     float aspectSqrt = std::sqrt(distAspect);
312     if (dist < 0.0f)
313       distAspectVec = QVector2D(1.0 / aspectSqrt, aspectSqrt);
314     else
315       distAspectVec = QVector2D(aspectSqrt, 1.0 / aspectSqrt);
316   }
317 
318   TPointD outImgOrigin =
319       rectOut.getP00() +
320       TPointD(ri.m_cameraBox.getLx() / 2.0, ri.m_cameraBox.getLy() / 2.0);
321 
322   for (int j = 0; j < (int)rectOut.getLy(); j++) {
323     for (int i = 0; i < (int)rectOut.getLx(); i++, result_p++) {
324       QVector2D ru(QPointF((float)i, (float)j) +
325                    QPointF(outImgOrigin.x, outImgOrigin.y) -
326                    QPointF(point.x, point.y));
327       // apply global scaling
328       ru /= scale;
329       float val = (ru * distAspectVec).lengthSquared() / squaredSize;
330       if (dist > 0.0f && val > 1.0f / dist) {
331         (*result_p) = {0.0f, 0.0f, 0.0f, 0.0f};
332         continue;
333       }
334       float distortRatio = sizeAdjust / (1.0f + dist * val);
335       QVector2D rd       = distortRatio * ru;
336       QPointF samplePos  = offsetCenter + rd.toPointF() * precision;
337       if (samplePos.x() <= -1.0f || samplePos.x() >= (float)(sourceDim.lx) ||
338           samplePos.y() <= -1.0f || samplePos.y() >= (float)(sourceDim.ly)) {
339         (*result_p) = {0.0f, 0.0f, 0.0f, 0.0f};
340         continue;
341       }
342       QPoint pos(std::floor(samplePos.x()), std::floor(samplePos.y()));
343       QPointF ratio(samplePos.x() - (float)pos.x(),
344                     samplePos.y() - (float)pos.y());
345       (*result_p) = interp_CPU(
346           interp_CPU(
347               getSource_CPU(source_host, sourceDim, pos.x(), pos.y()),
348               getSource_CPU(source_host, sourceDim, pos.x() + 1, pos.y()),
349               ratio.x()),
350           interp_CPU(
351               getSource_CPU(source_host, sourceDim, pos.x(), pos.y() + 1),
352               getSource_CPU(source_host, sourceDim, pos.x() + 1, pos.y() + 1),
353               ratio.x()),
354           ratio.y());
355       if (doVignette) {
356         float distance = distortRatio * distortRatio * val;
357         (*result_p).x  = adjustExposure((*result_p).x, distance, vignetteAmount,
358                                        vignetteGamma, vignetteMidpoint);
359         (*result_p).y = adjustExposure((*result_p).y, distance, vignetteAmount,
360                                        vignetteGamma, vignetteMidpoint);
361         (*result_p).z = adjustExposure((*result_p).z, distance, vignetteAmount,
362                                        vignetteGamma, vignetteMidpoint);
363       }
364     }
365   }
366 }
367 
368 //------------------------------------------------------------------
369 
doCompute_chroma_CPU(TPointD & point,TPointD & sourcePoint,float dist,float distAspect,float chroma,float4 * source_host,float4 * result_host,TRectD & rectOut,TDimensionI & sourceDim,TPointD & offset,float precision,double vignetteAmount,double vignetteGamma,double vignetteMidpoint,float scale,const TRenderSettings & ri)370 void Iwa_BarrelDistortFx::doCompute_chroma_CPU(
371     TPointD &point, TPointD &sourcePoint, float dist, float distAspect,
372     float chroma, float4 *source_host, float4 *result_host, TRectD &rectOut,
373     TDimensionI &sourceDim, TPointD &offset, float precision,
374     double vignetteAmount, double vignetteGamma, double vignetteMidpoint,
375     float scale, const TRenderSettings &ri) {
376   float4 *result_p = result_host;
377   float squaredSize =
378       (float)(ri.m_cameraBox.getLx() * ri.m_cameraBox.getLy()) * 0.25f;
379   QPointF offsetCenter(sourcePoint.x - offset.x, sourcePoint.y - offset.y);
380   float dist_ch[3] = {dist + chroma, dist, dist - chroma};
381 
382   bool doVignette = !areAlmostEqual(vignetteAmount, 0.0);
383 
384   // Only the case of barrel distortion (dist < 0) , decrease the sample
385   // position.
386   // It will enlarge the result image.
387   // Such adjustment can be seen in Lens Correction feature of PhotoShop.
388   float sizeAdjust = (dist < 0.0f) ? (1.0f + dist) : 1.0f;
389 
390   QVector2D distAspectVec(1, 1);
391   if (distAspect > 0.0f && distAspect != 1.0f) {
392     float aspectSqrt = std::sqrt(distAspect);
393     if (dist < 0.0f)
394       distAspectVec = QVector2D(1.0 / aspectSqrt, aspectSqrt);
395     else
396       distAspectVec = QVector2D(aspectSqrt, 1.0 / aspectSqrt);
397   }
398 
399   TPointD outImgOrigin =
400       rectOut.getP00() +
401       TPointD(ri.m_cameraBox.getLx() / 2.0, ri.m_cameraBox.getLy() / 2.0);
402 
403   for (int j = 0; j < (int)rectOut.getLy(); j++) {
404     for (int i = 0; i < (int)rectOut.getLx(); i++, result_p++) {
405       for (int c = 0; c < 3; c++) {
406         QVector2D ru(QPointF((float)i, (float)j) +
407                      QPointF(outImgOrigin.x, outImgOrigin.y) -
408                      QPointF(point.x, point.y));
409         // apply global scaling
410         ru /= scale;
411         float val = (ru * distAspectVec).lengthSquared() / squaredSize;
412         if (dist_ch[c] > 0.0f && val > 1.0f / dist_ch[c]) {
413           (*result_p) = {0.0f, 0.0f, 0.0f, 0.0f};
414           continue;
415         }
416         float distortRatio = sizeAdjust / (1.0f + dist_ch[c] * val);
417         QVector2D rd       = distortRatio * ru;
418         QPointF samplePos  = offsetCenter + rd.toPointF() * precision;
419         if (samplePos.x() <= -1.0f || samplePos.x() >= (float)(sourceDim.lx) ||
420             samplePos.y() <= -1.0f || samplePos.y() >= (float)(sourceDim.ly)) {
421           (*result_p) = {0.0f, 0.0f, 0.0f, 0.0f};
422           continue;
423         }
424         QPoint pos(std::floor(samplePos.x()), std::floor(samplePos.y()));
425         QPointF ratio(samplePos.x() - (float)pos.x(),
426                       samplePos.y() - (float)pos.y());
427         float4 result_tmp = interp_CPU(
428             interp_CPU(
429                 getSource_CPU(source_host, sourceDim, pos.x(), pos.y()),
430                 getSource_CPU(source_host, sourceDim, pos.x() + 1, pos.y()),
431                 ratio.x()),
432             interp_CPU(
433                 getSource_CPU(source_host, sourceDim, pos.x(), pos.y() + 1),
434                 getSource_CPU(source_host, sourceDim, pos.x() + 1, pos.y() + 1),
435                 ratio.x()),
436             ratio.y());
437         switch (c) {
438         case 0:
439           (*result_p).x = result_tmp.x;
440           if (doVignette)
441             (*result_p).x =
442                 adjustExposure((*result_p).x, distortRatio * distortRatio * val,
443                                vignetteAmount, vignetteGamma, vignetteMidpoint);
444           break;
445         case 1:
446           (*result_p).y = result_tmp.y;
447           (*result_p).w = result_tmp.w;
448           if (doVignette)
449             (*result_p).y =
450                 adjustExposure((*result_p).y, distortRatio * distortRatio * val,
451                                vignetteAmount, vignetteGamma, vignetteMidpoint);
452           break;
453         case 2:
454           (*result_p).z = result_tmp.z;
455           if (doVignette)
456             (*result_p).z =
457                 adjustExposure((*result_p).z, distortRatio * distortRatio * val,
458                                vignetteAmount, vignetteGamma, vignetteMidpoint);
459           break;
460         default:
461           break;
462         }
463       }
464     }
465   }
466 }
467 
468 //------------------------------------------------------------------
469 
470 FX_PLUGIN_IDENTIFIER(Iwa_BarrelDistortFx, "iwa_BarrelDistortFx")