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