1 #include "image_io/gcontainer/gcontainer.h"
2
3 #include <fstream>
4
5 #include "image_io/base/data_segment.h"
6 #include "image_io/base/data_segment_data_source.h"
7 #include "image_io/base/istream_data_source.h"
8 #include "image_io/base/message_handler.h"
9 #include "image_io/base/ostream_data_destination.h"
10 #include "image_io/jpeg/jpeg_info.h"
11 #include "image_io/jpeg/jpeg_info_builder.h"
12 #include "image_io/jpeg/jpeg_scanner.h"
13 #include "image_io/utils/file_utils.h"
14
15 namespace photos_editing_formats {
16 namespace image_io {
17 namespace gcontainer {
18 namespace {
19
20 using photos_editing_formats::image_io::DataRange;
21 using photos_editing_formats::image_io::DataSegment;
22 using photos_editing_formats::image_io::DataSegmentDataSource;
23 using photos_editing_formats::image_io::IStreamRefDataSource;
24 using photos_editing_formats::image_io::JpegInfoBuilder;
25 using photos_editing_formats::image_io::JpegScanner;
26 using photos_editing_formats::image_io::Message;
27 using photos_editing_formats::image_io::MessageHandler;
28 using photos_editing_formats::image_io::OStreamDataDestination;
29 using std::string;
30
31 // Populates first_image_range with the first image (from the header metadata
32 // to the EOI marker) present in the JPEG file input_file_name. Returns true if
33 // such a first image is found, false otherwise.
34 //
35 // input_jpeg_stream must be a JPEG stream.
36 // image_data_segment is populated with the DataSegment for
37 // input_file_name, and is populated only in the successful case.
38 // first_image_range is populated with the first image found in the input file,
39 // only if such an image is found.
40
ExtractFirstImageInJpeg(std::istream & input_jpeg_stream,MessageHandler * message_handler,DataRange * first_image_range)41 bool ExtractFirstImageInJpeg(std::istream& input_jpeg_stream,
42 MessageHandler* message_handler,
43 DataRange* first_image_range) {
44 if (first_image_range == nullptr) {
45 return false;
46 }
47
48 // Get the input and output setup.
49 if (message_handler) {
50 message_handler->ClearMessages();
51 }
52
53 // Get the jpeg info and first image range from the input.
54 IStreamRefDataSource data_source(input_jpeg_stream);
55 JpegInfoBuilder jpeg_info_builder;
56 jpeg_info_builder.SetImageLimit(1);
57 JpegScanner jpeg_scanner(message_handler);
58 jpeg_scanner.Run(&data_source, &jpeg_info_builder);
59 data_source.Reset();
60
61 if (jpeg_scanner.HasError()) {
62 return false;
63 }
64
65 const auto& jpeg_info = jpeg_info_builder.GetInfo();
66 const auto& image_ranges = jpeg_info.GetImageRanges();
67 if (image_ranges.empty()) {
68 if (message_handler) {
69 message_handler->ReportMessage(Message::kPrematureEndOfDataError,
70 "No Images Found");
71 }
72 return false;
73 }
74
75 *first_image_range = image_ranges[0];
76 return true;
77 }
78
79 } // namespace
80
WriteImageAndFiles(const string & input_file_name,const std::vector<string> & other_files,const string & output_file_name)81 bool WriteImageAndFiles(const string& input_file_name,
82 const std::vector<string>& other_files,
83 const string& output_file_name) {
84 MessageHandler message_handler;
85 auto output_stream = OpenOutputFile(output_file_name, &message_handler);
86 if (!output_stream) {
87 return false;
88 }
89
90 OStreamDataDestination output_destination(std::move(output_stream),
91 &message_handler);
92 output_destination.SetName(output_file_name);
93
94 DataRange image_range;
95 std::unique_ptr<std::istream> input_stream =
96 OpenInputFile(input_file_name, &message_handler);
97
98 if (!ExtractFirstImageInJpeg(*input_stream, &message_handler, &image_range)) {
99 return false;
100 }
101
102 output_destination.StartTransfer();
103 IStreamDataSource data_source(
104 OpenInputFile(input_file_name, &message_handler));
105 data_source.TransferData(image_range, image_range.GetLength(),
106 &output_destination);
107
108 size_t bytes_transferred = image_range.GetLength();
109 for (const string& tack_on_file : other_files) {
110 if (tack_on_file.empty()) {
111 continue;
112 }
113 auto tack_on_data_segment = ReadEntireFile(tack_on_file, &message_handler);
114 if (!tack_on_data_segment) {
115 continue;
116 }
117
118 DataSegmentDataSource tack_on_source(tack_on_data_segment);
119 DataRange tack_on_range = tack_on_data_segment->GetDataRange();
120 bytes_transferred += tack_on_range.GetLength();
121 tack_on_source.TransferData(tack_on_range, tack_on_range.GetLength(),
122 &output_destination);
123 }
124
125 output_destination.FinishTransfer();
126 return output_destination.GetBytesTransferred() == bytes_transferred &&
127 !output_destination.HasError();
128 }
129
ParseFileAfterImage(const std::string & input_file_name,size_t file_start_offset,size_t file_length,std::string * out_file_contents)130 bool ParseFileAfterImage(const std::string& input_file_name,
131 size_t file_start_offset, size_t file_length,
132 std::string* out_file_contents) {
133 std::ifstream input_stream(input_file_name);
134 if (!input_stream.is_open()) {
135 return false;
136 }
137 return ParseFileAfterImageFromStream(file_start_offset, file_length,
138 input_stream, out_file_contents);
139 }
140
ParseFileAfterImageFromStream(size_t start_offset,size_t length,std::istream & input_jpeg_stream,std::string * out_contents)141 bool ParseFileAfterImageFromStream(size_t start_offset, size_t length,
142 std::istream& input_jpeg_stream,
143 std::string* out_contents) {
144 if (out_contents == nullptr || start_offset < 0 || length == 0) {
145 return false;
146 }
147
148 size_t curr_posn = input_jpeg_stream.tellg();
149 input_jpeg_stream.seekg(0, input_jpeg_stream.end);
150 size_t stream_size = input_jpeg_stream.tellg();
151 input_jpeg_stream.seekg(curr_posn, input_jpeg_stream.beg);
152
153 DataRange image_range;
154 MessageHandler message_handler;
155 if (!ExtractFirstImageInJpeg(input_jpeg_stream, &message_handler,
156 &image_range)) {
157 return false;
158 }
159
160 size_t image_bytes_end_offset = image_range.GetEnd();
161 size_t file_start_in_image = image_bytes_end_offset + start_offset;
162 size_t file_end_in_image = file_start_in_image + length;
163 if (stream_size < file_end_in_image) {
164 // Requested file is past the end of the image file.
165 return false;
166 }
167
168 // Get the file's contents.
169 const DataRange file_range(file_start_in_image, file_end_in_image);
170 size_t file_range_size = file_range.GetLength();
171 // TODO(miraleung): Consider subclassing image_io/data_destination.h and
172 // transferring bytes directly into the string. TBD pending additional mime
173 // type getters.
174 input_jpeg_stream.seekg(file_range.GetBegin(), input_jpeg_stream.beg);
175 out_contents->resize(file_range_size);
176 input_jpeg_stream.read(&(*out_contents)[0], file_range_size);
177 return true;
178 }
179
180 } // namespace gcontainer
181 } // namespace image_io
182 } // namespace photos_editing_formats
183