// 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 */ /* * ppm.h: Read and write PPM files. */ #ifndef __ppm_h__ #define __ppm_h__ #include "image_ale_real.h" #include "image_bayer_ale_real.h" #include "exposure/exposure.h" /* * Extended attributes */ struct extended_t { int is_extended; ale_real black_level; ale_real aperture; /* 1 == f/1.0, 1.4 == f/1.4, etc. */ ale_real shutter; /* 1 == 1 sec, 0.5 == 1/2 sec, etc. */ ale_real gain; /* 1 == ISO 100, 2 == ISO 200, etc. */ extended_t() { is_extended = 0; black_level = 0; aperture = 0; shutter = 0; gain = 0; } }; static inline void error_ppm(const char *filename) { fprintf(stderr, "\n\n*** '%s' doesn't look like a PPM file.\n" "\n*** (To handle other file types, use a version of ALE with\n" "*** ImageMagick support enabled.)\n\n", filename); exit(1); } static inline int digest_comment(FILE *f, const char *filename, extended_t *extended) { int next = '#'; int value; double fvalue, fvalue2; while (next != '\n' && next != '\r' && next != EOF) { while (next == ' ' || next == '\t' || next == '#') { next = fgetc(f); if (feof(f)) error_ppm(filename); } if (ungetc(next, f) == EOF) { assert(0); fprintf(stderr, "Unable to ungetc()."); exit(1); } fvalue2 = 1; if (extended->is_extended && fscanf(f, "Black-level: %d", &value) == 1) extended->black_level = value; else if (extended->is_extended && fscanf(f, "ISO: %lf", &fvalue) == 1) extended->gain = fvalue / 100; else if (extended->is_extended && fscanf(f, "Gain: %lf", &fvalue) == 1) extended->gain = fvalue; else if (extended->is_extended && fscanf(f, "Aperture: %lf", &fvalue) == 1) extended->aperture = fvalue; else if (extended->is_extended && fscanf(f, "Shutter: %lf/%lf", &fvalue, &fvalue2) > 0) extended->shutter = fvalue / fvalue2; else if (next != '\n' && next != '\r' && next != EOF) next = fgetc(f); next = fgetc(f); } return next; } static inline void eat_comments(FILE *f, const char *filename, extended_t *extended) { int next = ' '; while (next == ' ' || next == '\n' || next == '\t' || next == '#' || next == '\r') { next = fgetc(f); if (next == '#') next = digest_comment(f, filename, extended); if (feof(f)) error_ppm(filename); } if (ungetc(next, f) == EOF) { assert(0); fprintf(stderr, "Unable to ungetc()."); exit(1); } } static inline int is_eppm(const char *filename) { char m1, m2, m3, m4; int n; extended_t extended; FILE *f = fopen(filename, "rb"); if (f == NULL) return 0; /* Magic */ eat_comments(f, filename, &extended); /* XXX - should we eat comments here? */ n = fscanf(f, "%c%c%c%c", &m1, &m2, &m3, &m4); fclose(f); if (n != 4 || m1 != 'P' || (m2 != '6' && m2 != '3') || m3 != '#' || m4 != 'E') return 0; return 1; } static inline image *read_ppm(const char *filename, exposure *e, unsigned int bayer, int init_reference_gain = 0) { unsigned int i, j, k; image *im; unsigned char m1, m2, val; int m3, m4; int ival; int w, h, mcv; int n; struct extended_t extended; FILE *f = fopen(filename, "rb"); if (f == NULL) { fprintf(stderr, "\n\nUnable to open '%s'.\n\n", filename); exit(1); } assert(f); /* Magic */ eat_comments(f, filename, &extended); /* XXX - should we eat comments here? */ n = fscanf(f, "%c%c", &m1, &m2); if (n != 2 || m1 != 'P' || (m2 != '6' && m2 != '3')) error_ppm(filename); assert(n == 2 && m1 == 'P' && (m2 == '6' || m2 == '3')); /* Extended flag */ m3 = fgetc(f); if (m3 == '#') { m4 = fgetc(f); if (m4 == 'E') extended.is_extended = 1; else while (m4 != EOF && m4 != '\n' && m4 != '\r') m4 = fgetc(f); } else if (ungetc(m3, f) == EOF) { assert(0); fprintf(stderr, "Unable to ungetc()."); exit(1); } /* Width */ eat_comments(f, filename, &extended); n = fscanf(f, " %d", &w); assert(n == 1); if (n != 1) error_ppm(filename); /* Height */ eat_comments(f, filename, &extended); n = fscanf(f, "%d", &h); assert(n == 1); if (n != 1) error_ppm(filename); /* Maximum component value */ eat_comments(f, filename, &extended); n = fscanf(f, "%d", &mcv); assert(n == 1); assert(mcv <= 65535 || m2 == '3'); if (extended.black_level == 0) { extended.black_level = e->get_black_level(); } else { extended.black_level /= mcv; } if (n != 1 || (mcv > 65535 && m2 == '6')) error_ppm(filename); /* Make a new image */ if (bayer == IMAGE_BAYER_NONE) im = new_image_ale_real(h, w, 3, "file", e); else im = new_image_bayer_ale_real(h, w, 3, bayer, "file", e); assert (im); /* Trailing whitespace */ if (fgetc(f) == EOF) { assert(0); error_ppm(filename); } /* Pixels */ for (i = 0; i < im->height(); i++) for (j = 0; j < im->width(); j++) { pixel p; for (k = 0; k < im->depth(); k++) { if (m2 == '6') { /* Binary data */ n = fscanf(f, "%c", &val); assert (n == 1); if (n != 1) error_ppm(filename); ival = val; if (mcv > 255) { n = fscanf(f, "%c", &val); assert(n == 1); if (n != 1) error_ppm(filename); ival = (ival << 8) | val; } } else { /* ASCII data */ eat_comments(f, filename, &extended); n = fscanf(f, "%d", &ival); assert (n == 1); if (n != 1) error_ppm(filename); } p[k] = ale_real_from_int(ival, mcv); } pixel p_linear = (e->linearize(p) - e->get_multiplier() * extended.black_level) / (1 - extended.black_level); im->set_pixel(i, j, p_linear); } /* Handle exposure and gain */ if (extended.is_extended) { if (extended.aperture != 0 || extended.shutter != 0 || extended.gain != 0) { if (extended.aperture == 0) extended.aperture = 1; if (extended.shutter == 0) extended.shutter = 1; if (extended.gain == 0) extended.gain = 1; ale_real combined_gain = (1 / pow(extended.aperture, 2)) * extended.shutter * extended.gain; if (init_reference_gain) exposure::set_gain_reference(combined_gain); else e->set_gain_multiplier(exposure::get_gain_reference() / combined_gain); } } /* Done */ fclose(f); return im; } static inline void write_ppm(const char *filename, const image *im, exposure *e, unsigned int mcv, int plain, int rezero, int exposure_scale, double nn_defined_radius) { unsigned int i, j, k; FILE *f = fopen(filename, "wb"); if (f == NULL) { fprintf(stderr, "\n\nUnable to open '%s'.\n\n", filename); exit(1); } assert(f); /* * Output a plain (ASCII) or raw (binary) PPM file */ /* Magic */ if (plain) fprintf(f, "P3 "); else fprintf(f, "P6 "); /* Width */ fprintf(f, "%d ", im->width()); /* Height */ fprintf(f, "%d ", im->height()); /* Maximum component value */ fprintf(f, "%d\n", mcv); /* Automatic exposure adjustment information */ 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) { ale_real new_maxval = im->maxval(); if (new_maxval > maxval) maxval = new_maxval; } /* Pixels */ for (i = 0; i < im->height(); i++) for (j = 0; j < im->width(); 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 (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; } pixel exposure_adjust = (value - minval_pixel) / (maxval - minval); pixel unlinearized = (e->unlinearize(exposure_adjust)).clamp(); for (k = 0; k < im->depth(); k++) { uint16_t output_value = (uint16_t) ale_real_to_int(unlinearized[k], mcv); if (plain) { fprintf(f, "%d ", output_value); } else { if (mcv > 255) fprintf(f, "%c", output_value >> 8); fprintf(f, "%c", 0xff & output_value); } } if (plain) fprintf(f, "\n"); } /* Done */ fclose(f); } #endif