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