//================================================================
// Sigma X3F RAW file loading for lib2D, using dcraw
//
// AUTHOR:  Gabor Nagy
// DATE:    2025-Aug-20 02:52:06
//
// Copyright (C) 1996-2026 by Gabor Nagy. All rights reserved.
//================================================================
#include <netinet/in.h>    // For ntohs

#include <EMatrix.h>

#include <Image/DCamRAW.h>

#define EpMULTI_THREADED          0  // Sadly, we can't parallelize X3F Foveon data reading because the "huff" state table doesn't reset per channel. E.g. we son't know the initial state for the G channel until R is read

#define FORC(pCnt) for(int LCI = 0;LCI < pCnt;LCI++)
#define FORC3 FORC(3)


// Reduce quantization gaining up pixels before smoothing, and using higher-res curves via _CurveSamplingMultX
// It's not quite right yet. Things turn purple-ish when _CurveSamplingMultX > 1.0
static const float  _CurveSamplingMultX = 1.0;

static const float  _PreGain = 3.0;


EpNameAndType  _SDx_Types[] =
{
	{ "PostPolyMatrix",        EResFLOAT32 },

	{ "SaturationLevel",       EResINT32 },
	{ "RawSaturationLevel",       EResINT32 },
	{ "MaxOutputLevel",       EResINT32 },
	{ "DarkLevel",       EResINT32 },
	{ "DarkStddev",        EResFLOAT32 },

	{ "SpatialGain*",        EResFLOAT32 },
	{ "BadPixels",       EResINT32 },
	{ "CamToXYZ_Flash",        EResFLOAT32 },
	{ "ContentVersionNumber",       EResINT32 },
	{ "SensorISO",        EResFLOAT32 },
	{ "SensorBoardTemperature",        EResFLOAT32 },
	{ "TempGainFact",        EResFLOAT32 },
	{ "FnumberGainFact",        EResFLOAT32 },

	{ "BadPixThreshLevels",        EResINT32 },
	{ "BadPixThreshTimes",        EResFLOAT32 },

	{ "*CCMatrix",        EResFLOAT32 },
	{ "*WBGain",        EResFLOAT32 },
	{ "PGain",        EResFLOAT32 },
	{ "CP*Matrix",        EResFLOAT32 },
	{ "CMCM_*",        EResFLOAT32 },
	{ "CMCC*",        EResFLOAT32 },

	{ "CalTemperature",        EResFLOAT32 },
	{ "LUTAdjust",        EResFLOAT32 },
	{ "DSTemperature",        EResINT32 },

	{ "ChromaBlur",        EResFLOAT32 },
	{ "RedSharpen",        EResFLOAT32 },
	{ "FlareFactor",        EResFLOAT32 },
	{ "CCMatrix_Flag",        EResFLOAT32 },
	{ "LinLUT_ReductionFactor",        EResFLOAT32 },

	{ "SpatialDQ",        EResFLOAT32 },
	{ "ChromaDQ",        EResFLOAT32 },
	{ "ColorDQCamRGB",        EResFLOAT32 },
	{ "ColumnFilter",        EResFLOAT32 },
	{ "DarkShield*",       EResINT32 },
	{ "DespeckleExpTimeThresh",        EResFLOAT32 },
	{ "KeepImageArea",       EResINT32 },
	{ "ActiveImageArea",       EResINT32 },
	{ "GrayAverageArea",       EResINT32 },
	{ "GrayAverageRange",        EResFLOAT32 },
	{ "ExpectedOutputRatio",        EResFLOAT32 },
	{ "EnablePreviewColorDQ",        EResFLOAT32 },
	{ "UsePreComputedDrift",       EResINT32 },
	{ "DarkDrift",        EResFLOAT32 },
	{ "RawDrift",        EResFLOAT32 },
	{ "RowCorrectionGain",        EResFLOAT32 },
	{ "RowCorrectionScale",        EResFLOAT32 },
	{ "FlareAverage",        EResFLOAT32 },

	{ "FIR_ProportionLocalMeanImgWithEdgeRef",        EResFLOAT32 },
	{ "FIR_Locality",        EResFLOAT32 },
	{ "FIR_ProportionLocalMeanRef",        EResFLOAT32 },
	{ "FIR_ProportionLocalMeanEdgeRef",        EResFLOAT32 },
	{ "FIR_SigmoidOffset",        EResFLOAT32 },
	{ "FIR_EdgeMapExponent",        EResFLOAT32 },
	{ "FIR_ScaleFactorEdgeRef",        EResFLOAT32 },
	{ "FIR_ScaleFactorLocalMeanEdgeRef",        EResFLOAT32 },
	{ "FIR_Iterations",        EResINT32 },


	{ "CaptureISO",        EResFLOAT32 },
	{ "CaptureAperture",        EResFLOAT32 },
	{ "CaptureExpTime",        EResFLOAT32 },
	{ "CaptureShutter",        EResFLOAT32 },

	{ "WBCorrection_*",        EResFLOAT32 },

	{ "RawOffsetSourceRows",       EResINT32 },
	{ "RawOffsetFilterShift",       EResINT32 },

	{ "AutoRGBNeutral",        EResFLOAT32 },
};

EpNameAndType  _DPx_Types[] =
{
	{ "ContentVersionNumber",       EResINT32 },
	{ "KeepImageArea",       EResINT32 },
	{ "ActiveImageArea",       EResINT32 },
	{ "DarkShieldTop",       EResINT32 },
	{ "DarkShieldBottom",       EResINT32 },
	{ "DarkShieldColRange",       EResINT32 },
//	{ "",       EResINT32 },

	{ "SensorISO",        EResFLOAT32 },
	{ "FlareFactor",        EResFLOAT32 },
	{ "RGBtoYCC_Matrix",        EResFLOAT32 },
	{ "CCMatrix_Flag",        EResFLOAT32 },
	{ "LinLUT_ReductionFactor",        EResFLOAT32 },
	{ "BaseGain",        EResFLOAT32 },
	{ "ChromaBlur",        EResFLOAT32 },
	{ "ChromaSumShift",       EResINT32 },
	{ "SpatialDQ",        EResFLOAT32 },
	{ "ChromaDQ",        EResFLOAT32 },
	{ "ColorDQCamRGB",        EResFLOAT32 },
	{ "GrayAverageRange",        EResFLOAT32 },
	{ "FIR*",        EResFLOAT32 },
	{ "SensorTemperature",        EResFLOAT32 },
	{ "SensorBoardTemperature",        EResFLOAT32 },
	{ "TempGainFact",        EResFLOAT32 },
	{ "BadPixThreshLevels",        EResINT32 },
	{ "BadPixThreshTimes",        EResFLOAT32 },

	{ "CaptureAperture",        EResFLOAT32 },
	{ "CaptureExpComp",        EResFLOAT32 },
	{ "CaptureExpTime",        EResFLOAT32 },
	{ "CaptureISO",        EResFLOAT32 },
	{ "CaptureShutter",        EResFLOAT32 },

	{ "*_*CCMatrix",        EResFLOAT32 },
	{ "*_*Gain",        EResFLOAT32 },
	{ "*_CP*_Matrix",        EResFLOAT32 },
	{ "SaturationLevel",       EResINT32 },
	{ "RawSaturationLevel",       EResINT32 },
	{ "MaxOutputLevel",       EResINT32 },
	{ "DarkLevel",       EResINT32 },
	{ "DarkStddev",        EResFLOAT32 },
	{ "SpatialGain*",        EResFLOAT32 },
	{ "BadPixels",       EResINT32 },
	{ "CamToXYZ_Flash",        EResFLOAT32 },
	{ "PreviewGain",        EResFLOAT32 },
	{ "CalTemperature",        EResFLOAT32 },
	{ "ISOGainFact",        EResFLOAT32 },
	{ "AL_AWB_INITIAL_XY",        EResINT32 },

	{ "guiAlAwbColorSource",        EResINT32 },
	{ "guiAlAwbColorSourceAdobeRGB",  EResINT32 },

	{ "manualWB",        EResINT32 },
	{ "fsqRGB2XYZMatrix",        EResINT32 },
	{ "AL_AWB_*",       EResINT32 },
	{ "WB_CUSTOM_XY",        EResFLOAT32 },

	{ "aucOutXyMap",        EResUINT32 },
	{ "aucWhiteMap",        EResINT32 },
	{ "guiAlAwbXTblIndoor",        EResINT32 },
	{ "guiAlAwbXTblOutdoor",        EResINT32 },
	{ "guiAlAwbXTblOutdoorDark",        EResINT32 },
	{ "MCAS_sRGB*",       EResINT32 },
	{ "MCAS_AdobeRGB*",       EResINT32 },
//	{ "",        EResFLOAT32 },

	{ "WBCorrection_*", EResFLOAT32 },
};


EpNameAndType  _DPxMerrill_Types[] =
{
	{ "ContentVersionNumber",       EResINT32 },

	{ "SensorISO",        EResFLOAT32 },
	{ "SensorTemperature",          EResFLOAT32 },
	{ "SensorBoardTemperature",          EResFLOAT32 },
	{ "CaptureAperture",  EResFLOAT32 },
	{ "CaptureShutter",   EResFLOAT32 },
	{ "CaptureExpTime",   EResFLOAT32 },
	{ "CaptureExpComp",   EResFLOAT32 },
	{ "CaptureISO",       EResFLOAT32 },

	{ "ImageDepth",           EResINT32 },
	{ "DFSOffset",            EResINT32 },
	{ "RCPSBiases",           EResINT32 },

	{ "LinLUTBitDepth",       EResINT32 },
	{ "TempGainFact",     EResFLOAT32 },
	{ "FNumberGainFact",  EResFLOAT32 },
	{ "BadnessTable",     EResFLOAT32 },

	{ "SatMap?",          EResINT32 },

	{ "HighlightRestoreThresh", EResFLOAT32 },
	{ "HighlightChanThresh1",   EResFLOAT32 },
	{ "HighlightChanThresh2",   EResFLOAT32 },
	{ "HighlightSatFactor",     EResFLOAT32 },
	{ "CCMatrix_Flag",          EResFLOAT32 },
//	{ "",       EResINT32 },
//	{ "",     EResFLOAT32 },
	{ "*ColorShadingFactor",   EResFLOAT32 },
	{ "*WBGain",               EResFLOAT32 },
	{ "*CCMatrix",             EResFLOAT32 },
	{ "CP?Matrix",             EResFLOAT32 },
	{ "CMCM*",                 EResFLOAT32 },
	{ "CMCC*",                 EResFLOAT32 },

	{ "PGain",          EResFLOAT32 },
	{ "CP?_Matrix",          EResFLOAT32 },
	{ "WBXYValue",          EResFLOAT32 },

	{ "AESensorValueAmbient",          EResUINT32 },
	{ "AFSensorInformation",          EResUINT32 },
	{ "DefocusValue",          EResUINT32 },

	{ "HistBitDepth",          EResUINT32 },
	{ "Histogram",          EResUINT32 },
	{ "MaxHist",          EResUINT32 },
	{ "HistPercent",          EResFLOAT32 },

	{ "AutoAdjustHeadroom",          EResFLOAT32 },
	{ "AutoAdjustEXPCOMP",          EResFLOAT32 },

	{ "LensInformation",          EResUINT32 },

	{ "ChiefRayAngle",          EResFLOAT32 },

	{ "BurstExposureAdjust",          EResFLOAT32 },
	{ "DespAdjust",          EResINT32 },

	{ "TGMemData",          EResUINT32 },
	{ "RMPSData",          EResUINT32 },
	{ "PROMData",          EResUINT32 },


	{ "DenoisingSlidersStep",          EResFLOAT32 },
	{ "SliderContrastSensitivity",          EResFLOAT32 },
	{ "SliderShadowsSensitivity",          EResFLOAT32 },
	{ "SliderHighlightsSensitivity",          EResFLOAT32 },
	{ "EstimatedISOreference",          EResFLOAT32 },
	{ "EstimatedISOAWBGains",          EResINT32 },
	{ "EstimatedISONumOutliers",          EResINT32 },
	{ "EstimatedISODiffEV",          EResFLOAT32 },
	{ "EstimatedISOFlashAmbient",          EResFLOAT32 },

	{ "KeepImageArea",          EResINT32 },
	{ "ActiveImageArea",          EResINT32 },
	{ "DarkShieldTop",          EResINT32 },
	{ "DarkShieldBottom",          EResINT32 },
	{ "DarkShieldColRange",          EResINT32 },

	{ "ISONoiseSettings",          EResFLOAT32 },
	{ "ISONoiseSettingsFast",          EResFLOAT32 },
	{ "AdditionalISONoiseSettings",          EResFLOAT32 },
	{ "ResidualRCPS_Threshold",          EResFLOAT32 },
	{ "ResidualRCPS_ThresholdMap",          EResFLOAT32 },

	{ "BPBadnessOffset",          EResFLOAT32 },
	{ "BCBadnessOffset",          EResFLOAT32 },

	{ "BilateralFilterLUTBitDepth",          EResUINT32 },
	{ "SubSampleKernel",          EResFLOAT32 },
	{ "LocalMeanIters",          EResFLOAT32 },
	{ "EdgeMapScale",          EResFLOAT32 },
	{ "EdgeMapLinShift",          EResFLOAT32 },

	{ "ColorDQCamRGB",          EResFLOAT32 },

	{ "ResidualAnisoDiffK2",          EResFLOAT32 },
	{ "ResidualAnisoDiffIters2",          EResFLOAT32 },
	{ "ResidualAnisoDiffScaleNM",          EResFLOAT32 },
	{ "MinAnisoDiffScaleNMFactor",          EResFLOAT32 },
	{ "MaxAnisoDiffScaleNMFactor",          EResFLOAT32 },
	{ "ResidualEpsilon",          EResFLOAT32 },
	{ "MinResidualMixingFactor",          EResFLOAT32 },
	{ "MaxResidualMixingFactor",          EResFLOAT32 },

	{ "ColorNoiseModel",          EResFLOAT32 },

	{ "MinNoiseScalingFactor",          EResFLOAT32 },
	{ "MaxNoiseScalingFactor",          EResFLOAT32 },
	{ "TCGamma",          EResFLOAT32 },
	{ "TCStart",          EResFLOAT32 },
	{ "TCEnd",          EResFLOAT32 },
	{ "TCSteepness",          EResFLOAT32 },
	{ "SharpenLimit",          EResFLOAT32 },
	{ "TCAlpha",          EResFLOAT32 },
	{ "TCAlphaPower",          EResFLOAT32 },
	{ "TCColorModeSettings",          EResFLOAT32 },
	{ "TCDarkMinMax",          EResFLOAT32 },

	{ "SensorAdjustmentGainFact",     EResFLOAT32 },

	{ "ColumnOffsets_LV",          EResUINT32 },
//	{ "ColumnOffsets_MF",          EResUINT32 },
	{ "ColumnOffsets_MF",          0 },

	{ "SpatialGain_Fstop",     EResFLOAT32 },

/*
	{ "SpatialGain?_?_INF",    EResUINT32 },
	{ "SpatialGain?_?_MOD",    EResUINT32 },
*/
	{ "SpatialGain?_?_INF",    0 },  // Type=0 : don't show values
	{ "SpatialGain?_?_MOD",    0 },

	{ "SpatialGain?_LV_??_??", EResINT32 },
	{ "SpatialGain?_AFLV_28_??", EResINT32 },

	{ "BadClustersF20",          EResINT32 },
	{ "Jpeg_BadClusters",          EResINT32 },
	{ "CalTemperature",   EResFLOAT32 },

	{ "LinLUTs_?R",            EResINT32 },
	{ "CorrectColorGain_?R",   EResFLOAT32 },
	{ "RowOffsets_AB_*",       EResFLOAT32 },

	{ "BadPixelsF20",          EResINT32 },
	{ "BadPixelsF20_*",        0 },
};


static int _Foveon_SD_Load_RAW(EpDCamRAW* PData);
static int _Foveon_DP_Load_RAW(EpDCamRAW* PData);

static const EStrFilterTokens  _NameFilterTokens=
{
	.Single= "?", .Multi= "*", .SetStart= "[", .SetEnd= "]", .SetGrpStart= "(", .SetGrpSep= "|", .SetGrpEnd= ")"
};

enum
{
	EpSigmaDP = 1,
	EpSigmaDPMerrill
};

typedef struct
{
	float  SrcDstRGBs[3 * 6];
	float  CrossTalkMatrix[3 * 3];
	EBool  Active;
} EpFoveon;


typedef struct
{
	EIndex2    Size;

	union
	{
		uint8_t*   u8;
		uint32_t*  u32;
		float*     f32;
	} Pixels;
} EpFPixels;


static const float  _SigmaSD9_CrossTalkMatrix[3 * 3] =
{
   1.25,  0.0, -0.25,
  -2.50,  6.0, -2.50,
  -0.50,  0.0,  1.50
};

/*
static const float  _SigmaSD10_CrossTalkMatrix[3 * 3] =
{
	 0.100, 0.040, 0.048,
	 0.050, 0.100, 0.068,
	 0.015, 0.085, 0.100
};
*/

static const float  _SigmaSD10_CrossTalkMatrixInv[3 * 3] =
{
	 10.105364,   0.191570,  -4.980843,
	 -9.530650,  22.222221, -10.536397,
    6.585248, -18.917622,  19.157085
};


/*
static const float  _SigmaSD14_CrossTalkMatrix[3 * 3] =
{
	 0.100, 0.040, 0.048,
	 0.054, 0.100, 0.068,
	 0.015, 0.085, 0.100
};
*/

static const float  _SigmaSD14_CrossTalkMatrixInv[3 * 3] =
{
	9.422047,   0.178616,  -4.644042,
 -9.779280,  20.719572,  -9.395254,
  6.899080, -17.638428,  17.504467,
};


// DP1, DP2, DP3 (not Merrill!)
static const float _SigmaDPx_CrossTalkMatrix[3 * 3] =
{
/*
	1.00, 0.18, 0.05,  // R_obs = 1.00 * R_true + 0.18 * G_true + 0.05 * B_true
	0.42, 1.00, 0.20,  // G_obs = 1.00 * G_true + 0.09 * B_true
	0.00, 0.50, 1.00   // B_obs = 1.00 * B_true
*/

	 1.006642,  0.273137, -0.013877,
	 0.470798,  0.958640,  0.236884,
	-0.005939,  0.435700,  1.156192,
};


float _SigmaDPxMerrill_CrossTalkMatrix[3*3] =
{
	0.60, 0.05, 0.10,  // R_obs = 1.00 * R_true + 0.18 * G_true + 0.05 * B_true
	0.45, 1.00, 0.20,  // G_obs = 1.00 * G_true + 0.09 * B_true
	0.50, 0.20, 2.50   // B_obs = 1.00 * B_true

// Inverse of SunlightCCMatrix in my DP1M .X3F files
/*
	1.049,  0.393, -0.134,
	0.577,  0.698,  0.033,
	0.353,  0.606,  0.349
*/
};


static inline ushort _SGet2(const uint8_t* s, const uint16_t PByteOrder)
{
  if(PByteOrder == 0x4949)    // "II" means little-endian
    return s[0] | s[1] << 8;
  else    // "MM" means big-endian
    return s[0] << 8 | s[1];
}


static inline unsigned _SGet4(const uint16_t PByteOrder, const uint8_t* s)
{
	if(PByteOrder == 0x4949)
		return s[0] | s[1] << 8 | s[2] << 16 | s[3] << 24;
	else
		return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3];
}


static inline ushort _Get2(FILE* PFile, const uint16_t PByteOrder)
{
  uint8_t str[2] = { 0xff,0xff };
  const size_t  LR = fread(str, 1, 2, PFile);(void)LR;
  return _SGet2(str, PByteOrder);
}

static inline unsigned _Get4(FILE* PFile, const uint16_t PByteOrder)
{
  uint8_t str[4] = { 0xff,0xff,0xff,0xff };
  const size_t  LR = fread(str, 1, 4, PFile);(void)LR;
  return _SGet4(PByteOrder, str);
}


static inline char* _Foveon_GetS(FILE* PFile, const uint16_t PByteOrder, int offset, char* str, const int PLen)
{
	fseek(PFile, offset, SEEK_SET);
	int  LI = 0;for(;LI < PLen-1;LI++)
	{
		if((str[LI] = _Get2(PFile, PByteOrder)) == 0) break;
	 }
	str[LI] = 0;
	return str;
}


static void _Foveon_Huff(FILE* PFile, const uint16_t PByteOrder, ushort* huff)
{
  huff[0] = 8;
  for(int LI = 0;LI < 13;LI++)
  {
    int clen = getc(PFile);
    int code = getc(PFile);
    for(int LJ = 0;LJ < 256 >> clen;) huff[code+ ++LJ] = clen << 8 | LI;
  }
  _Get2(PFile, PByteOrder);
}


void _Foveon_Parse(FILE* PFile, EpDCamRAW* PData)
{
	int  LImg = 0, LPOff[256][2];

	const uint16_t  LByteOrder = 0x4949;  // Little-endian
	PData->ByteOrder = LByteOrder;
	fseek(PFile, 36, SEEK_SET);
	PData->RotatedDegrees = _Get4(PFile, LByteOrder);
	fseek(PFile, -4, SEEK_END);
	fseek(PFile, _Get4(PFile, LByteOrder), SEEK_SET);
	if(_Get4(PFile, LByteOrder) != 0x64434553) return;    // SECd

	int  LNEntries = (_Get4(PFile, LByteOrder),_Get4(PFile, LByteOrder));while(LNEntries--)
	{
		int        LOffset = _Get4(PFile, LByteOrder);
		const int  LLen = _Get4(PFile, LByteOrder);
		const int  LTag = _Get4(PFile, LByteOrder);
		int        save = ftell(PFile);
		fseek(PFile, LOffset, SEEK_SET);
		if(_Get4(PFile, LByteOrder) != (0x20434553 | (LTag << 24))) return;
		switch(LTag)
		{
			case 0x47414d49:    // IMAG
			case 0x32414d49:    // IMA2
				fseek(PFile, 8, SEEK_CUR);
				int  LPEnt = _Get4(PFile, LByteOrder);
				EIndex2  LSize;LSize.X = _Get4(PFile, LByteOrder);LSize.Y = _Get4(PFile, LByteOrder);
				if(LSize.X > PData->ISize.X && LSize.Y > PData->ISize.Y)
				{
					switch(LPEnt)
					{
						case  5:  PData->LoadFlags = TRUE;
						case  6:  PData->LoadFunc = _Foveon_SD_Load_RAW;break;
						case 30:  PData->LoadFunc = _Foveon_DP_Load_RAW;break;
						default:  break;
					}
					PData->ISize = LSize;
					PData->DataOffset = LOffset + 28;
				}
				fseek(PFile, LOffset + 28, SEEK_SET);
				if(fgetc(PFile) == 0xff && fgetc(PFile) == 0xd8 && PData->ThumbLength < LLen - 28)
				{
					PData->ThumbOffset = LOffset + 28;
					PData->ThumbLength = LLen - 28;
//				write_thumb = jpeg_thumb;
				}
				if(++LImg == 2 && !PData->ThumbLength)
				{
					PData->ThumbOffset = LOffset+24;
					PData->ThumbSize = LSize;
//				write_thumb = foveon_thumb;
				}
				break;

				case 0x464d4143:    // CAMF
					PData->MetaOffset = LOffset + 8;
					PData->MetaLength = LLen - 28;
				break;

				case 0x504f5250:    // PROP
					LPEnt = (_Get4(PFile, LByteOrder),_Get4(PFile, LByteOrder));
					fseek(PFile, 12, SEEK_CUR);
					LOffset += LPEnt * 8 + 24;
					if((unsigned) LPEnt > 256) LPEnt=256;
					for(int LI = 0;LI < LPEnt * 2;LI++) ((int*)LPOff)[LI] = LOffset + _Get4(PFile, LByteOrder) * 2;
					for(int LI = 0;LI < LPEnt;LI++)
					{
						char  name[64];_Foveon_GetS(PFile, LByteOrder, LPOff[LI][0], name, 64);
						char  value[64];_Foveon_GetS(PFile, LByteOrder, LPOff[LI][1], value, 64);
						if(EStr_Equal(name, "ISO")) PData->ISOSpeed = atoi(value);
						else if(EStr_Equal(name, "CAMMANUF"))
						{
							if(EStr_Equal(value, "SIGMA")) strcpy(PData->Make, "Sigma");
							else strcpy(PData->Make, value);
						}
						else if(EStr_Equal(name, "CAMMODEL"))
						{
							if(strncmp(value, "SIGMA ", 6) == 0) strcpy(PData->Model, value + 6);
							else strcpy(PData->Model, value);
if(PData->Verbosity > 1) { printf("%s() %s:%d  %s%s%s %s -> [%s]\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, name, ECodeNormal, value, PData->Model);fflush(stdout); }
						}
						else if(EStr_Equal(name, "WB_DESC")) strcpy(PData->WBName, value);
						else if(EStr_Equal(name, "TIME")) PData->TimeStamp = atoi(value);
						else if(EStr_Equal(name, "EXPTIME")) PData->Shutter = atoi(value) / 1000000.0;
						else if(EStr_Equal(name, "APERTURE")) PData->Aperture = atof(value);
						else if(EStr_Equal(name, "FLENGTH")) PData->FocalLength = atof(value);
					}
#ifdef LOCALTIME
					PData->TimeStamp = mktime (gmtime (&timestamp));
#endif
		}
		fseek(PFile, save, SEEK_SET);
	}
}


EIndex _Foveon_GetPropList(const uint8_t* PMetaData, const unsigned PMetaLength,  const uint16_t PByteOrder,  const char* PropListName,  ECharPtrListA* PResult)
{
	EIndex  LN = 0;
	const uint8_t*  LPos;
	unsigned  LI = 0;for(;LI < PMetaLength;LI += _SGet4(PByteOrder, LPos + 8))
	{
		LPos = PMetaData + LI;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		const char*  LLName = (char*)LPos + _SGet4(PByteOrder, LPos + 12);
		if(!EStr_Equal(LLName, PropListName)) continue;

		if(LPos[3] != 'P')
		{
			printf("\n");fflush(stdout);
			continue;
		}

		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);
		unsigned        LNum = _SGet4(PByteOrder, LCP);
		const uint8_t*  LDP = LPos + _SGet4(PByteOrder, LCP + 4);
		while(LNum--)
		{
			LCP += 8;
			const char*  LName = (char*)LDP + _SGet4(PByteOrder, LCP);

			if(EStr_Equal(LName, LLName)) continue;  // Self-reference to the parameter list. E.g. IncludeBlocks

			char*  LStr = EStrDup((char*)LName);
			if(PResult) EList_PointerAddA_p(PResult, 16, LStr);else LN++;
		}
	}

	if(PResult) return(PResult->Count);
	else return(LN);
}


//========================================================
// This does not make a copy, for efficiency reasons
//========================================================
static const char* _Foveon_GetProp(const uint8_t* PMetaData, const unsigned PMetaLength, const uint16_t PByteOrder,  const char* PropListName, const char* PPropName)
{
	const uint8_t*  LPos;
	unsigned  LI = 0;for(;LI < PMetaLength;LI += _SGet4(PByteOrder, LPos + 8))
	{
		LPos = PMetaData + LI;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		const char*  LLName = (char*)LPos + _SGet4(PByteOrder, LPos + 12);
		if(!EStr_Equal(LLName, PropListName)) continue;

		if(LPos[3] != 'P')
		{
			printf("\n");fflush(stdout);
			continue;
		}

		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);
		unsigned        LNum = _SGet4(PByteOrder, LCP);
		const uint8_t*  LDP = LPos + _SGet4(PByteOrder, LCP + 4);
		while(LNum--)
		{
			LCP += 8;
			const char*  LName = (char*)LDP + _SGet4(PByteOrder, LCP);

			if(EStr_Equal(LName, LLName)) continue;  // Self-reference to the parameter list. E.g. IncludeBlocks
			if(EStr_Equal(LName, PPropName)) return((char*)LDP + _SGet4(PByteOrder, LCP + 4));
		}
	}

	return(NULL);
}


//========================================================================
// Load an N x M x O matrix property into a buffer allocated here
//========================================================================
int _Foveon_GetMatrix(const uint8_t* PMetaData, const unsigned PMetaLength, const uint16_t PByteOrder,  const char* PName, unsigned PDim[3], void* PResult)
{
	const uint8_t*  LPos;unsigned  LIdx = 0;for(;LIdx < PMetaLength;LIdx += _SGet4(PByteOrder, LPos + 8))
	{
		LPos = PMetaData + LIdx;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		if(LPos[3] != 'M') continue;
		if(!EStr_Equal(PName, (char*)LPos + _SGet4(PByteOrder, LPos + 12))) continue;
		PDim[0] = PDim[1] = PDim[2] = 1;
		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);
		const unsigned  LType = _SGet4(PByteOrder, LCP);

		const unsigned  LNDim = _SGet4(PByteOrder, LCP + 4);
		if(LNDim > 3)
		{
			break;
		}
		const uint8_t*  LDP = LPos + _SGet4(PByteOrder, LCP + 8);

		{
			unsigned  LI = LNDim;for(;LI--;)
			{
				LCP += 12;
				PDim[LI] = _SGet4(PByteOrder, LCP);
			}
		}

		const EIndex  LSize = (EIndex)PDim[0] * (EIndex)PDim[1] * (EIndex)PDim[2];
/*
		const EIndex  LBytesRemaining = PMetaLength - (LDP - PMetaData);
		if(LSize * 4 > LBytesRemaining)
		{
printf("%s() %s:%d  %sNot enough data left in buffer%s %d x %d x %d  %lld %lld\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal,  XYZ_a(PDim), (long long)LSize * 4, (long long)LBytesRemaining);fflush(stdout);
			if(PResult) *((unsigned**)PResult) = NULL;
			return(E_PREMATURE_END_OF_DATA);
		}
*/

		if(PResult == NULL) return(E_SUCCESS);  // Caller was only interested if this property exists

		unsigned*  LMat = (unsigned*)EMalloc((LSize) * 4);
		if(LMat == NULL)
		{
			*((unsigned**)PResult) = NULL;
			return(E_NO_MEMORY);
		}
		unsigned  LI = 0;for(;LI < LSize;LI++)
		{
			if(LType && LType != 6) LMat[LI] = _SGet4(PByteOrder, LDP + LI * 4);
			else LMat[LI] = _SGet4(PByteOrder, LDP + LI * 2) & 0xffff;
		}
		*((unsigned**)PResult) = LMat;
		return(E_SUCCESS);
	}
	if(PResult) *((unsigned**)PResult) = NULL;
	return(E_NOT_FOUND);
}


//================================================================
// Load a property with a known size into a pre-allocated buffer
//================================================================
int _Foveon_GetFixed(const uint8_t* PMetaData, const unsigned PMetaLength, const uint16_t PByteOrder,  const char* PName, const int PVSize,  void* PPtr)
{
  if(PName == NULL) return(0);

	const uint8_t*  LPos;unsigned  LIdx = 0;for(;LIdx < PMetaLength;LIdx += _SGet4(PByteOrder, LPos + 8))
	{
		LPos = PMetaData + LIdx;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		if(LPos[3] != 'M') continue;
		if(!EStr_Equal(PName, (char*)LPos + _SGet4(PByteOrder, LPos + 12))) continue;

		unsigned  LDim[3];LDim[0] = LDim[1] = LDim[2] = 1;

		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);
		const unsigned  LType = _SGet4(PByteOrder, LCP);

		const unsigned  LNDim = _SGet4(PByteOrder, LCP + 4);if(LNDim > 3) break;
		const uint8_t*  LDP = LPos + _SGet4(PByteOrder, LCP + 8);

		{
			unsigned  LI = LNDim;for(;LI--;)
			{
				LCP += 12;
				LDim[LI] = _SGet4(PByteOrder, LCP);
			}
		}

		const EIndex  LBytesRemaining = PMetaLength - (LDP - PMetaData);
		const EIndex  LSize = (EIndex)LDim[0] * (EIndex)LDim[1] * (EIndex)LDim[2];if(LSize * 4 > LBytesRemaining) return(0);
		if(LSize != PVSize) return(0);
		unsigned  LI = 0;unsigned*  LMat = (unsigned*)PPtr;for(;LI < LSize;LI++)
		{
			if(LType && LType != 6) LMat[LI] = _SGet4(PByteOrder, LDP + LI * 4);
			else LMat[LI] = _SGet4(PByteOrder, LDP + LI * 2) & 0xffff;
		}
		return(1);
	}
	return(0);
}


//================================================================================
// Get a text property. This does not make a copy, for efficiency reasons
//================================================================================
const void* _Foveon_CamF_Text(const uint8_t* PMetaData, const unsigned PMetaLength, const uint16_t PByteOrder,  const char* PName)
{
	const uint8_t*  LPos;unsigned  LIdx = 0;for(;LIdx < PMetaLength;LIdx += _SGet4(PByteOrder, LPos + 8))
	{
		LPos = PMetaData + LIdx;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		if(LPos[3] != 'T') continue;
		if(strcmp(PName, (char*)LPos + _SGet4(PByteOrder, LPos + 12))) continue;

		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);

		return(LCP + 4);
	}
	return(NULL);
}


//========================================================================
// Create a LUT. Element 0 is the length (excluding element 0 itself)
//========================================================================
static int16_t* _Foveon_MakeCurve(const double PMax, const double PMul, const double PFilt)
{
  double    LFilt = PFilt;if(LFilt == 0.0) LFilt = 0.8;
  unsigned  LSize = 4.0 * M_PI * PMax / LFilt;
  if(LSize == UINT_MAX) LSize--;

//printf("%s() %s:%d  %sPMax%s %f  %sLSize%s %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, PMax,  ECodeGreen, ECodeNormal, LSize);fflush(stdout);

  int16_t*  LCurve = (int16_t*)ECalloc(LSize + 1, sizeof(*LCurve));
  LCurve[0] = LSize;
  unsigned  LI = 0;for(;LI < LSize;LI++)
  {
  	const double  LVI = (double)LI;
    const double  LX = LVI * LFilt / PMax / 4.0;
    LCurve[LI + 1] = ((cos(LX) + 1.0) / 2.0 * tanh(LVI * LFilt / PMul) * PMul + 0.5);
  }
  return(LCurve);
}


//========================================================================
// Create 3 LUTs.
//========================================================================
static void _Foveon_MakeCurves(int16_t** PCurveP, const float PDQ[3], const float PDiv[3], const float PFilt)
{
  double  LMul[3];
  FORC3 LMul[LCI] = PDQ[LCI] / PDiv[LCI];

  double  LMax = 0.0;
  FORC3 if(LMax < LMul[LCI]) LMax = LMul[LCI];
  FORC3 PCurveP[LCI] = _Foveon_MakeCurve(LMax, LMul[LCI], PFilt);
}


//========================================================================
// Apply a LUT
//========================================================================
static inline int _Foveon_ApplyCurve(const int16_t* PCurve, const int PV)
{
  if(abs(PV) >= PCurve[0]) return(0);  // Check for index range
  return(PV < 0 ? -PCurve[1 - PV] : PCurve[1 + PV]);
}


static inline float _ClampF(const float PValue)
{
	if(PValue < 0.0f) return 0.0;
	if(PValue > 1.0f) return 1.0;
	return(PValue);
}


//========================================
// Foveon sensor color conversion
//========================================
void _FoveonToRGB(const int PCameraID, const EIndex PSizeX, const EIndex PSizeY,  const float PBias[3], const int PSaturationLevel[3],  const float* PInvCrossTalkMatrix,  const int16_t* PSrcPixels,  int16_t* PDstPixels)
{
	const EIndex  LNPixels = PSizeX * PSizeY;
	const float   LNorm[3] = { 1.0 / 65535.0, 1.0 / 65535.0, 1.0 / 65535.0 };

//printf("%s() %s:%d  %sPSaturationLevel%s  %04x:%04x:%04x\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PSaturationLevel));fflush(stdout);
	const float   LTBias[3] = { 0.0, 0.0, 0.0 };
	const float*  LBias = PBias ? PBias : LTBias;

	{
		EIndex  LI = 0;const int16_t*  LSrcPixel = PSrcPixels;int16_t*  LDstPixel = PDstPixels;for(;LI < LNPixels;LI++, LSrcPixel += 3, LDstPixel += 3)
		{
			const EIndex  LCN = 3;

//			EBool  LSaturated[3];{ EIndex  LCI = 0;const EIndex  LCN = 3;for(;LCI < LCN;LCI++) LSaturated[LCI] = LSrcPixel[LCI] >= PSaturationLevel[LCI]; }
			EBool  LSaturated = FALSE;{ EIndex  LCI = 0;for(;LCI < LCN;LCI++) if(LSrcPixel[LCI] >= PSaturationLevel[LCI]) { LSaturated = TRUE;break; } }

			if(LSaturated)
			{
				uint16_t*  LD = (uint16_t*)LDstPixel;
				EIndex  LCI = 0;for(;LCI < LCN;LCI++) LD[LCI] = 0xFFFF;
			}
			else
			{
				const float  LRaw[3] = { (float)LSrcPixel[0] * LNorm[0], (float)LSrcPixel[1] * LNorm[1], (float)LSrcPixel[2] * LNorm[2] };
// Cross-talk removal
//
				float  LRGB[3];EMatrix_Mult3x3_f32(PInvCrossTalkMatrix, LRaw, LRGB);

				{
					EIndex  LCI = 0;for(;LCI < LCN;LCI++)
					{
						LRGB[LCI] -= LBias[LCI];
						LRGB[LCI] = _ClampF(LRGB[LCI]);
						LDstPixel[LCI] = LRGB[LCI] * 65535.0;

//					if(LDstPixel[LCI] < 1) LDstPixel[LCI] = 1;
//LDstPixel[LCI] = 0;
					}
				}
			}
		}
	}
}


#define FoveonSTRIDE  6
static float _Foveon_DPx_InOutRGB[]=
{
//FovR,FovG,FovB, R,G,B
/*
0.009961, 0.007629, 0.004575,  0.012, 0.00, 0.000,
0.004819, 0.007238, 0.006177,  0.000, 0.12, 0.000,
0.002775, 0.005569, 0.007349,  0.000, 0.00, 0.012,
*/

/*
// Results for RGB with (inverse of) _SigmaDPx_CrossTalkMatrix
0.009961, 0.007629, 0.004575,  0.00924, 0.00315, 0.00300,
0.004819, 0.007238, 0.006177,  0.00375, 0.00492, 0.00372,
0.002775, 0.005569, 0.007349,  0.00183, 0.00370, 0.00500,
*/

// Slightly less saturated than above and 10x for better precision
0.09961, 0.07629, 0.04575,  0.0800, 0.0300, 0.0300,
0.04819, 0.07238, 0.06177,  0.0300, 0.0500, 0.0372,
0.02775, 0.05569, 0.07349,  0.0250, 0.0300, 0.0500,

0.04019, 0.09335, 0.12254,  0.00, 0.12, 0.012,
0.10682, 0.08674, 0.06318,  0.12, 0.00, 0.012,
0.20836, 0.22106, 0.15178,  0.12, 0.12, 0.000,
};


//========================================
// Monte Carlo minimization
//========================================
static inline void _Foveon_IterateMatrix(float PMatrix[9])
{
ETime  LPrev_us = E_GetMicroseconds();
	float  LMatrix[9];EClr(LMatrix);LMatrix[M3x00] = LMatrix[M3x11] = LMatrix[M3x22] = 1.0;

	double  LBestRMSE = 1000.0;
	const EIndex  LNWeights = 6;
	float   LBestWeights[LNWeights];EClr(LBestWeights);
	ERandomState  LRandomState[5];E_RandomInit(42, LRandomState);
	const EIndex  LTrainingSamplesStride = 6;
	const EIndex  LNTrainingSamples = ECount(_Foveon_DPx_InOutRGB) / LTrainingSamplesStride;
	const float*  LTrainingSamples = _Foveon_DPx_InOutRGB;
	EIndex  LMCI = 0;const EIndex  LMCN = 100000000;for(;LMCI < LMCN;LMCI++)
	{
		float  LWeights[LNWeights];EClr(LWeights);
		{ EIndex  LI = 0;for(;LI < LNWeights;LI++) LWeights[LI] = E_RandomDN(LRandomState) * 0.5; }
// Auto-gradient
//		EIndex  LAGI = 0;const EIndex  LAGN = 10;for(;LAGI < LAGN;LAGI++)
		{
			double  LRMSE = 0.0;
			EIndex  LI = 0;const float*  LSample = LTrainingSamples;for(;LI < LNTrainingSamples;LI++, LSample += LTrainingSamplesStride)
			{
				EIndex  LWI = 0;
				LMatrix[1] = LWeights[LWI++];
				LMatrix[2] = LWeights[LWI++];
				LMatrix[3] = LWeights[LWI++];
				LMatrix[5] = LWeights[LWI++];
				LMatrix[6] = LWeights[LWI++];
				LMatrix[7] = LWeights[LWI++];

//memcpy(LMatrix, _SigmaDPx_CrossTalkMatrix, sizeof(_SigmaDPx_CrossTalkMatrix));

				float  LMatrixInv[9];EMatrix_Invert3x3_f32(LMatrix, LMatrixInv);

				float         LInferred[3];EMatrix_Mult3x3_f32(LMatrixInv, LSample + 0, LInferred);
				const float*  LGroundTruth = LSample + 3;

				Ec_SaturateRGBf((EcRGBf32Color*)LInferred, 3.0, (EcRGBf32Color*)LInferred);
				{ EIndex  LCI = 0;for(;LCI < 3;LCI++) { const float  LV = sqrtf(fabsf(LGroundTruth[LCI] - LInferred[LCI]));LRMSE += LV; } }
				if(LBestRMSE > LRMSE)
				{
printf("%s() %s:%d  %sRMSE%s %f -> %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LBestRMSE, LRMSE);fflush(stdout);
					LBestRMSE = LRMSE;
					memcpy(LBestWeights, LWeights, sizeof(float) * LNWeights);
				}
			}
		}
	}

	{
		const float*  LWeights = LBestWeights;
		EIndex  LWI = 0;
		LMatrix[1] = LWeights[LWI++];
		LMatrix[2] = LWeights[LWI++];
		LMatrix[3] = LWeights[LWI++];
		LMatrix[5] = LWeights[LWI++];
		LMatrix[6] = LWeights[LWI++];
		LMatrix[7] = LWeights[LWI++];
	}

//	float  LMatrix1[9];EMatrix_Invert3x3_f32(LMatrix, LMatrix1);
printf("%s() %s:%d  %sRMSE min%s %f  %sLMatrix1%s\n% f, % f, % f,\n% f, % f, % f,\n% f, % f, % f,\n\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal,  LBestRMSE,  ECodeGreen, ECodeNormal, XYZ_a(LMatrix + 0), XYZ_a(LMatrix + 3), XYZ_a(LMatrix + 6));fflush(stdout);
{ const ETime  LNow_us = E_GetMicroseconds();char  LTStr[256];EStr_MicrosecondsToString(LNow_us - LPrev_us, LTStr, FALSE);LPrev_us = LNow_us;printf("%s() %s:%d  %s\n", __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout); }

	memcpy(PMatrix, LMatrix, sizeof(float) * 9);
}


//========================================================================================
// Invert a matrix with Gaussian elimination
//
// Arguments
//  PInput      Input matrix
//  PDim        Matrix size (PDim x PDim)
//  PInverse    Return the result here
//
// Description
//  Inverts an NxN matrix with Gaussian elimination, if it's invertible.
//  Otherwise it returns an error and PInverse is not set.
//  Not tested yet with matrices larger than 3x3.
//
// Return value
//  E_SUCCESS (and PInverse filled in), or an error such as E_INVALID_PARAMETERS
//========================================================================================
int EMatrix_InvertGE3x3_f32(const float* PInput, const int PDim,  float* PInverse)
{
	float  LAugmented[PDim][PDim * 2];

// Initialize augmented matrix [input | identity]
	for(int LI = 0;LI < PDim;LI++)
	{
		int  LJ = 0;for(;LJ < PDim;LJ++)
		{
			LAugmented[LI][LJ] = PInput[LI * PDim + LJ];
			LAugmented[LI][LJ + PDim] = (LI == LJ) ? 1.0f : 0.0f;
		}
	}

// Gaussian elimination with partial pivoting
	for(int  LI = 0;LI < PDim;LI++)
	{
		int  LMaxRow = LI;
// Find pivot
		float  LMax = fabs(LAugmented[LI][LI]);
		{
			int  LK = LI + 1;for(;LK < PDim;LK++)
			{
				if(fabs(LAugmented[LK][LI]) > LMax)
				{
					LMax = fabs(LAugmented[LK][LI]);
					LMaxRow = LK;
				}
			}
		}

// Check for singular matrix
		if(LMax < 1e-10)
		{
			printf("Error: Matrix is singular or nearly singular\n");
			return(E_INVALID_PARAMETERS);
		}

// Swap rows
		if(LMaxRow != LI)
		{
			int  LJ = 0;for(;LJ < 6;LJ++)
			{
				float  LTmp = LAugmented[LI][LJ];
				LAugmented[LI][LJ] = LAugmented[LMaxRow][LJ];
				LAugmented[LMaxRow][LJ] = LTmp;
			}
		}

// Scale pivot row
		const float  LPivot = LAugmented[LI][LI];
		{
			int  LJ = 0;for(;LJ < 6;LJ++)
			{
				LAugmented[LI][LJ] /= LPivot;
			}
		}

// Eliminate column
		for(int  LK = 0;LK < PDim;LK++)
		{
			if(LK != LI)
			{
				float  LFactor = LAugmented[LK][LI];
				int  LJ = 0;for(;LJ < 6;LJ++)
				{
					LAugmented[LK][LJ] -= LFactor * LAugmented[LI][LJ];
				}
			}
		}
	}

// Copy inverse to output
	for(int  LI = 0;LI < PDim;LI++)
	{
		int  LJ = 0;for(;LJ < PDim;LJ++)
		{
			PInverse[LI * PDim + LJ] = LAugmented[LI][LJ + PDim];
		}
	}
	return(E_SUCCESS);
}


//================================================================================================
// Generate 3x3 matrix that transforms the given input vectors to the given output vectors
//================================================================================================
int E_Float3sToMatrix3x3(const float* PIn0, const float* PIn1, const float* PIn2,  const float* POut0, const float* POut1, const float* POut2,  float* PMatrix)
{
	const int  LDim = 3;

// Input matrix
	const float LInput[] =
	{
		PIn0[0], PIn1[0], PIn2[0],
		PIn0[1], PIn1[1], PIn2[1],
		PIn0[2], PIn1[2], PIn2[2]
	};

// Output matrix
	const float  LOutput[] =
	{
		POut0[0], POut1[0], POut2[0],
		POut0[1], POut1[1], POut2[1],
		POut0[2], POut1[2], POut2[2]
	};

// Compute inverse of input matrix
	float  LInverse[3 * 3];
	{ const int  LResult = EMatrix_InvertGE3x3_f32(LInput, LDim,  LInverse);if(LResult != E_SUCCESS) return(LResult); }
//	EMatrix_Invert3x3_f32(LInput, LInverse);

// Transformation matrix: output * inverse(input)
	for(int  LI = 0;LI < LDim;LI++)
	{
		int  LJ = 0;for(;LJ < LDim;LJ++)
		{
			PMatrix[LI * LDim + LJ] = 0.0;
			int  LK = 0;for(;LK < LDim;LK++)
			{
				PMatrix[LI * LDim + LJ] += LOutput[LI * LDim + LK] * LInverse[LK * LDim + LJ];
			}
		}
	}

	return(E_SUCCESS);
}


static inline void _Foveon_SolveMatrix(const float* PInOutRGB, float PMatrix[9])
{
	E_Float3sToMatrix3x3(PInOutRGB, PInOutRGB + FoveonSTRIDE, PInOutRGB + FoveonSTRIDE * 2,  PInOutRGB + 3, PInOutRGB + 3 + FoveonSTRIDE, PInOutRGB + 3 + FoveonSTRIDE * 2,  PMatrix);

//printf("%s() %s:%d  %sPMatrix1%s\n% f, % f, % f,\n% f, % f, % f,\n% f, % f, % f,\n\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PMatrix + 0), XYZ_a(PMatrix + 3), XYZ_a(PMatrix + 6));fflush(stdout);
//	EMatrix_Invert3x3_f32(PMatrix, PMatrix);
	EMatrix_InvertGE3x3_f32(PMatrix, 3, PMatrix);
printf("%s() %s:%d  %sPMatrix1%s\n% f, % f, % f,\n% f, % f, % f,\n% f, % f, % f,\n\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PMatrix + 0), XYZ_a(PMatrix + 3), XYZ_a(PMatrix + 6));fflush(stdout);
}


typedef struct
{
	const uint8_t*  Buffer;
	EIndex          Length, Index;
} EMemReader;


static inline int EMemReader_getc(EMemReader* PMemReader)
{
	if(PMemReader->Index >= PMemReader->Length) return(EOF);
	return(PMemReader->Buffer[PMemReader->Index++]);
}


static inline unsigned _GetBitHuff(FILE* PFile, const uint32_t PZeroAfterFF, const int PNBits, const uint16_t* PHuff)
{
	static unsigned  LBitBuf = 0;
	static int       LVBits = 0, LReset = 0;

	if(PNBits > 25) return 0;
	if(PNBits < 0) return LBitBuf = LVBits = LReset = 0;
	if(PNBits == 0 || LVBits < 0) return 0;

	{
		unsigned  LCh;
		while(!LReset && LVBits < PNBits && (LCh = fgetc(PFile)) != EOF && !(LReset = PZeroAfterFF && LCh == 0xff && fgetc(PFile)))
		{
			LBitBuf = (LBitBuf << 8) + (uint8_t)LCh;
			LVBits += 8;
		}
	}

	unsigned  LCh = LBitBuf << (32 - LVBits) >> (32 - PNBits);
	if(PHuff)
	{
		LVBits -= PHuff[LCh] >> 8;
		LCh = (uint8_t)PHuff[LCh];
	}
	else LVBits -= PNBits;
//	if(LVBits < 0) derror();
	return(LCh);
}


static inline unsigned getbithuff_mem(EMemReader* PMemReader, const uint32_t PZeroAfterFF, const int PNBits, const uint16_t* PHuff)
{
	static unsigned  LBitBuf = 0;
	static int       LVBits = 0, LReset = 0;

	if(PNBits > 25) return 0;
	if(PNBits < 0) return LBitBuf = LVBits = LReset = 0;
	if(PNBits == 0 || LVBits < 0) return 0;

	{
		unsigned  LCh;
		while(!LReset && LVBits < PNBits && (LCh = EMemReader_getc(PMemReader)) != EOF && !(LReset = PZeroAfterFF && LCh == 0xff && EMemReader_getc(PMemReader)))
		{
			LBitBuf = (LBitBuf << 8) + (uint8_t)LCh;
			LVBits += 8;
		}
	}

	unsigned  LCh = LBitBuf << (32 - LVBits) >> (32 - PNBits);
	if(PHuff)
	{
		LVBits -= PHuff[LCh] >> 8;
		LCh = (uint8_t) PHuff[LCh];
	}
	else LVBits -= PNBits;
//	if(LVBits < 0) derror();
	return(LCh);
}


static inline int _ljpeg_diff(FILE* PFile, const uint32_t PDNGVersion, const uint32_t PZeroAfterFF, const uint16_t* PHuff)
{
	const int  LLen = _GetBitHuff(PFile, PZeroAfterFF, *PHuff, PHuff + 1);if(LLen == 16 && (!PDNGVersion || PDNGVersion >= 0x1010000)) return(-32768);
	int  LDiff = _GetBitHuff(PFile, PZeroAfterFF, LLen, 0);
	if((LDiff & (1 << (LLen - 1))) == 0) LDiff -= (1 << LLen) - 1;
	return(LDiff);
}


static inline int ljpeg_diff_mem(EMemReader* PMemReader, const uint32_t PDNGVersion, const uint32_t PZeroAfterFF, const uint16_t* PHuff)
{
	const int  LLen = getbithuff_mem(PMemReader, PZeroAfterFF, *PHuff, PHuff + 1);if(LLen == 16 && (!PDNGVersion || PDNGVersion >= 0x1010000)) return(-32768);
	int        LDiff = getbithuff_mem(PMemReader, PZeroAfterFF, LLen, 0);
	if((LDiff & (1 << (LLen - 1))) == 0) LDiff -= (1 << LLen) - 1;
	return(LDiff);
}


typedef struct
{
	EThreadSetCore;

	EIndex2          Size;
	const uint8_t*   SrcBuffer;EIndex  SrcBufferSize;
	EIndex           FilePos;
	EIndex           DataOffset;
	uint32_t         DNGVersion, ZeroAfterFF;
	const uint16_t*  Huff;
	unsigned*        ROff;
	EpPixelBuffer*   DstPixelBuffer;
} EpDCamThreadSet;


//================================================
// Main function for multi-threaded rendering
//================================================
static void* _Foveon_Load_Thread(void* PP)
{
	EThread*          LThread = (EThread*)PP;
	EpDCamThreadSet*  LThreadSet = (EpDCamThreadSet*)LThread->ThreadSet;
	const EIndex2     LSize = LThreadSet->Size;
	const uint32_t    LDNGVersion = LThreadSet->DNGVersion;
	const uint32_t    LZeroAfterFF = LThreadSet->ZeroAfterFF;
	const uint8_t*    LBuffer = LThreadSet->SrcBuffer;
	const EIndex      LBytesRemaining = LThreadSet->SrcBufferSize;
	EpPixelBuffer*    LPixelBuffer = LThreadSet->DstPixelBuffer;
	const unsigned*   LROff = LThreadSet->ROff;

//if(LThread->Number != 0) return(NULL);
	const int  LChannelI = LThread->Number;const int  LChannelN = 3;
	{
		EMemReader  LMemReader = { .Buffer= LBuffer, .Length= LBytesRemaining, .Index= LThreadSet->DataOffset + LROff[LChannelI] - LThreadSet->FilePos };

	  uint16_t  LHuff[512];memcpy(LHuff, LThreadSet->Huff, sizeof(LHuff));
		getbithuff_mem(&LMemReader, LZeroAfterFF, -1, NULL);

	  uint16_t  LVPRed[2][2], LHPRed[2];
		LVPRed[0][0] = LVPRed[0][1] = LVPRed[1][0] = LVPRed[1][1] = 512;

		unsigned  LRow = 0;uint16_t*  LDstPixel = LPixelBuffer->u16;for(;LRow < LSize.Y;LRow++)
		{
			unsigned  LCol = 0;for(;LCol < LSize.X;LCol++, LDstPixel += LChannelN)
			{
				const unsigned  LDiff = ljpeg_diff_mem(&LMemReader, LDNGVersion, LZeroAfterFF, LHuff);
				if(LCol < 2) LHPRed[LCol] = LVPRed[LRow & 1][LCol] += LDiff;
				else LHPRed[LCol & 1] += LDiff;
				LDstPixel[LChannelI] = LHPRed[LCol & 1];
			}
		}
	}

	return(NULL);
}


// TESTME
static inline void _SwapBytes2(uint16_t* PData, const EIndex PNBytes)
{
	EIndex  LI = 0;const EIndex  LN = PNBytes >> 1;for(;LI < LN;LI++)
	{
		const uint16_t  LV = PData[LI];
		PData[LI] = (LV << 8) | (LV >> 8);
	}
}


static inline void _ReadShorts(FILE* PFile, const uint16_t PByteOrder, uint16_t* PPixel, const int PCount)
{
	if(fread(PPixel, 2, PCount, PFile) < PCount)
	{
//		derror();
printf("%s() %s:%d  %sERROR%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
	}
	if((PByteOrder == 0x4949) == (ntohs(0x1234) == 0x1234)) _SwapBytes2(PPixel, PCount * 2);
}


typedef struct EpFvDecode_S   EpFvDecode;

struct EpFvDecode_S
{
  EpFvDecode*  Branch[2];
  int          Leaf;
};

typedef struct
{
	EpFvDecode   FirstDecode[2048];
	EpFvDecode*  SecondDecode;
	EpFvDecode*  FreeDecode;
} EpFvDecoder;


//================================================
// Decode pixel data from Sigma SD cameras
//================================================
static void _Foveon_SDDecoder(EpDCamRAW* PData, EpFvDecoder* PDecoder,  const unsigned PDSize, const unsigned PCode)
{
	static unsigned  LHuff[1024];
	FILE*            LFile = PData->File;

	if(!PCode)
	{
		int  LI = 0;for(;LI < PDSize;LI++) LHuff[LI] = _Get4(LFile, PData->ByteOrder);
		memset(PDecoder->FirstDecode, 0, sizeof(PDecoder->FirstDecode));
		PDecoder->FreeDecode = PDecoder->FirstDecode;
	}
	EpFvDecode*  LCur = PDecoder->FreeDecode++;
	if(PDecoder->FreeDecode > PDecoder->FirstDecode + 2048)
	{
/*
		fprintf(stderr,_("%s: decoder table overflow\n"), ifname);
		longjmp(failure, 2);
*/
printf("%s() %s:%d  %sERROR%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
return;
	}

	if(PCode)
	{
		int LI = 0;for(;LI < PDSize;LI++)
		{
			if(LHuff[LI] == PCode)
			{
				LCur->Leaf = LI;
				return;
			}
		}
	}
	const int       LLen = PCode >> 27;if(LLen > 26) return;
	const uint32_t  LCode = (LLen +1) << 27 | (PCode & 0x3ffffff) << 1;

	LCur->Branch[0] = PDecoder->FreeDecode;
	_Foveon_SDDecoder(PData, PDecoder,  PDSize, LCode);
	LCur->Branch[1] = PDecoder->FreeDecode;
	_Foveon_SDDecoder(PData, PDecoder,  PDSize, LCode + 1);
}


//================================================
// Load pixel data from Sigma SD cameras
//================================================
static int _Foveon_SD_Load_RAW(EpDCamRAW* PData)
{
ETime  LPrev_us = E_GetMicroseconds();
	EpPixelBuffer*  LPixelBuffer = PData->PixelBuffer;if(LPixelBuffer)
	{
		FILE*           LFile = PData->File;
		int16_t         LDiff[1024];
		const uint16_t  LByteOrder = PData->ByteOrder;

		_ReadShorts(LFile, LByteOrder,  (uint16_t*)LDiff, 1024);

		EpFvDecoder     LDecoder;
		if(!PData->LoadFlags) _Foveon_SDDecoder(PData, &LDecoder,  1024, 0);

		int            LPred[3];
		unsigned       LBitBuf = 0;
		EpFvDecode*    LDIndex;
		uint16_t*      LDstPixel = LPixelBuffer->u16;
		const EIndex2  LSize = PData->ISize;
		const EBool    LLoadFlags = PData->LoadFlags;
		int  LRow = 0, LBit = -1;for(;LRow < LSize.Y;LRow++)
		{
			memset(LPred, 0, sizeof(LPred));
			if(!LBit && !PData->LoadFlags && atoi(PData->Model + 2) < 14) _Get4(LFile, LByteOrder);
			int  LCol = 0;LBit = 0;for(;LCol < LSize.X;LCol++, LDstPixel += 3)
			{
				if(LLoadFlags)
				{
					LBitBuf = _Get4(LFile, LByteOrder);
					FORC3 LPred[2 - LCI] += LDiff[LBitBuf >> LCI * 10 & 0x3ff];
				}
				else
				{
					FORC3
					{
						for(LDIndex = LDecoder.FirstDecode;LDIndex->Branch[0];)
						{
							if((LBit = (LBit - 1) & 31) == 31)
							{
								for(int LI = 0;LI < 4;LI++) LBitBuf = (LBitBuf << 8) + fgetc(LFile);
							}
							LDIndex = LDIndex->Branch[LBitBuf >> LBit & 1];
						}
						LPred[LCI] += LDiff[LDIndex->Leaf];
						if(LPred[LCI] >> 16 && ~LPred[LCI] >> 16)
						{
//						derror();
printf("%s() %s:%d  %sERROR%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
						}
					}
				}
				FORC3 LDstPixel[LCI] = LPred[LCI];
			}
		}
	}
{ const ETime  LNow_us = E_GetMicroseconds();char  LTStr[256];EStr_MicrosecondsToString(LNow_us - LPrev_us, LTStr, FALSE);LPrev_us = LNow_us;printf("%s() %s:%d  %s\n", __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout); }
	return(E_SUCCESS);
}


//================================================================
// Load pixel data from Sigma DP and DP Merrill cameras
//================================================================
static int _Foveon_DP_Load_RAW(EpDCamRAW* PData)
{
ETime  LPrev_us = E_GetMicroseconds();
	FILE*  LFile = PData->File;
	const uint16_t  LByteOrder = PData->ByteOrder;

  fseek(LFile, 8, SEEK_CUR);

  uint16_t    LHuff[512];_Foveon_Huff(LFile, LByteOrder,  LHuff);
  unsigned  LROff[4];LROff[0] = 48;
  FORC3 { LROff[LCI + 1] = -(-(LROff[LCI] + _Get4(LFile, LByteOrder)) & -16); }

	{
		const EIndex  LFilePos = ftell(LFile);

		fseek(LFile, 0, SEEK_END);const EIndex  LFileEnd = ftell(LFile);
		fseek(LFile, LFilePos, SEEK_SET);
		const EIndex  LBytesRemaining = LFileEnd - LFilePos;if(LBytesRemaining > 0)
		{
			uint8_t*  LBuffer = EMalloc(LBytesRemaining);if(LBuffer)
			{
				if(fread(LBuffer, 1, LBytesRemaining, LFile) == LBytesRemaining)
				{
					EpPixelBuffer*  LPixelBuffer = PData->PixelBuffer;if(LPixelBuffer)
					{
						const EIndex2  LSize = PData->ISize;
						if(EpMULTI_THREADED)
						{
							EpDCamThreadSet  LThreadSet;EThreadSet_Init((EThreadSet*)&LThreadSet, sizeof(EThread), TRUE);
							LThreadSet.Size = LSize;
							LThreadSet.DNGVersion = PData->DNGVersion;
							LThreadSet.ZeroAfterFF = PData->ZeroAfterFF;
							LThreadSet.Huff = LHuff;
							LThreadSet.ROff = LROff;
							LThreadSet.SrcBuffer = LBuffer;
							LThreadSet.SrcBufferSize = LBytesRemaining;
							LThreadSet.DataOffset = PData->DataOffset;
							LThreadSet.FilePos = LFilePos;

							LThreadSet.DstPixelBuffer = LPixelBuffer;

// Parallelize on RGB channels
							EIndex    LNThreads = 3;

							{
// Fill out Thread info
//
								EThreads_Init((EThreadSet*)&LThreadSet, LNThreads);
							}

							EThreads_Start((EThreadSet*)&LThreadSet, _Foveon_Load_Thread);
							EThreads_WaitForFinish((EThreadSet*)&LThreadSet);
							EThreadSet_Clear((EThreadSet*)&LThreadSet);
						}
						else
						{
							int  LChannelI = 0;const int  LChannelN = 3;for(;LChannelI < LChannelN;LChannelI++)
							{
								EMemReader  LMemReader = { .Buffer= LBuffer, .Length= LBytesRemaining, .Index= PData->DataOffset + LROff[LChannelI] - LFilePos };

								getbithuff_mem(&LMemReader, PData->ZeroAfterFF, -1, NULL);

							  uint16_t  LVPRed[2][2], LHPRed[2];
								LVPRed[0][0] = LVPRed[0][1] = LVPRed[1][0] = LVPRed[1][1] = 512;

								unsigned  LRow = 0;uint16_t*  LDstPixel = LPixelBuffer->u16;for(;LRow < LSize.Y;LRow++)
								{
									unsigned  LCol = 0;for(;LCol < LSize.X;LCol++, LDstPixel += LChannelN)
									{
										const unsigned  LDiff = ljpeg_diff_mem(&LMemReader, PData->DNGVersion, PData->ZeroAfterFF, LHuff);
										if(LCol < 2) LHPRed[LCol] = LVPRed[LRow & 1][LCol] += LDiff;
										else LHPRed[LCol & 1] += LDiff;
										LDstPixel[LChannelI] = LHPRed[LCol & 1];
									}
								}
							}
						}
					}
					else return(E_NO_MEMORY);
				}

				EFree(LBuffer);
if(PData->Verbosity > 1) { const ETime  LNow_us = E_GetMicroseconds();char  LTStr[256];EStr_MicrosecondsToString(LNow_us - LPrev_us, LTStr, FALSE);LPrev_us = LNow_us;printf("%s() %s:%d  %s\n", __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout); }
				return(E_SUCCESS);
			}
		}
	}

{ const ETime  LNow_us = E_GetMicroseconds();char  LTStr[256];EStr_MicrosecondsToString(LNow_us - LPrev_us, LTStr, FALSE);LPrev_us = LNow_us;printf("%s() %s:%d  %s\n", __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout); }

	return(E_SUCCESS);
}


//================================================
// Print Foveon metadata for debugging
//================================================
static inline void _Foveon_ParamsPrint(const uint8_t* PMetaData, const unsigned PMetaLength,  const uint16_t PByteOrder, EpNameAndType* PTypes, const EIndex PNTypes)
{
	if(PTypes && PTypes[0].NameTokenized == NULL)
	{
		EIndex  LI = 0;EpNameAndType*  LTypeRec = PTypes;for(;LI < PNTypes;LI++, LTypeRec++)
		{
			EStr_FilterTokenize(LTypeRec->Name, &_NameFilterTokens,  &LTypeRec->NameTokenized, 0,  TRUE);
		}
	}

	const uint8_t*  LPos;
	unsigned  LI = 0;for(;LI < PMetaLength;LI += _SGet4(PByteOrder,  LPos + 8))
	{
		LPos = PMetaData + LI;
		if(strncmp((char*)LPos, "CMb", 3)) break;
		const char*  LLName = (char*)LPos + _SGet4(PByteOrder, LPos + 12);
printf("%s() %s:%d  %s%c%s  %s%s%s",  __FUNCTION__, __FILE__, __LINE__,  ECodeCyan, LPos[3], ECodeNormal,  ECodeGreen, LLName, ECodeNormal);fflush(stdout);

		if(LPos[3] != 'P')
		{
			printf("\n");fflush(stdout);
			continue;
		}

		const uint8_t*  LCP = LPos + _SGet4(PByteOrder, LPos + 16);
		unsigned        LNum = _SGet4(PByteOrder, LCP);
printf(" - %sParams%s %d\n",  ECodeGreen, ECodeNormal, LNum);fflush(stdout);
		const uint8_t*  LDP = LPos + _SGet4(PByteOrder, LCP + 4);
		while(LNum--)
		{
//			const uint32_t  LCount = _SGet4(PByteOrder, LCP), LOffset = _SGet4(LCP + 4);printf("%s() %s:%d    %sCount%s %d  %sOffset%s %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LCount,  ECodeGreen, ECodeNormal, LOffset);fflush(stdout);
			LCP += 8;
			const char*  LName = (char*)LDP + _SGet4(PByteOrder, LCP);

//		const uint8_t*  LValue = LDP + _SGet4(LCP + 4);printf("%s() %s:%d    %s%s%s  %c%c%c%c\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, LName, ECodeNormal,  LValue[0], LValue[1], LValue[2], LValue[3]);fflush(stdout);
//		const uint8_t*  LValue = LDP + _SGet4(LCP + 4);printf("%s() %s:%d    %s%s%s %02x %02x %02x %02x\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, LName, ECodeNormal,  LValue[0], LValue[1], LValue[2], LValue[3]);fflush(stdout);

			const uint8_t*  LValueStr = LDP + _SGet4(PByteOrder, LCP + 4);
			if(EStr_Equal(LName, LLName)) continue;  // Self-reference to the parameter list. E.g. IncludeBlocks

			printf("%s() %s:%d    %s%s%s",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, LName, ECodeNormal);fflush(stdout);

			if(LValueStr && LValueStr[0]) { printf(" [%s%s%s]\n",  ECodeCyan, LValueStr, ECodeNormal);fflush(stdout); }
			else
			{
				unsigned   LDim[] = { 1, 1, 1 };void*  LDP1;if(_Foveon_GetMatrix(PMetaData, PMetaLength,  PByteOrder,  LName, LDim,  &LDP1) == E_SUCCESS)
			  {
					uint16_t  LType = EResCUSTOM;  // Not set
					{
						EIndex  LTI = 0;const EpNameAndType*  LTypeRec = PTypes;for(;LTI < PNTypes;LTI++, LTypeRec++)
						{
							if(EStrFilter_Pass(LTypeRec->NameTokenized,  LName, TRUE))
//							if(EStr_Equal(LTypeRec->Name, LName))
							{
//printf("%s() %s:%d  [%s%s%s]\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, LTypeRec->Name, ECodeNormal);fflush(stdout);if(LTypeRec->Name[0] == 0) { printf("%s() %s:%d  %sLTypeRec->NameTokenized[0]%s %04x %c\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LTypeRec->NameTokenized[0], LTypeRec->NameTokenized[0] ? LTypeRec->NameTokenized[0] : '-');fflush(stdout); }
								LType = LTypeRec->Type;
								break;
							}
						}
					}

					printf(" %d %d %d [%s",  XYZ_a(LDim), ECodeCyan);fflush(stdout);

					EIndex  LVI = 0;
					EIndex  LD0 = 0;for(;LD0 < LDim[0];LD0++)
					{
						EIndex  LD1 = 0;for(;LD1 < LDim[1];LD1++)
						{
							switch(LType)
							{
								case EResCUSTOM:
									{
										EIndex  LD2 = 0;for(;LD2 < LDim[2];LD2++, LVI++) printf(" %d %f", ((int32_t*)LDP1)[LVI], ((float*)LDP1)[LVI]);
									}
								break;

								case EResUINT32:
									{
										EIndex  LD2 = 0;for(;LD2 < LDim[2];LD2++, LVI++) printf(" %u", ((uint32_t*)LDP1)[LVI]);
									}
								break;

								case EResINT32:
									{
										EIndex  LD2 = 0;for(;LD2 < LDim[2];LD2++, LVI++) printf(" %d", ((int32_t*)LDP1)[LVI]);
									}
								break;

								case EResFLOAT32:
									{
										EIndex  LD2 = 0;for(;LD2 < LDim[2];LD2++, LVI++) printf(" %f", ((float*)LDP1)[LVI]);
									}
								break;
							}
						}
					}
					EFree(LDP1);
					printf("%s]\n", ECodeNormal);
				}
				else
				{
					const char*  LStr = (char*)_Foveon_CamF_Text(PMetaData, PMetaLength,  PByteOrder,  LName);if(LStr)
					{
printf(" [%s%s%s]\n",  ECodeCyan, LStr, ECodeNormal);fflush(stdout);
					}
					else printf("\n");
				}
				fflush(stdout);
			}

//		if(!strcmp(param, LName)) return(LValue);
		}
	}
}


static inline float _Foveon_Avg(const int16_t* PPix, const int PStride, const int PRange[2], const float PCFilt)
{
  float  LMin = FLT_MAX, LMax = -FLT_MAX, LSum = 0.0;
  for(int LI = PRange[0];LI <= PRange[1];LI++)
  {
  	const float  LVal = PPix[LI * PStride] + (PPix[LI * PStride] - PPix[(LI - 1) * PStride]) * PCFilt;
    LSum += LVal;
    if(LMin > LVal) LMin = LVal;
    if(LMax < LVal) LMax = LVal;
  }
  if(PRange[1] - PRange[0] == 1) return(LSum / 2.0);
  return((LSum - LMin - LMax) / (PRange[1] - PRange[0] - 1));
}


#define _Row(pRow)  (uint16_t*)((uint8_t*)LPixels + (pRow) * LBytesPerRow)

//========================================
// Chroma-smoothing denoise
//========================================
static inline void _Foveon_Denoise(const EIndex2 PISize, EpPixelBuffer* PPixelBuffer,  const int PSaturationLevel[3],  int16_t* PCurves[8], const float PDiv[3], const float PInvCrosstalk[9])
{
ETime  LPrev_us = E_GetMicroseconds();
	uint16_t*  LPixels = PPixelBuffer->u16;const EIndex  LNComponents = 3, LBytesPerRow = PPixelBuffer->BytesPerRow;
	uint16_t*  LEndPixel = LPixels + PISize.X * PISize.Y * LNComponents;


	float  LPreGain = _PreGain;
	int32_t*  LPixels32 = NULL;

// Pre-gain the image before chroma-smooting to reduce quantization in dark areas
// It's not quite right yet. We lose highlight detail, e.g. in /usr1/u/gnagy/.wine/drive_c/users/gnagy/Sigma/SD/RAW_SIGMA_SD14.X3F under the windows on the left of the big, white building
	if(LPreGain > 1.0)
	{
		const float  LGain = _PreGain;
		uint16_t*  LPix = LPixels;for(;LPix < LEndPixel;LPix += LNComponents)
		{
			FORC3 LPix[LCI] *= LGain;
		}
	}


// Array for 5x5 Gaussian averaging of red values
	int  (*LSmRow[7])[3];
	LSmRow[6] = (int (*)[3])ECalloc(PISize.X * 5, sizeof(**LSmRow));

	for(int  LI = 0;LI < 5;LI++) LSmRow[LI] = LSmRow[6] + LI * PISize.X;


	const int  LSmMax = 32000 * LPreGain;

// Sharpen the reds against these Gaussian averages
	{
		int  LSMRed_p = 0;
		int  LRow = 2, LSMLast = -1;for(;LRow < PISize.Y-2;LRow++)
		{
			while(LSMLast < LRow + 2)
			{
				for(int LI = 0;LI < 6;LI++) LSmRow[(LI + 5) % 6] = LSmRow[LI];

				++LSMLast;
				const int16_t*  LPix = (int16_t*)_Row(LSMLast) + 2 * LNComponents;
				int  LCol = 2;for(;LCol < PISize.X - 2;LCol++)
				{
					LSmRow[4][LCol][0] = (LPix[0] * 6 + (LPix[-LNComponents] + LPix[LNComponents]) * 4 + LPix[-LNComponents * 2] + LPix[LNComponents * 2] + 8) >> 4;
					LPix += LNComponents;
				}
			}
			int16_t*  LPix = (int16_t*)_Row(LRow) + 2 * LNComponents;
			int  LCol = 2;for(;LCol < PISize.X - 2;LCol++) 
			{
				int  LSMRed = (6 * LSmRow[2][LCol][0] + 4 * (LSmRow[1][LCol][0] + LSmRow[3][LCol][0]) + LSmRow[0][LCol][0] + LSmRow[4][LCol][0] + 8 ) >> 4;
				if(LCol == 2) LSMRed_p = LSMRed;
				int  LI = LPix[0] + ((LPix[0] - ((LSMRed * 7 + LSMRed_p) >> 3)) >> 3);
				if(LI > LSmMax) LI = LSmMax;
				LPix[0] = LI;
				LSMRed_p = LSMRed;
				LPix += LNComponents;
			}
		}
	}

// Adjust brighter pixels for better linearity
	int  LLimit;

	if(PDiv)
	{
		int  LI = 0, LMin = 0xffff;for(;LI < 3;LI++)
		{
			const int  LV = PSaturationLevel[LI] * LPreGain / PDiv[LI];
			if(LMin > LV) LMin = LV;
		}
		LLimit = LMin * 9 >> 4;
	}
	else
	{
		int  LI = 0, LMin = 0xffff;for(;LI < 3;LI++)
		{
			const int  LV = PSaturationLevel[LI] * LPreGain;
			if(LMin > LV) LMin = LV;
		}
		LLimit = LMin * 9 >> 4;
	}

	{
		uint16_t*  LPix = LPixels;for(;LPix < LEndPixel;LPix += LNComponents)
		{
			if(LPix[0] <= LLimit || LPix[1] <= LLimit || LPix[2] <= LLimit) continue;
			int  LMin, LMax;LMin = LMax = LPix[0];
			for(int LCI = 1;LCI < 3;LCI++)
			{
				if(LMin > LPix[LCI]) LMin = LPix[LCI];
				if(LMax < LPix[LCI]) LMax = LPix[LCI];
			}
			if(LMin >= LLimit * 2)
			{
				LPix[0] = LPix[1] = LPix[2] = LMax;
			}
			else
			{
				int  LI = 0x4000 - ((LMin - LLimit) << 14) / LLimit;
				LI = 0x4000 - (LI * LI >> 14);
				LI = LI * LI >> 14;
				FORC3 LPix[LCI] += (LMax - LPix[LCI]) * LI >> 14;
			}
		}
	}



// Photons that miss one detector often hit another, so the sum R+G+B is much less noisy than the individual colors. So, smooth the hues without smoothing the total.
	for(int LSMLast = -1, LRow = 2; LRow < PISize.Y-2; LRow++)
	{
		while(LSMLast < LRow + 2)
		{
			for(int LI = 0; LI < 6; LI++) LSmRow[(LI + 5) % 6] = LSmRow[LI];

			++LSMLast;
			const uint16_t*  LPix = _Row(LSMLast) + 2 * LNComponents;
			int  LCol = 2;for(;LCol < PISize.X-2;LCol++)
			{
				FORC3 LSmRow[4][LCol][LCI] = (LPix[LCI - LNComponents] + 2 * LPix[LCI] + LPix[LCI + LNComponents] + 2) >> 2;
				LPix += LNComponents;
			}
		}
		uint16_t*  LPix = _Row(LRow) + 2 * LNComponents;

		if(PCurves)
		{
			int  LCol = 2;for(;LCol < PISize.X - 2;LCol++)
			{
				int  LDev[3];
				FORC3 LDev[LCI] = -_Foveon_ApplyCurve(PCurves[7], LPix[LCI] - ((LSmRow[1][LCol][LCI] + 2 * LSmRow[2][LCol][LCI] + LSmRow[3][LCol][LCI]) >> 2));
				const int  LSum = (LDev[0] + LDev[1] + LDev[2]) >> 3;
				FORC3 LPix[LCI] += LDev[LCI] - LSum;
				LPix += LNComponents;
			}
		}
		else
		{
			int  LCol = 2;for(;LCol < PISize.X - 2;LCol++)
			{
				int  LDev[3];
				FORC3 LDev[LCI] = -(LPix[LCI] - ((LSmRow[1][LCol][LCI] + 2 * LSmRow[2][LCol][LCI] + LSmRow[3][LCol][LCI]) >> 2));
				const int  LSum = (LDev[0] + LDev[1] + LDev[2]) >> 3;
				FORC3 LPix[LCI] += LDev[LCI] - LSum;
				LPix += LNComponents;
			}
		}
	}

	for(int LSMLast = -1, LRow = 2;LRow < PISize.Y - 2;LRow++)
	{
		while(LSMLast < LRow + 2)
		{
			for(int LI = 0;LI < 6;LI++) LSmRow[(LI + 5) % 6] = LSmRow[LI];

			++LSMLast;
			const int16_t*  LPix = (int16_t*)_Row(LSMLast) + 2 * LNComponents;
			int  LCol = 2;for(; LCol < PISize.X - 2; LCol++)
			{
				FORC3
				{
					LSmRow[4][LCol][LCI] = (LPix[LCI - LNComponents * 2] + LPix[LCI - LNComponents] + LPix[LCI] + LPix[LCI + LNComponents] + LPix[LCI + LNComponents * 2] + 2) >> 2;
				}
				LPix += LNComponents;
			}
		}
		int16_t*  LPix = (int16_t*)_Row(LRow) + 2 * LNComponents;  // Pixel values can go negative from this point, so we need to use int16_t
		if(PCurves)
		{
			int  LCol = 2;for(; LCol < PISize.X - 2;LCol++)
			{
				int  LSum = 60 * _CurveSamplingMultX;
				int  LTotal[4];
				{
					LTotal[3] = 375 * _CurveSamplingMultX;FORC3
					{
						int  LI = 0;LTotal[LCI] = 0;for(;LI < 5;LI++) LTotal[LCI] += LSmRow[LI][LCol][LCI];
						LTotal[3] += LTotal[LCI];
						LSum += LPix[LCI];
					}
				}
				if(LSum < 0) LSum = 0;
				int  LJ = LTotal[3] > 375 * _CurveSamplingMultX ? (LSum << 16) / LTotal[3] : LSum * 174 * _CurveSamplingMultX;
				FORC3
				{
					const int  LV = _Foveon_ApplyCurve(PCurves[6], ((LJ * LTotal[LCI] + 0x8000) >> 16) - LPix[LCI]);
					LPix[LCI] += LV;
				}
				LPix += LNComponents;
			}
		}
		else
		{
			int  LCol = 2;for(; LCol < PISize.X - 2;LCol++)
			{
				int  LSum = 60;
				int  LTotal[4];
				{
					LTotal[3] = 375 * _CurveSamplingMultX;FORC3
					{
						int  LI = 0;LTotal[LCI] = 0;for(;LI < 5;LI++) LTotal[LCI] += LSmRow[LI][LCol][LCI];
						LTotal[3] += LTotal[LCI];
						LSum += LPix[LCI];
					}
				}
				if(LSum < 0) LSum = 0;
				int  LJ = LTotal[3] > 375 * _CurveSamplingMultX ? (LSum << 16) / LTotal[3] : LSum * 174 * _CurveSamplingMultX;
				FORC3 LPix[LCI] += (((LJ * LTotal[LCI] + 0x8000) >> 16) - LPix[LCI]);
				LPix += LNComponents;
			}
		}
	}

// Transform the image to a different colorspace
	const int  LSumMax = 24000 * _PreGain;
	if(PInvCrosstalk)
	{
		if(PCurves)
		{
			int16_t*  LPix = (int16_t*)LPixels;for(;LPix < (int16_t*)LEndPixel;LPix += LNComponents)
			{
				int32_t  LT0Pixel[3];

				FORC3 { const int32_t  LV = LPix[LCI];LT0Pixel[LCI] = LV - _Foveon_ApplyCurve(PCurves[LCI], LV); }
				int  LSum = (LPix[0] + LPix[1] + LPix[1] + LPix[2]) >> 2;
				FORC3 { const int32_t  LV = LPix[LCI];LT0Pixel[LCI] = LV - _Foveon_ApplyCurve(PCurves[LCI], LV - LSum); }

				int  LTmpPixel[3];
				{
					FORC3
					{
						double  LDSum = 0.0;int  LI = 0;for(;LI < 3;LI++) LDSum += PInvCrosstalk[LCI * 3 + LI] * LT0Pixel[LI];
						if(LDSum < 0.0) LDSum = 0.0;
						if(LDSum > LSumMax) LDSum = LSumMax;
						LTmpPixel[LCI] = LDSum + 0.5;
					}
				}
				FORC3 LPix[LCI] = LTmpPixel[LCI];
			}
		}
		else
		{
			int16_t*  LPix = (int16_t*)LPixels;for(;LPix < (int16_t*)LEndPixel;LPix += LNComponents)
			{
				int  LTmpPixel[3];
				{
					FORC3
					{
						double  LDSum = 0.0;int  LI = 0;for(;LI < 3;LI++) LDSum += PInvCrosstalk[LCI * 3 + LI] * LPix[LI];
						if(LDSum < 0.0) LDSum = 0.0;
						if(LDSum > LSumMax) LDSum = LSumMax;
						LTmpPixel[LCI] = LDSum + 0.5;
					}
				}
				FORC3 LPix[LCI] = LTmpPixel[LCI];
			}
		}
	}

// Smooth the image bottom-to-top and save at 1/4 scale
	uint16_t  (*LShrink)[3] = (uint16_t (*)[3])ECalloc((PISize.Y / 4), (PISize.X / 4) * sizeof(*LShrink));

	for(int LRow = PISize.Y / 4; LRow--;)
	{
		int  LCol = 0;for(; LCol < PISize.X / 4; LCol++)
		{
			int  LTmpPixel[3];
			LTmpPixel[0] = LTmpPixel[1] = LTmpPixel[2] = 0;
			int  LI = 0;for(;LI < 4;LI++)
			{
				int  LJ = 0;for(;LJ < 4;LJ++)
				{
					const uint16_t*  LPixel = _Row(LRow * 4 + LI) + (LCol * 4 + LJ) * LNComponents;
					FORC3
					{
						LTmpPixel[LCI] += LPixel[LCI];
					}
				}
				FORC3
				{
					if(LRow + 2 > PISize.Y / 4) LShrink[LRow * (PISize.X / 4) + LCol][LCI] = LTmpPixel[LCI] >> 4;
					else LShrink[LRow * (PISize.X / 4) + LCol][LCI] = (LShrink[(LRow + 1) * (PISize.X / 4) + LCol][LCI] * 1840 + LTmpPixel[LCI] * 141 + 2048) >> 12;
				}
			}
		}
	}

// From the 1/4-scale image, smooth right-to-left
	for(int LRow = 0;LRow < (PISize.Y & ~3);LRow++)
	{
		{
			int  LTmpPixel[3];
			LTmpPixel[0] = LTmpPixel[1] = LTmpPixel[2] = 0;
			if((LRow & 3) == 0) for(int LCol = PISize.X & ~3;LCol--;)
			{
				FORC3
				{
					const uint16_t  LS = LShrink[(LRow / 4) * (PISize.X / 4) + LCol / 4][LCI];
					LSmRow[0][LCol][LCI] = LTmpPixel[LCI] = (LS * 1485 + LTmpPixel[LCI] * 6707 + 4096) >> 13;
/*
if(LSmRow[0][LCol][LCI] < 0)
{
printf("%s() %s:%d  %sGREEN%s  %lld %lld\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, (long long)LS, (long long)LTmpPixel[LCI]);fflush(stdout);
	LSmRow[2][LCol][LCI] = 0;
assert(0);
}
*/
				}
			}
		}
// Then smooth left-to-right
		{
			int  LTmpPixel[3];
			LTmpPixel[0] = LTmpPixel[1] = LTmpPixel[2] = 0;
			for(int LCol = 0;LCol < (PISize.X & ~3);LCol++)
			{
				FORC3
				{
					const int32_t  L0 = LSmRow[0][LCol][LCI];
					LSmRow[1][LCol][LCI] = LTmpPixel[LCI] = (L0 * 1485 + (int64_t)LTmpPixel[LCI] * 6707 + 4096) >> 13;
/*
if(LSmRow[1][LCol][LCI] < 0)
{
printf("%s() %s:%d  %sGREEN%s  %lld %lld\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, (long long)L0, (long long)LTmpPixel[LCI]);fflush(stdout);
	LSmRow[2][LCol][LCI] = 0;
assert(0);
}
*/
				}
			}
		}

// Smooth top-to-bottom
		if(LRow == 0) memcpy(LSmRow[2], LSmRow[1], sizeof(**LSmRow) * PISize.X);
		else for(int LCol = 0;LCol < (PISize.X & ~3);LCol++)
		{
			FORC3
			{
				const int32_t  L1 = LSmRow[1][LCol][LCI], L2 = LSmRow[2][LCol][LCI];
				LSmRow[2][LCol][LCI] = (L2 * 6707 + L1 * 1485 + 4096) >> 13;
/*
if(LSmRow[2][LCol][LCI] < 0)
{
printf("%s() %s:%d  %sGREEN%s  %lld %lld\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, (long long)L1, (long long)L2);fflush(stdout);
	LSmRow[2][LCol][LCI] = 0;
assert(0);
}
*/
			}
		}

// Adjust the chroma toward the smooth values
		if(PCurves)
		{
			for(int LCol = 0; LCol < (PISize.X & ~3);LCol++)
			{
				int  LJ = 30;
				{
					int  LI = 30;
					{
						const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
						FORC3
						{
							LI += LSmRow[2][LCol][LCI];
							LJ += LPixel[LCI];
						}
					}
/*
if(LI == 0)
{
printf("%s() %s:%d  At pixel %d %d  %sLI is 0%s LSmRow[2][LCol] is %d %d %d\n",  __FUNCTION__, __FILE__, __LINE__,  LCol, LRow,  ECodeYellow, ECodeNormal, RGB_a(LSmRow[2][LCol]));fflush(stdout);
}
else if((LCol == 2273 && LRow == 296) || (LCol == 2179 && LRow == 421))
{
printf("%s() %s:%d  At pixel %d %d  %sLI is %d%s LSmRow[2][LCol] is %d %d %d\n",  __FUNCTION__, __FILE__, __LINE__,  LCol, LRow,  ECodeGreen, LI, ECodeNormal, RGB_a(LSmRow[2][LCol]));fflush(stdout);
}
*/
//					LJ = LI != 0 ? (LJ << 16) / LI : 0xFFFF;
						LJ = (LJ << 16) / LI;
					}

				int  LTmpPixel[3];int  LSum = 0;
				{
					const uint16_t*  LPixel = _Row(LRow) + LCol * LNComponents;FORC3
					{
						LTmpPixel[LCI] = _Foveon_ApplyCurve(PCurves[LCI + 3], ((LSmRow[2][LCol][LCI] * LJ + 0x8000) >> 16) - LPixel[LCI]);
						LSum += LTmpPixel[LCI];
					}
				}
				LSum >>= 3;
				uint16_t*  LPixel = _Row(LRow) + LCol * LNComponents;FORC3
				{
					int  LI = LPixel[LCI] + LTmpPixel[LCI] - LSum;
					if(LI < 0) LI = 0;
					LPixel[LCI] = LI;
				}
			}
		}
		else
		{
			for(int LCol = 0; LCol < (PISize.X & ~3);LCol++)
			{
				int  LJ = 30;
				{
					int  LI = 30;
					{
						const uint16_t*  LPixel = _Row(LRow) + LCol * LNComponents;
						FORC3
						{
							LI += LSmRow[2][LCol][LCI];
							LJ += LPixel[LCI];
						}
					}
					LJ = (LJ << 16) / LI;
				}

				int  LTmpPixel[3];int  LSum = 0;
				{
					const uint16_t*  LPixel = _Row(LRow) + LCol * LNComponents;FORC3
					{
						LTmpPixel[LCI] = ((LSmRow[2][LCol][LCI] * LJ + 0x8000) >> 16) - LPixel[LCI];
						LSum += LTmpPixel[LCI];
					}
				}
				LSum >>= 3;
				uint16_t*  LPixel = _Row(LRow) + LCol * LNComponents;FORC3
				{
					int  LI = LPixel[LCI] + LTmpPixel[LCI] - LSum;
					if(LI < 0) LI = 0;
					LPixel[LCI] = LI;
				}
			}
		}
	}
	EFree(LShrink);
	EFree(LSmRow[6]);

	EFreeChkClr(LPixels32);

if(EpDebugFoveonGENERAL > 0) { const ETime  LNow_us = E_GetMicroseconds();char  LTStr[256];EStr_MicrosecondsToString(LNow_us - LPrev_us, LTStr, FALSE);LPrev_us = LNow_us;printf("%s() %s:%d  %s\n", __FUNCTION__, __FILE__, __LINE__, LTStr);fflush(stdout); }
}


static inline void 	_Curve_SaveToCSV(const int16_t* PCurve, const char* PName)
{
	char  LFileName[512];sprintf(LFileName, "/tmp/FoveonCurve%s.csvspline", PName);
	FILE*  LFile = fopen(LFileName, "w");if(LFile)
	{
		fprintf(LFile, "X, %s\n", PName);
		EIndex  LI = 0;float  LX = 0.0;const EIndex  LN = PCurve[0];const float  LStepX = 1.0 / (LN - 1);for(;LI < LN;LI++, LX += LStepX)
		{
			fprintf(LFile, "%7.5f, %d,\n", LX, PCurve[1 + LI]);
		}
		fclose(LFile);
	}
}


void _Foveon_GetColorMatrices(const EpDCamRAW* PData, const char* PFileName,  const EIndex2 PISize, EpPixelBuffer* PPixelBuffer,  int16_t* PCurves[8], float PDiv[3], float PTrans[9])
{
	const uint8_t*  LMetaData = PData->MetaData;const uint32_t  LMetaLength = PData->MetaLength;const uint16_t  LByteOrder = PData->ByteOrder;

	float  LColorDQ[3];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  _Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "ColorDQ") ? "ColorDQ" : "ColorDQCamRGB", 3, LColorDQ);
	float  LChromaDQ[3];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "ChromaDQ", 3, LChromaDQ);
	float  LColumnFilter = 0.0;EBool  LHaveColumnFilter = FALSE;if(_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "ColumnFilter")) { _Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "ColumnFilter", 1, &LColumnFilter);LHaveColumnFilter = TRUE; }

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sLColorDQ%s %f %f %f  %sLChromaDQ%s %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(LColorDQ),  ECodeGreen, ECodeNormal, XYZ_a(LChromaDQ));fflush(stdout); }
if(PData->Verbosity > 1) { printf("%s() %s:%d  %sLHaveColumnFilter%s %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LHaveColumnFilter);fflush(stdout); }

	float  LFinalColorMatrix[3][3];memset(LFinalColorMatrix, 0, sizeof(LFinalColorMatrix));

	EBool  LOldPTrans = TRUE;

	if(EStr_Equal(PData->Model, "SD15"))
	{
if(0)
{
		float  LCCMatrix[3][3];
		{
			const char*  LName = _Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "WhiteBalanceColorCorrections", PData->WBName);if(LName)
			{
				_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LName, 9, LCCMatrix);
			}
			else
			{
				printf("%s() %s:%d  %sNo WhiteBalanceCorrections%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
			}
printf("%s() %s:%d  %sLCCMatrix%s is %s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LName);fflush(stdout);EMatrix_Print3x3_f32(LCCMatrix[0]);
		}

		memcpy(LFinalColorMatrix[0], LCCMatrix[0], sizeof(LCCMatrix));

		{
			float  LMatrix[9];
			if(_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "CP2_Matrix", 9, LMatrix))
			{
printf("%s() %s:%d  %sCP2_Matrix%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(LMatrix);
//				EMatrix_Mult3x3_3_f32(LMatrix, LFinalColorMatrix[0], LFinalColorMatrix[0]);
			}
		}

		{
			float  LMatrix[9 * 2];
			if(_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "CP1_Matrix", 9 * 2, LMatrix))
			{
printf("%s() %s:%d  %sCP1_Matrix-0%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(LMatrix);

printf("%s() %s:%d  %sCP1_Matrix-1%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(LMatrix + 9);
//				memcpy(LFinalColorMatrix[0], LMatrix, sizeof(float) * 9);
//				EMatrix_Mult3x3_3_f32(LFinalColorMatrix[0], LMatrix + 9, LFinalColorMatrix[0]);
			}
		}
}

		{
			const float  LCamToXYZ_Flash[] =
			{
				 1.2336,  0.0991,  0.5638,
				-2.2923,  5.2130, -1.7322,
				 2.4008, -8.0088,  8.6193,
			};
//			float  LColorBalance[9];EMatrix_LoadDiagonal3x3_f32(LColorBalance,  1.15, 1.0, 1.75);EMatrix_Mult3x3_3_f32(LColorBalance, LCamToXYZ_Flash, LFinalColorMatrix[0]);
			memcpy(LFinalColorMatrix[0], LCamToXYZ_Flash, sizeof(LFinalColorMatrix));
		}

		{
			char  LStr[128];sprintf(LStr, "%sRGBNeutral", PData->WBName);
			if(_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", LStr))
			{
				_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LStr, 3, PDiv);
if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }
			}
			else
			{
				float  LDiag[3][3];
				#define LAST(pX, pY) LFinalColorMatrix[(LI + pX) % 3][(LCI + pY) % 3]
				for(int  LI = 0;LI < 3;LI++)
				{	
					FORC3 LDiag[LCI][LI] = LAST(1,1) * LAST(2,2) - LAST(1,2) * LAST(2,1);
				}
				#undef LAST

				FORC3 PDiv[LCI] = LDiag[LCI][0] * 0.3127 + LDiag[LCI][1] * 0.329 + LDiag[LCI][2] * 0.3583;

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }

// Normalize PDiv to the highest value
				{
					float  LMax = 0.0;
					FORC3 if(LMax < PDiv[LCI]) LMax = PDiv[LCI];
					FORC3 PDiv[LCI] /= LMax;
				}

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }
//PDiv[0] = PDiv[1] = PDiv[2] = 1.0;
PDiv[0] = 0.816319;PDiv[1] = 1.000000;PDiv[0] = 0.960000;

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }
			}
		}
	}
	else if(EStr_Equal(PData->Model, "DP1") || EStr_Equal(PData->Model, "DP2") || EStr_Equal(PData->Model, "DP3"))
	{
		LOldPTrans = FALSE;

		{
/*
			const float  LCamToXYZ_Flash[] =
			{
				 1.2336,  0.0991,  0.5638,
				-2.2923,  5.2130, -1.7322,
				 2.4008, -8.0088,  8.6193,
			};
			memcpy(LFinalColorMatrix[0], LCamToXYZ_Flash, sizeof(LFinalColorMatrix));
*/

			{
				float  LMatrix[3*3];
				if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "SunlightCCMatrix", 9,  LMatrix)) memcpy(LFinalColorMatrix[0], LMatrix, sizeof(LMatrix));
				else if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "DP1_SunlightCCMatrix", 9,  LMatrix)) memcpy(LFinalColorMatrix[0], LMatrix, sizeof(LMatrix));
				else if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "DP2_SunlightCCMatrix", 9,  LMatrix)) memcpy(LFinalColorMatrix[0], LMatrix, sizeof(LMatrix));
				else if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "DP3_SunlightCCMatrix", 9,  LMatrix)) memcpy(LFinalColorMatrix[0], LMatrix, sizeof(LMatrix));
				else EMatrix_Invert3x3_f32(_SigmaDPx_CrossTalkMatrix, LFinalColorMatrix[0]);  // Fallback, if none of those matrices are defined
			}

/*
			LFinalColorMatrix[0][0] *= 4.0;
			LFinalColorMatrix[1][1] *= 4.0;
			LFinalColorMatrix[2][2] *= 4.0;
*/

			{
				float  LWB[3] = { 1.0, 1.0, 1.0 };
				if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "SunlightWBGain", 3,  LWB))
				{
//printf("%s() %s:%d  %sSunlightWBGain%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);
					float  LDiagonalMatrix[9];EClr(LDiagonalMatrix);
					LDiagonalMatrix[M3x00] = LWB[0];
					LDiagonalMatrix[M3x11] = LWB[1];
					LDiagonalMatrix[M3x22] = LWB[2];
					EMatrix_Mult3x3_3_f32(LFinalColorMatrix[0], LDiagonalMatrix, LFinalColorMatrix[0]);
				}
				else if(_Foveon_GetFixed(LMetaData, LMetaLength,  LByteOrder,  "DP1_SunlightWBGain", 3,  LWB))
				{
//printf("%s() %s:%d  %sDP1_SunlightWBGain%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);
					float  LDiagonalMatrix[9];EClr(LDiagonalMatrix);
					LDiagonalMatrix[M3x00] = LWB[0];
					LDiagonalMatrix[M3x11] = LWB[1];
					LDiagonalMatrix[M3x22] = LWB[2];
					EMatrix_Mult3x3_3_f32(LFinalColorMatrix[0], LDiagonalMatrix, LFinalColorMatrix[0]);
				}
			}
			PDiv[0] = 0.5;PDiv[1] = 1.0;PDiv[2] = 0.5;
		}
	}
	else  // Sigma SD9, SD10, SD14
	{
		float  LCamXYZ[3][3];
		{
			const char*  LName = _Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "WhiteBalanceIlluminants", PData->WBName);if(LName)
			{
				_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LName, 9, LCamXYZ);
			}
			else
			{
				printf("%s() %s:%d  %s%s: Invalid white balance%s \"%s\"\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, PFileName, ECodeNormal, PData->WBName);fflush(stdout);
			}
printf("%s() %s:%d  %sLCamXYZ%s is %s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LName);fflush(stdout);EMatrix_Print3x3_f32(LCamXYZ[0]);
		}

		float  LCCMatrix[3][3];
		{
			const char*  LName = _Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "WhiteBalanceCorrections", PData->WBName);if(LName)
			{
				_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LName, 9, LCCMatrix);
			}
			else
			{
				LName = _Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "WhiteBalanceColorCorrections", PData->WBName);if(LName)
				{
					_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LName, 9, LCCMatrix);
				}
				else
				{
					printf("%s() %s:%d  %sNo WhiteBalanceCorrections%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeYellow, ECodeNormal);fflush(stdout);
				}
			}
printf("%s() %s:%d  %sLCCMatrix%s is %s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LName);fflush(stdout);EMatrix_Print3x3_f32(LCCMatrix[0]);
		}

		{
			int  LI = 0;for(;LI < 3;LI++)
			{
				int  LJ = 0;for(;LJ < 3;LJ++)
				{
					FORC3 LFinalColorMatrix[LI][LJ] += LCCMatrix[LI][LCI] * LCamXYZ[LCI][LJ];
				}
			}
		}

		{
			char  LStr[128];sprintf(LStr, "%sRGBNeutral", PData->WBName);
			if(_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", LStr))
			{
				_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LStr, 3, PDiv);
if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }
			}
			else
			{
				float  LDiag[3][3];
				#define LAST(pX, pY) LFinalColorMatrix[(LI + pX) % 3][(LCI + pY) % 3]
				for(int  LI = 0;LI < 3;LI++)
				{	
					FORC3 LDiag[LCI][LI] = LAST(1,1) * LAST(2,2) - LAST(1,2) * LAST(2,1);
				}
				#undef LAST

				FORC3 PDiv[LCI] = LDiag[LCI][0] * 0.3127 + LDiag[LCI][1] * 0.329 + LDiag[LCI][2] * 0.3583;

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }

// Normalize PDiv to the highest value
				{
					float  LMax = 0.0;
					FORC3 if(LMax < PDiv[LCI]) LMax = PDiv[LCI];
					FORC3 PDiv[LCI] /= LMax;
				}
//PDiv[0] = PDiv[1] = PDiv[2] = 1.0;

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPDiv%s  %f %f %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(PDiv));fflush(stdout); }
			}
		}
	}

//if(PData->Verbosity > 1) { printf("%s() %s:%d  %sLFinalColorMatrix%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(LFinalColorMatrix[0]); }

	{
		double  LDSum;

		{
			float  LTTrans[3][3];memset(LTTrans, 0, sizeof(LTTrans));

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sPData->RGB_Cam%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(PData->RGB_Cam[0]); }

			if(LOldPTrans)
			{
				for(int LI = 0;LI < 3;LI++)
				{
					int  LJ = 0;for(;LJ < 3;LJ++)
					{
						FORC3 LTTrans[LI][LJ] += PData->RGB_Cam[LI][LCI] * LFinalColorMatrix[LCI][LJ] * PDiv[LJ];
					}
				}
			}
			else
			{
				for(int LI = 0;LI < 3;LI++)
				{
					int  LJ = 0;for(;LJ < 3;LJ++)
					{
						FORC3 LTTrans[LI][LJ] += LFinalColorMatrix[LCI][LJ] * PDiv[LJ];
					}
				}
			}

			double  LTRSum[3];
			FORC3 LTRSum[LCI] = LTTrans[LCI][0] + LTTrans[LCI][1] + LTTrans[LCI][2];
			LDSum = (6.0 * LTRSum[0] + 11.0 * LTRSum[1] + 3.0 * LTRSum[2]) / 20.0;

			if(LOldPTrans)
			{
				for(int LI = 0; LI < 3; LI++)
				{
					FORC3 LFinalColorMatrix[LI][LCI] = LTTrans[LI][LCI] * LDSum / LTRSum[LI];
				}
if(PData->Verbosity > 1) { printf("%s() %s:%d  %sLFinalColorMatrix%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(LFinalColorMatrix[0]); }

				memset(PTrans, 0, sizeof(PTrans[0]) * 3 * 3);
				for(int LI = 0;LI < 3;LI++)
				{
					int  LJ = 0;for(;LJ < 3;LJ++)
					{
						FORC3 PTrans[LI * 3 + LJ] += (LI == LCI ? 32.0 : -1.0) * LFinalColorMatrix[LCI][LJ] / 30.0;
					}
				}
			}
			else
			{
if(0)
{
				for(int LI = 0; LI < 3; LI++)
				{
					FORC3 LFinalColorMatrix[LI][LCI] = LFinalColorMatrix[LI][LCI] * LDSum / LTRSum[LI];
//					FORC3 LFinalColorMatrix[LI][LCI] = LTTrans[LI][LCI] * LDSum / LTRSum[LI];
				}

				memset(PTrans, 0, sizeof(PTrans[0]) * 3 * 3);
				for(int LI = 0;LI < 3;LI++)
				{
					int  LJ = 0;for(;LJ < 3;LJ++)
					{
						FORC3 PTrans[LI * 3 + LJ] += (LI == LCI ? 32.0 : -1.0) * LFinalColorMatrix[LCI][LJ] / 30.0;
					}
				}
}
else memcpy(PTrans, LFinalColorMatrix, sizeof(LFinalColorMatrix[0][0]) * 9);
			}
		}

if(PData->Verbosity > 1)
{
	printf("%s() %s:%d  %sPTrans%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal);fflush(stdout);EMatrix_Print3x3_f32(PTrans);
	printf("%s() %s:%d  %sLDSum%s %f  %sPDiv%s %f,%f,%f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LDSum,  ECodeGreen, ECodeNormal, RGB_a(PDiv));fflush(stdout);
}

		_Foveon_MakeCurves(PCurves, LColorDQ, PDiv, LColumnFilter);
		FORC3 LChromaDQ[LCI] /= 3.0;
		_Foveon_MakeCurves(PCurves + 3, LChromaDQ, PDiv, LColumnFilter);
		FORC3 LDSum += LChromaDQ[LCI] / PDiv[LCI];
		PCurves[6] = _Foveon_MakeCurve(LDSum * _PreGain, LDSum * _PreGain, LColumnFilter);
		PCurves[7] = _Foveon_MakeCurve(LDSum * _PreGain * 2.0, LDSum * _PreGain * 2.0, LColumnFilter);

if(PData->Verbosity >= 3)
{
	printf("%s() %s:%d  %sCurves%s  %sLColumnFilter%s %f\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal,  ECodeGreen, ECodeNormal, LColumnFilter);fflush(stdout);

/*
	{
		EIndex  LI = 0;for(;LI < 8;LI++)
		{
printf("%s() %s:%d  %s%d%s\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, PCurves[LI][0], ECodeNormal);fflush(stdout);
		}
	}
*/

	int  LI = 0;
	_Curve_SaveToCSV(PCurves[LI++], "C0R");
	_Curve_SaveToCSV(PCurves[LI++], "C1G");
	_Curve_SaveToCSV(PCurves[LI++], "C2B");
	_Curve_SaveToCSV(PCurves[LI++], "C3Chr");
	_Curve_SaveToCSV(PCurves[LI++], "C4Chr");
	_Curve_SaveToCSV(PCurves[LI++], "C5Chr");
	_Curve_SaveToCSV(PCurves[LI++], "C6");
	_Curve_SaveToCSV(PCurves[LI++], "C7");
}
	}
}


//================================================
// Dark subtraction and spatial gain
//================================================
void _Foveon_DarkSubAndSpatialGain(const EpDCamRAW* PData,  const EIndex2 PISize, EpPixelBuffer* PPixelBuffer,  const float PDiv[3])
{
	const uint8_t*  LMetaData = PData->MetaData;const uint32_t  LMetaLength = PData->MetaLength;const uint16_t  LByteOrder = PData->ByteOrder;

	float  LColumnFilter = 0.0;EBool  LHaveColumnFilter = FALSE;if(_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "ColumnFilter")) { _Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "ColumnFilter", 1, &LColumnFilter);LHaveColumnFilter = TRUE; }
	float  LPostPolyMatrix[3][3][3];EBool  LHavePostPolyMatrix = FALSE;if(_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "PostPolyMatrix", 27, LPostPolyMatrix[0][0])) LHavePostPolyMatrix = TRUE;

if(PData->Verbosity >= 1) { printf("%s() %s:%d  %sLHaveColumnFilter%s %d  %sLHavePostPolyMatrix%s %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LHaveColumnFilter,  ECodeGreen, ECodeNormal, LHavePostPolyMatrix);fflush(stdout); }

	float  LDarkDrift[3][3][2];memset(LDarkDrift, 0, sizeof(LDarkDrift));

	float (*LBlack)[3] = NULL;

	const EIndex  LNComponents = 3, LBytesPerRow = PPixelBuffer->BytesPerRow;

	{
		const uint16_t*  LPixels = PPixelBuffer->u16;

		int  LDarkShieldColRange[2][2];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "DarkShieldColRange", 4, LDarkShieldColRange);
		if(!_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "DarkDrift") || !_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "DarkDrift", 12, LDarkDrift[1][0]))
		{
			int  LI = 0;for(;LI < 2;LI++)
			{
				int  LDarkShieldBottom[4];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LI ? "DarkShieldBottom" : "DarkShieldTop", 4, LDarkShieldBottom);
				int  LRow = LDarkShieldBottom[1];for(;LRow <= LDarkShieldBottom[3];LRow++)
				{
					int  LCol = LDarkShieldBottom[0];for(;LCol <= LDarkShieldBottom[2];LCol++)
					{
						{
							const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
							FORC3 LDarkDrift[LI + 1][LCI][1] += LPixel[LCI];
						}
						FORC3 LDarkDrift[LI + 1][LCI][1] /= (LDarkShieldBottom[3] - LDarkShieldBottom[1] + 1) * (LDarkShieldBottom[2] - LDarkShieldBottom[0] + 1);
					}
				}
			}
		}

		LBlack = (float (*)[3])ECalloc(PISize.Y, sizeof(*LBlack));

		{
			int  LRow = 0;for(;LRow < PISize.Y;LRow++)
			{
				for(int LI = 0;LI < 6;LI++) ((float*)LDarkDrift[0])[LI] = ((float*)LDarkDrift[1])[LI] + LRow / (PISize.Y - 1.0) * (((float*)LDarkDrift[2])[LI] - ((float*)LDarkDrift[1])[LI]);
				FORC3
				{
					const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCI;
					LBlack[LRow][LCI] = ( _Foveon_Avg(LPixel, LNComponents, LDarkShieldColRange[0], LColumnFilter) + _Foveon_Avg(LPixel, LNComponents, LDarkShieldColRange[1], LColumnFilter) * 3 - LDarkDrift[0][LCI][0] ) / 4 - LDarkDrift[0][LCI][1];
				}
			}
		}

		memcpy(LBlack, LBlack + 8, sizeof(*LBlack) * 8);
		memcpy(LBlack + PISize.Y - 11, LBlack + PISize.Y - 22, 11 * sizeof(*LBlack));
		float  LFinalColorMatrix[3][3];memcpy(LFinalColorMatrix, LBlack, sizeof(LFinalColorMatrix));

		{
			int  LRow = 1;for(;LRow < PISize.Y - 1;LRow++)
			{
				FORC3 if(LFinalColorMatrix[1][LCI] > LFinalColorMatrix[0][LCI])
				{
					if(LFinalColorMatrix[1][LCI] > LFinalColorMatrix[2][LCI]) LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] > LFinalColorMatrix[2][LCI]) ? LFinalColorMatrix[0][LCI] : LFinalColorMatrix[2][LCI];
				}
				else if(LFinalColorMatrix[1][LCI] < LFinalColorMatrix[2][LCI]) LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] < LFinalColorMatrix[2][LCI]) ? LFinalColorMatrix[0][LCI]:LFinalColorMatrix[2][LCI];
				memmove(LFinalColorMatrix, LFinalColorMatrix + 1, 2 * sizeof(LFinalColorMatrix[0]));
				memcpy(LFinalColorMatrix[2], LBlack[LRow + 1], sizeof(LFinalColorMatrix[2]));
			}

			FORC3 LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] + LFinalColorMatrix[1][LCI]) / 2;
			FORC3 LBlack[0][LCI] = (LBlack[1][LCI] + LBlack[3][LCI]) / 2;
		}


		{
			float  LFSum[3];memcpy(LFSum, LBlack, sizeof(LFSum));

			{
				const float  LVal = 1.0 - exp(-1.0 / 24.0);

				{
					int  LRow = 1;for(;LRow < PISize.Y;LRow++)
					{
						FORC3 LFSum[LCI] += LBlack[LRow][LCI] = (LBlack[LRow][LCI] - LBlack[LRow - 1][LCI]) * LVal + LBlack[LRow - 1][LCI];
					}
				}

				memcpy(LFinalColorMatrix[0], LBlack[PISize.Y - 1], sizeof(LFinalColorMatrix[0]));
				FORC3 LFSum[LCI] /= PISize.Y;

				{
					int  LRow = PISize.Y;for(;LRow--;)
					{
						FORC3 LFinalColorMatrix[0][LCI] = LBlack[LRow][LCI] = (LBlack[LRow][LCI] - LFSum[LCI] - LFinalColorMatrix[0][LCI]) * LVal + LFinalColorMatrix[0][LCI];
					}
				}
			}

			int  LTotal[4];memset(LTotal, 0, sizeof(LTotal));
			for(int LRow = 2;LRow < PISize.Y;LRow += 4)
			{
				for(int LCol = 2; LCol < PISize.X; LCol += 4)
				{
					{
						FORC3
						{
							const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
							LTotal[LCI] += LPixel[LCI];
						}
					}
					LTotal[3]++;
				}
			}

			for(int LRow = 0;LRow < PISize.Y; LRow++)
			{
				FORC3 LBlack[LRow][LCI] += LFSum[LCI] / 2 + LTotal[LCI] / (LTotal[3] * 100.0);
			}
		}
	}

	{
		const uint16_t*  LPixels = PPixelBuffer->u16;
		unsigned  LDim[3];
		float  (*LSpatialGain)[3];_Foveon_GetMatrix(LMetaData, LMetaLength, LByteOrder,  "SpatialGain", LDim, &LSpatialGain);if(LSpatialGain)
		{
if(PData->Verbosity >= 1) { printf("%s() %s:%d  %sSpatialGain%s %d x %d x %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, XYZ_a(LDim));fflush(stdout); }
			float      (*LSpatialGainRow)[3] = (float (*)[3])ECalloc(LDim[1], sizeof(*LSpatialGainRow));
			const int  LSpatialGainX = (PISize.X + LDim[1] - 2) / (LDim[1] - 1);

			for(int LRow = 0;LRow < PISize.Y;LRow++)
			{
				for(int LI = 0;LI < 6;LI++) ((float*)LDarkDrift[0])[LI] = ((float*)LDarkDrift[1])[LI] + LRow / (PISize.Y - 1.0) * (((float*)LDarkDrift[2])[LI] - ((float*)LDarkDrift[1])[LI]);
				int16_t*  LPix = (int16_t*)_Row(LRow);

				int16_t  LPrev[3];memcpy(LPrev, LPix, sizeof(LPrev));
				float  LFRow = LRow / (PISize.Y - 1.0) * (LDim[2] - 1);

				int  LIRow = LFRow;
				if(LIRow == LDim[2] - 1) LIRow--;
				LFRow -= LIRow;
				for(int LI = 0; LI < LDim[1]; LI++)
				{
					FORC3 LSpatialGainRow[LI][LCI] = LSpatialGain[LIRow * LDim[1] + LI][LCI] * (1 - LFRow) + LSpatialGain[(LIRow + 1) * LDim[1] + LI][LCI] * LFRow;
				}
				for(int LCol = 0; LCol < PISize.X;LCol++)
				{
					int  LTmpPixel[3];
					FORC3
					{
						int  LDiff = LPix[LCI] - LPrev[LCI];
						LPrev[LCI] = LPix[LCI];
						LTmpPixel[LCI] = LPix[LCI] + floor((LDiff + (LDiff * LDiff >> 14)) * LColumnFilter - LDarkDrift[0][LCI][1] - LDarkDrift[0][LCI][0] * ((float)LCol / PISize.X - 0.5) - LBlack[LRow][LCI]);
//						LTmpPixel[LCI] = LPix[LCI] + floor((LDiff + (LDiff * LDiff >> 14)) * LColumnFilter - LDarkDrift[0][LCI][1] - LDarkDrift[0][LCI][0] * ((float)LCol / PISize.X - 0.5));
					}

					int  LWork[3][3];
					FORC3
					{
						LWork[0][LCI] = LTmpPixel[LCI] * LTmpPixel[LCI] >> 14;
						LWork[2][LCI] = LTmpPixel[LCI] * LWork[0][LCI] >> 14;
						LWork[1][2 - LCI] = LTmpPixel[(LCI + 1) % 3] * LTmpPixel[(LCI + 2) % 3] >> 14;
					}
					FORC3
					{
						float  LVal = 0.0;
						if(LHavePostPolyMatrix)
						{
							int  LI = 0;for(;LI < 3;LI++)
							{
								for(int LJ = 0;LJ < 3; LJ++) LVal += LPostPolyMatrix[LCI][LI][LJ] * LWork[LI][LJ];
							}
						}
						else
						{
							int  LI = 0;for(;LI < 3;LI++)
							{
								for(int LJ = 0;LJ < 3; LJ++) LVal += LWork[LI][LJ];
							}
						}
						LTmpPixel[LCI] = floor((LTmpPixel[LCI] + floor(LVal)) * (LSpatialGainRow[LCol / LSpatialGainX][LCI] * (LSpatialGainX - LCol % LSpatialGainX) + LSpatialGainRow[LCol / LSpatialGainX + 1][LCI] * (LCol % LSpatialGainX)) / LSpatialGainX / PDiv[LCI]);
						if(LTmpPixel[LCI] > 32000) LTmpPixel[LCI] = 32000;
						LPix[LCI] = LTmpPixel[LCI];
					}
					LPix += LNComponents;
				}
			}
			EFree(LSpatialGain);
			EFree(LSpatialGainRow);
		}
		EFree(LBlack);
	}
}


//================================================
// Dark subtraction and spatial gain
//================================================
void _Foveon_DarkSub(const EpDCamRAW* PData, const int PSaturationLevel[3],  const EIndex2 PISize, EpPixelBuffer* PPixelBuffer)
{
	const uint8_t*  LMetaData = PData->MetaData;const uint32_t  LMetaLength = PData->MetaLength;const uint16_t  LByteOrder = PData->ByteOrder;

	float  LColumnFilter = 0.0;EBool  LHaveColumnFilter = FALSE;if(_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "ColumnFilter")) { _Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "ColumnFilter", 1, &LColumnFilter);LHaveColumnFilter = TRUE; }
	float  LPostPolyMatrix[3][3][3];EBool  LHavePostPolyMatrix = FALSE;if(_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "PostPolyMatrix", 27, LPostPolyMatrix[0][0])) LHavePostPolyMatrix = TRUE;

if(PData->Verbosity > 1) { printf("%s() %s:%d  %sLHaveColumnFilter%s %d  %sLHavePostPolyMatrix%s %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LHaveColumnFilter,  ECodeGreen, ECodeNormal, LHavePostPolyMatrix);fflush(stdout); }

	float  LDarkDrift[3][3][2];memset(LDarkDrift, 0, sizeof(LDarkDrift));

	float (*LBlack)[3] = NULL;

	const EIndex  LNComponents = 3, LBytesPerRow = PPixelBuffer->BytesPerRow;

	{
		const uint16_t*  LPixels = PPixelBuffer->u16;

		int  LDarkShieldColRange[2][2];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "DarkShieldColRange", 4, LDarkShieldColRange);
		if(!_Foveon_GetProp(LMetaData, LMetaLength, LByteOrder,  "IncludeBlocks", "DarkDrift") || !_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  "DarkDrift", 12, LDarkDrift[1][0]))
		{
			int  LI = 0;for(;LI < 2;LI++)
			{
				int  LDarkShieldBottom[4];_Foveon_GetFixed(LMetaData, LMetaLength, LByteOrder,  LI ? "DarkShieldBottom" : "DarkShieldTop", 4, LDarkShieldBottom);
				int  LRow = LDarkShieldBottom[1];for(;LRow <= LDarkShieldBottom[3];LRow++)
				{
					int  LCol = LDarkShieldBottom[0];for(;LCol <= LDarkShieldBottom[2];LCol++)
					{
						{
							const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
							FORC3 LDarkDrift[LI + 1][LCI][1] += LPixel[LCI];
						}
						FORC3 LDarkDrift[LI + 1][LCI][1] /= (LDarkShieldBottom[3] - LDarkShieldBottom[1] + 1) * (LDarkShieldBottom[2] - LDarkShieldBottom[0] + 1);
					}
				}
			}
		}

		LBlack = (float (*)[3])ECalloc(PISize.Y, sizeof(*LBlack));

		{
			int  LRow = 0;for(;LRow < PISize.Y;LRow++)
			{
				for(int LI = 0;LI < 6;LI++) ((float*)LDarkDrift[0])[LI] = ((float*)LDarkDrift[1])[LI] + LRow / (PISize.Y - 1.0) * (((float*)LDarkDrift[2])[LI] - ((float*)LDarkDrift[1])[LI]);
				FORC3
				{
					const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCI;
					LBlack[LRow][LCI] = ( _Foveon_Avg(LPixel, LNComponents, LDarkShieldColRange[0], LColumnFilter) + _Foveon_Avg(LPixel, LNComponents, LDarkShieldColRange[1], LColumnFilter) * 3 - LDarkDrift[0][LCI][0] ) / 4 - LDarkDrift[0][LCI][1];
				}
			}
		}

		memcpy(LBlack, LBlack + 8, sizeof(*LBlack) * 8);
		memcpy(LBlack + PISize.Y - 11, LBlack + PISize.Y - 22, 11 * sizeof(*LBlack));
		float  LFinalColorMatrix[3][3];memcpy(LFinalColorMatrix, LBlack, sizeof(LFinalColorMatrix));

		{
			int  LRow = 1;for(;LRow < PISize.Y - 1;LRow++)
			{
				FORC3 if(LFinalColorMatrix[1][LCI] > LFinalColorMatrix[0][LCI])
				{
					if(LFinalColorMatrix[1][LCI] > LFinalColorMatrix[2][LCI]) LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] > LFinalColorMatrix[2][LCI]) ? LFinalColorMatrix[0][LCI] : LFinalColorMatrix[2][LCI];
				}
				else if(LFinalColorMatrix[1][LCI] < LFinalColorMatrix[2][LCI]) LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] < LFinalColorMatrix[2][LCI]) ? LFinalColorMatrix[0][LCI]:LFinalColorMatrix[2][LCI];
				memmove(LFinalColorMatrix, LFinalColorMatrix + 1, 2 * sizeof(LFinalColorMatrix[0]));
				memcpy(LFinalColorMatrix[2], LBlack[LRow + 1], sizeof(LFinalColorMatrix[2]));
			}

			FORC3 LBlack[LRow][LCI] = (LFinalColorMatrix[0][LCI] + LFinalColorMatrix[1][LCI]) / 2;
			FORC3 LBlack[0][LCI] = (LBlack[1][LCI] + LBlack[3][LCI]) / 2;
		}


		{
			float  LFSum[3];memcpy(LFSum, LBlack, sizeof(LFSum));

			{
				const float  LVal = 1.0 - exp(-1.0 / 24.0);

				{
					int  LRow = 1;for(;LRow < PISize.Y;LRow++)
					{
						FORC3 LFSum[LCI] += LBlack[LRow][LCI] = (LBlack[LRow][LCI] - LBlack[LRow - 1][LCI]) * LVal + LBlack[LRow - 1][LCI];
					}
				}

				memcpy(LFinalColorMatrix[0], LBlack[PISize.Y - 1], sizeof(LFinalColorMatrix[0]));
				FORC3 LFSum[LCI] /= PISize.Y;

				{
					int  LRow = PISize.Y;for(;LRow--;)
					{
						FORC3 LFinalColorMatrix[0][LCI] = LBlack[LRow][LCI] = (LBlack[LRow][LCI] - LFSum[LCI] - LFinalColorMatrix[0][LCI]) * LVal + LFinalColorMatrix[0][LCI];
					}
				}
			}

			int  LTotal[4];memset(LTotal, 0, sizeof(LTotal));
			for(int LRow = 2;LRow < PISize.Y;LRow += 4)
			{
				for(int LCol = 2; LCol < PISize.X; LCol += 4)
				{
					{
						FORC3
						{
							const int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
							LTotal[LCI] += LPixel[LCI];
						}
					}
					LTotal[3]++;
				}
			}

			for(int LRow = 0;LRow < PISize.Y; LRow++)
			{
				FORC3 LBlack[LRow][LCI] += LFSum[LCI] / 2 + LTotal[LCI] / (LTotal[3] * 100.0);
			}
		}
	}

	{
		const int16_t*  LPixels = PPixelBuffer->i16;

		for(int LRow = 0;LRow < PISize.Y;LRow++)
		{
			for(int LI = 0;LI < 6;LI++) ((float*)LDarkDrift[0])[LI] = ((float*)LDarkDrift[1])[LI] + LRow / (PISize.Y - 1.0) * (((float*)LDarkDrift[2])[LI] - ((float*)LDarkDrift[1])[LI]);
			int16_t*  LPix = (int16_t*)_Row(LRow);

			for(int LCol = 0; LCol < PISize.X;LCol++)
			{
/*
				EBool  LSaturated = FALSE;
				FORC3
				{
					if(LPix[LCI] >= PSaturationLevel[LCI]) { LSaturated = TRUE;break; }
				}

				if(LSaturated)
				{
					FORC3 LPix[LCI] = LSmMax;
				}
*/

				{
					int  LTmpPixel[3];

					FORC3
					{
						if(LPix[LCI] < PSaturationLevel[LCI])
						{
							LTmpPixel[LCI] = (LPix[LCI] + floor(-LDarkDrift[0][LCI][1] - LDarkDrift[0][LCI][0] * ((float)LCol / PISize.X - 0.5) - LBlack[LRow][LCI]));
//						LTmpPixel[LCI] = LPix[LCI] + floor(-LDarkDrift[0][LCI][1] - LDarkDrift[0][LCI][0] * ((float)LCol / PISize.X - 0.5));
							if(LTmpPixel[LCI] < 0) LTmpPixel[LCI] = 0;
							if(LTmpPixel[LCI] > 32000) LTmpPixel[LCI] = 32000;
							LPix[LCI] = LTmpPixel[LCI];
//LPix[LCI] = LBlack[LRow][LCI];
						}
					}
				}
				LPix += LNComponents;
			}
		}
		EFree(LBlack);
	}
}


//================================================
// Sigma SD and DP (non-Merrill) series
//================================================
static inline void _FillBadPixelsDPx(const EIndex2 PISize, EpPixelBuffer* PPixelBuffer,  const unsigned* PBadPixels, const EIndex PNBadPixels,  const int PKeepImageArea[2])
{
	const int16_t  LHood[] = { -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 };
	uint16_t*  LPixels = PPixelBuffer->u16;const int  LNChannels = 3;const EIndex  LNComponents = 3, LBytesPerRow = PPixelBuffer->BytesPerRow;
	{
		int  LI = 0;for(;LI < PNBadPixels;LI++)
		{
			int  LCol = (PBadPixels[LI] >> 8 & 0xfff) - PKeepImageArea[0];
			int  LRow = (PBadPixels[LI] >> 20       ) - PKeepImageArea[1];
			if((unsigned)(LRow - 1) > PISize.Y - 3 || (unsigned)(LCol - 1) > PISize.X - 3) continue;
			float  LFSum[3];memset(LFSum, 0, sizeof(LFSum));
			int  LSum = 0, LJ = 0;for(;LJ < 8;LJ++)
			{
				if(PBadPixels[LI] & (1 << LJ))
				{
					const int16_t*  LPixel = (int16_t*)LPixels + ((LRow + LHood[LJ * 2]) * PISize.X + LCol + LHood[LJ * 2 + 1]) * LNChannels;
					FORC3 LFSum[LCI] += LPixel[LCI];
					LSum++;
				}
			}
			if(LSum)
			{
				int16_t*  LPixel = (int16_t*)_Row(LRow) + LCol * LNComponents;
				FORC3 LPixel[LCI] = LFSum[LCI] / LSum;
			}
//LPixels[(LRow * LSizeX + LCol) * LNChannels + 0] = 0xFAFA;LPixels[(LRow * PSizeX + LCol) * LNChannels + 2] = 0xFAFA;
		}
	}
}


//================================================
// Sigma SD and DP Merrill series
//================================================
static inline void _FillBadPixelsDPxMerrill(EpPixelBuffer* PPixelBuffer,  const EIndex PSizeX, const EIndex PSizeY,  const unsigned* PBadPixels, const EIndex PNBadPixels,  const int PKeepImageArea[2])
{
	static const int16_t  LHood[] = { -1,-1, -1,0, -1,1, 0,-1, 0,1, 1,-1, 1,0, 1,1 };
	uint16_t*  LPixels = PPixelBuffer->u16;const int  LNChannels = 3;//int16_t*  LPixelsEnd = (int16_t*)(LPixels + PSizeY * PSizeX * LNChannels);
	{
		int  LI = 0;const unsigned*  LSrc = PBadPixels;for(;LI < PNBadPixels;LI++, LSrc += 3)
		{
			const int  LCol = LSrc[0] - PKeepImageArea[0];
			const int  LRow = LSrc[1] - PKeepImageArea[1];
			if((unsigned)(LRow - 1) > PSizeY - 3 || (unsigned)(LCol - 1) > PSizeX - 3) continue;
			float  LFSum[3];memset(LFSum, 0, sizeof(LFSum));
			int  LSum = 0, LJ = 0;for(;LJ < 8;LJ++)
			{
				if(PBadPixels[2] & (1 << LJ))
				{
					const int16_t*  LPixel = (int16_t*)LPixels + ((LRow + LHood[LJ * 2]) * PSizeX + LCol + LHood[LJ * 2 + 1]) * LNChannels;
					FORC3 LFSum[LCI] += LPixel[LCI];
					LSum++;
				}
			}
			if(LSum) FORC3 LPixels[(LRow * PSizeX + LCol) * LNChannels + LCI] = LFSum[LCI] / LSum;
//LPixels[(LRow * PSizeX + LCol) * LNChannels + 0] = 0xFAFA;LPixels[(LRow * PSizeX + LCol) * LNChannels + 2] = 0xFAFA;
		}
	}
}

#if EpUseLIBX3F
#define EpDCRAW  1
#include <x3f/src/x3f_spatial_gain.c>
#endif

//========================================================
// Decode SpatialGain for Merrill and Quattro sensors
//========================================================
static inline int _Foveon_GetMerrillSpatialGain(const uint8_t* PMetaData, const unsigned PMetaLength,  const uint16_t PByteOrder,  const EIndex2 PImageSize,  EpFPixels* PSpatialGain)
{
#if EpUseLIBX3F
	int32_t       LLensInformation = -1;_Foveon_GetFixed(PMetaData, PMetaLength,  PByteOrder,  "LensInformation", 1,  &LLensInformation);

//	const double  LMOD = _MODFromLensID(LLensInformation);
	x3f_spatial_gain_corr_t  LCorr[3];const int  LCN = x3f_get_merrill_type_spatial_gain(NULL, 0, LCorr,  PMetaData, PMetaLength, PByteOrder);
//if(EpDebugFoveonGENERAL) { printf("%s() %s:%d  %sLensInformation%s %d  Minimum Object Distance %lf LCN %d\n",  __FUNCTION__, __FILE__, __LINE__,  ECodeGreen, ECodeNormal, LLensInformation, LMOD, LCN);fflush(stdout); }
	if(LCN == 3)
	{
		{
			int  LCI = 0;for(;LCI < LCN;LCI++)
			{
				const x3f_spatial_gain_corr_t*  LCorrS = &LCorr[LCI];
				const int                       LNSamples = LCorrS->rows * LCorrS->cols * LCorrS->channels;
				const EIndex2                   LSGSize = EIndex2_s(LCorrS->cols, LCorrS->rows);

				float*  LV = EMalloc(LNSamples * sizeof(float));

				{
					int  LI = 0;for(;LI < LNSamples;LI++)
					{
						LV[LI] = 0.0;
						int  LGI = 0;const int  LGN = LCorrS->mgain_num;for(;LGI < LGN;LGI++)
						{
							const x3f_spatial_gain_corr_merrill_t*  LM = &LCorrS->mgain[LGI];

							const uint8_t*  LGains = (uint8_t*)LM->gain;  // These are actually uint8 values
//const uint32_t*  LGains = LM->gain;
//printf(" %02x", LGains[LI]);fflush(stdout);

							LV[LI] += LM->weight * (LM->mingain + LM->delta * LGains[LI]);
						}
					}
				}
//printf("\n");fflush(stdout);

				EpImage  LChSpatialGainImage;EpImage_Init(&LChSpatialGainImage);
				{
					EpImage_Resize(&LChSpatialGainImage,  LSGSize.X, LSGSize.Y,  FALSE);
					EpPixelBuffer*                        LChSpatialGainPixelBuffer = EpImage_PixelBufferByFormat(&LChSpatialGainImage,  EpGRAY, 0, EResFLOAT32,  TRUE,  NULL);if(LChSpatialGainPixelBuffer)
					{
						LChSpatialGainPixelBuffer->f32 = LV;

//{ char  s[512];sprintf(s, "/tmp/SpatialGain00-%d.tif", LCI);EpImage_SaveToFile(&LChSpatialGainImage, NULL,  s, NULL, 0,  NULL); }
						EpImage_Scale(&LChSpatialGainImage, &LChSpatialGainImage,  PImageSize.X, PImageSize.Y,  EpFilterGAUSSIAN, 1.0,  16,  NULL, NULL);
//{ char  s[512];sprintf(s, "/tmp/SpatialGain01-%d.tif", LCI);EpImage_SaveToFile(&LChSpatialGainImage, NULL,  s, NULL, 0,  NULL); }

						PSpatialGain[LCI].Pixels.f32 = LChSpatialGainPixelBuffer->f32;
						PSpatialGain[LCI].Size = PImageSize;

						LChSpatialGainPixelBuffer->f32 = NULL;
						EpImage_FreeContents(&LChSpatialGainImage);
					}
				}
			}
		}

// Consolidate the scaled-up spatial gain channels into an RGB Image
// Only used for debugging / saving the image, as we can just use the 3 separate pixel arrays for the gain correction
//
		if(EpDebugFoveonSPATIAL_GAIN)
		{
			EpImage  LSpatialGainImage;EpImage_Init(&LSpatialGainImage);EpPixelBuffer*  LSpatialGainPixelBuffer = NULL;
			EpImage_Resize(&LSpatialGainImage,  PImageSize.X, PImageSize.Y,  FALSE);
			LSpatialGainPixelBuffer = EpImage_PixelBufferByFormat(&LSpatialGainImage,  EpRGB, 0, EResFLOAT32,  TRUE,  NULL);if(LSpatialGainPixelBuffer)
			{
				EIndex  LI = 0;float*  LDst = LSpatialGainPixelBuffer->f32;const EIndex  LN = PImageSize.X * PImageSize.Y;for(;LI < LN;LI++, LDst += 3)
				{
					EIndex  LCI = 0;for(;LCI < 3;LCI++)
					{
						LDst[LCI] = PSpatialGain[LCI].Pixels.f32[LI];
					}
				}
EpImage_SaveToFile(&LSpatialGainImage, NULL,  "/tmp/SpatialGain00.tif", NULL, 0,  NULL);
			}
			EpImage_FreeContents(&LSpatialGainImage);
		}

		return(E_SUCCESS);
	}
	else return(E_NO_DATA);
#else
	return(E_NO_DATA);
#endif  // EpUseLIBX3F
}


static inline void _SimpleCoeff(EpDCamRAW* PData, const int PIndex)
{
  static const float  LTable[][12] =
  {
// Index 0 -- all Foveon cameras
  {
  	 1.4032, -0.2231, -0.1016,
	  -0.5263,  1.4816,  0.0170,
	  -0.0112,  0.0183,  0.9113
	},

// Index 1 -- Kodak DC20 and DC25
  {
  	 2.25,  0.75, -1.75,
  	-0.25, -0.25,  0.75,
  	 0.75, -0.25, -0.25,
  	-1.75,  0.75,  2.25
  },

// Index 2 -- Logitech Fotoman Pixtura
  {
  	 1.893, -0.418,-0.476,
  	-0.495,  1.773,-0.278,
  	-1.017, -0.655, 2.672
  },

// Index 3 -- Nikon E880, E900, and E990 */
  { -1.936280,  1.800443, -1.448486,  2.584324,
     1.405365, -0.524955, -0.289090,  0.408680,
    -1.204965,  1.082304,  2.941367, -1.818705 }
  };

	const int  LComponents = 3;
  for(int LI = 0; LI < 3; LI++)
  {
    FORC3 PData->RGB_Cam[LI][LCI] = LTable[PIndex][LI * LComponents + LCI];
	}
}


//========================================
// Load X3F metadata
//========================================
static inline void _Foveon_LoadCamF(const char* PFileName, EpDCamRAW* PData)
{
	if(PData->MetaLength > 0)
	{
		FILE*           LFile = PData->File;
		const uint16_t  LByteOrder = PData->ByteOrder;

		uint16_t  LHuff[258], LVPRed[2][2] = { { 512, 512 }, { 512, 512 } }, LHPRed[2];

		fseek(LFile, PData->MetaOffset, SEEK_SET);
		const uint32_t  LType = _Get4(LFile, LByteOrder);_Get4(LFile, LByteOrder);_Get4(LFile, LByteOrder);
		uint32_t        LSizeX = _Get4(LFile, LByteOrder);
		uint32_t        LSizeY = _Get4(LFile, LByteOrder);

		if(LType == 2)
		{
			const EIndex  LMetaLength = PData->MetaLength;
			PData->MetaData = EMalloc(LMetaLength);
			uint8_t*      LMetaData = PData->MetaData;
			const size_t  LRR = fread(LMetaData, 1, LMetaLength, LFile);(void)LRR;
			for(int LI = 0;LI < LMetaLength;LI++)
			{
				LSizeY = (LSizeY * 1597 + 51749) % 244944;
				LSizeX = LSizeY * (uint64_t)301593171 >> 24;
				LMetaData[LI] ^= ((((LSizeY << 8) - LSizeX) >> 1) + LSizeX) >> 17;
			}
		}
		else if(LType == 4)
		{
			EFreeChkClr(PData->MetaData);
			PData->MetaData = EMalloc(PData->MetaLength = LSizeX * LSizeY * 3 / 2);
			uint8_t*  LMetaData = PData->MetaData;

			_Foveon_Huff(LFile, LByteOrder,  LHuff);
			_Get4(LFile, LByteOrder);
			_GetBitHuff(LFile, PData->ZeroAfterFF, -1, 0);
			uint32_t  LJ = 0, LRow = 0;for(;LRow < LSizeY;LRow++)
			{
				for(int LCol = 0;LCol < LSizeX;LCol++)
				{
					const uint32_t  LDiff = _ljpeg_diff(LFile, PData->DNGVersion, PData->ZeroAfterFF, LHuff);
					if(LCol < 2) LHPRed[LCol] = LVPRed[LRow & 1][LCol] += LDiff;
					else LHPRed[LCol & 1] += LDiff;
					if(LCol & 1)
					{
						LMetaData[LJ++] = LHPRed[0] >> 4;
						LMetaData[LJ++] = LHPRed[0] << 4 | LHPRed[1] >> 8;
						LMetaData[LJ++] = LHPRed[1];
					}
				}
			}
		}
		else
		{
printf("%s() %s:%d  %s %sUnknown CamF type%s %d\n",  __FUNCTION__, __FILE__, __LINE__, PFileName,  ECodeYellow, ECodeNormal, LType);fflush(stdout);
		}
	}
	else
	{
printf("%s() %s:%d  %s %sPData->MetaLength is 0%s\n",  __FUNCTION__, __FILE__, __LINE__, PFileName,  ECodeYellow, ECodeNormal);fflush(stdout);
	}
}

