// Copyright 2002, 2003, 2004 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.h: Abstract base class for the internal representations of images used
 * by ALE.
 */

#ifndef __image_h__
#define __image_h__

#include "point.h"
#include "pixel.h"
#include "exposure/exposure.h"

#define IMAGE_BAYER_NONE 0

/*
 * This constant indicates that some other default value should be filled in.
 */

#define IMAGE_BAYER_DEFAULT 0x8

/*
 * Do not change these values without inspecting
 * image_bayer_ale_real::r_*_offset().
 */
#define IMAGE_BAYER_RGBG 0x4 /* binary 100 */
#define IMAGE_BAYER_GBGR 0x5 /* binary 101 */
#define IMAGE_BAYER_GRGB 0x6 /* binary 110 */
#define IMAGE_BAYER_BGRG 0x7 /* binary 111 */

class image : protected exposure::listener {
protected:
	static double resident;
	unsigned int _dimx, _dimy, _depth;
	point _offset;
	const char *name;
	mutable exposure *_exp;
	unsigned int bayer;
private:
	/*
	 * Memoized function variables.  We may want to change these even when
	 * *this is constant.
	 */
	mutable int _apm_memo;
	mutable ale_real _apm;
	mutable int _accm_memo;
	mutable pixel _accm;
	mutable int _acm_memo;
	mutable pixel _acm;

	void avg_channel_clamped_magnitude_memo() const {
		unsigned int i, j, k;
		pixel_accum accumulator;
		pixel_accum divisor;

		if (_accm_memo)
			return;

		_accm_memo = 1;

		accumulator = pixel_accum(0, 0, 0);

		for (i = 0; i < _dimy;  i++)
		for (j = 0; j < _dimx;  j++) {
			pixel value = get_pixel(i, j);

			for (k = 0; k < _depth; k++)
				if (finite(value[k])) {
					if (value[k] > 1)
						value[k] = 1;
					if (value[k] < 0)
						value[k] = 0;
					accumulator[k] += value[k];
					divisor[k] += 1;
				}
		}

		accumulator /= divisor;

		_accm = accumulator;
	}

	void avg_channel_magnitude_memo() const {
		unsigned int i, j, k;
		pixel_accum accumulator;
		pixel_accum divisor;

		if (_acm_memo)
			return;

		_acm_memo = 1;

		accumulator = pixel_accum(0, 0, 0);

		for (i = 0; i < _dimy;  i++)
		for (j = 0; j < _dimx;  j++) {
			pixel value = get_pixel(i, j);

			for (k = 0; k < _depth; k++) 
				if (finite(value[k])) {
					accumulator[k] += value[k];
					divisor[k] += 1;
				}
		}

		accumulator /= divisor;

		_acm = accumulator;
	}

protected:
	void image_updated() {
		_apm_memo = 0;
		_acm_memo = 0;
		_accm_memo = 0;
	}

public:
	static void set_resident(double r) {
		resident = r;
	}

	static double get_resident() {
		return resident;
	}

	image (unsigned int dimy, unsigned int dimx, unsigned int depth, 
			const char *name = "anonymous", exposure *_exp = NULL,
			unsigned int bayer = IMAGE_BAYER_NONE) {

		assert (depth == 3);
		_depth = 3;

		_dimx = dimx;
		_dimy = dimy;
		_offset = point(0, 0);
		_apm_memo = 0;
		_acm_memo = 0;
		_accm_memo = 0;
		this->name = name;
		this->_exp = _exp;
		this->bayer = bayer;

		if (_exp != NULL)
			_exp->add_listener(this, name);
	}

	unsigned int get_bayer() const {
		return bayer;
	}

	virtual char get_channels(int i, int j) const {
		return 0x7;
	}

	virtual unsigned int bayer_color(unsigned int i, unsigned int j) const {
		assert(0);
	}

	double storage_size() const {
		if (bayer != IMAGE_BAYER_NONE)
			return _dimx * _dimy * sizeof(ale_real);

		return 3 * _dimx * _dimy * sizeof(ale_real);
	}

	exposure &exp() const {
		return *_exp;
	}

	point offset() const {
		return _offset;
	}

	void set_offset(int i, int j) {
		_offset[0] = i;
		_offset[1] = j;
	}

	void set_offset(point p) {
		_offset = p;
	}

	unsigned int width() const {
		return _dimx;
	}

	unsigned int height() const {
		return _dimy;
	}

	unsigned int depth() const {
		return _depth;
	}

	virtual void set_pixel(unsigned int y, unsigned int x, spixel p) = 0;
	
	virtual spixel get_pixel(unsigned int y, unsigned int x) const = 0;

	virtual spixel get_raw_pixel(unsigned int y, unsigned int x) const {
		return ((const image *)this)->get_pixel(y, x);
	}

	virtual void add_pixel(unsigned int y, unsigned int x, pixel p) {
		assert(0);
	}

	virtual void mul_pixel(unsigned int y, unsigned int x, pixel p) {
		assert(0);
	}

	virtual void div_pixel(unsigned int y, unsigned int x, pixel p) {
		assert(0);
	}

	virtual void add_chan(unsigned int y, unsigned int x, unsigned int k, ale_real c) {
		assert(0);
	}

	virtual void div_chan(unsigned int y, unsigned int x, unsigned int k, ale_real c) {
		assert(0);
	}

	virtual void set_chan(unsigned int y, unsigned int x, unsigned int k, ale_sreal c) = 0;

	virtual ale_sreal get_chan(unsigned int y, unsigned int x, unsigned int k) const = 0;

	ale_real maxval() const {
		ale_real result = get_pixel(0, 0)[0];

		for (unsigned int i = 0; i < _dimy; i++)
		for (unsigned int j = 0; j < _dimx; j++) {
			pixel p = get_pixel(i, j);

			for (unsigned int k = 0; k < _depth; k++)
				if (p[k] > result || !finite(result))
					result = p[k];
		}

		return result;
	}

	ale_real minval() const {
		ale_real result = get_pixel(0, 0)[0];

		for (unsigned int i = 0; i < _dimy; i++)
		for (unsigned int j = 0; j < _dimx; j++) {
			pixel p = get_pixel(i, j);

			for (unsigned int k = 0; k < _depth; k++)
				if (p[k] < result || !finite(result))
					result = p[k];
		}

		return result;
	}

	/*
	 * Get the maximum difference among adjacent pixels.
	 */

	pixel get_max_diff(unsigned int i, unsigned int j) const {
		assert(i <= _dimy - 1);
		assert(j <= _dimx - 1);

		pixel max = get_pixel(i, j), min = get_pixel(i, j);

		for (int ii = -1; ii <= 1; ii++)
		for (int jj = -1; jj <= 1; jj++) {
			int iii = i + ii;
			int jjj = j + jj;

			if (iii < 0)
				continue;
			if (jjj < 0)
				continue;
			if ((unsigned int) iii > _dimy - 1)
				continue;
			if ((unsigned int) jjj > _dimx - 1)
				continue;

			pixel p = get_pixel(iii, jjj);

			for (int d = 0; d < 3; d++) {
				if (p[d] > max[d])
					max[d] = p[d];
				if (p[d] < min[d])
					min[d] = p[d];
			}
		}

		return max - min;
	}

	pixel get_max_diff(point x) const {
		assert (x[0] >= 0);
		assert (x[1] >= 0);
		assert (x[0] <= _dimy - 1);
		assert (x[1] <= _dimx - 1);

		unsigned int i = (unsigned int) round(x[0]);
		unsigned int j = (unsigned int) round(x[1]);

		return get_max_diff(i, j);
	}

	int in_bounds(point x) const {
		if (x[0] < 0
		 || x[1] < 0
		 || x[0] > height() - 1
		 || x[1] > width() - 1)
			return 0;
		if (!x.defined())
			return 0;
		return 1;
	}

	/*
	 * Get a color value at a given position using bilinear interpolation between the
	 * four nearest pixels.
	 */
	pixel get_bl(point x, int defined = 0) const {
//		fprintf(stderr, "get_bl x=%f %f\n", (double) x[0], (double) x[1]);

		pixel result;

		assert (x[0] >= 0);
		assert (x[1] >= 0);
		assert (x[0] <= _dimy - 1);
		assert (x[1] <= _dimx - 1);

		int lx = (int) floor(x[1]);
		int hx = (int) floor(x[1]) + 1;
		int ly = (int) floor(x[0]);
		int hy = (int) floor(x[0]) + 1;

//		fprintf(stderr, "get_bl l=%d %d h=%d %d\n", ly, lx, hy, hx);

		pixel neighbor[4];
		ale_real factor[4];

		neighbor[0] = get_pixel(ly, lx);
		neighbor[1] = get_pixel(hy % _dimy, lx);
		neighbor[2] = get_pixel(hy % _dimy, hx % _dimx);
		neighbor[3] = get_pixel(ly, hx % _dimx);

//		for (int d = 0; d < 4; d++)
//			fprintf(stderr, "neighbor_%d=%f %f %f\n", d,
//					(double) neighbor[d][0],
//					(double) neighbor[d][1],
//					(double) neighbor[d][2]);

		factor[0] = (ale_real) (hx - x[1]) * (ale_real) (hy - x[0]);
		factor[1] = (ale_real) (hx - x[1]) * (ale_real) (x[0] - ly);
		factor[2] = (ale_real) (x[1] - lx) * (ale_real) (x[0] - ly);
		factor[3] = (ale_real) (x[1] - lx) * (ale_real) (hy - x[0]);

//		for (int d = 0; d < 4; d++)
//			fprintf(stderr, "factor_%d=%f\n", d,
//					(double) factor[d]);

		/*
		 * Use bilinear and/or geometric interpolation
		 */

		if (defined == 0) {
			result = pixel(0, 0, 0);

			for (int n = 0; n < 4; n++)
				result += factor[n] * neighbor[n];
		} else {
#if 0
			/*
			 * Calculating the geometric mean may be expensive on
			 * some platforms (e.g., those without floating-point
			 * support.
			 */

			result = pixel(1, 1, 1);

			for (int n = 0; n < 4; n++)
				result *= ppow(neighbor[n], factor[n]);
#else
			/*
			 * Taking the minimum value may be cheaper than
			 * calculating a geometric mean.
			 */

			result = neighbor[0];

			for (int n = 1; n < 4; n++) 
			for (int k = 0; k < 3; k++) 
				if (neighbor[n][k] < result[k])
					result[k] = neighbor[n][k];
#endif
		}

//		fprintf(stderr, "result=%f %f %f\n", 
//				(double) result[0],
//				(double) result[1],
//				(double) result[2]);

		return result;
	}

	pixel get_scaled_bl(point x, ale_pos f, int defined = 0) const {
		point scaled(
				x[0]/f <= height() - 1
			               ? (x[0]/f)
				       : (ale_pos) (height() - 1),
				x[1]/f <= width() - 1
				       ? (x[1]/f)
				       : (ale_pos) (width() - 1));

		return get_bl(scaled, defined);
	}

		
	/*
	 * Make a new image suitable for receiving scaled values.
	 */
	virtual image *scale_generator(int height, int width, int depth, const char *name) const = 0;

	/*
	 * Generate an image of medians within a given radius
	 */

	image *medians(int radius) const {

		assert (radius >= 0);
		
		image *is = scale_generator(height(), width(), depth(), "median");
		assert(is);

		for (unsigned int i = 0; i < height(); i++)
		for (unsigned int j = 0; j < width(); j++) {

			std::vector<ale_real> p[3];

			for (int ii = -radius; ii <= radius; ii++)
			for (int jj = -radius; jj <= radius; jj++) {
				int iii = i + ii;
				int jjj = j + jj;

				if (in_bounds(point(iii, jjj)))
					for (int k = 0; k < 3; k++)
						if (finite(get_pixel(iii, jjj)[k]))
							p[k].push_back(get_pixel(iii, jjj)[k]);
			}

			is->set_pixel(i, j, d2::pixel::undefined());

			for (int k = 0; k < 3; k++) {
				std::sort(p[k].begin(), p[k].end());

				unsigned int pkc = p[k].size();

				if (pkc == 0)
					continue;

				if (pkc % 2 == 0) 
					is->set_chan(i, j, k, 
						(p[k][pkc / 2] + p[k][pkc / 2 - 1]) / 2);
				else
					is->set_chan(i, j, k, 
						p[k][pkc / 2]);
			}
		}

		return is;
	}

	/*
	 * Generate an image of differences of the first channel.  The first
	 * coordinate differences are stored in the first channel, second in the
	 * second channel.
	 */

	image *fcdiffs() const {
		image *is = scale_generator(height(), width(), depth(), "diff");

		assert(is);

		for (unsigned int i = 0; i < height(); i++)
		for (unsigned int j = 0; j < width(); j++) {

			if (i + 1 < height()
			 && i > 0
			 && !finite(get_chan(i, j, 0))) {

				is->set_chan(i, j, 0, (get_chan(i + 1, j, 0) 
				                     - get_chan(i - 1, j, 0)) / 2);

			} else if (i + 1 < height()
			        && i > 0
			        && finite(get_chan(i + 1, j, 0))
			        && finite(get_chan(i - 1, j, 0))) {

				is->set_chan(i, j, 0, ((get_chan(i, j, 0) - get_chan(i - 1, j, 0))
						     + (get_chan(i + 1, j, 0) - get_chan(i, j, 0))) / 2);

			} else if (i + 1 < height()
				&& finite(get_chan(i + 1, j, 0))) {

				is->set_chan(i, j, 0, get_chan(i + 1, j, 0) - get_chan(i, j, 0));

			} else if (i > 0
				&& finite(get_chan(i - 1, j, 0))) {
				 
				is->set_chan(i, j, 0, get_chan(i, j, 0) - get_chan(i - 1, j, 0));

			} else {
				is->set_chan(i, j, 0, 0);
			}

			if (j + 1 < width()
			 && j > 0
			 && !finite(get_chan(i, j, 0))) {

				is->set_chan(i, j, 1, (get_chan(i, j + 1, 0) - get_chan(i, j - 1, 0)) / 2);

			} else if (j + 1 < width()
			        && j > 0
			        && finite(get_chan(i, j + 1, 0))
			        && finite(get_chan(i, j - 1, 0))) {

				is->set_chan(i, j, 1, ((get_chan(i, j, 0) - get_chan(i, j - 1, 0))
						     + (get_chan(i, j + 1, 0) - get_chan(i, j, 0))) / 2);

			} else if (j + 1 < width() && finite(get_chan(i, j + 1, 0))) {

				is->set_chan(i, j, 1, get_chan(i, j + 1, 0) - get_chan(i, j, 0));

			} else if (j > 0 && finite(get_chan(i, j - 1, 0))) {
				 
				is->set_chan(i, j, 1, get_chan(i, j, 0) - get_chan(i, j - 1, 0));

			} else {
				is->set_chan(i, j, 1, 0);
			}
		}

		return is;
	}

	/*
	 * Generate an image of median (within a given radius) difference of the
	 * first channel.
	 */

	image *fcdiff_median(int radius) const {
		image *diff = fcdiffs();

		assert(diff);

		image *median = diff->medians(radius);

		assert(median);

		delete diff;

		return median;
	}

	/*
	 * Scale by half.  We use the following filter:
	 *
	 * 	1/16	1/8	1/16
	 * 	1/8	1/4	1/8
	 * 	1/16	1/8	1/16
	 *
	 * At the edges, these values are normalized so that the sum of the
	 * weights of contributing pixels is 1.
	 */
	class scale_by_half_threaded : public thread::decompose_domain {
		image *is;
		const image *iu;
	protected:
		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

			ale_real _0625 = (ale_real) 0.0625;
			ale_real _125  = (ale_real) 0.125;
			ale_real _25   = (ale_real) 0.25;
			ale_real _0    = (ale_real) 0;

			unsigned int ui_min = (unsigned int) i_min;
			unsigned int ui_max = (unsigned int) i_max;
			unsigned int uj_min = (unsigned int) j_min;
			unsigned int uj_max = (unsigned int) j_max;

			for (unsigned int i = ui_min; i < ui_max; i++)
			for (unsigned int j = uj_min; j < uj_max; j++) {
				is->set_pixel(i, j, 

				      ( ( ((i > 0 && j > 0) 
					    ? iu->get_pixel(2 * i - 1, 2 * j - 1) * _0625
					    : pixel(0, 0, 0))
					+ ((i > 0)
					    ? iu->get_pixel(2 * i - 1, 2 * j) * _125
					    : pixel(0, 0, 0))
					+ ((i > 0 && j < is->width() - 1)
					    ? iu->get_pixel(2 * i - 1, 2 * j + 1) * _0625
					    : pixel(0, 0, 0))
					+ ((j > 0)
					    ? iu->get_pixel(2 * i, 2 * j - 1) * _125
					    : pixel(0, 0, 0))
					+ iu->get_pixel(2 * i, 2 * j) * _25
					+ ((j < is->width() - 1)
					    ? iu->get_pixel(2 * i, 2 * j + 1) * _125
					    : pixel(0, 0, 0))
					+ ((i < is->height() - 1 && j > 0)
					    ? iu->get_pixel(2 * i + 1, 2 * j - 1) * _0625
					    : pixel(0, 0, 0))
					+ ((i < is->height() - 1)
					    ? iu->get_pixel(2 * i + 1, 2 * j) * _125
					    : pixel(0, 0, 0))
					+ ((i < is->height() && j < is->width() - 1)
					    ? iu->get_pixel(2 * i + 1, 2 * j + 1) * _0625 
					    : pixel(0, 0, 0)))

				     /

					( ((i > 0 && j > 0) 
					    ? _0625
					    : _0)
					+ ((i > 0)
					    ? _125
					    : _0)
					+ ((i > 0 && j < is->width() - 1)
					    ? _0625
					    : _0)
					+ ((j > 0)
					    ? _125
					    : _0)
					+ _25
					+ ((j < is->width() - 1)
					    ? _125
					    : _0)
					+ ((i < is->height() - 1 && j > 0)
					    ? _0625
					    : _0)
					+ ((i < is->height() - 1)
					    ? _125
					    : _0)
					+ ((i < is->height() && j < is->width() - 1)
					    ? _0625
					    : _0) ) ) );
			}
		}

	public:
		scale_by_half_threaded(image *_is, const image *_iu) 
					: decompose_domain(0, _is->height(),
					                   0, _is->width()) {
			is = _is;	
			iu = _iu;
		}
	};

	image *scale_by_half(const char *name) const {
		ale_pos f = 0.5;

		image *is = scale_generator(
			(int) floor(height() * (double) f), 
			(int) floor(width()  * (double) f), depth(), name);

		assert(is);

		scale_by_half_threaded sbht(is, this);
		sbht.run();

		is->_offset = point(_offset[0] * f, _offset[1] * f);

		return is;
	}

	/*
	 * Scale by half.  This function uses externally-provided weights,
	 * multiplied by the following filter:
	 *
	 * 	1/16	1/8	1/16
	 * 	1/8	1/4	1/8
	 * 	1/16	1/8	1/16
	 *
	 * Values are normalized so that the sum of the weights of contributing
	 * pixels is 1.
	 */
	image *scale_by_half(const image *weights, const char *name) const {

		if (weights == NULL)
			return scale_by_half(name);

		ale_pos f = 0.5;

		image *is = scale_generator(
			(int) floor(height() * (double) f), 
			(int) floor(width()  * (double) f), depth(), name);

		assert(is);

		for (unsigned int i = 0; i < is->height(); i++)
		for (unsigned int j = 0; j < is->width(); j++) {

			pixel value = pixel

			      ( ( ((i > 0 && j > 0) 
				    ? (pixel) get_pixel(2 * i - 1, 2 * j - 1) 
				    * (pixel) weights->get_pixel(2 * i - 1, 2 * j - 1) 
				    * (ale_real) 0.0625
				    : pixel(0, 0, 0))
				+ ((i > 0)
				    ? (pixel) get_pixel(2 * i - 1, 2 * j) 
				    * (pixel) weights->get_pixel(2 * i - 1, 2 * j)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i > 0 && j < is->width() - 1)
				    ? (pixel) get_pixel(2 * i - 1, 2 * j + 1) 
				    * (pixel) weights->get_pixel(2 * i - 1, 2 * j + 1)
				    * 0.0625
				    : pixel(0, 0, 0))
				+ ((j > 0)
				    ? (pixel) get_pixel(2 * i, 2 * j - 1) 
				    * (pixel) weights->get_pixel(2 * i, 2 * j - 1)
				    * 0.125
				    : pixel(0, 0, 0))
				+ get_pixel(2 * i, 2 * j) 
				    * (pixel) weights->get_pixel(2 * i, 2 * j)
				    * 0.25
				+ ((j < is->width() - 1)
				    ? (pixel) get_pixel(2 * i, 2 * j + 1) 
				    * (pixel) weights->get_pixel(2 * i, 2 * j + 1)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i < is->height() - 1 && j > 0)
				    ? (pixel) get_pixel(2 * i + 1, 2 * j - 1) 
				    * (pixel) weights->get_pixel(2 * i + 1, 2 * j - 1)
				    * 0.0625
				    : pixel(0, 0, 0))
				+ ((i < is->height() - 1)
				    ? (pixel) get_pixel(2 * i + 1, 2 * j) 
				    * (pixel) weights->get_pixel(2 * i + 1, 2 * j)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i < is->height() && j < is->width() - 1)
				    ? (pixel) get_pixel(2 * i + 1, 2 * j + 1) 
				    * (pixel) weights->get_pixel(2 * i + 1, 2 * j + 1)
				    * 0.0625 
				    : pixel(0, 0, 0)))

			     /

				( ((i > 0 && j > 0) 
				    ? weights->get_pixel(2 * i - 1, 2 * j - 1)
				    * 0.0625
				    : pixel(0, 0, 0))
				+ ((i > 0)
				    ? weights->get_pixel(2 * i - 1, 2 * j)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i > 0 && j < is->width() - 1)
				    ? weights->get_pixel(2 * i - 1, 2 * j + 1)
				    * 0.0625
				    : pixel(0, 0, 0))
				+ ((j > 0)
				    ? weights->get_pixel(2 * i, 2 * j - 1)
				    * 0.125
				    : pixel(0, 0, 0))
				+ weights->get_pixel(2 * i, 2 * j) 
				* 0.25
				+ ((j < is->width() - 1)
				    ? weights->get_pixel(2 * i, 2 * j + 1)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i < is->height() - 1 && j > 0)
				    ? weights->get_pixel(2 * i + 1, 2 * j - 1)
				    * 0.0625
				    : pixel(0, 0, 0))
				+ ((i < is->height() - 1)
				    ? weights->get_pixel(2 * i + 1, 2 * j)
				    * 0.125
				    : pixel(0, 0, 0))
				+ ((i < is->height() && j < is->width() - 1)
				    ? weights->get_pixel(2 * i + 1, 2 * j + 1)
				    * 0.0625
				    : pixel(0, 0, 0)) ) );

			for (int k = 0; k < 3; k++)
				if (!finite(value[k]))
					value[k] = 0;

			is->set_pixel(i, j, value);
		}

		is->_offset = point(_offset[0] * f, _offset[1] * f);

		return is;
	}

	/*
	 * Scale an image definition array by 1/2.
	 *
	 * ALE considers an image definition array as a special kind of image
	 * weight array (typedefs of which should appear below the definition
	 * of this class).  ALE uses nonzero pixel values to mean 'defined' and
	 * zero values to mean 'undefined'.  Through this interpretation, the
	 * image weight array implementation that ALE uses allows image weight
	 * arrays to also serve as image definition arrays.
	 *
	 * Whereas scaling of image weight arrays is not generally obvious in
	 * either purpose or method, ALE requires that image definition arrays
	 * be scalable.  (Note that in the special case where weight is treated
	 * as certainty, using a geometric mean is probably correct.)
	 *
	 * We currently use a geometric mean to implement scaling of
	 * definition arrays.
	 */

	class defined_scale_by_half_threaded : public thread::decompose_domain {
		image *is;
		const image *iu;
	protected:
		void subdomain_algorithm(unsigned int thread,
				int i_min, int i_max, int j_min, int j_max) {

#if 0
			ale_real _0625 = (ale_real) 0.0625;
			ale_real _125  = (ale_real) 0.125;
			ale_real _25   = (ale_real) 0.25;
#endif

			int ui_min = (int) i_min;
			int ui_max = (int) i_max;
			int uj_min = (int) j_min;
			int uj_max = (int) j_max;

			for (int i = ui_min; i < ui_max; i++)
			for (int j = uj_min; j < uj_max; j++) {

#if 0

				/*
				 * Calculate a geometric mean; this approach
				 * may be expensive on some platforms (e.g.,
				 * those without floating-point support in
				 * hardware).
				 */

				pixel value = pixel

				      ( ( ((i > 0 && j > 0) 
					    ? ppow(iu->get_pixel(2 * i - 1, 2 * j - 1), _0625) 
					    : pixel(0, 0, 0))
					* ((i > 0)
					    ? ppow(iu->get_pixel(2 * i - 1, 2 * j), _125)
					    : pixel(0, 0, 0))
					* ((i > 0 && j < is->width() - 1)
					    ? ppow(iu->get_pixel(2 * i - 1, 2 * j + 1), _0625)
					    : pixel(0, 0, 0))
					* ((j > 0)
					    ? ppow(iu->get_pixel(2 * i, 2 * j - 1), _125)
					    : pixel(0, 0, 0))
					* ppow(iu->get_pixel(2 * i, 2 * j), _25) 
					* ((j < is->width() - 1)
					    ? ppow(iu->get_pixel(2 * i, 2 * j + 1), _125)
					    : pixel(0, 0, 0))
					* ((i < is->height() - 1 && j > 0)
					    ? ppow(iu->get_pixel(2 * i + 1, 2 * j - 1), _0625)
					    : pixel(0, 0, 0))
					* ((i < is->height() - 1)
					    ? ppow(iu->get_pixel(2 * i + 1, 2 * j), _125)
					    : pixel(0, 0, 0))
					* ((i < is->height() && j < is->width() - 1)
					    ? ppow(iu->get_pixel(2 * i + 1, 2 * j + 1), _0625)
					    : pixel(0, 0, 0))));
#else

				pixel value = iu->get_pixel(2 * i, 2 * j);

				for (int ii = 2 * i - 1; ii <= 2 * i + 1; ii++)
				for (int jj = 2 * j - 1; jj <= 2 * j + 1; jj++) {
					if (ii < 0
					 || jj < 0
					 || ii > (int) iu->height() - 1
					 || jj > (int) iu->height() - 1)
						continue;

					pixel value2 = iu->get_pixel(ii, jj);

					for (int k = 0; k < 3; k++)
						if (value2[k] < value[k]
						 || !finite(value2[k]))   /* propagate non-finites */
							value[k] = value2[k];
				}

#endif


				for (int k = 0; k < 3; k++)
					if (!finite(value[k]))
						value[k] = 0;

				is->set_pixel(i, j, value);
			}
		}

	public:
		defined_scale_by_half_threaded(image *_is, const image *_iu) 
					: decompose_domain(0, _is->height(),
					                   0, _is->width()) {
			is = _is;	
			iu = _iu;
		}
	};

	image *defined_scale_by_half(const char *name) const {
		ale_pos f = 0.5;

		image *is = scale_generator(
			(int) floor(height() * (double) f), 
			(int) floor(width()  * (double) f), depth(), name);

		assert(is);

		defined_scale_by_half_threaded dsbht(is, this);
		dsbht.run();

		is->_offset = point(_offset[0] * f, _offset[1] * f);

		return is;
	}

	/*
	 * Return an image scaled by some factor != 1.0, using bilinear
	 * interpolation.
	 */
	image *scale(ale_pos f, const char *name, int defined = 0) const {

		/*
		 * We probably don't want to scale images by a factor of 1.0,
		 * or by non-positive values.
		 */
		assert (f != 1.0 && f > 0);

		if (f > 1.0) {
			image *is = scale_generator(
				(int) floor(height() * (double) f), 
				(int) floor(width()  * (double) f), depth(), name);

			assert(is);

			unsigned int i, j, k;

			for (i = 0; i < is->height(); i++)
			for (j = 0; j < is->width(); j++) 
			for (k = 0; k < is->depth(); k++)
				is->set_pixel(i, j,
					get_scaled_bl(point(i, j), f, defined));

			is->_offset = point(_offset[0] * f, _offset[1] * f);

			return is;
		} else if (f == 0.5) {
			if (defined == 0)
				return scale_by_half(name);
			else
				return defined_scale_by_half(name);
		} else {
			image *is = scale(2*f, name, defined);
			image *result = is->scale(0.5, name, defined);
			delete is;
			return result;
		}

	}

	/*
	 * Extend the image area to the top, bottom, left, and right,
	 * initializing the new image areas with black pixels.  Negative values
	 * shrink the image.
	 */
	virtual image *_extend(int top, int bottom, int left, int right) = 0;

	static void extend(image **i, int top, int bottom, int left, int right) {
		image *is = (*i)->_extend(top, bottom, left, right);

		if (is != NULL) {
			delete (*i);
			*i = is;
		}
	}

	/*
	 * Clone 
	 */
	image *clone(const char *name) const {
		image *ic = scale_generator(
			height(), width(), depth(), name);

		assert(ic);

		for (unsigned int i = 0; i < height(); i++)
		for (unsigned int j = 0; j < width();  j++)
			ic->set_pixel(i, j, 
				get_pixel(i, j));


		ic->_offset = _offset;

		ic->_apm_memo = _apm_memo;
		ic->_acm_memo = _acm_memo;
		ic->_accm_memo = _accm_memo;
		ic->_apm = _apm;
		ic->_acm = _acm;
		ic->_accm = _accm;

		return ic;
	}

	/*
	 * Calculate the average (mean) clamped magnitude of a channel across
	 * all pixels in an image.  The magnitude is clamped to the range of 
	 * real inputs.
	 */
	ale_real avg_channel_clamped_magnitude(unsigned int k) const {

		/*
		 * This is a memoized function
		 */

		assert (k < _depth);

		avg_channel_clamped_magnitude_memo();
		return _accm[k];
	}

	pixel avg_channel_clamped_magnitude() const {
		avg_channel_clamped_magnitude_memo();
		return _accm;
	}

	/*
	 * Calculate the average (mean) magnitude of a channel across all
	 * pixels in an image.
	 */
	ale_real avg_channel_magnitude(unsigned int k) const {

		/*
		 * This is a memoized function
		 */

		assert (k < _depth);

		avg_channel_magnitude_memo();
		return _acm[k];
	}

	pixel avg_channel_magnitude() const {
		avg_channel_magnitude_memo();
		return _acm;
	}

	/*
	 * Calculate the average (mean) magnitude of a pixel (where magnitude
	 * is defined as the mean of the channel values).
	 */
	ale_real avg_pixel_magnitude() const {
		unsigned int i, j, k;

		ale_accum accumulator;
		ale_accum divisor = 0;

		if (_apm_memo)
			return _apm;

		_apm_memo = 1;
		accumulator = 0;

		for (i = 0; i < _dimy;  i++)
		for (j = 0; j < _dimx;  j++) {
			pixel value = get_pixel(i, j);

			for (k = 0; k < _depth; k++) 
				if (finite(value[k])) {
					accumulator += value[k];
					divisor += 1;
				}
		}

		accumulator /= divisor;

		_apm = accumulator;

		return _apm;
	}

	virtual ~image() {
	}
};

#endif
