xref: /aosp_15_r20/external/skia/src/codec/SkJpegXmp.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2023 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker  *
4*c8dee2aaSAndroid Build Coastguard Worker  * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker  * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker  */
7*c8dee2aaSAndroid Build Coastguard Worker 
8*c8dee2aaSAndroid Build Coastguard Worker #include "src/codec/SkJpegXmp.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/SkGainmapInfo.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/utils/SkParse.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "src/codec/SkCodecPriv.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "src/codec/SkJpegConstants.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkMD5.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "src/xml/SkDOM.h"
16*c8dee2aaSAndroid Build Coastguard Worker 
17*c8dee2aaSAndroid Build Coastguard Worker #include <string>
18*c8dee2aaSAndroid Build Coastguard Worker #include <tuple>
19*c8dee2aaSAndroid Build Coastguard Worker 
20*c8dee2aaSAndroid Build Coastguard Worker constexpr size_t kGuidAsciiSize = 32;
21*c8dee2aaSAndroid Build Coastguard Worker 
22*c8dee2aaSAndroid Build Coastguard Worker /*
23*c8dee2aaSAndroid Build Coastguard Worker  * Extract standard XMP metadata. The decoderApp1Params must outlive the returned SkData.
24*c8dee2aaSAndroid Build Coastguard Worker  *
25*c8dee2aaSAndroid Build Coastguard Worker  * See XMP Specification Part 3: Storage in files, Section 1.1.3: JPEG.
26*c8dee2aaSAndroid Build Coastguard Worker  */
read_xmp_standard(const std::vector<sk_sp<SkData>> & decoderApp1Params)27*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkData> read_xmp_standard(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
28*c8dee2aaSAndroid Build Coastguard Worker     constexpr size_t kSigSize = sizeof(kXMPStandardSig);
29*c8dee2aaSAndroid Build Coastguard Worker     // Iterate through the image's segments.
30*c8dee2aaSAndroid Build Coastguard Worker     for (const auto& params : decoderApp1Params) {
31*c8dee2aaSAndroid Build Coastguard Worker         // Skip segments that don't have the right marker, signature, or are too small.
32*c8dee2aaSAndroid Build Coastguard Worker         if (params->size() <= kSigSize) {
33*c8dee2aaSAndroid Build Coastguard Worker             continue;
34*c8dee2aaSAndroid Build Coastguard Worker         }
35*c8dee2aaSAndroid Build Coastguard Worker         if (memcmp(params->bytes(), kXMPStandardSig, kSigSize) != 0) {
36*c8dee2aaSAndroid Build Coastguard Worker             continue;
37*c8dee2aaSAndroid Build Coastguard Worker         }
38*c8dee2aaSAndroid Build Coastguard Worker         return SkData::MakeWithoutCopy(params->bytes() + kSigSize, params->size() - kSigSize);
39*c8dee2aaSAndroid Build Coastguard Worker     }
40*c8dee2aaSAndroid Build Coastguard Worker     return nullptr;
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker 
43*c8dee2aaSAndroid Build Coastguard Worker /*
44*c8dee2aaSAndroid Build Coastguard Worker  * Extract and validate extended XMP metadata.
45*c8dee2aaSAndroid Build Coastguard Worker  *
46*c8dee2aaSAndroid Build Coastguard Worker  * See XMP Specification Part 3: Storage in files, Section 1.1.3.1: Extended XMP in JPEG:
47*c8dee2aaSAndroid Build Coastguard Worker  * Each chunk is written into the JPEG file within a separate APP1 marker segment. Each ExtendedXMP
48*c8dee2aaSAndroid Build Coastguard Worker  * marker segment contains:
49*c8dee2aaSAndroid Build Coastguard Worker  *   - A null-terminated signature string
50*c8dee2aaSAndroid Build Coastguard Worker  *   - A 128-bit GUID stored as a 32-byte ASCII hex string, capital A-F, no null termination. The
51*c8dee2aaSAndroid Build Coastguard Worker  *     GUID is a 128-bit MD5 digest of the full ExtendedXMP serialization.
52*c8dee2aaSAndroid Build Coastguard Worker  *   - The full length of the ExtendedXMP serialization as a 32-bit unsigned integer.
53*c8dee2aaSAndroid Build Coastguard Worker  *   - The offset of this portion as a 32-bit unsigned integer.
54*c8dee2aaSAndroid Build Coastguard Worker  *   - The portion of the ExtendedXMP
55*c8dee2aaSAndroid Build Coastguard Worker  */
read_xmp_extended(const std::vector<sk_sp<SkData>> & decoderApp1Params,const char * guidAscii)56*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<SkData> read_xmp_extended(const std::vector<sk_sp<SkData>>& decoderApp1Params,
57*c8dee2aaSAndroid Build Coastguard Worker                                        const char* guidAscii) {
58*c8dee2aaSAndroid Build Coastguard Worker     constexpr size_t kSigSize = sizeof(kXMPExtendedSig);
59*c8dee2aaSAndroid Build Coastguard Worker     constexpr size_t kFullLengthSize = 4;
60*c8dee2aaSAndroid Build Coastguard Worker     constexpr size_t kOffsetSize = 4;
61*c8dee2aaSAndroid Build Coastguard Worker     constexpr size_t kHeaderSize = kSigSize + kGuidAsciiSize + kFullLengthSize + kOffsetSize;
62*c8dee2aaSAndroid Build Coastguard Worker 
63*c8dee2aaSAndroid Build Coastguard Worker     // Validate the provided ASCII guid.
64*c8dee2aaSAndroid Build Coastguard Worker     if (strlen(guidAscii) != kGuidAsciiSize) {
65*c8dee2aaSAndroid Build Coastguard Worker         SkCodecPrintf("Invalid ASCII GUID size.\n");
66*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
67*c8dee2aaSAndroid Build Coastguard Worker     }
68*c8dee2aaSAndroid Build Coastguard Worker     SkMD5::Digest guidAsDigest;
69*c8dee2aaSAndroid Build Coastguard Worker     for (size_t i = 0; i < kGuidAsciiSize; ++i) {
70*c8dee2aaSAndroid Build Coastguard Worker         uint8_t digit = 0;
71*c8dee2aaSAndroid Build Coastguard Worker         if (guidAscii[i] >= '0' && guidAscii[i] <= '9') {
72*c8dee2aaSAndroid Build Coastguard Worker             digit = guidAscii[i] - '0';
73*c8dee2aaSAndroid Build Coastguard Worker         } else if (guidAscii[i] >= 'A' && guidAscii[i] <= 'F') {
74*c8dee2aaSAndroid Build Coastguard Worker             digit = guidAscii[i] - 'A' + 10;
75*c8dee2aaSAndroid Build Coastguard Worker         } else {
76*c8dee2aaSAndroid Build Coastguard Worker             SkCodecPrintf("GUID is not upper-case hex.\n");
77*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
78*c8dee2aaSAndroid Build Coastguard Worker         }
79*c8dee2aaSAndroid Build Coastguard Worker         if (i % 2 == 0) {
80*c8dee2aaSAndroid Build Coastguard Worker             guidAsDigest.data[i / 2] = 16 * digit;
81*c8dee2aaSAndroid Build Coastguard Worker         } else {
82*c8dee2aaSAndroid Build Coastguard Worker             guidAsDigest.data[i / 2] += digit;
83*c8dee2aaSAndroid Build Coastguard Worker         }
84*c8dee2aaSAndroid Build Coastguard Worker     }
85*c8dee2aaSAndroid Build Coastguard Worker 
86*c8dee2aaSAndroid Build Coastguard Worker     // Iterate through the image's segments.
87*c8dee2aaSAndroid Build Coastguard Worker     uint32_t fullLength = 0;
88*c8dee2aaSAndroid Build Coastguard Worker     using Part = std::tuple<uint32_t, sk_sp<SkData>>;
89*c8dee2aaSAndroid Build Coastguard Worker     std::vector<Part> parts;
90*c8dee2aaSAndroid Build Coastguard Worker     for (const auto& params : decoderApp1Params) {
91*c8dee2aaSAndroid Build Coastguard Worker         // Skip segments that don't have the right marker, signature, or are too small.
92*c8dee2aaSAndroid Build Coastguard Worker         if (params->size() <= kHeaderSize) {
93*c8dee2aaSAndroid Build Coastguard Worker             continue;
94*c8dee2aaSAndroid Build Coastguard Worker         }
95*c8dee2aaSAndroid Build Coastguard Worker         if (memcmp(params->bytes(), kXMPExtendedSig, kSigSize) != 0) {
96*c8dee2aaSAndroid Build Coastguard Worker             continue;
97*c8dee2aaSAndroid Build Coastguard Worker         }
98*c8dee2aaSAndroid Build Coastguard Worker 
99*c8dee2aaSAndroid Build Coastguard Worker         // Ignore parts that do not match the expected GUID.
100*c8dee2aaSAndroid Build Coastguard Worker         const uint8_t* partGuidAscii = params->bytes() + kSigSize;
101*c8dee2aaSAndroid Build Coastguard Worker         if (memcmp(guidAscii, partGuidAscii, kGuidAsciiSize) != 0) {
102*c8dee2aaSAndroid Build Coastguard Worker             SkCodecPrintf("Ignoring unexpected GUID.\n");
103*c8dee2aaSAndroid Build Coastguard Worker             continue;
104*c8dee2aaSAndroid Build Coastguard Worker         }
105*c8dee2aaSAndroid Build Coastguard Worker 
106*c8dee2aaSAndroid Build Coastguard Worker         // Read the full length and the offset for this part.
107*c8dee2aaSAndroid Build Coastguard Worker         uint32_t partFullLength = 0;
108*c8dee2aaSAndroid Build Coastguard Worker         uint32_t partOffset = 0;
109*c8dee2aaSAndroid Build Coastguard Worker         const uint8_t* partFullLengthBytes = params->bytes() + kSigSize + kGuidAsciiSize;
110*c8dee2aaSAndroid Build Coastguard Worker         const uint8_t* partOffsetBytes =
111*c8dee2aaSAndroid Build Coastguard Worker                 params->bytes() + kSigSize + kGuidAsciiSize + kFullLengthSize;
112*c8dee2aaSAndroid Build Coastguard Worker         for (size_t i = 0; i < 4; ++i) {
113*c8dee2aaSAndroid Build Coastguard Worker             partFullLength *= 256;
114*c8dee2aaSAndroid Build Coastguard Worker             partOffset *= 256;
115*c8dee2aaSAndroid Build Coastguard Worker             partFullLength += partFullLengthBytes[i];
116*c8dee2aaSAndroid Build Coastguard Worker             partOffset += partOffsetBytes[i];
117*c8dee2aaSAndroid Build Coastguard Worker         }
118*c8dee2aaSAndroid Build Coastguard Worker 
119*c8dee2aaSAndroid Build Coastguard Worker         // If this is the first part, set our global full length size.
120*c8dee2aaSAndroid Build Coastguard Worker         if (parts.empty()) {
121*c8dee2aaSAndroid Build Coastguard Worker             fullLength = partFullLength;
122*c8dee2aaSAndroid Build Coastguard Worker         }
123*c8dee2aaSAndroid Build Coastguard Worker 
124*c8dee2aaSAndroid Build Coastguard Worker         // Ensure all parts agree on the full length.
125*c8dee2aaSAndroid Build Coastguard Worker         if (partFullLength != fullLength) {
126*c8dee2aaSAndroid Build Coastguard Worker             SkCodecPrintf("Multiple parts had different total lengths.\n");
127*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
128*c8dee2aaSAndroid Build Coastguard Worker         }
129*c8dee2aaSAndroid Build Coastguard Worker 
130*c8dee2aaSAndroid Build Coastguard Worker         // Add it to the list.
131*c8dee2aaSAndroid Build Coastguard Worker         auto partData = SkData::MakeWithoutCopy(params->bytes() + kHeaderSize,
132*c8dee2aaSAndroid Build Coastguard Worker                                                 params->size() - kHeaderSize);
133*c8dee2aaSAndroid Build Coastguard Worker         parts.push_back({partOffset, partData});
134*c8dee2aaSAndroid Build Coastguard Worker     }
135*c8dee2aaSAndroid Build Coastguard Worker     if (parts.empty() || fullLength == 0) {
136*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
137*c8dee2aaSAndroid Build Coastguard Worker     }
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker     // Sort the list of parts by offset.
140*c8dee2aaSAndroid Build Coastguard Worker     std::sort(parts.begin(), parts.end(), [](const Part& a, const Part& b) {
141*c8dee2aaSAndroid Build Coastguard Worker         return std::get<0>(a) < std::get<0>(b);
142*c8dee2aaSAndroid Build Coastguard Worker     });
143*c8dee2aaSAndroid Build Coastguard Worker 
144*c8dee2aaSAndroid Build Coastguard Worker     // Stitch the parts together. Fail if we find that they are not contiguous.
145*c8dee2aaSAndroid Build Coastguard Worker     auto xmpExtendedData = SkData::MakeUninitialized(fullLength);
146*c8dee2aaSAndroid Build Coastguard Worker     uint8_t* xmpExtendedBase = reinterpret_cast<uint8_t*>(xmpExtendedData->writable_data());
147*c8dee2aaSAndroid Build Coastguard Worker     uint8_t* xmpExtendedCurrent = xmpExtendedBase;
148*c8dee2aaSAndroid Build Coastguard Worker     SkMD5 md5;
149*c8dee2aaSAndroid Build Coastguard Worker     for (const auto& part : parts) {
150*c8dee2aaSAndroid Build Coastguard Worker         uint32_t currentOffset = static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase);
151*c8dee2aaSAndroid Build Coastguard Worker         uint32_t partOffset = std::get<0>(part);
152*c8dee2aaSAndroid Build Coastguard Worker         const sk_sp<SkData>& partData = std::get<1>(part);
153*c8dee2aaSAndroid Build Coastguard Worker         // Make sure the data is contiguous and doesn't overflow the buffer.
154*c8dee2aaSAndroid Build Coastguard Worker         if (partOffset != currentOffset) {
155*c8dee2aaSAndroid Build Coastguard Worker             SkCodecPrintf("XMP extension parts not contiguous\n");
156*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
157*c8dee2aaSAndroid Build Coastguard Worker         }
158*c8dee2aaSAndroid Build Coastguard Worker         if (partData->size() > fullLength - currentOffset) {
159*c8dee2aaSAndroid Build Coastguard Worker             SkCodecPrintf("XMP extension parts overflow\n");
160*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
161*c8dee2aaSAndroid Build Coastguard Worker         }
162*c8dee2aaSAndroid Build Coastguard Worker         memcpy(xmpExtendedCurrent, partData->data(), partData->size());
163*c8dee2aaSAndroid Build Coastguard Worker         xmpExtendedCurrent += partData->size();
164*c8dee2aaSAndroid Build Coastguard Worker     }
165*c8dee2aaSAndroid Build Coastguard Worker     // Make sure we wrote the full buffer.
166*c8dee2aaSAndroid Build Coastguard Worker     if (static_cast<uint32_t>(xmpExtendedCurrent - xmpExtendedBase) != fullLength) {
167*c8dee2aaSAndroid Build Coastguard Worker         SkCodecPrintf("XMP extension did not match full length.\n");
168*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
169*c8dee2aaSAndroid Build Coastguard Worker     }
170*c8dee2aaSAndroid Build Coastguard Worker 
171*c8dee2aaSAndroid Build Coastguard Worker     // Make sure the MD5 hash of the extended data matched the GUID.
172*c8dee2aaSAndroid Build Coastguard Worker     md5.write(xmpExtendedData->data(), xmpExtendedData->size());
173*c8dee2aaSAndroid Build Coastguard Worker     if (md5.finish() != guidAsDigest) {
174*c8dee2aaSAndroid Build Coastguard Worker         SkCodecPrintf("XMP extension did not hash to GUID.\n");
175*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
176*c8dee2aaSAndroid Build Coastguard Worker     }
177*c8dee2aaSAndroid Build Coastguard Worker 
178*c8dee2aaSAndroid Build Coastguard Worker     return xmpExtendedData;
179*c8dee2aaSAndroid Build Coastguard Worker }
180*c8dee2aaSAndroid Build Coastguard Worker 
SkJpegMakeXmp(const std::vector<sk_sp<SkData>> & decoderApp1Params)181*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkXmp> SkJpegMakeXmp(const std::vector<sk_sp<SkData>>& decoderApp1Params) {
182*c8dee2aaSAndroid Build Coastguard Worker     auto xmpStandard = read_xmp_standard(decoderApp1Params);
183*c8dee2aaSAndroid Build Coastguard Worker     if (!xmpStandard) {
184*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
185*c8dee2aaSAndroid Build Coastguard Worker     }
186*c8dee2aaSAndroid Build Coastguard Worker 
187*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<SkXmp> xmp = SkXmp::Make(xmpStandard);
188*c8dee2aaSAndroid Build Coastguard Worker     if (!xmp) {
189*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
190*c8dee2aaSAndroid Build Coastguard Worker     }
191*c8dee2aaSAndroid Build Coastguard Worker 
192*c8dee2aaSAndroid Build Coastguard Worker     // Extract the GUID (the MD5 hash) of the extended metadata.
193*c8dee2aaSAndroid Build Coastguard Worker     const char* extendedGuid = xmp->getExtendedXmpGuid();
194*c8dee2aaSAndroid Build Coastguard Worker     if (!extendedGuid) {
195*c8dee2aaSAndroid Build Coastguard Worker         return xmp;
196*c8dee2aaSAndroid Build Coastguard Worker     }
197*c8dee2aaSAndroid Build Coastguard Worker 
198*c8dee2aaSAndroid Build Coastguard Worker     // Extract and validate the extended metadata from the JPEG structure.
199*c8dee2aaSAndroid Build Coastguard Worker     auto xmpExtended = read_xmp_extended(decoderApp1Params, extendedGuid);
200*c8dee2aaSAndroid Build Coastguard Worker     if (!xmpExtended) {
201*c8dee2aaSAndroid Build Coastguard Worker         SkCodecPrintf("Extended XMP was indicated but failed to read or validate.\n");
202*c8dee2aaSAndroid Build Coastguard Worker         return xmp;
203*c8dee2aaSAndroid Build Coastguard Worker     }
204*c8dee2aaSAndroid Build Coastguard Worker 
205*c8dee2aaSAndroid Build Coastguard Worker     return SkXmp::Make(xmpStandard, xmpExtended);
206*c8dee2aaSAndroid Build Coastguard Worker }
207