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")