2 // Tizen Web Device API
3 // Copyright (c) 2014 Samsung Electronics Co., Ltd.
5 // Licensed under the Apache License, Version 2.0 (the License);
6 // you may not use this file except in compliance with the License.
7 // You may obtain a copy of the License at
9 // http://www.apache.org/licenses/LICENSE-2.0
11 // Unless required by applicable law or agreed to in writing, software
12 // distributed under the License is distributed on an "AS IS" BASIS,
13 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 // See the License for the specific language governing permissions and
15 // limitations under the License.
19 // For details of JPEG file format see:
20 // http://www.media.mit.edu/pia/Research/deepview/exif.html
30 #include <PlatformException.h>
34 using namespace DeviceAPI::Common;
39 * Size of maximal JPEG's section data length
40 * (since it is stored as unsigned short limit is 2^16 -1)
42 const unsigned int MAX_JPEG_SECTION_DATA_SIZE = 65535;
45 * JPEG's section data length includes 2 bytes for length therefore we need to
46 * substract 2 from MAX_JPEG_SECTION_DATA_SIZE
48 const unsigned int MAX_AVAILABLE_JPEG_SECTION_DATA_SIZE = MAX_JPEG_SECTION_DATA_SIZE - 2;
50 bool isJpegMarker(const int value)
52 return value >= JPEG_MARKER_LOWEST_ID && value <= JPEG_MARKER_HIGHEST_ID;
55 JpegMarker castToJpegMarker(const unsigned char byte)
57 if (byte < JPEG_MARKER_LOWEST_ID || byte > JPEG_MARKER_HIGHEST_ID) {
58 return JPEG_MARKER_UNKNOWN;
61 return static_cast<JpegMarker>(byte);
64 long readUShortBE(unsigned char* src)
66 return ((static_cast<long>(src[0]) << 8) | static_cast<long>(src[1]));
69 void writeUShortBE(unsigned short value, unsigned char* buffer)
71 buffer[0] = static_cast<unsigned char>(value >> 8);
72 buffer[1] = static_cast<unsigned char>(value);
75 struct CArrayDeleter {
76 void operator()(void* buffer) { free(buffer); }
79 JpegFile::JpegFile() :
85 m_padding_data_size(0),
98 m_padding_data = NULL;
99 m_padding_data_size = 0;
101 for(SectionsVec::iterator it = m_sections.begin(); it != m_sections.end(); ++it) {
102 JpegFileSectionPtr cur = *it;
104 if (cur->exif_data) {
105 exif_data_unref(cur->exif_data);
106 cur->exif_data = NULL;
109 cur->data_ptr = NULL;
111 cur->type = JPEG_MARKER_UNKNOWN;
128 JpegFilePtr JpegFile::loadFile(const std::string& path)
130 JpegFile* new_jpg = new (std::nothrow) JpegFile();
132 LOGE("Couldn't allocate Jpegfile!");
133 throw UnknownException("Memory allocation failed");
136 JpegFilePtr jpg_ptr(new_jpg);
141 void JpegFile::load(const std::string& path)
143 LOGD("Entered file:%s", path.c_str());
145 m_source_file_path = path;
147 m_in_file = fopen(path.c_str(), "rb");
149 LOGE("Couldn't open Jpeg file: [%s]", path.c_str());
150 throw NotFoundException("Could not open JPG file");
153 fseek(m_in_file, 0, SEEK_END);
154 long ftell_val = ftell(m_in_file);
156 LOGE("Input file [%s] access error! [%d]", path.c_str(), errno);
157 throw UnknownException("JPEG file is invalid");
159 const size_t in_file_size = static_cast<size_t>(ftell_val);
160 fseek(m_in_file, 0, SEEK_SET);
161 LOGD("JPEG file: [%s] size:%d", path.c_str(), in_file_size);
162 if (0 == in_file_size) {
163 LOGE("Input file [%s] is empty!", path.c_str());
164 throw UnknownException("JPEG file is invalid");
167 m_in_data = new (std::nothrow) unsigned char[in_file_size];
169 LOGE("Couldn't allocate buffer with size: %d", in_file_size);
170 throw UnknownException("Memory allocation failed");
173 m_in_data_size = in_file_size;
175 const size_t read_bytes = fread(m_in_data, 1, m_in_data_size, m_in_file);
176 if (read_bytes != m_in_data_size) {
177 LOGE("Couldn't read all: %d bytes. Read only: %d bytes!", m_in_data_size,
179 throw UnknownException("Could not read JPEG file");
182 if (fclose(m_in_file) == EOF) {
183 LOGE("Couldn't close input file: %s!", path.c_str());
187 generateListOfSections();
190 std::string JpegFile::getPartOfFile(const size_t offset,
191 const size_t num_bytes_before,
192 const size_t num_bytes_after)
194 long long int start = static_cast<long long int>(offset) - num_bytes_before;
199 long long int end = static_cast<long long int>(offset) + num_bytes_after;
200 if (end >= m_in_data_size) {
201 end = m_in_data_size - 1;
204 std::stringstream ss;
205 ss << std::setfill('0') << std::setw(2) << std::hex;
206 for(long long int i = start; i <= end; ++i) {
207 ss << static_cast<int>(m_in_data[i]);
213 void JpegFile::generateListOfSections()
218 //FFD8 (2 bytes) - SOI Marker
221 //N sections - format of section:
222 //0xFF(1 byte) + Marker Number(1 byte) + Data size(2 bytes) + Data
225 //SOS 0xFF(1 byte) + Marker Number(1 byte) + Data size(2 bytes) + Data
229 //FFD9 (2 bytes) - EOI Marker
231 //Warning: some images taken on Android contains some extra data at the end
232 //we will keep it in m_padding_data
234 m_padding_data = NULL;
235 m_padding_data_size = 0;
237 for(size_t offset = 0, iterration = 0; offset < m_in_data_size;++iterration) {
239 LOGD("offset:%d | Starting iteration: %d", offset, iterration);
240 const size_t search_len = 10;
241 size_t search_offset = 0;
242 for(search_offset = 0; search_offset < search_len; ++search_offset) {
243 //Skip bytes until first no 0xff
244 unsigned char& tmp_marker = m_in_data[offset + search_offset];
245 if (tmp_marker != 0xff) {
250 if (search_len == search_offset) {
251 LOGE("offset:%d | Couldn't find marker! RAW DATA:{%s}", offset,
252 getPartOfFile(offset, 0, 10).c_str());
253 throw UnknownException("JPEG file is invalid");
256 const size_t section_offset = offset + search_offset - 1;
257 unsigned char* section_begin = m_in_data + section_offset;
259 offset = section_offset; //Move to section begin
260 LOGD("offset:%d | Moved to section begin", offset);
262 if (!isJpegMarker(section_begin[1])) {
263 LOGE("offset:%d | Is not valid marker: 0x%x RAW DATA:{%s}", offset,
264 section_begin[1], getPartOfFile(section_offset,0,4).c_str());
265 throw UnknownException("JPEG file is invalid");
268 const JpegMarker cur_marker = castToJpegMarker(section_begin[1]);
269 LOGD("offset:%d | Found valid marker: 0x%x RAW DATA:{%s}", offset,
271 getPartOfFile(section_offset,0,4).c_str());
273 offset += 2; //Read 0xffxx marker tag - 2 bytes
275 JpegFileSectionPtr section;
277 JpegFileSection* sec = new (std::nothrow) JpegFileSection();
279 LOGE("Couldn't allocate JpegFileSection");
280 throw UnknownException("Memory allocation failed");
283 section = JpegFileSectionPtr(sec);
286 section->type = cur_marker;
287 m_sections.push_back(section);
288 if (cur_marker == JPEG_MARKER_SOI ||
289 cur_marker == JPEG_MARKER_EOI) {
290 LOGD("offset:%d | Found: %s marker, moving to next marker at:%d",
291 section_offset, ((cur_marker == JPEG_MARKER_SOI) ? "SOI" : "EOI"),
294 if(cur_marker == JPEG_MARKER_EOI && m_padding_data != NULL) {
295 LOGW("Padding data have been found - do not try to parse end of file");
300 //From JPEG/EXIF info:
301 // Please notice that "Data" contains Data size descriptor, if there is
302 // a Marker like this;
305 // It means this Marker(0xFFC1) has 0x000C(equal 12)bytes of data. But the
306 // data size '12' includes "Data size" descriptor, it follows only 10 bytes of
307 // data after 0x000C.
310 const long total_section_len = readUShortBE(section_begin + 2); //Include data
313 const long section_data_len = total_section_len - 2; //Exclude data
316 LOGD("offset:%d tag:0x%x | Read total_section_len:%d (data len:%d)",
317 section_offset, cur_marker, total_section_len, section_data_len);
319 offset += 2; //Read data size - 2 bytes
321 if (total_section_len < 0) {
322 LOGE("offset:%d tag:0x%x | Error: total_section_len is: %d < 0", offset,
323 cur_marker, total_section_len);
324 throw UnknownException("JPEG file is invalid");
327 if (section_offset + 2 + total_section_len > m_in_data_size) {
328 LOGE("offset:%d tag:0x%x | Error: current section offset:%d"
329 " + 2 + total_section_len:%d = %d is greater then file size:%d",
331 section_offset, total_section_len,
332 section_offset + total_section_len, m_in_data_size);
333 throw UnknownException("JPEG file is invalid");
336 if (JPEG_MARKER_APP1 == cur_marker) {
338 //-4 --> 0xFF(1 byte)+Marker Number(1 byte)+Data size(2 bytes))
339 //const unsigned int exif_data_size = section_length - 4;
341 const unsigned int exif_data_size = total_section_len + 2;
342 section->exif_data = exif_data_new_from_data (section_begin,
345 LOGD("offset:%d tag:0x%x | Loading exif from offset:%d"
346 " len:%d exif_data_new_from_data returned: %p",
347 offset, cur_marker, section_offset, exif_data_size,
350 if (!section->exif_data) {
351 LOGW("offset:%d tag:0x%x | Couldn't load Exif!", offset, cur_marker);
355 //This just saves pointer not copying data
356 section->data_ptr = section_begin + 2 + 2; //2 bytes marker + 2 bytes data size
357 section->size = section_data_len; //Exclude data size
359 if (JPEG_MARKER_SOS == cur_marker) {
360 //Calculate offset of first image data which is just after this SOS section
361 const size_t image_data_offset = section_offset + 2 + total_section_len;
363 //Calculate size of image data from start to expected EOI at end of file.
365 //-2 (exclude ending EOI marker (2 bytes)
366 size_t image_size = m_in_data_size - image_data_offset - 2;
367 LOGW("offset:%d tag:0x%x | Image data offset:%d Estimated image size:%d",
368 offset, cur_marker, image_data_offset, image_size);
370 m_image_data = m_in_data + image_data_offset;
372 size_t eoi_tag_index = 0;
373 bool found_eoi_tag = searchForTagInBuffer(m_in_data + image_data_offset,
374 m_in_data + m_in_data_size, JPEG_MARKER_EOI, eoi_tag_index);
376 LOGE("Could not find EOI tag! Assume that there is no EOI and rest of "
377 "JPEG file contains image data stream: image_size+= 2");
378 image_size += 2; //Skip expected EOI tag which is not present
380 LOGD("EOI tag found at offset: %d from SOS data", eoi_tag_index);
382 if(eoi_tag_index != image_size) {
383 LOGW("Estimated image size:%d doesn't match EOI tag index:%d"
384 " delta:%d", image_size, eoi_tag_index,
385 image_size - eoi_tag_index);
387 LOGW("Setting image_size to EOI tag: %d", eoi_tag_index);
388 image_size = eoi_tag_index;
390 m_padding_data = m_image_data + image_size + 2; //(skip EOI tag)
391 m_padding_data_size = (m_in_data + m_in_data_size) - m_padding_data;
392 LOGW("Saving padding data from offset:%d with size:%d",
393 m_padding_data - m_in_data, m_padding_data_size);
397 m_image_size = image_size;
399 offset = image_data_offset + image_size;
400 LOGD("offset:%d tag:0x%x | SOS Offset moved to next marker", offset,
404 offset += section_data_len;
405 LOGD("offset:%d tag:0x%x | Offset moved to next marker", offset, cur_marker);
411 bool JpegFile::searchForTagInBuffer(const unsigned char* buffer_start,
412 const unsigned char* buffer_end,
413 const JpegMarker marker,
416 LOGD("Entered start:%p end:%p marker:0x%x", buffer_start, buffer_end, marker);
419 LOGE("buffer_start is NULL");
424 LOGE("buffer_end is NULL");
428 if(buffer_end <= buffer_start) {
429 LOGE("buffer_end: %p <= buffer_start: %p", buffer_end, buffer_start);
433 LOGD("Bytes to scan: %d", static_cast<size_t>(buffer_end - buffer_start));
434 const unsigned char marker_uchar = static_cast<unsigned char>(marker);
436 for(const unsigned char* ptr = buffer_start; ptr < buffer_end; ++ptr) {
438 if((0xff == *ptr) && (ptr+1 < buffer_end)) {
439 if(marker_uchar == *(ptr+1)) {
440 out_index = static_cast<size_t>(ptr - buffer_start);
450 void JpegFile::setNewExifData(ExifData* new_exif_data)
452 if (!new_exif_data) {
453 LOGE("Trying to set NULL exif_data!");
454 throw UnknownException("Could not save Exif in JPEG file");
457 JpegFileSectionPtr exif = getExifSection();
459 LOGW("Could't find Exif section - creating new one");
461 JpegFileSection* new_sec = new (std::nothrow) JpegFileSection();
463 LOGE("Couldn't allocate JpegFileSection");
464 throw UnknownException("Memory allocation failed");
466 new_sec->type = JPEG_MARKER_APP1;
468 exif = JpegFileSectionPtr(new_sec);
471 SectionsVec::iterator insert_it = m_sections.begin();
472 bool soi_is_present = false;
474 if (insert_it != m_sections.end()) {
475 if ((*insert_it)->type != JPEG_MARKER_SOI) {
476 LOGW("First section is not SOI - Start Of Image!");
479 soi_is_present = true;
483 if (!soi_is_present) {
484 LOGW("SOI section is missing");
485 throw UnknownException("JPEG file is invalid");
488 //Insert new Exif sections just after SOI
490 if (insert_it != m_sections.begin()) {
491 m_sections.insert(insert_it, exif);
494 //This shouldn't happen since we at lest need SOS and EOI sections
495 m_sections.push_back(exif);
499 //We don't want to save old data
500 exif->data_ptr = NULL;
503 exif_data_unref(exif->exif_data);
504 exif_data_ref (new_exif_data);
505 exif->exif_data = new_exif_data;
508 ExifData* JpegFile::getExifData()
510 JpegFileSectionPtr exif = getExifSection();
515 exif_data_ref(exif->exif_data);
516 return exif->exif_data;
519 void JpegFile::saveToFile(const std::string& out_path)
521 LOGD("Entered out_path:%s", out_path.c_str());
523 saveToFilePriv(out_path);
526 LOGE("Exception occured during saveToFilePriv "
527 "original file: [%] new: [%s]",
528 m_source_file_path.c_str(),
531 if (out_path == m_source_file_path) {
533 LOGD("Trying to recover broken JPEG file: [%s]", out_path.c_str());
534 //We were writing to source file and since something went wrong let's
535 //restore old file - we have it in m_in_data
537 FILE* outf = fopen(out_path.c_str(), "wb");
539 LOGE("Couldn't open output file: [%s] - JPEG file will not be restored!");
542 size_t bytes_wrote = fwrite(m_in_data, 1, m_in_data_size, outf);
543 if (bytes_wrote != m_in_data_size) {
544 LOGE("Couldn't restore whole JPEG! "
545 "Only %d of %d bytes have been wrote!",
546 bytes_wrote, m_in_data_size);
548 if (EOF == fclose(outf)) {
549 LOGE("Couldn't close restore output file: [%s]", out_path.c_str());
558 void JpegFile::saveToFilePriv(const std::string& out_path)
560 LOGD("Entered out_path:%s", out_path.c_str());
562 m_out_file = fopen(out_path.c_str(), "wb");
564 LOGE("Couldn't open output file: %s", out_path.c_str());
565 throw UnknownException("Could not write JPEG file");
568 unsigned char tmp_buf[128];
571 int section_index = 0;
572 for(SectionsVec::iterator it = m_sections.begin();
573 it != m_sections.end();
574 ++it, ++ section_index) {
576 JpegFileSectionPtr cur = *it;
577 const JpegMarker cur_marker = cur->type;
579 LOGD("offset:%d | Section: %d marker 0x%x", offset, section_index, cur_marker);
581 size_t bytes_to_write = 0;
582 size_t bytes_wrote = 0;
585 tmp_buf[1] = cur_marker;
588 bool write_section_data = false;
590 bool write_exif_data = false;
592 std::unique_ptr<unsigned char, CArrayDeleter> exif_output_data;
593 unsigned int exif_output_size = 0;
595 if (cur_marker != JPEG_MARKER_SOI &&
596 cur_marker != JPEG_MARKER_EOI) {
598 unsigned short section_size = 2;
599 if (JPEG_MARKER_APP1 && cur->exif_data) {
601 unsigned char* tmp = NULL;
602 exif_data_save_data (cur->exif_data, &tmp, &exif_output_size);
603 if (!tmp || 0 == exif_output_size) {
604 LOGE("Couldn't generate RAW Exif data!");
605 throw UnknownException("Could not save Exif in JPEG file");
608 LOGD("offset:%d | Generated Exif RAW Data length:%d", offset,
611 exif_output_data.reset(tmp);
613 if (exif_output_size > MAX_AVAILABLE_JPEG_SECTION_DATA_SIZE) {
614 LOGE("exif_output_size:%d is greater then maximum JPEG section"
615 "data block size: %d", exif_output_size,
616 MAX_AVAILABLE_JPEG_SECTION_DATA_SIZE);
617 throw UnknownException("Exif data is to big to be saved in JPEG file");
619 section_size += exif_output_size;
620 write_exif_data = true;
623 section_size += cur->size;
624 write_section_data = true;
627 writeUShortBE(section_size, tmp_buf + bytes_to_write);
631 LOGD("offset:%d | Writing section: marker:0x%x size:%d", offset, cur_marker,
634 bytes_wrote = fwrite(tmp_buf, 1, bytes_to_write, m_out_file);
635 offset += bytes_wrote;
637 if (bytes_wrote != bytes_to_write) {
638 LOGE("Couldn't wrote %d bytes! Only %d bytes wrote", bytes_to_write,
640 throw UnknownException("Could not write JPEG file");
643 if (write_section_data && cur->size > 0) {
644 LOGD("offset:%d | Writing data with length:%d", offset, cur->size);
646 bytes_to_write = cur->size;
647 bytes_wrote = fwrite(cur->data_ptr, 1, bytes_to_write, m_out_file);
648 offset += bytes_wrote;
650 if (bytes_wrote != bytes_to_write) {
651 LOGE("Couldn't wrote %d bytes! Only %d bytes wrote", bytes_to_write,
653 throw UnknownException("Could not write JPEG file");
657 if (write_exif_data && exif_output_data && exif_output_size > 0) {
658 LOGD("offset:%d | Writing new exif data with length:%d", offset,
661 bytes_to_write = exif_output_size;
662 bytes_wrote = fwrite(exif_output_data.get(), 1, bytes_to_write, m_out_file);
663 offset += bytes_wrote;
665 if (bytes_wrote != bytes_to_write) {
666 LOGE("Couldn't wrote %d bytes! Only %d bytes wrote", bytes_to_write,
668 throw UnknownException("Could not write JPEG file");
672 if (JPEG_MARKER_SOS == cur_marker) {
673 LOGD("offset:%d | Writing image data stream with lenght:%d", offset,
676 bytes_to_write = m_image_size;
677 bytes_wrote = fwrite(m_image_data, 1, bytes_to_write, m_out_file);
678 offset += bytes_wrote;
680 if (bytes_wrote != bytes_to_write) {
681 LOGE("Couldn't wrote %d bytes! Only %d bytes wrote", bytes_to_write,
683 throw UnknownException("Could not write JPEG file");
688 if(m_padding_data && m_padding_data_size > 0) {
689 LOGD("Padding data exists and contains:%d bytes saving to JPEG file");
690 const size_t bytes_wrote = fwrite(m_image_data, 1, m_padding_data_size,
693 if (bytes_wrote != m_padding_data_size) {
694 LOGE("Couldn't wrote %d bytes! Only %d bytes wrote", m_padding_data_size,
696 throw UnknownException("Could not write JPEG file");
700 if (fclose(m_out_file) == EOF) {
701 LOGE("Couldn't close output file: %s", out_path.c_str());
705 LOGD("Closed output file: %s wrote:%d bytes: %d", out_path.c_str(), offset);
709 JpegFileSectionPtr JpegFile::getExifSection()
711 size_t num_exif_sections = 0;
712 JpegFileSectionPtr first_exif_section;
714 for(SectionsVec::iterator it = m_sections.begin(); it != m_sections.end(); ++it) {
715 JpegFileSectionPtr cur = *it;
717 if (JPEG_MARKER_APP1 == cur->type) {
718 if (!cur->exif_data) {
719 LOGW("Warning: found APP1 section but exif_data is NULL (Not Exif?)");
724 if (!first_exif_section) {
725 first_exif_section = cur;
728 LOGW("Warning: found %d APP1/Exif sections - only first is currently supported!");
733 return first_exif_section;
738 } // namespace DeviceAPI