/*******************************************************************************
 * image.cpp
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray is based on the popular DKB raytracer version 2.12.
 * DKBTrace was originally written by David K. Buck.
 * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/base/image/image.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

#include <vector>
#include <algorithm>
#include <cassert>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


// configbase.h must always be the first POV file included within base *.cpp files
#include "base/configbase.h"

#include "base/image/image.h"
#include "base/image/targa.h"
#include "base/image/gif.h"
#include "base/image/png_pov.h"
#include "base/image/jpeg_pov.h"
#include "base/image/iff.h"
#include "base/image/pgm.h"
#include "base/image/ppm.h"
#include "base/image/tiff_pov.h"
#include "base/image/bmp.h"
#include "base/image/openexr.h"
#include "base/image/hdr.h"
#include "base/platformbase.h"
#include "base/safemath.h"

#include "base/povmsgid.h"

#ifdef USE_SYSPROTO
#include "syspovprotobase.h"
#endif

#define CHECK_BOUNDS(x,y) assert(((x) < width) && ((y) < height))

#define ALPHA_OPAQUE                (1.0f)
#define ALPHA_OPAQUE_INT(MAX)       (MAX)

#define FT_OPAQUE                   (0.0f)
#define FT_OPAQUE_INT(MAX)          (0)

#define IS_NONZERO_RGB(r,g,b)       ((r)*(g)*(b) != 0.0f) // TODO FIXME - [CLi] this tests whether *all* channels are nonzero - is this desired?
#define IS_NONZERO_RGB_INT(r,g,b)   ((r)*(g)*(b) != 0)    // TODO FIXME - [CLi] this tests whether *all* channels are nonzero - is this desired?

// this must be the last file included
#include "base/povdebug.h"

namespace pov_base
{

template<class Allocator = std::allocator<bool> >
class BitMapImage : public Image
{
	public:
		BitMapImage(unsigned int w, unsigned int h) :
			Image(w, h, Bit_Map) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		BitMapImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, Bit_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		BitMapImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, Bit_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		BitMapImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, Bit_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		~BitMapImage() { }

		bool IsOpaque() const
		{
			return true;
		}
		bool IsGrayscale() const
		{
			return true;
		}
		bool IsColour() const
		{
			return false;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return 1;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return pixels[x + y * size_t(width)];
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			if(pixels[x + y * size_t(width)] == true)
				return 1.0f;
			else
				return 0.0f;
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			gray = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			red = green = blue = GetGrayValue(x, y);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			red = green = blue = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			red = green = blue = GetGrayValue(x, y);
			filter = transm = FT_OPAQUE;
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = bit;
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = (gray != 0.0f);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = (gray != 0);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = (gray != 0.0f);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = (gray != 0);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = IS_NONZERO_RGB(red, green, blue);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = IS_NONZERO_RGB_INT(red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float, float)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			SetRGBValue(x, y, col.red(), col.green(), col.blue());
		}

		void FillBitValue(bool bit)
		{
			fill(pixels.begin(), pixels.end(), bit);
		}
		void FillGrayValue(float gray)
		{
			FillBitValue(gray != 0.0f);
		}
		void FillGrayValue(unsigned int gray)
		{
			FillBitValue(gray != 0);
		}
		void FillGrayAValue(float gray, float)
		{
			FillBitValue(gray != 0.0f);
		}
		void FillGrayAValue(unsigned int gray, unsigned int)
		{
			FillBitValue(gray != 0);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillBitValue(IS_NONZERO_RGB(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillBitValue(IS_NONZERO_RGB_INT(red, green, blue));
		}
		void FillRGBAValue(float red, float green, float blue, float)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			FillRGBValue(red, green, blue);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			FillRGBValue(red, green, blue);
		}
		void FillRGBFTValue(float red, float green, float blue, float, float)
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			FillRGBValue(red, green, blue);
		}
	private:
		vector<bool, Allocator> pixels;
};

typedef BitMapImage<> MemoryBitMapImage;

template<class Allocator = std::allocator<unsigned char> >
class ColourMapImage : public Image
{
	public:
		ColourMapImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, Colour_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		ColourMapImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, Colour_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		ColourMapImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, Colour_Map, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		~ColourMapImage() { }

		bool IsOpaque() const
		{
			if((colormaptype != RGBAColourMap) && (colormaptype != RGBFTColourMap))
				return true;

			bool transp = false;

			for(size_t i = 0; i < colormap.size(); i++)
			{
				if(colormaptype == RGBAColourMap)
					transp = (colormap[i].filter < 1.0); // with RGBAColourMap, .filter is actually alpha
				else if(colormaptype == RGBFTColourMap)
					transp = (colormap[i].transm > 0.0);
				if(transp == true)
					break;
			}

			if(transp == false)
				return true;

			if(colormaptype == RGBAColourMap)
			{
				for(typename vector<unsigned char, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i++)
				{
					if(colormap[*i].filter < 1.0) // with RGBAColourMap, .filter is actually alpha
						return false;
				}
			}
			else
			{
				for(typename vector<unsigned char, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i++)
				{
					if(colormap[*i].transm > 0.0)
						return false;
				}
			}

			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return true;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return (colormaptype == RGBAColourMap);
		}
		bool HasFilterTransmit() const
		{
			return (colormaptype == RGBFTColourMap);
		}
		unsigned int GetMaxIntValue() const
		{
			return 255;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, filter, transm, alpha;
			switch(colormaptype)
			{
				case RGBColourMap:
					GetRGBValue(x, y, red, green, blue);
					return IS_NONZERO_RGB(red, green, blue);
				case RGBAColourMap:
					// TODO FIXME - [CLi] This takes into account opacity information; other bit-based code doesn't.
					GetRGBAValue(x, y, red, green, blue, alpha);
					return IS_NONZERO_RGB(red, green, blue) && (alpha == ALPHA_OPAQUE);
				case RGBFTColourMap:
					// TODO FIXME - [CLi] This takes into account opacity information; other bit-based code doesn't.
					GetRGBFTValue(x, y, red, green, blue, filter, transm);
					return IS_NONZERO_RGB(red, green, blue) && (filter == FT_OPAQUE) && (transm == FT_OPAQUE);
				default:
					return false;
			}
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, filter, transm, alpha;
			switch(colormaptype)
			{
				case RGBColourMap:
					GetRGBValue(x, y, red, green, blue);
					return RGB2Gray(red, green, blue);
				case RGBAColourMap:
					GetRGBAValue(x, y, red, green, blue, alpha);
					return RGB2Gray(red, green, blue);
				case RGBFTColourMap:
					GetRGBFTValue(x, y, red, green, blue, filter, transm);
					return RGB2Gray(red, green, blue);
				default:
					return 0.0f;
			}
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			float red, green, blue, filter, transm;
			alpha = ALPHA_OPAQUE; // (unless noted otherwise)
			switch(colormaptype)
			{
				case RGBColourMap:
					GetRGBValue(x, y, red, green, blue);
					gray = RGB2Gray(red, green, blue);
					return;
				case RGBAColourMap:
					GetRGBAValue(x, y, red, green, blue, alpha);
					gray = RGB2Gray(red, green, blue);
					return;
				case RGBFTColourMap:
					GetRGBFTValue(x, y, red, green, blue, filter, transm);
					gray = RGB2Gray(red, green, blue);
					alpha = Colour::FTtoA(filter, transm);
					return;
				default:
					gray = 0.0f;
					return;
			}
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			CHECK_BOUNDS(x, y);
			MapEntry e(colormap[pixels[x + y * size_t(width)]]);
			red = e.red;
			green = e.green;
			blue = e.blue;
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			MapEntry e(colormap[pixels[x + y * size_t(width)]]);
			red = e.red;
			green = e.green;
			blue = e.blue;
			alpha = ALPHA_OPAQUE; // (unless noted otherwise)
			switch(colormaptype)
			{
				case RGBAColourMap:
					alpha = e.filter; // with RGBAColourMap, .filter is actually alpha
					return;
				case RGBFTColourMap:
					alpha = Colour::FTtoA(e.filter, e.transm);
					return;
				default:
					return;
			}
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			CHECK_BOUNDS(x, y);
			MapEntry e(colormap[pixels[x + y * size_t(width)]]);
			red = e.red;
			green = e.green;
			blue = e.blue;
			filter = FT_OPAQUE; // (unless noted otherwise)
			transm = FT_OPAQUE; // (unless noted otherwise)
			switch(colormaptype)
			{
				case RGBAColourMap:
					Colour::AtoFT(e.filter, filter, transm); // with RGBAColourMap, .filter is actually alpha
					return;
				case RGBFTColourMap:
					filter = e.filter;
					transm = e.transm;
					return;
				default:
					return;
			}
		}
		unsigned char GetIndexedValue(unsigned int x, unsigned int y)
		{
			CHECK_BOUNDS(x, y);
			return pixels[x + y * size_t(width)];
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = Bit2Map(bit);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = Gray2Map(gray);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = Gray2Map(float(gray) / 255.0);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = GrayA2Map(gray, alpha);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = GrayA2Map(float(gray) / 255.0, float(alpha) / 255.0);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = RGB2Map(red, green, blue);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = RGB2Map(float(red) / 255.0, float(green) / 255.0, float(blue) / 255.0);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = RGBA2Map(red, green, blue, alpha);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = RGBA2Map(float(red) / 255.0, float(green) / 255.0, float(blue) / 255.0, float(alpha) / 255.0);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			CHECK_BOUNDS(x, y);
			// [CLi 2009-09] this was dividing by 255 - which I presume to have been a bug.
			pixels[x + y * size_t(width)] = RGBFT2Map(red, green, blue, filter, transm);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			CHECK_BOUNDS(x, y);
			// [CLi 2009-09] this was dividing by 255 - which I presume to have been a bug.
			pixels[x + y * size_t(width)] = RGBFT2Map(col.red(), col.green(), col.blue(), col.filter(), col.transm());
		}
		void SetIndexedValue(unsigned int x, unsigned int y, unsigned char index)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = index;
		}

		void FillBitValue(bool bit)
		{
			fill(pixels.begin(), pixels.end(), Bit2Map(bit));
		}
		void FillGrayValue(float gray)
		{
			fill(pixels.begin(), pixels.end(), Gray2Map(gray));
		}
		void FillGrayValue(unsigned int gray)
		{
			fill(pixels.begin(), pixels.end(), Gray2Map(float(gray) / 255.0));
		}
		void FillGrayAValue(float gray, float alpha)
		{
			fill(pixels.begin(), pixels.end(), GrayA2Map(gray, alpha));
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			fill(pixels.begin(), pixels.end(), GrayA2Map(float(gray) / 255.0, float(alpha) / 255.0));
		}
		void FillRGBValue(float red, float green, float blue)
		{
			fill(pixels.begin(), pixels.end(), RGB2Map(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			fill(pixels.begin(), pixels.end(), RGB2Map(float(red) / 255.0, float(green) / 255.0, float(blue) / 255.0));
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			fill(pixels.begin(), pixels.end(), RGBA2Map(red, green, blue, alpha));
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			fill(pixels.begin(), pixels.end(), RGBA2Map(float(red) / 255.0, float(green) / 255.0, float(blue) / 255.0, float(alpha) / 255.0));
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			// [CLi 2009-09] this was dividing by 255 - which I presume to have been a bug.
			fill(pixels.begin(), pixels.end(), RGBFT2Map(red, green, blue, filter, transm));
		}
		void FillIndexedValue(unsigned char index)
		{
			fill(pixels.begin(), pixels.end(), index);
		}
	private:
		vector<unsigned char, Allocator> pixels;

		unsigned char Bit2Map(bool bit) const
		{
			if(bit == true)
				return Gray2Map(1.0f);
			else
				return Gray2Map(0.0f);
		}
		unsigned char Gray2Map(float gray) const
		{
			switch(colormaptype)
			{
				case RGBColourMap:
					return FindBestRGB(gray, gray, gray);
				case RGBAColourMap:
					return FindBestRGBA(gray, gray, gray, ALPHA_OPAQUE);
				case RGBFTColourMap:
					return FindBestRGBFT(gray, gray, gray, FT_OPAQUE, FT_OPAQUE);
				default:
					return 0;
			}
		}
		unsigned char GrayA2Map(float gray, float alpha) const
		{
			float filter, transm;
			switch(colormaptype)
			{
				case RGBColourMap:
					return FindBestRGB(gray, gray, gray);
				case RGBAColourMap:
					return FindBestRGBA(gray, gray, gray, alpha);
				case RGBFTColourMap:
					Colour::AtoFT(alpha, filter, transm);
					return FindBestRGBFT(gray, gray, gray, filter, transm);
				default:
					return 0;
			}
		}
		unsigned char RGB2Map(float red, float green, float blue) const
		{
			switch(colormaptype)
			{
				case RGBColourMap:
					return FindBestRGB(red, green, blue);
				case RGBAColourMap:
					return FindBestRGBA(red, green, blue, ALPHA_OPAQUE);
				case RGBFTColourMap:
					return FindBestRGBFT(red, green, blue, FT_OPAQUE, FT_OPAQUE);
				default:
					return 0;
			}
		}
		unsigned char RGBA2Map(float red, float green, float blue, float alpha) const
		{
			float filter, transm;
			switch(colormaptype)
			{
				case RGBColourMap:
					return FindBestRGB(red, green, blue);
				case RGBAColourMap:
					return FindBestRGBA(red, green, blue, alpha);
				case RGBFTColourMap:
					Colour::AtoFT(alpha, filter, transm);
					return FindBestRGBFT(red, green, blue, filter, transm);
				default:
					return 0;
			}
		}
		unsigned char RGBFT2Map(float red, float green, float blue, float filter, float transm) const
		{
			switch(colormaptype)
			{
				case RGBColourMap:
					return FindBestRGB(red, green, blue);
				case RGBAColourMap:
					return FindBestRGBA(red, green, blue, Colour::FTtoA(filter, transm));
				case RGBFTColourMap:
					return FindBestRGBFT(red, green, blue, filter, transm);
				default:
					return 0;
			}
		}
		unsigned char FindBestRGB(float red, float green, float blue) const
		{
			unsigned char best = 0;
			float diff = 3.0f;

			for(size_t i = 0; i < colormap.size(); i++)
			{
				float d(RGB2Gray(fabs(colormap[i].red - red), fabs(colormap[i].green - green), fabs(colormap[i].red - blue)));
				if(d < diff)
				{
					d = diff;
					best = (unsigned char)i;
				}
			}

			return best;
		}
		unsigned char FindBestRGBA(float red, float green, float blue, float alpha) const
		{
			unsigned char best = 0;
			float diff = 3.0f;

			for(size_t i = 0; i < colormap.size(); i++)
			{
				float d((RGB2Gray(fabs(colormap[i].red - red), fabs(colormap[i].green - green), fabs(colormap[i].red - blue)) * 3.0f +
				         fabs(colormap[i].filter - alpha)) / 4.0f);  // with RGBAColourMap, .filter is actually alpha
				if(d < diff)
				{
					d = diff;
					best = (unsigned char)i;
				}
			}

			return best;
		}
		unsigned char FindBestRGBFT(float red, float green, float blue, float filter, float transm) const
		{
			unsigned char best = 0;
			float diff = 3.0f;

			for(size_t i = 0; i < colormap.size(); i++)
			{
				float d((RGB2Gray(fabs(colormap[i].red - red), fabs(colormap[i].green - green), fabs(colormap[i].red - blue)) * 3.0f +
				         fabs(colormap[i].filter - filter) + fabs(colormap[i].transm - transm)) / 5.0f);
				if(d < diff)
				{
					d = diff;
					best = (unsigned char)i;
				}
			}

			return best;
		}
};

typedef ColourMapImage<> MemoryColourMapImage;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class GrayImage : public Image
{
	public:
		GrayImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		GrayImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		GrayImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		GrayImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		~GrayImage() { }

		bool IsOpaque() const
		{
			return true;
		}
		bool IsGrayscale() const
		{
			return true;
		}
		bool IsColour() const
		{
			return false;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return (pixels[x + y * size_t(width)] != 0);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return float(pixels[x + y * size_t(width)]) / float(TMAX);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			gray = float(pixels[x + y * size_t(width)]) / float(TMAX);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			red = green = blue = GetGrayValue(x, y);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			red = green = blue = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			red = green = blue = GetGrayValue(x, y);
			filter = transm = FT_OPAQUE;
		}
		unsigned char GetIndexedValue(unsigned int x, unsigned int y)
		{
			CHECK_BOUNDS(x, y);
			return (unsigned char)(int(pixels[x + y * size_t(width)]) / ((TMAX + 1) >> 8));
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray * float(TMAX));
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray * float(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetGrayValue(x, y, RGB2Gray(red, green, blue));
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetGrayValue(x, y, RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetRGBValue(x, y, col.red(), col.green(), col.blue());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue((unsigned int)(gray * float(TMAX)));
		}
		void FillGrayValue(unsigned int gray)
		{
			fill(pixels.begin(), pixels.end(), T(gray));
		}
		void FillGrayAValue(float gray, float)
		{
			FillGrayValue(gray);
		}
		void FillGrayAValue(unsigned int gray, unsigned int)
		{
			FillGrayValue(gray);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillGrayValue(RGB2Gray(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillGrayValue(RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)));
		}
		void FillRGBAValue(float red, float green, float blue, float)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBFTValue(float red, float green, float blue, float, float)
		{
			FillRGBValue(red, green, blue);
		}
	private:
		vector<T, Allocator> pixels;
};

typedef GrayImage<unsigned char, 255, Image::Gray_Int8> MemoryGray8Image;

typedef GrayImage<unsigned short, 65535, Image::Gray_Int16> MemoryGray16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class GrayAImage : public Image
{
	public:
		GrayAImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		GrayAImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		GrayAImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		GrayAImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		~GrayAImage() { }

		bool IsOpaque() const
		{
			for(typename vector<T, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i += 2)
			{
				if(i[1] < TMAX)
					return false;
			}

			return true;
		}
		bool IsGrayscale() const
		{
			return true;
		}
		bool IsColour() const
		{
			return false;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return true;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			CHECK_BOUNDS(x, y);
			return (pixels[(x + y * size_t(width)) * 2] != 0);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return float(pixels[(x + y * size_t(width)) * 2]) / float(TMAX);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			gray  = float(pixels[(x + y * size_t(width)) * 2])     / float(TMAX);
			alpha = float(pixels[(x + y * size_t(width)) * 2 + 1]) / float(TMAX);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			red = green = blue = GetGrayValue(x, y);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			GetGrayAValue(x, y, red, alpha);
			green = blue = red;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			float alpha;
			GetGrayAValue(x, y, red, alpha);
			green = blue = red;
			Colour::AtoFT(alpha, filter, transm);
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayAValue(x, y, TMAX, ALPHA_OPAQUE_INT(TMAX));
			else
				SetGrayAValue(x, y, (unsigned int)0, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetGrayAValue(x, y, gray, ALPHA_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetGrayAValue(x, y, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 2]     = T(gray * float(TMAX));
			pixels[(x + y * size_t(width)) * 2 + 1] = T(alpha * float(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 2]     = gray;
			pixels[(x + y * size_t(width)) * 2 + 1] = alpha;
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetGrayValue(x, y, RGB2Gray(red, green, blue));
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetGrayValue(x, y, RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			SetGrayAValue(x, y, RGB2Gray(red, green, blue), alpha);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// TODO FIXME - this unnecessarily converts alpha from int to float, requiring it to be converted back to int
			SetGrayAValue(x, y, RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)), float(alpha) / float(TMAX));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			SetGrayAValue(x, y, RGB2Gray(red, green, blue), Colour::FTtoA(filter, transm));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetGrayAValue(x, y, RGB2Gray(col.red(), col.green(), col.blue()), col.FTtoA());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue((unsigned int)(gray * float(TMAX)));
		}
		void FillGrayValue(unsigned int gray)
		{
			FillGrayAValue(gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillGrayAValue(float gray, float alpha)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T g(gray * float(TMAX)), a(alpha * float(TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = g;
				i++;
				*i = a;
			}
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(gray);
				i++;
				*i = T(alpha);
			}
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillGrayValue(RGB2Gray(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillGrayValue(RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)));
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			FillGrayAValue(RGB2Gray(red, green, blue), alpha);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// TODO FIXME - this unnecessarily converts alpha from int to float, requiring it to be converted back to int
			FillGrayAValue(RGB2Gray(float(red) / float(TMAX), float(green) / float(TMAX), float(blue) / float(TMAX)), float(alpha) / float(TMAX));
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			FillGrayAValue(RGB2Gray(red, green, blue), Colour::FTtoA(filter, transm));
		}
	private:
		vector<T, Allocator> pixels;
};

typedef GrayAImage<unsigned char, 255, Image::GrayA_Int8> MemoryGrayA8Image;

typedef GrayAImage<unsigned short, 65535, Image::GrayA_Int16> MemoryGrayA16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class RGBImage : public Image
{
	public:
		RGBImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		RGBImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		RGBImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		RGBImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		~RGBImage() { }

		bool IsOpaque() const
		{
			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue;
			GetRGBValue(x, y, red, green, blue);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue;
			GetRGBValue(x, y, red, green, blue);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			gray = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			CHECK_BOUNDS(x, y);
			red   = float(pixels[(x + y * size_t(width)) * 3])     / float(TMAX);
			green = float(pixels[(x + y * size_t(width)) * 3 + 1]) / float(TMAX);
			blue  = float(pixels[(x + y * size_t(width)) * 3 + 2]) / float(TMAX);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			GetRGBValue(x, y, red, green, blue);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			GetRGBValue(x, y, red, green, blue);
			filter = transm = FT_OPAQUE;
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBValue(x, y, gray, gray, gray);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = gray;
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = T(gray * float(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = gray;
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 3]     = T(red   * float(TMAX));
			pixels[(x + y * size_t(width)) * 3 + 1] = T(green * float(TMAX));
			pixels[(x + y * size_t(width)) * 3 + 2] = T(blue  * float(TMAX));
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 3]     = T(red);
			pixels[(x + y * size_t(width)) * 3 + 1] = T(green);
			pixels[(x + y * size_t(width)) * 3 + 2] = T(blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetRGBValue(x, y, col.red(), col.green(), col.blue());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue((unsigned int)(gray * float(TMAX)));
		}
		void FillGrayValue(unsigned int gray)
		{
			fill(pixels.begin(), pixels.end(), gray);
		}
		void FillGrayAValue(float gray, float)
		{
			FillRGBValue(gray, gray, gray);
		}
		void FillGrayAValue(unsigned int gray, unsigned int)
		{
			FillRGBValue(gray, gray, gray);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T r(red * float(TMAX)), g(green * float(TMAX)), b(blue * float(TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = r;
				i++;
				*i = g;
				i++;
				*i = b;
			}
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(red);
				i++;
				*i = T(green);
				i++;
				*i = T(blue);
			}
		}
		void FillRGBAValue(float red, float green, float blue, float)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBFTValue(float red, float green, float blue, float, float)
		{
			FillRGBValue(red, green, blue);
		}
	private:
		vector<T, Allocator> pixels;
};

typedef RGBImage<unsigned char, 255, Image::RGB_Int8> MemoryRGB8Image;

typedef RGBImage<unsigned short, 65535, Image::RGB_Int16> MemoryRGB16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class RGBAImage : public Image
{
	public:
		RGBAImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		RGBAImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		RGBAImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		RGBAImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		~RGBAImage() { }

		bool IsOpaque() const
		{
			for(typename vector<T, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i += 4)
			{
				if(i[3] < TMAX)
					return false;
			}

			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return true;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			float red, green, blue, alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			float red, green, blue;
			GetRGBAValue(x, y, red, green, blue, alpha);
			gray = RGB2Gray(red, green, blue);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			float alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			red   = float(pixels[(x + y * size_t(width)) * 4])     / float(TMAX);
			green = float(pixels[(x + y * size_t(width)) * 4 + 1]) / float(TMAX);
			blue  = float(pixels[(x + y * size_t(width)) * 4 + 2]) / float(TMAX);
			alpha = float(pixels[(x + y * size_t(width)) * 4 + 3]) / float(TMAX);
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			float alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			Colour::AtoFT(alpha, filter, transm);
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBAValue(x, y, gray, gray, gray, ALPHA_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetRGBAValue(x, y, gray, gray, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			SetRGBAValue(x, y, gray, gray, gray, alpha);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			SetRGBAValue(x, y, gray, gray, gray, alpha);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetRGBAValue(x, y, red, green, blue, ALPHA_OPAQUE);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetRGBAValue(x, y, red, green, blue, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(red   * float(TMAX));
			pixels[(x + y * size_t(width)) * 4 + 1] = T(green * float(TMAX));
			pixels[(x + y * size_t(width)) * 4 + 2] = T(blue  * float(TMAX));
			pixels[(x + y * size_t(width)) * 4 + 3] = T(alpha * float(TMAX));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(red);
			pixels[(x + y * size_t(width)) * 4 + 1] = T(green);
			pixels[(x + y * size_t(width)) * 4 + 2] = T(blue);
			pixels[(x + y * size_t(width)) * 4 + 3] = T(alpha);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			SetRGBAValue(x, y, red, green, blue, Colour::FTtoA(filter, transm));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(col.red());
			pixels[(x + y * size_t(width)) * 4 + 1] = T(col.green());
			pixels[(x + y * size_t(width)) * 4 + 2] = T(col.blue());
			pixels[(x + y * size_t(width)) * 4 + 3] = T(col.FTtoA());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillRGBAValue(gray, gray, gray, ALPHA_OPAQUE);
		}
		void FillGrayValue(unsigned int gray)
		{
			FillRGBAValue(gray, gray, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillGrayAValue(float gray, float alpha)
		{
			FillRGBAValue(gray, gray, gray, alpha);
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			FillRGBAValue(gray, gray, gray, alpha);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillRGBAValue(red, green, blue, ALPHA_OPAQUE);
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillRGBAValue(red, green, blue, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T r(red * float(TMAX)), g(green * float(TMAX)), b(blue * float(TMAX)), a(alpha * float(TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = r;
				i++;
				*i = g;
				i++;
				*i = b;
				i++;
				*i = a;
			}
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(red);
				i++;
				*i = T(green);
				i++;
				*i = T(blue);
				i++;
				*i = T(alpha);
			}
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			FillRGBAValue(red, green, blue, Colour::FTtoA(filter, transm));
		}
	private:
		vector<T, Allocator> pixels;
};

typedef RGBAImage<unsigned char, 255, Image::RGBA_Int8> MemoryRGBA8Image;

typedef RGBAImage<unsigned short, 65535, Image::RGBA_Int16> MemoryRGBA16Image;

template<class PixelContainer = vector<float, std::allocator<float> > >
class RGBFTImage : public Image
{
	public:
		RGBFTImage(unsigned int w, unsigned int h) :
			Image(w, h, RGBFT_Float) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 5u)); FillBitValue(false); }
		RGBFTImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, RGBFT_Float, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 5u)); FillBitValue(false); }
		RGBFTImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, RGBFT_Float, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 5u)); FillBitValue(false); }
		RGBFTImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, RGBFT_Float, m) { pixels.resize(SafeUnsignedProduct<size_t>(w, h, 5u)); FillBitValue(false); }
		~RGBFTImage() { }

		bool IsOpaque() const
		{
			for(typename PixelContainer::const_iterator i(pixels.begin()); i != pixels.end(); i += 5)
			{
				if(i[4] > 0.0f) // TODO FIXME - this ignores filter
					return false;
			}
			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return true;
		}
		bool IsInt() const
		{
			return false;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return false;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return true;
		}
		unsigned int GetMaxIntValue() const
		{
			return 255;
		}
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int)
		{
			return false;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			gray = RGB2Gray(red, green, blue);
			alpha = Colour::FTtoA(filter, transm);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			float filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			float filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			alpha = Colour::FTtoA(filter, transm);
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			CHECK_BOUNDS(x, y);
			red    = pixels[(x + y * size_t(width)) * 5];
			green  = pixels[(x + y * size_t(width)) * 5 + 1];
			blue   = pixels[(x + y * size_t(width)) * 5 + 2];
			filter = pixels[(x + y * size_t(width)) * 5 + 3];
			transm = pixels[(x + y * size_t(width)) * 5 + 4];
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, 1.0f);
			else
				SetGrayValue(x, y, 0.0f);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBFTValue(x, y, gray, gray, gray, FT_OPAQUE, FT_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetGrayValue(x, y, float(gray) / 255.0f);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			SetRGBFTValue(x, y, gray, gray, gray, filter, transm);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			float c = float(gray) / 255.0f;
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			SetRGBFTValue(x, y, c, c, c, filter, transm);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetRGBFTValue(x, y, red, green, blue, FT_OPAQUE, FT_OPAQUE);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetRGBFTValue(x, y, float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, FT_OPAQUE, FT_OPAQUE);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			SetRGBFTValue(x, y, red, green, blue, filter, transm);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			SetRGBFTValue(x, y, float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, filter, transm);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 5]     = red;
			pixels[(x + y * size_t(width)) * 5 + 1] = green;
			pixels[(x + y * size_t(width)) * 5 + 2] = blue;
			pixels[(x + y * size_t(width)) * 5 + 3] = filter;
			pixels[(x + y * size_t(width)) * 5 + 4] = transm;
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 5]     = col.red();
			pixels[(x + y * size_t(width)) * 5 + 1] = col.green();
			pixels[(x + y * size_t(width)) * 5 + 2] = col.blue();
			pixels[(x + y * size_t(width)) * 5 + 3] = col.filter();
			pixels[(x + y * size_t(width)) * 5 + 4] = col.transm();
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(1.0f);
			else
				FillGrayValue(0.0f);
		}
		void FillGrayValue(float gray)
		{
			FillRGBFTValue(gray, gray, gray, FT_OPAQUE, FT_OPAQUE);
		}
		void FillGrayValue(unsigned int gray)
		{
			FillGrayValue(float(gray) / 255.0f);
		}
		void FillGrayAValue(float gray, float alpha)
		{
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			FillRGBFTValue(gray, gray, gray, filter, transm);
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			FillGrayAValue(float(gray) / 255.0f, float(alpha) / 255.0f);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillRGBFTValue(red, green, blue, FT_OPAQUE, FT_OPAQUE);
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillRGBFTValue(float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, FT_OPAQUE, FT_OPAQUE);
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			FillRGBFTValue(red, green, blue, filter, transm);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			FillRGBFTValue(float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, filter, transm);
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			for(typename PixelContainer::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = red;
				i++;
				*i = green;
				i++;
				*i = blue;
				i++;
				*i = filter;
				i++;
				*i = transm;
			}
		}
	private:
		PixelContainer pixels;
};

typedef RGBFTImage<> MemoryRGBFTImage;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class NonlinearGrayImage : public Image
{
	public:
		NonlinearGrayImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		NonlinearGrayImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		NonlinearGrayImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		NonlinearGrayImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h)); FillBitValue(false); }
		~NonlinearGrayImage() { }

		bool IsOpaque() const
		{
			return true;
		}
		bool IsGrayscale() const
		{
			return true;
		}
		bool IsColour() const
		{
			return false;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return true;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr& g, unsigned int max)
		{
			if (max != TMAX) return false;
			if (!GammaCurve::IsNeutral(gamma)) return !g;
			gamma.swap(g);
			gammaLUT = gamma->GetLookupTable(TMAX);
			return true;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return (pixels[x + y * size_t(width)] != 0);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return gammaLUT[pixels[x + y * size_t(width)]];
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			gray = gammaLUT[pixels[x + y * size_t(width)]];
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			red = green = blue = GetGrayValue(x, y);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			red = green = blue = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			red = green = blue = GetGrayValue(x, y);
			filter = transm = FT_OPAQUE;
		}
		unsigned char GetIndexedValue(unsigned int x, unsigned int y)
		{
			CHECK_BOUNDS(x, y);
			return (unsigned char)(int(pixels[x + y * size_t(width)]) / ((TMAX + 1) >> 8));
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = IntEncode(gamma, gray, TMAX);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = IntEncode(gamma, gray, TMAX);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width)] = T(gray);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetGrayValue(x, y, RGB2Gray(red, green, blue));
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetGrayValue(x, y, RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetRGBValue(x, y, col.red(), col.green(), col.blue());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue(IntEncode(gamma, gray, TMAX));
		}
		void FillGrayValue(unsigned int gray)
		{
			fill(pixels.begin(), pixels.end(), T(gray));
		}
		void FillGrayAValue(float gray, float)
		{
			FillGrayValue(gray);
		}
		void FillGrayAValue(unsigned int gray, unsigned int)
		{
			FillGrayValue(gray);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillGrayValue(RGB2Gray(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillGrayValue(RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]));
		}
		void FillRGBAValue(float red, float green, float blue, float)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBFTValue(float red, float green, float blue, float, float)
		{
			FillRGBValue(red, green, blue);
		}
	private:
		vector<T, Allocator> pixels;
		GammaCurvePtr gamma;
		const float* gammaLUT;
};

typedef NonlinearGrayImage<unsigned char, 255, Image::Gray_Gamma8> MemoryNonlinearGray8Image;

typedef NonlinearGrayImage<unsigned short, 65535, Image::Gray_Gamma16> MemoryNonlinearGray16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class NonlinearGrayAImage : public Image
{
	public:
		NonlinearGrayAImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		NonlinearGrayAImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		NonlinearGrayAImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		NonlinearGrayAImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 2u)); FillBitValue(false); }
		~NonlinearGrayAImage() { }

		bool IsOpaque() const
		{
			for(typename vector<T, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i += 2)
			{
				if(i[1] < TMAX)
					return false;
			}

			return true;
		}
		bool IsGrayscale() const
		{
			return true;
		}
		bool IsColour() const
		{
			return false;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return true;
		}
		bool HasAlphaChannel() const
		{
			return true;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr& g, unsigned int max)
		{
			if (max != TMAX) return false;
			if (!GammaCurve::IsNeutral(gamma)) return !g;
			gamma.swap(g);
			gammaLUT = gamma->GetLookupTable(TMAX);
			return true;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			CHECK_BOUNDS(x, y);
			return (pixels[(x + y * size_t(width)) * 2] != 0);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			CHECK_BOUNDS(x, y);
			return gammaLUT[pixels[(x + y * size_t(width)) * 2]];
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			gray  = gammaLUT[pixels[(x + y * size_t(width)) * 2]];
			alpha =          pixels[(x + y * size_t(width)) * 2 + 1] / float(TMAX);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			red = green = blue = GetGrayValue(x, y);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			GetGrayAValue(x, y, red, alpha);
			green = blue = red;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			float alpha;
			GetGrayAValue(x, y, red, alpha);
			green = blue = red;
			Colour::AtoFT(alpha, filter, transm);
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayAValue(x, y, TMAX, ALPHA_OPAQUE_INT(TMAX));
			else
				SetGrayAValue(x, y, (unsigned int)0, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetGrayAValue(x, y, gray, ALPHA_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetGrayAValue(x, y, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 2]     = IntEncode(gamma, gray, TMAX);
			pixels[(x + y * size_t(width)) * 2 + 1] = T(alpha * float(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 2]     = gray;
			pixels[(x + y * size_t(width)) * 2 + 1] = alpha;
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetGrayValue(x, y, RGB2Gray(red, green, blue));
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			// not really pretty here, but we're doing color math, so we need to decode and re-encode
			SetGrayValue(x, y, RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			SetGrayAValue(x, y, RGB2Gray(red, green, blue), alpha);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// not really pretty here, but we're doing color math, so we need to decode and re-encode
			// TODO FIXME - this unnecessarily converts alpha from int to float, requiring it to be converted back to int
			SetGrayAValue(x, y, RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]), float(alpha) / float(TMAX));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			SetGrayAValue(x, y, RGB2Gray(red, green, blue), Colour::FTtoA(filter, transm));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetGrayAValue(x, y, RGB2Gray(col.red(), col.green(), col.blue()), col.FTtoA());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue(IntEncode(gamma, gray, TMAX));
		}
		void FillGrayValue(unsigned int gray)
		{
			FillGrayAValue(gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillGrayAValue(float gray, float alpha)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T g(IntEncode(gamma, gray, TMAX)), a(IntEncode(alpha, TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = g;
				i++;
				*i = a;
			}
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(gray);
				i++;
				*i = T(alpha);
			}
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillGrayValue(RGB2Gray(red, green, blue));
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			// not really pretty here, but we're doing color math, so we need to decode and re-encode
			FillGrayValue(RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]));
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			FillGrayAValue(RGB2Gray(red, green, blue), alpha);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// not really pretty here, but we're doing color math, so we need to decode and re-encode
			// TODO FIXME - this unnecessarily converts alpha from int to float, requiring it to be converted back to int
			FillGrayAValue(RGB2Gray(gammaLUT[red], gammaLUT[green], gammaLUT[blue]), float(alpha) / float(TMAX));
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			FillGrayAValue(RGB2Gray(red, green, blue), Colour::FTtoA(filter, transm));
		}
	private:
		vector<T, Allocator> pixels;
		GammaCurvePtr gamma;
		const float* gammaLUT;
};

typedef NonlinearGrayAImage<unsigned char, 255, Image::GrayA_Gamma8> MemoryNonlinearGrayA8Image;

typedef NonlinearGrayAImage<unsigned short, 65535, Image::GrayA_Gamma16> MemoryNonlinearGrayA16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class NonlinearRGBImage : public Image
{
	public:
		NonlinearRGBImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		NonlinearRGBImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		NonlinearRGBImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		NonlinearRGBImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 3u)); FillBitValue(false); }
		~NonlinearRGBImage() { }

		bool IsOpaque() const
		{
			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return true;
		}
		bool HasAlphaChannel() const
		{
			return false;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr& g, unsigned int max)
		{
			if (max != TMAX) return false;
			if (!GammaCurve::IsNeutral(gamma)) return !g;
			gamma.swap(g);
			gammaLUT = gamma->GetLookupTable(TMAX);
			return true;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue;
			GetRGBValue(x, y, red, green, blue);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue;
			GetRGBValue(x, y, red, green, blue);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			gray = GetGrayValue(x, y);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			CHECK_BOUNDS(x, y);
			red   = gammaLUT[pixels[(x + y * size_t(width)) * 3]];
			green = gammaLUT[pixels[(x + y * size_t(width)) * 3 + 1]];
			blue  = gammaLUT[pixels[(x + y * size_t(width)) * 3 + 2]];
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			GetRGBValue(x, y, red, green, blue);
			alpha = ALPHA_OPAQUE;
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			GetRGBValue(x, y, red, green, blue);
			filter = transm = FT_OPAQUE;
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBValue(x, y, gray, gray, gray);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = gray;
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = IntEncode(gamma, gray, TMAX);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int)
		{
			CHECK_BOUNDS(x, y);
			pixels[x + y * size_t(width) * 3]     =
			pixels[x + y * size_t(width) * 3 + 1] =
			pixels[x + y * size_t(width) * 3 + 2] = gray;
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 3]     = IntEncode(gamma, red,   TMAX);
			pixels[(x + y * size_t(width)) * 3 + 1] = IntEncode(gamma, green, TMAX);
			pixels[(x + y * size_t(width)) * 3 + 2] = IntEncode(gamma, blue,  TMAX);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 3]     = T(red);
			pixels[(x + y * size_t(width)) * 3 + 1] = T(green);
			pixels[(x + y * size_t(width)) * 3 + 2] = T(blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float, float)
		{
			SetRGBValue(x, y, red, green, blue);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			SetRGBValue(x, y, col.red(), col.green(), col.blue());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillGrayValue((unsigned int)(IntEncode(gamma, gray, TMAX)));
		}
		void FillGrayValue(unsigned int gray)
		{
			fill(pixels.begin(), pixels.end(), gray);
		}
		void FillGrayAValue(float gray, float)
		{
			FillRGBValue(gray, gray, gray);
		}
		void FillGrayAValue(unsigned int gray, unsigned int)
		{
			FillRGBValue(gray, gray, gray);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T r(IntEncode(gamma, red, TMAX)), g(IntEncode(gamma, green, TMAX)), b(IntEncode(gamma, blue, TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = r;
				i++;
				*i = g;
				i++;
				*i = b;
			}
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(red);
				i++;
				*i = T(green);
				i++;
				*i = T(blue);
			}
		}
		void FillRGBAValue(float red, float green, float blue, float)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int)
		{
			FillRGBValue(red, green, blue);
		}
		void FillRGBFTValue(float red, float green, float blue, float, float)
		{
			FillRGBValue(red, green, blue);
		}
	private:
		vector<T, Allocator> pixels;
		GammaCurvePtr gamma;
		const float* gammaLUT;
};

typedef NonlinearRGBImage<unsigned char, 255, Image::RGB_Gamma8> MemoryNonlinearRGB8Image;

typedef NonlinearRGBImage<unsigned short, 65535, Image::RGB_Gamma16> MemoryNonlinearRGB16Image;

template<typename T, unsigned int TMAX, int IDT, class Allocator = std::allocator<T> >
class NonlinearRGBAImage : public Image
{
	public:
		NonlinearRGBAImage(unsigned int w, unsigned int h) :
			Image(w, h, ImageDataType(IDT)), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		NonlinearRGBAImage(unsigned int w, unsigned int h, const vector<RGBMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		NonlinearRGBAImage(unsigned int w, unsigned int h, const vector<RGBAMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		NonlinearRGBAImage(unsigned int w, unsigned int h, const vector<RGBFTMapEntry>& m) :
			Image(w, h, ImageDataType(IDT), m), gamma(NeutralGammaCurve::Get()) { gammaLUT = gamma->GetLookupTable(TMAX); pixels.resize(SafeUnsignedProduct<size_t>(w, h, 4u)); FillBitValue(false); }
		~NonlinearRGBAImage() { }

		bool IsOpaque() const
		{
			for(typename vector<T, Allocator>::const_iterator i(pixels.begin()); i != pixels.end(); i += 4)
			{
				if(i[3] < TMAX)
					return false;
			}

			return true;
		}
		bool IsGrayscale() const
		{
			return false;
		}
		bool IsColour() const
		{
			return true;
		}
		bool IsFloat() const
		{
			return false;
		}
		bool IsInt() const
		{
			return true;
		}
		bool IsIndexed() const
		{
			return false;
		}
		bool IsGammaEncoded() const
		{
			return true;
		}
		bool HasAlphaChannel() const
		{
			return true;
		}
		bool HasFilterTransmit() const
		{
			return false;
		}
		unsigned int GetMaxIntValue() const
		{
			return TMAX;
		}
		bool TryDeferDecoding(GammaCurvePtr& g, unsigned int max)
		{
			if (max != TMAX) return false;
			if (!GammaCurve::IsNeutral(gamma)) return !g;
			gamma.swap(g);
			gammaLUT = gamma->GetLookupTable(TMAX);
			return true;
		}

		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			float red, green, blue, alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			float red, green, blue;
			GetRGBAValue(x, y, red, green, blue, alpha);
			gray = RGB2Gray(red, green, blue);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			float alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			CHECK_BOUNDS(x, y);
			red   = gammaLUT[pixels[(x + y * size_t(width)) * 4]];
			green = gammaLUT[pixels[(x + y * size_t(width)) * 4 + 1]];
			blue  = gammaLUT[pixels[(x + y * size_t(width)) * 4 + 2]];
			alpha =    float(pixels[(x + y * size_t(width)) * 4 + 3]) / float(TMAX);
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			float alpha;
			GetRGBAValue(x, y, red, green, blue, alpha);
			Colour::AtoFT(alpha, filter, transm);
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, TMAX);
			else
				SetGrayValue(x, y, (unsigned int)0);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBAValue(x, y, gray, gray, gray, ALPHA_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetRGBAValue(x, y, gray, gray, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			SetRGBAValue(x, y, gray, gray, gray, alpha);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			SetRGBAValue(x, y, gray, gray, gray, alpha);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetRGBAValue(x, y, red, green, blue, ALPHA_OPAQUE);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetRGBAValue(x, y, red, green, blue, ALPHA_OPAQUE_INT(TMAX));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(IntEncode(gamma, red,   TMAX));
			pixels[(x + y * size_t(width)) * 4 + 1] = T(IntEncode(gamma, green, TMAX));
			pixels[(x + y * size_t(width)) * 4 + 2] = T(IntEncode(gamma, blue,  TMAX));
			pixels[(x + y * size_t(width)) * 4 + 3] = T(IntEncode(       alpha, TMAX));
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(red);
			pixels[(x + y * size_t(width)) * 4 + 1] = T(green);
			pixels[(x + y * size_t(width)) * 4 + 2] = T(blue);
			pixels[(x + y * size_t(width)) * 4 + 3] = T(alpha);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			SetRGBAValue(x, y, red, green, blue, Colour::FTtoA(filter, transm));
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			CHECK_BOUNDS(x, y);
			pixels[(x + y * size_t(width)) * 4]     = T(col.red());
			pixels[(x + y * size_t(width)) * 4 + 1] = T(col.green());
			pixels[(x + y * size_t(width)) * 4 + 2] = T(col.blue());
			pixels[(x + y * size_t(width)) * 4 + 3] = T(col.FTtoA());
		}

		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(TMAX);
			else
				FillGrayValue((unsigned int)0);
		}
		void FillGrayValue(float gray)
		{
			FillRGBAValue(gray, gray, gray, ALPHA_OPAQUE);
		}
		void FillGrayValue(unsigned int gray)
		{
			FillRGBAValue(gray, gray, gray, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillGrayAValue(float gray, float alpha)
		{
			FillRGBAValue(gray, gray, gray, alpha);
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			FillRGBAValue(gray, gray, gray, alpha);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillRGBAValue(red, green, blue, ALPHA_OPAQUE);
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillRGBAValue(red, green, blue, ALPHA_OPAQUE_INT(TMAX));
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			// [CLi 2009-09] this was dividing by float(TMAX) - which I presume to have been a bug.
			T r(IntEncode(gamma, red, TMAX)), g(IntEncode(gamma, green, TMAX)), b(IntEncode(gamma, blue, TMAX)), a(IntEncode(alpha, TMAX));
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = r;
				i++;
				*i = g;
				i++;
				*i = b;
				i++;
				*i = a;
			}
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			for(typename vector<T, Allocator>::iterator i(pixels.begin()); i != pixels.end(); i++)
			{
				*i = T(red);
				i++;
				*i = T(green);
				i++;
				*i = T(blue);
				i++;
				*i = T(alpha);
			}
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			FillRGBAValue(red, green, blue, Colour::FTtoA(filter, transm));
		}
	private:
		vector<T, Allocator> pixels;
		GammaCurvePtr gamma;
		const float* gammaLUT;
};

typedef NonlinearRGBAImage<unsigned char, 255, Image::RGBA_Gamma8> MemoryNonlinearRGBA8Image;

typedef NonlinearRGBAImage<unsigned short, 65535, Image::RGBA_Gamma16> MemoryNonlinearRGBA16Image;

// sample basic file-based pixel container. not very efficient.
// it is expected that for performance reasons, platforms will provide their own specific
// implementation of this; hence, this should be considered only a fall-back default.

// [JG] That code is hostile to the system, as the file seek are just passed directly.
// (and there is a lot, out of sequence!)
// On the render (1st write), the cache will always miss, but as post-process might access more than once to
// this container, it is mandatory to still read the file every time.
// Measurement about the small read cache (compared to the alternative) show no significant delta in performance
// to read the contained data into a PNG.
// (it's a linear read, the system is able to anticipate it, so we are fine!)
class FileBackedPixelContainer
{
	public:
		typedef long size_type;
		enum
		{
			RED    = 0,
			GREEN  = 1,
			BLUE   = 2,
			FILTER = 3,
			TRANSM = 4
		};
		class pixel_type
		{
			public:
				pixel_type()
				{
					elements[RED] = 0.0;
					elements[GREEN] = 0.0;
					elements[BLUE] = 0.0;
					elements[FILTER] = 0.0;
					elements[TRANSM] = 0.0;
				}
				pixel_type(const COLC *vals)
				{
					memcpy(elements, vals, sizeof(elements));
				}
				pixel_type(float r, float g, float b, float f, float t)
				{
					elements[RED] = r;
					elements[GREEN] = g;
					elements[BLUE] = b;
					elements[FILTER] = f;
					elements[TRANSM] = t;
				}
				~pixel_type() {}
				operator COLC *() { return elements; }
				operator const COLC *() const { return elements; }
				bool operator==(float val) const { return elements[RED] == val && elements[GREEN] == val && elements[BLUE] == val && elements[FILTER] == val && elements[TRANSM] == val; }
				bool operator!=(float val) const { return !(*this == val); }

			protected:
				COLC elements[5];
		};

		FileBackedPixelContainer(size_type width, size_type height, size_type bs):
			m_File(-1), m_Width(width), m_Height(height), m_xPos(0), m_yPos(0), m_Dirty(false), m_Path(POV_PLATFORM_BASE.CreateTemporaryFile())
		{
			if ((m_File = open(UCS2toASCIIString(m_Path).c_str(), O_CREAT | O_RDWR | O_TRUNC, S_IRUSR | S_IWUSR)) == -1)
				throw POV_EXCEPTION(kCannotOpenFileErr, "Cannot open backing file for intermediate image storage.");
			m_Blocksize = bs;
			m_Buffer.resize(m_Blocksize);
			// write extra data to create the big file and help 3rd party reader
			POV_LONG pos;
			// NB: The following use of SafeUnsignedProduct also safeguards later coputations of
			// pixel positions within the file, as long as x and y coordinates are sane
			pos = SafeUnsignedProduct<POV_LONG>(m_Width, m_Height);
			if ( pos% m_Blocksize)
			{ /* issue: the block would overlap the end of file */
				pos /= m_Blocksize;
				pos++;
				pos = SafeUnsignedProduct<POV_LONG>(pos, m_Blocksize);
			}
			/* else fine case: the boundary of block match the boundary of pixels in file */
			pos = SafeUnsignedProduct<POV_LONG>(pos, sizeof(pixel_type));
			size_type meta[3];
			meta[0] = sizeof(pixel_type);
			meta[1] = m_Width;
			meta[2] = m_Height;
			if (lseek64(m_File, pos, SEEK_SET) != pos)
				throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file write/seek failed at creation.");
			if (write(m_File, &meta[0], (int) sizeof(size_type)*3) != (sizeof(size_type)*3))
				throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file write failed at creation.");
			// m_Committed.resize(width * height / m_Blocksize);
		}

		virtual ~FileBackedPixelContainer()
		{
			if (m_File != -1)
			{
				Flush();
				close(m_File);
			}
 			if (m_Path.empty() == false)
 			{
 				// if shutdown has been delayed, by the time we reach here, the platform base
 				// may no longer be valid (see crashdump #77 for an example of this). we need
 				// to take the address of the reference to see if it's now NULL before we use it.
 				PlatformBase *pb(&POV_PLATFORM_BASE);
 				if (pb != NULL)
 					pb->DeleteTemporaryFile(m_Path);
 			}
		}

		void Flush(void)
		{
			WriteCurrentBlock();
		}

		void SetPixel(size_type x, size_type y, const pixel_type& pixel)
		{
			WritePixel(x, y, pixel);
			NextPixel();
		}

		void SetPixel(size_type x, size_type y, float red, float green, float blue, float filter, float transm)
		{
			pixel_type pixel;

			pixel[RED] = red;
			pixel[GREEN] = green;
			pixel[BLUE] = blue;
			pixel[FILTER] = filter;
			pixel[TRANSM] = transm;
			WritePixel(x, y, pixel);
		}

		void SetPixel(float red, float green, float blue, float filter, float transm)
		{
			SetPixel(m_xPos, m_yPos, red, green, blue, filter, transm);
			NextPixel();
		}

		void GetPixel(pixel_type& pixel)
		{
			ReadPixel(m_xPos, m_yPos, pixel);
			NextPixel();
		}

		void GetPixel(float& red, float& green, float& blue, float& filter, float& transm)
		{
			pixel_type pixel;

			GetPixel(pixel);	// advances NextPixel
			red = pixel[RED];
			green = pixel[GREEN];
			blue = pixel[BLUE];
			filter = pixel[FILTER];
			transm = pixel[TRANSM];
		}

		void GetPixel(size_type x, size_type y, pixel_type& pixel)
		{
			SetPos(x, y);
			ReadPixel(x, y, pixel);
		}

		void GetPixel(size_type x, size_type y, float& red, float& green, float& blue, float& filter, float& transm)
		{
			pixel_type pixel;

			GetPixel(x, y, pixel);	// sets Position
			red = pixel[RED];
			green = pixel[GREEN];
			blue = pixel[BLUE];
			filter = pixel[FILTER];
			transm = pixel[TRANSM];
		}

		/* void ClearCache(const pixel_type& pixel = pixel_type())
		{
			for (int i = 0; i < m_Width; i++)
				memcpy(&m_Buffer[i], &pixel, sizeof(pixel));
		} */

		void FillLine(size_type y, const pixel_type& pixel)
		{
			// bool notBlank(pixel != 0.0);

			for (size_type x = 0; x < m_Width; x++)
				// if (notBlank)
					WritePixel(x, y, pixel);
		}

		void Fill(const pixel_type& pixel)
		{
			for (size_type y = 0; y < m_Height; y++)
				FillLine(y, pixel);
		}

		void Fill(float red, float green, float blue, float filter, float transm)
		{
			pixel_type pixel(red, green, blue, filter, transm);
			Fill(pixel);
		}

	protected:
		int                 m_File;
		bool                m_Dirty;
		size_type           m_Blocksize;
		POV_LONG            m_CurrentBlock;
		size_type           m_Width;
		size_type           m_Height;
		size_type           m_xPos;
		size_type           m_yPos;
		UCS2String          m_Path;
		//vector<bool>        m_Committed;
		vector<pixel_type>  m_Buffer;

		void SetPos(size_type x, size_type y, bool cache = true)
		{
			if (x < 0 || x >= m_Width || y < 0 || y >= m_Height)
				throw POV_EXCEPTION(kFileDataErr, "Invalid coordinates in intermediate image file seek.");
			if (y == m_yPos)
			{
				m_xPos = x;
				return;
			}
			m_xPos = x;
			m_yPos = y;
		}

		void NextPixel(void)
		{
			if (m_xPos == m_Width - 1)
			{
				if (m_yPos == m_Height - 1)
					return;
			}
			else
				m_xPos++;
		}

		void ReadPixel(size_type x, size_type y, pixel_type& pixel)
		{
			POV_LONG pos, block = (y * (POV_LONG)(m_Width) + x) / m_Blocksize;

			if (block != m_CurrentBlock) {
				WriteCurrentBlock();
#if 0
				if (m_Committed[block] == false) {
					COLC pixel[5] = {0.0, 0.0, 0.0, 0.0, 0.0};
					for (size_type i = 0; i < m_Blocksize; i++)
						memcpy(&m_Buffer[i], pixel, sizeof(pixel));
					m_CurrentBlock = block;
					return;
				}
#endif
				pos = block * sizeof(pixel_type) * m_Blocksize;
				int chunk = sizeof(pixel_type) * m_Blocksize;
				if (lseek64(m_File, pos, SEEK_SET) != pos)
					throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file read/seek failed.");
				int bytes = read(m_File, &m_Buffer[0], chunk);
				if (bytes != (sizeof(pixel_type) * m_Blocksize))
					throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file read failed.");
				m_CurrentBlock = block;
			}
			memcpy(&pixel, m_Buffer[(y * (POV_LONG)(m_Width) + x) % m_Blocksize], sizeof(pixel));
		}
#if 0
		bool BlockCommitted(size_type x, size_type y)
		{
			POV_LONG block = (y * POV_LONG(m_Width) + x) / m_Blocksize;

			return(m_Committed[block]);
		}
#endif
		void WriteCurrentBlock()
		{
			POV_LONG pos;

			if (m_Dirty) {
				pos = m_CurrentBlock * sizeof(pixel_type) * m_Blocksize;
				if (lseek64(m_File, pos, SEEK_SET) != pos)
					throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file write/seek failed.");
				if (write(m_File, &m_Buffer[0], (int) sizeof(pixel_type) * m_Blocksize) != (sizeof(pixel_type) * m_Blocksize))
					throw POV_EXCEPTION(kFileDataErr, "Intermediate image storage backing file write failed.");
			//	m_Committed[m_CurrentBlock] = true;
				m_Dirty = false;
			}
		}

		void WritePixel(size_type x, size_type y, const pixel_type& pixel)
		{
			pixel_type dummy;

			ReadPixel(x, y, dummy);
			memcpy(m_Buffer[(y * (POV_LONG)(m_Width) + x) % m_Blocksize], &pixel, sizeof(pixel));
			m_Dirty = true;
		}

	private:
		// not available
		FileBackedPixelContainer(void) {}
};

class FileRGBFTImage : public Image
{
	public:
		typedef FileBackedPixelContainer::pixel_type pixel_type;

		FileRGBFTImage(unsigned int w, unsigned int h, unsigned int bs): Image(w, h, RGBFT_Float), pixels(width, height, bs) { }
		~FileRGBFTImage() { }

		bool IsGrayscale() const { return false; }
		bool IsColour() const { return true; }
		bool IsFloat() const { return true; }
		bool IsInt() const { return false; }
		bool IsIndexed() const { return false; }
		bool IsGammaEncoded() const { return false; }
		bool HasAlphaChannel() const { return false; }
		bool HasFilterTransmit() const { return true; }
		unsigned int GetMaxIntValue() const { return 255; }
		void SetEncodingGamma(GammaCurvePtr gamma) { ; }
		bool TryDeferDecoding(GammaCurvePtr&, unsigned int) { return false; }
		bool IsOpaque() const { throw POV_EXCEPTION(kUncategorizedError, "Internal error: IsOpaque() not supported in FileRGBFTImage"); }
		bool GetBitValue(unsigned int x, unsigned int y) const
		{
			// TODO FIXME - [CLi] This ignores opacity information; other bit-based code doesn't.
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			return IS_NONZERO_RGB(red, green, blue);
		}
		float GetGrayValue(unsigned int x, unsigned int y) const
		{
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			return RGB2Gray(red, green, blue);
		}
		void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const
		{
			float red, green, blue, filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			gray = RGB2Gray(red, green, blue);
			alpha = Colour::FTtoA(filter, transm);
		}
		void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const
		{
			float filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
		}
		void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const
		{
			float filter, transm;
			GetRGBFTValue(x, y, red, green, blue, filter, transm);
			alpha = Colour::FTtoA(filter, transm);
		}
		void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const
		{
			CHECK_BOUNDS(x, y);
			pixels.GetPixel(x, y, red, green, blue, filter, transm);
		}

		void SetBitValue(unsigned int x, unsigned int y, bool bit)
		{
			if(bit == true)
				SetGrayValue(x, y, 1.0f);
			else
				SetGrayValue(x, y, 0.0f);
		}
		void SetGrayValue(unsigned int x, unsigned int y, float gray)
		{
			SetRGBFTValue(x, y, gray, gray, gray, FT_OPAQUE, FT_OPAQUE);
		}
		void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray)
		{
			SetGrayValue(x, y, float(gray) / 255.0f);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			SetRGBFTValue(x, y, gray, gray, gray, filter, transm);
		}
		void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float c = float(gray) / 255.0f;
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			SetRGBFTValue(x, y, c, c, c, filter, transm);
		}
		void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue)
		{
			SetRGBFTValue(x, y, red, green, blue, FT_OPAQUE, FT_OPAQUE);
		}
		void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue)
		{
			SetRGBFTValue(x, y, float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, FT_OPAQUE, FT_OPAQUE);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			SetRGBFTValue(x, y, red, green, blue, filter, transm);
		}
		void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			SetRGBFTValue(x, y, float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, filter, transm);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm)
		{
			CHECK_BOUNDS(x, y);
			pixels.SetPixel(x, y, red, green, blue, filter, transm);
		}
		void SetRGBFTValue(unsigned int x, unsigned int y, const Colour& col)
		{
			CHECK_BOUNDS(x, y);
			pixels.SetPixel(x, y, *col);
		}
		void FillBitValue(bool bit)
		{
			if(bit == true)
				FillGrayValue(1.0f);
			else
				FillGrayValue(0.0f);
		}
		void FillGrayValue(float gray)
		{
			FillRGBFTValue(gray, gray, gray, FT_OPAQUE, FT_OPAQUE);
		}
		void FillGrayValue(unsigned int gray)
		{
			FillGrayValue(float(gray) / 255.0f);
		}
		void FillGrayAValue(float gray, float alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			FillRGBFTValue(gray, gray, gray, filter, transm);
		}
		void FillGrayAValue(unsigned int gray, unsigned int alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			FillGrayAValue(float(gray) / 255.0f, float(alpha) / 255.0f);
		}
		void FillRGBValue(float red, float green, float blue)
		{
			FillRGBFTValue(red, green, blue, FT_OPAQUE, FT_OPAQUE);
		}
		void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue)
		{
			FillRGBFTValue(float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, FT_OPAQUE, FT_OPAQUE);
		}
		void FillRGBAValue(float red, float green, float blue, float alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(alpha, filter, transm);
			FillRGBFTValue(red, green, blue, filter, transm);
		}
		void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha)
		{
			// TODO - should alpha be converted to filter and transm? [trf]
			float filter, transm;
			Colour::AtoFT(float(alpha) / 255.0f, filter, transm);
			FillRGBFTValue(float(red) / 255.0f, float(green) / 255.0f, float(blue) / 255.0f, filter, transm);
		}
		void FillRGBFTValue(float red, float green, float blue, float filter, float transm)
		{
			pixels.Fill(red, green, blue, filter, transm);
		}

	protected:
		mutable FileBackedPixelContainer pixels;
};

void RGBMap2RGBAMap(const vector<Image::RGBMapEntry>& m, vector<Image::RGBAMapEntry>& n);
void RGBMap2RGBFTMap(const vector<Image::RGBMapEntry>& m, vector<Image::RGBFTMapEntry>& n);
void RGBAMap2RGBMap(const vector<Image::RGBAMapEntry>& m, vector<Image::RGBMapEntry>& n);
void RGBAMap2RGBFTMap(const vector<Image::RGBAMapEntry>& m, vector<Image::RGBFTMapEntry>& n);
void RGBFTMap2RGBMap(const vector<Image::RGBFTMapEntry>& m, vector<Image::RGBMapEntry>& n);
void RGBFTMap2RGBAMap(const vector<Image::RGBFTMapEntry>& m, vector<Image::RGBAMapEntry>& n);

void RGBMap2RGBAMap(const vector<Image::RGBMapEntry>& m, vector<Image::RGBAMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
		n.push_back(Image::RGBAMapEntry(i->red, i->green, i->blue, ALPHA_OPAQUE));
}

void RGBMap2RGBFTMap(const vector<Image::RGBMapEntry>& m, vector<Image::RGBFTMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
		n.push_back(Image::RGBFTMapEntry(i->red, i->green, i->blue, FT_OPAQUE, FT_OPAQUE));
}

void RGBAMap2RGBMap(const vector<Image::RGBAMapEntry>& m, vector<Image::RGBMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBAMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
		n.push_back(Image::RGBMapEntry(i->red, i->green, i->blue));
}

void RGBAMap2RGBFTMap(const vector<Image::RGBAMapEntry>& m, vector<Image::RGBFTMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBAMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
	{
		float filter, transm;
		Colour::AtoFT(i->alpha, filter, transm);
		n.push_back(Image::RGBFTMapEntry(i->red, i->green, i->blue, filter, transm));
	}
}

void RGBFTMap2RGBMap(const vector<Image::RGBFTMapEntry>& m, vector<Image::RGBMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBFTMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
		n.push_back(Image::RGBMapEntry(i->red, i->green, i->blue));
}

void RGBFTMap2RGBAMap(const vector<Image::RGBFTMapEntry>& m, vector<Image::RGBAMapEntry>& n)
{
	n.clear();
	n.reserve(m.size());

	for(vector<Image::RGBFTMapEntry>::const_iterator i(m.begin()); i != m.end(); i++)
		n.push_back(Image::RGBAMapEntry(i->red, i->green, i->blue, Colour::FTtoA(i->filter, i->transm)));
}

Image *Image::Create(unsigned int w, unsigned int h, ImageDataType t, unsigned int maxRAMmbHint, unsigned int pixelsPerBlockHint)
{
	try
	{
		switch(t)
		{
			case Bit_Map:
				return new MemoryBitMapImage(w, h);
			case Gray_Int8:
				return new MemoryGray8Image(w, h);
			case Gray_Int16:
				return new MemoryGray16Image(w, h);
			case GrayA_Int8:
				return new MemoryGrayA8Image(w, h);
			case GrayA_Int16:
				return new MemoryGrayA16Image(w, h);
			case RGB_Int8:
				return new MemoryRGB8Image(w, h);
			case RGB_Int16:
				return new MemoryRGB16Image(w, h);
			case RGBA_Int8:
				return new MemoryRGBA8Image (w, h);
			case RGBA_Int16:
				return new MemoryRGBA16Image(w, h);
			case RGBFT_Float:
				if (maxRAMmbHint > 0)
					if (SafeUnsignedProduct<POV_ULONG>(w, h, sizeof(FileRGBFTImage::pixel_type)) / 1048576 > maxRAMmbHint)
						return new FileRGBFTImage(w, h, pixelsPerBlockHint);
				return new MemoryRGBFTImage(w, h);
			case RGB_Gamma8:
				return new MemoryNonlinearRGB8Image(w, h);
			case RGB_Gamma16:
				return new MemoryNonlinearRGB16Image(w, h);
			case RGBA_Gamma8:
				return new MemoryNonlinearRGBA8Image (w, h);
			case RGBA_Gamma16:
				return new MemoryNonlinearRGBA16Image(w, h);
			case Gray_Gamma8:
				return new MemoryNonlinearGray8Image(w, h);
			case Gray_Gamma16:
				return new MemoryNonlinearGray16Image(w, h);
			case GrayA_Gamma8:
				return new MemoryNonlinearGrayA8Image(w, h);
			case GrayA_Gamma16:
				return new MemoryNonlinearGrayA16Image(w, h);
			default:
				throw POV_EXCEPTION_STRING("Undefined image format in Image::Create");
		}
	}
	catch(std::bad_alloc&)
	{
		throw POV_EXCEPTION(kOutOfMemoryErr, "Insufficient memory to allocate intermediate image storage.");
	}
}

Image *Image::Create(unsigned int w, unsigned int h, ImageDataType t, bool allowFileBacking)
{
	try
	{
		switch(t)
		{
			case Bit_Map:
				return new MemoryBitMapImage(w, h);
			case Gray_Int8:
				return new MemoryGray8Image(w, h);
			case Gray_Int16:
				return new MemoryGray16Image(w, h);
			case GrayA_Int8:
				return new MemoryGrayA8Image(w, h);
			case GrayA_Int16:
				return new MemoryGrayA16Image(w, h);
			case RGB_Int8:
				return new MemoryRGB8Image(w, h);
			case RGB_Int16:
				return new MemoryRGB16Image(w, h);
			case RGBA_Int8:
				return new MemoryRGBA8Image (w, h);
			case RGBA_Int16:
				return new MemoryRGBA16Image(w, h);
			case RGBFT_Float:
#ifdef FILE_MAPPED_IMAGE_ALLOCATOR
				if (allowFileBacking)
					return new RGBFTImage<FILE_MAPPED_IMAGE_ALLOCATOR<float> >(w, h);
#endif
				return new MemoryRGBFTImage(w, h);
			case RGB_Gamma8:
				return new MemoryNonlinearRGB8Image(w, h);
			case RGB_Gamma16:
				return new MemoryNonlinearRGB16Image(w, h);
			case RGBA_Gamma8:
				return new MemoryNonlinearRGBA8Image (w, h);
			case RGBA_Gamma16:
				return new MemoryNonlinearRGBA16Image(w, h);
			case Gray_Gamma8:
				return new MemoryNonlinearGray8Image(w, h);
			case Gray_Gamma16:
				return new MemoryNonlinearGray16Image(w, h);
			case GrayA_Gamma8:
				return new MemoryNonlinearGrayA8Image(w, h);
			case GrayA_Gamma16:
				return new MemoryNonlinearGrayA16Image(w, h);
			default:
				throw POV_EXCEPTION_STRING("Undefined image format in Image::Create");
		}
	}
	catch(std::bad_alloc&)
	{
		throw POV_EXCEPTION(kOutOfMemoryErr, "Insufficient memory to allocate intermediate image storage.");
	}
}

Image *Image::Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBMapEntry>& m, bool allowFileBacking)
{
	try
	{
		switch(t)
		{
			case Bit_Map:
				return new MemoryBitMapImage(w, h, m);
			case Colour_Map:
				return new MemoryColourMapImage(w, h, m);
			case Gray_Int8:
				return new MemoryGray8Image(w, h, m);
			case Gray_Int16:
				return new MemoryGray16Image(w, h, m);
			case GrayA_Int8:
				return new MemoryGrayA8Image(w, h, m);
			case GrayA_Int16:
				return new MemoryGrayA16Image(w, h, m);
			case RGB_Int8:
				return new MemoryRGB8Image(w, h, m);
			case RGB_Int16:
				return new MemoryRGB16Image(w, h, m);
			case RGBA_Int8:
				return new MemoryRGBA8Image (w, h, m);
			case RGBA_Int16:
				return new MemoryRGBA16Image(w, h, m);
			case RGBFT_Float:
#ifdef FILE_MAPPED_IMAGE_ALLOCATOR
				if (allowFileBacking)
					return new RGBFTImage<FILE_MAPPED_IMAGE_ALLOCATOR<float> >(w, h, m);
#endif
				return new MemoryRGBFTImage(w, h, m);
			case RGB_Gamma8:
				return new MemoryNonlinearRGB8Image(w, h, m);
			case RGB_Gamma16:
				return new MemoryNonlinearRGB16Image(w, h, m);
			case RGBA_Gamma8:
				return new MemoryNonlinearRGBA8Image (w, h, m);
			case RGBA_Gamma16:
				return new MemoryNonlinearRGBA16Image(w, h, m);
			case Gray_Gamma8:
				return new MemoryNonlinearGray8Image(w, h, m);
			case Gray_Gamma16:
				return new MemoryNonlinearGray16Image(w, h, m);
			case GrayA_Gamma8:
				return new MemoryNonlinearGrayA8Image(w, h, m);
			case GrayA_Gamma16:
				return new MemoryNonlinearGrayA16Image(w, h, m);
			default:
				throw POV_EXCEPTION_STRING("Image::Create Exception TODO"); // TODO FIXME WIP
		}
	}
	catch(std::bad_alloc&)
	{
		throw POV_EXCEPTION(kOutOfMemoryErr, "Insufficient memory to allocate intermediate image storage.");
	}
}

Image *Image::Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBAMapEntry>& m, bool allowFileBacking)
{
	try
	{
		switch(t)
		{
			case Bit_Map:
				return new MemoryBitMapImage(w, h, m);
			case Colour_Map:
				return new MemoryColourMapImage(w, h, m);
			case Gray_Int8:
				return new MemoryGray8Image(w, h, m);
			case Gray_Int16:
				return new MemoryGray16Image(w, h, m);
			case GrayA_Int8:
				return new MemoryGrayA8Image(w, h, m);
			case GrayA_Int16:
				return new MemoryGrayA16Image(w, h, m);
			case RGB_Int8:
				return new MemoryRGB8Image(w, h, m);
			case RGB_Int16:
				return new MemoryRGB16Image(w, h, m);
			case RGBA_Int8:
				return new MemoryRGBA8Image (w, h, m);
			case RGBA_Int16:
				return new MemoryRGBA16Image(w, h, m);
			case RGBFT_Float:
#ifdef FILE_MAPPED_RGBFT_IMAGE_ALLOCATOR
				if (allowFileBacking)
					return new RGBFTImage<FILE_MAPPED_RGBFT_IMAGE_ALLOCATOR<float> >(w, h, m);
#endif
				return new MemoryRGBFTImage(w, h, m);
			case RGB_Gamma8:
				return new MemoryNonlinearRGB8Image(w, h, m);
			case RGB_Gamma16:
				return new MemoryNonlinearRGB16Image(w, h, m);
			case RGBA_Gamma8:
				return new MemoryNonlinearRGBA8Image (w, h, m);
			case RGBA_Gamma16:
				return new MemoryNonlinearRGBA16Image(w, h, m);
			case Gray_Gamma8:
				return new MemoryNonlinearGray8Image(w, h, m);
			case Gray_Gamma16:
				return new MemoryNonlinearGray16Image(w, h, m);
			case GrayA_Gamma8:
				return new MemoryNonlinearGrayA8Image(w, h, m);
			case GrayA_Gamma16:
				return new MemoryNonlinearGrayA16Image(w, h, m);
			default:
				throw POV_EXCEPTION_STRING("Image::Create Exception TODO"); // TODO FIXME WIP
		}
	}
	catch(std::bad_alloc&)
	{
		throw POV_EXCEPTION(kOutOfMemoryErr, "Insufficient memory to allocate intermediate image storage.");
	}
}

Image *Image::Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBFTMapEntry>& m, bool allowFileBacking)
{
	try
	{
		switch(t)
		{
			case Bit_Map:
				return new MemoryBitMapImage(w, h, m);
			case Colour_Map:
				return new MemoryColourMapImage(w, h, m);
			case Gray_Int8:
				return new MemoryGray8Image(w, h, m);
			case Gray_Int16:
				return new MemoryGray16Image(w, h, m);
			case GrayA_Int8:
				return new MemoryGrayA8Image(w, h, m);
			case GrayA_Int16:
				return new MemoryGrayA16Image(w, h, m);
			case RGB_Int8:
				return new MemoryRGB8Image(w, h, m);
			case RGB_Int16:
				return new MemoryRGB16Image(w, h, m);
			case RGBA_Int8:
				return new MemoryRGBA8Image (w, h, m);
			case RGBA_Int16:
				return new MemoryRGBA16Image(w, h, m);
			case RGBFT_Float:
#ifdef FILE_MAPPED_IMAGE_ALLOCATOR
				if (allowFileBacking)
					return new RGBFTImage<FILE_MAPPED_IMAGE_ALLOCATOR<float> >(w, h, m);
#endif
				return new MemoryRGBFTImage(w, h, m);
			case RGB_Gamma8:
				return new MemoryNonlinearRGB8Image(w, h, m);
			case RGB_Gamma16:
				return new MemoryNonlinearRGB16Image(w, h, m);
			case RGBA_Gamma8:
				return new MemoryNonlinearRGBA8Image (w, h, m);
			case RGBA_Gamma16:
				return new MemoryNonlinearRGBA16Image(w, h, m);
			case Gray_Gamma8:
				return new MemoryNonlinearGray8Image(w, h, m);
			case Gray_Gamma16:
				return new MemoryNonlinearGray16Image(w, h, m);
			case GrayA_Gamma8:
				return new MemoryNonlinearGrayA8Image(w, h, m);
			case GrayA_Gamma16:
				return new MemoryNonlinearGrayA16Image(w, h, m);
			default:
				throw POV_EXCEPTION_STRING("Image::Create Exception TODO"); // TODO FIXME WIP
		}
	}
	catch(std::bad_alloc&)
	{
		throw POV_EXCEPTION(kOutOfMemoryErr, "Insufficient memory to allocate intermediate image storage.");
	}
}

Image *Image::Read(ImageFileType type, IStream *file, const ReadOptions& options)
{
	#ifdef SYS_TO_STANDARD
		if (type == SYS)
			type = SYS_TO_STANDARD ;
	#endif

	switch (type)
	{
		case HDR:
			return (HDR::Read(file, options));

		case EXR:
#ifndef OPENEXR_MISSING
			return (OpenEXR::Read(file, options));
#else
			throw POV_EXCEPTION(kCannotOpenFileErr,
"This unofficial POV-Ray binary was built without support for the OpenEXR \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the OpenEXR library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: HDR.");
			return NULL;
#endif

		case PNG:
#ifndef LIBPNG_MISSING
			return (Png::Read(file, options));
#else
			throw POV_EXCEPTION(kCannotOpenFileErr,
"This unofficial POV-Ray binary was built without support for the PNG \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the libPNG library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: GIF, TGA, IFF, PGM, PPM, BMP.");
			return NULL;
#endif

		case GIF:
			return (Gif::Read(file, options, false));

		case POT:
			return (Gif::Read(file, options, true));

		case TGA:
			return (Targa::Read(file, options));

		case JPEG:
#ifndef LIBJPEG_MISSING
			return (Jpeg::Read(file, options));
#else
			throw POV_EXCEPTION(kCannotOpenFileErr,
"This unofficial POV-Ray binary was built without support for the JPEG \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the libJPEG library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: GIF, TGA, IFF, PGM, PPM, BMP.");
			return NULL;
#endif

		case IFF:
			return (Iff::Read(file, options));

		case PGM:
			return (Pgm::Read(file, options));

		case PPM:
			return (Ppm::Read(file, options));

		case BMP:
			return (Bmp::Read(file, options));

		case TIFF:
#ifndef LIBTIFF_MISSING
			return (Tiff::Read(file, options));
#else
			throw POV_EXCEPTION(kCannotOpenFileErr,
"This unofficial POV-Ray binary was built without support for the TIFF \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the libTIFF library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: GIF, TGA, IFF, PGM, PPM, BMP.");
			return NULL;
#endif

		case SYS:
			throw POV_EXCEPTION(kCannotOpenFileErr, "This platform has not defined a SYS file type");
			return (NULL);

		default :
			throw POV_EXCEPTION(kParamErr, "Invalid file type");
			return (NULL);
	}
}

void Image::Write(ImageFileType type, OStream *file, const Image *image, const WriteOptions& options)
{
	if (image->GetWidth() == 0 || image->GetHeight() == 0)
		throw POV_EXCEPTION(kParamErr, "Invalid image size for output");

	if (file == NULL)
		throw POV_EXCEPTION(kCannotOpenFileErr, "Invalid image file");

#ifdef SYS_TO_STANDARD
	if (type == SYS)
		type = SYS_TO_STANDARD ;
#endif

	switch (type)
	{
		case GIF:
		case IFF:
		case PGM:
		case TIFF:
		case POT:
			throw POV_EXCEPTION(kParamErr, "Unsupported file type for output");
			break;

		case SYS:
			throw POV_EXCEPTION(kCannotOpenFileErr, "This platform has not defined a SYS file type");
			break;

		case HDR:
			HDR::Write(file, image, options);
			break;

		case EXR:
#ifndef OPENEXR_MISSING
			OpenEXR::Write(file, image, options);
#else
			throw POV_EXCEPTION(kParamErr,
"This unofficial POV-Ray binary was built without support for the OpenEXR \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the OpenEXR library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: HDR.");
#endif
			break;

		case PNG:
#ifndef LIBPNG_MISSING
			Png::Write(file, image, options);
#else
			throw POV_EXCEPTION(kParamErr,
"This unofficial POV-Ray binary was built without support for the PNG \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the libPNG library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: TGA, PPM, BMP.");
#endif
			break;

		case TGA:
			Targa::Write(file, image, options);
			break;

		case PPM:
			Ppm::Write(file, image, options);
			break;

		case BMP:
			Bmp::Write(file, image, options);
			break;

		case JPEG:
#ifndef LIBJPEG_MISSING
			Jpeg::Write(file, image, options);
#else
			throw POV_EXCEPTION(kParamErr,
"This unofficial POV-Ray binary was built without support for the JPEG \
file format.  You must either use an official POV-Ray binary or recompile \
the POV-Ray sources on a system providing you with the libJPEG library \
to make use of this facility.  Alternatively, you may use any of the \
following built-in formats: TGA, PPM, BMP.");
#endif
			break;

		default :
			throw POV_EXCEPTION(kParamErr, "Invalid file type");
			break;
	}
}

void Image::GetRGBIndexedValue(unsigned char index, float& red, float& green, float& blue) const
{
	switch(colormaptype)
	{
		case NoColourMap:
			red = 0.0f;
			green = 0.0f;
			blue = 0.0f;
			break;
		case RGBColourMap:
		case RGBAColourMap:
		case RGBFTColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			break;
	}
}

void Image::GetRGBAIndexedValue(unsigned char index, float& red, float& green, float& blue, float& alpha) const
{
	switch(colormaptype)
	{
		case NoColourMap:
			red = 0.0f;
			green = 0.0f;
			blue = 0.0f;
			alpha = ALPHA_OPAQUE;
			break;
		case RGBColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			alpha = ALPHA_OPAQUE;
			break;
		case RGBAColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			alpha = colormap[index].filter; // with RGBAColourMap, .filter is actually alpha
			break;
		case RGBFTColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			alpha = Colour::FTtoA(colormap[index].filter, colormap[index].transm);
			break;
	}
}

void Image::GetRGBFTIndexedValue(unsigned char index, float& red, float& green, float& blue, float& filter, float& transm) const
{
	switch(colormaptype)
	{
		case NoColourMap:
			red = 0.0f;
			green = 0.0f;
			blue = 0.0f;
			filter = FT_OPAQUE;
			transm = FT_OPAQUE;
			break;
		case RGBColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			filter = transm = FT_OPAQUE;
			break;
		case RGBAColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			Colour::AtoFT(colormap[index].filter, filter, transm); // with RGBAColourMap, .filter is actually alpha
			break;
		case RGBFTColourMap:
			red = colormap[index].red;
			green = colormap[index].green;
			blue = colormap[index].blue;
			filter = colormap[index].filter;
			transm = colormap[index].transm;
			break;
	}
}

void Image::SetRGBIndexedValue(unsigned char index, float red, float green, float blue)
{
	switch(colormaptype)
	{
		case NoColourMap:
			break;
		case RGBColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = 0.0f; // not used with RGBColourMap
			colormap[index].transm = 0.0f; // not used with RGBColourMap
			break;
		case RGBAColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = ALPHA_OPAQUE; // with RGBAColourMap, .filter is actually alpha
			colormap[index].transm = 0.0f; // not used with RGBAColourMap
			break;
		case RGBFTColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = FT_OPAQUE;
			colormap[index].transm = FT_OPAQUE;
			break;
	}
}

void Image::SetRGBAIndexedValue(unsigned char index, float red, float green, float blue, float alpha)
{
	switch(colormaptype)
	{
		case NoColourMap:
			break;
		case RGBColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = 0.0f; // not used with RGBColourMap
			colormap[index].transm = 0.0f; // not used with RGBColourMap
			break;
		case RGBAColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = alpha; // with RGBAColourMap, .filter is actually alpha
			colormap[index].transm = 0.0f; // not used with RGBAColourMap
			break;
		case RGBFTColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			Colour::AtoFT(alpha, colormap[index].filter, colormap[index].transm);
			break;
	}
}

void Image::SetRGBFTIndexedValue(unsigned char index, float red, float green, float blue, float filter, float transm)
{
	switch(colormaptype)
	{
		case NoColourMap:
			break;
		case RGBColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = 0.0f; // not used with RGBColourMap
			colormap[index].transm = 0.0f; // not used with RGBColourMap
			break;
		case RGBAColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = Colour::FTtoA(filter, transm); // note: filter is alpha in RGBA maps
			colormap[index].transm = 0.0f; // not used with RGBAColourMap
			break;
		case RGBFTColourMap:
			colormap[index].red = red;
			colormap[index].green = green;
			colormap[index].blue = blue;
			colormap[index].filter = filter;
			colormap[index].transm = transm;
			break;
	}
}

void Image::SetRGBFTIndexedValue(unsigned char index, const Colour& col)
{
	switch(colormaptype)
	{
		case NoColourMap:
			break;
		case RGBColourMap:
			colormap[index].red = col.red();
			colormap[index].green = col.green();
			colormap[index].blue = col.blue();
			colormap[index].filter = 0.0f; // not used with RGBColourMap
			colormap[index].transm = 0.0f; // not used with RGBColourMap
			break;
		case RGBAColourMap:
			colormap[index].red = col.red();
			colormap[index].green = col.green();
			colormap[index].blue = col.blue();
			colormap[index].filter = col.FTtoA(); // note: filter is alpha in RGBA maps
			colormap[index].transm = 0.0f; // not used with RGBAColourMap
			break;
		case RGBFTColourMap:
			colormap[index].red = col.red();
			colormap[index].green = col.green();
			colormap[index].blue = col.blue();
			colormap[index].filter = col.filter();
			colormap[index].transm = col.transm();
			break;
	}
}

unsigned char Image::GetIndexedValue(unsigned int, unsigned int)
{
	return 0;
}

void Image::SetIndexedValue(unsigned int x, unsigned int y, unsigned char index)
{
	CHECK_BOUNDS(x, y);
	switch(colormaptype)
	{
		case NoColourMap:
			SetBitValue(x,y, false);
			break;
		case RGBColourMap:
			SetRGBValue(x, y, colormap[index].red, colormap[index].green, colormap[index].blue);
			break;
		case RGBAColourMap:
			SetRGBAValue(x, y, colormap[index].red, colormap[index].green, colormap[index].blue, colormap[index].filter); // with RGBAColourMap, .filter is actually alpha
			break;
		case RGBFTColourMap:
			SetRGBFTValue(x, y, colormap[index].red, colormap[index].green, colormap[index].blue, colormap[index].filter, colormap[index].transm);
			break;
	}
}

void Image::FillIndexedValue(unsigned char index)
{
	switch(colormaptype)
	{
		case NoColourMap:
			FillBitValue(false);
			break;
		case RGBColourMap:
			FillRGBValue(colormap[index].red, colormap[index].green, colormap[index].blue);
			break;
		case RGBAColourMap:
			FillRGBAValue(colormap[index].red, colormap[index].green, colormap[index].blue, colormap[index].filter); // with RGBAColourMap, .filter is actually alpha
			break;
		case RGBFTColourMap:
			FillRGBFTValue(colormap[index].red, colormap[index].green, colormap[index].blue, colormap[index].filter, colormap[index].transm);
			break;
	}
}

void Image::GetRGBValue(unsigned int x, unsigned int y, RGBColour& colour, bool premul) const
{
	if (premul && !premultiplied && HasTransparency())
	{
		// data is non-premultiplied, but caller expects premultiplied data
		float alpha;
		GetRGBAValue(x, y, colour.red(), colour.green(), colour.blue(), alpha);
		colour *= alpha;
	}
	if (!premul && premultiplied && HasTransparency())
	{
		// data is premultiplied, but caller expects non-premultiplied data
		float alpha;
		GetRGBAValue(x, y, colour.red(), colour.green(), colour.blue(), alpha);
		if (alpha != 0.0) // TODO maybe use some epsilon?!
			colour /= alpha;
	}
	else
	{
		GetRGBValue(x, y, colour.red(), colour.green(), colour.blue());
	}
}
void Image::GetRGBFTValue(unsigned int x, unsigned int y, Colour& colour, bool premul) const
{
	GetRGBFTValue(x, y, colour.red(), colour.green(), colour.blue(), colour.filter(), colour.transm());
	if (premul && !premultiplied && HasTransparency())
	{
		// data is non-premultiplied, but caller expects premultiplied data
		float alpha = colour.FTtoA();
		colour.red()   *= alpha;
		colour.green() *= alpha;
		colour.blue()  *= alpha;
	}
	else if (!premul && premultiplied && HasTransparency())
	{
		// data is premultiplied, but caller expects non-premultiplied data
		float alpha = colour.FTtoA();
		if (alpha != 0.0) // TODO maybe use some epsilon?!
		{
			colour.red()   /= alpha;
			colour.green() /= alpha;
			colour.blue()  /= alpha;
		}
	}
}

unsigned int Image::GetColourMapSize() const
{
	return colormap.size();
}

void Image::GetColourMap(vector<RGBMapEntry>& m) const
{
	m.resize(colormap.size());
	for(size_t i = 0; i < colormap.size(); i++)
		GetRGBIndexedValue((unsigned char)(i), m[i].red, m[i].green, m[i].blue);
}

void Image::GetColourMap(vector<RGBAMapEntry>& m) const
{
	m.resize(colormap.size());
	for(size_t i = 0; i < colormap.size(); i++)
		GetRGBAIndexedValue((unsigned char)(i), m[i].red, m[i].green, m[i].blue, m[i].alpha);
}

void Image::GetColourMap(vector<RGBFTMapEntry>& m) const
{
	m.resize(colormap.size());
	for(size_t i = 0; i < colormap.size(); i++)
		GetRGBFTIndexedValue((unsigned char)(i), m[i].red, m[i].green, m[i].blue, m[i].filter, m[i].transm);
}

void Image::SetColourMap(const vector<RGBMapEntry>& m)
{
	colormap.resize(max(m.size(), sizeof(unsigned char) * 256));
	colormaptype = RGBColourMap;
	colormap.assign(m.begin(), m.end());
}

void Image::SetColourMap(const vector<RGBAMapEntry>& m)
{
	colormap.resize(max(m.size(), sizeof(unsigned char) * 256));
	colormaptype = RGBAColourMap;
	colormap.assign(m.begin(), m.end());
}

void Image::SetColourMap(const vector<RGBFTMapEntry>& m)
{
	colormap.resize(max(m.size(), sizeof(unsigned char) * 256));
	colormaptype = RGBFTColourMap;
	colormap.assign(m.begin(), m.end());
}

}
