// Copyright 2002 David Hilvert , // /* 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