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

#include "dng_parse_utils.h"

#include "dng_date_time.h"
#include "dng_globals.h"
#include "dng_ifd.h"
#include "dng_tag_codes.h"
#include "dng_tag_types.h"
#include "dng_tag_values.h"
#include "dng_types.h"
#include "dng_stream.h"
#include "dng_exceptions.h"
#include "dng_utils.h"

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

#if qDNGValidate || qDNGDebug

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

struct dng_name_table
    {
    uint32 key;
    const char *name;
    };

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

static const char * LookupName (uint32 key,
                                const dng_name_table *table,
                                uint32 table_entries)
    {

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

        if (key == table [index] . key)
            {

            return table [index] . name;

            }

        }

    return NULL;

    }

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

const char * LookupParentCode (uint32 parentCode)
    {

    const dng_name_table kParentCodeNames [] =
        {
        {   0,                          "IFD 0"                         },
        {   tcExifIFD,                  "Exif IFD"                      },
        {   tcGPSInfo,                  "GPS IFD"                       },
        {   tcInteroperabilityIFD,      "Interoperability IFD"          },
        {   tcKodakDCRPrivateIFD,       "Kodak DCR Private IFD"         },
        {   tcKodakKDCPrivateIFD,       "Kodak KDC Private IFD"         },
        {   tcCanonMakerNote,           "Canon MakerNote"               },
        {   tcEpsonMakerNote,           "Epson MakerNote"               },
        {   tcFujiMakerNote,            "Fuji MakerNote"                },
        {   tcHasselbladMakerNote,      "Hasselblad MakerNote"          },
        {   tcKodakMakerNote,           "Kodak MakerNote"               },
        {   tcKodakMakerNote65280,      "Kodak MakerNote 65280"         },
        {   tcLeicaMakerNote,           "Leica MakerNote"               },
        {   tcMamiyaMakerNote,          "Mamiya MakerNote"              },
        {   tcMinoltaMakerNote,         "Minolta MakerNote"             },
        {   tcNikonMakerNote,           "Nikon MakerNote"               },
        {   tcOlympusMakerNote,         "Olympus MakerNote"             },
        {   tcOlympusMakerNote8208,     "Olympus MakerNote 8208"        },
        {   tcOlympusMakerNote8224,     "Olympus MakerNote 8224"        },
        {   tcOlympusMakerNote8240,     "Olympus MakerNote 8240"        },
        {   tcOlympusMakerNote8256,     "Olympus MakerNote 8256"        },
        {   tcOlympusMakerNote8272,     "Olympus MakerNote 8272"        },
        {   tcOlympusMakerNote12288,    "Olympus MakerNote 12288"       },
        {   tcPanasonicMakerNote,       "Panasonic MakerNote"           },
        {   tcPentaxMakerNote,          "Pentax MakerNote"              },
        {   tcPhaseOneMakerNote,        "Phase One MakerNote"           },
        {   tcRicohMakerNote,           "Ricoh MakerNote"               },
        {   tcRicohMakerNoteCameraInfo, "Ricoh MakerNote Camera Info"   },
        {   tcSonyMakerNote,            "Sony MakerNote"                },
        {   tcSonyMakerNoteSubInfo,     "Sony MakerNote SubInfo"        },
        {   tcSonyPrivateIFD1,          "Sony Private IFD 1"            },
        {   tcSonyPrivateIFD2,          "Sony Private IFD 2"            },
        {   tcSonyPrivateIFD3A,         "Sony Private IFD 3A"           },
        {   tcSonyPrivateIFD3B,         "Sony Private IFD 3B"           },
        {   tcSonyPrivateIFD3C,         "Sony Private IFD 3C"           },
        {   tcCanonCRW,                 "Canon CRW"                     },
        {   tcContaxRAW,                "Contax RAW"                    },
        {   tcFujiRAF,                  "Fuji RAF"                      },
        {   tcLeafMOS,                  "Leaf MOS"                      },
        {   tcMinoltaMRW,               "Minolta MRW"                   },
        {   tcPanasonicRAW,             "Panasonic RAW"                 },
        {   tcFoveonX3F,                "Foveon X3F"                    },
        {   tcJPEG,                     "JPEG"                          },
        {   tcAdobePSD,                 "Adobe PSD"                     },
        {   tcPNG,                      "PNG"                           },
        {   tcHEIC,                     "HEIC"                          },
        {   tcJXL,                      "JXL"                           },
        {   tcAVIF,                     "AVIF"                          },
        {   tcAppleMakerNote,           "Apple MakerNote"               },
        };

    const char *name = LookupName (parentCode,
                                   kParentCodeNames,
                                   sizeof (kParentCodeNames    ) /
                                   sizeof (kParentCodeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    if (parentCode >= tcFirstSubIFD &&
        parentCode <= tcLastSubIFD)
        {

        snprintf (s, 32, "SubIFD %u", (unsigned) (parentCode - tcFirstSubIFD + 1));

        }

    else if (parentCode >= tcFirstChainedIFD &&
             parentCode <= tcLastChainedIFD)
        {

        snprintf (s, 32, "Chained IFD %u", (unsigned) (parentCode - tcFirstChainedIFD + 1));

        }

    else
        {

        snprintf (s, 32, "ParentIFD %u", (unsigned) parentCode);

        }

    return s;

    }

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

const char * LookupTagCode (uint32 parentCode,
                            uint32 tagCode)
    {

    const dng_name_table kTagNames [] =
        {
        {   tcNewSubFileType,                   "NewSubFileType"                },
        {   tcSubFileType,                      "SubFileType"                   },
        {   tcImageWidth,                       "ImageWidth"                    },
        {   tcImageLength,                      "ImageLength"                   },
        {   tcBitsPerSample,                    "BitsPerSample"                 },
        {   tcCompression,                      "Compression"                   },
        {   tcPhotometricInterpretation,        "PhotometricInterpretation"     },
        {   tcThresholding,                     "Thresholding"                  },
        {   tcCellWidth,                        "CellWidth"                     },
        {   tcCellLength,                       "CellLength"                    },
        {   tcFillOrder,                        "FillOrder"                     },
        {   tcImageDescription,                 "ImageDescription"              },
        {   tcMake,                             "Make"                          },
        {   tcModel,                            "Model"                         },
        {   tcStripOffsets,                     "StripOffsets"                  },
        {   tcOrientation,                      "Orientation"                   },
        {   tcSamplesPerPixel,                  "SamplesPerPixel"               },
        {   tcRowsPerStrip,                     "RowsPerStrip"                  },
        {   tcStripByteCounts,                  "StripByteCounts"               },
        {   tcMinSampleValue,                   "MinSampleValue"                },
        {   tcMaxSampleValue,                   "MaxSampleValue"                },
        {   tcXResolution,                      "XResolution"                   },
        {   tcYResolution,                      "YResolution"                   },
        {   tcPlanarConfiguration,              "PlanarConfiguration"           },
        {   tcFreeOffsets,                      "FreeOffsets"                   },
        {   tcFreeByteCounts,                   "FreeByteCounts"                },
        {   tcGrayResponseUnit,                 "GrayResponseUnit"              },
        {   tcGrayResponseCurve,                "GrayResponseCurve"             },
        {   tcResolutionUnit,                   "ResolutionUnit"                },
        {   tcTransferFunction,                 "TransferFunction"              },
        {   tcSoftware,                         "Software"                      },
        {   tcDateTime,                         "DateTime"                      },
        {   tcArtist,                           "Artist"                        },
        {   tcHostComputer,                     "HostComputer"                  },
        {   tcWhitePoint,                       "WhitePoint"                    },
        {   tcPrimaryChromaticities,            "PrimaryChromaticities"         },
        {   tcColorMap,                         "ColorMap"                      },
        {   tcTileWidth,                        "TileWidth"                     },
        {   tcTileLength,                       "TileLength"                    },
        {   tcTileOffsets,                      "TileOffsets"                   },
        {   tcTileByteCounts,                   "TileByteCounts"                },
        {   tcSubIFDs,                          "SubIFDs"                       },
        {   tcExtraSamples,                     "ExtraSamples"                  },
        {   tcSampleFormat,                     "SampleFormat"                  },
        {   tcJPEGTables,                       "JPEGTables"                    },
        {   tcJPEGProc,                         "JPEGProc"                      },
        {   tcJPEGInterchangeFormat,            "JPEGInterchangeFormat"         },
        {   tcJPEGInterchangeFormatLength,      "JPEGInterchangeFormatLength"   },
        {   tcYCbCrCoefficients,                "YCbCrCoefficients"             },
        {   tcYCbCrSubSampling,                 "YCbCrSubSampling"              },
        {   tcYCbCrPositioning,                 "YCbCrPositioning"              },
        {   tcReferenceBlackWhite,              "ReferenceBlackWhite"           },
        {   tcXMP,                              "XMP"                           },
        {   tcKodakCameraSerialNumber,          "KodakCameraSerialNumber"       },
        {   tcCFARepeatPatternDim,              "CFARepeatPatternDim"           },
        {   tcCFAPattern,                       "CFAPattern"                    },
        {   tcBatteryLevel,                     "BatteryLevel"                  },
        {   tcKodakDCRPrivateIFD,               "KodakDCRPrivateIFD"            },
        {   tcCopyright,                        "Copyright"                     },
        {   tcExposureTime,                     "ExposureTime"                  },
        {   tcFNumber,                          "FNumber"                       },
        {   tcIPTC_NAA,                         "IPTC/NAA"                      },
        {   tcLeafPKTS,                         "LeafPKTS"                      },
        {   tcAdobeData,                        "AdobeData"                     },
        {   tcExifIFD,                          "ExifIFD"                       },
        {   tcICCProfile,                       "ICCProfile"                    },
        {   tcExposureProgram,                  "ExposureProgram"               },
        {   tcSpectralSensitivity,              "SpectralSensitivity"           },
        {   tcGPSInfo,                          "GPSInfo"                       },
        {   tcISOSpeedRatings,                  "ISOSpeedRatings"               },
        {   tcOECF,                             "OECF"                          },
        {   tcInterlace,                        "Interlace"                     },
        {   tcTimeZoneOffset,                   "TimeZoneOffset"                },
        {   tcSelfTimerMode,                    "SelfTimerMode"                 },
        {   tcSensitivityType,                  "SensitivityType"               },
        {   tcStandardOutputSensitivity,        "StandardOutputSensitivity"     },
        {   tcRecommendedExposureIndex,         "RecommendedExposureIndex"      },
        {   tcISOSpeed,                         "ISOSpeed"                      },
        {   tcISOSpeedLatitudeyyy,              "ISOSpeedLatitudeyyy"           },
        {   tcISOSpeedLatitudezzz,              "ISOSpeedLatitudezzz"           },
        {   tcExifVersion,                      "ExifVersion"                   },
        {   tcDateTimeOriginal,                 "DateTimeOriginal"              },
        {   tcDateTimeDigitized,                "DateTimeDigitized"             },
        {   tcOffsetTime,                       "OffsetTime"                    },
        {   tcOffsetTimeOriginal,               "OffsetTimeOriginal"            },
        {   tcOffsetTimeDigitized,              "OffsetTimeDigitized"           },
        {   tcComponentsConfiguration,          "ComponentsConfiguration"       },
        {   tcCompressedBitsPerPixel,           "CompressedBitsPerPixel"        },
        {   tcShutterSpeedValue,                "ShutterSpeedValue"             },
        {   tcApertureValue,                    "ApertureValue"                 },
        {   tcBrightnessValue,                  "BrightnessValue"               },
        {   tcExposureBiasValue,                "ExposureBiasValue"             },
        {   tcMaxApertureValue,                 "MaxApertureValue"              },
        {   tcSubjectDistance,                  "SubjectDistance"               },
        {   tcMeteringMode,                     "MeteringMode"                  },
        {   tcLightSource,                      "LightSource"                   },
        {   tcFlash,                            "Flash"                         },
        {   tcFocalLength,                      "FocalLength"                   },
        {   tcFlashEnergy,                      "FlashEnergy"                   },
        {   tcSpatialFrequencyResponse,         "SpatialFrequencyResponse"      },
        {   tcNoise,                            "Noise"                         },
        {   tcFocalPlaneXResolution,            "FocalPlaneXResolution"         },
        {   tcFocalPlaneYResolution,            "FocalPlaneYResolution"         },
        {   tcFocalPlaneResolutionUnit,         "FocalPlaneResolutionUnit"      },
        {   tcImageNumber,                      "ImageNumber"                   },
        {   tcSecurityClassification,           "SecurityClassification"        },
        {   tcImageHistory,                     "ImageHistory"                  },
        {   tcSubjectArea,                      "SubjectArea"                   },
        {   tcExposureIndex,                    "ExposureIndex"                 },
        {   tcTIFF_EP_StandardID,               "TIFF/EPStandardID"             },
        {   tcSensingMethod,                    "SensingMethod"                 },
        {   tcMakerNote,                        "MakerNote"                     },
        {   tcUserComment,                      "UserComment"                   },
        {   tcSubsecTime,                       "SubsecTime"                    },
        {   tcSubsecTimeOriginal,               "SubsecTimeOriginal"            },
        {   tcSubsecTimeDigitized,              "SubsecTimeDigitized"           },
        {   tcAdobeLayerData,                   "AdobeLayerData"                },
        {   tcTemperature,                      "Temperature"                   },
        {   tcHumidity,                         "Humidity"                      },
        {   tcPressure,                         "Pressure"                      },
        {   tcWaterDepth,                       "WaterDepth"                    },
        {   tcAcceleration,                     "Acceleration"                  },
        {   tcCameraElevationAngle,             "CameraElevationAngle"          },
        {   tcFlashPixVersion,                  "FlashPixVersion"               },
        {   tcColorSpace,                       "ColorSpace"                    },
        {   tcPixelXDimension,                  "PixelXDimension"               },
        {   tcPixelYDimension,                  "PixelYDimension"               },
        {   tcRelatedSoundFile,                 "RelatedSoundFile"              },
        {   tcInteroperabilityIFD,              "InteroperabilityIFD"           },
        {   tcFlashEnergyExif,                  "FlashEnergyExif"               },
        {   tcSpatialFrequencyResponseExif,     "SpatialFrequencyResponseExif"  },
        {   tcFocalPlaneXResolutionExif,        "FocalPlaneXResolutionExif"     },
        {   tcFocalPlaneYResolutionExif,        "FocalPlaneYResolutionExif"     },
        {   tcFocalPlaneResolutionUnitExif,     "FocalPlaneResolutionUnitExif"  },
        {   tcSubjectLocation,                  "SubjectLocation"               },
        {   tcExposureIndexExif,                "ExposureIndexExif"             },
        {   tcSensingMethodExif,                "SensingMethodExif"             },
        {   tcFileSource,                       "FileSource"                    },
        {   tcSceneType,                        "SceneType"                     },
        {   tcCFAPatternExif,                   "CFAPatternExif"                },
        {   tcCustomRendered,                   "CustomRendered"                },
        {   tcExposureMode,                     "ExposureMode"                  },
        {   tcWhiteBalance,                     "WhiteBalance"                  },
        {   tcDigitalZoomRatio,                 "DigitalZoomRatio"              },
        {   tcFocalLengthIn35mmFilm,            "FocalLengthIn35mmFilm"         },
        {   tcSceneCaptureType,                 "SceneCaptureType"              },
        {   tcGainControl,                      "GainControl"                   },
        {   tcContrast,                         "Contrast"                      },
        {   tcSaturation,                       "Saturation"                    },
        {   tcSharpness,                        "Sharpness"                     },
        {   tcDeviceSettingDescription,         "DeviceSettingDescription"      },
        {   tcSubjectDistanceRange,             "SubjectDistanceRange"          },
        {   tcImageUniqueID,                    "ImageUniqueID"                 },
        {   tcCameraOwnerNameExif,              "CameraOwnerNameExif"           },
        {   tcCameraSerialNumberExif,           "CameraSerialNumberExif"        },
        {   tcLensSpecificationExif,            "LensSpecificationExif"         },
        {   tcLensMakeExif,                     "LensMakeExif"                  },
        {   tcLensModelExif,                    "LensModelExif"                 },
        {   tcLensSerialNumberExif,             "LensSerialNumberExif"          },
        {   tcGamma,                            "Gamma"                         },
        {   tcPrintImageMatchingInfo,           "PrintImageMatchingInfo"        },
        {   tcDNGVersion,                       "DNGVersion"                    },
        {   tcDNGBackwardVersion,               "DNGBackwardVersion"            },
        {   tcUniqueCameraModel,                "UniqueCameraModel"             },
        {   tcLocalizedCameraModel,             "LocalizedCameraModel"          },
        {   tcCFAPlaneColor,                    "CFAPlaneColor"                 },
        {   tcCFALayout,                        "CFALayout"                     },
        {   tcLinearizationTable,               "LinearizationTable"            },
        {   tcBlackLevelRepeatDim,              "BlackLevelRepeatDim"           },
        {   tcBlackLevel,                       "BlackLevel"                    },
        {   tcBlackLevelDeltaH,                 "BlackLevelDeltaH"              },
        {   tcBlackLevelDeltaV,                 "BlackLevelDeltaV"              },
        {   tcWhiteLevel,                       "WhiteLevel"                    },
        {   tcDefaultScale,                     "DefaultScale"                  },
        {   tcDefaultCropOrigin,                "DefaultCropOrigin"             },
        {   tcDefaultCropSize,                  "DefaultCropSize"               },
        {   tcDefaultUserCrop,                  "DefaultUserCrop"               },
        {   tcColorMatrix1,                     "ColorMatrix1"                  },
        {   tcColorMatrix2,                     "ColorMatrix2"                  },
        {   tcColorMatrix3,                     "ColorMatrix3"                  },
        {   tcCameraCalibration1,               "CameraCalibration1"            },
        {   tcCameraCalibration2,               "CameraCalibration2"            },
        {   tcCameraCalibration3,               "CameraCalibration3"            },
        {   tcReductionMatrix1,                 "ReductionMatrix1"              },
        {   tcReductionMatrix2,                 "ReductionMatrix2"              },
        {   tcReductionMatrix3,                 "ReductionMatrix3"              },
        {   tcAnalogBalance,                    "AnalogBalance"                 },
        {   tcAsShotNeutral,                    "AsShotNeutral"                 },
        {   tcAsShotWhiteXY,                    "AsShotWhiteXY"                 },
        {   tcBaselineExposure,                 "BaselineExposure"              },
        {   tcBaselineNoise,                    "BaselineNoise"                 },
        {   tcBaselineSharpness,                "BaselineSharpness"             },
        {   tcBayerGreenSplit,                  "BayerGreenSplit"               },
        {   tcLinearResponseLimit,              "LinearResponseLimit"           },
        {   tcCameraSerialNumber,               "CameraSerialNumber"            },
        {   tcLensInfo,                         "LensInfo"                      },
        {   tcChromaBlurRadius,                 "ChromaBlurRadius"              },
        {   tcAntiAliasStrength,                "AntiAliasStrength"             },
        {   tcShadowScale,                      "ShadowScale"                   },
        {   tcDNGPrivateData,                   "DNGPrivateData"                },
        {   tcMakerNoteSafety,                  "MakerNoteSafety"               },
        {   tcCalibrationIlluminant1,           "CalibrationIlluminant1"        },
        {   tcCalibrationIlluminant2,           "CalibrationIlluminant2"        },
        {   tcCalibrationIlluminant3,           "CalibrationIlluminant3"        },
        {   tcIlluminantData1,                  "IlluminantData1"               },
        {   tcIlluminantData2,                  "IlluminantData2"               },
        {   tcIlluminantData3,                  "IlluminantData3"               },
        {   tcBestQualityScale,                 "BestQualityScale"              },
        {   tcRawDataUniqueID,                  "RawDataUniqueID"               },
        {   tcOriginalRawFileName,              "OriginalRawFileName"           },
        {   tcOriginalRawFileData,              "OriginalRawFileData"           },
        {   tcActiveArea,                       "ActiveArea"                    },
        {   tcMaskedAreas,                      "MaskedAreas"                   },
        {   tcAsShotICCProfile,                 "AsShotICCProfile"              },
        {   tcAsShotPreProfileMatrix,           "AsShotPreProfileMatrix"        },
        {   tcCurrentICCProfile,                "CurrentICCProfile"             },
        {   tcCurrentPreProfileMatrix,          "CurrentPreProfileMatrix"       },
        {   tcColorimetricReference,            "ColorimetricReference"         },
        {   tcCameraCalibrationSignature,       "CameraCalibrationSignature"    },
        {   tcProfileCalibrationSignature,      "ProfileCalibrationSignature"   },
        {   tcExtraCameraProfiles,              "ExtraCameraProfiles"           },
        {   tcAsShotProfileName,                "AsShotProfileName"             },
        {   tcNoiseReductionApplied,            "NoiseReductionApplied"         },
        {   tcProfileName,                      "ProfileName"                   },
        {   tcProfileHueSatMapDims,             "ProfileHueSatMapDims"          },
        {   tcProfileHueSatMapData1,            "ProfileHueSatMapData1"         },
        {   tcProfileHueSatMapData2,            "ProfileHueSatMapData2"         },
        {   tcProfileHueSatMapData3,            "ProfileHueSatMapData3"         },
        {   tcProfileHueSatMapEncoding,         "ProfileHueSatMapEncoding"      },
        {   tcProfileToneCurve,                 "ProfileToneCurve"              },
        {   tcProfileToneMethod,                "ProfileToneMethod"             },
        {   tcProfileEmbedPolicy,               "ProfileEmbedPolicy"            },
        {   tcProfileCopyright,                 "ProfileCopyright"              },
        {   tcForwardMatrix1,                   "ForwardMatrix1"                },
        {   tcForwardMatrix2,                   "ForwardMatrix2"                },
        {   tcForwardMatrix3,                   "ForwardMatrix3"                },
        {   tcPreviewApplicationName,           "PreviewApplicationName"        },
        {   tcPreviewApplicationVersion,        "PreviewApplicationVersion"     },
        {   tcPreviewSettingsName,              "PreviewSettingsName"           },
        {   tcPreviewSettingsDigest,            "PreviewSettingsDigest"         },
        {   tcPreviewColorSpace,                "PreviewColorSpace"             },
        {   tcPreviewDateTime,                  "PreviewDateTime"               },
        {   tcRawImageDigest,                   "RawImageDigest"                },
        {   tcOriginalRawFileDigest,            "OriginalRawFileDigest"         },
        {   tcSubTileBlockSize,                 "SubTileBlockSize"              },
        {   tcRowInterleaveFactor,              "RowInterleaveFactor"           },
        {   tcColumnInterleaveFactor,           "ColumnInterleaveFactor"        },
        {   tcProfileLookTableDims,             "ProfileLookTableDims"          },
        {   tcProfileLookTableData,             "ProfileLookTableData"          },
        {   tcProfileLookTableEncoding,         "ProfileLookTableEncoding"      },
        {   tcBaselineExposureOffset,           "BaselineExposureOffset"        },
        {   tcDefaultBlackRender,               "DefaultBlackRender"            },
        {   tcOpcodeList1,                      "OpcodeList1"                   },
        {   tcOpcodeList2,                      "OpcodeList2"                   },
        {   tcOpcodeList3,                      "OpcodeList3"                   },
        {   tcNoiseProfile,                     "NoiseProfile"                  },
        {   tcOriginalDefaultFinalSize,         "OriginalDefaultFinalSize"      },
        {   tcOriginalBestQualityFinalSize,     "OriginalBestQualityFinalSize"  },
        {   tcOriginalDefaultCropSize,          "OriginalDefaultCropSize"       },
        {   tcProfileHueSatMapEncoding,         "ProfileHueSatMapEncoding"      },
        {   tcProfileLookTableEncoding,         "ProfileLookTableEncoding"      },
        {   tcBaselineExposureOffset,           "BaselineExposureOffset"        },
        {   tcDefaultBlackRender,               "DefaultBlackRender"            },
        {   tcNewRawImageDigest,                "NewRawImageDigest"             },
        {   tcRawToPreviewGain,                 "RawToPreviewGain"              },
        {   tcCacheBlob,                        "CacheBlob"                     },
        {   tcCacheVersion,                     "CacheVersion"                  },
        {   tcDefaultUserCrop,                  "DefaultUserCrop"               },
        {   tcDepthFormat,                      "DepthFormat"                   },
        {   tcDepthNear,                        "DepthNear"                     },
        {   tcDepthFar,                         "DepthFar"                      },
        {   tcDepthUnits,                       "DepthUnits"                    },
        {   tcDepthMeasureType,                 "DepthMeasureType"              },
        {   tcEnhanceParams,                    "EnhanceParams"                 },
        {   tcProfileGainTableMap,              "ProfileGainTableMap"           },
        {   tcProfileGainTableMap2,             "ProfileGainTableMap2"          },
        {   tcRGBTablesDraft,                   "RGBTablesDraft"                },
        {   tcRGBTables,                        "RGBTables"                     },
        {   tcBigTableDigests,                  "BigTableDigests"               },
        {   tcBigTableOffsets,                  "BigTableOffsets"               },
        {   tcBigTableByteCounts,               "BigTableByteCounts"            },
        {   tcMaskSubArea,                      "MaskSubArea"                   },
        {   tcImageSequenceInfo,                "ImageSequenceInfo"             },
        {   tcImageStats,                       "ImageStats"                    },
        {   tcProfileDynamicRange,              "ProfileDynamiceRange"          },
        {   tcProfileGroupName,                 "ProfileGroupName"              },
        {   tcJXLDistance,                      "JXLDistance"                   },
        {   tcJXLEffort,                        "JXLEffort"                     },
        {   tcJXLDecodeSpeed,                   "JXLDecodeSpeed"                },
        {   tcBigTableGroupIndex,               "BigTableGroupIndex"            },
        {   tcKodakKDCPrivateIFD,               "KodakKDCPrivateIFD"            }
        };

    const dng_name_table kGPSTagNames [] =
        {
        {   tcGPSVersionID,                 "GPSVersionID"          },
        {   tcGPSLatitudeRef,               "GPSLatitudeRef"        },
        {   tcGPSLatitude,                  "GPSLatitude"           },
        {   tcGPSLongitudeRef,              "GPSLongitudeRef"       },
        {   tcGPSLongitude,                 "GPSLongitude"          },
        {   tcGPSAltitudeRef,               "GPSAltitudeRef"        },
        {   tcGPSAltitude,                  "GPSAltitude"           },
        {   tcGPSTimeStamp,                 "GPSTimeStamp"          },
        {   tcGPSSatellites,                "GPSSatellites"         },
        {   tcGPSStatus,                    "GPSStatus"             },
        {   tcGPSMeasureMode,               "GPSMeasureMode"        },
        {   tcGPSDOP,                       "GPSDOP"                },
        {   tcGPSSpeedRef,                  "GPSSpeedRef"           },
        {   tcGPSSpeed,                     "GPSSpeed"              },
        {   tcGPSTrackRef,                  "GPSTrackRef"           },
        {   tcGPSTrack,                     "GPSTrack"              },
        {   tcGPSImgDirectionRef,           "GPSImgDirectionRef"    },
        {   tcGPSImgDirection,              "GPSImgDirection"       },
        {   tcGPSMapDatum,                  "GPSMapDatum"           },
        {   tcGPSDestLatitudeRef,           "GPSDestLatitudeRef"    },
        {   tcGPSDestLatitude,              "GPSDestLatitude"       },
        {   tcGPSDestLongitudeRef,          "GPSDestLongitudeRef"   },
        {   tcGPSDestLongitude,             "GPSDestLongitude"      },
        {   tcGPSDestBearingRef,            "GPSDestBearingRef"     },
        {   tcGPSDestBearing,               "GPSDestBearing"        },
        {   tcGPSDestDistanceRef,           "GPSDestDistanceRef"    },
        {   tcGPSDestDistance,              "GPSDestDistance"       },
        {   tcGPSProcessingMethod,          "GPSProcessingMethod"   },
        {   tcGPSAreaInformation,           "GPSAreaInformation"    },
        {   tcGPSDateStamp,                 "GPSDateStamp"          },
        {   tcGPSDifferential,              "GPSDifferential"       },
        {   tcGPSHPositioningError,         "GPSHPositioningError"  },
        };

    const dng_name_table kInteroperabilityTagNames [] =
        {
        {   tcInteroperabilityIndex,        "InteroperabilityIndex"     },
        {   tcInteroperabilityVersion,      "InteroperabilityVersion"   },
        {   tcRelatedImageFileFormat,       "RelatedImageFileFormat"    },
        {   tcRelatedImageWidth,            "RelatedImageWidth"         },
        {   tcRelatedImageLength,           "RelatedImageLength"        }
        };

    const dng_name_table kFujiTagNames [] =
        {
        {   tcFujiHeader,                   "FujiHeader"    },
        {   tcFujiRawInfo1,                 "FujiRawInfo1"  },
        {   tcFujiRawInfo2,                 "FujiRawInfo2"  }
        };

    const dng_name_table kContaxTagNames [] =
        {
        {   tcContaxHeader,                 "ContaxHeader"  }
        };

    const char *name = NULL;

    if (parentCode == 0                                                  ||
        parentCode == tcExifIFD                                          ||
        parentCode == tcLeafMOS                                          ||
        (parentCode >= tcFirstSubIFD     && parentCode <= tcLastSubIFD)  ||
        (parentCode >= tcFirstChainedIFD && parentCode <= tcLastChainedIFD))
        {

        name = LookupName (tagCode,
                           kTagNames,
                           sizeof (kTagNames    ) /
                           sizeof (kTagNames [0]));

        }

    else if (parentCode == tcGPSInfo)
        {

        name = LookupName (tagCode,
                           kGPSTagNames,
                           sizeof (kGPSTagNames    ) /
                           sizeof (kGPSTagNames [0]));

        }

    else if (parentCode == tcInteroperabilityIFD)
        {

        name = LookupName (tagCode,
                           kInteroperabilityTagNames,
                           sizeof (kInteroperabilityTagNames    ) /
                           sizeof (kInteroperabilityTagNames [0]));

        }

    else if (parentCode == tcFujiRAF)
        {

        name = LookupName (tagCode,
                           kFujiTagNames,
                           sizeof (kFujiTagNames    ) /
                           sizeof (kFujiTagNames [0]));

        }

    else if (parentCode == tcContaxRAW)
        {

        name = LookupName (tagCode,
                           kContaxTagNames,
                           sizeof (kContaxTagNames    ) /
                           sizeof (kContaxTagNames [0]));

        }

    if (name)
        {
        return name;
        }

    static char s [32];

    if (parentCode == tcCanonCRW)
        {
        snprintf (s, 32, "CRW_%04X", (unsigned) tagCode);
        }

    else if (parentCode == tcMinoltaMRW)
        {

        char c1 = (char) ((tagCode >> 24) & 0xFF);
        char c2 = (char) ((tagCode >> 16) & 0xFF);
        char c3 = (char) ((tagCode >>  8) & 0xFF);
        char c4 = (char) ((tagCode      ) & 0xFF);

        if (c1 < ' ') c1 = '_';
        if (c2 < ' ') c2 = '_';
        if (c3 < ' ') c3 = '_';
        if (c4 < ' ') c4 = '_';

        snprintf (s, 32, "MRW%c%c%c%c", c1, c2, c3, c4);

        }

    else if (parentCode == tcFujiRawInfo1)
        {
        snprintf (s, 32, "RAF1_%04X", (unsigned) tagCode);
        }

    else if (parentCode == tcFujiRawInfo2)
        {
        snprintf (s, 32, "RAF2_%04X", (unsigned) tagCode);
        }

    else
        {
        snprintf (s, 32, "Tag%u", (unsigned) tagCode);
        }

    return s;

    }

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

const char * LookupTagType (uint32 tagType)
    {

    const dng_name_table kTagTypeNames [] =
        {
        {   ttByte,         "Byte"      },
        {   ttAscii,        "ASCII"     },
        {   ttShort,        "Short"     },
        {   ttLong,         "Long"      },
        {   ttRational,     "Rational"  },
        {   ttSByte,        "SByte"     },
        {   ttUndefined,    "Undefined" },
        {   ttSShort,       "SShort"    },
        {   ttSLong,        "SLong"     },
        {   ttSRational,    "SRational" },
        {   ttFloat,        "Float"     },
        {   ttDouble,       "Double"    },
        {   ttIFD,          "IFD"       },
        {   ttUnicode,      "Unicode"   },
        {   ttComplex,      "Complex"   },
        {   ttLong8,        "Long8"     },
        {   ttSLong8,       "SLong8"    },
        {   ttIFD8,         "IFD8"      }
        };

    const char *name = LookupName (tagType,
                                   kTagTypeNames,
                                   sizeof (kTagTypeNames    ) /
                                   sizeof (kTagTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "Type%u", (unsigned) tagType);

    return s;

    }

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

const char * LookupNewSubFileType (uint32 key)
    {

    const dng_name_table kNewSubFileTypeNames [] =
        {
        {   sfMainImage         , "Main Image"          },
        {   sfPreviewImage      , "Preview Image"       },
        {   sfTransparencyMask  , "Transparency Mask"   },
        {   sfPreviewMask       , "Preview Mask"        },
        {   sfDepthMap          , "Depth Map"           },
        {   sfPreviewDepthMap   , "Preview Depth Map"   },
        {   sfEnhancedImage     , "Enhanced Image"      },
        {   sfAltPreviewImage   , "Alt Preview Image"   },
        {   sfSemanticMask      , "Semantic Mask"       },
        {   sfGainMap           , "Gain Map"            }
        };

    const char *name = LookupName (key,
                                   kNewSubFileTypeNames,
                                   sizeof (kNewSubFileTypeNames    ) /
                                   sizeof (kNewSubFileTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupCompression (uint32 key)
    {

    const dng_name_table kCompressionNames [] =
        {
        {   ccUncompressed,     "Uncompressed"  },
        {   ccLZW,              "LZW"           },
        {   ccOldJPEG,          "Old JPEG"      },
        {   ccJPEG,             "JPEG"          },
        {   ccDeflate,          "Deflate"       },
        {   ccPackBits,         "PackBits"      },
        {   ccOldDeflate,       "OldDeflate"    },
        {   ccLossyJPEG,        "Lossy JPEG"    },
        {   ccJXL,              "JXL"           }
        };

    const char *name = LookupName (key,
                                   kCompressionNames,
                                   sizeof (kCompressionNames    ) /
                                   sizeof (kCompressionNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupPredictor (uint32 key)
    {

    const dng_name_table kPredictorNames [] =
        {
        {   cpNullPredictor,            "NullPredictor"             },
        {   cpHorizontalDifference,     "HorizontalDifference"      },
        {   cpFloatingPoint,            "FloatingPoint"             },
        {   cpHorizontalDifferenceX2,   "HorizontalDifferenceX2"    },
        {   cpHorizontalDifferenceX4,   "HorizontalDifferenceX4"    },
        {   cpFloatingPointX2,          "FloatingPointX2"           },
        {   cpFloatingPointX4,          "FloatingPointX4"           }
        };

    const char *name = LookupName (key,
                                   kPredictorNames,
                                   sizeof (kPredictorNames    ) /
                                   sizeof (kPredictorNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSampleFormat (uint32 key)
    {

    const dng_name_table kSampleFormatNames [] =
        {
        {   sfUnsignedInteger,  "UnsignedInteger"   },
        {   sfSignedInteger,    "SignedInteger"     },
        {   sfFloatingPoint,    "FloatingPoint"     },
        {   sfUndefined,        "Undefined"         }
        };

    const char *name = LookupName (key,
                                   kSampleFormatNames,
                                   sizeof (kSampleFormatNames    ) /
                                   sizeof (kSampleFormatNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupPhotometricInterpretation (uint32 key)
    {

    const dng_name_table kPhotometricInterpretationNames [] =
        {
        {   piWhiteIsZero,          "WhiteIsZero"       },
        {   piBlackIsZero,          "BlackIsZero"       },
        {   piRGB,                  "RGB"               },
        {   piRGBPalette,           "RGBPalette"        },
        {   piTransparencyMask,     "TransparencyMask"  },
        {   piDepth,                "Depth"             },
        {   piPhotometricMask,      "PhotometricMask"   },
        {   piGainMap,              "GainMap"           },
        {   piCMYK,                 "CMYK"              },
        {   piYCbCr,                "YCbCr"             },
        {   piCIELab,               "CIELab"            },
        {   piICCLab,               "ICCLab"            },
        {   piCFA,                  "CFA"               },
        {   piLinearRaw,            "LinearRaw"         }
        };

    const char *name = LookupName (key,
                                   kPhotometricInterpretationNames,
                                   sizeof (kPhotometricInterpretationNames    ) /
                                   sizeof (kPhotometricInterpretationNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupOrientation (uint32 key)
    {

    const dng_name_table kOrientationNames [] =
        {
        {   1,  "1 - 0th row is top, 0th column is left"        },
        {   2,  "2 - 0th row is top, 0th column is right"       },
        {   3,  "3 - 0th row is bottom, 0th column is right"    },
        {   4,  "4 - 0th row is bottom, 0th column is left"     },
        {   5,  "5 - 0th row is left, 0th column is top"        },
        {   6,  "6 - 0th row is right, 0th column is top"       },
        {   7,  "7 - 0th row is right, 0th column is bottom"    },
        {   8,  "8 - 0th row is left, 0th column is bottom"     },
        {   9,  "9 - unknown"                                   }
        };

    const char *name = LookupName (key,
                                   kOrientationNames,
                                   sizeof (kOrientationNames    ) /
                                   sizeof (kOrientationNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupResolutionUnit (uint32 key)
    {

    const dng_name_table kResolutionUnitNames [] =
        {
        {   ruNone,     "None"          },
        {   ruInch,     "Inch"          },
        {   ruCM,       "cm"            },
        {   ruMM,       "mm"            },
        {   ruMicroM,   "Micrometer"    }
        };

    const char *name = LookupName (key,
                                   kResolutionUnitNames,
                                   sizeof (kResolutionUnitNames    ) /
                                   sizeof (kResolutionUnitNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupCFAColor (uint32 key)
    {

    const dng_name_table kCFAColorNames [] =
        {
        {   0, "Red"        },
        {   1, "Green"      },
        {   2, "Blue"       },
        {   3, "Cyan"       },
        {   4, "Magenta"    },
        {   5, "Yellow"     },
        {   6, "White"      }
        };

    const char *name = LookupName (key,
                                   kCFAColorNames,
                                   sizeof (kCFAColorNames    ) /
                                   sizeof (kCFAColorNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "Color%u", (unsigned) key);

    return s;

    }

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

const char * LookupSensingMethod (uint32 key)
    {

    const dng_name_table kSensingMethodNames [] =
        {
        {   0, "Undefined"              },
        {   1, "MonochromeArea"         },
        {   2, "OneChipColorArea"       },
        {   3, "TwoChipColorArea"       },
        {   4, "ThreeChipColorArea"     },
        {   5, "ColorSequentialArea"    },
        {   6, "MonochromeLinear"       },
        {   7, "TriLinear"              },
        {   8, "ColorSequentialLinear"  }
        };

    const char *name = LookupName (key,
                                   kSensingMethodNames,
                                   sizeof (kSensingMethodNames    ) /
                                   sizeof (kSensingMethodNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupExposureProgram (uint32 key)
    {

    const dng_name_table kExposureProgramNames [] =
        {
        {   epUnidentified,     "Unidentified"      },
        {   epManual,           "Manual"            },
        {   epProgramNormal,    "Program Normal"    },
        {   epAperturePriority, "Aperture Priority" },
        {   epShutterPriority,  "Shutter Priority"  },
        {   epProgramCreative,  "Program Creative"  },
        {   epProgramAction,    "Program Action"    },
        {   epPortraitMode,     "Portrait Mode"     },
        {   epLandscapeMode,    "Landscape Mode"    }
        };

    const char *name = LookupName (key,
                                   kExposureProgramNames,
                                   sizeof (kExposureProgramNames    ) /
                                   sizeof (kExposureProgramNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupMeteringMode (uint32 key)
    {

    const dng_name_table kMeteringModeNames [] =
        {
        {   mmUnidentified,             "Unknown"               },
        {   mmAverage,                  "Average"               },
        {   mmCenterWeightedAverage,    "CenterWeightedAverage" },
        {   mmSpot,                     "Spot"                  },
        {   mmMultiSpot,                "MultiSpot"             },
        {   mmPattern,                  "Pattern"               },
        {   mmPartial,                  "Partial"               },
        {   mmOther,                    "Other"                 }
        };

    const char *name = LookupName (key,
                                   kMeteringModeNames,
                                   sizeof (kMeteringModeNames    ) /
                                   sizeof (kMeteringModeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupLightSource (uint32 key)
    {

    const dng_name_table kLightSourceNames [] =
        {
        {   lsUnknown,              "Unknown"                                   },
        {   lsDaylight,             "Daylight"                                  },
        {   lsFluorescent,          "Fluorescent"                               },
        {   lsTungsten,             "Tungsten (incandescent light)"             },
        {   lsFlash,                "Flash"                                     },
        {   lsFineWeather,          "Fine weather"                              },
        {   lsCloudyWeather,        "Cloudy weather"                            },
        {   lsShade,                "Shade"                                     },
        {   lsDaylightFluorescent,  "Daylight fluorescent (D 5700 - 7100K)"     },
        {   lsDayWhiteFluorescent,  "Day white fluorescent (N 4600 - 5500K)"    },
        {   lsCoolWhiteFluorescent, "Cool white fluorescent (W 3800 - 4500K)"   },
        {   lsWhiteFluorescent,     "White fluorescent (WW 3250 - 3800K)"       },
        {   lsWarmWhiteFluorescent, "Warm white fluorescent (L 2600 - 3250K)"   },
        {   lsStandardLightA,       "Standard light A"                          },
        {   lsStandardLightB,       "Standard light B"                          },
        {   lsStandardLightC,       "Standard light C"                          },
        {   lsD55,                  "D55"                                       },
        {   lsD65,                  "D65"                                       },
        {   lsD75,                  "D75"                                       },
        {   lsD50,                  "D50"                                       },
        {   lsISOStudioTungsten,    "ISO studio tungsten"                       },
        {   lsOther,                "Other"                                     }
        };

    const char *name = LookupName (key,
                                   kLightSourceNames,
                                   sizeof (kLightSourceNames    ) /
                                   sizeof (kLightSourceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    if (key & 0x08000)
        {

        snprintf (s, 32, "%uK", (unsigned) (key & 0x7FFF));

        }

    else
        {

        snprintf (s, 32, "%u", (unsigned) key);

        }

    return s;

    }

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

const char * LookupColorSpace (uint32 key)
    {

    const dng_name_table kColorSpaceNames [] =
        {
        {   1,      "sRGB"          },
        {   0xFFFF, "Uncalibrated"  }
        };

    const char *name = LookupName (key,
                                   kColorSpaceNames,
                                   sizeof (kColorSpaceNames    ) /
                                   sizeof (kColorSpaceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupFileSource (uint32 key)
    {

    const dng_name_table kFileSourceNames [] =
        {
        {   3,      "DSC"   }
        };

    const char *name = LookupName (key,
                                   kFileSourceNames,
                                   sizeof (kFileSourceNames    ) /
                                   sizeof (kFileSourceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSceneType (uint32 key)
    {

    const dng_name_table kSceneTypeNames [] =
        {
        {   1,  "A directly photographed image"     }
        };

    const char *name = LookupName (key,
                                   kSceneTypeNames,
                                   sizeof (kSceneTypeNames    ) /
                                   sizeof (kSceneTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupCustomRendered (uint32 key)
    {

    const dng_name_table kCustomRenderedNames [] =
        {
        {   0,      "Normal process"    },
        {   1,      "Custom process"    }
        };

    const char *name = LookupName (key,
                                   kCustomRenderedNames,
                                   sizeof (kCustomRenderedNames    ) /
                                   sizeof (kCustomRenderedNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupExposureMode (uint32 key)
    {

    const dng_name_table kExposureModeNames [] =
        {
        {   0,  "Auto exposure"     },
        {   1,  "Manual exposure"   },
        {   2,  "Auto bracket"      }
        };

    const char *name = LookupName (key,
                                   kExposureModeNames,
                                   sizeof (kExposureModeNames    ) /
                                   sizeof (kExposureModeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupWhiteBalance (uint32 key)
    {

    const dng_name_table kWhiteBalanceNames [] =
        {
        {   0,  "Auto white balance"    },
        {   1,  "Manual white balance"  }
        };

    const char *name = LookupName (key,
                                   kWhiteBalanceNames,
                                   sizeof (kWhiteBalanceNames    ) /
                                   sizeof (kWhiteBalanceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSceneCaptureType (uint32 key)
    {

    const dng_name_table kSceneCaptureTypeNames [] =
        {
        {   0,  "Standard"      },
        {   1,  "Landscape"     },
        {   2,  "Portrait"      },
        {   3,  "Night scene"   }
        };

    const char *name = LookupName (key,
                                   kSceneCaptureTypeNames,
                                   sizeof (kSceneCaptureTypeNames    ) /
                                   sizeof (kSceneCaptureTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupGainControl (uint32 key)
    {

    const dng_name_table kGainControlNames [] =
        {
        {   0,  "None"              },
        {   1,  "Low gain up"       },
        {   2,  "High gain up"      },
        {   3,  "Low gain down"     },
        {   4,  "High gain down"    }
        };

    const char *name = LookupName (key,
                                   kGainControlNames,
                                   sizeof (kGainControlNames    ) /
                                   sizeof (kGainControlNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupContrast (uint32 key)
    {

    const dng_name_table kContrastNames [] =
        {
        {   0,  "Normal"    },
        {   1,  "Soft"      },
        {   2,  "Hard"      }
        };

    const char *name = LookupName (key,
                                   kContrastNames,
                                   sizeof (kContrastNames    ) /
                                   sizeof (kContrastNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSaturation (uint32 key)
    {

    const dng_name_table kSaturationNames [] =
        {
        {   0,  "Normal"            },
        {   1,  "Low saturation"    },
        {   2,  "High saturation"   }
        };

    const char *name = LookupName (key,
                                   kSaturationNames,
                                   sizeof (kSaturationNames    ) /
                                   sizeof (kSaturationNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSharpness (uint32 key)
    {

    const dng_name_table kSharpnessNames [] =
        {
        {   0,  "Normal"    },
        {   1,  "Soft"      },
        {   2,  "Hard"      }
        };

    const char *name = LookupName (key,
                                   kSharpnessNames,
                                   sizeof (kSharpnessNames    ) /
                                   sizeof (kSharpnessNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupSubjectDistanceRange (uint32 key)
    {

    const dng_name_table kSubjectDistanceRangeNames [] =
        {
        {   0,  "Unknown"       },
        {   1,  "Macro"         },
        {   2,  "Close view"    },
        {   3,  "Distant view"  }
        };

    const char *name = LookupName (key,
                                   kSubjectDistanceRangeNames,
                                   sizeof (kSubjectDistanceRangeNames    ) /
                                   sizeof (kSubjectDistanceRangeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupComponent (uint32 key)
    {

    const dng_name_table kComponentNames [] =
        {
        {   0, "-"  },
        {   1, "Y"  },
        {   2, "Cb" },
        {   3, "Cr" },
        {   4, "R"  },
        {   5, "G"  },
        {   6, "B"  }
        };

    const char *name = LookupName (key,
                                   kComponentNames,
                                   sizeof (kComponentNames    ) /
                                   sizeof (kComponentNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupCFALayout (uint32 key)
    {

    const dng_name_table kCFALayoutNames [] =
        {
        {   1,  "Rectangular (or square) layout"                                                                        },
        {   2,  "Staggered layout A: even columns are offset down by 1/2 row"                                           },
        {   3,  "Staggered layout B: even columns are offset up by 1/2 row"                                             },
        {   4,  "Staggered layout C: even rows are offset right by 1/2 column"                                          },
        {   5,  "Staggered layout D: even rows are offset left by 1/2 column"                                           },
        {   6,  "Staggered layout E: even rows are offset up by 1/2 row, even columns are offset left by 1/2 column"    },
        {   7,  "Staggered layout F: even rows are offset up by 1/2 row, even columns are offset right by 1/2 column"   },
        {   8,  "Staggered layout G: even rows are offset down by 1/2 row, even columns are offset left by 1/2 column"  },
        {   9,  "Staggered layout H: even rows are offset down by 1/2 row, even columns are offset right by 1/2 column" }
        };

    const char *name = LookupName (key,
                                   kCFALayoutNames,
                                   sizeof (kCFALayoutNames    ) /
                                   sizeof (kCFALayoutNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupMakerNoteSafety (uint32 key)
    {

    const dng_name_table kMakerNoteSafetyNames [] =
        {
        {   0,  "Unsafe"    },
        {   1,  "Safe"      }
        };

    const char *name = LookupName (key,
                                   kMakerNoteSafetyNames,
                                   sizeof (kMakerNoteSafetyNames    ) /
                                   sizeof (kMakerNoteSafetyNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupColorimetricReference (uint32 key)
    {

    const dng_name_table kColorimetricReferenceNames [] =
        {
        {   crSceneReferred,     "Scene Referred"      },
        {   crICCProfilePCS,     "ICC Profile PCS"     },
        {   crOutputReferredHDR, "Output Referred HDR" }
        };

    const char *name = LookupName (key,
                                   kColorimetricReferenceNames,
                                   sizeof (kColorimetricReferenceNames    ) /
                                   sizeof (kColorimetricReferenceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupPreviewColorSpace (uint32 key)
    {

    const dng_name_table kPreviewColorSpaceNames [] =
        {
        {   previewColorSpace_Unknown    ,  "Unknown"           },
        {   previewColorSpace_GrayGamma22,  "Gray Gamma 2.2"    },
        {   previewColorSpace_sRGB       ,  "sRGB"              },
        {   previewColorSpace_AdobeRGB   ,  "Adobe RGB (1998)"  },
        {   previewColorSpace_ProPhotoRGB,  "Pro Photo RGB"     }
        };

    const char *name = LookupName (key,
                                   kPreviewColorSpaceNames,
                                   sizeof (kPreviewColorSpaceNames    ) /
                                   sizeof (kPreviewColorSpaceNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupJPEGMarker (uint32 key)
    {

    const dng_name_table kJPEGMarkerNames [] =
        {
        {   M_TEM,      "TEM"   },
        {   M_SOF0,     "SOF0"  },
        {   M_SOF1,     "SOF1"  },
        {   M_SOF2,     "SOF2"  },
        {   M_SOF3,     "SOF3"  },
        {   M_DHT,      "DHT"   },
        {   M_SOF5,     "SOF5"  },
        {   M_SOF6,     "SOF6"  },
        {   M_SOF7,     "SOF7"  },
        {   M_JPG,      "JPG"   },
        {   M_SOF9,     "SOF9"  },
        {   M_SOF10,    "SOF10" },
        {   M_SOF11,    "SOF11" },
        {   M_DAC,      "DAC"   },
        {   M_SOF13,    "SOF13" },
        {   M_SOF14,    "SOF14" },
        {   M_SOF15,    "SOF15" },
        {   M_RST0,     "RST0"  },
        {   M_RST1,     "RST1"  },
        {   M_RST2,     "RST2"  },
        {   M_RST3,     "RST3"  },
        {   M_RST4,     "RST4"  },
        {   M_RST5,     "RST5"  },
        {   M_RST6,     "RST6"  },
        {   M_RST7,     "RST7"  },
        {   M_SOI,      "SOI"   },
        {   M_EOI,      "EOI"   },
        {   M_SOS,      "SOS"   },
        {   M_DQT,      "DQT"   },
        {   M_DNL,      "DNL"   },
        {   M_DRI,      "DRI"   },
        {   M_DHP,      "DHP"   },
        {   M_EXP,      "EXP"   },
        {   M_APP0,     "APP0"  },
        {   M_APP1,     "APP1"  },
        {   M_APP2,     "APP2"  },
        {   M_APP3,     "APP3"  },
        {   M_APP4,     "APP4"  },
        {   M_APP5,     "APP5"  },
        {   M_APP6,     "APP6"  },
        {   M_APP7,     "APP7"  },
        {   M_APP8,     "APP8"  },
        {   M_APP9,     "APP9"  },
        {   M_APP10,    "APP10" },
        {   M_APP11,    "APP11" },
        {   M_APP12,    "APP12" },
        {   M_APP13,    "APP13" },
        {   M_APP14,    "APP14" },
        {   M_APP15,    "APP15" },
        {   M_JPG0,     "JPG0"  },
        {   M_JPG1,     "JPG1"  },
        {   M_JPG2,     "JPG2"  },
        {   M_JPG3,     "JPG3"  },
        {   M_JPG4,     "JPG4"  },
        {   M_JPG5,     "JPG5"  },
        {   M_JPG6,     "JPG6"  },
        {   M_JPG7,     "JPG7"  },
        {   M_JPG8,     "JPG8"  },
        {   M_JPG9,     "JPG9"  },
        {   M_JPG10,    "JPG10" },
        {   M_JPG11,    "JPG11" },
        {   M_JPG12,    "JPG12" },
        {   M_JPG13,    "JPG13" },
        {   M_COM,      "COM"   },
        {   M_ERROR,    "ERROR" }
        };

    const char *name = LookupName (key,
                                   kJPEGMarkerNames,
                                   sizeof (kJPEGMarkerNames    ) /
                                   sizeof (kJPEGMarkerNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "0x%02X", (unsigned) key);

    return s;

    }

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

const char * LookupSensitivityType (uint32 key)
    {

    const dng_name_table kSensitivityTypeNames [] =
        {
        {   stUnknown,                   "Unknown"                                                                              },
        {   stStandardOutputSensitivity, "Standard Output Sensitivity (SOS)"                                                    },
        {   stRecommendedExposureIndex,  "Recommended Exposure Index (REI)"                                                     },
        {   stISOSpeed,                  "ISO Speed"                                                                            },
        {   stSOSandREI,                 "Standard Output Sensitivity (SOS) and Recommended Exposure Index (REI)"               },
        {   stSOSandISOSpeed,            "Standard Output Sensitivity (SOS) and ISO Speed"                                      },
        {   stREIandISOSpeed,            "Recommended Exposure Index (REI) and ISO Speed"                                       },
        {   stSOSandREIandISOSpeed,      "Standard Output Sensitivity (SOS) and Recommended Exposure Index (REI) and ISO Speed" },
        };

    const char *name = LookupName (key,
                                   kSensitivityTypeNames,
                                   sizeof (kSensitivityTypeNames    ) /
                                   sizeof (kSensitivityTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupDepthFormat (uint32 key)
    {

    const dng_name_table kDepthFormatNames [] =
        {
        {    depthFormatUnknown,    "Unknown"   },
        {    depthFormatLinear,     "Linear"    },
        {    depthFormatInverse,    "Inverse"   },
        };

    const char *name = LookupName (key,
                                   kDepthFormatNames,
                                   sizeof (kDepthFormatNames    ) /
                                   sizeof (kDepthFormatNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupDepthUnits (uint32 key)
    {

    const dng_name_table kDepthUnitNames [] =
        {
        {    depthUnitsUnknown,    "Unknown"   },
        {    depthUnitsMeters,     "Meters"    },
        };

    const char *name = LookupName (key,
                                   kDepthUnitNames,
                                   sizeof (kDepthUnitNames    ) /
                                   sizeof (kDepthUnitNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

const char * LookupDepthMeasureType (uint32 key)
    {

    const dng_name_table kDepthMeasureTypeNames [] =
        {
        {    depthMeasureUnknown,       "Unknown"       },
        {    depthMeasureOpticalAxis,   "Optical Axis"  },
        {    depthMeasureOpticalRay,    "Optical Ray"   },
        };

    const char *name = LookupName (key,
                                   kDepthMeasureTypeNames,
                                   sizeof (kDepthMeasureTypeNames    ) /
                                   sizeof (kDepthMeasureTypeNames [0]));

    if (name)
        {
        return name;
        }

    static char s [32];

    snprintf (s, 32, "%u", (unsigned) key);

    return s;

    }

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

void DumpHexAscii (dng_stream &stream,
                   uint32 count)
    {

    uint32 rows = (count + 15) >> 4;

    if (rows > gDumpLineLimit)
        rows = gDumpLineLimit;

    for (uint32 row = 0; row < rows; row++)
        {

        printf ("    ");

        uint32 col;

        uint32 cols = count - (row << 4);

        if (cols > 16)
            cols = 16;

        uint8 x [16];

        for (col = 0; col < 16; col++)
            {

            x [col] = ' ';

            if (col < cols)
                {

                x [col] = stream.Get_uint8 ();

                printf ("%02x ", x [col]);

                }

            else
                {
                printf ("   ");
                }

            }

        printf ("   ");

        for (col = 0; col < 16; col++)
            {

            if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
                {
                printf ("%c", x [col]);
                }

            else
                {
                printf (".");
                }

            }

        printf ("\n");

        }

    if (count > rows * 16)
        {
        printf ("    ... %u more bytes\n", (unsigned) (count - rows * 16));
        }

    }

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

void DumpHexAscii (const uint8 *buf,
                   uint32 count)
    {

    uint32 rows = (count + 15) >> 4;

    if (rows > gDumpLineLimit)
        rows = gDumpLineLimit;

    for (uint32 row = 0; row < rows; row++)
        {

        printf ("    ");

        uint32 col;

        uint32 cols = count - (row << 4);

        if (cols > 16)
            cols = 16;

        uint8 x [16];

        for (col = 0; col < 16; col++)
            {

            x [col] = ' ';

            if (col < cols)
                {

                x [col] = *(buf++);

                printf ("%02x ", x [col]);

                }

            else
                {
                printf ("   ");
                }

            }

        printf ("   ");

        for (col = 0; col < 16; col++)
            {

            if (x [col] >= (uint8) ' ' && x [col] <= (uint8) '~')
                {
                printf ("%c", x [col]);
                }

            else
                {
                printf (".");
                }

            }

        printf ("\n");

        }

    if (count > rows * 16)
        {
        printf ("    ... %u more bytes\n", (unsigned) (count - rows * 16));
        }

    }

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

void DumpXMP (dng_stream &stream,
              uint32 count)
    {

    uint32 lineLength = 0;

    while (count > 0)
        {

        uint32 x = stream.Get_uint8 ();

        if (x == 0) break;

        count--;

        if (lineLength == 0)
            {

            printf ("XMP: ");

            lineLength = 5;

            }

        if (x == '\n' ||
            x == '\r')
            {

            printf ("\n");

            lineLength = 0;

            }

        else
            {

            if (lineLength >= 128)
                {

                printf ("\nXMP: ");

                lineLength = 5;

                }

            if (x >= ' ' && x <= '~')
                {

                printf ("%c", (char) x);

                lineLength += 1;

                }

            else
                {

                printf ("\\%03o", (unsigned) x);

                lineLength += 4;

                }

            }

        }

    if (lineLength != 0)
        {

        printf ("\n");

        }

    }

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

void DumpString (const dng_string &s)
    {

    const uint32 kMaxDumpString = gDumpLineLimit * 64;

    printf ("\"");

    const char *ss = s.Get ();

    uint32 total = 0;

    while (*ss != 0 && total++ < kMaxDumpString)
        {

        uint32 c = dng_string::DecodeUTF8 (ss);

        if (c >= ' ' && c <= '~')
            {
            printf ("%c", (char) c);
            }

        else switch (c)
            {

            case '\t':
                {
                printf ("\\t");
                break;
                }

            case '\n':
                {
                printf ("\\n");
                break;
                }

            case '\r':
                {
                printf ("\\r");
                break;
                }

            default:
                {
                printf ("[%X]", (unsigned) c);
                }

            }

        }

    uint32 extra = (uint32) strlen (ss);

    if (extra > 0)
        {
        printf ("...\" (%u more bytes)", (unsigned) extra);
        }

    else
        {
        printf ("\"");
        }

    }

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

void DumpTagValues (dng_stream &stream,
                    const char *entry_name,
                    uint32 parentCode,
                    uint32 tagCode,
                    uint32 tagType,
                    uint32 tagCount,
                    const char *tag_name)
    {

    const uint32 kMaxDumpSingleLine = 4;

    const uint32 kMaxDumpArray = Max_uint32 (gDumpLineLimit, kMaxDumpSingleLine);

    printf ("%s:", tag_name ? tag_name
                            : LookupTagCode (parentCode, tagCode));

    switch (tagType)
        {

        case ttShort:
        case ttLong:
        case ttIFD:
        case ttSByte:
        case ttSShort:
        case ttSLong:
        case ttRational:
        case ttSRational:
        case ttFloat:
        case ttDouble:
        case ttLong8:
        case ttSLong8:
        case ttIFD8:
            {

            if (tagCount > kMaxDumpSingleLine)
                {

                printf (" %u entries", (unsigned) tagCount);

                }

            for (uint32 j = 0; j < tagCount && j < kMaxDumpArray; j++)
                {

                if (tagCount <= kMaxDumpSingleLine)
                    {

                    if (j == 0)
                        {

                        printf (" %s =", entry_name);

                        }

                    printf (" ");

                    }

                else
                    {

                    printf ("\n    %s [%u] = ", entry_name, (unsigned) j);

                    }

                switch (tagType)
                    {

                    case ttByte:
                    case ttShort:
                    case ttLong:
                    case ttIFD:
                        {

                        uint32 x = stream.TagValue_uint32 (tagType);

                        printf ("%u", (unsigned) x);

                        break;

                        }

                    case ttLong8:
                    case ttIFD8:
                        {

                        uint64 x = stream.TagValue_uint64 (tagType);

                        printf ("%llu", (unsigned long long) x);

                        break;

                        }

                    case ttSByte:
                    case ttSShort:
                    case ttSLong:
                        {

                        int32 x = stream.TagValue_int32 (tagType);

                        printf ("%d", (int) x);

                        break;

                        }

                    case ttSLong8:
                        {

                        int64 x = stream.TagValue_int64 (tagType);

                        printf ("%lld", (long long) x);

                        break;

                        }

                    case ttRational:
                        {

                        dng_urational x = stream.TagValue_urational (tagType);

                        printf ("%u/%u", (unsigned) x.n, (unsigned) x.d);

                        break;

                        }

                    case ttSRational:
                        {

                        dng_srational x = stream.TagValue_srational (tagType);

                        printf ("%d/%d", (int) x.n, (int) x.d);

                        break;

                        }

                    default:
                        {

                        real64 x = stream.TagValue_real64 (tagType);

                        printf ("%f", x);

                        }

                    }

                }

            printf ("\n");

            if (tagCount > kMaxDumpArray)
                {

                printf ("    ... %u more entries\n", (unsigned) (tagCount - kMaxDumpArray));

                }

            break;

            }

        case ttAscii:
            {

            dng_string s;

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

            printf (" ");

            DumpString (s);

            printf ("\n");

            break;

            }

        default:
            {

            uint32 tagSize = tagCount * TagTypeSize (tagType);

            if (tagCount == 1 && (tagType == ttByte ||
                                  tagType == ttUndefined))
                {

                uint8 x = stream.Get_uint8 ();

                printf (" %s = %u\n", LookupTagType (tagType), x);

                }

            else
                {

                printf (" %s, size = %u\n", LookupTagType (tagType), (unsigned) tagSize);

                DumpHexAscii (stream, tagSize);

                }

            }

        }

    }

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

void DumpMatrix (const dng_matrix &m)
    {

    for (uint32 row = 0; row < m.Rows (); row++)
        {

        for (uint32 col = 0; col < m.Cols (); col++)
            {

            if (col == 0)
                printf ("    ");
            else
                printf (" ");

            printf ("%8.4f", m [row] [col]);

            }

        printf ("\n");

        }

    }

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

void DumpVector (const dng_vector &v)
    {

    for (uint32 index = 0; index < v.Count (); index++)
        {

        printf (" %0.4f", v [index]);

        }

    printf ("\n");

    }

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

void DumpDateTime (const dng_date_time &dt)
    {

    printf ("%04d:%02d:%02d %02d:%02d:%02d",
            (int) dt.fYear,
            (int) dt.fMonth,
            (int) dt.fDay,
            (int) dt.fHour,
            (int) dt.fMinute,
            (int) dt.fSecond);

    }

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

void DumpExposureTime (real64 x)
    {

    if (x > 0.0)
        {

        if (x >= 0.25)
            {
            printf ("%0.2f sec", x);
            }

        else if (x >= 0.01)
            {
            printf ("1/%0.1f sec", 1.0 / x);
            }

        else
            {
            printf ("1/%0.0f sec", 1.0 / x);
            }

        }

    else
        {

        printf ("<invalid>");

        }

    }

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

void DumpFingerprint (const dng_fingerprint &p)
    {

    printf ("<");

    for (uint32 j = 0; j < 16; j++)
        {
        printf ("%02x", p.data [j]);
        }

    printf (">");

    }

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

void DumpHueSatMap (dng_stream &stream,
                    uint32 hues,
                    uint32 sats,
                    uint32 vals,
                    bool skipSat0)
    {

    uint32 doneLines = 0;
    uint32 skipLines = 0;

    for (uint32 v = 0; v < vals; v++)
        {

        for (uint32 h = 0; h < hues; h++)
            {

            for (uint32 s = skipSat0 ? 1 : 0; s < sats; s++)
                {

                real32 dh = stream.Get_real32 ();
                real32 ds = stream.Get_real32 ();
                real32 dv = stream.Get_real32 ();

                if (gDumpLineLimit == 0 ||
                    gDumpLineLimit > doneLines)
                    {

                    doneLines++;

                    if (vals == 1)
                        {

                        printf ("    h [%2u] s [%2u]:  h=%8.4f s=%6.4f v=%6.4f\n",
                                (unsigned) h,
                                (unsigned) s,
                                (double) dh,
                                (double) ds,
                                (double) dv);

                        }

                    else
                        {

                        printf ("    v [%2u] h [%2u] s [%2u]:  h=%8.4f s=%6.4f v=%6.4f\n",
                                (unsigned) v,
                                (unsigned) h,
                                (unsigned) s,
                                (double) dh,
                                (double) ds,
                                (double) dv);

                        }

                    }

                else
                    {

                    skipLines++;

                    }

                }

            }

        }

    if (skipLines > 0)
        {

        printf ("    ... %u more entries\n", (unsigned) skipLines);

        }

    }

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

#endif

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

bool CheckTagType (uint32 parentCode,
                   uint32 tagCode,
                   uint32 tagType,
                   uint16 validType0,
                   uint16 validType1,
                   uint16 validType2,
                   uint16 validType3)
    {

    if (tagType != validType0 &&
        tagType != validType1 &&
        tagType != validType2 &&
        tagType != validType3)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s has unexpected type (%s)",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode),
                      LookupTagType (tagType));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;     // Unused

        #endif

        return false;

        }

    return true;

    }

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

bool CheckTagCount (uint32 parentCode,
                    uint32 tagCode,
                    uint32 tagCount,
                    uint32 minCount,
                    uint32 maxCount)
    {

    if (maxCount < minCount)
        maxCount = minCount;

    if (tagCount < minCount ||
        tagCount > maxCount)
        {

        #if qDNGValidate

            {

            char message [256];

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

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;     // Unused

        #endif

        return false;

        }

    return true;

    }

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

bool CheckColorImage (uint32 parentCode,
                      uint32 tagCode,
                      uint32 colorPlanes)
    {

    if (colorPlanes == 0)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed with unknown color plane count "
                      " (missing ColorMatrix1 tag?)",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;     // Unused

        #endif

        return false;

        }

    if (colorPlanes == 1)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed with monochrome images",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #endif

        return false;

        }

    return true;

    }

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

bool CheckMainIFD (uint32 parentCode,
                   uint32 tagCode,
                   uint32 newSubFileType)
    {

    if (newSubFileType != sfMainImage)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed IFDs with NewSubFileType != 0",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        return false;

        }

    return true;

    }

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

bool CheckMainOrEnhancedIFD (uint32 parentCode,
                             uint32 tagCode,
                             uint32 newSubFileType)
    {

    if (newSubFileType != sfMainImage &&
        newSubFileType != sfEnhancedImage)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed IFDs with NewSubFileType != 0",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        return false;

        }

    return true;

    }

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

bool CheckRawIFD (uint32 parentCode,
                  uint32 tagCode,
                  uint32 photometricInterpretation)
    {

    if (photometricInterpretation != piCFA &&
        photometricInterpretation != piLinearRaw)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed in IFDs with a non-raw PhotometricInterpretation",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        return false;

        }

    return true;

    }

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

bool CheckCFA (uint32 parentCode,
               uint32 tagCode,
               uint32 photometricInterpretation)
    {

    if (photometricInterpretation != piCFA)
        {

        #if qDNGValidate

            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s is not allowed in IFDs with a non-CFA PhotometricInterpretation",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        return false;

        }

    return true;

    }

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

void ParseStringTag (dng_stream &stream,
                     uint32 parentCode,
                     uint32 tagCode,
                     uint32 tagCount,
                     dng_string &s,
                     bool trimBlanks)
    {

    if (tagCount == 0 ||
        tagCount == 0xFFFFFFFF)
        {

        s.Clear ();

        return;

        }

    dng_memory_data temp_buffer (tagCount + 1);

    char *buffer = temp_buffer.Buffer_char ();

    stream.Get (buffer, tagCount);

    // Make sure the string is null terminated.

    if (buffer [tagCount - 1] != 0)
        {

        buffer [tagCount] = 0;

        #if qDNGValidate

            {

            bool hasNull = false;

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

                if (buffer [j] == 0)
                    {

                    hasNull = true;

                    break;

                    }

                }

            if (!hasNull && parentCode < tcFirstMakerNoteIFD)
                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s is not NULL terminated",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        }

    // Medata working group - Allow UTF-8

    s.Set_UTF8_or_System (buffer);

    if (trimBlanks)
        {

        s.TrimTrailingBlanks ();

        }

    }

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

void ParseDualStringTag (dng_stream &stream,
                         uint32 parentCode,
                         uint32 tagCode,
                         uint32 tagCount,
                         dng_string &s1,
                         dng_string &s2)
    {

    if (tagCount == 0 ||
        tagCount == 0xFFFFFFFF)
        {

        s1.Clear ();
        s2.Clear ();

        return;

        }

    dng_memory_data temp_buffer (tagCount + 1);

    char *buffer = temp_buffer.Buffer_char ();

    stream.Get (buffer, tagCount);

    // Make sure the string is null terminated.

    if (buffer [tagCount - 1] != 0)
        {

        buffer [tagCount] = 0;

        #if qDNGValidate

            {

            uint32 nullCount = 0;

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

                if (buffer [j] == 0)
                    {

                    nullCount++;

                    }

                }

            if (nullCount < 2 && parentCode < tcFirstMakerNoteIFD)
                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s is not NULL terminated",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        }

    // Medata working group - Allow UTF-8

    s1.Set_UTF8_or_System (buffer);

    s2.Set_ASCII (NULL);

    for (uint32 j = 1; j < tagCount - 1; j++)
        {

        if (buffer [j - 1] != 0 &&
            buffer [j    ] == 0)
            {

            // Medata working group - Allow UTF-8

            s2.Set_UTF8_or_System (buffer + j + 1);

            break;

            }

        }

    s1.TrimTrailingBlanks ();
    s2.TrimTrailingBlanks ();

    }

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

void ParseEncodedStringTag (dng_stream &stream,
                            uint32 parentCode,
                            uint32 tagCode,
                            uint32 tagCount,
                            dng_string &s)
    {

    if (tagCount < 8)
        {

        #if qDNGValidate

            {

            char message [256];

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

            ReportWarning (message);

            }

        #else

        (void) parentCode;      // Unused
        (void) tagCode;         // Unused

        #endif

        s.Clear ();

        return;

        }

    char label [8];

    stream.Get (label, 8);

    // Sometimes lowercase is used by mistake.  Accept this, but issue
    // warning.

        {

        #if qDNGValidate

        bool hadLower = false;

        #endif

        for (uint32 j = 0; j < 8; j++)
            {

            if (label [j] >= 'a' && label [j] <= 'z')
                {

                label [j] = 'A' + (label [j] - 'a');

                #if qDNGValidate

                hadLower = true;

                #endif

                }

            }

        #if qDNGValidate

        if (hadLower)
            {

            char message [256];

            snprintf (message,
                      256,
                      "%s %s text encoding label not all uppercase",
                      LookupParentCode (parentCode),
                      LookupTagCode (parentCode, tagCode));

            ReportWarning (message);

            }

        #endif

        }

    if (memcmp (label, "UNICODE\000", 8) == 0)
        {

        uint32 uChars = (tagCount - 8) >> 1;

        dng_memory_data temp_buffer ((uChars + 1) * 2);

        uint16 *buffer = temp_buffer.Buffer_uint16 ();

        for (uint32 j = 0; j < uChars; j++)
            {

            buffer [j] = stream.Get_uint16 ();

            }

        buffer [uChars] = 0;

        #if qDNGValidate

            {

            // If the writer used UTF-8 rather than UTF-16, and padded
            // the string with blanks, then there will be lots of 0x2020
            // (unicode dagger symbol) characters in the string.

            uint32 count2020 = 0;

            for (uint32 k = 0; buffer [k] != 0; k++)
                {

                if (buffer [k] == 0x2020)
                    {

                    count2020++;

                    }

                }

            if (count2020 > 1)
                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s text appears to be UTF-8 rather than UTF-16",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            }

        #endif

        s.Set_UTF16 (buffer);

        }

    else
        {

        uint32 aChars = tagCount - 8;

        dng_memory_data temp_buffer (aChars + 1);

        char *buffer = temp_buffer.Buffer_char ();

        stream.Get (buffer, aChars);

        buffer [aChars] = 0;

        enum dng_encoding
            {
            dng_encoding_ascii,
            dng_encoding_jis_x208_1990,
            dng_encoding_unknown
            };

        dng_encoding encoding = dng_encoding_unknown;

        if (memcmp (label, "ASCII\000\000\000", 8) == 0)
            {

            encoding = dng_encoding_ascii;

            }

        else if (memcmp (label, "JIS\000\000\000\000\000\000", 8) == 0)
            {

            encoding = dng_encoding_jis_x208_1990;

            }

        else
            {

            // Some Nikon D1 files have UserComment tags with zero encoding bits and
            // garbage text values.  So don't try to parse tags with unknown text
            // encoding unless all the characters are printing ASCII.

            #if qDNGValidate

            if (memcmp (label, "\000\000\000\000\000\000\000\000\000", 8) == 0)
                {

                // Many camera makes store null tags with all zero encoding, so
                // don't report a warning message for null strings.

                if (buffer [0] != 0)
                    {

                    char message [256];

                    snprintf (message,
                              256,
                              "%s %s has unknown encoding",
                              LookupParentCode (parentCode),
                              LookupTagCode (parentCode, tagCode));

                    ReportWarning (message);

                    }

                }

            else
                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s has unexpected text encoding",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            #endif

            }

        // If text encoding was unknown, and the text is anything
        // other than pure ASCII, then ignore it.

        if (encoding == dng_encoding_unknown)
            {

            encoding = dng_encoding_ascii;

            for (uint32 i = 0; i < aChars && buffer [i] != 0; i++)
                {

                if (buffer [i] < ' ' ||
                    buffer [i] > '~')
                    {

                    buffer [0] = 0;

                    break;

                    }

                }

            }

        switch (encoding)
            {

            case dng_encoding_ascii:
                {

                // Medata working group - allow UTF-8 for ASCII tags.

                s.Set_UTF8_or_System (buffer);

                break;

                }

            case dng_encoding_jis_x208_1990:
                {
                s.Set_JIS_X208_1990 (buffer);
                break;
                }

            case dng_encoding_unknown:
                {
                s.Set_SystemEncoding (buffer);
                break;
                }

            default:
                break;

            }

        #if qDNGValidate

            {

            if (encoding == dng_encoding_ascii && !s.IsASCII ())
                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s has non-ASCII characters",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            }

        #endif

        }

    s.TrimTrailingBlanks ();

    }

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

bool ParseMatrixTag (dng_stream &stream,
                     uint32 parentCode,
                     uint32 tagCode,
                     uint32 tagType,
                     uint32 tagCount,
                     uint32 rows,
                     uint32 cols,
                     dng_matrix &m)
    {

    if (CheckTagCount (parentCode, tagCode, tagCount, rows * cols))
        {

        dng_matrix temp (rows, cols);

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

                temp [row] [col] = stream.TagValue_real64 (tagType);

                }

        m = temp;

        return true;

        }

    return false;

    }

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

bool ParseVectorTag (dng_stream &stream,
                     uint32 parentCode,
                     uint32 tagCode,
                     uint32 tagType,
                     uint32 tagCount,
                     uint32 count,
                     dng_vector &v)
    {

    if (CheckTagCount (parentCode, tagCode, tagCount, count))
        {

        dng_vector temp (count);

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

            temp [index] = stream.TagValue_real64 (tagType);

            }

        v = temp;

        return true;

        }

    return false;

    }

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

bool ParseDateTimeTag (dng_stream &stream,
                       uint32 parentCode,
                       uint32 tagCode,
                       uint32 tagType,
                       uint32 tagCount,
                       dng_date_time &dt)
    {

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

    // Kludge: Some versions of PaintShop Pro write these fields
    // with a length of 21 rather than 20.  Otherwise they are
    // correctly formatted. So relax this test and allow these
    // these longer than standard tags to be parsed.

    (void) CheckTagCount (parentCode, tagCode, tagCount, 20);

    if (tagCount < 20)
        {
        return false;
        }

    char s [21];

    stream.Get (s, 20);

    s [20] = 0;

    // See if this is a valid date/time string.

    if (dt.Parse (s))
        {
        return true;
        }

    // Accept strings that contain only blanks, colons, and zeros as
    // valid "null" dates.

    dt = dng_date_time ();

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

        char c = s [index];

        if (c == 0)
            {
            return true;
            }

        if (c != ' ' && c != ':' && c != '0')
            {

            #if qDNGValidate

                {

                char message [256];

                snprintf (message,
                          256,
                          "%s %s is not a valid date/time",
                          LookupParentCode (parentCode),
                          LookupTagCode (parentCode, tagCode));

                ReportWarning (message);

                }

            #endif

            return false;

            }

        }

    return false;

    }

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