/*
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 .
The author can be contacted at davidhorman47@gmail.com
*/
#define NOMINMAX
#include
#include
#include
#include
#ifdef __APPLE__
#define memalign(a,b) malloc((b))
#else
#include
#endif
#include "tiffio.h"
#include "jpeglib.h"
#ifndef _WIN32
#include
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 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 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 Specify temporary directory (default: system temp)\n");
printf(" --save-seams Save seams to PNG file for external editing\n");
printf(" --load-seams 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 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 widths;
std::vector 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 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 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 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;
}