/*****************************************************************************/
// Copyright 2008-2009 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file in
// accordance with the terms of the Adobe license agreement accompanying it.
/*****************************************************************************/

/* $Id: //mondo/dng_sdk_1_4/dng_sdk/source/dng_gain_map.cpp#1 $ */ 
/* $DateTime: 2012/05/30 13:28:51 $ */
/* $Change: 832332 $ */
/* $Author: tknoll $ */

/*****************************************************************************/

#include "dng_gain_map.h"

#include "dng_exceptions.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_pixel_buffer.h"
#include "dng_safe_arithmetic.h"
#include "dng_stream.h"
#include "dng_tag_values.h"

/*****************************************************************************/

class dng_gain_map_interpolator
	{
	
	private:
	
		const dng_gain_map &fMap;
		
		dng_point_real64 fScale;
		dng_point_real64 fOffset;
		
		int32 fColumn;
		int32 fPlane;
		
		uint32 fRowIndex1;
		uint32 fRowIndex2;
		real32 fRowFract;
		
		int32 fResetColumn;
		
		real32 fValueBase;
		real32 fValueStep;
		real32 fValueIndex;
			
	public:
	
		dng_gain_map_interpolator (const dng_gain_map &map,
								   const dng_rect &mapBounds,
								   int32 row,
								   int32 column,
								   uint32 plane);

		real32 Interpolate () const
			{
			
			return fValueBase + fValueStep * fValueIndex;
			
			}
			
		void Increment ()
			{
			
			if (++fColumn >= fResetColumn)
				{
				
				ResetColumn ();
				
				}
				
			else
				{
				
				fValueIndex += 1.0f;
				
				}
						
			}
	
	private:
			
		real32 InterpolateEntry (uint32 colIndex);
			
		void ResetColumn ();
			
	};
			
/*****************************************************************************/

dng_gain_map_interpolator::dng_gain_map_interpolator (const dng_gain_map &map,
													  const dng_rect &mapBounds,
													  int32 row,
													  int32 column,
													  uint32 plane)

	:	fMap (map)
	
	,	fScale (1.0 / mapBounds.H (),
				1.0 / mapBounds.W ())
	
	,	fOffset (0.5 - mapBounds.t,
				 0.5 - mapBounds.l)
	
	,	fColumn (column)
	,	fPlane  (plane)
	
	,	fRowIndex1 (0)
	,	fRowIndex2 (0)
	,	fRowFract  (0.0f)
	
	,	fResetColumn (0)
	
	,	fValueBase  (0.0f)
	,	fValueStep  (0.0f)
	,	fValueIndex (0.0f)
	
	{
	
	real64 rowIndexF = (fScale.v * (row + fOffset.v) -
						fMap.Origin ().v) / fMap.Spacing ().v;
	
	if (rowIndexF <= 0.0)
		{
		
		fRowIndex1 = 0;
		fRowIndex2 = 0;
		
		fRowFract = 0.0f;

		}
		
	else
		{
		
		if (fMap.Points ().v < 1)
			{
			ThrowProgramError ("Empty gain map");
			}
		uint32 lastRow = static_cast<uint32> (fMap.Points ().v - 1);
		
		if (rowIndexF >= static_cast<real64> (lastRow))
			{
			
			fRowIndex1 = lastRow;
			fRowIndex2 = fRowIndex1;
			
			fRowFract = 0.0f;
			
			}
			
		else
			{
			
			// If we got here, we know that rowIndexF can safely be converted to
			// a uint32 and that static_cast<uint32> (rowIndexF) < lastRow. This
			// implies fRowIndex2 <= lastRow below.
			fRowIndex1 = static_cast<uint32> (rowIndexF);
			fRowIndex2 = fRowIndex1 + 1;
			
			fRowFract = (real32) (rowIndexF - (real64) fRowIndex1);
			
			}
			
		}
	
	ResetColumn ();
		
	}

/*****************************************************************************/

real32 dng_gain_map_interpolator::InterpolateEntry (uint32 colIndex)
	{
	
	return fMap.Entry (fRowIndex1, colIndex, fPlane) * (1.0f - fRowFract) +
		   fMap.Entry (fRowIndex2, colIndex, fPlane) * (       fRowFract);
	
	}

/*****************************************************************************/

void dng_gain_map_interpolator::ResetColumn ()
	{
	
	real64 colIndexF = ((fScale.h * (fColumn + fOffset.h)) - 
						fMap.Origin ().h) / fMap.Spacing ().h;
	
	if (colIndexF <= 0.0)
		{
		
		fValueBase = InterpolateEntry (0);
		
		fValueStep = 0.0f;
		
		fResetColumn = (int32) ceil (fMap.Origin ().h / fScale.h - fOffset.h);

		}
		
	else
		{
	
		if (fMap.Points ().h < 1)
			{
			ThrowProgramError ("Empty gain map");
			}
		uint32 lastCol = static_cast<uint32> (fMap.Points ().h - 1);
		
		if (colIndexF >= static_cast<real64> (lastCol))
			{
			
			fValueBase = InterpolateEntry (lastCol);
			
			fValueStep = 0.0f;
			
			fResetColumn = 0x7FFFFFFF;
			
			}
		
		else
			{
			
			// If we got here, we know that colIndexF can safely be converted to
			// a uint32 and that static_cast<uint32> (colIndexF) < lastCol. This
			// implies colIndex + 1 <= lastCol, i.e. the argument to
			// InterpolateEntry() below is valid.
			uint32 colIndex = static_cast<uint32> (colIndexF);
			real64 base  = InterpolateEntry (colIndex);
			real64 delta = InterpolateEntry (colIndex + 1) - base;
			
			fValueBase = (real32) (base + delta * (colIndexF - (real64) colIndex));
			
			fValueStep = (real32) ((delta * fScale.h) / fMap.Spacing ().h);
			
			fResetColumn = (int32) ceil (((colIndex + 1) * fMap.Spacing ().h +
										  fMap.Origin ().h) / fScale.h - fOffset.h);
			
			}
			
		}
	
	fValueIndex = 0.0f;
	
	}

/*****************************************************************************/

dng_gain_map::dng_gain_map (dng_memory_allocator &allocator,
							const dng_point &points,
							const dng_point_real64 &spacing,
							const dng_point_real64 &origin,
							uint32 planes)
					
	:	fPoints  (points)
	,	fSpacing (spacing)
	,	fOrigin  (origin)
	,	fPlanes  (planes)
	
	,	fRowStep (SafeUint32Mult(planes, points.h))
	
	,	fBuffer ()
	
	{
	
	fBuffer.Reset (allocator.Allocate (
		ComputeBufferSize (ttFloat, fPoints, fPlanes, pad16Bytes)));
	
	}
					  
/*****************************************************************************/

real32 dng_gain_map::Interpolate (int32 row,
								  int32 col,
								  uint32 plane,
								  const dng_rect &bounds) const
	{
	
	dng_gain_map_interpolator interp (*this,
									  bounds,
									  row,
									  col,
									  plane);
									  
	return interp.Interpolate ();
	
	}
					  
/*****************************************************************************/

uint32 dng_gain_map::PutStreamSize () const
	{
	
	return 44 + fPoints.v * fPoints.h * fPlanes * 4;
	
	}
					  
/*****************************************************************************/

void dng_gain_map::PutStream (dng_stream &stream) const
	{
	
	stream.Put_uint32 (fPoints.v);
	stream.Put_uint32 (fPoints.h);
	
	stream.Put_real64 (fSpacing.v);
	stream.Put_real64 (fSpacing.h);
	
	stream.Put_real64 (fOrigin.v);
	stream.Put_real64 (fOrigin.h);
	
	stream.Put_uint32 (fPlanes);
	
	for (int32 rowIndex = 0; rowIndex < fPoints.v; rowIndex++)
		{
		
		for (int32 colIndex = 0; colIndex < fPoints.h; colIndex++)
			{
			
			for (uint32 plane = 0; plane < fPlanes; plane++)
				{
				
				stream.Put_real32 (Entry (rowIndex,
										  colIndex,
										  plane));
										  
				}
				
			}
			
		}
	
	}

/*****************************************************************************/

dng_gain_map * dng_gain_map::GetStream (dng_host &host,
										dng_stream &stream)
	{
	
	dng_point mapPoints;
	
	mapPoints.v = stream.Get_uint32 ();
	mapPoints.h = stream.Get_uint32 ();
	
	dng_point_real64 mapSpacing;
	
	mapSpacing.v = stream.Get_real64 ();
	mapSpacing.h = stream.Get_real64 ();
	
	dng_point_real64 mapOrigin;
	
	mapOrigin.v = stream.Get_real64 ();
	mapOrigin.h = stream.Get_real64 ();
	
	uint32 mapPlanes = stream.Get_uint32 ();
	
	#if qDNGValidate
	
	if (gVerbose)
		{
		
		printf ("Points: v=%d, h=%d\n", 
				(int) mapPoints.v,
				(int) mapPoints.h);
		
		printf ("Spacing: v=%.6f, h=%.6f\n", 
				mapSpacing.v,
				mapSpacing.h);
		
		printf ("Origin: v=%.6f, h=%.6f\n", 
				mapOrigin.v,
				mapOrigin.h);
		
		printf ("Planes: %u\n", 
				(unsigned) mapPlanes);
		
		}
		
	#endif
	
	if (mapPoints.v == 1)
		{
		mapSpacing.v = 1.0;
		mapOrigin.v  = 0.0;
		}
	
	if (mapPoints.h == 1)
		{
		mapSpacing.h = 1.0;
		mapOrigin.h  = 0.0;
		}
		
	if (mapPoints.v < 1 ||
		mapPoints.h < 1 ||
		mapSpacing.v <= 0.0 ||
		mapSpacing.h <= 0.0 ||
		mapPlanes < 1)
		{
		ThrowBadFormat ();
		}
	
	AutoPtr<dng_gain_map> map (new dng_gain_map (host.Allocator (),
												 mapPoints,
												 mapSpacing,
												 mapOrigin,
												 mapPlanes));
												 
	#if qDNGValidate
	
	uint32 linesPrinted = 0;
	uint32 linesSkipped = 0;
	
	#endif
	
	for (int32 rowIndex = 0; rowIndex < mapPoints.v; rowIndex++)
		{
		
		for (int32 colIndex = 0; colIndex < mapPoints.h; colIndex++)
			{
			
			for (uint32 plane = 0; plane < mapPlanes; plane++)
				{
				
				real32 x = stream.Get_real32 ();
				
				map->Entry (rowIndex, colIndex, plane) = x;
				
				#if qDNGValidate
				
				if (gVerbose)
					{
					
					if (linesPrinted < gDumpLineLimit)
						{
						
						printf ("    Map [%3u] [%3u] [%u] = %.4f\n",
								(unsigned) rowIndex,
								(unsigned) colIndex,
								(unsigned) plane,
								x);
						
						linesPrinted++;
						
						}
						
					else
						linesSkipped++;
						
					}
					
				#endif

				}
				
			}
			
		}
												 
	#if qDNGValidate
	
	if (linesSkipped)
		{
		
		printf ("    ... %u map entries skipped\n", (unsigned) linesSkipped);
		
		}
	
	#endif
				
	return map.Release ();
	
	}

/*****************************************************************************/

dng_opcode_GainMap::dng_opcode_GainMap (const dng_area_spec &areaSpec,
										AutoPtr<dng_gain_map> &gainMap)
	
	:	dng_inplace_opcode (dngOpcode_GainMap,
						    dngVersion_1_3_0_0,
						    kFlag_None)
							
	,	fAreaSpec (areaSpec)
					
	,	fGainMap ()
	
	{
	
	fGainMap.Reset (gainMap.Release ());
	
	}
		
/*****************************************************************************/

dng_opcode_GainMap::dng_opcode_GainMap (dng_host &host,
										dng_stream &stream)
												
	:	dng_inplace_opcode (dngOpcode_GainMap,
							stream,
							"GainMap")
							
	,	fAreaSpec ()
							
	,	fGainMap ()
							
	{
	
	uint32 byteCount = stream.Get_uint32 ();
	
	uint64 startPosition = stream.Position ();
	
	fAreaSpec.GetData (stream);
	
	fGainMap.Reset (dng_gain_map::GetStream (host, stream));
	
	if (stream.Position () != startPosition + byteCount)
		{
		ThrowBadFormat ();
		}
		
	}
		
/*****************************************************************************/

void dng_opcode_GainMap::PutData (dng_stream &stream) const
	{
	
	stream.Put_uint32 (dng_area_spec::kDataSize +
					   fGainMap->PutStreamSize ());
					   
	fAreaSpec.PutData (stream);
	
	fGainMap->PutStream (stream);
	
	}
		
/*****************************************************************************/

void dng_opcode_GainMap::ProcessArea (dng_negative & /* negative */,
									  uint32 /* threadIndex */,
									  dng_pixel_buffer &buffer,
									  const dng_rect &dstArea,
									  const dng_rect &imageBounds)
	{
	
	dng_rect overlap = fAreaSpec.Overlap (dstArea);
	
	if (overlap.NotEmpty ())
		{
		
		uint32 cols = overlap.W ();
		
		uint32 colPitch = fAreaSpec.ColPitch ();
		
		for (uint32 plane = fAreaSpec.Plane ();
			 plane < fAreaSpec.Plane () + fAreaSpec.Planes () &&
			 plane < buffer.Planes ();
			 plane++)
			{
			
			uint32 mapPlane = Min_uint32 (plane, fGainMap->Planes () - 1);
			
			for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ())
				{
				
				real32 *dPtr = buffer.DirtyPixel_real32 (row, overlap.l, plane);
				
				dng_gain_map_interpolator interp (*fGainMap,
												  imageBounds,
												  row,
												  overlap.l,
												  mapPlane);
										   
				for (uint32 col = 0; col < cols; col += colPitch)
					{
					
					real32 gain = interp.Interpolate ();
					
					dPtr [col] = Min_real32 (dPtr [col] * gain, 1.0f);
					
					for (uint32 j = 0; j < colPitch; j++)
						{
						interp.Increment ();
						}
					
					}
				
				}
			
			}
		
		}

	}
	
/*****************************************************************************/