#include "main.hpp"
#define SHIFT(n) do { argc -= n; argv += n; } while (0)
static void usage(const char* name=NULL) {
LOG("Usage: %s [--dim=...] [--tint=...] [--fulltint=...] [--dist=avg|euclid|lab] input.jpg output.jpg sample_1.jpg... ", name ?: "");
LOG("Example: %s ~/Pictures/foo/input.jpg mosaic.jpg ~/Pictures/foo/*.jpg", name ?: "");
exit(1);
}
int main(int argc, const char** argv) {
SHIFT(1);
int args = parse_args(argc, argv);
if (args < 0) usage();
SHIFT(args);
if (!argc) usage();
Image* in = JpegImage::in(argv[0]);
if (!in) {
return 1;
}
SHIFT(1);
if (!argc) usage();
const char* outfn = argv[0];
SHIFT(1);
Mosaics mosaics;
while (argc) {
Image* im = JpegImage::in(argv[0]);
if (im) {
if (!mosaics.add(im)) {
LOG("Ignoring '%s'", argv[0]);
}
delete im;
}
SHIFT(1);
}
if (!mosaics.size()) {
LOG("No input files");
return 1;
}
if (in->buf.w < config.mosaic_dim || in->buf.h < config.mosaic_dim) {
LOG("Input file too small");
return 1;
}
Image* out = new Image((in->buf.w/config.mosaic_dim)*config.mosaic_dim, (in->buf.h/config.mosaic_dim)*config.mosaic_dim);
for (unsigned boxx=0; boxx<in->buf.w/config.mosaic_dim; ++boxx) {
LOG("Matching %u/%u ...", boxx, in->buf.w/config.mosaic_dim);
for (unsigned boxy=0; boxy<in->buf.h/config.mosaic_dim; ++boxy) {
#if 1 // pixel-wise distances, slow
Image* in_box = in->boxed(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim);
Image* in_box_half[] = {
in->boxed((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
in->boxed((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
in->boxed((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half),
in->boxed((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half)
};
double dist_full;
const Image* best_full = mosaics.find_best(in_box, dist_full);
double dist_half[4];
const Image* best_half[] = {
mosaics.find_best(in_box_half[0], dist_half[0]),
mosaics.find_best(in_box_half[1], dist_half[1]),
mosaics.find_best(in_box_half[2], dist_half[2]),
mosaics.find_best(in_box_half[3], dist_half[3]),
};
unsigned dist_half_avg = (dist_half[0] + dist_half[1] + dist_half[2] + dist_half[3]) / 4.0;
rgb_t avg_full = in_box->med();
rgb_t avg_half[] = {in_box_half[0]->med(), in_box_half[1]->med(), in_box_half[2]->med(), in_box_half[3]->med()};
#else // average/median based distances, fast
rgb_t avg_full = in->med(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim);
rgb_t avg_half[] = {
in->med((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
in->med((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half),
in->med((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half),
in->med((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half)
};
unsigned char dist_full;
const Image* best_full = mosaics.find_best(config.mosaic_dim, avg_full, dist_full);
unsigned char dist_half[4];
const Image* best_half[] = {
mosaics.find_best(config.mosaic_dim_half, avg_half[0], dist_half[0]),
mosaics.find_best(config.mosaic_dim_half, avg_half[1], dist_half[1]),
mosaics.find_best(config.mosaic_dim_half, avg_half[2], dist_half[2]),
mosaics.find_best(config.mosaic_dim_half, avg_half[3], dist_half[3])
};
unsigned dist_half_avg = ((unsigned)dist_half[0] + (unsigned)dist_half[1] + (unsigned)dist_half[2] + (unsigned)dist_half[3]) / 4;
#endif
if (dist_half_avg < dist_full && !(best_half[0] == best_half[1] && best_half[0] == best_half[2] && best_half[0] == best_half[3])) {
out->put((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), best_half[0]);
out->put((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), best_half[1]);
out->put((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, best_half[2]);
out->put((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, best_half[3]);
if (config.tint) {
out->tint((boxx*config.mosaic_dim), (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half, avg_half[0], config.tint);
out->tint((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim), config.mosaic_dim_half, config.mosaic_dim_half, avg_half[1], config.tint);
out->tint((boxx*config.mosaic_dim), (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half, avg_half[2], config.tint);
out->tint((boxx*config.mosaic_dim)+config.mosaic_dim_half, (boxy*config.mosaic_dim)+config.mosaic_dim_half, config.mosaic_dim_half, config.mosaic_dim_half, avg_half[3], config.tint);
}
} else {
out->put(boxx*config.mosaic_dim, boxy*config.mosaic_dim, best_full);
if (config.tint) {
out->tint(boxx*config.mosaic_dim, boxy*config.mosaic_dim, config.mosaic_dim, config.mosaic_dim, avg_full, config.tint);
}
}
#if 1
delete in_box;
delete in_box_half[0];
delete in_box_half[1];
delete in_box_half[2];
delete in_box_half[3];
#endif
}
}
if (config.full_tint) {
out->tint(in, config.full_tint);
}
JpegImage::out(outfn, out);
delete in;
delete out;
return 0;
}