1 /*
2 * PTfeather.c
3 *
4 * Many of the routines are based on the program PTStitcher by Helmut
5 * Dersch.
6 *
7 * Copyright Helmut Dersch and Daniel M. German
8 *
9 * Nov 2006
10 *
11 * This program 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 * General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this software; see the file COPYING. If not, a copy
23 * can be downloaded from http://www.gnu.org/licenses/gpl.html, or
24 * obtained by writing to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
26 *
27 *
28 * Author: Daniel M German dmgerman at uvic doooot ca
29 *
30 */
31
32
33 #include "filter.h"
34
35 #include "pttiff.h"
36 #include "file.h"
37 #include "PTcommon.h"
38 #include "ptstitch.h"
39 #include "metadata.h"
40 #include "ptfeather.h"
41
42 #include <assert.h>
43 #include <float.h>
44
panoFeatherSnowPixel8Bit(unsigned char * pixel,int featherSize,unsigned int index)45 static void panoFeatherSnowPixel8Bit(unsigned char *pixel, int featherSize, unsigned int index)
46 {
47 int newPixel = 0;
48 int randomComponent = 0;
49 unsigned int level;
50
51 // printf("Value %d %d\n", *pixel, index);
52
53 // This operation could potentially overflow
54 level = (index * 255)/ featherSize;
55
56 // TODO: check this expression. It needs to be evaluated in the order specified by the
57 // parenthesis
58
59 //Make sure we do the arithmetic in long long to avoid overflows
60
61 randomComponent = ((rand() - RAND_MAX/2) * (0xfeLL /featherSize)) / RAND_MAX;
62
63 // we need to split the following expression to guarantee it is computed as integer, not unsigned char
64
65 newPixel = *pixel;
66 newPixel = newPixel- level + randomComponent;
67
68 // printf("Value %d newvalue %d Contribution %d Random %d\n", *pixel, newPixel, level, randomComponent);
69
70 if ( newPixel < 0 )
71 // we can't make it zero. We rely on value 1 to know where the actual edge of an image is
72 *pixel = 0;
73 else if (newPixel > 0xff)
74 *pixel = 0xff;
75 else
76 *pixel = newPixel;
77 }
78
panoFeatherSnowPixel16Bit(unsigned char * pixel,int featherSize,int index)79 static void panoFeatherSnowPixel16Bit(unsigned char *pixel, int featherSize, int index)
80 {
81 int newPixel = 0;
82 int randomComponent = 0;
83 unsigned long long int level;
84
85 uint16_t *pixel16;
86
87
88 level = (index * 0xffff)/ featherSize;
89
90 pixel16 = (uint16_t *) pixel;
91
92 // Make sure we do the arithmetic in long long to avoid overflows
93 randomComponent = ((rand() - RAND_MAX/2) * (0xfe00LL /featherSize)) / RAND_MAX;
94
95 newPixel = (int)(*pixel16 - level + randomComponent);
96
97 // printf("Value %d newvalue %d Contribution %d Random %d\n", *pixel16, newPixel, level, randomComponent);
98
99 if ( newPixel <= 0 )
100 // we can't make it zero. We rely on value 1 to know where the actual edge of an image is
101 *pixel16 = 0;
102 else if (newPixel > 0xffff)
103 *pixel16 = 0xffff;
104 else {
105 *pixel16 = newPixel;
106 }
107 }
108
panoFeatherSnowPixel(unsigned char * pixel,int featherSize,int index,int bytesPerSample)109 static void panoFeatherSnowPixel(unsigned char *pixel, int featherSize, int index, int bytesPerSample)
110 {
111 if (bytesPerSample == 1)
112 panoFeatherSnowPixel8Bit(pixel, featherSize, index);
113 else if (bytesPerSample == 2)
114 panoFeatherSnowPixel16Bit(pixel, featherSize, index);
115 else
116 assert(0);
117 }
118
119
panoFeatherSnowingHorizontalLeft(int column,int featherSize,unsigned char * ptrData,Image * image)120 static void panoFeatherSnowingHorizontalLeft(int column, int featherSize, unsigned char *ptrData, Image *image)
121 {
122 int index;
123 int currentColumn;
124
125 unsigned char *ptrPixel;
126 unsigned int pixel;
127 int bytesPerPixel = panoImageBytesPerPixel(image);
128 int bytesPerSample = panoImageBytesPerSample(image);
129
130 // ptrData points to the beginning of the line
131
132 // We start to the right, because the current column is the empty one
133 for (currentColumn = column+1, index = featherSize; currentColumn < column + featherSize+1; currentColumn++, index-- ) {
134
135
136 // only operate within the image
137 // and IF the mask is not zero
138 // We do not want to "feather" outside the boundary
139 if (currentColumn < 0 || currentColumn >= panoImageWidth(image))
140 continue;
141
142 ptrPixel = ptrData + currentColumn * bytesPerPixel;
143 pixel = panoStitchPixelChannelGet(ptrPixel, bytesPerSample, 0);
144
145
146 if (pixel == 0) {// stop when we find the edge
147 // printf("Breaking %d\n", currentColumn);
148 break;
149 }
150 panoFeatherSnowPixel(ptrPixel, featherSize, index, bytesPerSample);
151
152 } ///for
153
154 // printf("End\n");
155 }
156
panoFeatherSnowingHorizontalRight(int column,int featherSize,unsigned char * ptrData,Image * image)157 static void panoFeatherSnowingHorizontalRight(int column, int featherSize, unsigned char *ptrData, Image *image)
158 {
159 int index;
160 int currentColumn;
161
162 unsigned int pixel;
163 unsigned char *ptrPixel;
164 int bytesPerPixel = panoImageBytesPerPixel(image);
165 int bytesPerSample = panoImageBytesPerSample(image);
166
167 // ptrData points to the beginning of the line
168
169 // panoFeatherSnowingAreaVerticalFind(ptrData, bytesPerLine, gradient, column, &leftLines, &rightLines);
170
171 // determine where we start snowing to the left
172
173 index = 1;
174
175 for (currentColumn = column, index = featherSize; currentColumn > column - featherSize; currentColumn--, index-- ) {
176
177
178 // only operate within the image
179 // and IF the mask is not zero
180 // We do not want to "feather" outside the boundary
181 if (currentColumn < 0 || currentColumn >= panoImageWidth(image))
182 continue;
183
184 ptrPixel = ptrData + currentColumn * bytesPerPixel;
185 pixel = panoStitchPixelChannelGet(ptrPixel, bytesPerSample, 0);
186
187 if (pixel == 0) {// stop when we find the edge
188 // printf("Breaking %d\n", currentColumn);
189 break;
190 }
191 panoFeatherSnowPixel(ptrPixel, featherSize, index, bytesPerSample);
192
193 } ///for (currentColumn = column - gradient/2; currentColumn <= column; currentColumn++, index++ ) {
194
195 // printf("End\n");
196 }
197
panoFeatherSnowingVerticalBottom(int row,int featherSize,unsigned char * ptrData,Image * image)198 static void panoFeatherSnowingVerticalBottom(int row, int featherSize, unsigned char *ptrData, Image *image)
199 {
200 int index;
201 int currentRow;
202 int pixel;
203 unsigned char *ptrPixel;
204
205 int bytesPerLine = panoImageBytesPerLine(image);
206 int bytesPerSample = panoImageBytesPerSample(image);
207
208 for (currentRow = row, index=featherSize; currentRow > row - featherSize; currentRow--, index-- ) {
209
210 // only operate within the image
211 // and IF the mask is not zero
212 // We do not want to "feather" outside the boundary
213 if (currentRow < 0 || currentRow >= panoImageHeight(image)) {
214 continue;
215 }
216
217 ptrPixel = ptrData + currentRow * bytesPerLine;
218
219 pixel = panoStitchPixelChannelGet(ptrPixel, bytesPerSample, 0);
220
221 if (pixel == 0) {// stop when we find the edge
222 // printf("Breaking %d\n", currentRow);
223 break;
224 }
225 // printf("Doing...\n");
226
227 panoFeatherSnowPixel(ptrPixel, featherSize, index, bytesPerSample);
228
229 } ///for (currentRow = row - gradient/2; currentRow <= row; currentRow++, index++ ) {
230
231
232 }
233
panoFeatherSnowingVerticalTop(int row,int featherSize,unsigned char * ptrData,Image * image)234 static void panoFeatherSnowingVerticalTop(int row, int featherSize, unsigned char *ptrData, Image *image)
235 {
236 int index;
237 int currentRow;
238 int pixel;
239 unsigned char *ptrPixel;
240
241 int bytesPerLine = panoImageBytesPerLine(image);
242 int bytesPerSample = panoImageBytesPerSample(image);
243
244 for (currentRow = row+1, index=featherSize; currentRow < row + featherSize+1; currentRow++, index-- ) {
245
246 // only operate within the image
247 // and IF the mask is not zero
248 // We do not want to "feather" outside the boundary
249
250 if (currentRow < 0 || currentRow >= panoImageHeight(image)) {
251 continue;
252 }
253
254 ptrPixel = ptrData + currentRow * bytesPerLine;
255
256 pixel = panoStitchPixelChannelGet(ptrPixel, bytesPerSample, 0);
257
258 if (pixel == 0) {// stop when we find the edge
259 // printf("Breaking %d\n", currentRow);
260 break;
261 }
262 panoFeatherSnowPixel(ptrPixel, featherSize, index, bytesPerSample);
263 } ///for (currentRow = row - gradient/2; currentRow <= row; currentRow++, index++ ) {
264
265 }
266
267
268
panoFeatherMaskReplace(Image * image,unsigned int from,unsigned int to)269 void panoFeatherMaskReplace(Image* image, unsigned int from, unsigned int to)
270 {
271
272 // Replace a given value in the first channel with the desired value
273
274 int row;
275 int column;
276 uint16_t *pixel16;
277
278 int bitsPerSample = panoImageBitsPerSample(image);
279
280 int bytesPerPixel = panoImageBytesPerPixel(image);
281
282 int bytesPerLine = panoImageBytesPerLine(image);
283
284 int imageHeight = panoImageHeight(image);
285
286 int imageWidth = panoImageWidth(image);
287
288 unsigned char *pixel = panoImageData(image);
289
290
291 for (row = 0; row < imageHeight; row ++) {
292
293 pixel = panoImageData(image) + row * bytesPerLine;
294
295 for (column = 0; column < imageWidth; column ++, pixel += bytesPerPixel) {
296 if (bitsPerSample == 8) {
297 if ( *pixel == from ) {
298 *pixel = to;
299 }
300 }
301 else if (bitsPerSample == 16) {
302 pixel16 = (uint16_t *) pixel;
303 if (*pixel16 == from) {
304 *pixel16 = to;
305 }
306 } else {
307 assert(0);
308 }
309 } // for column
310
311 } // for row
312
313 }
314
panoFeatherChannelSave(unsigned char * channelBuffer,Image * image,int channel)315 void panoFeatherChannelSave(unsigned char *channelBuffer, Image *image, int channel)
316 {
317 // Copy a given channel to a preallocated buffer area
318 int i, j,k;
319 int bytesPerChannel;
320 unsigned char *imageData;
321 int bytesPerPixel;
322
323 bytesPerChannel = panoImageBytesPerSample(image);
324 imageData = panoImageData(image);
325 bytesPerPixel = panoImageBytesPerPixel(image);
326
327 for (i=0;i<panoImageWidth(image);i++)
328 for (j=0;j<panoImageHeight(image);j++) {
329 for (k=0;k<bytesPerChannel;k++) {
330 *(channelBuffer+k) = *(imageData + bytesPerChannel * channel + k);
331 }
332 channelBuffer += bytesPerChannel;
333 imageData += bytesPerPixel;
334 }
335 }
336
panoFeatherChannelMerge(unsigned char * channelBuffer,Image * image,int channel)337 void panoFeatherChannelMerge(unsigned char *channelBuffer, Image *image, int channel)
338 {
339 // We merge two alpha channels using the "multiply" operation.
340
341 // Copy a given channel to a preallocated buffer area
342 int i, j;
343 int bytesPerChannel;
344 unsigned char *imageData;
345 int bytesPerPixel;
346 unsigned int a, b;
347 unsigned long long int la, lb;
348
349 // FIrst test the rest of the logic before we do this
350
351 bytesPerChannel = panoImageBytesPerSample(image);
352 imageData = panoImageData(image);
353 bytesPerPixel = panoImageBytesPerPixel(image);
354
355 for (i=0;i<panoImageWidth(image);i++)
356 for (j=0;j<panoImageHeight(image);j++) {
357 if (bytesPerChannel == 1) {
358 a = *(imageData);
359 b = *(channelBuffer);
360
361 if (a < b)
362 *(imageData) = a;
363 else
364 *(imageData) = b;
365 } else if (bytesPerChannel == 2) {
366 la = *((uint16_t*)imageData);
367 lb = *((uint16_t*)channelBuffer);
368 if (la < lb)
369 *((uint16_t*)imageData) = (uint16_t)(la);
370 else
371 *((uint16_t*)imageData) = (uint16_t)(lb);
372 } else {
373 assert(0);
374 }
375 channelBuffer += bytesPerChannel;
376 imageData += bytesPerPixel;
377 }
378 }
379
panoFeatherChannelSwap(unsigned char * channelBuffer,Image * image,int channel)380 void panoFeatherChannelSwap(unsigned char *channelBuffer, Image *image, int channel)
381 {
382 // Swaps the data from a given channel
383 int i, j,k;
384 int bytesPerChannel;
385 unsigned char temp;
386 unsigned char *imageData;
387 int bytesPerPixel;
388
389 bytesPerChannel = panoImageBytesPerSample(image);
390 imageData = panoImageData(image);
391 bytesPerPixel = panoImageBytesPerPixel(image);
392 // printf("Bytes per channel %d\n", bytesPerChannel);
393 for (i=0;i<panoImageWidth(image);i++)
394 for (j=0;j<panoImageHeight(image);j++) {
395 for (k=0;k<bytesPerChannel;k++) {
396 temp = *(channelBuffer+k);
397 *(channelBuffer+k) = *(imageData + bytesPerChannel * channel + k);
398 *(imageData + bytesPerChannel * channel + k) = temp;
399 }
400 channelBuffer += bytesPerChannel;
401 imageData += bytesPerPixel;
402 }
403 }
404
405
406
407
panoFeatherImage(Image * image,int featherSize)408 static int panoFeatherImage(Image * image, int featherSize)
409 {
410
411 int ratio;
412 int difference;
413 unsigned char *pixelPtr;
414 unsigned char *ptrData;
415 unsigned char *savedAlphaChannel;
416 int column;
417 int row;
418 int gradient;
419
420 int bytesPerPixel;
421 int bytesPerLine;
422 int imageWidth;
423 int imageHeight;
424 int imageIsCropped;
425 int imageLeftOffset;
426 int imageFullWidth;
427 int imageFullHeight;
428 int bitsPerSample;
429 int bytesPerSample;
430 unsigned char *imageData;
431
432 if (featherSize == 0)
433 return 1;
434
435
436 // Use local variables so we don't have to make function calls for each
437 // iteration
438 bitsPerSample = panoImageBitsPerSample(image);
439 bytesPerSample = bitsPerSample /8;
440 bytesPerPixel = panoImageBytesPerPixel(image);
441 bytesPerLine = panoImageBytesPerLine(image);
442 imageHeight = panoImageHeight(image);
443 imageWidth = panoImageWidth(image);
444 imageIsCropped = panoImageIsCropped(image);
445 imageData = panoImageData(image);
446 imageFullWidth = panoImageFullWidth(image);
447 imageFullHeight = panoImageFullHeight(image);
448
449 imageLeftOffset = panoImageOffsetX(image);
450
451 // This is sort of a hack. We replace 0's in the mask with 1's
452 // we have to "undo" it at the end
453
454 // panoFeatherMaskReplace(image, 0, 1);
455
456 ratio = 0xfe00 / featherSize;
457
458 // Horizontal first
459
460 assert(bitsPerSample == 8 ||
461 bitsPerSample == 16);
462
463 // This algorithm is not perfect. It does not deal very well with images that have very wavy edges.
464 // For that reason I feather in one direction, then in the other, and then I combine the feathers
465 // This means we need to allocate space for an extra channel
466
467 savedAlphaChannel = calloc(bytesPerLine * imageHeight, 1);
468 if (savedAlphaChannel == NULL) {
469 return 0;
470 }
471 panoFeatherChannelSave(savedAlphaChannel, image, 0);
472 ptrData = imageData;
473
474 for ( row = 0; row < imageHeight; row++, ptrData += bytesPerLine) {
475 int widthToProcess;
476
477 pixelPtr = ptrData;
478
479 // The following code deals with images that are cropped. We should feather edges only
480 // if they are not the absolute edge of an image.
481
482 // by default we start in column zero
483 column = 0;
484 widthToProcess = imageWidth;
485 if (imageIsCropped) {
486 // we need to deal with edges that are not "real" edges (as in the uncropped image
487
488 if ( imageLeftOffset > 0) {
489 // we have a mask to the left... so we start in column "-1"
490 column = -1;
491 }
492
493 if (imageLeftOffset + widthToProcess < imageFullWidth) {
494 // then "add" one pixel to the right */
495 widthToProcess ++;
496 }
497 }
498
499
500 for (/*empty, see initialization above */; column < widthToProcess -1;
501 column ++, pixelPtr+=bytesPerPixel) {
502
503 // Values of mask in this pixel and next
504 int thisPixel;
505 int nextPixel;
506
507
508 if (column < 0) {
509 // this is the imaginary pixel to the left of the edge that should be feathered
510 thisPixel = 1;
511 } else {
512 thisPixel = panoStitchPixelChannelGet(pixelPtr, bytesPerSample, 0);
513 }
514
515 if (column >= imageWidth -1) {
516 // this is the imaginary pixel to the right of the edge that should be feathered
517 nextPixel = 1;
518 } else {
519 nextPixel = panoStitchPixelChannelGet(pixelPtr + bytesPerPixel, bytesPerSample, 0);
520 }
521
522 difference = thisPixel - nextPixel;
523
524 // This operation needs to be done here, otherwise 0x100/ratio will underflow
525 if (bitsPerSample == 8) {
526 gradient = (abs(difference) * 0x100LL) / ratio;
527 }
528 else if (bitsPerSample == 16) {
529 gradient = abs(difference) / ratio;
530 } else
531 assert(0);
532
533 if (nextPixel == 0 && thisPixel != 0) {
534 // Moving from the mask... proceed if there is not
535
536 if ( gradient > 1 ) { //
537 panoFeatherSnowingHorizontalRight(column, featherSize, ptrData, image);
538 }
539 }
540
541 if (thisPixel == 0 && nextPixel != 0) {
542 if ( gradient > 1 ) { //
543 panoFeatherSnowingHorizontalLeft(column, featherSize, ptrData, image);
544 } //
545
546 } //
547
548 } // for column...
549
550 } // for row
551
552 // We need to do the same in the orthogonal direction
553 // Sometimes I wished I had iterators over an image...
554
555 panoFeatherChannelSwap(savedAlphaChannel, image, 0);
556
557 ptrData = imageData;
558
559 for (column = 0; column < image->width; column ++, ptrData+=bytesPerPixel) {
560 int heightToProcess;
561
562 // The following code deals with images that are cropped. We should feather edges only
563 // if they are not the absolute edge of an image.
564
565 // by default we start in column zero
566 row = 0;
567 heightToProcess = imageHeight;
568
569 if (imageIsCropped) {
570 // we need to deal with edges that are not "real" edges (as in the uncropped image
571 int imageTopOffset;
572
573 imageTopOffset = panoImageOffsetY(image);
574
575 if ( imageTopOffset > 0) {
576 // we have a mask to the left... so we start in column "-1"
577 row = -1;
578 }
579
580 if (imageTopOffset + heightToProcess < imageFullHeight) {
581 // then "add" one pixel to the right */
582 heightToProcess ++;
583 }
584 }
585
586 pixelPtr = ptrData;
587 for (/*empty, see initialization above */; row < heightToProcess - 1;
588 row++, pixelPtr += bytesPerLine) {
589 int thisPixel;
590 int nextPixel;
591
592 // get pixel in current row
593 // with pixel in the next row
594
595 if (row < 0) {
596 // this is the imaginary pixel to the left of the edge that should be feathered
597 thisPixel = 1;
598 } else {
599 thisPixel = panoStitchPixelChannelGet(pixelPtr, bytesPerSample, 0);
600 }
601
602 if (row >= imageHeight -1) {
603 // this is the imaginary pixel to the right of the edge that should be feathered
604 nextPixel = 1;
605 } else {
606 nextPixel = panoStitchPixelChannelGet(pixelPtr + bytesPerLine, bytesPerSample, 0);
607 }
608
609 difference = thisPixel - nextPixel;
610
611 // This operation needs to be done here, otherwise 0x100/ratio will underflow
612 if (bitsPerSample == 8) {
613 gradient = (abs(difference) * 0x100LL) / ratio;
614 }
615 else if (bitsPerSample == 16) {
616 gradient = abs(difference) / ratio;
617 } else
618 assert(0);
619
620 if (gradient > 1) {
621
622 if (nextPixel == 0 && thisPixel != 0) {
623 // Moving from the mask... proceed if there is not
624 panoFeatherSnowingVerticalBottom(row, featherSize, ptrData, image);
625 }
626
627 if (nextPixel != 0 && thisPixel == 0) {
628 panoFeatherSnowingVerticalTop(row, featherSize, ptrData, image);
629 }
630 }
631
632 } // for column...
633
634 } // for row
635
636 // Average mask to avoid banding
637
638 #if 0
639
640 // THIS WAS AN ATTEMPT TO REMOVE BANDING, BUT IT IS TOO SIMPLE... IT WOULD REQUIRE A LARGER AREA,
641 // OR EVEN BETTER, A KERNEL BASED BLUR
642
643 {
644 int row, column;
645 unsigned int above, below,left, right, thisPixel;
646
647 pixelPtr = imageData;
648
649 for ( row = 0; row < imageHeight; row++) {
650 for ( column = 0; column < imageWidth; column++, pixelPtr += bytesPerPixel) {
651 // average pixel to its pixels around
652 thisPixel = panoStitchPixelChannelGet(pixelPtr, bytesPerSample, 0);
653
654
655 if (thisPixel == 0)
656 continue;
657
658 if ((bitsPerSample == 8 && thisPixel == 0xff) ||
659 (bitsPerSample == 16 && thisPixel == 0xffff)) {
660 continue;
661 }
662
663 // average to its neighbors
664
665 above = below = left = right = thisPixel;
666 if (row > 0)
667 above = panoStitchPixelChannelGet(pixelPtr - bytesPerLine, bytesPerSample, 0);
668
669 if (row < imageHeight)
670 below = panoStitchPixelChannelGet(pixelPtr + bytesPerLine, bytesPerSample, 0);
671
672 if (column > 0)
673 left = panoStitchPixelChannelGet(pixelPtr - bytesPerLine, bytesPerSample, 0);
674
675 if (column < imageWidth)
676 right = panoStitchPixelChannelGet(pixelPtr + bytesPerLine, bytesPerSample, 0);
677
678 thisPixel = (thisPixel + above + below + left + right)/ 5;
679
680 panoStitchPixelChannelSet(pixelPtr, bytesPerSample, 0, thisPixel);
681
682 }
683 }
684 }
685 #endif
686
687 panoFeatherChannelMerge(savedAlphaChannel, image, 0);
688 free(savedAlphaChannel);
689
690 return 1;
691 }
692
693
panoFeatherFile(fullPath * inputFile,fullPath * outputFile,int featherSize)694 int panoFeatherFile(fullPath * inputFile, fullPath * outputFile,
695 int featherSize)
696 {
697 Image image;
698 if (panoTiffRead(&image, inputFile->name) == 0) {
699 PrintError("Could not open TIFF-file [%s]", inputFile->name);
700 return 0;
701 }
702
703 if (panoImageBitsPerSample(&image) == 8 ||
704 panoImageBitsPerSample(&image) == 16) {
705 panoFeatherImage(&image, featherSize);
706 }
707 else {
708 fprintf(stderr,
709 "Apply feather not supported for this image type (%d bitsPerPixel)\n",
710 (int) image.bitsPerPixel);
711 exit(1);
712 }
713
714 if (panoTiffWrite(&image, outputFile->name) == 0) {
715 PrintError("Could not write TIFF-file [%s]", outputFile->name);
716 return 0;
717 }
718
719 panoImageDispose(&image);
720
721 return 1;
722
723 }
724
725