431 lines
9.4 KiB
C
431 lines
9.4 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
|
|
*/
|
|
|
|
/*
|
|
* 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
|