/*****************************************************************************/
// Copyright 2006-2023 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_shared.h"

#include "dng_camera_profile.h"
#include "dng_exceptions.h"
#include "dng_gain_map.h"
#include "dng_globals.h"
#include "dng_host.h"
#include "dng_memory.h"
#include "dng_parse_utils.h"
#include "dng_sdk_limits.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_temperature.h"
#include "dng_utils.h"

#include <cstdio>

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

bool dng_camera_profile_dynamic_range::IsValid () const
    {

    if (fVersion != 1)
        return false;

    if (fDynamicRange >= 2)
        return false;

    if (IsSDR () && fHintMaxOutputValue > 1.0f)
        return false;

    return true;

    }

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

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

    stream.Put_uint16 (fVersion);

    stream.Put_uint16 (fDynamicRange);

    stream.Put_real32 (fHintMaxOutputValue);

    }

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

#if qDNGValidate

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

void dng_camera_profile_dynamic_range::Dump () const
    {

    printf ("ProfileDynamicRange: version=%u, range=%s, hint_max=%g\n",
            unsigned (fVersion),
            IsHDR () ? "high" : "standard",
            float (fHintMaxOutputValue));

    }

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

#endif  // qDNGValidate

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

dng_camera_profile_info::dng_camera_profile_info ()

    :   fBigEndian (false)

    ,   fColorPlanes (0)

    ,   fCalibrationIlluminant1 (lsUnknown)
    ,   fCalibrationIlluminant2 (lsUnknown)
    ,   fCalibrationIlluminant3 (lsUnknown)

    ,   fColorMatrix1 ()
    ,   fColorMatrix2 ()
    ,   fColorMatrix3 ()

    ,   fForwardMatrix1 ()
    ,   fForwardMatrix2 ()
    ,   fForwardMatrix3 ()

    ,   fReductionMatrix1 ()
    ,   fReductionMatrix2 ()
    ,   fReductionMatrix3 ()

    ,   fProfileCalibrationSignature ()

    ,   fProfileName ()

    ,   fProfileCopyright ()

    ,   fEmbedPolicy (pepAllowCopying)

    ,   fProfileHues (0)
    ,   fProfileSats (0)
    ,   fProfileVals (0)

    ,   fHueSatDeltas1Offset (0)
    ,   fHueSatDeltas1Count  (0)

    ,   fHueSatDeltas2Offset (0)
    ,   fHueSatDeltas2Count  (0)

    ,   fHueSatDeltas3Offset (0)
    ,   fHueSatDeltas3Count  (0)

    ,   fHueSatMapEncoding (encoding_Linear)

    ,   fLookTableHues (0)
    ,   fLookTableSats (0)
    ,   fLookTableVals (0)

    ,   fLookTableOffset (0)
    ,   fLookTableCount  (0)

    ,   fLookTableEncoding (encoding_Linear)

    ,   fBaselineExposureOffset (0, 100)

    ,   fDefaultBlackRender (defaultBlackRender_Auto)

    ,   fToneCurveOffset     (0)
    ,   fToneCurveCount      (0)

    ,   fToneMethod (profileToneMethod_Unspecified)

    ,   fUniqueCameraModel ()

    {

    }

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

dng_camera_profile_info::~dng_camera_profile_info ()
    {

    }

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

DNG_ATTRIB_NO_SANITIZE("unsigned-integer-overflow")
bool dng_camera_profile_info::ParseTag (dng_stream &stream,
                                        uint32 parentCode,
                                        uint32 tagCode,
                                        uint32 tagType,
                                        uint32 tagCount,
                                        uint64 tagOffset)
    {

    switch (tagCode)
        {

        case tcCalibrationIlluminant1:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fCalibrationIlluminant1 = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CalibrationIlluminant1: %s\n",
                        LookupLightSource (fCalibrationIlluminant1));

                }

            #endif

            break;

            }

        case tcCalibrationIlluminant2:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fCalibrationIlluminant2 = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CalibrationIlluminant2: %s\n",
                        LookupLightSource (fCalibrationIlluminant2));

                }

            #endif

            break;

            }

        case tcCalibrationIlluminant3:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fCalibrationIlluminant3 = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CalibrationIlluminant3: %s\n",
                        LookupLightSource (fCalibrationIlluminant3));

                }

            #endif

            break;

            }

        case tcIlluminantData1:
        case tcIlluminantData2:
        case tcIlluminantData3:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
                {
                return false;
                }

            if (!CheckTagCount (parentCode, tagCode, tagCount, 2, 10000))
                {
                return false;
                }

            dng_illuminant_data *dstPtr = &fIlluminantData1;

            if (tagCode == tcIlluminantData2)
                {
                dstPtr = &fIlluminantData2;
                }

            else if (tagCode == tcIlluminantData3)
                {
                dstPtr = &fIlluminantData3;
                }

            #if qDNGValidate
            const char *tagName = LookupTagCode (parentCode, tagCode);
            #else
            const char *tagName = "";
            #endif

            dstPtr->Get (stream,
                         tagCount,
                         tagName);

            break;

            }

        case tcColorMatrix1:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (fColorPlanes == 0)
                {

                fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);

                }

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fColorPlanes,
                                 3,
                                 fColorMatrix1))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ColorMatrix1:\n");

                DumpMatrix (fColorMatrix1);

                }

            #endif

            break;

            }

        case tcColorMatrix2:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            // Kludge - Hasselblad FFF files are very DNG-like, but sometimes
            // only have a ColorMatrix2 tag and no ColorMatrix1 tag.

            bool hasselbladHack = (fColorPlanes == 0);

            if (hasselbladHack)
                {

                fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);

                #if qDNGValidate

                ReportWarning ("ColorMatrix2 without ColorMatrix1");

                #endif

                }

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fColorPlanes,
                                 3,
                                 fColorMatrix2))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ColorMatrix2:\n");

                DumpMatrix (fColorMatrix2);

                }

            #endif

            if (hasselbladHack)
                {

                fColorMatrix1 = fColorMatrix2;

                fColorMatrix2 = dng_matrix ();

                }

            break;

            }

        case tcColorMatrix3:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (fColorPlanes == 0)
                {

                fColorPlanes = Pin_uint32 (0, tagCount / 3, kMaxColorPlanes);

                }

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fColorPlanes,
                                 3,
                                 fColorMatrix3))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ColorMatrix3:\n");

                DumpMatrix (fColorMatrix3);

                }

            #endif

            break;

            }

        case tcForwardMatrix1:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fForwardMatrix1))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ForwardMatrix1:\n");

                DumpMatrix (fForwardMatrix1);

                }

            #endif

            break;

            }

        case tcForwardMatrix2:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fForwardMatrix2))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ForwardMatrix2:\n");

                DumpMatrix (fForwardMatrix2);

                }

            #endif

            break;

            }

        case tcForwardMatrix3:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fForwardMatrix3))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ForwardMatrix3:\n");

                DumpMatrix (fForwardMatrix3);

                }

            #endif

            break;

            }

        case tcReductionMatrix1:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fReductionMatrix1))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ReductionMatrix1:\n");

                DumpMatrix (fReductionMatrix1);

                }

            #endif

            break;

            }

        case tcReductionMatrix2:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fReductionMatrix2))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ReductionMatrix2:\n");

                DumpMatrix (fReductionMatrix2);

                }

            #endif

            break;

            }

        case tcReductionMatrix3:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 3,
                                 fColorPlanes,
                                 fReductionMatrix3))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ReductionMatrix3:\n");

                DumpMatrix (fReductionMatrix3);

                }

            #endif

            break;

            }

        case tcProfileCalibrationSignature:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fProfileCalibrationSignature,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileCalibrationSignature: ");

                DumpString (fProfileCalibrationSignature);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcProfileName:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fProfileName,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileName: ");

                DumpString (fProfileName);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcProfileCopyright:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fProfileCopyright,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileCopyright: ");

                DumpString (fProfileCopyright);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcProfileEmbedPolicy:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fEmbedPolicy = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                const char *policy;

                switch (fEmbedPolicy)
                    {

                    case pepAllowCopying:
                        policy = "Allow copying";
                        break;

                    case pepEmbedIfUsed:
                        policy = "Embed if used";
                        break;

                    case pepEmbedNever:
                        policy = "Embed never";
                        break;

                    case pepNoRestrictions:
                        policy = "No restrictions";
                        break;

                    default:
                        policy = "INVALID VALUE";

                    }

                printf ("ProfileEmbedPolicy: %s\n", policy);

                }

            #endif

            break;

            }

        case tcProfileHueSatMapDims:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 2, 3);

            fProfileHues = stream.TagValue_uint32 (tagType);
            fProfileSats = stream.TagValue_uint32 (tagType);

            if (tagCount > 2)
                fProfileVals = stream.TagValue_uint32 (tagType);
            else
                fProfileVals = 1;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileHueSatMapDims: Hues = %u, Sats = %u, Vals = %u\n",
                        (unsigned) fProfileHues,
                        (unsigned) fProfileSats,
                        (unsigned) fProfileVals);

                }

            #endif

            break;

            }

        case tcProfileHueSatMapData1:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
                return false;

            if (fProfileSats == 0)
                return false;

            dng_safe_uint32 hueCount (fProfileHues);
            dng_safe_uint32 valCount (fProfileVals);

            bool skipSat0 = (tagCount == (hueCount *
                                          (fProfileSats - 1) *
                                          (valCount * 3u)).Get ());

            if (!skipSat0)
                {

                dng_safe_uint32 expected = hueCount * valCount * fProfileSats * 3u;

                if (!CheckTagCount (parentCode,
                                    tagCode,
                                    tagCount,
                                    expected.Get ()))
                    {
                    return false;
                    }

                }

            fBigEndian = stream.BigEndian ();

            fHueSatDeltas1Offset = tagOffset;
            fHueSatDeltas1Count  = tagCount;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileHueSatMapData1:\n");

                DumpHueSatMap (stream,
                               fProfileHues,
                               fProfileSats,
                               fProfileVals,
                               skipSat0);

                }

            #endif

            break;

            }

        case tcProfileHueSatMapData2:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
                return false;

            if (fProfileSats == 0)
                return false;

            dng_safe_uint32 hueCount (fProfileHues);
            dng_safe_uint32 valCount (fProfileVals);

            bool skipSat0 = (tagCount == (hueCount *
                                          (fProfileSats - 1) *
                                          (valCount * 3u)).Get ());

            if (!skipSat0)
                {

                dng_safe_uint32 expected = hueCount * valCount * fProfileSats * 3u;

                if (!CheckTagCount (parentCode,
                                    tagCode,
                                    tagCount,
                                    expected.Get ()))
                    {
                    return false;
                    }

                }

            fBigEndian = stream.BigEndian ();

            fHueSatDeltas2Offset = tagOffset;
            fHueSatDeltas2Count  = tagCount;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileHueSatMapData2:\n");

                DumpHueSatMap (stream,
                               fProfileHues,
                               fProfileSats,
                               fProfileVals,
                               skipSat0);

                }

            #endif

            break;

            }

        case tcProfileHueSatMapData3:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
                return false;

            if (fProfileSats == 0)
                return false;

            dng_safe_uint32 hueCount (fProfileHues);
            dng_safe_uint32 valCount (fProfileVals);

            bool skipSat0 = (tagCount == (hueCount *
                                          (fProfileSats - 1) *
                                          (valCount * 3u)).Get ());

            if (!skipSat0)
                {

                dng_safe_uint32 expected = hueCount * valCount * fProfileSats * 3u;

                if (!CheckTagCount (parentCode,
                                    tagCode,
                                    tagCount,
                                    expected.Get ()))
                    {
                    return false;
                    }

                }

            fBigEndian = stream.BigEndian ();

            fHueSatDeltas3Offset = tagOffset;
            fHueSatDeltas3Count  = tagCount;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileHueSatMapData3:\n");

                DumpHueSatMap (stream,
                               fProfileHues,
                               fProfileSats,
                               fProfileVals,
                               skipSat0);

                }

            #endif

            break;

            }

        case tcProfileHueSatMapEncoding:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fHueSatMapEncoding = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                const char *encoding = NULL;

                switch (fHueSatMapEncoding)
                    {

                    case encoding_Linear:
                        encoding = "Linear";
                        break;

                    case encoding_sRGB:
                        encoding = "sRGB";
                        break;

                    default:
                        encoding = "INVALID VALUE";

                    }

                printf ("ProfileHueSatMapEncoding: %s\n", encoding);

                }

            #endif

            break;

            }

        case tcProfileLookTableDims:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 2, 3);

            fLookTableHues = stream.TagValue_uint32 (tagType);
            fLookTableSats = stream.TagValue_uint32 (tagType);

            if (tagCount > 2)
                fLookTableVals = stream.TagValue_uint32 (tagType);
            else
                fLookTableVals = 1;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileLookTableDims: Hues = %u, Sats = %u, Vals = %u\n",
                        (unsigned) fLookTableHues,
                        (unsigned) fLookTableSats,
                        (unsigned) fLookTableVals);

                }

            #endif

            break;

            }

        case tcProfileLookTableData:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
                return false;

            if (fLookTableSats == 0)
                return false;

            dng_safe_uint32 hueCount (fLookTableHues);
            dng_safe_uint32 valCount (fLookTableVals);

            bool skipSat0 = (tagCount == (hueCount *
                                          (fLookTableSats - 1) *
                                          valCount * 3u).Get ());

            if (!skipSat0)
                {

                dng_safe_uint32 expected = hueCount * valCount * fLookTableSats * 3u;

                if (!CheckTagCount (parentCode,
                                    tagCode,
                                    tagCount,
                                    expected.Get ()))
                    {
                    return false;
                    }

                }

            fBigEndian = stream.BigEndian ();

            fLookTableOffset = tagOffset;
            fLookTableCount  = tagCount;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileLookTableData:\n");

                DumpHueSatMap (stream,
                               fLookTableHues,
                               fLookTableSats,
                               fLookTableVals,
                               skipSat0);

                }

            #endif

            break;

            }

        case tcProfileLookTableEncoding:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fLookTableEncoding = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                const char *encoding = NULL;

                switch (fLookTableEncoding)
                    {

                    case encoding_Linear:
                        encoding = "Linear";
                        break;

                    case encoding_sRGB:
                        encoding = "sRGB";
                        break;

                    default:
                        encoding = "INVALID VALUE";

                    }

                printf ("ProfileLookTableEncoding: %s\n", encoding);

                }

            #endif

            break;

            }

        case tcBaselineExposureOffset:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fBaselineExposureOffset = stream.TagValue_srational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BaselineExposureOffset: %+0.2f\n",
                        fBaselineExposureOffset.As_real64 ());

                }

            #endif

            break;

            }

        case tcDefaultBlackRender:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDefaultBlackRender = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                const char *setting = NULL;

                switch (fDefaultBlackRender)
                    {

                    case defaultBlackRender_Auto:
                        setting = "Auto";
                        break;

                    case defaultBlackRender_None:
                        setting = "None";
                        break;

                    default:
                        setting = "INVALID VALUE";

                    }

                printf ("DefaultBlackRender: %s\n",
                        setting);

                }

            #endif

            break;

            }

        case tcProfileToneCurve:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttFloat))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 4, tagCount))
                return false;

            if ((tagCount & 1) != 0)
                {

                #if qDNGValidate

                    {

                    char message [256];

                    snprintf (message,
                              256,
                              "%s %s has odd count (%u)",
                              LookupParentCode (parentCode),
                              LookupTagCode (parentCode, tagCode),
                              (unsigned) tagCount);

                    ReportWarning (message);

                    }

                #endif

                return false;

                }

            fBigEndian = stream.BigEndian ();

            fToneCurveOffset = tagOffset;
            fToneCurveCount  = tagCount;

            #if qDNGValidate

            if (gVerbose)
                {

                DumpTagValues (stream,
                               "Coord",
                               parentCode,
                               tagCode,
                               tagType,
                               tagCount);


                }

            #endif

            break;

            }

        case tcProfileToneMethod:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fToneMethod = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                const char *setting = NULL;

                switch (fToneMethod)
                    {

                    case profileToneMethod_Unspecified:
                        setting = "Unspecified";
                        break;

                    case profileToneMethod_AdobePV5:
                        setting = "Adobe PV5";
                        break;

                    case profileToneMethod_AdobePV6:
                        setting = "Adobe PV6";
                        break;

                    default:
                        setting = "INVALID VALUE";

                    }

                printf ("ProfileToneMethod: %s\n",
                        setting);

                }

            #endif

            break;

            }

        case tcUniqueCameraModel:
            {

            // Note: This code is only used when parsing stand-alone
            // profiles.  The embedded profiles are assumed to be restricted
            // to the model they are embedded in.

            CheckTagType (parentCode, tagCode, tagType, ttAscii);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fUniqueCameraModel,
                            false);

            bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();

            #if qDNGValidate

            if (didTrim)
                {

                ReportWarning ("UniqueCameraModel string has trailing blanks");

                }

            if (gVerbose)
                {

                printf ("UniqueCameraModel: ");

                DumpString (fUniqueCameraModel);

                printf ("\n");

                }

            #else

            (void) didTrim;     // Unused

            #endif

            break;

            }

        case tcProfileGainTableMap2:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
                return false;

            // For Camera Profile IFD, only ProfileGainTableMap2 is permitted;
            // not the original ProfileGainTableMap.

            constexpr bool useVersion2format = true;

            dng_host host;                   // use default allocator

            fProfileGainTableMap.reset
                (dng_gain_table_map::GetStream (host,
                                                stream,
                                                useVersion2format));

            auto pgtm = fProfileGainTableMap;

            #if qDNGValidate

            if (pgtm && gVerbose)
                {

                dng_md5_printer printer;

                pgtm->AddDigest (printer);

                auto digest = printer.Result ();

                char str [2 * dng_fingerprint::kDNGFingerprintSize + 1];

                digest.ToUtf8HexString (str);

                printf ("ProfileGainTableMap2 (digest): %s\n", str);

                }

            #endif  // qDNGValidate

            if (stream.Position () > tagOffset + (uint64) tagCount)
                ThrowBadFormat ("tcProfileGainTableMap2 parse error");

            break;

            }

        case tcProfileDynamicRange:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 8))
                return false;

            uint16 version = stream.Get_uint16 ();

            if (version == 1)
                {

                fProfileDynamicRange.fVersion = version;

                fProfileDynamicRange.fDynamicRange = stream.Get_uint16 ();

                fProfileDynamicRange.fHintMaxOutputValue = stream.Get_real32 ();

                }

            else
                {

                ThrowBadFormat ("Unsupported version in ProfileDynamicRange");

                }

            #if qDNGValidate

            if (gVerbose)
                fProfileDynamicRange.Dump ();

            #endif

            break;

            }

        case tcProfileGroupName:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fProfileGroupName,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ProfileGroupName: ");

                DumpString (fProfileGroupName);

                printf ("\n");

                }

            #endif  // qDNGValidate

            break;

            }

        case tcRGBTablesDraft:
        case tcRGBTables:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
                return false;

            const bool isDraft = (tagCode == tcRGBTablesDraft);

            dng_host host;

            fMaskedRGBTables.reset (dng_masked_rgb_tables::GetStream (host,
                                                                      stream,
                                                                      isDraft));

            #if qDNGValidate

            if (gVerbose && fMaskedRGBTables)
                {

                dng_md5_printer printer;

                fMaskedRGBTables->AddDigest (printer);

                const dng_fingerprint &digest = printer.Result ();

                dng_string str = digest.ToUtf8HexString ();

                const char *tagName = isDraft ? "RGBTablesDraft" : "RGBTables";

                printf ("%s (digest): %s\n", tagName, str.Get ());

                fMaskedRGBTables->Dump ();

                }

            #endif  // qDNGValidate

            if (stream.Position () > tagOffset + (uint64) tagCount)
                {

                ThrowBadFormat ("tcRGBTables parse error");

                }

            break;

            }

        default:
            {

            return false;

            }

        }

    return true;

    }

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

bool dng_camera_profile_info::ParseExtended (dng_stream &stream)
    {

    try
        {

        // Offsets are relative to the start of this structure, not the entire file.

        uint64 startPosition = stream.Position ();

        // Read header. Like a TIFF header, but with different magic number
        // Plus all offsets are relative to the start of the IFD, not to the
        // stream or file.

        uint16 byteOrder = stream.Get_uint16 ();

        if (byteOrder == byteOrderMM)
            fBigEndian = true;

        else if (byteOrder == byteOrderII)
            fBigEndian = false;

        else
            return false;

        TempBigEndian setEndianness (stream, fBigEndian);

        uint16 magicNumber = stream.Get_uint16 ();

        if (magicNumber != magicExtendedProfile)
            {
            return false;
            }

        uint32 offset = stream.Get_uint32 ();

        stream.Skip (SafeUint32Sub (offset, 8u));

        // Start on IFD entries.

        uint32 ifdEntries = stream.Get_uint16 ();

        if (ifdEntries < 1)
            {
            return false;
            }

        for (uint32 tag_index = 0; tag_index < ifdEntries; tag_index++)
            {

            stream.SetReadPosition (startPosition + 8 + 2 + tag_index * 12);

            uint16 tagCode  = stream.Get_uint16 ();
            uint32 tagType  = stream.Get_uint16 ();
            uint32 tagCount = stream.Get_uint32 ();

            uint64 tagOffset = stream.Position ();

            if (SafeUint32Mult (TagTypeSize (tagType), tagCount) > 4)
                {

                tagOffset = startPosition + stream.Get_uint32 ();

                stream.SetReadPosition (tagOffset);

                }

            if (!ParseTag (stream,
                           0,
                           tagCode,
                           tagType,
                           tagCount,
                           tagOffset))
                {

                #if qDNGValidate

                if (gVerbose)
                    {

                    stream.SetReadPosition (tagOffset);

                    printf ("*");

                    DumpTagValues (stream,
                                   LookupTagType (tagType),
                                   0,
                                   tagCode,
                                   tagType,
                                   tagCount);

                    }

                #endif

                }

            }

        return true;

        }

    catch (...)
        {

        // Eat parsing errors.

        }

    return false;

    }

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

dng_shared::dng_shared ()

    :   fExifIFD             (0)
    ,   fGPSInfo             (0)
    ,   fInteroperabilityIFD (0)
    ,   fKodakDCRPrivateIFD  (0)
    ,   fKodakKDCPrivateIFD  (0)

    ,   fXMPCount  (0)
    ,   fXMPOffset (0)

    ,   fIPTC_NAA_Count  (0)
    ,   fIPTC_NAA_Offset (0)

    ,   fMakerNoteCount  (0)
    ,   fMakerNoteOffset (0)
    ,   fMakerNoteSafety (0)

    ,   fDNGVersion         (0)
    ,   fDNGBackwardVersion (0)

    ,   fUniqueCameraModel    ()
    ,   fLocalizedCameraModel ()

    ,   fCameraProfile ()

    ,   fExtraCameraProfiles ()

    ,   fCameraCalibration1 ()
    ,   fCameraCalibration2 ()
    ,   fCameraCalibration3 ()

    ,   fCameraCalibrationSignature  ()

    ,   fAnalogBalance ()

    ,   fAsShotNeutral ()

    ,   fAsShotWhiteXY ()

    ,   fBaselineExposure      (0, 1)
    ,   fBaselineNoise         (1, 1)
    ,   fBaselineSharpness     (1, 1)
    ,   fLinearResponseLimit   (1, 1)
    ,   fShadowScale           (1, 1)

    ,   fHasBaselineExposure (false)
    ,   fHasShadowScale      (false)

    ,   fDNGPrivateDataCount  (0)
    ,   fDNGPrivateDataOffset (0)

    ,   fRawImageDigest    ()
    ,   fNewRawImageDigest ()

    ,   fRawDataUniqueID ()

    ,   fOriginalRawFileName ()

    ,   fOriginalRawFileDataCount  (0)
    ,   fOriginalRawFileDataOffset (0)

    ,   fOriginalRawFileDigest ()

    ,   fAsShotICCProfileCount  (0)
    ,   fAsShotICCProfileOffset (0)

    ,   fAsShotPreProfileMatrix ()

    ,   fCurrentICCProfileCount  (0)
    ,   fCurrentICCProfileOffset (0)

    ,   fCurrentPreProfileMatrix ()

    ,   fColorimetricReference (crSceneReferred)

    ,   fAsShotProfileName ()

    ,   fOriginalDefaultFinalSize     ()
    ,   fOriginalBestQualityFinalSize ()

    ,   fOriginalDefaultCropSizeH ()
    ,   fOriginalDefaultCropSizeV ()

    ,   fDepthFormat      (depthFormatUnknown)
    ,   fDepthNear        (0, 0)
    ,   fDepthFar         (0, 0)
    ,   fDepthUnits       (depthUnitsUnknown)
    ,   fDepthMeasureType (depthMeasureUnknown)

    ,   fBigTableDigests    ()
    ,   fBigTableOffsets    ()
    ,   fBigTableByteCounts ()

    {

    }

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

dng_shared::~dng_shared ()
    {

    }

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

bool dng_shared::ParseTag (dng_stream &stream,
                           dng_exif &exif,
                           uint32 parentCode,
                           bool /* isMainIFD */,
                           uint32 tagCode,
                           uint32 tagType,
                           uint32 tagCount,
                           uint64 tagOffset,
                           int64 /* offsetDelta */)
    {

    if (parentCode == 0)
        {

        if (Parse_ifd0 (stream,
                        exif,
                        parentCode,
                        tagCode,
                        tagType,
                        tagCount,
                        tagOffset))
            {

            return true;

            }

        }

    if (parentCode == 0 ||
        parentCode == tcExifIFD)
        {

        if (Parse_ifd0_exif (stream,
                             exif,
                             parentCode,
                             tagCode,
                             tagType,
                             tagCount,
                             tagOffset))
            {

            return true;

            }

        }

    return false;

    }

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

// Parses tags that should only appear in IFD 0.

bool dng_shared::Parse_ifd0 (dng_stream &stream,
                             dng_exif & /* exif */,
                             uint32 parentCode,
                             uint32 tagCode,
                             uint32 tagType,
                             uint32 tagCount,
                             uint64 tagOffset)
    {

    switch (tagCode)
        {

        case tcXMP:
            {

            CheckTagType (parentCode, tagCode, tagType, ttByte, ttUndefined);

            fXMPCount  = tagCount;
            fXMPOffset = fXMPCount ? tagOffset : 0;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("XMP: Count = %u, Offset = %u\n",
                        (unsigned) fXMPCount,
                        (unsigned) fXMPOffset);

                if (fXMPCount)
                    {

                    DumpXMP (stream, fXMPCount);

                    }

                }

            #endif

            break;

            }

        case tcIPTC_NAA:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttAscii, ttUndefined);

            fIPTC_NAA_Count = (dng_safe_uint32 (tagCount) * TagTypeSize (tagType)).Get ();
            fIPTC_NAA_Offset = fIPTC_NAA_Count ? tagOffset : 0;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("IPTC/NAA: Count = %u, Offset = %llu\n",
                        (unsigned) fIPTC_NAA_Count,
                        (unsigned long long) fIPTC_NAA_Offset);

                if (fIPTC_NAA_Count)
                    {

                    DumpHexAscii (stream, fIPTC_NAA_Count);

                    }

                // Compute and output the digest.

                dng_memory_data buffer (fIPTC_NAA_Count);

                stream.SetReadPosition (fIPTC_NAA_Offset);

                stream.Get (buffer.Buffer (), fIPTC_NAA_Count);

                const uint8 *data = buffer.Buffer_uint8 ();

                uint32 count = fIPTC_NAA_Count;

                // Method 1: Counting all bytes (this is correct).

                    {

                    dng_md5_printer printer;

                    printer.Process (data, count);

                    printf ("IPTCDigest: ");

                    DumpFingerprint (printer.Result ());

                    printf ("\n");

                    }

                // Method 2: Ignoring zero padding.

                    {

                    uint32 removed = 0;

                    while ((removed < 3) && (count > 0) && (data [count - 1] == 0))
                        {
                        removed++;
                        count--;
                        }

                    if (removed != 0)
                        {

                        dng_md5_printer printer;

                        printer.Process (data, count);

                        printf ("IPTCDigest (ignoring zero padding): ");

                        DumpFingerprint (printer.Result ());

                        printf ("\n");

                        }

                    }

                }

            #endif

            break;

            }

        case tcExifIFD:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD, ttLong8, ttIFD8);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fExifIFD = stream.TagValue_uint64 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("ExifIFD: %llu\n", (unsigned long long) fExifIFD);
                }

            #endif

            break;

            }

        case tcGPSInfo:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD, ttLong8, ttIFD8);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fGPSInfo = stream.TagValue_uint64 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("GPSInfo: %llu\n", (unsigned long long) fGPSInfo);
                }

            #endif

            break;

            }

        case tcKodakDCRPrivateIFD:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fKodakDCRPrivateIFD = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("KodakDCRPrivateIFD: %u\n", (unsigned) fKodakDCRPrivateIFD);
                }

            #endif

            break;

            }

        case tcKodakKDCPrivateIFD:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fKodakKDCPrivateIFD = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("KodakKDCPrivateIFD: %u\n", (unsigned) fKodakKDCPrivateIFD);
                }

            #endif

            break;

            }

        case tcDNGVersion:
            {

            CheckTagType (parentCode, tagCode, tagType, ttByte);

            CheckTagCount (parentCode, tagCode, tagCount, 4);

            uint32 b0 = stream.Get_uint8 ();
            uint32 b1 = stream.Get_uint8 ();
            uint32 b2 = stream.Get_uint8 ();
            uint32 b3 = stream.Get_uint8 ();

            fDNGVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("DNGVersion: %u.%u.%u.%u\n",
                        (unsigned) b0,
                        (unsigned) b1,
                        (unsigned) b2,
                        (unsigned) b3);
                }

            #endif

            break;

            }

        case tcDNGBackwardVersion:
            {

            CheckTagType (parentCode, tagCode, tagType, ttByte);

            CheckTagCount (parentCode, tagCode, tagCount, 4);

            uint32 b0 = stream.Get_uint8 ();
            uint32 b1 = stream.Get_uint8 ();
            uint32 b2 = stream.Get_uint8 ();
            uint32 b3 = stream.Get_uint8 ();

            fDNGBackwardVersion = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3;

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("DNGBackwardVersion: %u.%u.%u.%u\n",
                        (unsigned) b0,
                        (unsigned) b1,
                        (unsigned) b2,
                        (unsigned) b3);
                }

            #endif

            break;

            }

        case tcUniqueCameraModel:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fUniqueCameraModel,
                            false);

            bool didTrim = fUniqueCameraModel.TrimTrailingBlanks ();

            #if qDNGValidate

            if (didTrim)
                {

                ReportWarning ("UniqueCameraModel string has trailing blanks");

                }

            if (gVerbose)
                {

                printf ("UniqueCameraModel: ");

                DumpString (fUniqueCameraModel);

                printf ("\n");

                }

            #else

            (void) didTrim;     // Unused

            #endif

            break;

            }

        case tcLocalizedCameraModel:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fLocalizedCameraModel,
                            false);

            bool didTrim = fLocalizedCameraModel.TrimTrailingBlanks ();

            #if qDNGValidate

            if (didTrim)
                {

                ReportWarning ("LocalizedCameraModel string has trailing blanks");

                }

            if (gVerbose)
                {

                printf ("LocalizedCameraModel: ");

                DumpString (fLocalizedCameraModel);

                printf ("\n");

                }

            #else

            (void) didTrim;     // Unused

            #endif

            break;

            }

        case tcCameraCalibration1:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fCameraProfile.fColorPlanes,
                                 fCameraProfile.fColorPlanes,
                                 fCameraCalibration1))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CameraCalibration1:\n");

                DumpMatrix (fCameraCalibration1);

                }

            #endif

            break;

            }

        case tcCameraCalibration2:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fCameraProfile.fColorPlanes,
                                 fCameraProfile.fColorPlanes,
                                 fCameraCalibration2))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CameraCalibration2:\n");

                DumpMatrix (fCameraCalibration2);

                }

            #endif

            break;

            }

        case tcCameraCalibration3:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fCameraProfile.fColorPlanes,
                                 fCameraProfile.fColorPlanes,
                                 fCameraCalibration3))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CameraCalibration3:\n");

                DumpMatrix (fCameraCalibration3);

                }

            #endif

            break;

            }

        case tcCameraCalibrationSignature:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fCameraCalibrationSignature,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CameraCalibrationSignature: ");

                DumpString (fCameraCalibrationSignature);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcAnalogBalance:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            // Kludge - Hasselblad FFF files are very DNG-like, but sometimes
            // they don't have any ColorMatrix tags.

            bool hasselbladHack = (fDNGVersion == 0 &&
                                   fCameraProfile.fColorPlanes == 0);

            if (hasselbladHack)
                {

                fCameraProfile.fColorPlanes = Pin_uint32 (0, tagCount, kMaxColorPlanes);

                #if qDNGValidate

                ReportWarning ("AnalogBalance without ColorMatrix1");

                #endif

                }

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!ParseVectorTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fCameraProfile.fColorPlanes,
                                 fAnalogBalance))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AnalogBalance:");

                DumpVector (fAnalogBalance);

                }

            #endif

            break;

            }

        case tcAsShotNeutral:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            // Kludge - Hasselblad FFF files are very DNG-like, but sometimes
            // they don't have any ColorMatrix tags.

            bool hasselbladHack = (fDNGVersion == 0 &&
                                   fCameraProfile.fColorPlanes == 0);

            if (hasselbladHack)
                {

                fCameraProfile.fColorPlanes = Pin_uint32 (0, tagCount, kMaxColorPlanes);

                #if qDNGValidate

                ReportWarning ("AsShotNeutral without ColorMatrix1");

                #endif

                }

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!ParseVectorTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 fCameraProfile.fColorPlanes,
                                 fAsShotNeutral))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AsShotNeutral:");

                DumpVector (fAsShotNeutral);

                }

            #endif

            break;

            }

        case tcAsShotWhiteXY:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
                return false;

            fAsShotWhiteXY.x = stream.TagValue_real64 (tagType);
            fAsShotWhiteXY.y = stream.TagValue_real64 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AsShotWhiteXY: %0.4f %0.4f\n",
                        fAsShotWhiteXY.x,
                        fAsShotWhiteXY.y);

                }

            #endif

            break;

            }

        case tcBaselineExposure:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fBaselineExposure = stream.TagValue_srational (tagType);

            fHasBaselineExposure = true;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BaselineExposure: %+0.2f\n",
                        fBaselineExposure.As_real64 ());

                }

            #endif

            break;

            }

        case tcBaselineNoise:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fBaselineNoise = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BaselineNoise: %0.2f\n",
                        fBaselineNoise.As_real64 ());

                }

            #endif

            break;

            }

        case tcBaselineSharpness:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fBaselineSharpness = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BaselineSharpness: %0.2f\n",
                        fBaselineSharpness.As_real64 ());

                }

            #endif

            break;

            }

        case tcLinearResponseLimit:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fLinearResponseLimit = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("LinearResponseLimit: %0.2f\n",
                        fLinearResponseLimit.As_real64 ());

                }

            #endif

            break;

            }

        case tcShadowScale:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fShadowScale = stream.TagValue_urational (tagType);

            fHasShadowScale = true;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ShadowScale: %0.4f\n",
                        fShadowScale.As_real64 ());

                }

            #endif

            break;

            }

        case tcDNGPrivateData:
            {

            CheckTagType (parentCode, tagCode, tagType, ttByte);

            fDNGPrivateDataCount  = tagCount;
            fDNGPrivateDataOffset = tagOffset;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DNGPrivateData: Count = %u, Offset = %u\n",
                        (unsigned) fDNGPrivateDataCount,
                        (unsigned) fDNGPrivateDataOffset);

                DumpHexAscii (stream, tagCount);

                }

            #endif

            break;

            }

        case tcMakerNoteSafety:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fMakerNoteSafety = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("MakerNoteSafety: %s\n",
                        LookupMakerNoteSafety (fMakerNoteSafety));

                }

            #endif

            break;

            }

        case tcRawImageDigest:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
                return false;

            stream.Get (fRawImageDigest.data, 16);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("RawImageDigest: ");

                DumpFingerprint (fRawImageDigest);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcNewRawImageDigest:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
                return false;

            stream.Get (fNewRawImageDigest.data, 16);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("NewRawImageDigest: ");

                DumpFingerprint (fNewRawImageDigest);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcRawDataUniqueID:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
                return false;

            stream.Get (fRawDataUniqueID.data, 16);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("RawDataUniqueID: ");

                DumpFingerprint (fRawDataUniqueID);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcOriginalRawFileName:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fOriginalRawFileName,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalRawFileName: ");

                DumpString (fOriginalRawFileName);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcOriginalRawFileData:
            {

            CheckTagType (parentCode, tagCode, tagType, ttUndefined);

            fOriginalRawFileDataCount  = tagCount;
            fOriginalRawFileDataOffset = tagOffset;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalRawFileData: Count = %u, Offset = %u\n",
                        (unsigned) fOriginalRawFileDataCount,
                        (unsigned) fOriginalRawFileDataOffset);

                DumpHexAscii (stream, tagCount);

                }

            #endif

            break;

            }

        case tcOriginalRawFileDigest:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 16))
                return false;

            stream.Get (fOriginalRawFileDigest.data, 16);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalRawFileDigest: ");

                DumpFingerprint (fOriginalRawFileDigest);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcAsShotICCProfile:
            {

            CheckTagType (parentCode, tagCode, tagType, ttUndefined);

            fAsShotICCProfileCount  = tagCount;
            fAsShotICCProfileOffset = tagOffset;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AsShotICCProfile: Count = %u, Offset = %u\n",
                        (unsigned) fAsShotICCProfileCount,
                        (unsigned) fAsShotICCProfileOffset);

                DumpHexAscii (stream, tagCount);

                }

            #endif

            break;

            }

        case tcAsShotPreProfileMatrix:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            uint32 rows = fCameraProfile.fColorPlanes;

            if (tagCount == fCameraProfile.fColorPlanes * 3)
                {
                rows = 3;
                }

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 rows,
                                 fCameraProfile.fColorPlanes,
                                 fAsShotPreProfileMatrix))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AsShotPreProfileMatrix:\n");

                DumpMatrix (fAsShotPreProfileMatrix);

                }

            #endif

            break;

            }

        case tcCurrentICCProfile:
            {

            CheckTagType (parentCode, tagCode, tagType, ttUndefined);

            fCurrentICCProfileCount  = tagCount;
            fCurrentICCProfileOffset = tagOffset;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CurrentICCProfile: Count = %u, Offset = %u\n",
                        (unsigned) fCurrentICCProfileCount,
                        (unsigned) fCurrentICCProfileOffset);

                DumpHexAscii (stream, tagCount);

                }

            #endif

            break;

            }

        case tcCurrentPreProfileMatrix:
            {

            CheckTagType (parentCode, tagCode, tagType, ttSRational);

            if (!CheckColorImage (parentCode, tagCode, fCameraProfile.fColorPlanes))
                return false;

            uint32 rows = fCameraProfile.fColorPlanes;

            if (tagCount == fCameraProfile.fColorPlanes * 3)
                {
                rows = 3;
                }

            if (!ParseMatrixTag (stream,
                                 parentCode,
                                 tagCode,
                                 tagType,
                                 tagCount,
                                 rows,
                                 fCameraProfile.fColorPlanes,
                                 fCurrentPreProfileMatrix))
                return false;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("CurrentPreProfileMatrix:\n");

                DumpMatrix (fCurrentPreProfileMatrix);

                }

            #endif

            break;

            }

        case tcColorimetricReference:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fColorimetricReference = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ColorimetricReference: %s\n",
                        LookupColorimetricReference (fColorimetricReference));

                }

            #endif

            break;

            }

        case tcExtraCameraProfiles:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttLong8);

            CheckTagCount (parentCode, tagCode, tagCount, 1, tagCount);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ExtraCameraProfiles: %u\n", (unsigned) tagCount);

                }

            #endif

            fExtraCameraProfiles.reserve (tagCount);

            for (uint32 index = 0; index < tagCount; index++)
                {

                #if qDNGValidate

                if (gVerbose)
                    {

                    printf ("\nExtraCameraProfile [%u]:\n\n", (unsigned) index);

                    }

                #endif

                uint64 profileOffset = stream.TagValue_uint64 (tagType);

                uint64 savePosition = stream.Position ();

                dng_camera_profile_info profileInfo;

                stream.SetReadPosition (profileOffset);

                if (profileInfo.ParseExtended (stream))
                    {

                    fExtraCameraProfiles.push_back (profileInfo);

                    }

                else
                    {

                    #if qDNGValidate

                    ReportWarning ("Unable to parse extra camera profile");

                    #endif

                    }

                stream.SetReadPosition (savePosition);

                }

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("\nDone with ExtraCameraProfiles\n\n");

                }

            #endif

            break;

            }

        case tcAsShotProfileName:
            {

            CheckTagType (parentCode, tagCode, tagType, ttAscii, ttByte);

            ParseStringTag (stream,
                            parentCode,
                            tagCode,
                            tagCount,
                            fAsShotProfileName,
                            false);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("AsShotProfileName: ");

                DumpString (fAsShotProfileName);

                printf ("\n");

                }

            #endif

            break;

            }

        case tcOriginalDefaultFinalSize:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);

            if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
                return false;

            fOriginalDefaultFinalSize.h = stream.TagValue_int32 (tagType);
            fOriginalDefaultFinalSize.v = stream.TagValue_int32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalDefaultFinalSize: H = %d V = %d\n",
                        (int) fOriginalDefaultFinalSize.h,
                        (int) fOriginalDefaultFinalSize.v);

                }

            #endif

            break;

            }

        case tcOriginalBestQualityFinalSize:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong);

            if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
                return false;

            fOriginalBestQualityFinalSize.h = stream.TagValue_int32 (tagType);
            fOriginalBestQualityFinalSize.v = stream.TagValue_int32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalBestQualityFinalSize: H = %d V = %d\n",
                        (int) fOriginalBestQualityFinalSize.h,
                        (int) fOriginalBestQualityFinalSize.v);

                }

            #endif

            break;

            }

        case tcOriginalDefaultCropSize:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort, ttLong, ttRational);

            if (!CheckTagCount (parentCode, tagCode, tagCount, 2))
                return false;

            fOriginalDefaultCropSizeH = stream.TagValue_urational (tagType);
            fOriginalDefaultCropSizeV = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("OriginalDefaultCropSize: H = %0.2f V = %0.2f\n",
                        fOriginalDefaultCropSizeH.As_real64 (),
                        fOriginalDefaultCropSizeV.As_real64 ());

                }

            #endif

            break;

            }

        case tcDepthFormat:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDepthFormat = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DepthFormat: %s\n",
                        LookupDepthFormat (fDepthFormat));

                }

            #endif

            break;

            }

        case tcDepthNear:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDepthNear = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DepthNear: ");

                if (fDepthNear == dng_urational (0, 0))
                    {
                    printf ("Unknown");
                    }
                else if (fDepthNear.d == 0)
                    {
                    printf ("Infinity");
                    }
                else
                    {
                    printf ("%0.2f", fDepthNear.As_real64 ());
                    }

                printf ("\n");

                }

            #endif

            break;

            }

         case tcDepthFar:
            {

            CheckTagType (parentCode, tagCode, tagType, ttRational);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDepthFar = stream.TagValue_urational (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DepthFar: ");

                if (fDepthFar == dng_urational (0, 0))
                    {
                    printf ("Unknown");
                    }
                else if (fDepthFar.d == 0)
                    {
                    printf ("Infinity");
                    }
                else
                    {
                    printf ("%0.2f", fDepthFar.As_real64 ());
                    }

                printf ("\n");

                }

            #endif

            break;

            }

        case tcDepthUnits:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDepthUnits = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DepthUnits: %s\n",
                        LookupDepthUnits (fDepthUnits));

                }

            #endif

            break;

            }

        case tcDepthMeasureType:
            {

            CheckTagType (parentCode, tagCode, tagType, ttShort);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fDepthMeasureType = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("DepthMeasureType: %s\n",
                        LookupDepthMeasureType (fDepthMeasureType));

                }

            #endif

            break;

            }

        case tcBigTableDigests:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 16, 0xFFFFFFF0))
                return false;

            uint32 count = tagCount >> 4;

            fBigTableDigests.clear ();
            fBigTableDigests.reserve (count);

            fBigTableOffsets.clear ();
            fBigTableOffsets.reserve (count);

            fBigTableByteCounts.clear ();
            fBigTableByteCounts.reserve (count);

            for (uint32 index = 0; index < count; index++)
                {

                dng_fingerprint fingerprint;

                stream.Get (fingerprint.data, 16);

                fBigTableDigests.push_back (fingerprint);

                fBigTableOffsets   .push_back (0);
                fBigTableByteCounts.push_back (0);

                }

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BigTableDigests:\n");

                for (uint32 index = 0; index < count; index++)
                    {

                    printf ("\t[%u] = ", index);

                    DumpFingerprint (fBigTableDigests [index]);

                    printf ("\n");

                    }

                }

            #endif

            break;

            }

        case tcBigTableOffsets:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttLong, ttLong8))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, (uint32) fBigTableOffsets.size ()))
                return false;

            for (uint32 index = 0; index < tagCount; index++)
                {

                fBigTableOffsets [index] = stream.TagValue_uint64 (tagType);

                if (fBigTableOffsets [index] >= stream.Length ())
                    {

                    DNG_REPORT ("Invalid big table offset");

                    fBigTableDigests   .clear ();
                    fBigTableOffsets   .clear ();
                    fBigTableByteCounts.clear ();

                    return false;

                    }

                }

            #if qDNGValidate

            if (gVerbose)
                {

                stream.SetReadPosition (tagOffset);

                DumpTagValues (stream,
                               "Offset",
                               parentCode,
                               tagCode,
                               tagType,
                               tagCount);

                }

            #endif

            break;

            }

        case tcBigTableByteCounts:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttLong, ttLong8))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, (uint32) fBigTableByteCounts.size ()))
                return false;

            for (uint32 index = 0; index < tagCount; index++)
                {

                uint64 byteCount64 = stream.TagValue_uint64 (tagType);

                if (byteCount64 > 0xFFFFFFFF)
                    {
                    continue;
                    }

                fBigTableByteCounts [index] = (uint32) byteCount64;

                if (fBigTableByteCounts [index] +
                    fBigTableOffsets    [index] > stream.Length ())
                    {

                    DNG_REPORT ("Invalid big table byte count");

                    fBigTableDigests   .clear ();
                    fBigTableOffsets   .clear ();
                    fBigTableByteCounts.clear ();

                    return false;

                    }

                }

            #if qDNGValidate

            if (gVerbose)
                {

                stream.SetReadPosition (tagOffset);

                DumpTagValues (stream,
                               "Count",
                               parentCode,
                               tagCode,
                               tagType,
                               tagCount);

                }

            #endif

            break;

            }

        case tcBigTableGroupIndex:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttByte))
                return false;

            if (!CheckTagCount (parentCode, tagCode, tagCount, 32, 0xFFFFFFF0))
                return false;

            // Pairs of digests:
            // group_digest, dng_fingerprint, 16 bytes
            // instance_digest, dng_fingerprint, 16 bytes
            // Each pair is 32 bytes.

            const uint32 count = tagCount >> 5;

            fBigTableGroupIndex.clear ();

            for (uint32 index = 0; index < count; index++)
                {

                dng_fingerprint groupDigest;
                dng_fingerprint instanceDigest;

                stream.Get (groupDigest   .data, 16);
                stream.Get (instanceDigest.data, 16);

                fBigTableGroupIndex.insert (std::make_pair (groupDigest,
                                                            instanceDigest));

                }

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("BigTableGroupIndex:\n");

                uint32 index = 0;

                for (const auto &entry : fBigTableGroupIndex)
                    {

                    printf ("\t[%u] = ", index);

                    DumpFingerprint (entry.first);

                    printf (" -> ");

                    DumpFingerprint (entry.second);

                    printf ("\n");

                    index++;

                    }

                }

            #endif  // qDNGValidate

            break;

            }

        case tcImageSequenceInfo:
            {

            if (!CheckTagType (parentCode, tagCode, tagType, ttUndefined))
                return false;

            constexpr uint32 kMaxTagCount = 1024 * 1024;

            constexpr uint32 kMinTagCount = (9 + // Sequence ID
                                             2 + // Sequence Type
                                             1 + // Frame Info
                                             4 + // Index
                                             4 + // Count
                                             1); // Final

            if (tagCount < kMinTagCount)
                {

                #if qDNGValidate
                ReportWarning ("ImageSequenceInfo tag too small -- skipping");
                #endif

                return false;

                }

            if (tagCount > kMaxTagCount)
                {

                #if qDNGValidate
                ReportWarning ("ImageSequenceInfo tag unusually large -- skipping");
                #endif

                return false;

                }

            dng_image_sequence_info &info = fImageSequenceInfo;

            std::vector<char> buf;

            buf.resize (tagCount + 1);

            char *ptr = &buf [0];

            // Read sequence ID.

            stream.Get_CString (ptr, tagCount);

            info.fSequenceID.Set (ptr);

            // Read sequence type.

            stream.Get_CString (ptr, tagCount);

            info.fSequenceType.Set (ptr);

            // Read frame info.

            stream.Get_CString (ptr, tagCount);

            info.fFrameInfo.Set (ptr);

            // Get index, count, and final fields.

            TempBigEndian tempEndian (stream);

            info.fIndex = stream.Get_uint32 ();
            info.fCount = stream.Get_uint32 ();

            info.fIsFinal = stream.Get_uint8 ();

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("ImageSequenceInfo: seq_id=%s, seq_type=%s, "
                        "frame_info=%s, index=%u, count=%u, final=%u\n",
                        info.fSequenceID.Get (),
                        info.fSequenceType.Get (),
                        info.fFrameInfo.Get (),
                        unsigned (info.fIndex),
                        unsigned (info.fCount),
                        unsigned (info.fIsFinal));


                }

            #endif  // qDNGValidate

            break;

            }

        default:
            {

            // The main camera profile tags also appear in IFD 0

            return fCameraProfile.ParseTag (stream,
                                            parentCode,
                                            tagCode,
                                            tagType,
                                            tagCount,
                                            tagOffset);

            }

        }

    return true;

    }

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

// Parses tags that should only appear in IFD 0 or EXIF IFD.

bool dng_shared::Parse_ifd0_exif (dng_stream &stream,
                                  dng_exif & /* exif */,
                                  uint32 parentCode,
                                  uint32 tagCode,
                                  uint32 tagType,
                                  uint32 tagCount,
                                  uint64 tagOffset)
    {

    switch (tagCode)
        {

        case tcMakerNote:
            {

            CheckTagType (parentCode, tagCode, tagType, ttUndefined);

            fMakerNoteCount  = tagCount;
            fMakerNoteOffset = tagOffset;

            #if qDNGValidate

            if (gVerbose)
                {

                printf ("MakerNote: Count = %u, Offset = %u\n",
                        (unsigned) fMakerNoteCount,
                        (unsigned) fMakerNoteOffset);

                DumpHexAscii (stream, tagCount);

                }

            #endif

            break;

            }

        case tcInteroperabilityIFD:
            {

            CheckTagType (parentCode, tagCode, tagType, ttLong, ttIFD);

            CheckTagCount (parentCode, tagCode, tagCount, 1);

            fInteroperabilityIFD = stream.TagValue_uint32 (tagType);

            #if qDNGValidate

            if (gVerbose)
                {
                printf ("InteroperabilityIFD: %u\n", (unsigned) fInteroperabilityIFD);
                }

            #endif

            break;

            }

        default:
            {

            return false;

            }

        }

    return true;

    }

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

void dng_shared::PostParse (dng_host & /* host */,
                            dng_exif & /* exif */)
    {

    // Fill in default values for DNG images.

    if (fDNGVersion != 0)
        {

        // Support for DNG versions before 1.0.0.0.

        if (fDNGVersion < dngVersion_1_0_0_0)
            {

            #if qDNGValidate

            ReportWarning ("DNGVersion less than 1.0.0.0");

            #endif

            // The CalibrationIlluminant tags were added just before
            // DNG version 1.0.0.0, and were hardcoded before that.

            fCameraProfile.fCalibrationIlluminant1 = lsStandardLightA;
            fCameraProfile.fCalibrationIlluminant2 = lsD65;
            fCameraProfile.fCalibrationIlluminant3 = lsD65;

            fDNGVersion = dngVersion_1_0_0_0;

            }

        // Default value for DNGBackwardVersion tag.

        if (fDNGBackwardVersion == 0)
            {

            fDNGBackwardVersion = fDNGVersion & 0xFFFF0000;

            }

        // Check DNGBackwardVersion value.

        if (fDNGBackwardVersion < dngVersion_1_0_0_0)
            {

            #if qDNGValidate

            ReportWarning ("DNGBackwardVersion less than 1.0.0.0");

            #endif

            fDNGBackwardVersion = dngVersion_1_0_0_0;

            }

        if (fDNGBackwardVersion > fDNGVersion)
            {

            #if qDNGValidate

            ReportWarning ("DNGBackwardVersion > DNGVersion");

            #endif

            fDNGBackwardVersion = fDNGVersion;

            }

        // Check UniqueCameraModel.

        if (fUniqueCameraModel.IsEmpty ())
            {

            #if qDNGValidate

            ReportWarning ("Missing or invalid UniqueCameraModel");

            #endif

            fUniqueCameraModel.Set ("Digital Negative");

            }

        // If we don't know the color depth yet, it must be a monochrome DNG.

        if (fCameraProfile.fColorPlanes == 0)
            {

            fCameraProfile.fColorPlanes = 1;

            }

        if ((fCameraProfile.fCalibrationIlluminant3 != lsUnknown) &&
            fCameraProfile.fColorMatrix3.IsEmpty ())
            {

            #if qDNGValidate

            ReportWarning ("Ignoring CalibrationIlluminant3 because "
                           "ColorMatrix3 invalid or missing");

            #endif

            fCameraProfile.fCalibrationIlluminant3 = lsUnknown;

            }

        if (fCameraProfile.fCalibrationIlluminant3 == lsUnknown)
            {

            if (fCameraProfile.fColorMatrix3.NotEmpty ())
                {

                #if qDNGValidate

                ReportWarning ("Ignoring ColorMatrix3 because "
                               "CalibrationIlluminant3 is unknown/missing");

                #endif

                fCameraProfile.fColorMatrix3.Clear ();

                }

            if (fCameraProfile.fForwardMatrix3.NotEmpty ())
                {

                #if qDNGValidate

                ReportWarning ("Ignoring ForwardMatrix3 because "
                               "CalibrationIlluminant3 is unknown/missing");

                #endif

                fCameraProfile.fForwardMatrix3.Clear ();

                }

            if (fCameraProfile.fReductionMatrix3.NotEmpty ())
                {

                #if qDNGValidate

                ReportWarning ("Ignoring ReductionMatrix3 because "
                               "CalibrationIlluminant3 is unknown/missing");

                #endif

                fCameraProfile.fReductionMatrix3.Clear ();

                }

            if (fCameraProfile.fHueSatDeltas3Offset != 0 ||
                fCameraProfile.fHueSatDeltas3Count  != 0)
                {

                #if qDNGValidate

                ReportWarning ("Ignoring ProfileHueSatMapData3 because "
                               "CalibrationIlluminant3 is unknown/missing");

                #endif

                fCameraProfile.fHueSatDeltas3Offset = 0;
                fCameraProfile.fHueSatDeltas3Count  = 0;

                }

            }

        // Check color info.

        if (fCameraProfile.fColorPlanes > 1)
            {

            // Check illuminant pair. This is for the dual-illuminant model.

            // More detailed checks for triple-illuminant profiles are done in
            // dng_camera_profile::IsValid.

            if (fCameraProfile.fColorMatrix2.NotEmpty ())
                {

                // Are illuminants 1 and 2 the same? Start by assuming not.

                bool sameLight = false;

                if (fCameraProfile.fCalibrationIlluminant1 == fCameraProfile.fCalibrationIlluminant2)
                    {

                    // Lights 1 and 2 have the same tag code. If they are
                    // something besides lsOther, then they're the same light.

                    if (fCameraProfile.fCalibrationIlluminant1 != lsOther)
                        {

                        sameLight = true;

                        }

                    // Both are lsOther, so check the custom data fields for
                    // the same white point.

                    else
                        {

                        dng_xy_coord white1 = fCameraProfile.fIlluminantData1.WhiteXY ();
                        dng_xy_coord white2 = fCameraProfile.fIlluminantData2.WhiteXY ();

                        sameLight = (white1 == white2);

                        }

                    }

                if (fCameraProfile.fCalibrationIlluminant1 == lsUnknown ||
                    fCameraProfile.fCalibrationIlluminant2 == lsUnknown ||
                    sameLight)
                    {

                    #if qDNGValidate

                    ReportWarning ("Invalid CalibrationIlluminant pair");

                    #endif

                    fCameraProfile.fColorMatrix2 = dng_matrix ();

                    }

                }

            // If the colorimetric reference is the ICC profile PCS, then the
            // data must already be white balanced. The "AsShotWhiteXY" is
            // required to be the ICC Profile PCS white point.

            if (fColorimetricReference == crICCProfilePCS ||
                fColorimetricReference == crOutputReferredHDR)
                {

                if (fAsShotNeutral.NotEmpty ())
                    {

                    #if qDNGValidate

                    ReportWarning ("AsShotNeutral not allowed for this "
                                   "ColorimetricReference value");

                    #endif

                    fAsShotNeutral.Clear ();

                    }

                dng_xy_coord pcs = PCStoXY ();

                #if qDNGValidate

                if (fAsShotWhiteXY.IsValid ())
                    {

                    if (Abs_real64 (fAsShotWhiteXY.x - pcs.x) > 0.01 ||
                        Abs_real64 (fAsShotWhiteXY.y - pcs.y) > 0.01)
                        {

                        ReportWarning ("AsShotWhiteXY does not match the ICC Profile PCS");

                        }

                    }

                #endif

                fAsShotWhiteXY = pcs;

                }

            else
                {

                // Warn if both AsShotNeutral and AsShotWhiteXY are specified.

                if (fAsShotNeutral.NotEmpty () && fAsShotWhiteXY.IsValid ())
                    {

                    #if qDNGValidate

                    ReportWarning ("Both AsShotNeutral and AsShotWhiteXY included");

                    #endif

                    fAsShotWhiteXY = dng_xy_coord ();

                    }

                // Warn if neither AsShotNeutral nor AsShotWhiteXY are specified.

                #if qDNGValidate

                if (fAsShotNeutral.IsEmpty () && !fAsShotWhiteXY.IsValid ())
                    {

                    ReportWarning ("Neither AsShotNeutral nor AsShotWhiteXY included",
                                   "legal but not recommended");

                    }

                #endif

                }

            // Default values of calibration signatures are required for legacy
            // compatibility.

            if (fCameraProfile.fCalibrationIlluminant1 == lsStandardLightA &&
                fCameraProfile.fCalibrationIlluminant2 == lsD65            &&
                fCameraProfile.fCalibrationIlluminant3 == lsUnknown        &&
                fCameraCalibration1.Rows () == fCameraProfile.fColorPlanes &&
                fCameraCalibration1.Cols () == fCameraProfile.fColorPlanes &&
                fCameraCalibration2.Rows () == fCameraProfile.fColorPlanes &&
                fCameraCalibration2.Cols () == fCameraProfile.fColorPlanes &&
                fCameraCalibration3.IsEmpty ()                             &&
                fCameraCalibrationSignature.IsEmpty ()                     &&
                fCameraProfile.fProfileCalibrationSignature.IsEmpty ()     )
                {

                fCameraCalibrationSignature.Set (kAdobeCalibrationSignature);

                fCameraProfile.fProfileCalibrationSignature.Set (kAdobeCalibrationSignature);

                }

            }

        // Check BaselineNoise.

        if (fBaselineNoise.As_real64 () <= 0.0)
            {

            #if qDNGValidate

            ReportWarning ("Invalid BaselineNoise");

            #endif

            fBaselineNoise = dng_urational (1, 1);

            }

        // Check BaselineSharpness.

        if (fBaselineSharpness.As_real64 () <= 0.0)
            {

            #if qDNGValidate

            ReportWarning ("Invalid BaselineSharpness");

            #endif

            fBaselineSharpness = dng_urational (1, 1);

            }

        // Check LinearResponseLimit.

        if (fLinearResponseLimit.As_real64 () < 0.5 ||
            fLinearResponseLimit.As_real64 () > 1.0)
            {

            #if qDNGValidate

            ReportWarning ("Invalid LinearResponseLimit");

            #endif

            fLinearResponseLimit = dng_urational (1, 1);

            }

        // Check ShadowScale.

        if (fShadowScale.As_real64 () <= 0.0)
            {

            #if qDNGValidate

            ReportWarning ("Invalid ShadowScale");

            #endif

            fShadowScale = dng_urational (1, 1);

            }

        }

    }

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

bool dng_shared::IsValidDNG ()
    {

    // Check DNGVersion value.

    if (fDNGVersion < dngVersion_1_0_0_0)
        {

        #if qDNGValidate

        if (fDNGVersion != dngVersion_None)
            {

            ReportError ("Invalid DNGVersion");

            }

        #if qDNGValidateTarget

        else
            {

            ReportError ("Missing DNGVersion");

            }

        #endif

        #endif

        return false;

        }

    // Check DNGBackwardVersion value.

    if (fDNGBackwardVersion > dngVersion_Current)
        {

        #if qDNGValidate

        ReportError ("DNGBackwardVersion (or DNGVersion) is too high");

        #endif

        ThrowUnsupportedDNG ();

        }

    // Check color transform info.

    if (fCameraProfile.fColorPlanes > 1)
        {

        // CameraCalibration1 is optional, but it must be valid if present.

        if (fCameraCalibration1.Cols () != 0 ||
            fCameraCalibration1.Rows () != 0)
            {

            if (fCameraCalibration1.Cols () != fCameraProfile.fColorPlanes ||
                fCameraCalibration1.Rows () != fCameraProfile.fColorPlanes)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration1 is wrong size");

                #endif

                return false;

                }

            // Make sure it is invertable.

            try
                {

                (void) Invert (fCameraCalibration1);

                }

            catch (...)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration1 is not invertable");

                #endif

                return false;

                }

            }

        // CameraCalibration2 is optional, but it must be valid if present.

        if (fCameraCalibration2.Cols () != 0 ||
            fCameraCalibration2.Rows () != 0)
            {

            if (fCameraCalibration2.Cols () != fCameraProfile.fColorPlanes ||
                fCameraCalibration2.Rows () != fCameraProfile.fColorPlanes)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration2 is wrong size");

                #endif

                return false;

                }

            // Make sure it is invertable.

            try
                {

                (void) Invert (fCameraCalibration2);

                }

            catch (...)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration2 is not invertable");

                #endif

                return false;

                }

            }

        // CameraCalibration3 is optional, but it must be valid if present.

        if (fCameraCalibration3.Cols () != 0 ||
            fCameraCalibration3.Rows () != 0)
            {

            if (fCameraCalibration3.Cols () != fCameraProfile.fColorPlanes ||
                fCameraCalibration3.Rows () != fCameraProfile.fColorPlanes)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration3 is wrong size");

                #endif

                return false;

                }

            // Make sure it is invertable.

            try
                {

                (void) Invert (fCameraCalibration3);

                }

            catch (...)
                {

                #if qDNGValidate

                ReportError ("CameraCalibration3 is not invertable");

                #endif

                return false;

                }

            }

        // Check analog balance

        dng_matrix analogBalance;

        if (fAnalogBalance.NotEmpty ())
            {

            analogBalance = fAnalogBalance.AsDiagonal ();

            try
                {

                (void) Invert (analogBalance);

                }

            catch (...)
                {

                #if qDNGValidate

                ReportError ("AnalogBalance is not invertable");

                #endif

                return false;

                }

            }

        }

    return true;

    }

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