648 lines
15 KiB
C++
648 lines
15 KiB
C++
// Copyright 2002 David Hilvert <dhilvert@auricle.dyndns.org>,
|
|
// <dhilvert@ugcs.caltech.edu>
|
|
|
|
/* This file is part of the Anti-Lamenessing Engine.
|
|
|
|
The Anti-Lamenessing Engine 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.
|
|
|
|
The Anti-Lamenessing Engine 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 the Anti-Lamenessing Engine; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/*
|
|
* image_rw.h: Read and write images.
|
|
*/
|
|
|
|
|
|
#ifndef __image_rw_h__
|
|
#define __image_rw_h__
|
|
|
|
#include "image.h"
|
|
#include "image_ale_real.h"
|
|
#include "image_bayer_ale_real.h"
|
|
#include "ppm.h"
|
|
#include "exposure/exposure.h"
|
|
#include "exposure/exposure_default.h"
|
|
|
|
class image_rw {
|
|
|
|
/*
|
|
* Private data members
|
|
*/
|
|
|
|
/*
|
|
* PPM type
|
|
*
|
|
* 0 = No type selected
|
|
* 1 = PPM Raw
|
|
* 2 = PPM Plain
|
|
*/
|
|
static int ppm_type;
|
|
|
|
/*
|
|
* Bit depth
|
|
*/
|
|
static unsigned int mcv;
|
|
|
|
/*
|
|
* Nearest-neighbor defined value radius.
|
|
*/
|
|
static double nn_defined_radius;
|
|
|
|
/*
|
|
* Input and output exposure models
|
|
*/
|
|
static exposure **input_exposure;
|
|
static exposure *output_exposure;
|
|
static int exposure_scale;
|
|
|
|
/*
|
|
* Default bayer pattern
|
|
*/
|
|
static unsigned int bayer_default;
|
|
|
|
/*
|
|
* Image-specific bayer patterns.
|
|
*/
|
|
static unsigned int *bayer_specific;
|
|
|
|
/*
|
|
* Pointer to the output filename
|
|
*/
|
|
static const char *output_filename;
|
|
|
|
/*
|
|
* Variables relating to input image files and image data structures.
|
|
*/
|
|
static const char **filenames;
|
|
static unsigned int file_count;
|
|
static const image **images;
|
|
static int *files_open;
|
|
|
|
/*
|
|
* The most recently closed image number.
|
|
*/
|
|
static int latest_close_num;
|
|
|
|
/*
|
|
* Maximum cache size, in megabytes (2^20 * bytes), for images not most
|
|
* recently closed.
|
|
*/
|
|
static double cache_size_max;
|
|
|
|
/*
|
|
* Actual cache size.
|
|
*/
|
|
static double cache_size;
|
|
|
|
/*
|
|
* Number of cached files.
|
|
*/
|
|
static unsigned int cache_count;
|
|
|
|
/*
|
|
* Private methods to init and shut down the file reader.
|
|
*/
|
|
|
|
/*
|
|
* Initialize the image file handler
|
|
*/
|
|
static void init_image() {
|
|
#ifdef USE_MAGICK
|
|
InitializeMagick("ale");
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Destroy the image file handler
|
|
*/
|
|
static void destroy_image() {
|
|
#ifdef USE_MAGICK
|
|
DestroyMagick();
|
|
#endif
|
|
}
|
|
|
|
public:
|
|
|
|
/*
|
|
* Methods to read and write image files
|
|
*/
|
|
|
|
/*
|
|
* Read an image from a file
|
|
*/
|
|
static image *read_image(const char *filename, exposure *exp, const char *name = "file",
|
|
unsigned int bayer = IMAGE_BAYER_DEFAULT, int init_reference_gain = 0) {
|
|
|
|
static int warned = 0;
|
|
|
|
if (bayer == IMAGE_BAYER_DEFAULT)
|
|
bayer = bayer_default;
|
|
|
|
if (is_eppm(filename)) {
|
|
return read_ppm(filename, exp, bayer, init_reference_gain);
|
|
}
|
|
|
|
#ifdef USE_MAGICK
|
|
|
|
if (MaxRGB < 65535 && mcv == 65535 && !warned) {
|
|
fprintf(stderr, "\n\n*** Warning: " MagickPackageName " has not been compiled with 16 bit support.\n");
|
|
fprintf(stderr, "*** Reading input using 8 bits per channel.\n");
|
|
fprintf(stderr, "*** \n");
|
|
fprintf(stderr, "*** (To silence this warning, specify option --8bpc)\n\n\n");
|
|
|
|
warned = 1;
|
|
}
|
|
|
|
/*
|
|
* Patterned after http://www.imagemagick.org/www/api.html
|
|
* and http://www.imagemagick.org/www/smile.c
|
|
*/
|
|
|
|
ExceptionInfo exception;
|
|
Image *mi;
|
|
ImageInfo *image_info;
|
|
image *im;
|
|
const PixelPacket *p;
|
|
|
|
unsigned int i, j;
|
|
|
|
ale_real black_level = exp->get_black_level();
|
|
|
|
GetExceptionInfo(&exception);
|
|
image_info = CloneImageInfo((ImageInfo *) NULL);
|
|
|
|
strncpy(image_info->filename, filename, MaxTextExtent);
|
|
mi = ReadImage(image_info, &exception);
|
|
if (exception.severity != UndefinedException) {
|
|
fprintf(stderr, "\n\n");
|
|
CatchException(&exception);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
if (mi == (Image *) NULL)
|
|
exit(1);
|
|
|
|
if (bayer == IMAGE_BAYER_NONE)
|
|
im = new_image_ale_real(mi->rows, mi->columns, 3, name, exp);
|
|
else
|
|
im = new_image_bayer_ale_real(mi->rows, mi->columns, 3, bayer, name, exp);
|
|
|
|
for (i = 0; i < mi->rows; i++) {
|
|
p = AcquireImagePixels(mi, 0, i, mi->columns, 1, &exception);
|
|
|
|
if (exception.severity != UndefinedException)
|
|
CatchException(&exception);
|
|
if (p == NULL)
|
|
exit(1);
|
|
|
|
for (j = 0; j < mi->columns; j++) {
|
|
|
|
pixel input ( ale_real_from_int(p->red, MaxRGB),
|
|
ale_real_from_int(p->green, MaxRGB),
|
|
ale_real_from_int(p->blue, MaxRGB) );
|
|
|
|
pixel linear_input = (exp->linearize(input) - exp->get_multiplier() * black_level)
|
|
/ (1 - black_level);
|
|
|
|
im->set_pixel(i, j, linear_input);
|
|
|
|
p++;
|
|
}
|
|
}
|
|
|
|
DestroyImage(mi);
|
|
DestroyImageInfo(image_info);
|
|
|
|
return im;
|
|
#else
|
|
return read_ppm(filename, exp, bayer);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Initializer.
|
|
*
|
|
* Handle FILE_COUNT input files with names in array FILENAMES and
|
|
* output file OUTPUT_FILENAME. FILENAMES should be an array of char *
|
|
* that is never freed. OUTPUT_FILENAME should be a char * that is
|
|
* never freed.
|
|
*
|
|
* INPUT_EXPOSURE should be an array of FILE_COUNT exposure objects
|
|
* that is never freed. OUTPUT_EXPOSURE should be an exposure * that
|
|
* is never freed.
|
|
*/
|
|
static void init(unsigned int _file_count, const char **_filenames,
|
|
const char *_output_filename, exposure **_input_exposure,
|
|
exposure *_output_exposure){
|
|
assert (_file_count > 0);
|
|
|
|
init_image();
|
|
|
|
filenames = _filenames;
|
|
file_count = _file_count;
|
|
output_filename = _output_filename;
|
|
input_exposure = _input_exposure;
|
|
output_exposure = _output_exposure;
|
|
|
|
images = (const image **)malloc(file_count * sizeof(image *));
|
|
bayer_specific = (unsigned int *)malloc(file_count * sizeof(unsigned int));
|
|
files_open = (int *)calloc(file_count, sizeof(int));
|
|
|
|
assert (images);
|
|
assert (bayer_specific);
|
|
assert (files_open);
|
|
|
|
if (!images || !files_open || !bayer_specific) {
|
|
fprintf(stderr, "Unable to allocate memory for images.\n");
|
|
exit(1);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < file_count; i++)
|
|
bayer_specific[i] = IMAGE_BAYER_DEFAULT;
|
|
|
|
ui::get()->identify_output(output_filename);
|
|
}
|
|
|
|
static void ppm_plain() {
|
|
ppm_type = 2;
|
|
}
|
|
|
|
static void ppm_raw() {
|
|
ppm_type = 1;
|
|
}
|
|
|
|
static void ppm_auto() {
|
|
#ifdef USE_MAGICK
|
|
ppm_type = 0;
|
|
#else
|
|
fprintf(stderr, "\n\n*** Error: --auto flag not supported on this build. ***\n"
|
|
"*** (Hint: Rebuild with IMAGEMAGICK=1) ***\n\n");
|
|
exit(1);
|
|
#endif
|
|
}
|
|
|
|
static void set_default_bayer(unsigned int b) {
|
|
bayer_default = b;
|
|
}
|
|
|
|
static void set_specific_bayer(unsigned int index, unsigned int b) {
|
|
assert (bayer_specific);
|
|
bayer_specific[index] = b;
|
|
}
|
|
|
|
static void depth16() {
|
|
mcv = 65535;
|
|
}
|
|
|
|
static void depth8() {
|
|
mcv = 255;
|
|
}
|
|
|
|
static void set_cache(double size) {
|
|
cache_size_max = size;
|
|
}
|
|
|
|
static void destroy() {
|
|
assert (file_count > 0);
|
|
destroy_image();
|
|
}
|
|
|
|
static unsigned int count() {
|
|
assert (file_count > 0);
|
|
return file_count;
|
|
}
|
|
|
|
static const char *name(unsigned int image) {
|
|
assert (image < file_count);
|
|
|
|
return filenames[image];
|
|
}
|
|
|
|
static void def_nn(double _nn) {
|
|
nn_defined_radius = _nn;
|
|
}
|
|
|
|
static const char *output_name() {
|
|
assert (file_count > 0);
|
|
return output_filename;
|
|
}
|
|
|
|
/*
|
|
* Write an image to a file
|
|
*/
|
|
static void write_image(const char *filename, const image *im, exposure *exp = output_exposure, int rezero = 0, int exp_scale_override = 0) {
|
|
static int warned = 0;
|
|
|
|
/*
|
|
* Handle ALE-specific magical filenames.
|
|
*/
|
|
|
|
if (!strcmp(filename, "dump:")) {
|
|
fprintf(stderr, "Image dump: ");
|
|
for (unsigned int i = 0; i < im->height(); i++)
|
|
for (unsigned int j = 0; j < im->width(); j++) {
|
|
pixel p = im->get_pixel(i, j);
|
|
fprintf(stderr, "(%d, %d): [%f %f %f] ", i, j, (double) p[0], (double) p[1], (double) p[2]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
|
|
return;
|
|
}
|
|
|
|
#ifdef USE_MAGICK
|
|
|
|
/*
|
|
* Patterned after http://www.imagemagick.org/www/api.html
|
|
* and http://www.imagemagick.org/www/smile.c
|
|
*/
|
|
|
|
ExceptionInfo exception;
|
|
Image *mi;
|
|
ImageInfo *image_info;
|
|
PixelPacket *p;
|
|
|
|
unsigned int i, j;
|
|
|
|
GetExceptionInfo(&exception);
|
|
image_info = CloneImageInfo((ImageInfo *) NULL);
|
|
strncpy(image_info->filename, filename, MaxTextExtent);
|
|
|
|
mi = AllocateImage(image_info);
|
|
if (mi == (Image *) NULL)
|
|
MagickError(ResourceLimitError,
|
|
"Unable to display image", "MemoryAllocationFailed");
|
|
|
|
mi->columns = im->width();
|
|
mi->rows = im->height();
|
|
|
|
/*
|
|
* Set the output image depth
|
|
*/
|
|
|
|
if (MaxRGB < 65535 || mcv < 65535)
|
|
mi->depth = 8;
|
|
else
|
|
mi->depth = 16;
|
|
|
|
if (MaxRGB < 65535 && mcv == 65535 && !warned) {
|
|
fprintf(stderr, "\n\n*** Warning: " MagickPackageName " has not been compiled with 16 bit support.\n");
|
|
fprintf(stderr, "*** Writing output using 8 bits per channel.\n");
|
|
fprintf(stderr, "*** \n");
|
|
fprintf(stderr, "*** (To silence this warning, specify option --8bpc)\n\n\n");
|
|
|
|
warned = 1;
|
|
}
|
|
|
|
/*
|
|
* Set compression type
|
|
*/
|
|
|
|
if (ppm_type == 2) {
|
|
mi->compression = NoCompression;
|
|
image_info->compression = NoCompression;
|
|
strncpy(mi->magick, "PNM", MaxTextExtent);
|
|
strncpy(image_info->magick, "PNM", MaxTextExtent);
|
|
} else if (ppm_type == 1) {
|
|
strncpy(mi->magick, "PNM", MaxTextExtent);
|
|
strncpy(image_info->magick, "PNM", MaxTextExtent);
|
|
}
|
|
|
|
/*
|
|
* Automatic exposure adjustment (don't blow out highlights)
|
|
*/
|
|
ale_real maxval = 1;
|
|
ale_real minval = (rezero ? im->minval() : (ale_real) 0);
|
|
if (minval > 0)
|
|
minval = 0;
|
|
pixel minval_pixel(minval, minval, minval);
|
|
|
|
|
|
if (exposure_scale || exp_scale_override) {
|
|
ale_real new_maxval = im->maxval();
|
|
|
|
if (new_maxval > maxval)
|
|
maxval = new_maxval;
|
|
}
|
|
|
|
/*
|
|
* Write the image
|
|
*/
|
|
|
|
for (i = 0; i < mi->rows; i++) {
|
|
p = SetImagePixels(mi, 0, i, mi->columns, 1);
|
|
if (p == NULL)
|
|
break;
|
|
|
|
for (j = 0; j < mi->columns; j++) {
|
|
|
|
pixel value = im->get_pixel(i, j);
|
|
|
|
/*
|
|
* Get nearest-neighbor defined values.
|
|
*
|
|
* XXX: While this implementation is correct, it is inefficient
|
|
* for large radii. A better implementation would search
|
|
* perimeters of squares of ever-increasing radius, tracking
|
|
* the best-so-far data until the square perimeter exceeded the
|
|
* best-so-far radius.
|
|
*/
|
|
|
|
for (int k = 0; k < 3; k++)
|
|
if (isnan(value[k]))
|
|
for (int radius = 1; radius <= nn_defined_radius; radius++) {
|
|
double nearest_radius_squared = (radius + 1) * (radius + 1);
|
|
for (int ii = -radius; ii <= radius; ii++)
|
|
for (int jj = -radius; jj <= radius; jj++) {
|
|
if (!im->in_bounds(point(i + ii, j + jj)))
|
|
continue;
|
|
if (ii * ii + jj * jj < nearest_radius_squared
|
|
&& finite(im->get_pixel(i + ii, j + jj)[k])) {
|
|
value[k] = im->get_pixel(i + ii, j + jj)[k];
|
|
nearest_radius_squared = ii * ii + jj * jj;
|
|
}
|
|
}
|
|
if (nearest_radius_squared < (radius + 1) * (radius + 1))
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Unlinearize
|
|
*/
|
|
|
|
pixel unlinearized(exp->unlinearize((value - minval_pixel)
|
|
/ (maxval - minval)));
|
|
|
|
unlinearized = unlinearized.clamp();
|
|
|
|
p->red = (Quantum) ale_real_to_int(unlinearized[0], MaxRGB);
|
|
p->green = (Quantum) ale_real_to_int(unlinearized[1], MaxRGB);
|
|
p->blue = (Quantum) ale_real_to_int(unlinearized[2], MaxRGB);
|
|
p++;
|
|
}
|
|
|
|
if (!SyncImagePixels(mi))
|
|
break;
|
|
}
|
|
|
|
if (!WriteImage(image_info, mi)) {
|
|
|
|
/*
|
|
* Perhaps file type was unknown? Set to PNM by default.
|
|
*/
|
|
|
|
strncpy(mi->magick, "PNM", MaxTextExtent);
|
|
strncpy(image_info->magick, "PNM", MaxTextExtent);
|
|
|
|
if (!WriteImage(image_info, mi)) {
|
|
fprintf(stderr, "\n\n");
|
|
CatchException(&mi->exception);
|
|
fprintf(stderr, "\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
DestroyImage(mi);
|
|
DestroyImageInfo(image_info);
|
|
#else
|
|
write_ppm(filename, im, exp, mcv, ppm_type == 2, rezero, exposure_scale || exp_scale_override,
|
|
nn_defined_radius);
|
|
#endif
|
|
}
|
|
|
|
static void output(const image *i) {
|
|
assert (file_count > 0);
|
|
write_image(output_name(), i, output_exposure);
|
|
}
|
|
|
|
static void vise_write(const char *p, const char *s, const image *i) {
|
|
static int count = 0;
|
|
int length = strlen(p) + strlen(s) + 8;
|
|
char *output_string = (char *) malloc(length * sizeof(char));
|
|
|
|
snprintf(output_string, length, "%s%08d%s", p, count, s);
|
|
|
|
write_image(output_string, i, output_exposure);
|
|
|
|
count++;
|
|
}
|
|
|
|
static exposure &exp(int n) {
|
|
return *input_exposure[n];
|
|
}
|
|
|
|
static const exposure &const_exp(int n) {
|
|
return *input_exposure[n];
|
|
}
|
|
|
|
static exposure &exp() {
|
|
return *output_exposure;
|
|
}
|
|
|
|
static void exp_scale() {
|
|
exposure_scale = 1;
|
|
}
|
|
|
|
static void exp_noscale() {
|
|
exposure_scale = 0;
|
|
}
|
|
|
|
static const exposure &const_exp() {
|
|
return *output_exposure;
|
|
}
|
|
|
|
static const unsigned int bayer(unsigned int n) {
|
|
if (bayer_specific[n] == IMAGE_BAYER_DEFAULT)
|
|
return bayer_default;
|
|
else
|
|
return bayer_specific[n];
|
|
}
|
|
|
|
static const image *open(unsigned int n) {
|
|
assert (n < file_count);
|
|
assert (!files_open[n]);
|
|
|
|
files_open[n] = 1;
|
|
|
|
if (latest_close_num >= 0 && n == (unsigned int) latest_close_num) {
|
|
latest_close_num = -1;
|
|
return images[n];
|
|
}
|
|
|
|
if (n < cache_count)
|
|
return images[n];
|
|
|
|
ui::get()->loading_file();
|
|
image *i = read_image(filenames[n], input_exposure[n], "file", bayer(n), (n == 0));
|
|
|
|
images[n] = i;
|
|
|
|
return images[n];
|
|
}
|
|
|
|
static void open_all() {
|
|
for (unsigned int n = 0; n < file_count; n++)
|
|
open(n);
|
|
}
|
|
|
|
static const image *get_open(unsigned int n) {
|
|
assert (files_open[n]);
|
|
return images[n];
|
|
}
|
|
|
|
static image *copy(unsigned int n, const char *name) {
|
|
assert (n < file_count);
|
|
|
|
if (files_open[n])
|
|
return images[n]->clone(name);
|
|
else {
|
|
image *i = read_image(filenames[n], input_exposure[n], name, bayer(n), (n == 0));
|
|
return i;
|
|
}
|
|
}
|
|
|
|
static void close(unsigned int image) {
|
|
assert (image < file_count);
|
|
assert (files_open[image]);
|
|
|
|
files_open[image] = 0;
|
|
|
|
if (image < cache_count)
|
|
return;
|
|
|
|
if (image == cache_count) {
|
|
double image_size = ((double) images[image]->storage_size()) / pow(2, 20);
|
|
|
|
if (image_size + cache_size < cache_size_max) {
|
|
cache_size += image_size;
|
|
cache_count++;
|
|
ui::get()->cache(cache_size, cache_size_max);
|
|
return;
|
|
} else {
|
|
ui::get()->cache_status(0);
|
|
}
|
|
}
|
|
|
|
if (latest_close_num >= 0)
|
|
delete images[latest_close_num];
|
|
|
|
latest_close_num = image;
|
|
}
|
|
|
|
static void close_all() {
|
|
for (unsigned int n = 0; n < file_count; n++)
|
|
close(n);
|
|
}
|
|
|
|
};
|
|
|
|
#endif
|