SkJpegMetadataDecoderImpl: Move to separate file

The metadata decoding for JPEG files will be getting larger due to
the inclusion of ISO gainmap support. Move to a separate file to
avoid creating more clutter.

Bug: b/338342146
Change-Id: Ic28c7ba1aa36edb488906dc227df8570818c3af3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/849356
Reviewed-by: Brian Osman <brianosman@google.com>
Reviewed-by: Christopher Cameron <ccameron@google.com>
Commit-Queue: Christopher Cameron <ccameron@google.com>
diff --git a/BUILD.gn b/BUILD.gn
index b9f7105..abfe4d1 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -1111,6 +1111,7 @@
   sources = [
     "src/codec/SkJpegCodec.cpp",
     "src/codec/SkJpegDecoderMgr.cpp",
+    "src/codec/SkJpegMetadataDecoderImpl.cpp",
     "src/codec/SkJpegSourceMgr.cpp",
     "src/codec/SkJpegUtility.cpp",
   ]
diff --git a/public.bzl b/public.bzl
index 9c5bd974..0294362 100644
--- a/public.bzl
+++ b/public.bzl
@@ -1832,6 +1832,8 @@
     "src/codec/SkJpegCodec.h",
     "src/codec/SkJpegDecoderMgr.cpp",
     "src/codec/SkJpegDecoderMgr.h",
+    "src/codec/SkJpegMetadataDecoderImpl.cpp",
+    "src/codec/SkJpegMetadataDecoderImpl.h",
     "src/codec/SkJpegPriv.h",
     "src/codec/SkJpegSourceMgr.cpp",
     "src/codec/SkJpegSourceMgr.h",
diff --git a/src/codec/BUILD.bazel b/src/codec/BUILD.bazel
index 052e3a3..cac4a1d 100644
--- a/src/codec/BUILD.bazel
+++ b/src/codec/BUILD.bazel
@@ -93,6 +93,8 @@
     "SkJpegCodec.h",
     "SkJpegDecoderMgr.cpp",
     "SkJpegDecoderMgr.h",
+    "SkJpegMetadataDecoderImpl.cpp",
+    "SkJpegMetadataDecoderImpl.h",
     "SkJpegSourceMgr.cpp",
     "SkJpegSourceMgr.h",
     "SkJpegUtility.cpp",
@@ -491,6 +493,8 @@
         "SkJpegCodec.h",
         "SkJpegDecoderMgr.cpp",
         "SkJpegDecoderMgr.h",
+        "SkJpegMetadataDecoderImpl.cpp",
+        "SkJpegMetadataDecoderImpl.h",
         "SkJpegSourceMgr.cpp",
         "SkJpegSourceMgr.h",
         "SkJpegUtility.cpp",
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 5563f67..a74ac9c 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -18,7 +18,6 @@
 #include "include/core/SkStream.h"
 #include "include/core/SkTypes.h"
 #include "include/core/SkYUVAInfo.h"
-#include "include/private/SkJpegMetadataDecoder.h"
 #include "include/private/base/SkAlign.h"
 #include "include/private/base/SkMalloc.h"
 #include "include/private/base/SkTemplates.h"
@@ -26,23 +25,19 @@
 #include "src/codec/SkCodecPriv.h"
 #include "src/codec/SkJpegConstants.h"
 #include "src/codec/SkJpegDecoderMgr.h"
+#include "src/codec/SkJpegMetadataDecoderImpl.h"
 #include "src/codec/SkJpegPriv.h"
 #include "src/codec/SkParseEncodedOrigin.h"
 #include "src/codec/SkSwizzler.h"
 
 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
 #include "include/private/SkGainmapInfo.h"
-#include "include/private/SkXmp.h"
-#include "src/codec/SkJpegMultiPicture.h"
-#include "src/codec/SkJpegSegmentScan.h"
-#include "src/codec/SkJpegXmp.h"
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
 
 #include <array>
 #include <csetjmp>
 #include <cstring>
 #include <utility>
-#include <vector>
 
 using namespace skia_private;
 
@@ -62,9 +57,6 @@
     return bytesRead >= sizeof(kJpegSig) && !memcmp(buffer, kJpegSig, sizeof(kJpegSig));
 }
 
-using SkJpegMarker = SkJpegMetadataDecoder::Segment;
-using SkJpegMarkerList = std::vector<SkJpegMarker>;
-
 SkJpegMarkerList get_sk_marker_list(jpeg_decompress_struct* dinfo) {
     SkJpegMarkerList markerList;
     for (auto* marker = dinfo->marker_list; marker; marker = marker->next) {
@@ -74,173 +66,6 @@
     return markerList;
 }
 
-/**
- * Return true if the specified SkJpegMarker has marker |targetMarker| and begins with the specified
- * signature.
- */
-static bool marker_has_signature(const SkJpegMarker& marker,
-                                 const uint32_t targetMarker,
-                                 const uint8_t* signature,
-                                 size_t signatureSize) {
-    if (targetMarker != marker.fMarker) {
-        return false;
-    }
-    if (marker.fData->size() <= signatureSize) {
-        return false;
-    }
-    if (memcmp(marker.fData->bytes(), signature, signatureSize) != 0) {
-        return false;
-    }
-    return true;
-}
-
-/*
- * Return metadata with a specific marker and signature.
- *
- * Search for segments that start with the specified targetMarker, followed by the specified
- * signature, followed by (optional) padding.
- *
- * Some types of metadata (e.g, ICC profiles) are too big to fit into a single segment's data (which
- * is limited to 64k), and come in multiple parts. For this type of data, bytesInIndex is >0. After
- * the signature comes bytesInIndex bytes (big endian) for the index of the segment's part, followed
- * by bytesInIndex bytes (big endian) for the total number of parts. If all parts are present,
- * stitch them together and return the combined result. Return failure if parts are absent, there
- * are duplicate parts, or parts disagree on the total number of parts.
- *
- * Visually, each segment is:
- * [|signatureSize| bytes containing |signature|]
- * [|signaturePadding| bytes that are unexamined]
- * [|bytesInIndex] bytes listing the segment index for multi-segment metadata]
- * [|bytesInIndex] bytes listing the segment count for multi-segment metadata]
- * [the returned data]
- *
- * If alwaysCopyData is true, then return a copy of the data. If alwaysCopyData is false, then
- * return a direct reference to the data pointed to by dinfo, if possible.
- */
-static sk_sp<SkData> read_metadata(const SkJpegMarkerList& markerList,
-                                   const uint32_t targetMarker,
-                                   const uint8_t* signature,
-                                   size_t signatureSize,
-                                   size_t signaturePadding,
-                                   size_t bytesInIndex,
-                                   bool alwaysCopyData) {
-    // Compute the total size of the entire header (signature plus padding plus index plus count),
-    // since we'll use it often.
-    const size_t headerSize = signatureSize + signaturePadding + 2 * bytesInIndex;
-
-    // A map from part index to the data in each part.
-    std::vector<sk_sp<SkData>> parts;
-
-    // Running total of number of data in all parts.
-    size_t partsTotalSize = 0;
-
-    // Running total number of parts found.
-    uint32_t foundPartCount = 0;
-
-    // The expected number of parts (initialized at the first part we encounter).
-    uint32_t expectedPartCount = 0;
-
-    // Iterate through the image's segments.
-    for (const auto& marker : markerList) {
-        // Skip segments that don't have the right marker or signature.
-        if (!marker_has_signature(marker, targetMarker, signature, signatureSize)) {
-            continue;
-        }
-
-        // Skip segments that are too small to include the index and count.
-        const size_t dataLength = marker.fData->size();
-        if (dataLength <= headerSize) {
-            continue;
-        }
-
-        // Read this part's index and count as big-endian (if they are present, otherwise hard-code
-        // them to 1).
-        const uint8_t* data = marker.fData->bytes();
-        uint32_t partIndex = 0;
-        uint32_t partCount = 0;
-        if (bytesInIndex == 0) {
-            partIndex = 1;
-            partCount = 1;
-        } else {
-            for (size_t i = 0; i < bytesInIndex; ++i) {
-                const size_t offset = signatureSize + signaturePadding;
-                partIndex = (partIndex << 8) + data[offset + i];
-                partCount = (partCount << 8) + data[offset + bytesInIndex + i];
-            }
-        }
-
-        // A part count of 0 is invalid.
-        if (!partCount) {
-            SkCodecPrintf("Invalid marker part count zero\n");
-            return nullptr;
-        }
-
-        // The indices must in the range 1, ..., count.
-        if (partIndex <= 0 || partIndex > partCount) {
-            SkCodecPrintf("Invalid marker index %u for count %u\n", partIndex, partCount);
-            return nullptr;
-        }
-
-        // If this is the first marker we've encountered set the expected part count to its count.
-        if (expectedPartCount == 0) {
-            expectedPartCount = partCount;
-            parts.resize(expectedPartCount);
-        }
-
-        // If this does not match the expected part count, then fail.
-        if (partCount != expectedPartCount) {
-            SkCodecPrintf("Conflicting marker counts %u vs %u\n", partCount, expectedPartCount);
-            return nullptr;
-        }
-
-        // Make an SkData directly referencing the decoder's data for this part.
-        auto partData = SkData::MakeWithoutCopy(data + headerSize, dataLength - headerSize);
-
-        // Fail if duplicates are found.
-        if (parts[partIndex-1]) {
-            SkCodecPrintf("Duplicate parts for index %u of %u\n", partIndex, expectedPartCount);
-            return nullptr;
-        }
-
-        // Save part in the map.
-        partsTotalSize += partData->size();
-        parts[partIndex-1] = std::move(partData);
-        foundPartCount += 1;
-
-        // Stop as soon as we find all of the parts.
-        if (foundPartCount == expectedPartCount) {
-            break;
-        }
-    }
-
-    // Return nullptr if we don't find the data (this is not an error).
-    if (expectedPartCount == 0) {
-        return nullptr;
-    }
-
-    // Fail if we don't have all of the parts.
-    if (foundPartCount != expectedPartCount) {
-        SkCodecPrintf("Incomplete set of markers (expected %u got %u)\n",
-                      expectedPartCount,
-                      foundPartCount);
-        return nullptr;
-    }
-
-    // Return a direct reference to the data if there is only one part and we're allowed to.
-    if (!alwaysCopyData && expectedPartCount == 1) {
-        return std::move(parts[0]);
-    }
-
-    // Copy all of the markers and stitch them together.
-    auto result = SkData::MakeUninitialized(partsTotalSize);
-    void* copyDest = result->writable_data();
-    for (const auto& part : parts) {
-        memcpy(copyDest, part->data(), part->size());
-        copyDest = SkTAddOffset<void>(copyDest, part->size());
-    }
-    return result;
-}
-
 static SkEncodedOrigin get_exif_orientation(sk_sp<SkData> exifData) {
     SkEncodedOrigin origin = kDefault_SkEncodedOrigin;
     if (exifData && SkParseEncodedOrigin(exifData->bytes(), exifData->size(), &origin)) {
@@ -293,7 +118,8 @@
             return kInvalidInput;
         }
 
-        auto metadataDecoder = SkJpegMetadataDecoder::Make(get_sk_marker_list(dinfo));
+        auto metadataDecoder =
+                std::make_unique<SkJpegMetadataDecoderImpl>(get_sk_marker_list(dinfo));
 
         SkEncodedOrigin orientation =
                 get_exif_orientation(metadataDecoder->getExifMetadata(/*copyData=*/false));
@@ -1079,289 +905,25 @@
     return kSuccess;
 }
 
+bool SkJpegCodec::onGetGainmapInfo(SkGainmapInfo* info,
+                                   std::unique_ptr<SkStream>* gainmapImageStream) {
 #ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
-// Collect and parse the primary and extended XMP metadata.
-static std::unique_ptr<SkXmp> get_xmp_metadata(const SkJpegMarkerList& markerList) {
-    std::vector<sk_sp<SkData>> decoderApp1Params;
-    for (const auto& marker : markerList) {
-        if (marker.fMarker == kXMPMarker) {
-            decoderApp1Params.push_back(marker.fData);
-        }
-    }
-    return SkJpegMakeXmp(decoderApp1Params);
-}
+    sk_sp<SkData> gainmap_data;
+    SkGainmapInfo gainmap_info;
 
-// Extract the SkJpegMultiPictureParameters from this image (if they exist). If |sourceMgr| and
-// |outMpParamsSegment| are non-nullptr, then also return the SkJpegSegment that the parameters came
-// from (and return nullptr if one cannot be found).
-static std::unique_ptr<SkJpegMultiPictureParameters> find_mp_params(
-        const SkJpegMarkerList& markerList,
-        SkJpegSourceMgr* sourceMgr,
-        SkJpegSegment* outMpParamsSegment) {
-    std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
-    size_t skippedSegmentCount = 0;
-
-    // Search though the libjpeg segments until we find a segment that parses as MP parameters. Keep
-    // track of how many segments with the MPF marker we skipped over to get there.
-    for (const auto& marker : markerList) {
-        if (marker.fMarker != kMpfMarker) {
-            continue;
-        }
-        mpParams = SkJpegMultiPictureParameters::Make(marker.fData);
-        if (mpParams) {
-            break;
-        }
-        ++skippedSegmentCount;
-    }
-    if (!mpParams) {
-        return nullptr;
-    }
-
-    // If |sourceMgr| is not specified, then do not try to find the SkJpegSegment.
-    if (!sourceMgr) {
-        SkASSERT(!outMpParamsSegment);
-        return mpParams;
-    }
-
-    // Now, find the SkJpegSegmentScanner segment that corresponds to the libjpeg marker.
-    // TODO(ccameron): It may be preferable to make SkJpegSourceMgr save segments with certain
-    // markers to avoid this strangeness.
-    for (const auto& segment : sourceMgr->getAllSegments()) {
-        if (segment.marker != kMpfMarker) {
-            continue;
-        }
-        if (skippedSegmentCount == 0) {
-            *outMpParamsSegment = segment;
-            return mpParams;
-        }
-        skippedSegmentCount--;
-    }
-    return nullptr;
-}
-
-// Attempt to extract a gainmap image from a specified offset and size within the decoder's stream.
-// Returns true only if the extracted gainmap image includes XMP metadata that specifies HDR gainmap
-// rendering parameters.
-static bool extract_gainmap(SkJpegSourceMgr* decoderSource,
-                            size_t offset,
-                            size_t size,
-                            bool base_image_has_hdrgm,
-                            SkGainmapInfo* outInfo,
-                            std::unique_ptr<SkStream>* outGainmapImageStream) {
-    // Extract the SkData for this image.
-    bool imageDataWasCopied = false;
-    auto imageData = decoderSource->getSubsetData(offset, size, &imageDataWasCopied);
-    if (!imageData) {
-        SkCodecPrintf("Failed to extract MP image.\n");
+    auto metadataDecoder =
+            std::make_unique<SkJpegMetadataDecoderImpl>(get_sk_marker_list(fDecoderMgr->dinfo()));
+    if (!metadataDecoder->findGainmapImage(
+                fDecoderMgr->getSourceMgr(), gainmap_data, gainmap_info)) {
         return false;
     }
 
-    // Scan through the image up to the StartOfScan. We'll be searching for the XMP metadata.
-    SkJpegSegmentScanner scan(kJpegMarkerStartOfScan);
-    scan.onBytes(imageData->data(), imageData->size());
-    if (scan.hadError() || !scan.isDone()) {
-        SkCodecPrintf("Failed to scan header of MP image.\n");
-        return false;
-    }
-
-    // Collect the potential XMP segments and build the XMP.
-    std::vector<sk_sp<SkData>> app1Params;
-    for (const auto& segment : scan.getSegments()) {
-        if (segment.marker != kXMPMarker) {
-            continue;
-        }
-        auto parameters = SkJpegSegmentScanner::GetParameters(imageData.get(), segment);
-        if (!parameters) {
-            continue;
-        }
-        app1Params.push_back(std::move(parameters));
-    }
-    auto xmp = SkJpegMakeXmp(app1Params);
-    if (!xmp) {
-        return false;
-    }
-
-    // Check if this image identifies itself as a gainmap.
-    bool did_populate_info = false;
-    SkGainmapInfo info;
-
-    // Check for HDRGM only if the base image specified hdrgm:Version="1.0".
-    did_populate_info = base_image_has_hdrgm && xmp->getGainmapInfoHDRGM(&info);
-
-    // Next, check HDRGainMap. This does not require anything specific from the base image.
-    if (!did_populate_info) {
-        did_populate_info = xmp->getGainmapInfoHDRGainMap(&info);
-    }
-
-    // If none of the formats identified itself as a gainmap and populated |info| then fail.
-    if (!did_populate_info) {
-        return false;
-    }
-
-    // This image is a gainmap. Populate its stream.
-    if (outGainmapImageStream) {
-        if (imageDataWasCopied) {
-            *outGainmapImageStream = SkMemoryStream::Make(imageData);
-        } else {
-            *outGainmapImageStream = SkMemoryStream::MakeCopy(imageData->data(), imageData->size());
-        }
-    }
-    *outInfo = info;
+    *info = gainmap_info;
+    *gainmapImageStream = SkMemoryStream::Make(gainmap_data);
     return true;
-}
-
-static bool get_gainmap_info(const SkJpegMarkerList& markerList,
-                             SkJpegSourceMgr* sourceMgr,
-                             SkGainmapInfo* info,
-                             std::unique_ptr<SkStream>* gainmapImageStream) {
-    // All non-ISO formats require XMP metadata. Extract it now.
-    std::unique_ptr<SkXmp> xmp = get_xmp_metadata(markerList);
-
-    // Let |base_image_info| be the HDRGM gainmap information found in the base image (if any).
-    SkGainmapInfo base_image_info;
-
-    // Set |base_image_has_hdrgm| to be true if the base image has HDRGM XMP metadata that includes
-    // the a Version 1.0 attribute.
-    const bool base_image_has_hdrgm = xmp && xmp->getGainmapInfoHDRGM(&base_image_info);
-
-    // Attempt to locate the gainmap from the container XMP.
-    size_t containerGainmapOffset = 0;
-    size_t containerGainmapSize = 0;
-    if (xmp && xmp->getContainerGainmapLocation(&containerGainmapOffset, &containerGainmapSize)) {
-        const auto& segments = sourceMgr->getAllSegments();
-        if (!segments.empty()) {
-            const auto& lastSegment = segments.back();
-            if (lastSegment.marker == kJpegMarkerEndOfImage) {
-                containerGainmapOffset += lastSegment.offset + kJpegMarkerCodeSize;
-            }
-        }
-    }
-
-    // Attempt to find MultiPicture parameters.
-    SkJpegSegment mpParamsSegment;
-    auto mpParams = find_mp_params(markerList, sourceMgr, &mpParamsSegment);
-
-    // First, search through the Multi-Picture images.
-    if (mpParams) {
-        for (size_t mpImageIndex = 1; mpImageIndex < mpParams->images.size(); ++mpImageIndex) {
-            size_t mpImageOffset = SkJpegMultiPictureParameters::GetAbsoluteOffset(
-                    mpParams->images[mpImageIndex].dataOffset, mpParamsSegment.offset);
-            size_t mpImageSize = mpParams->images[mpImageIndex].size;
-
-            if (extract_gainmap(sourceMgr,
-                                mpImageOffset,
-                                mpImageSize,
-                                base_image_has_hdrgm,
-                                info,
-                                gainmapImageStream)) {
-                // If the GContainer also suggested an offset and size, assert that we found the
-                // image that the GContainer suggested.
-                if (containerGainmapOffset) {
-                    SkASSERT(containerGainmapOffset == mpImageOffset);
-                    SkASSERT(containerGainmapSize == mpImageSize);
-                }
-                return true;
-            }
-        }
-    }
-
-    // Next, try the location suggested by the container XMP.
-    if (containerGainmapOffset) {
-        if (extract_gainmap(sourceMgr,
-                            containerGainmapOffset,
-                            containerGainmapSize,
-                            base_image_has_hdrgm,
-                            info,
-                            gainmapImageStream)) {
-            return true;
-        }
-        SkCodecPrintf("Failed to extract container-specified gainmap.\n");
-    }
-
-    return false;
-}
-
-bool SkJpegCodec::onGetGainmapInfo(SkGainmapInfo* info,
-                                   std::unique_ptr<SkStream>* gainmapImageStream) {
-    auto markerList = get_sk_marker_list(fDecoderMgr->dinfo());
-    return get_gainmap_info(markerList, fDecoderMgr->getSourceMgr(), info, gainmapImageStream);
-}
-
 #else
-bool SkJpegCodec::onGetGainmapInfo(SkGainmapInfo* info,
-                                   std::unique_ptr<SkStream>* gainmapImageStream) {
     return false;
-}
 #endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
-
-class SkJpegMetadataDecoderImpl : public SkJpegMetadataDecoder {
-public:
-    SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList) : fMarkerList(std::move(markerList)) {}
-
-    sk_sp<SkData> getExifMetadata(bool copyData) const override {
-        return read_metadata(fMarkerList,
-                             kExifMarker,
-                             kExifSig,
-                             sizeof(kExifSig),
-                             /*signaturePadding=*/1,
-                             /*bytesInIndex=*/0,
-                             copyData);
-    }
-
-    sk_sp<SkData> getICCProfileData(bool copyData) const override {
-        return read_metadata(fMarkerList,
-                             kICCMarker,
-                             kICCSig,
-                             sizeof(kICCSig),
-                             /*signaturePadding=*/0,
-                             kICCMarkerIndexSize,
-                             copyData);
-    }
-
-    bool mightHaveGainmapImage() const override {
-#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
-        // All supported gainmap formats require MPF. Reject images that do not have MPF.
-        return find_mp_params(fMarkerList, nullptr, nullptr) != nullptr;
-#else
-        return false;
-#endif
-    }
-
-    bool findGainmapImage(sk_sp<SkData> baseImageData,
-                          sk_sp<SkData>& outGainmapImageData,
-                          SkGainmapInfo& outGainmapInfo) override {
-#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
-        auto baseImageStream = SkMemoryStream::Make(baseImageData);
-        auto sourceMgr = SkJpegSourceMgr::Make(baseImageStream.get());
-        SkGainmapInfo gainmapInfo;
-        std::unique_ptr<SkStream> gainmapImageStream;
-        if (!get_gainmap_info(fMarkerList, sourceMgr.get(), &gainmapInfo, &gainmapImageStream)) {
-            return false;
-        }
-
-        // TODO(https://crbug.com/1404000): The function |get_gainmap_info| will always return an
-        // SkStream that is backed by an SkData. Change it to return that SkData, to avoid this
-        // re-extraction of the SkData.
-        SkASSERT(gainmapImageStream->getMemoryBase());
-        outGainmapImageData = SkData::MakeWithCopy(gainmapImageStream->getMemoryBase(),
-                                                   gainmapImageStream->getLength());
-        outGainmapInfo = gainmapInfo;
-        return true;
-#else
-        return false;
-#endif
-    }
-
-private:
-    SkJpegMarkerList fMarkerList;
-};
-
-std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(std::vector<Segment> segments) {
-    SkJpegMarkerList markerList;
-    for (const auto& segment : segments) {
-        markerList.emplace_back(segment.fMarker, segment.fData);
-    }
-    return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(markerList));
 }
 
 namespace SkJpegDecoder {
diff --git a/src/codec/SkJpegMetadataDecoderImpl.cpp b/src/codec/SkJpegMetadataDecoderImpl.cpp
new file mode 100644
index 0000000..921b886
--- /dev/null
+++ b/src/codec/SkJpegMetadataDecoderImpl.cpp
@@ -0,0 +1,456 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "src/codec/SkJpegMetadataDecoderImpl.h"
+
+#include "include/core/SkData.h"
+#include "include/private/base/SkTemplates.h"
+#include "src/codec/SkCodecPriv.h"
+#include "src/codec/SkJpegConstants.h"
+
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <utility>
+
+#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
+#include "include/core/SkStream.h"
+#include "include/private/SkGainmapInfo.h"
+#include "include/private/SkXmp.h"
+#include "src/codec/SkJpegMultiPicture.h"
+#include "src/codec/SkJpegSegmentScan.h"
+#include "src/codec/SkJpegSourceMgr.h"
+#include "src/codec/SkJpegXmp.h"
+#else
+struct SkGainmapInfo;
+#endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
+
+#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
+// Collect and parse the primary and extended XMP metadata.
+static std::unique_ptr<SkXmp> get_xmp_metadata(const SkJpegMarkerList& markerList) {
+    std::vector<sk_sp<SkData>> decoderApp1Params;
+    for (const auto& marker : markerList) {
+        if (marker.fMarker == kXMPMarker) {
+            decoderApp1Params.push_back(marker.fData);
+        }
+    }
+    return SkJpegMakeXmp(decoderApp1Params);
+}
+
+// Extract the SkJpegMultiPictureParameters from this image (if they exist). If |sourceMgr| and
+// |outMpParamsSegment| are non-nullptr, then also return the SkJpegSegment that the parameters came
+// from (and return nullptr if one cannot be found).
+static std::unique_ptr<SkJpegMultiPictureParameters> find_mp_params(
+        const SkJpegMarkerList& markerList,
+        SkJpegSourceMgr* sourceMgr,
+        SkJpegSegment* outMpParamsSegment) {
+    std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
+    size_t skippedSegmentCount = 0;
+
+    // Search though the libjpeg segments until we find a segment that parses as MP parameters. Keep
+    // track of how many segments with the MPF marker we skipped over to get there.
+    for (const auto& marker : markerList) {
+        if (marker.fMarker != kMpfMarker) {
+            continue;
+        }
+        mpParams = SkJpegMultiPictureParameters::Make(marker.fData);
+        if (mpParams) {
+            break;
+        }
+        ++skippedSegmentCount;
+    }
+    if (!mpParams) {
+        return nullptr;
+    }
+
+    // If |sourceMgr| is not specified, then do not try to find the SkJpegSegment.
+    if (!sourceMgr) {
+        SkASSERT(!outMpParamsSegment);
+        return mpParams;
+    }
+
+    // Now, find the SkJpegSegmentScanner segment that corresponds to the libjpeg marker.
+    // TODO(ccameron): It may be preferable to make SkJpegSourceMgr save segments with certain
+    // markers to avoid this strangeness.
+    for (const auto& segment : sourceMgr->getAllSegments()) {
+        if (segment.marker != kMpfMarker) {
+            continue;
+        }
+        if (skippedSegmentCount == 0) {
+            *outMpParamsSegment = segment;
+            return mpParams;
+        }
+        skippedSegmentCount--;
+    }
+    return nullptr;
+}
+
+// Attempt to extract a gainmap image from a specified offset and size within the decoder's stream.
+// Returns true only if the extracted gainmap image includes XMP metadata that specifies HDR gainmap
+// rendering parameters.
+static bool extract_gainmap(SkJpegSourceMgr* decoderSource,
+                            size_t offset,
+                            size_t size,
+                            bool base_image_has_hdrgm,
+                            SkGainmapInfo& outInfo,
+                            sk_sp<SkData>& outData) {
+    // Extract the SkData for this image.
+    bool imageDataWasCopied = false;
+    auto imageData = decoderSource->getSubsetData(offset, size, &imageDataWasCopied);
+    if (!imageData) {
+        SkCodecPrintf("Failed to extract MP image.\n");
+        return false;
+    }
+
+    // Scan through the image up to the StartOfScan. We'll be searching for the XMP metadata.
+    SkJpegSegmentScanner scan(kJpegMarkerStartOfScan);
+    scan.onBytes(imageData->data(), imageData->size());
+    if (scan.hadError() || !scan.isDone()) {
+        SkCodecPrintf("Failed to scan header of MP image.\n");
+        return false;
+    }
+
+    // Collect the potential XMP segments and build the XMP.
+    std::vector<sk_sp<SkData>> app1Params;
+    for (const auto& segment : scan.getSegments()) {
+        if (segment.marker != kXMPMarker) {
+            continue;
+        }
+        auto parameters = SkJpegSegmentScanner::GetParameters(imageData.get(), segment);
+        if (!parameters) {
+            continue;
+        }
+        app1Params.push_back(std::move(parameters));
+    }
+    auto xmp = SkJpegMakeXmp(app1Params);
+    if (!xmp) {
+        return false;
+    }
+
+    // Check if this image identifies itself as a gainmap.
+    bool did_populate_info = false;
+    SkGainmapInfo info;
+
+    // Check for HDRGM only if the base image specified hdrgm:Version="1.0".
+    did_populate_info = base_image_has_hdrgm && xmp->getGainmapInfoHDRGM(&info);
+
+    // Next, check HDRGainMap. This does not require anything specific from the base image.
+    if (!did_populate_info) {
+        did_populate_info = xmp->getGainmapInfoHDRGainMap(&info);
+    }
+
+    // If none of the formats identified itself as a gainmap and populated |info| then fail.
+    if (!did_populate_info) {
+        return false;
+    }
+
+    // This image is a gainmap.
+    outInfo = info;
+    if (imageDataWasCopied) {
+        outData = imageData;
+    } else {
+        outData = SkData::MakeWithCopy(imageData->data(), imageData->size());
+    }
+    return true;
+}
+
+static bool get_gainmap_info(const SkJpegMarkerList& markerList,
+                             SkJpegSourceMgr* sourceMgr,
+                             SkGainmapInfo& outInfo,
+                             sk_sp<SkData>& outData) {
+    // All non-ISO formats require XMP metadata. Extract it now.
+    std::unique_ptr<SkXmp> xmp = get_xmp_metadata(markerList);
+
+    // Let |base_image_info| be the HDRGM gainmap information found in the base image (if any).
+    SkGainmapInfo base_image_info;
+
+    // Set |base_image_has_hdrgm| to be true if the base image has HDRGM XMP metadata that includes
+    // the a Version 1.0 attribute.
+    const bool base_image_has_hdrgm = xmp && xmp->getGainmapInfoHDRGM(&base_image_info);
+
+    // Attempt to locate the gainmap from the container XMP.
+    size_t containerGainmapOffset = 0;
+    size_t containerGainmapSize = 0;
+    if (xmp && xmp->getContainerGainmapLocation(&containerGainmapOffset, &containerGainmapSize)) {
+        const auto& segments = sourceMgr->getAllSegments();
+        if (!segments.empty()) {
+            const auto& lastSegment = segments.back();
+            if (lastSegment.marker == kJpegMarkerEndOfImage) {
+                containerGainmapOffset += lastSegment.offset + kJpegMarkerCodeSize;
+            }
+        }
+    }
+
+    // Attempt to find MultiPicture parameters.
+    SkJpegSegment mpParamsSegment;
+    auto mpParams = find_mp_params(markerList, sourceMgr, &mpParamsSegment);
+
+    // First, search through the Multi-Picture images.
+    if (mpParams) {
+        for (size_t mpImageIndex = 1; mpImageIndex < mpParams->images.size(); ++mpImageIndex) {
+            size_t mpImageOffset = SkJpegMultiPictureParameters::GetAbsoluteOffset(
+                    mpParams->images[mpImageIndex].dataOffset, mpParamsSegment.offset);
+            size_t mpImageSize = mpParams->images[mpImageIndex].size;
+
+            if (extract_gainmap(sourceMgr,
+                                mpImageOffset,
+                                mpImageSize,
+                                base_image_has_hdrgm,
+                                outInfo,
+                                outData)) {
+                // If the GContainer also suggested an offset and size, assert that we found the
+                // image that the GContainer suggested.
+                if (containerGainmapOffset) {
+                    SkASSERT(containerGainmapOffset == mpImageOffset);
+                    SkASSERT(containerGainmapSize == mpImageSize);
+                }
+                return true;
+            }
+        }
+    }
+
+    // Next, try the location suggested by the container XMP.
+    if (containerGainmapOffset) {
+        if (extract_gainmap(sourceMgr,
+                            containerGainmapOffset,
+                            containerGainmapSize,
+                            base_image_has_hdrgm,
+                            outInfo,
+                            outData)) {
+            return true;
+        }
+        SkCodecPrintf("Failed to extract container-specified gainmap.\n");
+    }
+
+    return false;
+}
+#endif  // SK_CODEC_DECODES_JPEG_GAINMAPS
+
+/**
+ * Return true if the specified SkJpegMarker has marker |targetMarker| and begins with the specified
+ * signature.
+ */
+static bool marker_has_signature(const SkJpegMarker& marker,
+                                 const uint32_t targetMarker,
+                                 const uint8_t* signature,
+                                 size_t signatureSize) {
+    if (targetMarker != marker.fMarker) {
+        return false;
+    }
+    if (marker.fData->size() <= signatureSize) {
+        return false;
+    }
+    if (memcmp(marker.fData->bytes(), signature, signatureSize) != 0) {
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Return metadata with a specific marker and signature.
+ *
+ * Search for segments that start with the specified targetMarker, followed by the specified
+ * signature, followed by (optional) padding.
+ *
+ * Some types of metadata (e.g, ICC profiles) are too big to fit into a single segment's data (which
+ * is limited to 64k), and come in multiple parts. For this type of data, bytesInIndex is >0. After
+ * the signature comes bytesInIndex bytes (big endian) for the index of the segment's part, followed
+ * by bytesInIndex bytes (big endian) for the total number of parts. If all parts are present,
+ * stitch them together and return the combined result. Return failure if parts are absent, there
+ * are duplicate parts, or parts disagree on the total number of parts.
+ *
+ * Visually, each segment is:
+ * [|signatureSize| bytes containing |signature|]
+ * [|signaturePadding| bytes that are unexamined]
+ * [|bytesInIndex] bytes listing the segment index for multi-segment metadata]
+ * [|bytesInIndex] bytes listing the segment count for multi-segment metadata]
+ * [the returned data]
+ *
+ * If alwaysCopyData is true, then return a copy of the data. If alwaysCopyData is false, then
+ * return a direct reference to the data pointed to by dinfo, if possible.
+ */
+static sk_sp<SkData> read_metadata(const SkJpegMarkerList& markerList,
+                                   const uint32_t targetMarker,
+                                   const uint8_t* signature,
+                                   size_t signatureSize,
+                                   size_t signaturePadding,
+                                   size_t bytesInIndex,
+                                   bool alwaysCopyData) {
+    // Compute the total size of the entire header (signature plus padding plus index plus count),
+    // since we'll use it often.
+    const size_t headerSize = signatureSize + signaturePadding + 2 * bytesInIndex;
+
+    // A map from part index to the data in each part.
+    std::vector<sk_sp<SkData>> parts;
+
+    // Running total of number of data in all parts.
+    size_t partsTotalSize = 0;
+
+    // Running total number of parts found.
+    uint32_t foundPartCount = 0;
+
+    // The expected number of parts (initialized at the first part we encounter).
+    uint32_t expectedPartCount = 0;
+
+    // Iterate through the image's segments.
+    for (const auto& marker : markerList) {
+        // Skip segments that don't have the right marker or signature.
+        if (!marker_has_signature(marker, targetMarker, signature, signatureSize)) {
+            continue;
+        }
+
+        // Skip segments that are too small to include the index and count.
+        const size_t dataLength = marker.fData->size();
+        if (dataLength <= headerSize) {
+            continue;
+        }
+
+        // Read this part's index and count as big-endian (if they are present, otherwise hard-code
+        // them to 1).
+        const uint8_t* data = marker.fData->bytes();
+        uint32_t partIndex = 0;
+        uint32_t partCount = 0;
+        if (bytesInIndex == 0) {
+            partIndex = 1;
+            partCount = 1;
+        } else {
+            for (size_t i = 0; i < bytesInIndex; ++i) {
+                const size_t offset = signatureSize + signaturePadding;
+                partIndex = (partIndex << 8) + data[offset + i];
+                partCount = (partCount << 8) + data[offset + bytesInIndex + i];
+            }
+        }
+
+        // A part count of 0 is invalid.
+        if (!partCount) {
+            SkCodecPrintf("Invalid marker part count zero\n");
+            return nullptr;
+        }
+
+        // The indices must in the range 1, ..., count.
+        if (partIndex <= 0 || partIndex > partCount) {
+            SkCodecPrintf("Invalid marker index %u for count %u\n", partIndex, partCount);
+            return nullptr;
+        }
+
+        // If this is the first marker we've encountered set the expected part count to its count.
+        if (expectedPartCount == 0) {
+            expectedPartCount = partCount;
+            parts.resize(expectedPartCount);
+        }
+
+        // If this does not match the expected part count, then fail.
+        if (partCount != expectedPartCount) {
+            SkCodecPrintf("Conflicting marker counts %u vs %u\n", partCount, expectedPartCount);
+            return nullptr;
+        }
+
+        // Make an SkData directly referencing the decoder's data for this part.
+        auto partData = SkData::MakeWithoutCopy(data + headerSize, dataLength - headerSize);
+
+        // Fail if duplicates are found.
+        if (parts[partIndex - 1]) {
+            SkCodecPrintf("Duplicate parts for index %u of %u\n", partIndex, expectedPartCount);
+            return nullptr;
+        }
+
+        // Save part in the map.
+        partsTotalSize += partData->size();
+        parts[partIndex - 1] = std::move(partData);
+        foundPartCount += 1;
+
+        // Stop as soon as we find all of the parts.
+        if (foundPartCount == expectedPartCount) {
+            break;
+        }
+    }
+
+    // Return nullptr if we don't find the data (this is not an error).
+    if (expectedPartCount == 0) {
+        return nullptr;
+    }
+
+    // Fail if we don't have all of the parts.
+    if (foundPartCount != expectedPartCount) {
+        SkCodecPrintf("Incomplete set of markers (expected %u got %u)\n",
+                      expectedPartCount,
+                      foundPartCount);
+        return nullptr;
+    }
+
+    // Return a direct reference to the data if there is only one part and we're allowed to.
+    if (!alwaysCopyData && expectedPartCount == 1) {
+        return std::move(parts[0]);
+    }
+
+    // Copy all of the markers and stitch them together.
+    auto result = SkData::MakeUninitialized(partsTotalSize);
+    void* copyDest = result->writable_data();
+    for (const auto& part : parts) {
+        memcpy(copyDest, part->data(), part->size());
+        copyDest = SkTAddOffset<void>(copyDest, part->size());
+    }
+    return result;
+}
+
+SkJpegMetadataDecoderImpl::SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList)
+        : fMarkerList(std::move(markerList)) {}
+
+sk_sp<SkData> SkJpegMetadataDecoderImpl::getExifMetadata(bool copyData) const {
+    return read_metadata(fMarkerList,
+                         kExifMarker,
+                         kExifSig,
+                         sizeof(kExifSig),
+                         /*signaturePadding=*/1,
+                         /*bytesInIndex=*/0,
+                         copyData);
+}
+
+sk_sp<SkData> SkJpegMetadataDecoderImpl::getICCProfileData(bool copyData) const {
+    return read_metadata(fMarkerList,
+                         kICCMarker,
+                         kICCSig,
+                         sizeof(kICCSig),
+                         /*signaturePadding=*/0,
+                         kICCMarkerIndexSize,
+                         copyData);
+}
+
+bool SkJpegMetadataDecoderImpl::mightHaveGainmapImage() const {
+#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
+    // All supported gainmap formats require MPF. Reject images that do not have MPF.
+    return find_mp_params(fMarkerList, nullptr, nullptr) != nullptr;
+#else
+    return false;
+#endif
+}
+
+bool SkJpegMetadataDecoderImpl::findGainmapImage(SkJpegSourceMgr* sourceMgr,
+                                                 sk_sp<SkData>& outGainmapImageData,
+                                                 SkGainmapInfo& outGainmapInfo) const {
+#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
+    return get_gainmap_info(fMarkerList, sourceMgr, outGainmapInfo, outGainmapImageData);
+#else
+    return false;
+#endif
+}
+
+bool SkJpegMetadataDecoderImpl::findGainmapImage(sk_sp<SkData> baseImageData,
+                                                 sk_sp<SkData>& outGainmapImageData,
+                                                 SkGainmapInfo& outGainmapInfo) {
+#ifdef SK_CODEC_DECODES_JPEG_GAINMAPS
+    auto baseImageStream = SkMemoryStream::Make(baseImageData);
+    auto sourceMgr = SkJpegSourceMgr::Make(baseImageStream.get());
+    return findGainmapImage(sourceMgr.get(), outGainmapImageData, outGainmapInfo);
+#else
+    return false;
+#endif
+}
+
+std::unique_ptr<SkJpegMetadataDecoder> SkJpegMetadataDecoder::Make(std::vector<Segment> segments) {
+    return std::make_unique<SkJpegMetadataDecoderImpl>(std::move(segments));
+}
diff --git a/src/codec/SkJpegMetadataDecoderImpl.h b/src/codec/SkJpegMetadataDecoderImpl.h
new file mode 100644
index 0000000..77ac9ec
--- /dev/null
+++ b/src/codec/SkJpegMetadataDecoderImpl.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2024 Google LLC
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef SkJpegMetadataDecoderImpl_DEFINED
+#define SkJpegMetadataDecoderImpl_DEFINED
+
+#include "include/core/SkRefCnt.h"
+#include "include/private/SkJpegMetadataDecoder.h"
+
+#include <vector>
+
+class SkData;
+class SkJpegSourceMgr;
+struct SkGainmapInfo;
+
+using SkJpegMarker = SkJpegMetadataDecoder::Segment;
+using SkJpegMarkerList = std::vector<SkJpegMarker>;
+
+class SkJpegMetadataDecoderImpl : public SkJpegMetadataDecoder {
+public:
+    SkJpegMetadataDecoderImpl(SkJpegMarkerList markerList);
+    bool findGainmapImage(SkJpegSourceMgr* sourceMgr,
+                          sk_sp<SkData>& outGainmapImageData,
+                          SkGainmapInfo& outGainmapInfo) const;
+
+    // SkJpegMetadataDecoder implementation:
+    sk_sp<SkData> getExifMetadata(bool copyData) const override;
+    sk_sp<SkData> getICCProfileData(bool copyData) const override;
+    bool mightHaveGainmapImage() const override;
+    bool findGainmapImage(sk_sp<SkData> baseImageData,
+                          sk_sp<SkData>& outGainmapImageData,
+                          SkGainmapInfo& outGainmapInfo) override;
+
+private:
+    SkJpegMarkerList fMarkerList;
+};
+
+#endif  // SkJpegMetadataDecoderImpl_DEFINED