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