| /* | |
| * Copyright 2023 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/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/codec/SkTiffUtility.h" | |
| #include "src/core/SkEndian.h" | |
| #include "src/core/SkSafeMath.h" | |
| #include "src/core/SkStreamPriv.h" | |
| #include <cstring> | |
| constexpr uint16_t kVersionTag = 0xB000; | |
| 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 uint32_t kNumberOfImagesCount = 1; | |
| constexpr uint16_t kMPEntryTag = 0xB002; | |
| 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; | |
| 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; | |
| } | |
| auto ifdData = SkData::MakeSubset( | |
| segmentParameters.get(), sizeof(kMpfSig), segmentParameters->size() - sizeof(kMpfSig)); | |
| SkASSERT(ifdData); | |
| // 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), and read the Index IFD offset. | |
| bool littleEndian = false; | |
| uint32_t ifdOffset = 0; | |
| if (!SkTiff::ImageFileDirectory::ParseHeader(ifdData.get(), &littleEndian, &ifdOffset)) { | |
| SkCodecPrintf("Failed to parse endian-ness and index IFD offset.\n"); | |
| return nullptr; | |
| } | |
| // Create the Index Image File Directory (Index IFD). | |
| auto ifd = SkTiff::ImageFileDirectory::MakeFromOffset(ifdData, littleEndian, ifdOffset); | |
| if (!ifd) { | |
| SkCodecPrintf("Failed to create MP Index IFD offset.\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. | |
| uint16_t tagCount = ifd->getNumEntries(); | |
| // We will extract the number of images from the tags. | |
| uint32_t numberOfImages = 0; | |
| // The data for the MP entries. | |
| sk_sp<SkData> mpEntriesData; | |
| // 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 previousTag = 0; | |
| for (uint16_t idfEntryIndex = 0; idfEntryIndex < tagCount; ++idfEntryIndex) { | |
| uint16_t tag = ifd->getEntryTag(idfEntryIndex); | |
| if (previousTag >= tag) { | |
| SkCodecPrintf("MPF tags not in order.\n"); | |
| return nullptr; | |
| } | |
| previousTag = tag; | |
| switch (tag) { | |
| case kVersionTag: { | |
| // See 5.2.3.1: MP Format Version. | |
| sk_sp<SkData> data = ifd->getEntryUndefinedData(idfEntryIndex); | |
| if (!data) { | |
| SkCodecPrintf("Version must be undefined type.\n"); | |
| return nullptr; | |
| } | |
| if (data->size() != kVersionSize) { | |
| SkCodecPrintf("Version must be 4 bytes.\n"); | |
| return nullptr; | |
| } | |
| if (memcmp(data->data(), kVersionExpected, kVersionSize) != 0) { | |
| SkCodecPrintf("Version value is not 0100.\n"); | |
| return nullptr; | |
| } | |
| break; | |
| } | |
| case kNumberOfImagesTag: | |
| // See 5.2.3.2: Number of Images. | |
| if (!ifd->getEntryUnsignedLong(idfEntryIndex, 1, &numberOfImages)) { | |
| SkCodecPrintf("Number of Images was not 1 unsigned long.\n"); | |
| } | |
| if (numberOfImages < 1) { | |
| SkCodecPrintf("Invalid number of images.\n"); | |
| return nullptr; | |
| } | |
| break; | |
| case kMPEntryTag: { | |
| // See 5.2.3.3: MP Entry. | |
| mpEntriesData = ifd->getEntryUndefinedData(idfEntryIndex); | |
| if (!mpEntriesData) { | |
| SkCodecPrintf("MP entries data could not be extracted.\n"); | |
| return nullptr; | |
| } | |
| SkSafeMath expectedSizeSafe; | |
| const size_t expectedSize = expectedSizeSafe.mul(kMPEntrySize, numberOfImages); | |
| if (!expectedSizeSafe.ok() || mpEntriesData->size() != expectedSize) { | |
| SkCodecPrintf("MP entries data should be %ux%u bytes, was %u.\n", | |
| kMPEntrySize, | |
| numberOfImages, | |
| static_cast<uint32_t>(mpEntriesData->size())); | |
| return nullptr; | |
| } | |
| 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. | |
| sk_sp<SkData> data = ifd->getEntryUndefinedData(idfEntryIndex); | |
| if (!data) { | |
| SkCodecPrintf("Image Unique ID must be undefined type.\n"); | |
| return nullptr; | |
| } | |
| if (data->size() != kIndividualImageUniqueIDSize * numberOfImages) { | |
| SkCodecPrintf("Invalid Image Unique ID count.\n"); | |
| return nullptr; | |
| } | |
| break; | |
| } | |
| case kTotalNumberCapturedFramesTag: { | |
| // See 5.2.3.5: Total Number of Captured Frames. | |
| uint32_t totalNumCapturedFrames = 0; | |
| if (!ifd->getEntryUnsignedLong(idfEntryIndex, 1, &totalNumCapturedFrames)) { | |
| SkCodecPrintf("Total Number of Captures Frames was not 1 unsigned long.\n"); | |
| } | |
| break; | |
| } | |
| default: | |
| return nullptr; | |
| } | |
| } | |
| if (!numberOfImages) { | |
| SkCodecPrintf("Number of images must be greater than zero.\n"); | |
| return nullptr; | |
| } | |
| if (!mpEntriesData) { | |
| SkCodecPrintf("MP Entry data was not present.\n"); | |
| return nullptr; | |
| } | |
| // Start to prepare the result that we will return. | |
| auto result = std::make_unique<SkJpegMultiPictureParameters>(numberOfImages); | |
| // The next IFD is the Attribute IFD offset. We will not read or validate the Attribute IFD. | |
| // Parse the MP Entries data. | |
| for (uint32_t i = 0; i < numberOfImages; ++i) { | |
| const auto mpEntry = mpEntriesData->shareSubset(kMPEntrySize * i, kMPEntrySize); | |
| if (!mpEntry) { | |
| SkCodecPrintf("MP Entry data unavailable\n"); | |
| return nullptr; | |
| } | |
| const uint32_t attribute = SkCodecPriv::GetEndianInt(mpEntry->bytes() + 0, littleEndian); | |
| const uint32_t size = SkCodecPriv::GetEndianInt(mpEntry->bytes() + 4, littleEndian); | |
| const uint32_t dataOffset = SkCodecPriv::GetEndianInt(mpEntry->bytes() + 8, littleEndian); | |
| 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; | |
| } | |
| if (i == 0 && dataOffset != 0) { | |
| SkCodecPrintf("First individual Image offset must be NULL.\n"); | |
| return nullptr; | |
| } | |
| result->images[i].dataOffset = dataOffset; | |
| result->images[i].size = size; | |
| } | |
| return result; | |
| } | |
| sk_sp<SkData> SkJpegMultiPictureParameters::serialize(uint32_t individualImageNumber) const { | |
| SkDynamicMemoryWStream s; | |
| const uint32_t numberOfImages = static_cast<uint32_t>(images.size()); | |
| // Write the MPF signature. | |
| s.write(kMpfSig, sizeof(kMpfSig)); | |
| // We will always write as big-endian. | |
| s.write(SkTiff::kEndianBig, sizeof(SkTiff::kEndianBig)); | |
| // Set the first IFD offset be the position after the endianness value and this offset. This | |
| // will be the MP Index IFD for the first individual image and the MP Attribute IFD for all | |
| // other images. | |
| constexpr uint32_t firstIfdOffset = sizeof(SkTiff::kEndianBig) + // Endian-ness | |
| sizeof(uint32_t); // Index IFD offset | |
| SkStreamPriv::WriteU32BE(&s, firstIfdOffset); | |
| SkASSERT(s.bytesWritten() - sizeof(kMpfSig) == firstIfdOffset); | |
| if (individualImageNumber == 0) { | |
| // The MP Index IFD will write 3 tags (version, number of images, and MP entries). See | |
| // in Table 6 (MP Index IFD Tag Support Level) that these are the only mandatory entries. | |
| const uint32_t mpIndexIfdNumberOfTags = 3; | |
| SkStreamPriv::WriteU16BE(&s, mpIndexIfdNumberOfTags); | |
| } else { | |
| // The MP Attribute IFD will write 1 tags (version). See in Table 7 (MP Attribute IFD Tag | |
| // Support Level for Baseline MP Files) that no tags are required. If gainmap images support | |
| // is added to CIPA DC-007, then some tags may be added and become mandatory. | |
| const uint16_t mpAttributeIfdNumberOfTags = 1; | |
| SkStreamPriv::WriteU16BE(&s, mpAttributeIfdNumberOfTags); | |
| } | |
| // Write the version. | |
| SkStreamPriv::WriteU16BE(&s, kVersionTag); | |
| SkStreamPriv::WriteU16BE(&s, SkTiff::kTypeUndefined); | |
| SkStreamPriv::WriteU32BE(&s, kVersionCount); | |
| s.write(kVersionExpected, kVersionSize); | |
| if (individualImageNumber == 0) { | |
| // Write the number of images. | |
| SkStreamPriv::WriteU16BE(&s, kNumberOfImagesTag); | |
| SkStreamPriv::WriteU16BE(&s, SkTiff::kTypeUnsignedLong); | |
| SkStreamPriv::WriteU32BE(&s, kNumberOfImagesCount); | |
| SkStreamPriv::WriteU32BE(&s, numberOfImages); | |
| // Write the MP entries tag. | |
| SkStreamPriv::WriteU16BE(&s, kMPEntryTag); | |
| SkStreamPriv::WriteU16BE(&s, SkTiff::kTypeUndefined); | |
| const uint32_t mpEntriesSize = kMPEntrySize * numberOfImages; | |
| SkStreamPriv::WriteU32BE(&s, mpEntriesSize); | |
| 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. | |
| SkStreamPriv::WriteU32BE(&s, mpEntryOffset); | |
| // Write the attribute IFD offset (zero because there is none). | |
| SkStreamPriv::WriteU32BE(&s, 0); | |
| // Write the MP entries data. | |
| SkASSERT(s.bytesWritten() - sizeof(kMpfSig) == mpEntryOffset); | |
| for (size_t i = 0; i < images.size(); ++i) { | |
| const auto& image = images[i]; | |
| uint32_t attribute = kMPEntryAttributeFormatJpeg; | |
| if (i == 0) { | |
| attribute |= kMPEntryAttributeTypePrimary; | |
| } | |
| SkStreamPriv::WriteU32BE(&s, attribute); | |
| SkStreamPriv::WriteU32BE(&s, image.size); | |
| SkStreamPriv::WriteU32BE(&s, image.dataOffset); | |
| // Dependent image 1 and 2 entries are zero. | |
| SkStreamPriv::WriteU16BE(&s, 0); | |
| SkStreamPriv::WriteU16BE(&s, 0); | |
| } | |
| } else { | |
| // The non-first-individual-images do not have any further IFDs. | |
| SkStreamPriv::WriteU32BE(&s, 0); | |
| } | |
| return s.detachAsData(); | |
| } | |
| static size_t mp_header_absolute_offset(size_t mpSegmentOffset) { | |
| return mpSegmentOffset + // The offset to the segment's marker | |
| kJpegMarkerCodeSize + // The marker itself | |
| kJpegSegmentParameterLengthSize + // The segment parameter length | |
| sizeof(kMpfSig); // The {'M','P','F',0} signature | |
| } | |
| size_t SkJpegMultiPictureParameters::GetImageAbsoluteOffset(uint32_t dataOffset, | |
| size_t mpSegmentOffset) { | |
| // The value of zero is used by the primary image. | |
| if (dataOffset == 0) { | |
| return 0; | |
| } | |
| return mp_header_absolute_offset(mpSegmentOffset) + dataOffset; | |
| } | |
| uint32_t SkJpegMultiPictureParameters::GetImageDataOffset(size_t imageAbsoluteOffset, | |
| size_t mpSegmentOffset) { | |
| // The value of zero is used by the primary image. | |
| if (imageAbsoluteOffset == 0) { | |
| return 0; | |
| } | |
| return static_cast<uint32_t>(imageAbsoluteOffset - mp_header_absolute_offset(mpSegmentOffset)); | |
| } |