SkJpegXmp: Consolidate XMP parsing in SkJpegXmp

Move the static helper functions for parsing XMP for gainmap
information (namely XmpIsHDRGainMap, SkJpegGetJpegRGainmapParseXMP,
and SkJpegGetHDRGMGainmapInfo) to be members of SkJpegXmp. Also move
the various XMP parsing helpers into SkJpegXmp.cpp.

Update SkJpegGetMultiPictureGainmap to use SkJpegXmp, to allow it
to use the above functions. SkJpegSourceMgr::copyParameters, which
was used to look for just one XMP segment to
SkJpegSourceMgr::getSegmentParameters, which is used to populate the
list of APP1 segments' parameters that is used by SkJpegXmp::Make.

There is nothing Jpeg-specific about SkJpegXmp. At some point, when
it is used by more codecs, it should be renamed to SkCodecXmp.

Bug: skia:14031
Change-Id: I5d3618d561ebbdd7f1268d4a8eda3824d5853fa5
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/638436
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Christopher Cameron <ccameron@google.com>
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index dd726d5..f8bee5a 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -32,6 +32,7 @@
 
 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
 #include "src/codec/SkJpegGainmap.h"
+#include "src/codec/SkJpegMultiPicture.h"
 #include "src/codec/SkJpegXmp.h"
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 
@@ -1111,7 +1112,7 @@
     }
 
     // Attempt to extract SkGainmapInfo from the HDRGM XMP.
-    if (xmp && SkJpegGetHDRGMGainmapInfo(xmp.get(), info)) {
+    if (xmp && xmp->getGainmapInfoHDRGM(info)) {
         auto gainmapData = read_metadata(fDecoderMgr->dinfo(),
                                          kGainmapMarker,
                                          kGainmapSig,
@@ -1135,10 +1136,20 @@
     }
 
     // Attempt to extract Multi-Picture Format gainmap formats.
-    auto mpfMetadata = read_metadata(fDecoderMgr->dinfo(), kMpfMarker, kMpfSig, sizeof(kMpfSig));
-    if (SkJpegGetMultiPictureGainmap(
-                mpfMetadata, fDecoderMgr->getSourceMgr(), info, gainmapImageStream)) {
-        return true;
+    for (jpeg_marker_struct* marker = fDecoderMgr->dinfo()->marker_list; marker;
+         marker = marker->next) {
+        if (marker->marker != kMpfMarker) {
+            continue;
+        }
+        auto mpParams =
+                SkJpegParseMultiPicture(SkData::MakeWithoutCopy(marker->data, marker->data_length));
+        if (!mpParams) {
+            continue;
+        }
+        if (SkJpegGetMultiPictureGainmap(
+                    mpParams.get(), fDecoderMgr->getSourceMgr(), info, gainmapImageStream)) {
+            return true;
+        }
     }
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
     return false;
diff --git a/src/codec/SkJpegGainmap.cpp b/src/codec/SkJpegGainmap.cpp
index c32728e..36d9db9 100644
--- a/src/codec/SkJpegGainmap.cpp
+++ b/src/codec/SkJpegGainmap.cpp
@@ -13,197 +13,25 @@
 #include "include/core/SkStream.h"
 #include "include/private/SkGainmapInfo.h"
 #include "include/private/base/SkFloatingPoint.h"
-#include "include/utils/SkParse.h"
 #include "src/codec/SkCodecPriv.h"
 #include "src/codec/SkJpegMultiPicture.h"
 #include "src/codec/SkJpegPriv.h"
 #include "src/codec/SkJpegSegmentScan.h"
 #include "src/codec/SkJpegSourceMgr.h"
 #include "src/codec/SkJpegXmp.h"
-#include "src/xml/SkDOM.h"
 
 #include <cstdint>
 #include <cstring>
 #include <utility>
 #include <vector>
 
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// SkDOM and XMP helpers.
-
-/*
- * Build an SkDOM from an SkData. Return true on success and false on failure (including the input
- * data being nullptr).
- */
-bool SkDataToSkDOM(sk_sp<const SkData> data, SkDOM* dom) {
-    if (!data) {
-        return false;
-    }
-    auto stream = SkMemoryStream::MakeDirect(data->data(), data->size());
-    if (!stream) {
-        return false;
-    }
-    return dom->build(*stream) != nullptr;
-}
-
-/*
- * Given an SkDOM, verify that the dom is XMP, and find the first rdf:Description node that matches
- * the specified namespaces to the specified URIs. The XML structure that this function matches is
- * as follows (with NAMESPACEi and URIi being the parameters specified to this function):
- *
- *   <x:xmpmeta ...>
- *     <rdf:RDF ...>
- *       <rdf:Description NAMESPACE0="URI0" NAMESPACE1="URI1" .../>
- *     </rdf:RDF>
- *   </x:xmpmeta>
- */
-const SkDOM::Node* FindXmpNamespaceUriMatch(const SkDOM& dom,
-                                            const char* namespaces[],
-                                            const char* uris[],
-                                            size_t count) {
-    const SkDOM::Node* root = dom.getRootNode();
-    if (!root) {
-        return nullptr;
-    }
-    const char* rootName = dom.getName(root);
-    if (!rootName || strcmp(rootName, "x:xmpmeta") != 0) {
-        return nullptr;
-    }
-
-    const char* kRdf = "rdf:RDF";
-    for (const auto* rdf = dom.getFirstChild(root, kRdf); rdf;
-         rdf = dom.getNextSibling(rdf, kRdf)) {
-        const char* kDesc = "rdf:Description";
-        for (const auto* desc = dom.getFirstChild(rdf, kDesc); desc;
-             desc = dom.getNextSibling(desc, kDesc)) {
-            bool allNamespaceURIsMatch = true;
-            for (size_t i = 0; i < count; ++i) {
-                if (!dom.hasAttr(desc, namespaces[i], uris[i])) {
-                    allNamespaceURIsMatch = false;
-                    break;
-                }
-            }
-            if (allNamespaceURIsMatch) {
-                return desc;
-            }
-        }
-    }
-    return nullptr;
-}
-
-/*
- * Given a node, see if that node has only one child with the indicated name. If so, see if that
- * child has only a single child of its own, and that child is text. If all of that is the case
- * then return the text, otherwise return nullptr.
- *
- * In the following example, innerText will be returned.
- *    <node><childName>innerText</childName></node>
- *
- * In the following examples, nullptr will be returned (because there are multiple children with
- * childName in the first case, and because the child has children of its own in the second).
- *    <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
- *    <node><childName>innerText<otherGrandChild/></childName></node>
- */
-static const char* GetUniqueChildText(const SkDOM& dom,
-                                      const SkDOM::Node* node,
-                                      const char* childName) {
-    // Fail if there are multiple children with childName.
-    if (dom.countChildren(node, childName) != 1) {
-        return nullptr;
-    }
-    const auto* child = dom.getFirstChild(node, childName);
-    if (!child) {
-        return nullptr;
-    }
-    // Fail if the child has any children besides text.
-    if (dom.countChildren(child) != 1) {
-        return nullptr;
-    }
-    const auto* grandChild = dom.getFirstChild(child);
-    if (dom.getType(grandChild) != SkDOM::kText_Type) {
-        return nullptr;
-    }
-    // Return the text.
-    return dom.getName(grandChild);
-}
-
-// Helper function that builds on GetUniqueChildText, returning true if the unique child with
-// childName has inner text that matches an expected text.
-static bool UniqueChildTextMatches(const SkDOM& dom,
-                                   const SkDOM::Node* node,
-                                   const char* childName,
-                                   const char* expectedText) {
-    const char* text = GetUniqueChildText(dom, node, childName);
-    if (text && !strcmp(text, expectedText)) {
-        return true;
-    }
-    return false;
-}
-
-// Helper function that builds on GetUniqueChildText, returning true if the unique child with
-// childName has inner text that matches an expected integer.
-static bool UniqueChildTextMatches(const SkDOM& dom,
-                                   const SkDOM::Node* node,
-                                   const char* childName,
-                                   int32_t expectedValue) {
-    const char* text = GetUniqueChildText(dom, node, childName);
-    int32_t actualValue = 0;
-    if (text && SkParse::FindS32(text, &actualValue)) {
-        return actualValue == expectedValue;
-    }
-    return false;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// Multi-PictureFormat Gainmap Functions
-
-// Return true if the specified XMP metadata identifies this image as an HDR gainmap.
-static bool XmpIsHDRGainMap(const sk_sp<const SkData>& xmpMetadata) {
-    // Parse the XMP.
-    SkDOM dom;
-    if (!SkDataToSkDOM(xmpMetadata, &dom)) {
-        return false;
-    }
-
-    // Find a node that matches the requested namespaces and URIs.
-    const char* namespaces[2] = {"xmlns:apdi", "xmlns:HDRGainMap"};
-    const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
-                           "http://ns.apple.com/HDRGainMap/1.0/"};
-    const SkDOM::Node* node = FindXmpNamespaceUriMatch(dom, namespaces, uris, 2);
-    if (!node) {
-        return false;
-    }
-    if (!UniqueChildTextMatches(
-                dom, node, "apdi:AuxiliaryImageType", "urn:com:apple:photo:2020:aux:hdrgainmap")) {
-        SkCodecPrintf("Did not find auxiliary image type.\n");
-        return false;
-    }
-    if (!UniqueChildTextMatches(dom, node, "HDRGainMap:HDRGainMapVersion", 65536)) {
-        SkCodecPrintf("HDRGainMapVersion absent or not 65536.\n");
-        return false;
-    }
-
-    // This node will often have StoredFormat and NativeFormat children that have inner text that
-    // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
-    return true;
-}
-
-bool SkJpegGetMultiPictureGainmap(sk_sp<const SkData> decoderMpfMetadata,
+bool SkJpegGetMultiPictureGainmap(const SkJpegMultiPictureParameters* mpParams,
                                   SkJpegSourceMgr* decoderSource,
                                   SkGainmapInfo* outInfo,
                                   std::unique_ptr<SkStream>* outGainmapImageStream) {
-    // The decoder has already scanned for MPF metadata. If it doesn't exist, or it doesn't parse,
-    // then early-out.
-    if (!decoderMpfMetadata) {
-        return false;
-    }
-    auto mpParams = SkJpegParseMultiPicture(decoderMpfMetadata);
-    if (!mpParams) {
-        return false;
-    }
-
     // Extract the Multi-Picture image streams in the original decoder stream (we needed the scan to
     // find the offsets of the MP images within the original decoder stream).
-    auto mpStreams = SkJpegExtractMultiPictureStreams(mpParams.get(), decoderSource);
+    auto mpStreams = SkJpegExtractMultiPictureStreams(mpParams, decoderSource);
     if (!mpStreams) {
         SkCodecPrintf("Failed to extract MP image streams.\n");
         return false;
@@ -218,176 +46,42 @@
         // Create a temporary source manager for this MP image.
         auto mpImageSource = SkJpegSourceMgr::Make(mpImage.stream.get());
 
-        // Search for the XMP metadata in the MP image's scan.
+        // Collect the potential XMP segments.
+        std::vector<sk_sp<SkData>> app1Params;
         for (const auto& segment : mpImageSource->getAllSegments()) {
             if (segment.marker != kXMPMarker) {
                 continue;
             }
-            auto xmpMetadata = mpImageSource->copyParameters(
-                    segment, kXMPStandardSig, sizeof(kXMPStandardSig));
-            if (!xmpMetadata) {
+            auto parameters = mpImageSource->getSegmentParameters(segment);
+            if (!parameters) {
                 continue;
             }
-
-            // If this XMP does not indicate that the image is an HDR gainmap, then continue.
-            if (!XmpIsHDRGainMap(xmpMetadata)) {
-                continue;
-            }
-
-            // This MP image is the gainmap image. Populate its stream and the rendering parameters
-            // for its format.
-            if (outGainmapImageStream) {
-                if (!mpImage.stream->rewind()) {
-                    SkCodecPrintf("Failed to rewind gainmap image stream.\n");
-                    return false;
-                }
-                *outGainmapImageStream = std::move(mpImage.stream);
-            }
-            const float kRatioMax = sk_float_exp(1.f);
-            outInfo->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
-            outInfo->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
-            outInfo->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
-            outInfo->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
-            outInfo->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
-            outInfo->fDisplayRatioSdr = 1.f;
-            outInfo->fDisplayRatioHdr = kRatioMax;
-            outInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
-            outInfo->fType = SkGainmapInfo::Type::kMultiPicture;
-            return true;
+            app1Params.push_back(std::move(parameters));
         }
-    }
-    return false;
-}
 
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// JpegR Gainmap functions
-
-static bool SkJpegGetJpegRGainmapParseXMP(const SkJpegXmp* xmp,
-                                          size_t* outOffset,
-                                          size_t* outSize,
-                                          SkGainmapInfo::Type* outType,
-                                          float* outRangeScalingFactor) {
-    // Find a node that matches the requested namespaces and URIs.
-    const char* namespaces[2] = {"xmlns:GContainer", "xmlns:RecoveryMap"};
-    const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
-                           "http://ns.google.com/photos/1.0/recoverymap/"};
-    const SkDOM* dom = nullptr;
-    const SkDOM::Node* node = nullptr;
-    if (!xmp->findNamespaceUriMatch(namespaces, uris, 1, &dom, &node)) {
-        return false;
-    }
-
-    // The node must have a GContainer:Version child that specifies version 1.
-    if (!UniqueChildTextMatches(*dom, node, "GContainer:Version", 1)) {
-        SkCodecPrintf("GContainer:Version is absent or not 1");
-        return false;
-    }
-
-    // The node must have a GContainer:Directory.
-    const auto* directory = dom->getFirstChild(node, "GContainer:Directory");
-    if (!directory) {
-        SkCodecPrintf("Missing GContainer:Directory");
-        return false;
-    }
-
-    // That GContainer:Directory must have a sequence of  items.
-    const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
-    if (!seq) {
-        SkCodecPrintf("Missing rdf:Seq");
-        return false;
-    }
-
-    // Iterate through the items in the GContainer:Directory's sequence. Keep a running sum of the
-    // GContainer::ItemLength of all items that appear before the RecoveryMap.
-    bool isFirstItem = true;
-    size_t itemLengthSum = 0;
-    for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
-         li = dom->getNextSibling(li, "rdf:li")) {
-        // Each list item must contain a GContainer item.
-        const auto* item = dom->getFirstChild(li, "GContainer:Item");
-        if (!item) {
-            SkCodecPrintf("List item does not have GContainer:Item.\n");
-            return false;
+        // Build XMP if possible.
+        auto xmp = SkJpegXmp::Make(app1Params);
+        if (!xmp) {
+            continue;
         }
-        // An ItemSemantic is required for every GContainer item.
-        const char* itemSemantic = dom->findAttr(item, "GContainer:ItemSemantic");
-        if (!itemSemantic) {
-            SkCodecPrintf("GContainer item is missing ItemSemantic.\n");
-            return false;
-        }
-        // An ItemMime is required for every GContainer item.
-        const char* itemMime = dom->findAttr(item, "GContainer:ItemMime");
-        if (!itemMime) {
-            SkCodecPrintf("GContainer item is missing ItemMime.\n");
-            return false;
-        }
-        if (isFirstItem) {
-            isFirstItem = false;
-            // The first item must be Primary.
-            if (strcmp(itemSemantic, "Primary") != 0) {
-                SkCodecPrintf("First item is not Primary.\n");
-                return false;
-            }
-            // The first item has mime type image/jpeg (we are decoding a jpeg).
-            if (strcmp(itemMime, "image/jpeg") != 0) {
-                SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
-                return false;
-            }
-            // The Verison of 1 is required for the Primary.
-            if (!dom->hasAttr(item, "RecoveryMap:Version", "1")) {
-                SkCodecPrintf("RecoveryMap:Version is not 1.");
-                return false;
-            }
-            // The TransferFunction is required for the Primary.
-            int32_t transferFunction = 0;
-            if (!dom->findS32(item, "RecoveryMap:TransferFunction", &transferFunction)) {
-                SkCodecPrintf("RecoveryMap:TransferFunction is absent.");
-                return false;
-            }
-            switch (transferFunction) {
-                case 0:
-                    *outType = SkGainmapInfo::Type::kJpegR_Linear;
-                    break;
-                case 1:
-                    *outType = SkGainmapInfo::Type::kJpegR_HLG;
-                    break;
-                case 2:
-                    *outType = SkGainmapInfo::Type::kJpegR_PQ;
-                    break;
-                default:
-                    SkCodecPrintf("RecoveryMap:TransferFunction is out of range.");
-                    return false;
-            }
-            // The RangeScalingFactor is required for the Primary.
-            SkScalar rangeScalingFactor = 1.f;
-            if (!dom->findScalars(item, "RecoveryMap:RangeScalingFactor", &rangeScalingFactor, 1)) {
-                SkCodecPrintf("RecoveryMap:RangeScalingFactor is absent.");
-                return false;
-            }
-            *outRangeScalingFactor = rangeScalingFactor;
-        } else {
-            // An ItemLength is required for all non-Primary GContainter items.
-            int32_t itemLength = 0;
-            if (!dom->findS32(item, "GContainer:ItemLength", &itemLength)) {
-                SkCodecPrintf("GContainer:ItemLength is absent.");
-                return false;
-            }
-            // If this is not the recovery map, then read past it.
-            if (strcmp(itemSemantic, "RecoveryMap") != 0) {
-                itemLengthSum += itemLength;
-                continue;
-            }
-            // The recovery map must have mime type image/jpeg in this implementation.
-            if (strcmp(itemMime, "image/jpeg") != 0) {
-                SkCodecPrintf("RecoveryMap does not report that it is image/jpeg.\n");
-                return false;
-            }
 
-            // This is the recovery map.
-            *outOffset = itemLengthSum;
-            *outSize = itemLength;
-            return true;
+        // Check if this is an MPF gainmap.
+        SkGainmapInfo info;
+        if (!xmp->getGainmapInfoHDRGM(&info) && !xmp->getGainmapInfoHDRGainMap(&info)) {
+            continue;
         }
+
+        // This MP image is the gainmap image. Populate its stream and the rendering parameters
+        // for its format.
+        if (outGainmapImageStream) {
+            if (!mpImage.stream->rewind()) {
+                SkCodecPrintf("Failed to rewind gainmap image stream.\n");
+                return false;
+            }
+            *outGainmapImageStream = std::move(mpImage.stream);
+        }
+        *outInfo = info;
+        return true;
     }
     return false;
 }
@@ -397,12 +91,10 @@
                            SkGainmapInfo* outInfo,
                            std::unique_ptr<SkStream>* outGainmapImageStream) {
     // Parse the XMP metadata of the original image, to see if it specifies a RecoveryMap.
+    SkGainmapInfo info;
     size_t itemOffsetFromEndOfImage = 0;
     size_t itemSize = 0;
-    SkGainmapInfo::Type type = SkGainmapInfo::Type::kUnknown;
-    float rangeScalingFactor = 1.f;
-    if (!SkJpegGetJpegRGainmapParseXMP(
-                xmp, &itemOffsetFromEndOfImage, &itemSize, &type, &rangeScalingFactor)) {
+    if (!xmp->getGainmapInfoJpegR(&info, &itemOffsetFromEndOfImage, &itemSize)) {
         return false;
     }
 
@@ -434,86 +126,6 @@
         *outGainmapImageStream = std::move(gainmapImageStream);
     }
 
-    const float kRatioMax = rangeScalingFactor;
-    const float kRatioMin = 1.f / rangeScalingFactor;
-    outInfo->fGainmapRatioMin = {kRatioMin, kRatioMin, kRatioMin, 1.f};
-    outInfo->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
-    outInfo->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
-    outInfo->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
-    outInfo->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
-    outInfo->fDisplayRatioSdr = 1.f;
-    outInfo->fDisplayRatioHdr = rangeScalingFactor;
-    outInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
-    outInfo->fType = type;
-    return true;
-}
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-// HDRGM support
-
-// Helper function to read a 1 or 3 floats and write them into an SkColor4f.
-static void find_per_channel_attr(const SkDOM* dom,
-                                  const SkDOM::Node* node,
-                                  const char* attr,
-                                  SkColor4f* outColor) {
-    SkScalar values[3] = {0.f, 0.f, 0.f};
-    if (dom->findScalars(node, attr, values, 3)) {
-        *outColor = {values[0], values[1], values[2], 1.f};
-    } else if (dom->findScalars(node, attr, values, 1)) {
-        *outColor = {values[0], values[0], values[0], 1.f};
-    }
-}
-
-bool SkJpegGetHDRGMGainmapInfo(const SkJpegXmp* xmp, SkGainmapInfo* outGainmapInfo) {
-    // Find a node that matches the requested namespace and URI.
-    const char* namespaces[1] = {"xmlns:hdrgm"};
-    const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
-    const SkDOM* dom = nullptr;
-    const SkDOM::Node* node = nullptr;
-    if (!xmp->findNamespaceUriMatch(namespaces, uris, 1, &dom, &node)) {
-        return false;
-    }
-
-    // Initialize the parameters to their defaults.
-    SkColor4f gainMapMin = {1.f, 1.f, 1.f, 1.f};
-    SkColor4f gainMapMax = {2.f, 2.f, 2.f, 1.f};
-    SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
-    SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
-    SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
-    SkScalar hdrCapacityMin = 1.f;
-    SkScalar hdrCapacityMax = 2.f;
-
-    // Read all parameters that are present.
-    const char* baseRendition = dom->findAttr(node, "hdrgm:BaseRendition");
-    find_per_channel_attr(dom, node, "hdrgm:GainMapMin", &gainMapMin);
-    find_per_channel_attr(dom, node, "hdrgm:GainMapMax", &gainMapMax);
-    find_per_channel_attr(dom, node, "hdrgm:Gamma", &gamma);
-    find_per_channel_attr(dom, node, "hdrgm:OffsetSDR", &offsetSdr);
-    find_per_channel_attr(dom, node, "hdrgm:OffsetHDR", &offsetHdr);
-    dom->findScalar(node, "hdrgm:HDRCapacityMin", &hdrCapacityMin);
-    dom->findScalar(node, "hdrgm:HDRCapacityMax", &hdrCapacityMax);
-
-    // Translate all parameters to SkGainmapInfo's expected format.
-    // TODO(ccameron): Move all of SkGainmapInfo to linear space.
-    const float kLog2 = sk_float_log(2.f);
-    outGainmapInfo->fGainmapRatioMin = {sk_float_exp(gainMapMin.fR * kLog2),
-                                        sk_float_exp(gainMapMin.fG * kLog2),
-                                        sk_float_exp(gainMapMin.fB * kLog2),
-                                        1.f};
-    outGainmapInfo->fGainmapRatioMax = {sk_float_exp(gainMapMax.fR * kLog2),
-                                        sk_float_exp(gainMapMax.fG * kLog2),
-                                        sk_float_exp(gainMapMax.fB * kLog2),
-                                        1.f};
-    outGainmapInfo->fGainmapGamma = gamma;
-    outGainmapInfo->fEpsilonSdr = offsetSdr;
-    outGainmapInfo->fEpsilonHdr = offsetHdr;
-    outGainmapInfo->fDisplayRatioSdr = sk_float_exp(hdrCapacityMin * kLog2);
-    outGainmapInfo->fDisplayRatioHdr = sk_float_exp(hdrCapacityMax * kLog2);
-    if (baseRendition && !strcmp(baseRendition, "HDR")) {
-        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
-    } else {
-        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
-    }
-    outGainmapInfo->fType = SkGainmapInfo::Type::kHDRGM;
+    *outInfo = info;
     return true;
 }
diff --git a/src/codec/SkJpegGainmap.h b/src/codec/SkJpegGainmap.h
index 0c87275..49394b6 100644
--- a/src/codec/SkJpegGainmap.h
+++ b/src/codec/SkJpegGainmap.h
@@ -15,13 +15,14 @@
 class SkStream;
 class SkJpegXmp;
 struct SkGainmapInfo;
+struct SkJpegMultiPictureParameters;
 
 #include <memory>
 
 /*
  * Implementation of onGetGainmap that detects Multi-Picture Format based gainmaps.
  */
-bool SkJpegGetMultiPictureGainmap(sk_sp<const SkData> decoderMpfMetadata,
+bool SkJpegGetMultiPictureGainmap(const SkJpegMultiPictureParameters* mpParams,
                                   SkJpegSourceMgr* decoderSource,
                                   SkGainmapInfo* outInfo,
                                   std::unique_ptr<SkStream>* outGainmapImageStream);
@@ -34,10 +35,4 @@
                            SkGainmapInfo* outInfo,
                            std::unique_ptr<SkStream>* outGainmapImageStream);
 
-/*
- * Implementation of onGetGainmap that detects HDRGM based gainmaps and converts them to
- * SkGainmapInfo.
- */
-bool SkJpegGetHDRGMGainmapInfo(const SkJpegXmp* xmp, SkGainmapInfo* outGainmapInfo);
-
 #endif
diff --git a/src/codec/SkJpegMultiPicture.cpp b/src/codec/SkJpegMultiPicture.cpp
index d7d1274..ce1d6cc 100644
--- a/src/codec/SkJpegMultiPicture.cpp
+++ b/src/codec/SkJpegMultiPicture.cpp
@@ -35,10 +35,20 @@
     (void)VAR
 
 std::unique_ptr<SkJpegMultiPictureParameters> SkJpegParseMultiPicture(
-        const sk_sp<const SkData>& data) {
-    std::unique_ptr<SkMemoryStream> stream = SkMemoryStream::MakeDirect(data->data(), data->size());
-    // This function reads the structure described in Figure 6 of CIPA DC-x007-2009.
+        const sk_sp<const SkData>& segmentParameters) {
+    // Read the MP Format identifier starting after the APP2 Field Length. See Figure 4 of CIPA
+    // DC-x007-2009.
+    if (segmentParameters->size() < sizeof(kMpfSig)) {
+        return nullptr;
+    }
+    if (memcmp(segmentParameters->data(), kMpfSig, sizeof(kMpfSig)) != 0) {
+        return nullptr;
+    }
+    std::unique_ptr<SkMemoryStream> stream =
+            SkMemoryStream::MakeDirect(segmentParameters->bytes() + sizeof(kMpfSig),
+                                       segmentParameters->size() - sizeof(kMpfSig));
 
+    // The rest of this function reads the structure described in Figure 6 of CIPA DC-x007-2009.
     // Determine the endianness of the values in the structure. See Figure 5 (MP endian tag
     // structure).
     bool streamIsBigEndian = false;
diff --git a/src/codec/SkJpegMultiPicture.h b/src/codec/SkJpegMultiPicture.h
index c0fb716..eac0ae9 100644
--- a/src/codec/SkJpegMultiPicture.h
+++ b/src/codec/SkJpegMultiPicture.h
@@ -40,11 +40,13 @@
 };
 
 /*
- * Parse Jpeg Multi-Picture Format parameters. The specified data should start with the MP Header.
- * Returns nullptr on error.
+ * Parse Jpeg Multi-Picture Format parameters. The specified data should be APP2 segment parameters,
+ * which, if they are MPF parameter, should stat with the {'M', 'P', 'F', 0} signature. Returns
+ * nullptr the parameters do not start with the MPF signature, or if there is an error in parsing
+ * the parameters.
  */
 std::unique_ptr<SkJpegMultiPictureParameters> SkJpegParseMultiPicture(
-        const sk_sp<const SkData>& data);
+        const sk_sp<const SkData>& segmentParameters);
 
 /*
  * Create SkStreams for all MultiPicture images, given an SkJpegSourceMgr for an image. This will
diff --git a/src/codec/SkJpegSourceMgr.cpp b/src/codec/SkJpegSourceMgr.cpp
index 2854db5..4a7485b 100644
--- a/src/codec/SkJpegSourceMgr.cpp
+++ b/src/codec/SkJpegSourceMgr.cpp
@@ -91,6 +91,29 @@
         return SkMemoryStream::MakeCopy(
                 reinterpret_cast<const uint8_t*>(fStream->getMemoryBase()) + offset, size);
     }
+    sk_sp<SkData> getSegmentParameters(const SkJpegSegment& segment) override {
+        constexpr size_t kParameterLengthSize = SkJpegSegmentScanner::kParameterLengthSize;
+        constexpr size_t kMarkerCodeSize = SkJpegSegmentScanner::kMarkerCodeSize;
+        const uint8_t* base =
+                reinterpret_cast<const uint8_t*>(fStream->getMemoryBase()) + segment.offset;
+        SkASSERT(segment.offset < fStream->getLength());
+        SkASSERT(kMarkerCodeSize + segment.parameterLength <=
+                 fStream->getLength() - segment.offset);
+
+        // Read the marker and verify it matches `segment`.
+        SkASSERT(base[0] == 0xFF);
+        SkASSERT(base[1] == segment.marker);
+
+        // Read the parameter length and verify it matches `segment`.
+        SkASSERT(256 * base[2] + base[3] == segment.parameterLength);
+        if (segment.parameterLength <= SkJpegSegmentScanner::kParameterLengthSize) {
+            return nullptr;
+        }
+
+        // Read the remainder of the segment.
+        return SkData::MakeWithoutCopy(base + kMarkerCodeSize + kParameterLengthSize,
+                                       segment.parameterLength - kParameterLengthSize);
+    }
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 };
 
@@ -172,6 +195,48 @@
         }
         return SkMemoryStream::Make(data);
     }
+    sk_sp<SkData> getSegmentParameters(const SkJpegSegment& segment) override {
+        constexpr size_t kParameterLengthSize = SkJpegSegmentScanner::kParameterLengthSize;
+        constexpr size_t kMarkerCodeSize = SkJpegSegmentScanner::kMarkerCodeSize;
+        // If the segment's parameter length isn't longer than the two bytes for the length,
+        // early-out early-out.
+        if (segment.parameterLength <= kParameterLengthSize) {
+            return nullptr;
+        }
+
+        // Seek to the start of the segment.
+        ScopedSkStreamRestorer streamRestorer(fStream);
+        if (!fStream->seek(segment.offset)) {
+            SkCodecPrintf("Failed to seek to segment\n");
+            return nullptr;
+        }
+
+        // Read the marker and verify it matches `segment`.
+        uint8_t markerCode[kMarkerCodeSize] = {0};
+        if (fStream->read(markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
+            SkCodecPrintf("Failed to read segment marker code\n");
+            return nullptr;
+        }
+        SkASSERT(markerCode[0] == 0xFF);
+        SkASSERT(markerCode[1] == segment.marker);
+
+        // Read the parameter length and verify it matches `segment`.
+        uint8_t parameterLength[kParameterLengthSize] = {0};
+        if (fStream->read(parameterLength, kParameterLengthSize) != kParameterLengthSize) {
+            SkCodecPrintf("Failed to read parameter length\n");
+            return nullptr;
+        }
+        SkASSERT(256 * parameterLength[0] + parameterLength[1] == segment.parameterLength);
+
+        // Read the remainder of the segment.
+        size_t sizeToRead = segment.parameterLength - kParameterLengthSize;
+        auto result = SkData::MakeUninitialized(sizeToRead);
+        if (fStream->read(result->writable_data(), sizeToRead) != sizeToRead) {
+            return nullptr;
+        }
+
+        return result;
+    }
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 
 private:
@@ -309,6 +374,11 @@
 
         return SkMemoryStream::Make(subsetData);
     }
+    sk_sp<SkData> getSegmentParameters(const SkJpegSegment& segment) override {
+        // The only way to implement this for an unseekable stream is to record the parameters as
+        // they are scanned.
+        return nullptr;
+    }
 
 private:
     // Read the specified number of bytes into fBuffer and feed them to fScanner. The number of
@@ -335,68 +405,11 @@
     // into fBuffer.
     size_t fLastReadOffset = 0;
 };
-
-sk_sp<SkData> SkJpegSourceMgr::copyParameters(const SkJpegSegment& segment,
-                                              const void* signature,
-                                              const size_t signatureLength) {
-    // This functionality is only available for seekable streams.
-    if (!fStream->hasPosition()) {
-        return nullptr;
-    }
-
-    constexpr size_t kParameterLengthSize = SkJpegSegmentScanner::kParameterLengthSize;
-    constexpr size_t kMarkerCodeSize = SkJpegSegmentScanner::kMarkerCodeSize;
-    // If the segment's parameter length isn't long enough for the signature and the length,
-    // early-out.
-    if (segment.parameterLength < signatureLength + kParameterLengthSize) {
-        return nullptr;
-    }
-    size_t sizeToRead = segment.parameterLength - signatureLength - kParameterLengthSize;
-
-    // Seek to the start of the segment.
-    if (!fStream->seek(segment.offset)) {
-        SkCodecPrintf("Failed to seek to segment\n");
-        return nullptr;
-    }
-
-    // Read the marker and verify it matches `segment`.
-    uint8_t markerCode[kMarkerCodeSize] = {0};
-    if (fStream->read(markerCode, kMarkerCodeSize) != kMarkerCodeSize) {
-        SkCodecPrintf("Failed to read segment marker code\n");
-        return nullptr;
-    }
-    SkASSERT(markerCode[0] == 0xFF);
-    SkASSERT(markerCode[1] == segment.marker);
-
-    // Read the parameter length and verify it matches `segment`.
-    uint8_t parameterLength[kParameterLengthSize] = {0};
-    if (fStream->read(parameterLength, kParameterLengthSize) != kParameterLengthSize) {
-        SkCodecPrintf("Failed to read parameter length\n");
-        return nullptr;
-    }
-    SkASSERT(256 * parameterLength[0] + parameterLength[1] == segment.parameterLength);
-
-    // Check the next bytes against `signature`.
-    auto segmentSignature = SkData::MakeUninitialized(signatureLength);
-    if (fStream->read(segmentSignature->writable_data(), segmentSignature->size()) !=
-        segmentSignature->size()) {
-        SkCodecPrintf("Failed to read parameters\n");
-        return nullptr;
-    }
-    if (memcmp(segmentSignature->data(), signature, signatureLength) != 0) {
-        return nullptr;
-    }
-
-    // Finally, read the remainder of the segment.
-    auto result = SkData::MakeUninitialized(sizeToRead);
-    if (fStream->read(result->writable_data(), sizeToRead) != sizeToRead) {
-        return nullptr;
-    }
-
-    return result;
-}
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkJpegSourceMgr
+
 // static
 std::unique_ptr<SkJpegSourceMgr> SkJpegSourceMgr::Make(SkStream* stream, size_t bufferSize) {
 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
diff --git a/src/codec/SkJpegSourceMgr.h b/src/codec/SkJpegSourceMgr.h
index 7706b09..abcfe8c 100644
--- a/src/codec/SkJpegSourceMgr.h
+++ b/src/codec/SkJpegSourceMgr.h
@@ -56,13 +56,10 @@
     // Return a stream for the subset of this source's stream with the specified offset and size.
     virtual std::unique_ptr<SkStream> getSubsetStream(size_t offset, size_t size) = 0;
 
-    // Copy the parameters from a segment. Return nullptr if the initial bytes of the parameters
-    // section do not match the specified signature. Return the parameters starting from end of the
-    // signature (so, kParameterLengthSize + signatureLength bytes into the parameter data). This
-    // function only works for streams that are memory-backed.
-    sk_sp<SkData> copyParameters(const SkJpegSegment& segment,
-                                 const void* signature,
-                                 const size_t signatureLength);
+    // Segments start with a 2 byte marker, followed by a 2 byte parameter length (which includes
+    // those two bytes, followed by parameters. Return the parameters portion of the specified
+    // segment. If possible, the returned SkData will refer to memory owned by |fStream|.
+    virtual sk_sp<SkData> getSegmentParameters(const SkJpegSegment& segment) = 0;
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 
 protected:
diff --git a/src/codec/SkJpegXmp.cpp b/src/codec/SkJpegXmp.cpp
index 8340972..31344c1 100644
--- a/src/codec/SkJpegXmp.cpp
+++ b/src/codec/SkJpegXmp.cpp
@@ -7,12 +7,18 @@
 
 #include "src/codec/SkJpegXmp.h"
 
+#include "include/private/SkGainmapInfo.h"
+#include "include/utils/SkParse.h"
 #include "src/codec/SkCodecPriv.h"
 #include "src/codec/SkJpegPriv.h"
 #include "src/core/SkMD5.h"
+#include "src/xml/SkDOM.h"
 
 SkJpegXmp::SkJpegXmp() = default;
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// XMP JPEG extraction helper functions
+
 constexpr size_t kGuidAsciiSize = 32;
 
 /*
@@ -174,6 +180,87 @@
     return xmpExtendedData;
 }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// XMP parsing helper functions
+
+/*
+ * Helper function to read a 1 or 3 floats and write them into an SkColor4f.
+ */
+static void find_per_channel_attr(const SkDOM* dom,
+                                  const SkDOM::Node* node,
+                                  const char* attr,
+                                  SkColor4f* outColor) {
+    SkScalar values[3] = {0.f, 0.f, 0.f};
+    if (dom->findScalars(node, attr, values, 3)) {
+        *outColor = {values[0], values[1], values[2], 1.f};
+    } else if (dom->findScalars(node, attr, values, 1)) {
+        *outColor = {values[0], values[0], values[0], 1.f};
+    }
+}
+
+/*
+ * Given a node, see if that node has only one child with the indicated name. If so, see if that
+ * child has only a single child of its own, and that child is text. If all of that is the case
+ * then return the text, otherwise return nullptr.
+ *
+ * In the following example, innerText will be returned.
+ *    <node><childName>innerText</childName></node>
+ *
+ * In the following examples, nullptr will be returned (because there are multiple children with
+ * childName in the first case, and because the child has children of its own in the second).
+ *    <node><childName>innerTextA</childName><childName>innerTextB</childName></node>
+ *    <node><childName>innerText<otherGrandChild/></childName></node>
+ */
+static const char* get_unique_child_text(const SkDOM& dom,
+                                         const SkDOM::Node* node,
+                                         const char* childName) {
+    // Fail if there are multiple children with childName.
+    if (dom.countChildren(node, childName) != 1) {
+        return nullptr;
+    }
+    const auto* child = dom.getFirstChild(node, childName);
+    if (!child) {
+        return nullptr;
+    }
+    // Fail if the child has any children besides text.
+    if (dom.countChildren(child) != 1) {
+        return nullptr;
+    }
+    const auto* grandChild = dom.getFirstChild(child);
+    if (dom.getType(grandChild) != SkDOM::kText_Type) {
+        return nullptr;
+    }
+    // Return the text.
+    return dom.getName(grandChild);
+}
+
+// Helper function that builds on get_unique_child_text, returning true if the unique child with
+// childName has inner text that matches an expected text.
+static bool unique_child_text_matches(const SkDOM& dom,
+                                      const SkDOM::Node* node,
+                                      const char* childName,
+                                      const char* expectedText) {
+    const char* text = get_unique_child_text(dom, node, childName);
+    if (text && !strcmp(text, expectedText)) {
+        return true;
+    }
+    return false;
+}
+
+// Helper function that builds on get_unique_child_text, returning true if the unique child with
+// childName has inner text that matches an expected integer.
+static bool unique_child_text_matches(const SkDOM& dom,
+                                      const SkDOM::Node* node,
+                                      const char* childName,
+                                      int32_t expectedValue) {
+    const char* text = get_unique_child_text(dom, node, childName);
+    int32_t actualValue = 0;
+    if (text && SkParse::FindS32(text, &actualValue)) {
+        return actualValue == expectedValue;
+    }
+    return false;
+}
+
 /*
  * Given an SkDOM, verify that the dom is XMP, and find the first rdf:Description node that matches
  * the specified namespaces to the specified URIs. The XML structure that this function matches is
@@ -224,6 +311,9 @@
     return nullptr;
 }
 
+////////////////////////////////////////////////////////////////////////////////////////////////////
+// SkJpegXmp
+
 std::unique_ptr<SkJpegXmp> SkJpegXmp::Make(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
     auto xmpStandard = read_xmp_standard(decoderApp1Params);
     if (!xmpStandard) {
@@ -292,3 +382,233 @@
     *outDom = nullptr;
     return false;
 }
+
+bool SkJpegXmp::getGainmapInfoJpegR(SkGainmapInfo* outInfo,
+                                    size_t* outOffset,
+                                    size_t* outSize) const {
+    // Find a node that matches the requested namespaces and URIs.
+    const char* namespaces[2] = {"xmlns:GContainer", "xmlns:RecoveryMap"};
+    const char* uris[2] = {"http://ns.google.com/photos/1.0/container/",
+                           "http://ns.google.com/photos/1.0/recoverymap/"};
+    const SkDOM* dom = nullptr;
+    const SkDOM::Node* node = nullptr;
+    if (!findNamespaceUriMatch(namespaces, uris, 1, &dom, &node)) {
+        return false;
+    }
+
+    // The node must have a GContainer:Version child that specifies version 1.
+    if (!unique_child_text_matches(*dom, node, "GContainer:Version", 1)) {
+        SkCodecPrintf("GContainer:Version is absent or not 1");
+        return false;
+    }
+
+    // The node must have a GContainer:Directory.
+    const auto* directory = dom->getFirstChild(node, "GContainer:Directory");
+    if (!directory) {
+        SkCodecPrintf("Missing GContainer:Directory");
+        return false;
+    }
+
+    // That GContainer:Directory must have a sequence of  items.
+    const auto* seq = dom->getFirstChild(directory, "rdf:Seq");
+    if (!seq) {
+        SkCodecPrintf("Missing rdf:Seq");
+        return false;
+    }
+
+    // Iterate through the items in the GContainer:Directory's sequence. Keep a running sum of the
+    // GContainer::ItemLength of all items that appear before the RecoveryMap.
+    bool isFirstItem = true;
+    size_t itemLengthSum = 0;
+    SkGainmapInfo::Type type = SkGainmapInfo::Type::kUnknown;
+    SkScalar rangeScalingFactor = 1.f;
+    for (const auto* li = dom->getFirstChild(seq, "rdf:li"); li;
+         li = dom->getNextSibling(li, "rdf:li")) {
+        // Each list item must contain a GContainer item.
+        const auto* item = dom->getFirstChild(li, "GContainer:Item");
+        if (!item) {
+            SkCodecPrintf("List item does not have GContainer:Item.\n");
+            return false;
+        }
+        // An ItemSemantic is required for every GContainer item.
+        const char* itemSemantic = dom->findAttr(item, "GContainer:ItemSemantic");
+        if (!itemSemantic) {
+            SkCodecPrintf("GContainer item is missing ItemSemantic.\n");
+            return false;
+        }
+        // An ItemMime is required for every GContainer item.
+        const char* itemMime = dom->findAttr(item, "GContainer:ItemMime");
+        if (!itemMime) {
+            SkCodecPrintf("GContainer item is missing ItemMime.\n");
+            return false;
+        }
+        if (isFirstItem) {
+            isFirstItem = false;
+            // The first item must be Primary.
+            if (strcmp(itemSemantic, "Primary") != 0) {
+                SkCodecPrintf("First item is not Primary.\n");
+                return false;
+            }
+            // The first item has mime type image/jpeg (we are decoding a jpeg).
+            if (strcmp(itemMime, "image/jpeg") != 0) {
+                SkCodecPrintf("Primary does not report that it is image/jpeg.\n");
+                return false;
+            }
+            // The Verison of 1 is required for the Primary.
+            if (!dom->hasAttr(item, "RecoveryMap:Version", "1")) {
+                SkCodecPrintf("RecoveryMap:Version is not 1.");
+                return false;
+            }
+            // The TransferFunction is required for the Primary.
+            int32_t transferFunction = 0;
+            if (!dom->findS32(item, "RecoveryMap:TransferFunction", &transferFunction)) {
+                SkCodecPrintf("RecoveryMap:TransferFunction is absent.");
+                return false;
+            }
+            switch (transferFunction) {
+                case 0:
+                    type = SkGainmapInfo::Type::kJpegR_Linear;
+                    break;
+                case 1:
+                    type = SkGainmapInfo::Type::kJpegR_HLG;
+                    break;
+                case 2:
+                    type = SkGainmapInfo::Type::kJpegR_PQ;
+                    break;
+                default:
+                    SkCodecPrintf("RecoveryMap:TransferFunction is out of range.");
+                    return false;
+            }
+            // The RangeScalingFactor is required for the Primary.
+            if (!dom->findScalars(item, "RecoveryMap:RangeScalingFactor", &rangeScalingFactor, 1)) {
+                SkCodecPrintf("RecoveryMap:RangeScalingFactor is absent.");
+                return false;
+            }
+        } else {
+            // An ItemLength is required for all non-Primary GContainter items.
+            int32_t itemLength = 0;
+            if (!dom->findS32(item, "GContainer:ItemLength", &itemLength)) {
+                SkCodecPrintf("GContainer:ItemLength is absent.");
+                return false;
+            }
+            // If this is not the recovery map, then read past it.
+            if (strcmp(itemSemantic, "RecoveryMap") != 0) {
+                itemLengthSum += itemLength;
+                continue;
+            }
+            // The recovery map must have mime type image/jpeg in this implementation.
+            if (strcmp(itemMime, "image/jpeg") != 0) {
+                SkCodecPrintf("RecoveryMap does not report that it is image/jpeg.\n");
+                return false;
+            }
+
+            // Populate the SkGainmapInfo
+            const float kRatioMax = rangeScalingFactor;
+            const float kRatioMin = 1.f / rangeScalingFactor;
+            outInfo->fGainmapRatioMin = {kRatioMin, kRatioMin, kRatioMin, 1.f};
+            outInfo->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
+            outInfo->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
+            outInfo->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
+            outInfo->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
+            outInfo->fDisplayRatioSdr = 1.f;
+            outInfo->fDisplayRatioHdr = rangeScalingFactor;
+            outInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
+            outInfo->fType = type;
+
+            // Populate the location in the file at which to find the gainmap image.
+            *outOffset = itemLengthSum;
+            *outSize = itemLength;
+            return true;
+        }
+    }
+    return false;
+}
+
+// Return true if the specified XMP metadata identifies this image as an HDR gainmap.
+bool SkJpegXmp::getGainmapInfoHDRGainMap(SkGainmapInfo* info) const {
+    // Find a node that matches the requested namespaces and URIs.
+    const char* namespaces[2] = {"xmlns:apdi", "xmlns:HDRGainMap"};
+    const char* uris[2] = {"http://ns.apple.com/pixeldatainfo/1.0/",
+                           "http://ns.apple.com/HDRGainMap/1.0/"};
+    const SkDOM* dom = nullptr;
+    const SkDOM::Node* node = nullptr;
+    if (!findNamespaceUriMatch(namespaces, uris, 2, &dom, &node)) {
+        return false;
+    }
+    if (!unique_child_text_matches(
+                *dom, node, "apdi:AuxiliaryImageType", "urn:com:apple:photo:2020:aux:hdrgainmap")) {
+        SkCodecPrintf("Did not find auxiliary image type.\n");
+        return false;
+    }
+    if (!unique_child_text_matches(*dom, node, "HDRGainMap:HDRGainMapVersion", 65536)) {
+        SkCodecPrintf("HDRGainMapVersion absent or not 65536.\n");
+        return false;
+    }
+
+    // This node will often have StoredFormat and NativeFormat children that have inner text that
+    // specifies the integer 'L008' (also known as kCVPixelFormatType_OneComponent8).
+    const float kRatioMax = sk_float_exp(1.f);
+    info->fGainmapRatioMin = {1.f, 1.f, 1.f, 1.f};
+    info->fGainmapRatioMax = {kRatioMax, kRatioMax, kRatioMax, 1.f};
+    info->fGainmapGamma = {1.f, 1.f, 1.f, 1.f};
+    info->fEpsilonSdr = {0.f, 0.f, 0.f, 1.f};
+    info->fEpsilonHdr = {0.f, 0.f, 0.f, 1.f};
+    info->fDisplayRatioSdr = 1.f;
+    info->fDisplayRatioHdr = kRatioMax;
+    info->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
+    info->fType = SkGainmapInfo::Type::kMultiPicture;
+    return true;
+}
+
+bool SkJpegXmp::getGainmapInfoHDRGM(SkGainmapInfo* outGainmapInfo) const {
+    // Find a node that matches the requested namespace and URI.
+    const char* namespaces[1] = {"xmlns:hdrgm"};
+    const char* uris[1] = {"http://ns.adobe.com/hdr-gain-map/1.0/"};
+    const SkDOM* dom = nullptr;
+    const SkDOM::Node* node = nullptr;
+    if (!findNamespaceUriMatch(namespaces, uris, 1, &dom, &node)) {
+        return false;
+    }
+
+    // Initialize the parameters to their defaults.
+    SkColor4f gainMapMin = {1.f, 1.f, 1.f, 1.f};
+    SkColor4f gainMapMax = {2.f, 2.f, 2.f, 1.f};
+    SkColor4f gamma = {1.f, 1.f, 1.f, 1.f};
+    SkColor4f offsetSdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
+    SkColor4f offsetHdr = {1.f / 64.f, 1.f / 64.f, 1.f / 64.f, 0.f};
+    SkScalar hdrCapacityMin = 1.f;
+    SkScalar hdrCapacityMax = 2.f;
+
+    // Read all parameters that are present.
+    const char* baseRendition = dom->findAttr(node, "hdrgm:BaseRendition");
+    find_per_channel_attr(dom, node, "hdrgm:GainMapMin", &gainMapMin);
+    find_per_channel_attr(dom, node, "hdrgm:GainMapMax", &gainMapMax);
+    find_per_channel_attr(dom, node, "hdrgm:Gamma", &gamma);
+    find_per_channel_attr(dom, node, "hdrgm:OffsetSDR", &offsetSdr);
+    find_per_channel_attr(dom, node, "hdrgm:OffsetHDR", &offsetHdr);
+    dom->findScalar(node, "hdrgm:HDRCapacityMin", &hdrCapacityMin);
+    dom->findScalar(node, "hdrgm:HDRCapacityMax", &hdrCapacityMax);
+
+    // Translate all parameters to SkGainmapInfo's expected format.
+    const float kLog2 = sk_float_log(2.f);
+    outGainmapInfo->fGainmapRatioMin = {sk_float_exp(gainMapMin.fR * kLog2),
+                                        sk_float_exp(gainMapMin.fG * kLog2),
+                                        sk_float_exp(gainMapMin.fB * kLog2),
+                                        1.f};
+    outGainmapInfo->fGainmapRatioMax = {sk_float_exp(gainMapMax.fR * kLog2),
+                                        sk_float_exp(gainMapMax.fG * kLog2),
+                                        sk_float_exp(gainMapMax.fB * kLog2),
+                                        1.f};
+    outGainmapInfo->fGainmapGamma = gamma;
+    outGainmapInfo->fEpsilonSdr = offsetSdr;
+    outGainmapInfo->fEpsilonHdr = offsetHdr;
+    outGainmapInfo->fDisplayRatioSdr = sk_float_exp(hdrCapacityMin * kLog2);
+    outGainmapInfo->fDisplayRatioHdr = sk_float_exp(hdrCapacityMax * kLog2);
+    if (baseRendition && !strcmp(baseRendition, "HDR")) {
+        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kHDR;
+    } else {
+        outGainmapInfo->fBaseImageType = SkGainmapInfo::BaseImageType::kSDR;
+    }
+    outGainmapInfo->fType = SkGainmapInfo::Type::kHDRGM;
+    return true;
+}
diff --git a/src/codec/SkJpegXmp.h b/src/codec/SkJpegXmp.h
index 6228067..3cc75ff 100644
--- a/src/codec/SkJpegXmp.h
+++ b/src/codec/SkJpegXmp.h
@@ -25,6 +25,19 @@
     // Find and parse all XMP metadata, given a list of all APP1 segment parameters.
     static std::unique_ptr<SkJpegXmp> Make(const std::vector<sk_sp<SkData>>& decoderApp1Params);
 
+    // Extract HDRGM gainmap parameters.
+    bool getGainmapInfoHDRGM(SkGainmapInfo* info) const;
+
+    // Extract HDRGainMap gainmap parameters.
+    bool getGainmapInfoHDRGainMap(SkGainmapInfo* info) const;
+
+    // Extract JpegR gainmap parameters, along with the stream's offset from the end of the base
+    // image and its size.
+    bool getGainmapInfoJpegR(SkGainmapInfo* info, size_t* offset, size_t* size) const;
+
+private:
+    SkJpegXmp();
+
     // Find an XMP node that has the specified namespace attributes set to the specified URIs. The
     // XMP that this will search for is as follows (NAMESPACEi and URIi being the parameters
     // specified to this function):
@@ -43,9 +56,6 @@
                                const SkDOM** outDom,
                                const SkDOM::Node** outNode) const;
 
-private:
-    SkJpegXmp();
-
     // The DOM for the standard XMP.
     SkDOM fStandardDOM;
 
diff --git a/tests/JpegGainmapTest.cpp b/tests/JpegGainmapTest.cpp
index 52576f4..c92eb38 100644
--- a/tests/JpegGainmapTest.cpp
+++ b/tests/JpegGainmapTest.cpp
@@ -179,15 +179,17 @@
         auto sourceMgr = SkJpegSourceMgr::Make(stream.get());
         for (const auto& segment : sourceMgr->getAllSegments()) {
             static constexpr uint32_t kMpfMarker = 0xE2;
-            static constexpr uint8_t kMpfSig[] = {'M', 'P', 'F', '\0'};
             if (segment.marker != kMpfMarker) {
                 continue;
             }
-            auto parameterData = sourceMgr->copyParameters(segment, kMpfSig, sizeof(kMpfSig));
+            auto parameterData = sourceMgr->getSegmentParameters(segment);
             if (!parameterData) {
                 continue;
             }
             mpParams = SkJpegParseMultiPicture(parameterData);
+            if (mpParams) {
+                break;
+            }
         }
     }
     REPORTER_ASSERT(r, mpParams);