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

#include "dng_gain_map.h"

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

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

class dng_gain_map_interpolator
    {

    private:

        const dng_gain_map &fMap;

        dng_point_real64 fScale;
        dng_point_real64 fOffset;

        int32 fColumn;
        int32 fPlane;

        uint32 fRowIndex1;
        uint32 fRowIndex2;
        real32 fRowFract;

        int32 fResetColumn;

        real32 fValueBase;
        real32 fValueStep;
        real32 fValueIndex;

    public:

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

        real32 Interpolate () const
            {

            return fValueBase + fValueStep * fValueIndex;

            }

        void Increment ()
            {

            if (++fColumn >= fResetColumn)
                {

                ResetColumn ();

                }

            else
                {

                fValueIndex += 1.0f;

                }

            }

    private:

        real32 InterpolateEntry (uint32 colIndex);

        void ResetColumn ();

    };

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

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

    :   fMap (map)

    ,   fScale (1.0 / mapBounds.H (),
                1.0 / mapBounds.W ())

    ,   fOffset (0.5 - mapBounds.t,
                 0.5 - mapBounds.l)

    ,   fColumn (column)
    ,   fPlane  (plane)

    ,   fRowIndex1 (0)
    ,   fRowIndex2 (0)
    ,   fRowFract  (0.0f)

    ,   fResetColumn (0)

    ,   fValueBase  (0.0f)
    ,   fValueStep  (0.0f)
    ,   fValueIndex (0.0f)

    {

    real64 rowIndexF = (fScale.v * (row + fOffset.v) -
                        fMap.Origin ().v) / fMap.Spacing ().v;

    if (rowIndexF <= 0.0)
        {

        fRowIndex1 = 0;
        fRowIndex2 = 0;

        fRowFract = 0.0f;

        }

    else
        {

        if (fMap.Points ().v < 1)
            {
            ThrowProgramError ("Empty gain map");
            }

        uint32 lastRow = static_cast<uint32> (fMap.Points ().v - 1);

        if (rowIndexF >= static_cast<real64> (lastRow))
            {

            fRowIndex1 = lastRow;
            fRowIndex2 = fRowIndex1;

            fRowFract = 0.0f;

            }

        else
            {

            // If we got here, we know that rowIndexF can safely be converted to
            // a uint32 and that static_cast<uint32> (rowIndexF) < lastRow. This
            // implies fRowIndex2 <= lastRow below.

            fRowIndex1 = static_cast<uint32> (rowIndexF);
            fRowIndex2 = fRowIndex1 + 1;

            fRowFract = (real32) (rowIndexF - (real64) fRowIndex1);

            }

        }

    ResetColumn ();

    }

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

real32 dng_gain_map_interpolator::InterpolateEntry (uint32 colIndex)
    {

    return fMap.Entry (fRowIndex1, colIndex, fPlane) * (1.0f - fRowFract) +
           fMap.Entry (fRowIndex2, colIndex, fPlane) * (       fRowFract);

    }

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

void dng_gain_map_interpolator::ResetColumn ()
    {

    real64 colIndexF = ((fScale.h * (fColumn + fOffset.h)) -
                        fMap.Origin ().h) / fMap.Spacing ().h;

    if (colIndexF <= 0.0)
        {

        fValueBase = InterpolateEntry (0);

        fValueStep = 0.0f;

        fResetColumn = (int32) ceil (fMap.Origin ().h / fScale.h - fOffset.h);

        }

    else
        {

        if (fMap.Points ().h < 1)
            {
            ThrowProgramError ("Empty gain map");
            }

        uint32 lastCol = static_cast<uint32> (fMap.Points ().h - 1);

        if (colIndexF >= static_cast<real64> (lastCol))
            {

            fValueBase = InterpolateEntry (lastCol);

            fValueStep = 0.0f;

            fResetColumn = 0x7FFFFFFF;

            }

        else
            {

            // If we got here, we know that colIndexF can safely be converted
            // to a uint32 and that static_cast<uint32> (colIndexF) < lastCol.
            // This implies colIndex + 1 <= lastCol, i.e. the argument to
            // InterpolateEntry() below is valid.

            uint32 colIndex = static_cast<uint32> (colIndexF);

            real64 base  = InterpolateEntry (colIndex);
            real64 delta = InterpolateEntry (colIndex + 1) - base;

            fValueBase = (real32) (base + delta * (colIndexF - (real64) colIndex));

            fValueStep = (real32) ((delta * fScale.h) / fMap.Spacing ().h);

            fResetColumn =
                ConvertDoubleToInt32 (ceil (((colIndex + 1) * fMap.Spacing ().h +
                                             fMap.Origin ().h) / fScale.h - fOffset.h));

            }

        }

    fValueIndex = 0.0f;

    }

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

dng_gain_map::dng_gain_map (dng_memory_allocator &allocator,
                            const dng_point &points,
                            const dng_point_real64 &spacing,
                            const dng_point_real64 &origin,
                            uint32 planes)

    :   fPoints  (points)
    ,   fSpacing (spacing)
    ,   fOrigin  (origin)
    ,   fPlanes  (planes)

    ,   fRowStep (SafeUint32Mult (planes, points.h))

    ,   fBuffer ()

    {

    fBuffer.Reset (allocator.Allocate (ComputeBufferSize (ttFloat,
                                                          fPoints,
                                                          fPlanes,
                                                          padSIMDBytes)));

    }

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

real32 dng_gain_map::Interpolate (int32 row,
                                  int32 col,
                                  uint32 plane,
                                  const dng_rect &bounds) const
    {

    dng_gain_map_interpolator interp (*this,
                                      bounds,
                                      row,
                                      col,
                                      plane);

    return interp.Interpolate ();

    }

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

uint32 dng_gain_map::PutStreamSize () const
    {

    return 44 + fPoints.v * fPoints.h * fPlanes * 4;

    }

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

void dng_gain_map::PutStream (dng_stream &stream) const
    {

    stream.Put_uint32 (fPoints.v);
    stream.Put_uint32 (fPoints.h);

    stream.Put_real64 (fSpacing.v);
    stream.Put_real64 (fSpacing.h);

    stream.Put_real64 (fOrigin.v);
    stream.Put_real64 (fOrigin.h);

    stream.Put_uint32 (fPlanes);

    for (int32 rowIndex = 0; rowIndex < fPoints.v; rowIndex++)
        {

        for (int32 colIndex = 0; colIndex < fPoints.h; colIndex++)
            {

            for (uint32 plane = 0; plane < fPlanes; plane++)
                {

                stream.Put_real32 (Entry (rowIndex,
                                          colIndex,
                                          plane));

                }

            }

        }

    }

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

dng_gain_map * dng_gain_map::GetStream (dng_host &host,
                                        dng_stream &stream)
    {

    dng_point mapPoints;

    mapPoints.v = stream.Get_uint32 ();
    mapPoints.h = stream.Get_uint32 ();

    dng_point_real64 mapSpacing;

    mapSpacing.v = stream.Get_real64 ();
    mapSpacing.h = stream.Get_real64 ();

    dng_point_real64 mapOrigin;

    mapOrigin.v = stream.Get_real64 ();
    mapOrigin.h = stream.Get_real64 ();

    uint32 mapPlanes = stream.Get_uint32 ();

    #if qDNGValidate

    if (gVerbose)
        {

        printf ("Points: v=%d, h=%d\n",
                (int) mapPoints.v,
                (int) mapPoints.h);

        printf ("Spacing: v=%.6f, h=%.6f\n",
                mapSpacing.v,
                mapSpacing.h);

        printf ("Origin: v=%.6f, h=%.6f\n",
                mapOrigin.v,
                mapOrigin.h);

        printf ("Planes: %u\n",
                (unsigned) mapPlanes);

        }

    #endif

    if (mapPoints.v == 1)
        {
        mapSpacing.v = 1.0;
        mapOrigin.v  = 0.0;
        }

    if (mapPoints.h == 1)
        {
        mapSpacing.h = 1.0;
        mapOrigin.h  = 0.0;
        }

    if (mapPoints.v < 1 ||
        mapPoints.h < 1 ||
        mapSpacing.v <= 0.0 ||
        mapSpacing.h <= 0.0 ||
        mapPlanes < 1)
        {
        ThrowBadFormat ();
        }

    AutoPtr<dng_gain_map> map (new dng_gain_map (host.Allocator (),
                                                 mapPoints,
                                                 mapSpacing,
                                                 mapOrigin,
                                                 mapPlanes));

    #if qDNGValidate

    uint32 linesPrinted = 0;
    uint32 linesSkipped = 0;

    #endif

    for (int32 rowIndex = 0; rowIndex < mapPoints.v; rowIndex++)
        {

        for (int32 colIndex = 0; colIndex < mapPoints.h; colIndex++)
            {

            for (uint32 plane = 0; plane < mapPlanes; plane++)
                {

                real32 x = stream.Get_real32 ();

                map->Entry (rowIndex, colIndex, plane) = x;

                #if qDNGValidate

                if (gVerbose)
                    {

                    if (linesPrinted < gDumpLineLimit)
                        {

                        printf ("\tMap [%3u] [%3u] [%u] = %.4f\n",
                                (unsigned) rowIndex,
                                (unsigned) colIndex,
                                (unsigned) plane,
                                x);

                        linesPrinted++;

                        }

                    else
                        linesSkipped++;

                    }

                #endif

                }

            }

        }

    #if qDNGValidate

    if (linesSkipped)
        {

        printf ("\t ... %u map entries skipped\n", (unsigned) linesSkipped);

        }

    #endif

    return map.Release ();

    }

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

DNG_ATTRIB_NO_SANITIZE("unsigned-integer-overflow")
dng_gain_table_map::dng_gain_table_map (dng_memory_allocator &allocator,
                                        const dng_point &points,
                                        const dng_point_real64 &spacing,
                                        const dng_point_real64 &origin,
                                        uint32 numTablePoints,
                                        const real32 weights [5],
                                        uint32 dataType,
                                        real32 gamma,
                                        real32 gainMin,
                                        real32 gainMax)

    :   fPoints  (points)
    ,   fSpacing (spacing)
    ,   fOrigin  (origin)
    ,   fNumTablePoints (numTablePoints)

    ,   fRowStep (fNumTablePoints * points.h)
    ,   fColStep (fNumTablePoints)

    ,   fNumSamples (SafeUint32Mult (points.h,
                                     points.v,
                                     numTablePoints))

    ,   fDataType (dataType)

    ,   fGamma (gamma)

    ,   fGainMin (gainMin)
    ,   fGainMax (gainMax)

    {

    DNG_REQUIRE (dataType <= 3, "Unsupported DataType");

    DNG_REQUIRE (gamma >= kProfileGainTableMap_MinGamma &&
                 gamma <= kProfileGainTableMap_MaxGamma,
                 "Gamma out of range");

    DNG_REQUIRE (gainMin >= kProfileGainTableMap_MinGainValue,
                 "GainMin out of range");

    DNG_REQUIRE (gainMax <= kProfileGainTableMap_MaxGainValue,
                 "GainMax out of range");

    fSampleBytes = SafeUint32Mult (fNumSamples,
                                   (uint32) sizeof (real32));

    if (fNumSamples >= kMaxProfileGainTableMapPoints)
        {

        ThrowBadFormat ("too many points in gain table map");

        }

    memcpy (fMapInputWeights,
            weights,
            sizeof (fMapInputWeights));

    fBuffer.Reset (allocator.Allocate (ComputeBufferSize (ttFloat,
                                                          fPoints,
                                                          numTablePoints,
                                                          padSIMDBytes)));

    }

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

void * dng_gain_table_map::RawTablePtr () const
    {

    DNG_REQUIRE (fBuffer.Get (), "fBuffer");

    // The table currently starts at the beginning of the block's Buffer.

    return fBuffer->Buffer ();

    }

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

uint32 dng_gain_table_map::RawTableNumBytes () const
    {

    // This is a real32 table.

    return fNumSamples * sizeof (real32);

    }

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

uint32 dng_gain_table_map::PutStreamSize () const
    {

    return ((2 * 4) +                        // MapPointsV, MapPointsH
            (2 * 8) +                        // MapSpacingV, MapSpacingH
            (2 * 8) +                        // MapOriginV, MapOriginH
            (    4) +                        // MapPoints
            (5 * 4) +                        // MapInputWeights
            (RequiresVersion2 () ? (4 * 4)
                                 : 0) +      // DataType, Gamma, GainMin & Max
            DataStorageBytes ());

    }

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

void dng_gain_table_map::AddDigest (dng_md5_printer &printer) const
    {

    if (SupportsVersion1 ())
        printer.Process ("ProfileGainTableMap", 19);

    else
        printer.Process ("ProfileGainTableMap2", 20);

    EnsureFingerprint ();

    printer.Process (fFingerprint.data, dng_fingerprint::kDNGFingerprintSize);

    }

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

void dng_gain_table_map::EnsureFingerprint () const
    {

    if (fFingerprint.IsNull ())
        {

        dng_md5_printer_stream stream;

        PutStream (stream);

        fFingerprint = stream.Result ();

        }

    }

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

dng_fingerprint dng_gain_table_map::GetFingerprint () const
    {

    EnsureFingerprint ();

    return fFingerprint;

    }

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

void dng_gain_table_map::PutStream (dng_stream &stream,
                                    bool forceVersion2) const
    {

    stream.Put_uint32 (fPoints.v);
    stream.Put_uint32 (fPoints.h);

    stream.Put_real64 (fSpacing.v);
    stream.Put_real64 (fSpacing.h);

    stream.Put_real64 (fOrigin.v);
    stream.Put_real64 (fOrigin.h);

    stream.Put_uint32 (fNumTablePoints);

    for (uint32 i = 0; i < 5; i++)
        {
        stream.Put_real32 (fMapInputWeights [i]);
        }

    if (RequiresVersion2 () || forceVersion2)
        {
        stream.Put_uint32 (fDataType);
        stream.Put_real32 (fGamma);
        stream.Put_real32 (fGainMin);
        stream.Put_real32 (fGainMax);
        }

    const uint32 storageBytes = DataStorageBytes ();

    // If we have an original buffer, then just use that.

    if (fOriginalBuffer.Get () &&
        fOriginalBuffer->LogicalSize () == storageBytes)
        {

        stream.Put (fOriginalBuffer->Buffer (),
                    storageBytes);

        }

    else
        {

        const bool isFloat32 = IsFloat32 ();
        const bool isFloat16 = IsFloat16 ();
        const bool is8bit    = IsUint8   ();

        for (int32 rowIndex = 0; rowIndex < fPoints.v; rowIndex++)
            {

            for (int32 colIndex = 0; colIndex < fPoints.h; colIndex++)
                {

                // Write 32-bit float values directly.

                if (isFloat32)
                    {

                    for (uint32 p = 0; p < fNumTablePoints; p++)
                        {

                        stream.Put_real32 (Entry (rowIndex,
                                                  colIndex,
                                                  p));

                        }

                    }

                // Convert fp32 values to fp16.

                else if (isFloat16)
                    {

                    for (uint32 p = 0; p < fNumTablePoints; p++)
                        {

                        real32 x = Entry (rowIndex, colIndex, p);

                        uint16 x16 = DNG_FloatToHalf (*(const uint32 *) &x);

                        stream.Put_uint16 (x16);

                        }

                    }

                // Convert fp32 values to uint8.

                else if (is8bit)
                    {

                    DNG_REQUIRE (fGainMax >= fGainMin,
                                 "Expected fGainMax >= fGainMin");

                    if (fGainMax == fGainMin)
                        {

                        for (uint32 p = 0; p < fNumTablePoints; p++)
                            stream.Put_uint8 (0);

                        }

                    else
                        {

                        const real32 scale = 1.0f / (fGainMax - fGainMin);

                        const real32 offset = -fGainMin * scale;

                        for (uint32 p = 0; p < fNumTablePoints; p++)
                            {

                            real32 x = Entry (rowIndex, colIndex, p);

                            // Map via GainMin and GainMax.

                            x = Pin_real32 (x * scale + offset);

                            stream.Put_uint8 ((uint8) Round_int32 (x * 255.0f));

                            }

                        }

                    }

                // Convert fp32 values to uint16.

                else                         // 16-bit
                    {

                    DNG_REQUIRE (fGainMax >= fGainMin,
                                 "Expected fGainMax >= fGainMin");

                    if (fGainMax == fGainMin)
                        {

                        for (uint32 p = 0; p < fNumTablePoints; p++)
                            stream.Put_uint16 (0);

                        }

                    else
                        {

                        const real32 scale = 1.0f / (fGainMax - fGainMin);

                        const real32 offset = -fGainMin * scale;

                        for (uint32 p = 0; p < fNumTablePoints; p++)
                            {

                            real32 x = Entry (rowIndex, colIndex, p);

                            // Map via GainMin and GainMax.

                            x = Pin_real32 (x * scale + offset);

                            stream.Put_uint16 ((uint16) Round_int32 (x * 65535.0f));

                            }

                        }

                    } // fp32 vs u8 vs u16 cases

                } // cols

            } // rows

        } // original vs no original buffer

    }

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

dng_gain_table_map * dng_gain_table_map::GetStream (dng_host &host,
                                                    dng_stream &stream,
                                                    const bool useVersion2)
    {

    dng_point mapPoints;

    mapPoints.v = stream.Get_uint32 ();
    mapPoints.h = stream.Get_uint32 ();

    dng_point_real64 mapSpacing;

    mapSpacing.v = stream.Get_real64 ();
    mapSpacing.h = stream.Get_real64 ();

    dng_point_real64 mapOrigin;

    mapOrigin.v = stream.Get_real64 ();
    mapOrigin.h = stream.Get_real64 ();

    uint32 numTablePoints = stream.Get_uint32 ();

    real32 weights [5];

    for (uint32 i = 0; i < 5; i++)
        {
        weights [i] = stream.Get_real32 ();
        }

    uint32 dataType = 3;
    real32 gamma    = 1.0f;
    real32 gainMin  = 1.0f;
    real32 gainMax  = 1.0f;

    if (useVersion2)
        {

        dataType = stream.Get_uint32 ();
        gamma    = stream.Get_real32 ();
        gainMin  = stream.Get_real32 ();
        gainMax  = stream.Get_real32 ();

        if (gamma < kProfileGainTableMap_MinGamma ||
            gamma > kProfileGainTableMap_MaxGamma)
            {
            ThrowBadFormat ("Gamma out of range in ProfileGainTableMap2");
            }

        if (dataType > 3)
            {
            ThrowBadFormat ("Unsupported DataType in ProfileGainTableMap2");
            }

        if (gainMin < kProfileGainTableMap_MinGainValue)
            {
            ThrowBadFormat ("GainMin out of range in ProfileGainTableMap2");
            }

        if (gainMax > kProfileGainTableMap_MaxGainValue)
            {
            ThrowBadFormat ("GainMax out of range in ProfileGainTableMap2");
            }

        }

    #if qDNGValidate

    if (gVerbose)
        {

        printf ("GainTableMap:\n");

        printf ("  Points: v=%d, h=%d\n",
                (int) mapPoints.v,
                (int) mapPoints.h);

        printf ("  Spacing: v=%.6f, h=%.6f\n",
                mapSpacing.v,
                mapSpacing.h);

        printf ("  Origin: v=%.6f, h=%.6f\n",
                mapOrigin.v,
                mapOrigin.h);

        printf ("  NumTablePoints: %u\n",
                (unsigned) numTablePoints);

        printf ("  Weights: %.4f, %.4f, %.4f, %.4f, %.4f\n",
                (float) weights [0],
                (float) weights [1],
                (float) weights [2],
                (float) weights [3],
                (float) weights [4]);

        printf ("  DataType: %u\n", dataType);
        printf ("  Gamma: %.3f\n", gamma);
        printf ("  GainMin: %.4f\n", gainMin);
        printf ("  GainMax: %.4f\n", gainMax);

        }

    #endif

    if (mapPoints.v == 1)
        {
        mapSpacing.v = 1.0;
        mapOrigin.v  = 0.0;
        }

    if (mapPoints.h == 1)
        {
        mapSpacing.h = 1.0;
        mapOrigin.h  = 0.0;
        }

    if (mapPoints.v < 1 ||
        mapPoints.h < 1 ||
        mapSpacing.v <= 0.0 ||
        mapSpacing.h <= 0.0 ||
        numTablePoints < 1)
        {
        ThrowBadFormat ();
        }

    // Read the weights.

    AutoPtr<dng_gain_table_map> map
        (new dng_gain_table_map (host.Allocator (),
                                 mapPoints,
                                 mapSpacing,
                                 mapOrigin,
                                 numTablePoints,
                                 weights,
                                 dataType,
                                 gamma,
                                 gainMin,
                                 gainMax));

    const bool is8bit    = map->IsUint8   ();
    const bool isFloat16 = map->IsFloat16 ();
    const bool isFloat32 = map->IsFloat32 ();

    // If the data type is not 32-bit float, then keep a copy of the original
    // encoding around, for round-tripping purposes.

    void *origPtr = nullptr;

    if (!isFloat32)
        {

        map->fOriginalBuffer.Reset (host.Allocate (map->DataStorageBytes ()));

        origPtr = map->fOriginalBuffer->Buffer ();

        }

    uint8 *orig8ptr   = (uint8  *) origPtr;
    uint16 *orig16ptr = (uint16 *) origPtr;

    constexpr real32 scale8  = 1.0f /   255.0f;
    constexpr real32 scale16 = 1.0f / 65535.0f;

    #if qDNGValidate

    uint32 linesPrinted = 0;
    uint32 linesSkipped = 0;

    #endif

    for (int32 rowIndex = 0; rowIndex < mapPoints.v; rowIndex++)
        {

        for (int32 colIndex = 0; colIndex < mapPoints.h; colIndex++)
            {

            for (uint32 p = 0; p < numTablePoints; p++)
                {

                real32 x;

                if (isFloat32)
                    {

                    x = stream.Get_real32 ();

                    #if qDNGValidate

                    if (gVerbose)
                        {

                        if (linesPrinted < gDumpLineLimit)
                            {

                            printf ("\tMap [%3u] [%3u] [%3u] = %.4f\n",
                                    (unsigned) rowIndex,
                                    (unsigned) colIndex,
                                    (unsigned) p,
                                    x);

                            linesPrinted++;

                            }

                        else
                            linesSkipped++;

                        }

                    #endif

                    }

                else if (isFloat16)
                    {

                    uint16 x16 = stream.Get_uint16 ();

                    uint32 x32 = DNG_HalfToFloat (x16);

                    x = *(const real32 *) &x32;

                    #if qDNGValidate

                    if (gVerbose)
                        {

                        if (linesPrinted < gDumpLineLimit)
                            {

                            printf ("\tMap [%3u] [%3u] [%3u] = %.4f\n",
                                    (unsigned) rowIndex,
                                    (unsigned) colIndex,
                                    (unsigned) p,
                                    x);

                            linesPrinted++;

                            }

                        else
                            linesSkipped++;

                        }

                    #endif

                    }

                else if (is8bit)
                    {

                    uint8 x8 = stream.Get_uint8 ();

                    x = gainMin + (x8 * scale8) * (gainMax - gainMin);

                    *orig8ptr++ = x8;

                    #if qDNGValidate

                    if (gVerbose)
                        {

                        if (linesPrinted < gDumpLineLimit)
                            {

                            printf ("\tMap [%3u] [%3u] [%3u] = %3d (%.4f)\n",
                                    (unsigned) rowIndex,
                                    (unsigned) colIndex,
                                    (unsigned) p,
                                    (int) x8,
                                    x);

                            linesPrinted++;

                            }

                        else
                            linesSkipped++;

                        }

                    #endif

                    }

                else
                    {

                    // uint16 path

                    uint16 x16 = stream.Get_uint16 ();

                    x = gainMin + (x16 * scale16) * (gainMax - gainMin);

                    *orig16ptr++ = x16;

                    #if qDNGValidate

                    if (gVerbose)
                        {

                        if (linesPrinted < gDumpLineLimit)
                            {

                            printf ("\tMap [%3u] [%3u] [%3u] = %5d (%.4f)\n",
                                    (unsigned) rowIndex,
                                    (unsigned) colIndex,
                                    (unsigned) p,
                                    (int) x16,
                                    x);

                            linesPrinted++;

                            }

                        else
                            linesSkipped++;

                        }

                    #endif

                    }

                // Check range and bad values.

                if (x < kProfileGainTableMap_MinGainValue ||
                    x > kProfileGainTableMap_MaxGainValue)
                    {
                    ThrowBadFormat ("ProfileGainTableMap entry value out of range");
                    }

                if (x != x)
                    {
                    ThrowBadFormat ("Invalid ProfileGainTableMap entry value");
                    }

                // Store it in the fp32 table.

                map->Entry (rowIndex, colIndex, p) = x;

                } // for each point in the table

            } // for each column

        } // for each row

    #if qDNGValidate

    if (linesSkipped)
        {

        printf ("\t ... %u map entries skipped\n", (unsigned) linesSkipped);

        }

    #endif

    return map.Release ();

    }

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

bool dng_gain_table_map::SupportsVersion1 () const
    {

    return (fGamma == 1.0f &&                // NOP gamma
            fDataType == 3);                 // 32-bit float

    }

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

uint32 dng_gain_table_map::DataStorageBytes () const
    {

    return SafeUint32Mult ((uint32) fPoints.v,
                           (uint32) fPoints.h,
                           fNumTablePoints,
                           BytesPerEntry ());

    }

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

void dng_gain_table_map::ClearOriginalBuffer ()
    {

    fOriginalBuffer.Reset ();

    }

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

bool dng_gain_table_map::HasOriginalBuffer () const
    {

    return fOriginalBuffer.Get () != nullptr;

    }

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

const dng_memory_block * dng_gain_table_map::OriginalBuffer () const
    {

    return fOriginalBuffer.Get ();

    }

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

void dng_gain_table_map::SetOriginalBuffer (AutoPtr<dng_memory_block> &block)
    {

    fOriginalBuffer.Reset (block.Release ());

    }

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

dng_opcode_GainMap::dng_opcode_GainMap (const dng_area_spec &areaSpec,
                                        AutoPtr<dng_gain_map> &gainMap)

    :   dng_inplace_opcode (dngOpcode_GainMap,
                            dngVersion_1_3_0_0,
                            kFlag_None)

    ,   fAreaSpec (areaSpec)

    ,   fGainMap ()

    {

    fGainMap.Reset (gainMap.Release ());

    }

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

dng_opcode_GainMap::dng_opcode_GainMap (dng_host &host,
                                        dng_stream &stream)

    :   dng_inplace_opcode (dngOpcode_GainMap,
                            stream,
                            "GainMap")

    ,   fAreaSpec ()

    ,   fGainMap ()

    {

    uint32 byteCount = stream.Get_uint32 ();

    uint64 startPosition = stream.Position ();

    fAreaSpec.GetData (stream);

    fGainMap.Reset (dng_gain_map::GetStream (host, stream));

    if (stream.Position () != startPosition + byteCount)
        {
        ThrowBadFormat ();
        }

    }

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

void dng_opcode_GainMap::PutData (dng_stream &stream) const
    {

    stream.Put_uint32 (dng_area_spec::kDataSize +
                       fGainMap->PutStreamSize ());

    fAreaSpec.PutData (stream);

    fGainMap->PutStream (stream);

    }

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

void dng_opcode_GainMap::ProcessArea (dng_negative &negative,
                                      uint32 /* threadIndex */,
                                      dng_pixel_buffer &buffer,
                                      const dng_rect &dstArea,
                                      const dng_rect &imageBounds)
    {

    dng_rect overlap = fAreaSpec.ScaledOverlap (dstArea);

    if (overlap.NotEmpty ())
        {

        uint16 blackLevel = (Stage () >= 2) ? negative.Stage3BlackLevel () : 0;

        real32 blackScale1  = 1.0f;
        real32 blackScale2  = 1.0f;
        real32 blackOffset1 = 0.0f;
        real32 blackOffset2 = 0.0f;

        if (blackLevel != 0)
            {

            blackOffset2 = ((real32) blackLevel) / 65535.0f;
            blackScale2  = 1.0f - blackOffset2;
            blackScale1  = 1.0f / blackScale2;
            blackOffset1 = 1.0f - blackScale1;

            }

        uint32 cols = overlap.W ();

        uint32 colPitch = fAreaSpec.ColPitch ();

        colPitch = Min_uint32 (colPitch, cols);

        for (uint32 plane = fAreaSpec.Plane ();
             plane < fAreaSpec.Plane () + fAreaSpec.Planes () &&
             plane < buffer.Planes ();
             plane++)
            {

            uint32 mapPlane = Min_uint32 (plane, fGainMap->Planes () - 1);

            for (int32 row = overlap.t; row < overlap.b; row += fAreaSpec.RowPitch ())
                {

                real32 *dPtr = buffer.DirtyPixel_real32 (row, overlap.l, plane);

                dng_gain_map_interpolator interp (*fGainMap,
                                                  imageBounds,
                                                  row,
                                                  overlap.l,
                                                  mapPlane);

                if (blackLevel != 0)
                    {

                    for (uint32 col = 0; col < cols; col += colPitch)
                        {

                        dPtr [col] = dPtr [col] * blackScale1 + blackOffset1;

                        }

                    }

                for (uint32 col = 0; col < cols; col += colPitch)
                    {

                    real32 gain = interp.Interpolate ();

                    dPtr [col] = Min_real32 (dPtr [col] * gain, 1.0f);

                    for (uint32 j = 0; j < colPitch; j++)
                        {
                        interp.Increment ();
                        }

                    }

                if (blackLevel != 0)
                    {

                    for (uint32 col = 0; col < cols; col += colPitch)
                        {

                        dPtr [col] = dPtr [col] * blackScale2 + blackOffset2;

                        }

                    }

                }

            }

        }

    }

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