blob: f36c9856c927aa346553851fc285c1bc773a7b94 [file] [log] [blame]
/*
* 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/SkJpegPriv.h"
#include "src/codec/SkJpegSegmentScan.h"
#include <cstring>
// Helper macro for SkJpegParseMultiPicture. Define the indicated variable VAR of type TYPE, and
// read it from the stream, performing any endian-ness conversions as needed. 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##Data[sizeof(TYPE)] = {0}; \
if (!stream->read(VAR##Data, sizeof(TYPE))) { \
return nullptr; \
} \
for (size_t VAR##i = 0; VAR##i < sizeof(TYPE); ++VAR##i) { \
VAR *= 256; \
VAR += VAR##Data[streamIsBigEndian ? VAR##i : (sizeof(TYPE) - VAR##i - 1)]; \
} \
} \
(void)VAR
std::unique_ptr<SkJpegMultiPictureParameters> SkJpegParseMultiPicture(
const sk_sp<const SkData>& data) {
std::unique_ptr<SkMemoryStream> stream = SkMemoryStream::MakeDirect(data->data(), data->size());
// 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;
{
constexpr uint8_t kMpLittleEndian[] = {0x49, 0x49, 0x2A, 0x00};
constexpr uint8_t kMpBigEndian[] = {0x4D, 0x4D, 0x00, 0x2A};
uint8_t endianTag[4] = {0};
if (!stream->read(endianTag, sizeof(endianTag))) {
SkCodecPrintf("Failed to read MP endian tag.\n");
return nullptr;
}
if (!memcmp(endianTag, kMpBigEndian, 4)) {
streamIsBigEndian = true;
} else if (!memcmp(endianTag, kMpLittleEndian, 4)) {
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;
// We will need to MP Entries in order to determine the image offsets.
bool hasMpEntryTag = false;
// 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 0xB000:
// Version. We ignore this.
break;
case 0xB001:
// Number of images.
numberOfImages = value;
if (numberOfImages < 1) {
SkCodecPrintf("Invalid number of images.\n");
return nullptr;
}
break;
case 0xB002:
// MP Entry.
hasMpEntryTag = true;
if (count != 16 * numberOfImages) {
SkCodecPrintf("Invalid MPEntry count.\n");
return nullptr;
}
break;
case 0xB003:
// Individual Image Unique ID list. Validate it, but otherwise ignore it.
if (count != 33 * numberOfImages) {
SkCodecPrintf("Invalid Image Unique ID count.\n");
return nullptr;
}
break;
case 0xB004:
// Total number of captured frames. We ignore this.
break;
default:
return nullptr;
}
}
if (!numberOfImages) {
SkCodecPrintf("Number of images must be greater than zero.\n");
return nullptr;
}
if (!hasMpEntryTag) {
SkCodecPrintf("MP Entry tag was not present.\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 (which we verified to be present with hasMpEntryTag).
for (uint32_t i = 0; i < numberOfImages; ++i) {
DEFINE_AND_READ_UINT(uint32_t, attribute);
constexpr uint32_t kAttributeTypeMask = 0x7000000;
if ((attribute & kAttributeTypeMask) != 0) {
SkCodecPrintf("Image type must be 0 (JPEG).\n");
}
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");
}
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;
}
std::unique_ptr<SkJpegMultiPictureStreams> SkJpegExtractMultiPictureStreams(
SkJpegSeekableScan* scan) {
// Look through the scanned segments until we arrive at a MultiPicture segment that we can
// parse.
size_t mpSegmentOffset = 0;
std::unique_ptr<SkJpegMultiPictureParameters> mpParams;
for (const auto& segment : scan->segments()) {
if (segment.marker != kMpfMarker) {
continue;
}
auto parameterData = scan->copyParameters(segment, kMpfSig, sizeof(kMpfSig));
if (!parameterData) {
continue;
}
mpParams = SkJpegParseMultiPicture(parameterData);
if (mpParams) {
mpSegmentOffset = segment.offset;
break;
}
}
if (!mpParams) {
return nullptr;
}
// Create streams for each of the specified segments.
auto result = std::make_unique<SkJpegMultiPictureStreams>();
size_t numberOfImages = mpParams->images.size();
result->images.resize(numberOfImages);
for (size_t i = 0; i < numberOfImages; ++i) {
const auto& imageParams = mpParams->images[i];
if (imageParams.dataOffset == 0) {
continue;
}
size_t imageStreamOffset = mpSegmentOffset + SkJpegSegmentScanner::kMarkerCodeSize +
SkJpegSegmentScanner::kParameterLengthSize + sizeof(kMpfSig) +
imageParams.dataOffset;
size_t imageStreamSize = imageParams.size;
result->images[i].stream = scan->getSubsetStream(imageStreamOffset, imageStreamSize);
}
return result;
}