I tried to stitch a panorama with 1350 images with multiblend. It didn't 
work in Windows because the command line where all the image filenames are 
listed was longer than 32768 characters. At least in Windows the limit is 
32768 (or maybe one less).

I suggest adding the possibility to read the command line arguments from a 
file.
I changed multiblend.cpp so that you can add a command line option  
--argfile filename  or  --argfile=filename . After this no further 
arguments may follow in the command line, but each line in the file 
"filename" counts as another argument, e.g. call
multiblend.exe" --argfile=test.txt
with test.txt containing e.g.
--compression=LZW
-o
test110.tif
--
test1100000.tif
test1100001.tif
...

In the attachment I have the modified version of multiblend.cpp.

Maybe Hugin and HuginExecutor could be changed so that the arguments are 
written in a file if they are many.

Florian

klaus...@gmail.com schrieb am Sonntag, 13. Juni 2021 um 11:55:00 UTC+2:

> Hello,
>
> as there is actual coding of new software going on, maybe one can iron out 
> a deficiency in the Hugin lens model. At least lay the groundwork for it.
>
> The Brown-Conrady model parameters are sound, but the intersection with 
> the abc-Hugin parameter set contains only one (1) non-trivial distortion 
> parameter.
>
> I suggest to add further Brown-Conrady parameters to the software code you 
> are currently writing. Now.
>
> Best regards
>
> Klaus
> On 11.06.21 18:20, Florian Königstein wrote:
>
> Monkey, I much appreciate your software.
> I like it because I like big panoramas ... and the speedup is welcome.
>
> For big panoramas there's another issue: Geometrical optimization is slow.
> I developed a fork for the libpano library that I called fastPTOptimizer.
> For large panoramas the speedup factor for optimization can be 100 or more.
>
> I integrated both your multiblend and my fastPTOptimizer into a 
> "development version" of Hugin.
> Multiblend is now the default enblend-like program (in the GUI is still 
> written "enblend"
> but you can see that multiblend is used by choosing Preferences / 
> Programs).
> Only the CMakeLists.txt files are not updated so that Multiblend is 
> automatically integrated
> because I'm not yet so familiar with creating files for CMake.
>
> My version of Hugin is here:
> https://sourceforge.net/projects/huginplusplus/files/development/
>
>
> Monkey schrieb am Samstag, 10. April 2021 um 22:00:35 UTC+2:
>
>> Has anyone out there tried either the x64 or x86 versions of Multiblend 
>> 2.0 on Windows XP or Windows Vista? Someone's reporting vcredist problems 
>> and I'm not sure if it's because I built using the latest platform toolset.
>>
>>
>> On Sunday, 4 April 2021 at 17:11:16 UTC+1 lownsl...@gmail.com wrote:
>>
>>> I'll give it a shot. last time I used it it for a aerial 360 it removed 
>>> cars and other ground objects.
>>>
>>> On Friday, March 5, 2021 at 3:57:30 PM UTC-8 Monkey wrote:
>>>
>>>> *(* for a Gigapixel mosaic, anyway; it's complicated, see below)*
>>>>
>>>> http://horman.net/multiblend/
>>>>
>>>> It seems Groups won't let me post the quasi-essay I had written, 
>>>> complete with images, so the link above will have to suffice.
>>>>
>>>> Here's Multiblend 2.0, faster, better, more... blendy. I'm calling it a 
>>>> Release Candidate because there's only so much testing I can stand to do, 
>>>> and I've hit a dead-end with features, so I thought I'd put it out there 
>>>> for people to try. I expect some bugs to be found pretty quickly, which 
>>>> I'll hopefully fix pretty quickly.
>>>>
>>>> It's released under GPLv3.
>>>>
>>> -- 
>
> A list of frequently asked questions is available at: 
> http://wiki.panotools.org/Hugin_FAQ
> --- 
> You received this message because you are subscribed to the Google Groups 
> "hugin and other free panoramic software" group.
> To unsubscribe from this group and stop receiving emails from it, send an 
> email to hugin-ptx+...@googlegroups.com.
>
> To view this discussion on the web visit 
> https://groups.google.com/d/msgid/hugin-ptx/2699006b-895d-42f4-bd19-6ed0d3f3863bn%40googlegroups.com
>  
> <https://groups.google.com/d/msgid/hugin-ptx/2699006b-895d-42f4-bd19-6ed0d3f3863bn%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
>

-- 
A list of frequently asked questions is available at: 
http://wiki.panotools.org/Hugin_FAQ
--- 
You received this message because you are subscribed to the Google Groups 
"hugin and other free panoramic software" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to hugin-ptx+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/hugin-ptx/6c01e515-ad33-440e-ab47-4e28e44a475en%40googlegroups.com.
/*
        Multiblend 2.0 (c) 2021 David Horman

        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.

        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
        GNU General Public License for more details.

        You should have received a copy of the GNU General Public License
        along with this program. If not, see <https://www.gnu.org/licenses/>.

        The author can be contacted at davidhorma...@gmail.com
*/

#define NOMINMAX
#include <stdio.h>
#include <stdint.h>
#include <vector>
#include <algorithm>
#ifdef __APPLE__
#define memalign(a,b) malloc((b))
#else
#include <malloc.h>
#endif

#include "tiffio.h"
#include "jpeglib.h"

#ifndef _WIN32
#include <strings.h>
int _stricmp(const char* a, const char* b) { return strcasecmp(a, b); }
#define ZeroMemory(a,b) memset(a,0,b)
#define sprintf_s sprintf
#define sscanf_s sscanf
void* _aligned_malloc(size_t size, int boundary) { return memalign(boundary, 
size); }
void _aligned_free(void* a) { free(a); }
void fopen_s(FILE** f, const char* filename, const char* mode) { *f = 
fopen(filename, mode); }
#endif

int verbosity = 1;

#include "pnger.cpp"
#include "pyramid.cpp"
#include "functions.cpp"

#include "mapalloc.cpp"
#include "threadpool.cpp"
#include "geotiff.cpp"

class PyramidWithMasks : public Pyramid {
public:
        using Pyramid::Pyramid;
        std::vector<Flex*> masks;
};

enum class ImageType { MB_NONE, MB_TIFF, MB_JPEG, MB_PNG };

#include "image.cpp"

#ifdef _WIN32
FILE _iob[] = { *stdin, *stdout, *stderr };

extern "C" FILE * __cdecl __iob_func(void) {
        return _iob;
}
#pragma comment(lib, "legacy_stdio_definitions.lib")
// the above are required to support VS 2010 build of libjpeg-turbo 2.0.6
#pragma comment(lib, "tiff.lib")
#pragma comment(lib, "turbojpeg.lib")
#pragma comment(lib, "libpng16.lib")
#pragma comment(lib, "zlib.lib")
#pragma comment(lib, "lzma.lib")
#endif

#define MASKVAL(X) (((X) & 0x7fffffffffffffff) | images[(X) & 
0xffffffff]->mask_state)

int main(int argc, char* argv[]) {
// This is here because of a weird problem encountered during development with 
Visual Studio. It should never be triggered.
        if (verbosity != 1) {
                printf("bad compile?\n");
                exit(EXIT_FAILURE);
        }

        int i;
        Timer timer_all, timer;
        timer_all.Start();

        TIFFSetWarningHandler(NULL);

/***********************************************************************
* Variables
***********************************************************************/
        std::vector<Image*> images;
        int fixed_levels = 0;
        int add_levels = 0;

        int width = 0;
        int height = 0;

        bool no_mask = false;
        bool big_tiff = false;
        bool bgr = false;
        bool wideblend = false;
        bool reverse = false;
        bool timing = false;
        bool dither = true;
        bool gamma = false;
        bool all_threads = true;
        int wrap = 0;

        TIFF* tiff_file = NULL;
        FILE* jpeg_file = NULL;
        Pnger* png_file = NULL;
        ImageType output_type = ImageType::MB_NONE;
        int jpeg_quality = -1;
        int compression = -1;
        char* seamsave_filename = NULL;
        char* seamload_filename = NULL;
        char* xor_filename = NULL;
        char* output_filename = NULL;
        int output_bpp = 0;

        double images_time = 0;
        double copy_time = 0;
        double seam_time = 0;
        double shrink_mask_time = 0;
        double shrink_time = 0;
        double laplace_time = 0;
        double blend_time = 0;
        double collapse_time = 0;
        double wrap_time = 0;
        double out_time = 0;
        double write_time = 0;

/***********************************************************************
* Help
***********************************************************************/
        if (argc == 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help") 
|| !strcmp(argv[1], "/?")) {
                Output(1, "\n");
                Output(1, "Multiblend v2.0.0 (c) 2021 David Horman        
http://horman.net/multiblend/\n";);
                Output(1, 
"----------------------------------------------------------------------------\n");

                printf("Usage: multiblend [options] [-o OUTPUT] INPUT [X,Y] 
[INPUT] [X,Y] [INPUT]...\n");
                printf("\n");
                printf("Options:\n");
                printf("  --levels X / -l X      X: set number of blending 
levels to X\n");
                printf("                        -X: decrease number of blending 
levels by X\n");
                printf("                        +X: increase number of blending 
levels by X\n");
                printf("  --depth D / -d D       Override automatic output 
image depth (8 or 16)\n");
                printf("  --bgr                  Swap RGB order\n");
                printf("  --wideblend            Calculate number of levels 
based on output image size,\n");
                printf("                         rather than input image 
size\n");
                printf("  -w, --wrap=[mode]      Blend around images boundaries 
(NONE (default),\n");
                printf("                         HORIZONTAL, VERTICAL). When 
specified without a mode,\n");
                printf("                         defaults to HORIZONTAL.\n");
                printf("  --compression=X        Output file compression. For 
TIFF output, X may be:\n");
                printf("                         NONE (default), PACKBITS, or 
LZW\n");
                printf("                         For JPEG output, X is JPEG 
quality (0-100, default 75)\n");
                printf("                         For PNG output, X is PNG 
filter (0-9, default 3)\n");
                printf("  --cache-threshold=     Allocate memory beyond X 
bytes/[K]ilobytes/\n");
                printf("      X[K/M/G]           [M]egabytes/[G]igabytes to 
disk\n");
                printf("  --no-dither            Disable dithering\n");
                printf("  --tempdir <dir>        Specify temporary directory 
(default: system temp)\n");
                printf("  --save-seams <file>    Save seams to PNG file for 
external editing\n");
                printf("  --load-seams <file>    Load seams from PNG file\n");
                printf("  --no-output            Do not blend (for use with 
--save-seams)\n");
                printf("                         Must be specified as last 
option before input images\n");
                printf("  --bigtiff              BigTIFF output\n");
                printf("  --reverse              Reverse image priority 
(last=highest) for resolving\n");
                printf("                         indeterminate pixels\n");
                printf("  --quiet                Suppress output (except 
warnings)\n");
                printf("  --all-threads          Use all available CPU 
threads\n");
                printf("  [X,Y]                  Optional position adjustment 
for previous input image\n");
                exit(EXIT_SUCCESS);
        }

/***********************************************************************
************************************************************************
* Parse arguments
************************************************************************
***********************************************************************/
        std::vector<char*> my_argv;

        bool skip = false;
        long argbufferlength;
        char *argbuffermem = 0, *argbuffer, *curarg;
        FILE* argfile = 0;

        for (i = 1; i < argc; ++i) {

                if (!strcmp(argv[i], "--argfile")) {
                        if(++i >= argc) {
                                die("--argfile requires the filename in the 
next argument");
                        }
                        fopen_s(&argfile, argv[i], "r");
                        if(0 == argfile) {
                                die("cannot open argument file %s\n", argv[i]);
                        }
                        if (++i < argc) {
                                die("--argfile requires the filename in the 
next argument, but no other arguments may follow");
                        }
                        break;
                }

                my_argv.push_back(argv[i]);

                if (!skip) {
                        int c = 0;

                        while (argv[i][c]) {
                                if (argv[i][c] == '=') {
                                        argv[i][c++] = 0;

                                        if (!strcmp(argv[i], "--argfile")) {
                                                if (0 == argv[i][c]) {
                                                        die("--argfile= 
requires the filename after the = character");
                                                }
                                                fopen_s(&argfile, &argv[i][c], 
"r");
                                                if (0 == argfile) {
                                                        die("cannot open 
argument file %s\n", &argv[i][c]);
                                                }
                                                if (++i < argc) {
                                                        die("--argfile= 
requires the filename after the = character, but no other arguments may 
follow");
                                                }
                                                break;
                                        }

                                        if (argv[i][c]) {
                                                my_argv.push_back(&argv[i][c]);
                                        }
                                        break;
                                }
                                ++c;
                        }

                        if(argfile) {
                                my_argv.pop_back();
                                break;
                        }

                        if (!strcmp(argv[i], "-o") || !strcmp(argv[i], 
"--output")) {
                                skip = true;
                        }
                }
        }

        if(argfile)
        {
                long prevpos, filesize;
                if(-1 == (prevpos = ftell(argfile)) || 0 != fseek(argfile, 0L, 
SEEK_END) ||
                        -1 == (filesize = ftell(argfile)) || 0 != 
fseek(argfile, prevpos, SEEK_SET)) {
                        fclose(argfile);
                        die("error reading argument file");
                }

                argbufferlength = filesize + 1;
                if(0 == (argbuffermem = (char*)malloc(argbufferlength * 
sizeof(char)))) {
                        fclose(argfile);
                        die("cannot allocate memory for argument file");
                }

                argbuffermem[argbufferlength - 1] = 0;
                argbuffer = argbuffermem;

                while (fgets(argbuffer, argbufferlength, argfile)) {

                        curarg = argbuffer;
                        if(0 != argbuffer[0])
                        {
                                for(curarg = argbuffer + strnlen_s(argbuffer, 
argbufferlength); curarg-- != argbuffer && ('\r' == *curarg || '\n' == 
*curarg); )
                                        *curarg = 0;
                                for (curarg = argbuffer; '\r' == *curarg || 
'\n' == *curarg; curarg++)
                                        { }
                        }

                        argbufferlength -= (long)(strlen(curarg) + 1);
                        argbuffer = curarg + (strlen(curarg) + 1);

                        my_argv.push_back(curarg);

                        if (!skip) {
                                int c = 0;

                                while (curarg[c]) {
                                        if (curarg[c] == '=') {
                                                curarg[c++] = 0;
                                                if (curarg[c]) {
                                                        
my_argv.push_back(&curarg[c]);
                                                }
                                                break;
                                        }
                                        ++c;
                                }

                                if (!strcmp(curarg, "-o") || !strcmp(curarg, 
"--output")) {
                                        skip = true;
                                }
                        }
                }

                fclose(argfile);
        }

        if ((int)my_argv.size() < 3) die("Error: Not enough arguments (try -h 
for help)");

        for (i = 0; i < (int)my_argv.size(); ++i) {
                if (!strcmp(my_argv[i], "-d") || !strcmp(my_argv[i], "--d") || 
!strcmp(my_argv[i], "--depth") || !strcmp(my_argv[i], "--bpp")) {
                        if (++i < (int)my_argv.size()) {
                                output_bpp = atoi(my_argv[i]);
                                if (output_bpp != 8 && output_bpp != 16) {
                                        die("Error: Invalid output depth 
specified");
                                }
                        } else {
                                die("Error: Missing parameter value");
                        }
                }               else if (!strcmp(my_argv[i], "-l") || 
!strcmp(my_argv[i], "--levels")) {
                        if (++i < (int)my_argv.size()) {
                                int n;
                                if (my_argv[i][0] == '+' || my_argv[i][0] == 
'-') {
                                        sscanf_s(my_argv[i], "%d%n", 
&add_levels, &n);
                                } else {
                                        sscanf_s(my_argv[i], "%d%n", 
&fixed_levels, &n);
                                        if (fixed_levels == 0) fixed_levels = 1;
                                }
                                if (my_argv[i][n]) die("Error: Bad --levels 
parameter");
                        } else {
                                die("Error: Missing parameter value");
                        }
                }               else if (!strcmp(my_argv[i], "--wrap") || 
!strcmp(my_argv[i], "-w")) {
                        if (i + 1 >= (int)my_argv.size()) {
                                die("Error: Missing parameters");
                        }
                        if (!strcmp(my_argv[i + 1], "none") || 
!strcmp(my_argv[i + 1], "open")) ++i;
                        else if (!strcmp(my_argv[i + 1], "horizontal") || 
!strcmp(my_argv[i + 1], "h")) { wrap = 1; ++i; } else if (!strcmp(my_argv[i + 
1], "vertical") || !strcmp(my_argv[i + 1], "v")) { wrap = 2; ++i; } else if 
(!strcmp(my_argv[i + 1], "both") || !strcmp(my_argv[i + 1], "hv")) { wrap = 3; 
++i; } else wrap = 1;
                }               else if (!strcmp(my_argv[i], 
"--cache-threshold")) {
                        if (i + 1 >= (int)my_argv.size()) {
                                die("Error: Missing parameters");
                        }
                        ++i;
                        int shift = 0;
                        int n = 0;
                        size_t len = strlen(my_argv[i]);
                        size_t threshold;
                        sscanf_s(my_argv[i], "%zu%n", &threshold, &n);
                        if (n != len) {
                                if (n == len - 1) {
                                        switch (my_argv[i][len - 1]) {
                                                case 'k':
                                                case 'K': shift = 10; break;
                                                case 'm':
                                                case 'M': shift = 20; break;
                                                case 'g':
                                                case 'G': shift = 30; break;
                                                default: die("Error: Bad 
--cache-threshold parameter");
                                        }
                                        threshold <<= shift;
                                } else {
                                        die("Error: Bad --cache-threshold 
parameter");
                                }
                        }
                        MapAlloc::CacheThreshold(threshold);
                }               else if (!strcmp(my_argv[i], "--nomask") || 
!strcmp(my_argv[i], "--no-mask")) no_mask = true;
                else if (!strcmp(my_argv[i], "--timing") || !strcmp(my_argv[i], 
"--timings")) timing = true;
                else if (!strcmp(my_argv[i], "--bigtiff"))   big_tiff = true;
                else if (!strcmp(my_argv[i], "--bgr"))       bgr = true;
                else if (!strcmp(my_argv[i], "--wideblend")) wideblend = true;
                else if (!strcmp(my_argv[i], "--reverse"))   reverse = true;
                else if (!strcmp(my_argv[i], "--gamma"))     gamma = true;
                else if (!strcmp(my_argv[i], "--no-dither") || 
!strcmp(my_argv[i], "--nodither")) dither = false;
                //              else if (!strcmp(my_argv[i], "--force"))     
force_coverage = true;
                else if (!strncmp(my_argv[i], "-f", 2)) Output(0, "ignoring 
Enblend option -f\n");
                else if (!strcmp(my_argv[i], "-a")) Output(0, "ignoring Enblend 
option -a\n");
                else if (!strcmp(my_argv[i], "--no-ciecam")) Output(0, 
"ignoring Enblend option --no-ciecam\n");
                else if (!strcmp(my_argv[i], "--primary-seam-generator")) {
                        Output(0, "ignoring Enblend option 
--primary-seam-generator\n");
                        ++i;
                }

                else if (!strcmp(my_argv[i], "--compression")) {
                        if (++i < (int)my_argv.size()) {
                                if (strcmp(my_argv[i], "0") == 0) jpeg_quality 
= 0;
                                else if (atoi(my_argv[i]) > 0) jpeg_quality = 
atoi(my_argv[i]);
                                else if (_stricmp(my_argv[i], "lzw") == 0) 
compression = COMPRESSION_LZW;
                                else if (_stricmp(my_argv[i], "packbits") == 0) 
compression = COMPRESSION_PACKBITS;
                                //                              else if 
(_stricmp(my_argv[i], "deflate") == 0) compression = COMPRESSION_DEFLATE;
                                else if (_stricmp(my_argv[i], "none") == 0) 
compression = COMPRESSION_NONE;
                                else die("Error: Unknown compression codec %s", 
my_argv[i]);
                        } else {
                                die("Error: Missing parameter value");
                        }
                } else if (!strcmp(my_argv[i], "-v") || !strcmp(my_argv[i], 
"--verbose")) ++verbosity;
                else if (!strcmp(my_argv[i], "-q") || !strcmp(my_argv[i], 
"--quiet")) --verbosity;
                else if ((!strcmp(my_argv[i], "--saveseams") || 
!strcmp(my_argv[i], "--save-seams")) && i < (int)my_argv.size() - 1) 
seamsave_filename = my_argv[++i];
                else if ((!strcmp(my_argv[i], "--loadseams") || 
!strcmp(my_argv[i], "--load-seams")) && i < (int)my_argv.size() - 1) 
seamload_filename = my_argv[++i];
                else if ((!strcmp(my_argv[i], "--savexor") || 
!strcmp(my_argv[i], "--save-xor")) && i < (int)my_argv.size() - 1) xor_filename 
= my_argv[++i];
                else if (!strcmp(my_argv[i], "--tempdir") || 
!strcmp(my_argv[i], "--tmpdir") && i < (int)my_argv.size() - 1) 
MapAlloc::SetTmpdir(my_argv[++i]);
                else if (!strcmp(my_argv[i], "--all-threads")) all_threads = 
true;
                else if (!strcmp(my_argv[i], "-o") || !strcmp(my_argv[i], 
"--output")) {
                        if (++i < (int)my_argv.size()) {
                                output_filename = my_argv[i];
                                char* ext = strrchr(output_filename, '.');

                                if (!ext) {
                                        die("Error: Unknown output filetype");
                                }

                                ++ext;
                                if (!(_stricmp(ext, "jpg") && _stricmp(ext, 
"jpeg"))) {
                                        output_type = ImageType::MB_JPEG;
                                        if (jpeg_quality == -1) jpeg_quality = 
75;
                                } else if (!(_stricmp(ext, "tif") && 
_stricmp(ext, "tiff"))) {
                                        output_type = ImageType::MB_TIFF;
                                } else if (!_stricmp(ext, "png")) {
                                        output_type = ImageType::MB_PNG;
                                } else {
                                        die("Error: Unknown file extension");
                                }

                                ++i;
                                break;
                        }
                } else if (!strcmp(my_argv[i], "--no-output")) {
                        ++i;
                        break;
                } else {
                        die("Error: Unknown argument \"%s\"", my_argv[i]);
                }
        }

        if (compression != -1) {
                if (output_type != ImageType::MB_TIFF) {
                        Output(0, "Warning: non-TIFF output; ignoring TIFF 
compression setting\n");
                }
        } else if (output_type == ImageType::MB_TIFF) {
                compression = COMPRESSION_LZW;
        }

        if (jpeg_quality != -1 && output_type != ImageType::MB_JPEG && 
output_type != ImageType::MB_PNG) {
                Output(0, "Warning: non-JPEG/PNG output; ignoring compression 
quality setting\n");
        }

        if ((jpeg_quality < -1 || jpeg_quality > 9) && output_type == 
ImageType::MB_PNG) {
                die("Error: Bad PNG compression quality setting\n");
        }

        if (output_type == ImageType::MB_NONE && !seamsave_filename) 
die("Error: No output file specified");
        if (seamload_filename && seamsave_filename) die("Error: Cannot load and 
save seams at the same time");
        if (wrap == 3) die("Error: Wrapping in both directions is not currently 
supported");

        if (!strcmp(my_argv[i], "--")) ++i;

/***********************************************************************
* Push remaining arguments to images vector
***********************************************************************/
        int x, y, n;

        while (i < (int)my_argv.size()) {
                if (images.size()) {
                        n = 0;
                        sscanf_s(my_argv[i], "%d,%d%n", &x, &y, &n);
                        if (!my_argv[i][n]) {
                                images.back()->xpos_add = x;
                                images.back()->ypos_add = y;
                                i++;
                                continue;
                        }
                }
                images.push_back(new Image(my_argv[i++]));
        }

        int n_images = (int)images.size();

        if (n_images == 0) die("Error: No input files specified");
        if (seamsave_filename && n_images > 256) { seamsave_filename = NULL; 
Output(0, "Warning: seam saving not possible with more than 256 images"); }
        if (seamload_filename && n_images > 256) { seamload_filename = NULL; 
Output(0, "Warning: seam loading not possible with more than 256 images"); }
        if (xor_filename && n_images > 255) { xor_filename = NULL; Output(0, 
"Warning: XOR map saving not possible with more than 255 images"); }

/***********************************************************************
* Print banner
***********************************************************************/
        Output(1, "\n");
        Output(1, "Multiblend v2.0.0 (c) 2021 David Horman        
http://horman.net/multiblend/\n";);
        Output(1, 
"----------------------------------------------------------------------------\n");

        Threadpool* threadpool = Threadpool::GetInstance(all_threads ? 2 : 0);

/***********************************************************************
************************************************************************
* Open output
************************************************************************
***********************************************************************/
        switch (output_type) {
                case ImageType::MB_TIFF: {
                        if (!big_tiff) tiff_file = TIFFOpen(output_filename, 
"w"); else tiff_file = TIFFOpen(output_filename, "w8");
                        if (!tiff_file) die("Error: Could not open output 
file");
                } break;
                case ImageType::MB_JPEG: {
                        if (output_bpp == 16) die("Error: 16bpp output is 
incompatible with JPEG output");
                        fopen_s(&jpeg_file, output_filename, "wb");
                        if (!jpeg_file) die("Error: Could not open output 
file");
                } break;
                case ImageType::MB_PNG: {
                        fopen_s(&jpeg_file, output_filename, "wb");
                        if (!jpeg_file) die("Error: Could not open output 
file");
                } break;
        }

/***********************************************************************
************************************************************************
* Process images
************************************************************************
***********************************************************************/
        timer.Start();

/***********************************************************************
* Open images to get prelimary info
***********************************************************************/
        size_t untrimmed_bytes = 0;

        for (i = 0; i < n_images; ++i) {
                images[i]->Open();
                untrimmed_bytes = std::max(untrimmed_bytes, 
images[i]->untrimmed_bytes);
        }

/***********************************************************************
* Check paramters, display warnings
***********************************************************************/
        for (i = 1; i < n_images; ++i) {
                if (images[i]->tiff_xres != images[0]->tiff_xres || 
images[i]->tiff_yres != images[0]->tiff_yres) {
                        Output(0, "Warning: TIFF resolution mismatch (%f %f/%f 
%f)\n", images[0]->tiff_xres, images[0]->tiff_yres, images[i]->tiff_xres, 
images[i]->tiff_yres);
                }
        }

        for (i = 0; i < n_images; ++i) {
                if (output_bpp == 0 && images[i]->bpp == 16) output_bpp = 16;
                if (images[i]->bpp != images[0]->bpp) {
                        die("Error: mixture of 8bpp and 16bpp images detected 
(not currently handled)\n");
                }
        }

        if (output_bpp == 0) output_bpp = 8;
        else if (output_bpp == 16 && output_type == ImageType::MB_JPEG) {
                Output(0, "Warning: 8bpp output forced by JPEG output\n");
                output_bpp = 8;
        }

/***********************************************************************
* Allocate working space for reading/trimming/extraction
***********************************************************************/
        void* untrimmed_data = MapAlloc::Alloc(untrimmed_bytes);

/***********************************************************************
* Read/trim/extract
***********************************************************************/
        for (i = 0; i < n_images; ++i) {
                try {
                        images[i]->Read(untrimmed_data, gamma);
                } catch (char* e) {
                        printf("\n\n");
                        printf("%s\n", e);
                        exit(EXIT_FAILURE);
                }
        }

/***********************************************************************
* Clean up
***********************************************************************/
        MapAlloc::Free(untrimmed_data);

/***********************************************************************
* Tighten
***********************************************************************/
        int min_xpos = 0x7fffffff;
        int min_ypos = 0x7fffffff;
        width = 0;
        height = 0;

        for (i = 0; i < n_images; ++i) {
                min_xpos = std::min(min_xpos, images[i]->xpos);
                min_ypos = std::min(min_ypos, images[i]->ypos);
        }

        for (i = 0; i < n_images; ++i) {
                images[i]->xpos -= min_xpos;
                images[i]->ypos -= min_ypos;
                width = std::max(width, images[i]->xpos + images[i]->width);
                height = std::max(height, images[i]->ypos + images[i]->height);
        }

        images_time = timer.Read();

/***********************************************************************
* Determine number of levels
***********************************************************************/
        int blend_wh;
        int blend_levels;

        if (!fixed_levels) {
                if (!wideblend) {
                        std::vector<int> widths;
                        std::vector<int> heights;

                        for (auto image : images) {
                                widths.push_back(image->width);
                                heights.push_back(image->height);
                        }

                        std::sort(widths.begin(), widths.end());
                        std::sort(heights.begin(), heights.end());

                        size_t halfway = (widths.size() - 1) >> 1;

                        blend_wh = std::max(
                                widths.size() & 1 ? widths[halfway] : 
(widths[halfway] + widths[halfway + 1] + 1) >> 1,
                                heights.size() & 1 ? heights[halfway] : 
(heights[halfway] + heights[halfway + 1] + 1) >> 1
                        );
                } else {
                        blend_wh = (std::max)(width, height);
                }

                blend_levels = (int)floor(log2(blend_wh + 4.0f) - 1);
                if (wideblend) blend_levels++;
        } else {
                blend_levels = fixed_levels;
        }

        blend_levels += add_levels;

        if (n_images == 1) {
                blend_levels = 0;
                Output(1, "\n%d x %d, %d bpp\n\n", width, height, output_bpp);
        } else {
                Output(1, "\n%d x %d, %d levels, %d bpp\n\n", width, height, 
blend_levels, output_bpp);
        }
        
/***********************************************************************
************************************************************************
* Seaming
************************************************************************
***********************************************************************/
        timer.Start();

        Output(1, "Seaming");
        switch (((!!seamsave_filename) << 1) | !!xor_filename) {
                case 1: Output(1, " (saving XOR map)"); break;
                case 2: Output(1, " (saving seam map)"); break;
                case 3: Output(1, " (saving XOR and seam maps)"); break;
        }
        Output(1, "...\n");

        int min_count;
        int xor_count;
        int xor_image;
        uint64_t utemp;
        int stop;

        uint64_t best;
        uint64_t a, b, c, d;

#define DT_MAX 0x9000000000000000
        uint64_t* prev_line = NULL;
        uint64_t* this_line = NULL;
        bool last_pixel = false;
        bool arbitrary_seam = false;

        Flex* seam_flex = new Flex(width, height);
        int max_queue = 0;

/***********************************************************************
* Backward distance transform
***********************************************************************/
        int n_threads = std::max(2, threadpool->GetNThreads());
        uint64_t** thread_lines = new uint64_t*[n_threads];

        if (!seamload_filename) {
                std::mutex* flex_mutex_p = new std::mutex;
                std::condition_variable* flex_cond_p = new 
std::condition_variable;

                uint8_t** thread_comp_lines = new uint8_t*[n_threads];

                for (i = 0; i < n_threads; ++i) {
                        thread_lines[i] = new uint64_t[width];
                        thread_comp_lines[i] = new uint8_t[width];
                }

// set all image masks to bottom right
                for (i = 0; i < n_images; ++i) {
                        images[i]->tiff_mask->End();
                }

                for (y = height - 1; y >= 0; --y) {
                        int t = y % n_threads;
                        this_line = thread_lines[t];
                        uint8_t* comp = thread_comp_lines[t];

// set initial image mask states
                        for (i = 0; i < n_images; ++i) {
                                images[i]->mask_state = 0x8000000000000000;
                                if (y >= images[i]->ypos && y < images[i]->ypos 
+ images[i]->height) {
                                        images[i]->mask_count = width - 
(images[i]->xpos + images[i]->width);
                                        images[i]->mask_limit = images[i]->xpos;
                                } else {
                                        images[i]->mask_count = width;
                                        images[i]->mask_limit = width;
                                }
                        }

                        x = width - 1;

                        { // make sure the last compression thread to use this 
chunk of memory is finished
                                std::unique_lock<std::mutex> 
mlock(*flex_mutex_p);
                                flex_cond_p->wait(mlock, [=] { return 
seam_flex->y > (height - 1) - y - n_threads; });
                        }

                        while (x >= 0) {
                                min_count = x + 1;
                                xor_count = 0;

// update image mask states
                                for (i = 0; i < n_images; ++i) {
                                        if (!images[i]->mask_count) {
                                                if (x >= images[i]->mask_limit) 
{
                                                        utemp = 
images[i]->tiff_mask->ReadBackwards32();
                                                        images[i]->mask_state = 
((~utemp) << 32) & 0x8000000000000000;
                                                        images[i]->mask_count = 
utemp & 0x7fffffff;
                                                } else {
                                                        images[i]->mask_state = 
0x8000000000000000;
                                                        images[i]->mask_count = 
min_count;
                                                }
                                        }

                                        if (images[i]->mask_count < min_count) 
min_count = images[i]->mask_count;
                                        if (!images[i]->mask_state) { // 
mask_state is inverted
                                                ++xor_count;
                                                xor_image = i;
                                        }
                                }

                                stop = x - min_count;

                                if (xor_count == 1) {
                                        images[xor_image]->seam_present = true;
                                        while (x > stop) this_line[x--] = 
xor_image;
                                } else {
                                        if (y == height - 1) { // bottom row
                                                if (x == width - 1) { // first 
pixel(s)
                                                        while (x > stop) 
this_line[x--] = DT_MAX; // max
                                                } else {
                                                        utemp = this_line[x + 
1];
                                                        utemp = MASKVAL(utemp);
                                                        while (x > stop) {
                                                                utemp += 
0x300000000;
                                                                this_line[x--] 
= utemp; // was min(temp, DT_MAX) but this is unlikely to happen
                                                        }
                                                }
                                        } else { // other rows
                                                if (x == width - 1) { // first 
pixel(s)
                                                        utemp = prev_line[x - 
1] + 0x400000000;
                                                        a = MASKVAL(utemp);

                                                        utemp = prev_line[x] + 
0x300000000;
                                                        b = MASKVAL(utemp);

                                                        d = a < b ? a : b;

                                                        this_line[x--] = d;

                                                        if (x == stop) {
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        
images[i]->mask_count -= min_count;
                                                                }
                                                                continue;
                                                        }

                                                        c = b + 0x100000000;
                                                        b = a - 0x100000000;
                                                        d += 0x300000000;
                                                } else {
                                                        utemp = prev_line[x] + 
0x300000000;
                                                        b = MASKVAL(utemp);

                                                        utemp = prev_line[x + 
1] + 0x400000000;
                                                        c = MASKVAL(utemp);

                                                        utemp = this_line[x + 
1] + 0x300000000;
                                                        d = MASKVAL(utemp);
                                                }

                                                if (stop == -1) {
                                                        stop = 0;
                                                        last_pixel = true;
                                                }

                                                while (x > stop) {
                                                        utemp = prev_line[x - 
1] + 0x400000000;
                                                        a = MASKVAL(utemp);

                                                        if (a < d) d = a;
                                                        if (b < d) d = b;
                                                        if (c < d) d = c;
                                                        
                                                        this_line[x--] = d;

                                                        c = b + 0x100000000;
                                                        b = a - 0x100000000;
                                                        d += 0x300000000;
                                                }

                                                if (last_pixel) {
                                                        // d is the new "best" 
to compare against
                                                        if (b < d) d = b;
                                                        if (c < d) d = c;

                                                        this_line[x--] = d;

                                                        last_pixel = false;
                                                }
                                        }
                                }

                                for (i = 0; i < n_images; ++i) {
                                        images[i]->mask_count -= min_count;
                                }
                        }

                        if (y) {
                                threadpool->Queue([=] {
                                        int p = CompressSeamLine(this_line, 
comp, width);
                                        if (p > width) {
                                                printf("bad p: %d at line %d", 
p, y);
                                                exit(0);
                                        }

                                        {
                                                std::unique_lock<std::mutex> 
mlock(*flex_mutex_p);
                                                flex_cond_p->wait(mlock, [=] { 
return seam_flex->y == (height - 1) - y; });
                                                seam_flex->Copy(comp, p);
                                                seam_flex->NextLine();
                                        }
                                        flex_cond_p->notify_all();
                                });
                        }

                        prev_line = this_line;
                } // end of row loop

                threadpool->Wait();

                for (i = 0; i < n_images; ++i) {
                        if (!images[i]->seam_present) {
                                Output(1, "Warning: %s is fully obscured by 
other images\n", images[i]->filename);
                        }
                }

                for (i = 0; i < n_threads; ++i) {
                        if (i >= 2) delete[] thread_lines[i];
                        delete[] thread_comp_lines[i];
                }

                delete[] thread_comp_lines;
                delete flex_mutex_p;
                delete flex_cond_p;
        } else { // if seamload_filename:
                for (i = 0; i < n_images; ++i) {
                        images[i]->tiff_mask->Start();
                }
        }

// create top level masks
        for (i = 0; i < n_images; ++i) {
                images[i]->masks.push_back(new Flex(width, height));
        }

        Pnger* xor_map = xor_filename ? new Pnger(xor_filename, "XOR map", 
width, height, PNG_COLOR_TYPE_PALETTE) : NULL;
        Pnger* seam_map = seamsave_filename ? new Pnger(seamsave_filename, 
"Seam map", width, height, PNG_COLOR_TYPE_PALETTE) : NULL;

/***********************************************************************
* Forward distance transform
***********************************************************************/
        int current_count = 0;
        int64 current_step;
        uint64_t dt_val;

        prev_line = thread_lines[1];

        uint64_t total_pixels = 0;
        uint64_t channel_totals[3] = { 0 };

        Flex full_mask(width, height);
        Flex xor_mask(width, height);

        bool alpha = false;

        for (y = 0; y < height; ++y) {
                for (i = 0; i < n_images; ++i) {
                        images[i]->mask_state = 0x8000000000000000;
                        if (y >= images[i]->ypos && y < images[i]->ypos + 
images[i]->height) {
                                images[i]->mask_count = images[i]->xpos;
                                images[i]->mask_limit = images[i]->xpos + 
images[i]->width;
                        } else {
                                images[i]->mask_count = width;
                                images[i]->mask_limit = width;
                        }
                }

                x = 0;
                int mc = 0;
                int prev_i = -1;
                int current_i = -1;
                int best_temp;

                while (x < width) {
                        min_count = width - x;
                        xor_count = 0;

                        for (i = 0; i < n_images; ++i) {
                                if (!images[i]->mask_count) {
                                        if (x < images[i]->mask_limit) {
                                                utemp = 
images[i]->tiff_mask->ReadForwards32();
                                                images[i]->mask_state = 
((~utemp) << 32) & 0x8000000000000000;
                                                images[i]->mask_count = utemp & 
0x7fffffff;
                                        } else {
                                                images[i]->mask_state = 
0x8000000000000000;
                                                images[i]->mask_count = 
min_count;
                                        }
                                }

                                if (images[i]->mask_count < min_count) 
min_count = images[i]->mask_count;
                                if (!images[i]->mask_state) {
                                        ++xor_count;
                                        xor_image = i;
                                }
                        }

                        stop = x + min_count;

                        if (!xor_count) {
                                alpha = true;
                        }
                        full_mask.MaskWrite(min_count, xor_count);
                        xor_mask.MaskWrite(min_count, xor_count == 1);

                        if (xor_count == 1) {
                                if (xor_map) memset(&xor_map->line[x], 
xor_image, min_count);

                                size_t p = (y - images[xor_image]->ypos) * 
images[xor_image]->width + (x - images[xor_image]->xpos);

                                int total_count = min_count;
                                total_pixels += total_count;
                                if (gamma) {
                                        switch (images[xor_image]->bpp) {
                                                case 8: {
                                                        uint16_t v;
                                                        while (total_count--) {
                                                                v = 
((uint8_t*)images[xor_image]->channels[0]->data)[p];
                                                                
channel_totals[0] += v * v;
                                                                v = 
((uint8_t*)images[xor_image]->channels[1]->data)[p];
                                                                
channel_totals[1] += v * v;
                                                                v = 
((uint8_t*)images[xor_image]->channels[2]->data)[p];
                                                                
channel_totals[2] += v * v;
                                                                ++p;
                                                        }
                                                } break;
                                                case 16: {
                                                        uint32_t v;
                                                        while (total_count--) {
                                                                v = 
((uint16_t*)images[xor_image]->channels[0]->data)[p];
                                                                
channel_totals[0] += v * v;
                                                                v = 
((uint16_t*)images[xor_image]->channels[1]->data)[p];
                                                                
channel_totals[1] += v * v;
                                                                v = 
((uint16_t*)images[xor_image]->channels[2]->data)[p];
                                                                
channel_totals[2] += v * v;
                                                                ++p;
                                                        }
                                                } break;
                                        }
                                } else {
                                        switch (images[xor_image]->bpp) {
                                                case 8: {
                                                        while (total_count--) {
                                                                
channel_totals[0] += ((uint8_t*)images[xor_image]->channels[0]->data)[p];
                                                                
channel_totals[1] += ((uint8_t*)images[xor_image]->channels[1]->data)[p];
                                                                
channel_totals[2] += ((uint8_t*)images[xor_image]->channels[2]->data)[p];
                                                                ++p;
                                                        }
                                                } break;
                                                case 16: {
                                                        while (total_count--) {
                                                                
channel_totals[0] += ((uint16_t*)images[xor_image]->channels[0]->data)[p];
                                                                
channel_totals[1] += ((uint16_t*)images[xor_image]->channels[1]->data)[p];
                                                                
channel_totals[2] += ((uint16_t*)images[xor_image]->channels[2]->data)[p];
                                                                ++p;
                                                        }
                                                } break;
                                        }
                                }

                                if (!seamload_filename) {
                                        RECORD(xor_image, min_count);
                                        while (x < stop) {
                                                this_line[x++] = xor_image;
                                        }
                                } else {
                                        x = stop;
                                }

                                best = xor_image;
                        } else {
                                if (xor_map) memset(&xor_map->line[x], 0xff, 
min_count);

                                if (!seamload_filename) {
                                        if (y == 0) {
                                                // top row
                                                while (x < stop) {
                                                        best = this_line[x];

                                                        if (x > 0) {
                                                                utemp = 
this_line[x - 1] + 0x300000000;
                                                                d = 
MASKVAL(utemp);

                                                                if (d < best) 
best = d;
                                                        }

                                                        if (best & 
0x8000000000000000 && xor_count) {
                                                                arbitrary_seam 
= true;
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        if 
(!images[i]->mask_state) {
                                                                                
best = 0x8000000000000000 | i;
                                                                                
if (!reverse) break;
                                                                        }
                                                                }
                                                        }

                                                        best_temp = best & 
0xffffffff;
                                                        RECORD(best_temp, 1);
                                                        this_line[x++] = best;
                                                }
                                        } else {
                                                // other rows
                                                if (x == 0) {
                                                        SEAM_DT;
                                                        best = dt_val;

                                                        utemp = *prev_line + 
0x300000000;
                                                        b = MASKVAL(utemp);
                                                        if (b < best) best = b;

                                                        utemp = prev_line[1] + 
0x400000000;
                                                        c = MASKVAL(utemp);
                                                        if (c < best) best = c;

                                                        if (best & 
0x8000000000000000 && xor_count) {
                                                                arbitrary_seam 
= true;
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        if 
(!images[i]->mask_state) {
                                                                                
best = 0x8000000000000000 | i;
                                                                                
if (!reverse) break;
                                                                        }
                                                                }
                                                        }

                                                        best_temp = best & 
0xffffffff;
                                                        RECORD(best_temp, 1);
                                                        this_line[x++] = best;

                                                        if (x == stop) {
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        
images[i]->mask_count -= min_count;
                                                                }
                                                                continue;
                                                        }

                                                        a = b + 0x100000000;
                                                        b = c - 0x100000000;
                                                } else {
                                                        utemp = prev_line[x - 
1] + 0x400000000;
                                                        a = MASKVAL(utemp);

                                                        utemp = prev_line[x] + 
0x300000000;
                                                        b = MASKVAL(utemp);
                                                }

                                                utemp = best + 0x300000000;
                                                d = MASKVAL(utemp);

                                                if (stop == width) {
                                                        stop--;
                                                        last_pixel = true;
                                                }

                                                while (x < stop) {
                                                        utemp = prev_line[x + 
1] + 0x400000000;
                                                        c = MASKVAL(utemp);

                                                        SEAM_DT;
                                                        best = dt_val;

                                                        if (a < best) best = a;
                                                        if (b < best) best = b;
                                                        if (c < best) best = c;
                                                        if (d < best) best = d;

                                                        if (best & 
0x8000000000000000 && xor_count) {
                                                                arbitrary_seam 
= true;
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        if 
(!images[i]->mask_state) {
                                                                                
best = 0x8000000000000000 | i;
                                                                                
if (!reverse) break;
                                                                        }
                                                                }
                                                        }

                                                        best_temp = best & 
0xffffffff;
                                                        RECORD(best_temp, 1);
                                                        this_line[x++] = best; 
// best;

                                                        a = b + 0x100000000;
                                                        b = c - 0x100000000;
                                                        d = best + 0x300000000;
                                                }

                                                if (last_pixel) {
                                                        SEAM_DT;
                                                        best = dt_val;

                                                        if (a < best) best = a;
                                                        if (b < best) best = b;
                                                        if (d < best) best = d;

                                                        if (best & 
0x8000000000000000 && xor_count) {
                                                                arbitrary_seam 
= true;
                                                                for (i = 0; i < 
n_images; ++i) {
                                                                        if 
(!images[i]->mask_state) {
                                                                                
best = 0x8000000000000000 | i;
                                                                                
if (!reverse) break;
                                                                        }
                                                                }
                                                        }

                                                        best_temp = best & 
0xffffffff;
                                                        RECORD(best_temp, 1);
                                                        this_line[x++] = best; 
// best;

                                                        last_pixel = false;
                                                }
                                        }
                                } else { // if (seamload_filename)...
                                        x = stop;
                                }
                        }

                        for (i = 0; i < n_images; ++i) {
                                images[i]->mask_count -= min_count;
                        }
                }
                        
                if (!seamload_filename) {
                        RECORD(-1, 0);

                        for (i = 0; i < n_images; ++i) {
                                images[i]->masks[0]->NextLine();
                        }
                }

                full_mask.NextLine();
                xor_mask.NextLine();

                if (xor_map) xor_map->Write();
                if (seam_map) seam_map->Write();

                std::swap(this_line, prev_line);
        }

        if (!seamload_filename) {
                delete[] thread_lines[0];
                delete[] thread_lines[1];
                delete[] thread_lines;
        }

        delete xor_map;
        delete seam_map;

        if (!alpha || output_type == ImageType::MB_JPEG) no_mask = true;

/***********************************************************************
* Seam load
***********************************************************************/
        if (seamload_filename) {
                int png_depth, png_colour;
                png_uint_32 png_width, png_height;
                uint8_t sig[8];
                png_structp png_ptr;
                png_infop info_ptr;
                FILE* f;

                fopen_s(&f, seamload_filename, "rb");
                if (!f) die("Error: Couldn't open seam file");

                size_t r = fread(sig, 1, 8, f); // assignment suppresses g++ 
-Ofast warning
                if (!png_check_sig(sig, 8)) die("Error: Bad PNG signature");

                png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, 
NULL, NULL);
                if (!png_ptr) die("Error: Seam PNG problem");
                info_ptr = png_create_info_struct(png_ptr);
                if (!info_ptr) die("Error: Seam PNG problem");

                png_init_io(png_ptr, f);
                png_set_sig_bytes(png_ptr, 8);
                png_read_info(png_ptr, info_ptr);
                png_get_IHDR(png_ptr, info_ptr, &png_width, &png_height, 
&png_depth, &png_colour, NULL, NULL, NULL);

                if (png_width != width || png_height != png_height) die("Error: 
Seam PNG dimensions don't match workspace");
                if (png_depth != 8 || png_colour != PNG_COLOR_TYPE_PALETTE) 
die("Error: Incorrect seam PNG format");

                png_bytep png_line = (png_bytep)malloc(width);

                for (y = 0; y < height; ++y) {
                        png_read_row(png_ptr, png_line, NULL);

                        int ms = 0;
                        int mc = 0;
                        int prev_i = -1;
                        int current_i = -1;

                        for (x = 0; x < width; ++x) {
                                if (png_line[x] > n_images) die("Error: Bad 
pixel found in seam file: %d,%d", x, y);
                                RECORD(png_line[x], 1);
                        }

                        RECORD(-1, 0);

                        for (i = 0; i < n_images; ++i) {
                                images[i]->masks[0]->NextLine();
                        }

                }

                free(png_line);
        }

        seam_time = timer.Read();

/***********************************************************************
* No output?
***********************************************************************/
        void* output_channels[3] = { NULL, NULL, NULL };

        if (output_type != ImageType::MB_NONE) {

/***********************************************************************
* Shrink masks
***********************************************************************/
                Output(1, "Shrinking masks...\n");

                timer.Start();

                for (i = 0; i < n_images; ++i) {
                        threadpool->Queue([=] {
                                ShrinkMasks(images[i]->masks, blend_levels);
                                });
                }
                threadpool->Wait();

                shrink_mask_time = timer.Read();

/***********************************************************************
* Create shared input pyramids
***********************************************************************/
// wrapping
                std::vector<PyramidWithMasks*> wrap_pyramids;
                int wrap_levels_h = 0;
                int wrap_levels_v = 0;

                if (wrap & 1) {
                        wrap_levels_h = (int)floor(log2((width >> 1) + 4.0f) - 
1);
                        wrap_pyramids.push_back(new PyramidWithMasks(width >> 
1, height, wrap_levels_h, 0, 0, true));
                        wrap_pyramids.push_back(new PyramidWithMasks((width + 
1) >> 1, height, wrap_levels_h, width >> 1, 0, true));
                }

                if (wrap & 2) {
                        wrap_levels_v = (int)floor(log2((height >> 1) + 4.0f) - 
1);
                        wrap_pyramids.push_back(new PyramidWithMasks(width, 
height >> 1, wrap_levels_v, 0, 0, true));
                        wrap_pyramids.push_back(new PyramidWithMasks(width, 
(height + 1) >> 1, wrap_levels_v, 0, height >> 1, true));
                }

// masks
                for (auto& py : wrap_pyramids) {
                        threadpool->Queue([=] {
                                py->masks.push_back(new Flex(width, height));
                                for (int y = 0; y < height; ++y) {
                                        if (y < py->GetY() || y >= py->GetY() + 
py->GetHeight()) {
                                                
py->masks[0]->Write32(0x80000000 | width);
                                        } else {
                                                if (py->GetX()) {
                                                        
py->masks[0]->Write32(0x80000000 | py->GetX());
                                                        
py->masks[0]->Write32(0xc0000000 | py->GetWidth());
                                                } else {
                                                        
py->masks[0]->Write32(0xc0000000 | py->GetWidth());
                                                        if (py->GetWidth() != 
width) py->masks[0]->Write32(0x80000000 | (width - py->GetWidth()));
                                                }
                                        }
                                        py->masks[0]->NextLine();
                                }

                                ShrinkMasks(py->masks, py->GetWidth() == width 
? wrap_levels_v : wrap_levels_h);
                                });
                }

                threadpool->Wait();
// end wrapping

                int total_levels = std::max({ blend_levels, wrap_levels_h, 
wrap_levels_v, 1 });

                for (int i = 0; i < n_images; ++i) {
                        images[i]->pyramid = new Pyramid(images[i]->width, 
images[i]->height, blend_levels, images[i]->xpos, images[i]->ypos, true);
                }

                for (int l = total_levels - 1; l >= 0; --l) {
                        size_t max_bytes = 0;

                        if (l < blend_levels) {
                                for (auto& image : images) {
                                        max_bytes = std::max(max_bytes, 
image->pyramid->GetLevel(l).bytes);
                                }
                        }

                        for (auto& py : wrap_pyramids) {
                                if (l < py->GetNLevels()) max_bytes = 
std::max(max_bytes, py->GetLevel(l).bytes);
                        }

                        float* temp;

                        try {
                                temp = (float*)MapAlloc::Alloc(max_bytes);
                        } catch (char* e) {
                                printf("%s\n", e);
                                exit(EXIT_FAILURE);
                        }

                        if (l < blend_levels) {
                                for (auto& image : images) {
                                        image->pyramid->GetLevel(l).data = temp;
                                }
                        }

                        for (auto& py : wrap_pyramids) {
                                if (l < py->GetNLevels()) py->GetLevel(l).data 
= temp;
                        }
                }

/***********************************************************************
* Create output pyramid
***********************************************************************/
                Pyramid* output_pyramid = NULL;

                output_pyramid = new Pyramid(width, height, total_levels, 0, 0, 
true);

                for (int l = total_levels - 1; l >= 0; --l) {
                        float* temp;

                        try {
                                temp = 
(float*)MapAlloc::Alloc(output_pyramid->GetLevel(l).bytes);
                        } catch (char* e) {
                                printf("%s\n", e);
                                exit(EXIT_FAILURE);
                        }

                        output_pyramid->GetLevel(l).data = temp;
                }

/***********************************************************************
* Blend
***********************************************************************/
                if (n_images == 1) {
                        if (wrap) Output(1, "Wrapping...\n"); else Output(1, 
"Processing...\n");
                } else {
                        if (wrap) Output(1, "Blending/wrapping...\n"); else 
Output(1, "Blending...\n");
                }

                for (int c = 0; c < 3; ++c) {
                        if (n_images > 1) {
                                for (i = 0; i < n_images; ++i) {
                                        timer.Start();

                                        
images[i]->pyramid->Copy((uint8_t*)images[i]->channels[c]->data, 1, 
images[i]->width, gamma, images[i]->bpp);
                                        if (output_bpp != images[i]->bpp) 
images[i]->pyramid->Multiply(0, gamma ? (output_bpp == 8 ? 1.0f / 66049 : 
66049) : (output_bpp == 8 ? 1.0f / 257 : 257));

                                        delete images[i]->channels[c];
                                        images[i]->channels[c] = NULL;

                                        copy_time += timer.Read();

                                        timer.Start();
                                        images[i]->pyramid->Shrink();
                                        shrink_time += timer.Read();

                                        timer.Start();
                                        images[i]->pyramid->Laplace();
                                        laplace_time += timer.Read();

                // blend into output pyramid...

                                        timer.Start();

                                        for (int l = 0; l < blend_levels; ++l) {
                                                auto in_level = 
images[i]->pyramid->GetLevel(l);
                                                auto out_level = 
output_pyramid->GetLevel(l);

                                                int x_offset = (in_level.x - 
out_level.x) >> l;
                                                int y_offset = (in_level.y - 
out_level.y) >> l;

                                                for (int b = 0; b < 
(int)out_level.bands.size() - 1; ++b) {
                                                        int sy = 
out_level.bands[b];
                                                        int ey = 
out_level.bands[b + 1];

                                                        threadpool->Queue([=] {
                                                                for (int y = 
sy; y < ey; ++y) {
                                                                        int 
in_line = y - y_offset;
                                                                        if 
(in_line < 0) in_line = 0; else if (in_line > in_level.height - 1) in_line = 
in_level.height - 1;
                                                                        float* 
input_p = in_level.data + (size_t)in_line * in_level.pitch;
                                                                        float* 
output_p = out_level.data + (size_t)y * out_level.pitch;

                                                                        
CompositeLine(input_p, output_p, i, x_offset, in_level.width, out_level.width, 
out_level.pitch, images[i]->masks[l]->data, images[i]->masks[l]->rows[y]);
                                                                }
                                                                });
                                                }

                                                threadpool->Wait();
                                        }

                                        blend_time += timer.Read();
                                }

                                timer.Start();
                                output_pyramid->Collapse(blend_levels);
                                collapse_time += timer.Read();
                        } else {
                                timer.Start();

                                
output_pyramid->Copy((uint8_t*)images[0]->channels[c]->data, 1, 
images[0]->width, gamma, images[0]->bpp);
                                if (output_bpp != images[0]->bpp) 
output_pyramid->Multiply(0, gamma ? (output_bpp == 8 ? 1.0f / 66049 : 66049) : 
(output_bpp == 8 ? 1.0f / 257 : 257));

                                delete images[0]->channels[c];
                                images[0]->channels[c] = NULL;

                                copy_time += timer.Read();
                        }

/***********************************************************************
* Wrapping
***********************************************************************/
                        if (wrap) {
                                timer.Start();

                                int p = 0;

                                for (int w = 1; w <= 2; ++w) {
                                        if (wrap & w) {

                                                if (w == 1) {
                                                        SwapH(output_pyramid);
                                                } else {
                                                        SwapV(output_pyramid);
                                                }

                                                int wrap_levels = (w == 1) ? 
wrap_levels_h : wrap_levels_v;
                                                for (int wp = 0; wp < 2; ++wp) {
                                                        
wrap_pyramids[p]->Copy((uint8_t*)(output_pyramid->GetData() + 
wrap_pyramids[p]->GetX() + wrap_pyramids[p]->GetY() * 
(int64)output_pyramid->GetPitch()), 1, output_pyramid->GetPitch(), false, 32);
                                                        
wrap_pyramids[p]->Shrink();
                                                        
wrap_pyramids[p]->Laplace();

                                                        for (int l = 0; l < 
wrap_levels; ++l) {
                                                                auto in_level = 
wrap_pyramids[p]->GetLevel(l);
                                                                auto out_level 
= output_pyramid->GetLevel(l);

                                                                int x_offset = 
(in_level.x - out_level.x) >> l;
                                                                int y_offset = 
(in_level.y - out_level.y) >> l;

                                                                for (int b = 0; 
b < (int)out_level.bands.size() - 1; ++b) {
                                                                        int sy 
= out_level.bands[b];
                                                                        int ey 
= out_level.bands[b + 1];

                                                                        
threadpool->Queue([=] {
                                                                                
for (int y = sy; y < ey; ++y) {
                                                                                
        int in_line = y - y_offset;
                                                                                
        if (in_line < 0) in_line = 0; else if (in_line > in_level.height - 1) 
in_line = in_level.height - 1;
                                                                                
        float* input_p = in_level.data + (size_t)in_line * in_level.pitch;
                                                                                
        float* output_p = out_level.data + (size_t)y * out_level.pitch;

                                                                                
        CompositeLine(input_p, output_p, wp + (l == 0), x_offset, 
in_level.width, out_level.width, out_level.pitch, 
wrap_pyramids[p]->masks[l]->data, wrap_pyramids[p]->masks[l]->rows[y]);
                                                                                
}
                                                                                
});
                                                                }

                                                                
threadpool->Wait();
                                                        }
                                                        ++p;
                                                }

                                                
output_pyramid->Collapse(wrap_levels);

                                                if (w == 1) {
                                                        UnswapH(output_pyramid);
                                                } else {
                                                        UnswapV(output_pyramid);
                                                }
                                        } // if (wrap & w)
                                } // w loop

                                wrap_time += timer.Read();
                        } // if (wrap)
// end wrapping

/***********************************************************************
* Offset correction
***********************************************************************/
                        if (total_pixels) {
                                double channel_total = 0; // must be a double
                                float* data = output_pyramid->GetData();
                                xor_mask.Start();

                                for (y = 0; y < height; ++y) {
                                        x = 0;
                                        while (x < width) {
                                                uint32_t v = 
xor_mask.ReadForwards32();
                                                if (v & 0x80000000) {
                                                        v = x + v & 0x7fffffff;
                                                        while (x < (int)v) {
                                                                channel_total 
+= data[x++];
                                                        }
                                                } else {
                                                        x += v;
                                                }
                                        }

                                        data += output_pyramid->GetPitch();
                                }

                                float avg = (float)channel_totals[c] / 
total_pixels;
                                if (output_bpp != images[0]->bpp) {
                                        switch (output_bpp) {
                                                case 8: avg /= 256; break;
                                                case 16: avg *= 256; break;
                                        }
                                }
                                float output_avg = (float)channel_total / 
total_pixels;
                                output_pyramid->Add(avg - output_avg, 1);
                        }

/***********************************************************************
* Output
***********************************************************************/
                        timer.Start();

                        try {
                                output_channels[c] = 
MapAlloc::Alloc(((int64)width * height) << (output_bpp >> 4));
                        } catch (char* e) {
                                printf("%s\n", e);
                                exit(EXIT_FAILURE);
                        }

                        switch (output_bpp) {
                                case 8: 
output_pyramid->Out((uint8_t*)output_channels[c], width, gamma, dither, true); 
break;
                                case 16: 
output_pyramid->Out((uint16_t*)output_channels[c], width, gamma, dither, true); 
break;
                        }

                        out_time += timer.Read();
                }

/***********************************************************************
* Write
***********************************************************************/
#define ROWS_PER_STRIP 64

                Output(1, "Writing %s...\n", output_filename);

                timer.Start();

                struct jpeg_compress_struct cinfo;
                struct jpeg_error_mgr jerr;

                JSAMPARRAY scanlines = NULL;

                int spp = no_mask ? 3 : 4;

                int bytes_per_pixel = spp << (output_bpp >> 4);
                int bytes_per_row = bytes_per_pixel * width;

                int n_strips = (int)((height + ROWS_PER_STRIP - 1) / 
ROWS_PER_STRIP);
                int remaining = height;
                void* strip = malloc((ROWS_PER_STRIP * (int64)width) * 
bytes_per_pixel);
                void* oc_p[3] = { output_channels[0], output_channels[1], 
output_channels[2] };
                if (bgr) std::swap(oc_p[0], oc_p[2]);

                switch (output_type) {
                        case ImageType::MB_TIFF: {
                                TIFFSetField(tiff_file, TIFFTAG_IMAGEWIDTH, 
width);
                                TIFFSetField(tiff_file, TIFFTAG_IMAGELENGTH, 
height);
                                TIFFSetField(tiff_file, TIFFTAG_COMPRESSION, 
compression);
                                TIFFSetField(tiff_file, TIFFTAG_PLANARCONFIG, 
PLANARCONFIG_CONTIG);
                                TIFFSetField(tiff_file, TIFFTAG_ROWSPERSTRIP, 
ROWS_PER_STRIP);
                                TIFFSetField(tiff_file, TIFFTAG_BITSPERSAMPLE, 
output_bpp);
                                if (no_mask) {
                                        TIFFSetField(tiff_file, 
TIFFTAG_SAMPLESPERPIXEL, 3);
                                } else {
                                        TIFFSetField(tiff_file, 
TIFFTAG_SAMPLESPERPIXEL, 4);
                                        uint16_t out[1] = { 
EXTRASAMPLE_UNASSALPHA };
                                        TIFFSetField(tiff_file, 
TIFFTAG_EXTRASAMPLES, 1, &out);
                                }

                                TIFFSetField(tiff_file, TIFFTAG_PHOTOMETRIC, 
PHOTOMETRIC_RGB);
                                if (images[0]->tiff_xres != -1) { 
TIFFSetField(tiff_file, TIFFTAG_XRESOLUTION, images[0]->tiff_xres); 
TIFFSetField(tiff_file, TIFFTAG_XPOSITION, (float)(min_xpos / 
images[0]->tiff_xres)); }
                                if (images[0]->tiff_yres != -1) { 
TIFFSetField(tiff_file, TIFFTAG_YRESOLUTION, images[0]->tiff_yres); 
TIFFSetField(tiff_file, TIFFTAG_YPOSITION, (float)(min_ypos / 
images[0]->tiff_yres)); }

                                if (images[0]->geotiff.set) {
                                        // if we got a georeferenced input, 
store the geotags in the output
                                        GeoTIFFInfo info(images[0]->geotiff);
                                        info.XGeoRef = min_xpos * 
images[0]->geotiff.XCellRes;
                                        info.YGeoRef = -min_ypos * 
images[0]->geotiff.YCellRes;
                                        Output(1, "Output georef: UL: %f %f, 
pixel size: %f %f\n", info.XGeoRef, info.YGeoRef, info.XCellRes, info.YCellRes);
                                        geotiff_write(tiff_file, &info);
                                }
                        } break;
                        case ImageType::MB_JPEG: {
                                cinfo.err = jpeg_std_error(&jerr);
                                jpeg_create_compress(&cinfo);
                                jpeg_stdio_dest(&cinfo, jpeg_file);

                                cinfo.image_width = width;
                                cinfo.image_height = height;
                                cinfo.input_components = 3;
                                cinfo.in_color_space = JCS_RGB;

                                jpeg_set_defaults(&cinfo);
                                jpeg_set_quality(&cinfo, jpeg_quality, true);
                                jpeg_start_compress(&cinfo, true);
                        } break;
                        case ImageType::MB_PNG: {
                                png_file = new Pnger(output_filename, NULL, 
width, height, no_mask ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA, 
output_bpp, jpeg_file, jpeg_quality);
                        } break;
                }

                if (output_type == ImageType::MB_PNG || output_type == 
ImageType::MB_JPEG) {
                        scanlines = new JSAMPROW[ROWS_PER_STRIP];
                        for (i = 0; i < ROWS_PER_STRIP; ++i) {
                                scanlines[i] = (JSAMPROW) & ((uint8_t*)strip)[i 
* bytes_per_row];
                        }
                }

                full_mask.Start();

                for (int s = 0; s < n_strips; ++s) {
                        int strip_p = 0;
                        int rows = std::min(remaining, ROWS_PER_STRIP);

                        for (int strip_y = 0; strip_y < rows; ++strip_y) {
                                x = 0;
                                while (x < width) {
                                        uint32_t cur = 
full_mask.ReadForwards32();
                                        if (cur & 0x80000000) {
                                                int lim = x + (cur & 
0x7fffffff);
                                                switch (output_bpp) {
                                                        case 8: {
                                                                while (x < lim) 
{
                                                                        
((uint8_t*)strip)[strip_p++] = ((uint8_t*)(oc_p[0]))[x];
                                                                        
((uint8_t*)strip)[strip_p++] = ((uint8_t*)(oc_p[1]))[x];
                                                                        
((uint8_t*)strip)[strip_p++] = ((uint8_t*)(oc_p[2]))[x];
                                                                        if 
(!no_mask) ((uint8_t*)strip)[strip_p++] = 0xff;
                                                                        ++x;
                                                                }
                                                        } break;
                                                        case 16: {
                                                                while (x < lim) 
{
                                                                        
((uint16_t*)strip)[strip_p++] = ((uint16_t*)(oc_p[0]))[x];
                                                                        
((uint16_t*)strip)[strip_p++] = ((uint16_t*)(oc_p[1]))[x];
                                                                        
((uint16_t*)strip)[strip_p++] = ((uint16_t*)(oc_p[2]))[x];
                                                                        if 
(!no_mask) ((uint16_t*)strip)[strip_p++] = 0xffff;
                                                                        ++x;
                                                                }
                                                        } break;
                                                }
                                        } else {
                                                size_t t = (size_t)cur * 
bytes_per_pixel;
                                                switch (output_bpp) {
                                                        case 8: {
                                                                
ZeroMemory(&((uint8_t*)strip)[strip_p], t);
                                                        } break;
                                                        case 16: {
                                                                
ZeroMemory(&((uint16_t*)strip)[strip_p], t);
                                                        } break;
                                                }
                                                strip_p += cur * spp;
                                                x += cur;
                                        }
                                }

                                switch (output_bpp) {
                                        case 8: {
                                                oc_p[0] = 
&((uint8_t*)(oc_p[0]))[width];
                                                oc_p[1] = 
&((uint8_t*)(oc_p[1]))[width];
                                                oc_p[2] = 
&((uint8_t*)(oc_p[2]))[width];
                                        } break;
                                        case 16: {
                                                oc_p[0] = 
&((uint16_t*)(oc_p[0]))[width];
                                                oc_p[1] = 
&((uint16_t*)(oc_p[1]))[width];
                                                oc_p[2] = 
&((uint16_t*)(oc_p[2]))[width];
                                        } break;
                                }
                        }

                        switch (output_type) {
                                case ImageType::MB_TIFF: {
                                        TIFFWriteEncodedStrip(tiff_file, s, 
strip, rows * (int64)bytes_per_row);
                                } break;
                                case ImageType::MB_JPEG: {
                                        jpeg_write_scanlines(&cinfo, scanlines, 
rows);
                                } break;
                                case ImageType::MB_PNG: {
                                        png_file->WriteRows(scanlines, rows);
                                } break;
                        }

                        remaining -= ROWS_PER_STRIP;
                }

                switch (output_type) {
                        case ImageType::MB_TIFF: {
                                TIFFClose(tiff_file);
                        } break;
                        case ImageType::MB_JPEG: {
                                jpeg_finish_compress(&cinfo);
                                jpeg_destroy_compress(&cinfo);
                                fclose(jpeg_file);
                        } break;
                }

                write_time = timer.Read();
        }

/***********************************************************************
* Timing
***********************************************************************/
        if (timing) {
                printf("\n");
                printf("Images:   %.3fs\n", images_time);
                printf("Seaming:  %.3fs\n", seam_time);
                if (output_type != ImageType::MB_NONE) {
                        printf("Masks:    %.3fs\n", shrink_mask_time);
                        printf("Copy:     %.3fs\n", copy_time);
                        printf("Shrink:   %.3fs\n", shrink_time);
                        printf("Laplace:  %.3fs\n", laplace_time);
                        printf("Blend:    %.3fs\n", blend_time);
                        printf("Collapse: %.3fs\n", collapse_time);
                        if (wrap) printf("Wrapping: %.3fs\n", wrap_time);
                        printf("Output:   %.3fs\n", out_time);
                        printf("Write:    %.3fs\n", write_time);
                }
        }

/***********************************************************************
* Clean up
***********************************************************************/
        if (timing) {
                if (output_type == ImageType::MB_NONE) {
                        timer_all.Report("\nExecution complete. Total execution 
time");
                } else {
                        timer_all.Report("\nBlend complete. Total execution 
time");
                }
        }

        if (argbuffermem) {
                free(argbuffermem);
        }

        return EXIT_SUCCESS;
}

Reply via email to