diff options
Diffstat (limited to 'internal/xmpmeta/jpeg_io.cc')
-rw-r--r-- | internal/xmpmeta/jpeg_io.cc | 194 |
1 files changed, 194 insertions, 0 deletions
diff --git a/internal/xmpmeta/jpeg_io.cc b/internal/xmpmeta/jpeg_io.cc new file mode 100644 index 0000000..20fc7a6 --- /dev/null +++ b/internal/xmpmeta/jpeg_io.cc @@ -0,0 +1,194 @@ +#include "xmpmeta/jpeg_io.h" + +#include <fstream> +#include <sstream> + +#include "android-base/logging.h" + +namespace photos_editing_formats { +namespace { + +// File markers. +// See: http://www.fileformat.info/format/jpeg/egff.htm or +// https://en.wikipedia.org/wiki/JPEG +const int kSoi = 0xd8; // Start of image marker. +const int kApp1 = 0xe1; // Start of EXIF section. +const int kSos = 0xda; // Start of scan. + +// Number of bytes used to store a section's length in a JPEG file. +const int kSectionLengthByteSize = 2; + +// Returns the number of bytes available to be read. Sets the seek position +// to the place it was in before calling this function. +size_t GetBytesAvailable(std::istream* input_stream) { + const std::streamoff pos = input_stream->tellg(); + if (pos == -1) { + return 0; + } + + input_stream->seekg(0, std::ios::end); + if (!input_stream->good()) { + return 0; + } + + const std::streamoff end = input_stream->tellg(); + if (end == -1) { + return 0; + } + input_stream->seekg(pos); + + if (end <= pos) { + return 0; + } + return end - pos; +} + +// Returns the first byte in the stream cast to an integer. +int ReadByteAsInt(std::istream* input_stream) { + unsigned char byte; + input_stream->read(reinterpret_cast<char*>(&byte), 1); + if (!input_stream->good()) { + // Return an invalid value - no byte can be read as -1. + return -1; + } + return static_cast<int>(byte); +} + +// Reads the length of a section from 2 bytes. +size_t Read2ByteLength(std::istream* input_stream, bool* error) { + const int length_high = ReadByteAsInt(input_stream); + const int length_low = ReadByteAsInt(input_stream); + if (length_high == -1 || length_low == -1) { + *error = true; + return 0; + } + *error = false; + return length_high << 8 | length_low; +} + +bool HasPrefixString(const string& to_check, const string& prefix) { + if (to_check.size() < prefix.size()) { + return false; + } + return std::equal(prefix.begin(), prefix.end(), to_check.begin()); +} + +} // namespace + +Section::Section(const string& buffer) { + marker = kApp1; + is_image_section = false; + data = buffer; +} + +bool Section::IsMarkerApp1() { return marker == kApp1; } + +std::vector<Section> Parse(const ParseOptions& options, + std::istream* input_stream) { + std::vector<Section> sections; + // Return early if this is not the start of a JPEG section. + if (ReadByteAsInt(input_stream) != 0xff || + ReadByteAsInt(input_stream) != kSoi) { + LOG(WARNING) << "File's first two bytes does not match the sequence \xff" + << kSoi; + return std::vector<Section>(); + } + + int chr; // Short for character. + while ((chr = ReadByteAsInt(input_stream)) != -1) { + if (chr != 0xff) { + LOG(WARNING) << "Read non-padding byte: " << chr; + return sections; + } + // Skip padding bytes. + while ((chr = ReadByteAsInt(input_stream)) == 0xff) { + } + if (chr == -1) { + LOG(WARNING) << "No more bytes in file available to be read."; + return sections; + } + + const int marker = chr; + if (marker == kSos) { + // kSos indicates the image data will follow and no metadata after that, + // so read all data at one time. + if (!options.read_meta_only) { + Section section; + section.marker = marker; + section.is_image_section = true; + const size_t bytes_available = GetBytesAvailable(input_stream); + section.data.resize(bytes_available); + input_stream->read(§ion.data[0], bytes_available); + if (input_stream->good()) { + sections.push_back(section); + } + } + // All sections have been read. + return sections; + } + + bool error; + const size_t length = Read2ByteLength(input_stream, &error); + if (error || length < kSectionLengthByteSize) { + // No sections to read. + LOG(WARNING) << "No sections to read; section length is " << length; + return sections; + } + + const size_t bytes_left = GetBytesAvailable(input_stream); + if (length - kSectionLengthByteSize > bytes_left) { + LOG(WARNING) << "Invalid section length = " << length + << " total bytes available = " << bytes_left; + return sections; + } + + if (!options.read_meta_only || marker == kApp1) { + Section section; + section.marker = marker; + section.is_image_section = false; + const size_t data_size = length - kSectionLengthByteSize; + section.data.resize(data_size); + if (section.data.size() != data_size) { + LOG(WARNING) << "Discrepancy in section data size " + << section.data.size() << "and data size " << data_size; + return sections; + } + input_stream->read(§ion.data[0], section.data.size()); + if (input_stream->good() && + (options.section_header.empty() || + HasPrefixString(section.data, options.section_header))) { + sections.push_back(section); + // Return if we have specified to return the 1st section with + // the given name. + if (options.section_header_return_first) { + return sections; + } + } + } else { + // Skip this section since all EXIF/XMP meta will be in kApp1 section. + input_stream->ignore(length - kSectionLengthByteSize); + } + } + return sections; +} + +void WriteSections(const std::vector<Section>& sections, + std::ostream* output_stream) { + output_stream->put(0xff); + output_stream->put(kSoi); + for (const Section& section : sections) { + output_stream->put(0xff); + output_stream->put(section.marker); + if (!section.is_image_section) { + const int section_length = static_cast<int>(section.data.length()) + 2; + // It's not the image data. + const int lh = section_length >> 8; + const int ll = section_length & 0xff; + output_stream->put(lh); + output_stream->put(ll); + } + output_stream->write(section.data.c_str(), section.data.length()); + } +} + +} // namespace photos_editing_formats |