ale/d2/ppm.h
2022-07-30 14:46:04 -03:00

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