1 // -*- c-basic-offset: 4 -*-
2 /** @file nona/RemappedPanoImage.h
3  *
4  *  Contains functions to transform whole images.
5  *  Can use PTools::Transform or PanoCommand::SpaceTransform for the calculations
6  *
7  *  @author Pablo d'Angelo <pablo.dangelo@web.de>
8  *
9  *  $Id$
10  *
11  *  This is free software; you can redistribute it and/or
12  *  modify it under the terms of the GNU General Public
13  *  License as published by the Free Software Foundation; either
14  *  version 2 of the License, or (at your option) any later version.
15  *
16  *  This software is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  *  Lesser General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public
22  *  License along with this software. If not, see
23  *  <http://www.gnu.org/licenses/>.
24  *
25  */
26 
27 #ifndef _NONA_REMAPPEDPANOIMAGE_H
28 #define _NONA_REMAPPEDPANOIMAGE_H
29 
30 #include <vigra/imageinfo.hxx>
31 #include <vigra/initimage.hxx>
32 #include <vigra/copyimage.hxx>
33 #include <vigra/flatmorphology.hxx>
34 #include <vigra_ext/ROIImage.h>
35 #include <vigra_ext/openmp_vigra.h>
36 
37 #include <appbase/ProgressDisplay.h>
38 #include <nona/StitcherOptions.h>
39 
40 #include <panodata/SrcPanoImage.h>
41 #include <panodata/Mask.h>
42 #include <panodata/PanoramaOptions.h>
43 #include <panotools/PanoToolsInterface.h>
44 
45 // default values for exposure cutoff
46 #define NONA_DEFAULT_EXPOSURE_LOWER_CUTOFF 1/255.0f
47 #define NONA_DEFAULT_EXPOSURE_UPPER_CUTOFF 250/255.0f
48 
49 
50 namespace HuginBase {
51 namespace Nona {
52 
53 
54 /** calculate the outline of the image
55  *
56  *  @param src       description of source picture
57  *  @param dest      description of output picture (panorama)
58  *  @param imgRect   output: position of image in panorama.
59  */
60 template <class TRANSFORM>
61 void estimateImageRect(const SrcPanoImage & src,
62                        const PanoramaOptions & dest,
63                        TRANSFORM & transf,
64                        vigra::Rect2D & imgRect);
65 
66 ///
67 template <class TRANSFORM>
68 void estimateImageAlpha(const SrcPanoImage & src,
69                         const PanoramaOptions & dest,
70                         TRANSFORM & transf,
71                         vigra::Rect2D & imgRect,
72                          vigra::BImage & alpha,
73                          double & scale);
74 
75 
76 /** struct to hold a image state for stitching
77  *
78  */
79 template <class RemapImage, class AlphaImage>
80 class RemappedPanoImage : public vigra_ext::ROIImage<RemapImage, AlphaImage>
81 {
82 
83         typedef vigra_ext::ROIImage<RemapImage, AlphaImage> Base;
84 
85     public:
86     // typedefs for the children types
87         typedef typename RemapImage::value_type      image_value_type;
88         typedef typename RemapImage::traverser       image_traverser;
89         typedef typename RemapImage::const_traverser const_image_traverser;
90         typedef typename RemapImage::Accessor        ImageAccessor;
91         typedef typename RemapImage::ConstAccessor   ConstImageAccessor;
92 
93         typedef typename AlphaImage::value_type       mask_value_type;
94         typedef typename AlphaImage::traverser        mask_traverser;
95         typedef typename AlphaImage::const_traverser  const_mask_traverser;
96         typedef typename AlphaImage::Accessor         MaskAccessor;
97         typedef typename AlphaImage::ConstAccessor    ConstMaskAccessor;
98 
99         typedef typename vigra_ext::ValueTypeTraits<image_value_type>::value_type component_type;
100 
101 
102     public:
103         /** create a remapped pano image
104          *
105          *  the actual remapping is done by the remapImage() function.
106          */
RemappedPanoImage()107         RemappedPanoImage() : m_advancedOptions()
108         {};
109 
110 
111     public:
112         ///
113         void setPanoImage(const SrcPanoImage & src,
114                           const PanoramaOptions & dest,
115                           vigra::Rect2D roi);
setAdvancedOptions(const AdvancedOptions & advancedOptions)116         void setAdvancedOptions(const AdvancedOptions& advancedOptions)
117         {
118             m_advancedOptions = advancedOptions;
119         };
120 
121     public:
122         /** calculate distance map. pixels contain distance from image center
123          *
124          *  setPanoImage() has to be called before!
125          */
126         template<class DistImgType>
127             void calcSrcCoordImgs(DistImgType & imgX, DistImgType & imgY);
128 
129         /** calculate only the alpha channel.
130          *  works for arbitrary transforms, with holes and so on,
131          *  but is very crude and slow (remapps all image pixels...)
132          *
133          *  better transform all images, and get the alpha channel for free!
134          *
135          *  setPanoImage() should be called before.
136          */
137         void calcAlpha();
138 
139         /** remap a image without alpha channel*/
140         template <class ImgIter, class ImgAccessor>
141         void remapImage(vigra::triple<ImgIter, ImgIter, ImgAccessor> srcImg,
142                         vigra_ext::Interpolator interpol,
143                         AppBase::ProgressDisplay* progress, bool singleThreaded = false);
144 
145 
146         /** remap a image, with alpha channel */
147         template <class ImgIter, class ImgAccessor,
148                   class AlphaIter, class AlphaAccessor>
149         void remapImage(vigra::triple<ImgIter, ImgIter, ImgAccessor> srcImg,
150                         std::pair<AlphaIter, AlphaAccessor> alphaImg,
151                         vigra_ext::Interpolator interp,
152                         AppBase::ProgressDisplay* progress, bool singleThreaded = false);
153 
154 
155     public:
156         ///
157         vigra::ImageImportInfo::ICCProfile m_ICCProfile;
158 
159     protected:
160         SrcPanoImage m_srcImg;
161         PanoramaOptions m_destImg;
162         PTools::Transform m_transf;
163         AdvancedOptions m_advancedOptions;
164 
165 };
166 
167 
168 
169 /** remap a single image
170  */
171 template <class SrcImgType, class FlatImgType, class DestImgType, class MaskImgType>
172 void remapImage(SrcImgType & srcImg,
173                 const MaskImgType & srcAlpha,
174                 const FlatImgType & srcFlat,
175                 const SrcPanoImage & src,
176                 const PanoramaOptions & dest,
177                 vigra::Rect2D outputRect,
178                 RemappedPanoImage<DestImgType, MaskImgType> & remapped,
179                 AppBase::ProgressDisplay* progress);
180 
181 
182 } // namespace
183 } // namespace
184 
185 
186 
187 //==============================================================================
188 // templated implementations
189 
190 
191 #include <photometric/ResponseTransform.h>
192 #include <vigra_ext/ImageTransforms.h>
193 #include <vigra_ext/ImageTransformsGPU.h>
194 
195 // #define DEBUG_REMAP 1
196 
197 #ifdef DEBUG_REMAP
198 #include <vigra/impex.hxx> // for vigra::exportImage()
199 #ifdef _WIN32
200 #define DEBUG_FILE_PREFIX "C:/temp/"
201 #else
202 #define DEBUG_FILE_PREFIX "/tmp/"
203 #endif
204 #endif
205 
206 
207 namespace HuginBase {
208 namespace Nona {
209 
210 
211 template <class RemapImage, class AlphaImage>
setPanoImage(const SrcPanoImage & src,const PanoramaOptions & dest,vigra::Rect2D roi)212 void RemappedPanoImage<RemapImage,AlphaImage>::setPanoImage(const SrcPanoImage & src,
213                   const PanoramaOptions & dest, vigra::Rect2D roi)
214 {
215     // restrict to panorama size
216     m_srcImg = src;
217     m_destImg = dest;
218 
219     if (m_destImg.remapUsingGPU) {
220         // Make width multiple of 8 for fast GPU transfers.
221         const int r = roi.width() % 8;
222         if (r != 0) roi.addSize(vigra::Size2D(8 - r, 0));
223     }
224 
225     Base::resize(roi);
226     m_transf.createTransform(src, dest);
227 
228     DEBUG_DEBUG("after resize: " << Base::m_region);
229     DEBUG_DEBUG("m_srcImg size: " << m_srcImg.getSize());
230 }
231 
232 
233 #if 0
234 /** set a new image or panorama options
235  *
236  *  This is needed before any of the remap functions can be used.
237  *
238  *  calculates bounding box, and outline
239  */
240 template <class RemapImage, class AlphaImage>
241 void RemappedPanoImage<RemapImage,AlphaImage>::setPanoImage(const vigra::Size2D & srcSize,
242                   const PanoCommand::VariableMap & srcVars,
243                   PanoCommand::Lens::LensProjectionFormat srcProj,
244                   const PanoCommand::PanoImage & img,
245                   const vigra::Diff2D &destSize,
246                   HuginBase::PanoramaOptions::ProjectionFormat destProj,
247                   double destHFOV)
248 {
249     m_srcSize = srcSize;
250     m_srcOrigSize.x = img.getWidth();
251     m_srcOrigSize.y = img.getHeight();
252     m_srcProj = m_srcProj;
253 
254 
255     m_srcPanoImg = img;
256     m_destProj = destProj;
257     m_destHFOV = destHFOV;
258     // create transforms
259     //    SpaceTransform t;
260     //    SpaceTransform invT;
261     /*
262     m_invTransf.createInvTransform(srcSize, srcVars, srcProj,
263                                    destSize, destProj, destHFOV,
264                                    m_srcOrigSize);
265     */
266     // calculate ROI for this image.
267     m_transf.createTransform(srcSize, srcVars, srcProj,
268                              destSize, destProj, destHFOV,
269                              m_srcOrigSize);
270 
271     ImageOptions imgOpts = img.getOptions();
272 
273     // todo: resize crop!
274     bool circCrop = srcProj == Lens::CIRCULAR_FISHEYE;
275     estimateImageRect(destSize, m_srcOrigSize,
276                       imgOpts.docrop, imgOpts.cropRect, circCrop,
277                       m_transf,
278                       imageRect);
279 
280     m_warparound = (destProj == PanoramaOptions::EQUIRECTANGULAR && m_destHFOV == 360);
281 
282 
283 }
284 
285 template <class RemapImage, class AlphaImage>
286 void RemappedPanoImage<RemapImage,AlphaImage>::setPanoImage(const HuginBase::Panorama & pano, unsigned int imgNr,
287                   vigra::Size2D srcSize, const HuginBase::PanoramaOptions & opts)
288 {
289     const PanoCommand::PanoImage & img = pano.getImage(imgNr);
290 
291     m_srcSize = srcSize;
292     m_srcOrigSize.x = img.getWidth();
293     m_srcOrigSize.y = img.getHeight();
294     m_srcProj = pano.getLens(pano.getImage(imgNr).getLensNr()).getProjection();
295 
296     m_destProj = opts.getProjection();
297     m_destHFOV = opts.getHFOV();
298     m_warparound = (opts.getProjection() == PanoramaOptions::EQUIRECTANGULAR && opts.getHFOV() == 360);
299 
300     // create transforms
301     //    SpaceTransform t;
302     //    SpaceTransform invT;
303 
304 //        m_invTransf.createInvTransform(pano, imgNr, opts, m_srcSize);
305     m_transf.createTransform(pano, imgNr, opts, m_srcSize);
306 
307     // calculate ROI for this image.
308     m_srcPanoImg = pano.getImage(imgNr);
309     ImageOptions imgOpts = pano.getImage(imgNr).getOptions();
310     vigra::Rect2D imageRect;
311     // todo: resize crop!
312     bool circCrop = pano.getLens(pano.getImage(imgNr).getLensNr()).getProjection() == Lens::CIRCULAR_FISHEYE;
313     estimateImageRect(vigra::Size2D(opts.getWidth(), opts.getHeight()), srcSize,
314                       imgOpts.docrop, imgOpts.cropRect, circCrop,
315                       m_transf,
316                       imageRect);
317 
318 
319     // restrict to panorama size
320     Base::resize(imageRect);
321     DEBUG_DEBUG("after resize: " << Base::m_region);
322 }
323 #endif
324 
325 
326 /** calculate distance map. pixels contain distance from image center
327  *
328  *  setPanoImage() has to be called before!
329  */
330 template<class RemapImage, class AlphaImage>
331 template<class DistImgType>
calcSrcCoordImgs(DistImgType & imgX,DistImgType & imgY)332 void RemappedPanoImage<RemapImage,AlphaImage>::calcSrcCoordImgs(DistImgType & imgX, DistImgType & imgY)
333 {
334     if (Base::boundingBox().isEmpty()) return;
335     imgX.resize(Base::boundingBox().size().width(), Base::boundingBox().size().height(), vigra::NumericTraits<typename DistImgType::value_type>::max());
336     imgY.resize(Base::boundingBox().size().width(), Base::boundingBox().size().height(), vigra::NumericTraits<typename DistImgType::value_type>::max());
337     // calculate the alpha channel,
338     int xstart = Base::boundingBox().left();
339     int xend   = Base::boundingBox().right();
340     int ystart = Base::boundingBox().top();
341     int yend   = Base::boundingBox().bottom();
342 
343     // create dist y iterator
344     typename DistImgType::Iterator yImgX(imgX.upperLeft());
345     typename DistImgType::Iterator yImgY(imgY.upperLeft());
346     typename DistImgType::Accessor accX = imgX.accessor();
347     typename DistImgType::Accessor accY = imgY.accessor();
348     // loop over the image and transform
349     for(int y=ystart; y < yend; ++y, ++yImgX.y, ++yImgY.y)
350     {
351         // create x iterators
352         typename DistImgType::Iterator xImgX(yImgX);
353         typename DistImgType::Iterator xImgY(yImgY);
354         for(int x=xstart; x < xend; ++x, ++xImgY.x, ++xImgX.x)
355         {
356             double sx,sy;
357             if (m_transf.transformImgCoord(sx, sy, x, y))
358             {
359                 if (m_srcImg.isInside(vigra::Point2D(hugin_utils::roundi(sx), hugin_utils::roundi(sy))))
360                 {
361                     accX.set(sx, xImgX);
362                     accY.set(sy, xImgY);
363                 };
364             };
365         }
366     }
367 }
368 
369 /** calculate only the alpha channel.
370  *  works for arbitrary transforms, with holes and so on,
371  *  but is very crude and slow (remapps all image pixels...)
372  *
373  *  better transform all images, and get the alpha channel for free!
374  *
375  *  setPanoImage() should be called before.
376  */
377 template<class RemapImage, class AlphaImage>
calcAlpha()378 void RemappedPanoImage<RemapImage,AlphaImage>::calcAlpha()
379 {
380     if (Base::boundingBox().isEmpty())
381         return;
382 
383     Base::m_mask.resize(Base::boundingBox().size());
384     // calculate the alpha channel,
385     int xstart = Base::boundingBox().left();
386     int xend   = Base::boundingBox().right();
387     int ystart = Base::boundingBox().top();
388     int yend   = Base::boundingBox().bottom();
389 
390     // loop over the image and transform
391 #pragma omp parallel for schedule(dynamic, 10)
392     for(int y=ystart; y < yend; ++y)
393     {
394         // create dist y iterator
395         typename AlphaImage::Iterator yalpha(Base::m_mask.upperLeft());
396         yalpha.y += y - ystart;
397         // create x iterators
398         typename AlphaImage::Iterator xalpha(yalpha);
399         for(int x=xstart; x < xend; ++x, ++xalpha.x)
400         {
401             double sx,sy;
402             if(m_transf.transformImgCoord(sx,sy,x,y))
403             {
404                 if (m_srcImg.isInside(vigra::Point2D(hugin_utils::roundi(sx),hugin_utils::roundi(sy))))
405                 {
406                     *xalpha = 255;
407                 }
408                 else
409                 {
410                     *xalpha = 0;
411                 };
412             }
413             else
414             {
415                 *xalpha = 0;
416             };
417         }
418     }
419 }
420 
421 /** copies image into new image with changed size */
422 template <class ImageType>
CopyImageNewSize(const ImageType & image,const vigra::Size2D & newSize)423 ImageType CopyImageNewSize(const ImageType& image, const vigra::Size2D& newSize)
424 {
425     ImageType newImage(newSize);
426     vigra::omp::copyImage(vigra::srcImageRange(image, vigra::Rect2D(newSize)), vigra::destImage(newImage));
427     return newImage;
428 };
429 
430 /** remap a image without alpha channel*/
431 template<class RemapImage, class AlphaImage>
432 template<class ImgIter, class ImgAccessor>
remapImage(vigra::triple<ImgIter,ImgIter,ImgAccessor> srcImg,vigra_ext::Interpolator interpol,AppBase::ProgressDisplay * progress,bool singleThreaded)433 void RemappedPanoImage<RemapImage,AlphaImage>::remapImage(vigra::triple<ImgIter, ImgIter, ImgAccessor> srcImg,
434                                                           vigra_ext::Interpolator interpol,
435                                                           AppBase::ProgressDisplay* progress, bool singleThreaded)
436 {
437 
438     //        std::ostringstream msg;
439     //        msg <<"remapping image "  << imgNr;
440     //        progress.setMessage(msg.str().c_str());
441 
442     const bool useGPU = m_destImg.remapUsingGPU;
443 
444     if (Base::boundingBox().isEmpty())
445         return;
446 
447     vigra::Diff2D srcImgSize = srcImg.second - srcImg.first;
448 
449     vigra::Size2D expectedSize = m_srcImg.getSize();
450     if (useGPU)
451     {
452         const int r = expectedSize.width() % 8;
453         if (r != 0) expectedSize += vigra::Diff2D(8 - r, 0);
454     }
455 
456     DEBUG_DEBUG("srcImgSize: " << srcImgSize << " m_srcImgSize: " << m_srcImg.getSize());
457     vigra_precondition(srcImgSize == expectedSize,
458                        "RemappedPanoImage<RemapImage,AlphaImage>::remapImage(): image unexpectedly changed dimensions.");
459 
460     typedef typename ImgAccessor::value_type input_value_type;
461     typedef typename vigra_ext::ValueTypeTraits<input_value_type>::value_type input_component_type;
462 
463     // setup photometric transform for this image type
464     // this corrects for response curve, white balance, exposure and
465     // radial vignetting
466     Photometric::InvResponseTransform<input_component_type, double> invResponse(m_srcImg);
467     invResponse.enforceMonotonicity();
468     if (m_destImg.outputMode == PanoramaOptions::OUTPUT_LDR) {
469         // select exposure and response curve for LDR output
470         std::vector<double> outLut;
471         if (!m_destImg.outputEMoRParams.empty())
472         {
473             vigra_ext::EMoR::createEMoRLUT(m_destImg.outputEMoRParams, outLut);
474         };
475         double maxVal = vigra_ext::LUTTraits<input_value_type>::max();
476         if (!m_destImg.outputPixelType.empty()) {
477             maxVal = vigra_ext::getMaxValForPixelType(m_destImg.outputPixelType);
478         }
479 
480         invResponse.setOutput(1.0/pow(2.0,m_destImg.outputExposureValue), outLut,
481                               maxVal, m_destImg.outputRangeCompression);
482     } else {
483         invResponse.setHDROutput(true,1.0/pow(2.0,m_destImg.outputExposureValue));
484     }
485 
486     if ((m_srcImg.hasActiveMasks()) || (m_srcImg.getCropMode() != SrcPanoImage::NO_CROP) || Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposure", false))
487     {
488         // need to create and additional alpha image for the crop mask...
489         // not very efficient during the remapping phase, but works.
490         vigra::BImage alpha(srcImgSize.x, srcImgSize.y);
491 
492         switch (m_srcImg.getCropMode()) {
493         case SrcPanoImage::NO_CROP:
494             {
495                 if (useGPU) {
496                     if (srcImgSize != m_srcImg.getSize()) {
497                         // src image with was increased for alignment reasons.
498                         // Need to make an alpha image to mask off the extended region.
499                         initImage(vigra::destImageRange(alpha),0);
500                         initImage(alpha.upperLeft(),
501                             alpha.upperLeft()+m_srcImg.getSize(),
502                             alpha.accessor(),255);
503                     }
504                     else
505                         initImage(vigra::destImageRange(alpha),255);
506                 }
507                 else
508                     initImage(vigra::destImageRange(alpha),255);
509                 break;
510             }
511         case SrcPanoImage::CROP_CIRCLE:
512             {
513                 vigra::Rect2D cR = m_srcImg.getCropRect();
514                 hugin_utils::FDiff2D m( (cR.left() + cR.width()/2.0),
515                         (cR.top() + cR.height()/2.0) );
516 
517                 double radius = std::min(cR.width(), cR.height())/2.0;
518                 // Default the entire alpha channel to opaque..
519                 initImage(vigra::destImageRange(alpha),255);
520                 //..crop everything outside the circle
521                 vigra_ext::circularCrop(vigra::destImageRange(alpha), m, radius);
522                 break;
523             }
524         case SrcPanoImage::CROP_RECTANGLE:
525             {
526                 vigra::Rect2D cR = m_srcImg.getCropRect();
527                 // Default the entire alpha channel to transparent..
528                 initImage(vigra::destImageRange(alpha),0);
529                 // Make sure crop is inside the image..
530                 cR &= vigra::Rect2D(0,0, srcImgSize.x, srcImgSize.y);
531                 // Opaque only the area within the crop rectangle..
532                 initImage(alpha.upperLeft()+cR.upperLeft(),
533                           alpha.upperLeft()+cR.lowerRight(),
534                           alpha.accessor(),255);
535                 break;
536             }
537         default:
538             break;
539         }
540         if(m_srcImg.hasActiveMasks())
541             vigra_ext::applyMask(vigra::destImageRange(alpha), m_srcImg.getActiveMasks());
542         if (Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposure", false))
543         {
544             const float lowerCutoff = Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposureLowerCutoff", NONA_DEFAULT_EXPOSURE_LOWER_CUTOFF);
545             const float upperCutoff = Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposureUpperCutoff", NONA_DEFAULT_EXPOSURE_UPPER_CUTOFF);
546             vigra_ext::applyExposureClipMask(srcImg, vigra::destImageRange(alpha), lowerCutoff, upperCutoff);
547         };
548         if (useGPU) {
549             transformImageAlphaGPU(srcImg,
550                                    vigra::srcImage(alpha),
551                                    destImageRange(Base::m_image),
552                                    destImage(Base::m_mask),
553                                    Base::boundingBox().upperLeft(),
554                                    m_transf,
555                                    invResponse,
556                                    m_srcImg.horizontalWarpNeeded(),
557                                    interpol,
558                                    progress);
559             if (Base::boundingBox().right() > m_destImg.getROI().right())
560             {
561                 // dest image was enlarged for GPU alignment issue
562                 // delete the pixels outside
563                 vigra::Rect2D newBoundingBox = Base::boundingBox() & m_destImg.getROI();
564                 Base::m_image = CopyImageNewSize(Base::m_image, newBoundingBox.size());
565                 Base::m_mask = CopyImageNewSize(Base::m_mask, newBoundingBox.size());
566                 Base::m_region = newBoundingBox;
567             };
568         } else {
569             transformImageAlpha(srcImg,
570                                 vigra::srcImage(alpha),
571                                 destImageRange(Base::m_image),
572                                 destImage(Base::m_mask),
573                                 Base::boundingBox().upperLeft(),
574                                 m_transf,
575                                 invResponse,
576                                 m_srcImg.horizontalWarpNeeded(),
577                                 interpol,
578                                 progress,
579                                 singleThreaded);
580         }
581     } else {
582         if (useGPU) {
583             if (srcImgSize != m_srcImg.getSize()) {
584                 // src image with was increased for alignment reasons.
585                 // Need to make an alpha image to mask off the extended region.
586                 vigra::BImage alpha(srcImgSize.x, srcImgSize.y, vigra::UInt8(0));
587                 initImage(alpha.upperLeft(),
588                           alpha.upperLeft()+m_srcImg.getSize(),
589                           alpha.accessor(),255);
590                 transformImageAlphaGPU(srcImg,
591                                        vigra::srcImage(alpha),
592                                        destImageRange(Base::m_image),
593                                        destImage(Base::m_mask),
594                                        Base::boundingBox().upperLeft(),
595                                        m_transf,
596                                        invResponse,
597                                        m_srcImg.horizontalWarpNeeded(),
598                                        interpol,
599                                        progress);
600 
601             }
602             else {
603                 transformImageGPU(srcImg,
604                                   destImageRange(Base::m_image),
605                                   destImage(Base::m_mask),
606                                   Base::boundingBox().upperLeft(),
607                                   m_transf,
608                                   invResponse,
609                                   m_srcImg.horizontalWarpNeeded(),
610                                   interpol,
611                                   progress);
612             }
613             if (Base::boundingBox().right() > m_destImg.getROI().right())
614             {
615                 // dest image was enlarged for GPU alignment issue
616                 // delete the pixels outside
617                 vigra::Rect2D newBoundingBox = Base::boundingBox() & m_destImg.getROI();
618                 Base::m_image = CopyImageNewSize(Base::m_image, newBoundingBox.size());
619                 Base::m_mask = CopyImageNewSize(Base::m_mask, newBoundingBox.size());
620                 Base::m_region = newBoundingBox;
621             };
622         } else {
623             transformImage(srcImg,
624                            destImageRange(Base::m_image),
625                            destImage(Base::m_mask),
626                            Base::boundingBox().upperLeft(),
627                            m_transf,
628                            invResponse,
629                            m_srcImg.horizontalWarpNeeded(),
630                            interpol,
631                            progress,
632                            singleThreaded);
633         }
634     }
635 }
636 
637 
638 
639 /** remap a image, with alpha channel */
640 template<class RemapImage, class AlphaImage>
641 template<class ImgIter, class ImgAccessor,
642          class AlphaIter, class AlphaAccessor>
remapImage(vigra::triple<ImgIter,ImgIter,ImgAccessor> srcImg,std::pair<AlphaIter,AlphaAccessor> alphaImg,vigra_ext::Interpolator interp,AppBase::ProgressDisplay * progress,bool singleThreaded)643 void RemappedPanoImage<RemapImage,AlphaImage>::remapImage(vigra::triple<ImgIter, ImgIter, ImgAccessor> srcImg,
644                                                           std::pair<AlphaIter, AlphaAccessor> alphaImg,
645                                                           vigra_ext::Interpolator interp,
646                                                           AppBase::ProgressDisplay* progress, bool singleThreaded)
647 {
648     const bool useGPU = m_destImg.remapUsingGPU;
649 
650     if (Base::boundingBox().isEmpty())
651         return;
652 
653     progress->setMessage("remapping", hugin_utils::stripPath(m_srcImg.getFilename()));
654 
655     vigra::Diff2D srcImgSize = srcImg.second - srcImg.first;
656 
657     vigra::Size2D expectedSize = m_srcImg.getSize();
658     if (useGPU)
659     {
660         const int r = expectedSize.width() % 8;
661         if (r != 0) expectedSize += vigra::Diff2D(8 - r, 0);
662     }
663     vigra_precondition(srcImgSize == expectedSize,
664                        "RemappedPanoImage<RemapImage,AlphaImage>::remapImage(): image unexpectedly changed dimensions.");
665 
666     typedef typename ImgAccessor::value_type input_value_type;
667     typedef typename vigra_ext::ValueTypeTraits<input_value_type>::value_type input_component_type;
668 
669     // setup photometric transform for this image type
670     // this corrects for response curve, white balance, exposure and
671     // radial vignetting
672     Photometric::InvResponseTransform<input_component_type, double> invResponse(m_srcImg);
673     if (m_destImg.outputMode == PanoramaOptions::OUTPUT_LDR) {
674         // select exposure and response curve for LDR output
675         std::vector<double> outLut;
676         // scale up to desired output format
677         double maxVal = vigra_ext::LUTTraits<input_value_type>::max();
678         if (!m_destImg.outputPixelType.empty()) {
679             maxVal = vigra_ext::getMaxValForPixelType(m_destImg.outputPixelType);
680         }
681         if (!m_destImg.outputEMoRParams.empty())
682         {
683             vigra_ext::EMoR::createEMoRLUT(m_destImg.outputEMoRParams, outLut);
684             vigra_ext::enforceMonotonicity(outLut);
685         };
686         invResponse.setOutput(1.0/pow(2.0,m_destImg.outputExposureValue), outLut,
687                               maxVal, m_destImg.outputRangeCompression);
688     } else {
689         invResponse.setHDROutput(true,1.0/pow(2.0,m_destImg.outputExposureValue));
690     }
691 
692     if ((m_srcImg.hasActiveMasks()) || (m_srcImg.getCropMode() != SrcPanoImage::NO_CROP) || Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposure", false)) {
693         vigra::BImage alpha(srcImgSize);
694         vigra::Rect2D cR = m_srcImg.getCropRect();
695         switch (m_srcImg.getCropMode()) {
696             case SrcPanoImage::NO_CROP:
697             {
698                 // Just copy the source alpha channel and crop it down.
699                 vigra::copyImage(vigra::make_triple(alphaImg.first,
700                         alphaImg.first + srcImgSize, alphaImg.second),
701                         vigra::destImage(alpha));
702                 break;
703             }
704             case SrcPanoImage::CROP_CIRCLE:
705             {
706                 // Just copy the source alpha channel and crop it down.
707                 vigra::copyImage(vigra::make_triple(alphaImg.first,
708                         alphaImg.first + srcImgSize, alphaImg.second),
709                         vigra::destImage(alpha));
710                 hugin_utils::FDiff2D m( (cR.left() + cR.width()/2.0),
711                             (cR.top() + cR.height()/2.0) );
712                 double radius = std::min(cR.width(), cR.height())/2.0;
713                 vigra_ext::circularCrop(vigra::destImageRange(alpha), m, radius);
714                 break;
715             }
716             case SrcPanoImage::CROP_RECTANGLE:
717             {
718                 // Intersect the cropping rectangle with the one from the base
719                 // image to ensure it fits inside.
720                 cR &= vigra::Rect2D(0,0, srcImgSize.x, srcImgSize.y);
721 
722                 // Start with a blank alpha channel for the destination
723                 initImage(vigra::destImageRange(alpha),0);
724 
725                 // Copy in only the area inside the rectangle
726                 vigra::copyImage(alphaImg.first + cR.upperLeft(),
727                         alphaImg.first + cR.lowerRight(),
728                         alphaImg.second,
729                         alpha.upperLeft() + cR.upperLeft(),alpha.accessor());
730                 break;
731             }
732             default:
733                 break;
734         }
735         if(m_srcImg.hasActiveMasks())
736             vigra_ext::applyMask(vigra::destImageRange(alpha), m_srcImg.getActiveMasks());
737         if (Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposure", false))
738         {
739             const float lowerCutoff = Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposureLowerCutoff", NONA_DEFAULT_EXPOSURE_LOWER_CUTOFF);
740             const float upperCutoff = Nona::GetAdvancedOption(m_advancedOptions, "maskClipExposureUpperCutoff", NONA_DEFAULT_EXPOSURE_UPPER_CUTOFF);
741             vigra_ext::applyExposureClipMask(srcImg, vigra::destImageRange(alpha), lowerCutoff, upperCutoff);
742         };
743         if (useGPU) {
744             vigra_ext::transformImageAlphaGPU(srcImg,
745                                               vigra::srcImage(alpha),
746                                               destImageRange(Base::m_image),
747                                               destImage(Base::m_mask),
748                                               Base::boundingBox().upperLeft(),
749                                               m_transf,
750                                               invResponse,
751                                               m_srcImg.horizontalWarpNeeded(),
752                                               interp,
753                                               progress);
754             if (Base::boundingBox().right() > m_destImg.getROI().right())
755             {
756                 // dest image was enlarged for GPU alignment issue
757                 // delete the pixels outside
758                 vigra::Rect2D newBoundingBox = Base::boundingBox() & m_destImg.getROI();
759                 Base::m_image = CopyImageNewSize(Base::m_image, newBoundingBox.size());
760                 Base::m_mask = CopyImageNewSize(Base::m_mask, newBoundingBox.size());
761                 Base::m_region = newBoundingBox;
762             };
763         } else {
764             vigra_ext::transformImageAlpha(srcImg,
765                                            vigra::srcImage(alpha),
766                                            destImageRange(Base::m_image),
767                                            destImage(Base::m_mask),
768                                            Base::boundingBox().upperLeft(),
769                                            m_transf,
770                                            invResponse,
771                                            m_srcImg.horizontalWarpNeeded(),
772                                            interp,
773                                            progress,
774                                            singleThreaded);
775         }
776     } else {
777         if (useGPU) {
778             // extended region (if any) should already be cleared since ImportImageAlpha shouldn't have touched it.
779             vigra_ext::transformImageAlphaGPU(srcImg,
780                                               alphaImg,
781                                               destImageRange(Base::m_image),
782                                               destImage(Base::m_mask),
783                                               Base::boundingBox().upperLeft(),
784                                               m_transf,
785                                               invResponse,
786                                               m_srcImg.horizontalWarpNeeded(),
787                                               interp,
788                                               progress);
789             if (Base::boundingBox().right() > m_destImg.getROI().right())
790             {
791                 // dest image was enlarged for GPU alignment issue
792                 // delete the pixels outside
793                 vigra::Rect2D newBoundingBox = Base::boundingBox() & m_destImg.getROI();
794                 Base::m_image = CopyImageNewSize(Base::m_image, newBoundingBox.size());
795                 Base::m_mask = CopyImageNewSize(Base::m_mask, newBoundingBox.size());
796                 Base::m_region = newBoundingBox;
797             };
798         } else {
799             vigra_ext::transformImageAlpha(srcImg,
800                                            alphaImg,
801                                            destImageRange(Base::m_image),
802                                            destImage(Base::m_mask),
803                                            Base::boundingBox().upperLeft(),
804                                            m_transf,
805                                            invResponse,
806                                            m_srcImg.horizontalWarpNeeded(),
807                                            interp,
808                                            progress,
809                                            singleThreaded);
810         }
811     }
812 }
813 
814 
815 
816 
817 
818 
819 /** remap a single image
820  */
821 template <class SrcImgType, class FlatImgType, class DestImgType, class MaskImgType>
remapImage(SrcImgType & srcImg,const MaskImgType & srcAlpha,const FlatImgType & srcFlat,const SrcPanoImage & src,const PanoramaOptions & dest,vigra::Rect2D outputROI,RemappedPanoImage<DestImgType,MaskImgType> & remapped,AppBase::ProgressDisplay * progress)822 void remapImage(SrcImgType & srcImg,
823                 const MaskImgType & srcAlpha,
824                 const FlatImgType & srcFlat,
825                 const SrcPanoImage & src,
826                 const PanoramaOptions & dest,
827                 vigra::Rect2D outputROI,
828 //                vigra_ext::Interpolator interpolator,
829                 RemappedPanoImage<DestImgType, MaskImgType> & remapped,
830                 AppBase::ProgressDisplay* progress)
831 {
832 #ifdef DEBUG_REMAP
833     {
834         vigra::ImageExportInfo exi( DEBUG_FILE_PREFIX "hugin03_BeforeRemap.tif");
835                 vigra::exportImage(vigra::srcImageRange(srcImg), exi);
836     }
837     {
838         if (srcAlpha.width() > 0) {
839             vigra::ImageExportInfo exi(DEBUG_FILE_PREFIX "hugin04_BeforeRemapAlpha.tif");
840                     vigra::exportImage(vigra::srcImageRange(srcAlpha), exi);
841         }
842     }
843 #endif
844 
845     progress->setMessage("remapping", hugin_utils::stripPath(src.getFilename()));
846     // set pano image
847     DEBUG_DEBUG("setting src image with size: " << src.getSize());
848     remapped.setPanoImage(src, dest, outputROI);
849     // TODO: add provide support for flatfield images.
850     if (srcAlpha.size().x > 0) {
851         remapped.remapImage(vigra::srcImageRange(srcImg),
852                             vigra::srcImage(srcAlpha), dest.interpolator,
853                             progress);
854     } else {
855         remapped.remapImage(vigra::srcImageRange(srcImg), dest.interpolator, progress);
856     }
857 
858 #ifdef DEBUG_REMAP
859     {
860         vigra::ImageExportInfo exi( DEBUG_FILE_PREFIX "hugin04_AfterRemap.tif");
861                 vigra::exportImage(vigra::srcImageRange(remapped.m_image), exi);
862     }
863     {
864         vigra::ImageExportInfo exi(DEBUG_FILE_PREFIX "hugin04_AfterRemapAlpha.tif");
865                 vigra::exportImage(vigra::srcImageRange(remapped.m_mask), exi);
866     }
867 #endif
868 }
869 
870 
871 } //namespace
872 } //namespace
873 
874 #endif // _H
875