| /* | |
| * 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 <cstring> | |
| 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> SkJpegMultiPictureParameters::Make( | |
| const sk_sp<const SkData>& segmentParameters) { | |
| // Read the MP Format identifier starting after the APP2 Field Length. See Figure 4 of CIPA | |
| // DC-x007-2009. | |
| if (segmentParameters->size() < sizeof(kMpfSig)) { | |
| return nullptr; | |
| } | |
| if (memcmp(segmentParameters->data(), kMpfSig, sizeof(kMpfSig)) != 0) { | |
| return nullptr; | |
| } | |
| std::unique_ptr<SkMemoryStream> stream = | |
| SkMemoryStream::MakeDirect(segmentParameters->bytes() + sizeof(kMpfSig), | |
| segmentParameters->size() - sizeof(kMpfSig)); | |
| // The rest of this function reads the structure described in Figure 6 of CIPA DC-x007-2009. | |
| // Determine the endianness of the values in the structure. See Figure 5 (MP endian tag | |
| // structure). | |
| bool streamIsBigEndian = false; | |
| { | |
| 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<SkJpegMultiPictureParameters>(); | |
| 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<SkData> 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<uint32_t>(images.size()); | |
| // Set the Index IFD offset be the position after the endianness value and this offset. | |
| constexpr uint32_t indexIfdOffset = | |
| static_cast<uint16_t>(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<uint32_t>(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; | |
| } |