/* * Copyright 2023 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "src/codec/SkJpegMultiPicture.h" #include "include/core/SkData.h" #include "include/core/SkStream.h" #include "src/codec/SkCodecPriv.h" #include "src/codec/SkJpegConstants.h" #include "src/codec/SkJpegSegmentScan.h" #include "src/core/SkEndian.h" #include constexpr size_t kMpEndianSize = 4; constexpr uint8_t kMpLittleEndian[kMpEndianSize] = {0x49, 0x49, 0x2A, 0x00}; constexpr uint8_t kMpBigEndian[kMpEndianSize] = {0x4D, 0x4D, 0x00, 0x2A}; constexpr uint16_t kTypeLong = 0x4; constexpr uint16_t kTypeUndefined = 0x7; constexpr uint32_t kTagSize = 12; constexpr uint32_t kTagSerializedCount = 3; constexpr uint16_t kVersionTag = 0xB000; constexpr uint16_t kVersionType = kTypeUndefined; constexpr uint32_t kVersionCount = 4; constexpr size_t kVersionSize = 4; constexpr uint8_t kVersionExpected[kVersionSize] = {'0', '1', '0', '0'}; constexpr uint16_t kNumberOfImagesTag = 0xB001; constexpr uint16_t kNumberOfImagesType = kTypeLong; constexpr uint32_t kNumberOfImagesCount = 1; constexpr uint16_t kMPEntryTag = 0xB002; constexpr uint16_t kMPEntryType = kTypeUndefined; constexpr uint32_t kMPEntrySize = 16; constexpr uint32_t kMPEntryAttributeFormatMask = 0x7000000; constexpr uint32_t kMPEntryAttributeFormatJpeg = 0x0000000; constexpr uint32_t kMPEntryAttributeTypeMask = 0xFFFFFF; constexpr uint32_t kMPEntryAttributeTypePrimary = 0x030000; constexpr uint16_t kIndividualImageUniqueIDTag = 0xB003; constexpr uint32_t kIndividualImageUniqueIDSize = 33; constexpr uint16_t kTotalNumberCapturedFramesTag = 0xB004; constexpr uint32_t kTotalNumberCaptureFramesCount = 1; // Helper macro for SkJpegMultiPictureParameters::Make. Define the indicated variable VAR of type // TYPE, and read it from the stream, performing any endian-ness conversions as needed. Also define // the variable VAR##Bytes with the raw bytes (with no endian-ness conversion applied). If any // errors are encountered, then return nullptr. The last void line is present to suppress unused // variable warnings for parameters that we don't use. #define DEFINE_AND_READ_UINT(TYPE, VAR) \ TYPE VAR = 0; \ uint8_t VAR##Bytes[sizeof(TYPE)] = {0}; \ { \ if (!stream->read(VAR##Bytes, sizeof(TYPE))) { \ return nullptr; \ } \ for (size_t VAR##i = 0; VAR##i < sizeof(TYPE); ++VAR##i) { \ VAR *= 256; \ VAR += VAR##Bytes[streamIsBigEndian ? VAR##i : (sizeof(TYPE) - VAR##i - 1)]; \ } \ } \ (void)VAR std::unique_ptr SkJpegMultiPictureParameters::Make( const sk_sp& segmentParameters) { // Read the MP Format identifier starting after the APP2 Field Length. See Figure 4 of CIPA // DC-x007-2009. if (segmentParameters->size() < sizeof(kMpfSig)) { return nullptr; } if (memcmp(segmentParameters->data(), kMpfSig, sizeof(kMpfSig)) != 0) { return nullptr; } std::unique_ptr stream = SkMemoryStream::MakeDirect(segmentParameters->bytes() + sizeof(kMpfSig), segmentParameters->size() - sizeof(kMpfSig)); // The rest of this function reads the structure described in Figure 6 of CIPA DC-x007-2009. // Determine the endianness of the values in the structure. See Figure 5 (MP endian tag // structure). bool streamIsBigEndian = false; { uint8_t endianTag[kMpEndianSize] = {0}; if (!stream->read(endianTag, kMpEndianSize)) { SkCodecPrintf("Failed to read MP endian tag.\n"); return nullptr; } if (!memcmp(endianTag, kMpBigEndian, kMpEndianSize)) { streamIsBigEndian = true; } else if (!memcmp(endianTag, kMpLittleEndian, kMpEndianSize)) { streamIsBigEndian = false; } else { SkCodecPrintf("MP endian tag was invalid.\n"); return nullptr; } } // Seek to the Index Image File Directory (Index IFD). DEFINE_AND_READ_UINT(uint32_t, indexIfdOffset); if (stream->getPosition() < indexIfdOffset) { SkCodecPrintf("MP Index IFD offset moves backwards.\n"); return nullptr; } if (!stream->seek(indexIfdOffset)) { SkCodecPrintf("Failed to seek to MPF IFD.\n"); return nullptr; } // Read the number of tags in the Index IFD. See Table 3 (MP Index IFD Tags) for a description // of all possible tags. DEFINE_AND_READ_UINT(uint16_t, tagCount); // We will extract the number of images from the tags. uint32_t numberOfImages = 0; // The offset to the MP entries. Zero is an invalid value. uint32_t mpEntryOffset = 0; // The MP Index IFD tags shall be specified in the order of their tag IDs (text from // section 5.2.3), so keep track of the previous tag id read. uint16_t previousTagId = 0; for (uint16_t tagIndex = 0; tagIndex < tagCount; ++tagIndex) { DEFINE_AND_READ_UINT(uint16_t, tagId); DEFINE_AND_READ_UINT(uint16_t, type); DEFINE_AND_READ_UINT(uint32_t, count); DEFINE_AND_READ_UINT(uint32_t, value); if (previousTagId >= tagId) { SkCodecPrintf("MPF tags not in order.\n"); return nullptr; } previousTagId = tagId; switch (tagId) { case kVersionTag: // See 5.2.3.1: MP Format Version. if (memcmp(valueBytes, kVersionExpected, kVersionSize) != 0) { SkCodecPrintf("Version value is not 0100.\n"); return nullptr; } if (count != kVersionCount) { SkCodecPrintf("Version count not 4.\n"); return nullptr; } break; case kNumberOfImagesTag: // See 5.2.3.2: Number of Images. numberOfImages = value; if (type != kTypeLong) { SkCodecPrintf("Invalid Total Number of Captured Frames type.\n"); return nullptr; } if (numberOfImages < 1) { SkCodecPrintf("Invalid number of images.\n"); return nullptr; } break; case kMPEntryTag: { // See 5.2.3.3: MP Entry. if (count != kMPEntrySize * numberOfImages) { SkCodecPrintf("Invalid MPEntry count.\n"); return nullptr; } mpEntryOffset = value; break; } case kIndividualImageUniqueIDTag: // See 5.2.3.4: Individual Image Unique ID List. // Validate that the count parameter is correct, but do not extract any other // information. if (count != kIndividualImageUniqueIDSize * numberOfImages) { SkCodecPrintf("Invalid Image Unique ID count.\n"); return nullptr; } break; case kTotalNumberCapturedFramesTag: // See 5.2.3.5: Total Number of Captured Frames. if (type != kTypeLong) { SkCodecPrintf("Invalid Total Number of Captured Frames type.\n"); return nullptr; } if (count != kTotalNumberCaptureFramesCount) { SkCodecPrintf("Invalid Total Number of Captured Frames count.\n"); return nullptr; } break; default: return nullptr; } } if (!numberOfImages) { SkCodecPrintf("Number of images must be greater than zero.\n"); return nullptr; } if (!mpEntryOffset) { SkCodecPrintf("MP Entry tag was not present or had invalid offset.\n"); return nullptr; } // Start to prepare the result that we will return. auto result = std::make_unique(); result->images.resize(numberOfImages); // Read the Attribute IFD offset, and verify that it is zero (absent) or greater than our // current offset. We will not read or validate the Attribute IFD. DEFINE_AND_READ_UINT(uint32_t, attributeIfdOffset); if (attributeIfdOffset > 0) { if (stream->getPosition() < attributeIfdOffset) { SkCodecPrintf("MP Attribute IFD offset moves backwards.\n"); return nullptr; } } // Read the MP Entries starting at the offset that we read earlier. if (!stream->seek(mpEntryOffset)) { SkCodecPrintf("Failed to seek to MP entries' offset.\n"); return nullptr; } for (uint32_t i = 0; i < numberOfImages; ++i) { DEFINE_AND_READ_UINT(uint32_t, attribute); const bool isPrimary = (attribute & kMPEntryAttributeTypeMask) == kMPEntryAttributeTypePrimary; const bool isJpeg = (attribute & kMPEntryAttributeFormatMask) == kMPEntryAttributeFormatJpeg; if (isPrimary != (i == 0)) { SkCodecPrintf("Image must be primary iff it is the first image..\n"); return nullptr; } if (!isJpeg) { SkCodecPrintf("Image format must be 0 (JPEG).\n"); return nullptr; } DEFINE_AND_READ_UINT(uint32_t, size); DEFINE_AND_READ_UINT(uint32_t, dataOffset); if (i == 0 && dataOffset != 0) { SkCodecPrintf("First individual Image offset must be NULL.\n"); return nullptr; } DEFINE_AND_READ_UINT(uint16_t, dependentImage1EntryNumber); DEFINE_AND_READ_UINT(uint16_t, dependentImage2EntryNumber); result->images[i].dataOffset = dataOffset; result->images[i].size = size; } return result; } #undef DEFINE_AND_READ_UINT // Return the number of bytes that will be written by SkJpegMultiPictureParametersSerialize, for a // given number of images. size_t multi_picture_params_serialized_size(size_t numberOfImages) { return sizeof(kMpfSig) + // Signature kMpEndianSize + // Endianness sizeof(uint32_t) + // Index IFD Offset sizeof(uint16_t) + // Tag count kTagSerializedCount * kTagSize + // 3 tags at 12 bytes each sizeof(uint32_t) + // Attribute IFD offset numberOfImages * kMPEntrySize; // MP Entries for each image } // Helper macros for SkJpegMultiPictureParameters::serialize. Byte-swap and write the specified // value, and return nullptr on failure. #define WRITE_UINT16(value) \ do { \ if (!s.write16(SkEndian_SwapBE16(value))) { \ return nullptr; \ } \ } while (0) #define WRITE_UINT32(value) \ do { \ if (!s.write32(SkEndian_SwapBE32(value))) { \ return nullptr; \ } \ } while (0) sk_sp SkJpegMultiPictureParameters::serialize() const { // Write the MPF signature. SkDynamicMemoryWStream s; if (!s.write(kMpfSig, sizeof(kMpfSig))) { SkCodecPrintf("Failed to write signature.\n"); return nullptr; } // We will always write as big-endian. if (!s.write(kMpBigEndian, kMpEndianSize)) { SkCodecPrintf("Failed to write endianness.\n"); return nullptr; } // Compute the number of images. uint32_t numberOfImages = static_cast(images.size()); // Set the Index IFD offset be the position after the endianness value and this offset. constexpr uint32_t indexIfdOffset = static_cast(sizeof(kMpBigEndian) + sizeof(uint32_t)); WRITE_UINT32(indexIfdOffset); // We will write 3 tags (version, number of images, MP entries). constexpr uint32_t numberOfTags = 3; WRITE_UINT16(numberOfTags); // Write the version tag. WRITE_UINT16(kVersionTag); WRITE_UINT16(kVersionType); WRITE_UINT32(kVersionCount); if (!s.write(kVersionExpected, kVersionSize)) { SkCodecPrintf("Failed to write version.\n"); return nullptr; } // Write the number of images. WRITE_UINT16(kNumberOfImagesTag); WRITE_UINT16(kNumberOfImagesType); WRITE_UINT32(kNumberOfImagesCount); WRITE_UINT32(numberOfImages); // Write the MP entries. WRITE_UINT16(kMPEntryTag); WRITE_UINT16(kMPEntryType); WRITE_UINT32(kMPEntrySize * numberOfImages); const uint32_t mpEntryOffset = static_cast(s.bytesWritten() - // The bytes written so far sizeof(kMpfSig) + // Excluding the MPF signature sizeof(uint32_t) + // The 4 bytes for this offset sizeof(uint32_t)); // The 4 bytes for the attribute IFD offset. WRITE_UINT32(mpEntryOffset); // Write the attribute IFD offset (zero because we don't write it). WRITE_UINT32(0); // Write the MP entries. for (size_t i = 0; i < images.size(); ++i) { const auto& image = images[i]; uint32_t attribute = kMPEntryAttributeFormatJpeg; if (i == 0) { attribute |= kMPEntryAttributeTypePrimary; } WRITE_UINT32(attribute); WRITE_UINT32(image.size); WRITE_UINT32(image.dataOffset); // Dependent image 1 and 2 entries are zero. WRITE_UINT16(0); WRITE_UINT16(0); } SkASSERT(s.bytesWritten() == multi_picture_params_serialized_size(images.size())); return s.detachAsData(); } #undef WRITE_UINT16 #undef WRITE_UINT32 size_t SkJpegMultiPictureParameters::GetAbsoluteOffset(uint32_t dataOffset, size_t mpSegmentOffset) { // The value of zero is used by the primary image. if (dataOffset == 0) { return 0; } return mpSegmentOffset + // The offset to the marker kJpegMarkerCodeSize + // The marker itself kJpegSegmentParameterLengthSize + // The parameter length sizeof(kMpfSig) + // The signature dataOffset; }