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
[email protected] 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 [email protected] 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 [email protected].
>
> 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 [email protected].
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 [email protected]
*/
#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;
}