cpp/spectrum/image/metadata/ICCProfile.cpp (116 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // This source code is licensed under the MIT license found in the // LICENSE file in the root directory of this source tree. #include "ICCProfile.h" #include <spectrum/core/SpectrumEnforce.h> #include <cstring> // strncmp #include <map> namespace facebook { namespace spectrum { namespace image { namespace metadata { namespace error { const folly::StringPiece InvalidProfileHeader{ "metadata_invalid_profile_header"}; const folly::StringPiece ProfileTooLargeForMarker{ "metadata_profile_too_large_for_marker"}; const folly::StringPiece UnexpectedProfileChunkIndex{ "metadata_profile_unexpected_chunk_index"}; const folly::StringPiece UnexpectedProfileChunkTotal{ "metadata_profile_unexpected_chunk_total"}; } // namespace error static constexpr std::size_t MARKER_MAX_SIZE = 1024 * 64; // 64k static constexpr const char* MARKER_HEADER_TAG = "ICC_PROFILE"; static constexpr std::size_t MARKER_HEADER_TAG_SIZE = 12; /* inc. \0 */ static constexpr std::size_t MARKER_HEADER_SIZE = MARKER_HEADER_TAG_SIZE + 2; /* current marker + total marker */ static constexpr std::size_t MARKER_PAYLOAD_SIZE = MARKER_MAX_SIZE - MARKER_HEADER_SIZE; namespace { bool ensureIsProfileData( const core::DataRange& dataRange, const std::size_t totalCount) { return dataRange.length <= MARKER_MAX_SIZE && dataRange.length > MARKER_HEADER_SIZE && std::strncmp( reinterpret_cast<const char*>(dataRange.begin), MARKER_HEADER_TAG, MARKER_HEADER_TAG_SIZE) == 0; } std::size_t indexOfChunk( const core::DataRange& dataRange, std::uint8_t& currentTotalCount) { SPECTRUM_ERROR_IF_NOT( ensureIsProfileData(dataRange, currentTotalCount), error::InvalidProfileHeader); const std::uint8_t chunkIndex = *(dataRange.begin + MARKER_HEADER_TAG_SIZE); const std::uint8_t chunkTotalCount = *(dataRange.begin + MARKER_HEADER_TAG_SIZE + 1); if (currentTotalCount != 0) { SPECTRUM_ERROR_IF( currentTotalCount != chunkTotalCount, error::UnexpectedProfileChunkIndex); } // 1 <= chunkIndex <= chunkTotalCount SPECTRUM_ERROR_IF_NOT( chunkIndex > 0 && chunkIndex <= chunkTotalCount, error::UnexpectedProfileChunkIndex); // Update the currentTotalCount with value for the first found byte. if (currentTotalCount == 0) { currentTotalCount = chunkTotalCount; } return chunkIndex; } } // namespace ICCProfile::ICCProfile(const std::vector<std::uint8_t>& data) : _data(data) { SPECTRUM_ERROR_IF( _data.size() >= 255 * MARKER_PAYLOAD_SIZE, error::ProfileTooLargeForMarker); } ICCProfile::ICCProfile(const std::vector<core::DataRange>& chunks) { std::uint8_t currentTotalCount = 0; std::map<std::size_t, std::size_t> indexesByChunkIndexes; for (std::size_t i = 0; i < chunks.size(); ++i) { try { indexesByChunkIndexes[indexOfChunk(chunks[i], currentTotalCount)] = i; } catch (const SpectrumException& e) { // We've got an error. Its not a profile, or an invalid one. Skip it. } } if (currentTotalCount == indexesByChunkIndexes.size()) { for (std::size_t i = 1; i <= currentTotalCount; ++i) { // Ensure's we've got consecutive indexes. i.e not 3 items with ids 1, 2, // & 5. const auto chunk = chunks[indexesByChunkIndexes[i]]; if (chunk.length > MARKER_HEADER_SIZE) { _data.insert( _data.cend(), chunk.begin + MARKER_HEADER_SIZE, chunk.end()); } } } } std::vector<std::vector<std::uint8_t>> ICCProfile::makeEncodedData() const { const auto totalChunks = (_data.size() + MARKER_PAYLOAD_SIZE - 1) / MARKER_PAYLOAD_SIZE; std::vector<std::vector<std::uint8_t>> dataChunks; for (auto iterator = _data.cbegin(); iterator < _data.cend(); iterator += MARKER_PAYLOAD_SIZE) { const std::size_t dataLeft = std::distance(iterator, _data.cend()); const auto dataSize = std::min(MARKER_PAYLOAD_SIZE, dataLeft); std::vector<std::uint8_t> data( MARKER_HEADER_TAG, MARKER_HEADER_TAG + MARKER_HEADER_TAG_SIZE); // Header element: marker id data.push_back(static_cast<std::uint8_t>(dataChunks.size() + 1)); // Header element: total marker count data.push_back(static_cast<std::uint8_t>(totalChunks)); data.insert(data.end(), iterator, iterator + dataSize); dataChunks.emplace_back(std::move(data)); } return dataChunks; } std::size_t ICCProfile::size() const { return _data.size(); } bool ICCProfile::empty() const { return _data.empty(); } const std::vector<std::uint8_t>& ICCProfile::data() const { return _data; } bool ICCProfile::operator==(const ICCProfile& other) const { return _data == other._data; } } // namespace metadata } // namespace image } // namespace spectrum } // namespace facebook