| // Copyright 2012 Google Inc. All Rights Reserved. | 
 | // | 
 | // Use of this source code is governed by a BSD-style license | 
 | // that can be found in the COPYING file in the root of the source | 
 | // tree. An additional intellectual property rights grant can be found | 
 | // in the file PATENTS. All contributing project authors may | 
 | // be found in the AUTHORS file in the root of the source tree. | 
 | // ----------------------------------------------------------------------------- | 
 | // | 
 | // JPEG decode. | 
 |  | 
 | #include "./jpegdec.h" | 
 |  | 
 | #ifdef HAVE_CONFIG_H | 
 | #include "webp/config.h" | 
 | #endif | 
 |  | 
 | #include <stdio.h> | 
 |  | 
 | #ifdef WEBP_HAVE_JPEG | 
 | #include <jpeglib.h> | 
 | #include <jerror.h> | 
 | #include <setjmp.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 |  | 
 | #include "webp/encode.h" | 
 | #include "./imageio_util.h" | 
 | #include "./metadata.h" | 
 |  | 
 | // ----------------------------------------------------------------------------- | 
 | // Metadata processing | 
 |  | 
 | #ifndef JPEG_APP1 | 
 | # define JPEG_APP1 (JPEG_APP0 + 1) | 
 | #endif | 
 | #ifndef JPEG_APP2 | 
 | # define JPEG_APP2 (JPEG_APP0 + 2) | 
 | #endif | 
 |  | 
 | typedef struct { | 
 |   const uint8_t* data; | 
 |   size_t data_length; | 
 |   int seq;  // this segment's sequence number [1, 255] for use in reassembly. | 
 | } ICCPSegment; | 
 |  | 
 | static void SaveMetadataMarkers(j_decompress_ptr dinfo) { | 
 |   const unsigned int max_marker_length = 0xffff; | 
 |   jpeg_save_markers(dinfo, JPEG_APP1, max_marker_length);  // Exif/XMP | 
 |   jpeg_save_markers(dinfo, JPEG_APP2, max_marker_length);  // ICC profile | 
 | } | 
 |  | 
 | static int CompareICCPSegments(const void* a, const void* b) { | 
 |   const ICCPSegment* s1 = (const ICCPSegment*)a; | 
 |   const ICCPSegment* s2 = (const ICCPSegment*)b; | 
 |   return s1->seq - s2->seq; | 
 | } | 
 |  | 
 | // Extract ICC profile segments from the marker list in 'dinfo', reassembling | 
 | // and storing them in 'iccp'. | 
 | // Returns true on success and false for memory errors and corrupt profiles. | 
 | static int StoreICCP(j_decompress_ptr dinfo, MetadataPayload* const iccp) { | 
 |   // ICC.1:2010-12 (4.3.0.0) Annex B.4 Embedding ICC Profiles in JPEG files | 
 |   static const char kICCPSignature[] = "ICC_PROFILE"; | 
 |   static const size_t kICCPSignatureLength = 12;  // signature includes '\0' | 
 |   static const size_t kICCPSkipLength = 14;  // signature + seq & count | 
 |   int expected_count = 0; | 
 |   int actual_count = 0; | 
 |   int seq_max = 0; | 
 |   size_t total_size = 0; | 
 |   ICCPSegment iccp_segments[255]; | 
 |   jpeg_saved_marker_ptr marker; | 
 |  | 
 |   memset(iccp_segments, 0, sizeof(iccp_segments)); | 
 |   for (marker = dinfo->marker_list; marker != NULL; marker = marker->next) { | 
 |     if (marker->marker == JPEG_APP2 && | 
 |         marker->data_length > kICCPSkipLength && | 
 |         !memcmp(marker->data, kICCPSignature, kICCPSignatureLength)) { | 
 |       // ICC_PROFILE\0<seq><count>; 'seq' starts at 1. | 
 |       const int seq = marker->data[kICCPSignatureLength]; | 
 |       const int count = marker->data[kICCPSignatureLength + 1]; | 
 |       const size_t segment_size = marker->data_length - kICCPSkipLength; | 
 |       ICCPSegment* segment; | 
 |  | 
 |       if (segment_size == 0 || count == 0 || seq == 0) { | 
 |         fprintf(stderr, "[ICCP] size (%d) / count (%d) / sequence number (%d)" | 
 |                         " cannot be 0!\n", | 
 |                 (int)segment_size, seq, count); | 
 |         return 0; | 
 |       } | 
 |  | 
 |       if (expected_count == 0) { | 
 |         expected_count = count; | 
 |       } else if (expected_count != count) { | 
 |         fprintf(stderr, "[ICCP] Inconsistent segment count (%d / %d)!\n", | 
 |                 expected_count, count); | 
 |         return 0; | 
 |       } | 
 |  | 
 |       segment = iccp_segments + seq - 1; | 
 |       if (segment->data_length != 0) { | 
 |         fprintf(stderr, "[ICCP] Duplicate segment number (%d)!\n" , seq); | 
 |         return 0; | 
 |       } | 
 |  | 
 |       segment->data = marker->data + kICCPSkipLength; | 
 |       segment->data_length = segment_size; | 
 |       segment->seq = seq; | 
 |       total_size += segment_size; | 
 |       if (seq > seq_max) seq_max = seq; | 
 |       ++actual_count; | 
 |     } | 
 |   } | 
 |  | 
 |   if (actual_count == 0) return 1; | 
 |   if (seq_max != actual_count) { | 
 |     fprintf(stderr, "[ICCP] Discontinuous segments, expected: %d actual: %d!\n", | 
 |             actual_count, seq_max); | 
 |     return 0; | 
 |   } | 
 |   if (expected_count != actual_count) { | 
 |     fprintf(stderr, "[ICCP] Segment count: %d does not match expected: %d!\n", | 
 |             actual_count, expected_count); | 
 |     return 0; | 
 |   } | 
 |  | 
 |   // The segments may appear out of order in the file, sort them based on | 
 |   // sequence number before assembling the payload. | 
 |   qsort(iccp_segments, actual_count, sizeof(*iccp_segments), | 
 |         CompareICCPSegments); | 
 |  | 
 |   iccp->bytes = (uint8_t*)malloc(total_size); | 
 |   if (iccp->bytes == NULL) return 0; | 
 |   iccp->size = total_size; | 
 |  | 
 |   { | 
 |     int i; | 
 |     size_t offset = 0; | 
 |     for (i = 0; i < seq_max; ++i) { | 
 |       memcpy(iccp->bytes + offset, | 
 |              iccp_segments[i].data, iccp_segments[i].data_length); | 
 |       offset += iccp_segments[i].data_length; | 
 |     } | 
 |   } | 
 |   return 1; | 
 | } | 
 |  | 
 | // Returns true on success and false for memory errors and corrupt profiles. | 
 | // The caller must use MetadataFree() on 'metadata' in all cases. | 
 | static int ExtractMetadataFromJPEG(j_decompress_ptr dinfo, | 
 |                                    Metadata* const metadata) { | 
 |   static const struct { | 
 |     int marker; | 
 |     const char* signature; | 
 |     size_t signature_length; | 
 |     size_t storage_offset; | 
 |   } kJPEGMetadataMap[] = { | 
 |     // Exif 2.2 Section 4.7.2 Interoperability Structure of APP1 ... | 
 |     { JPEG_APP1, "Exif\0",                        6, METADATA_OFFSET(exif) }, | 
 |     // XMP Specification Part 3 Section 3 Embedding XMP Metadata ... #JPEG | 
 |     // TODO(jzern) Add support for 'ExtendedXMP' | 
 |     { JPEG_APP1, "http://ns.adobe.com/xap/1.0/", 29, METADATA_OFFSET(xmp) }, | 
 |     { 0, NULL, 0, 0 }, | 
 |   }; | 
 |   jpeg_saved_marker_ptr marker; | 
 |   // Treat ICC profiles separately as they may be segmented and out of order. | 
 |   if (!StoreICCP(dinfo, &metadata->iccp)) return 0; | 
 |  | 
 |   for (marker = dinfo->marker_list; marker != NULL; marker = marker->next) { | 
 |     int i; | 
 |     for (i = 0; kJPEGMetadataMap[i].marker != 0; ++i) { | 
 |       if (marker->marker == kJPEGMetadataMap[i].marker && | 
 |           marker->data_length > kJPEGMetadataMap[i].signature_length && | 
 |           !memcmp(marker->data, kJPEGMetadataMap[i].signature, | 
 |                   kJPEGMetadataMap[i].signature_length)) { | 
 |         MetadataPayload* const payload = | 
 |             (MetadataPayload*)((uint8_t*)metadata + | 
 |                                kJPEGMetadataMap[i].storage_offset); | 
 |  | 
 |         if (payload->bytes == NULL) { | 
 |           const char* marker_data = (const char*)marker->data + | 
 |                                     kJPEGMetadataMap[i].signature_length; | 
 |           const size_t marker_data_length = | 
 |               marker->data_length - kJPEGMetadataMap[i].signature_length; | 
 |           if (!MetadataCopy(marker_data, marker_data_length, payload)) return 0; | 
 |         } else { | 
 |           fprintf(stderr, "Ignoring additional '%s' marker\n", | 
 |                   kJPEGMetadataMap[i].signature); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |   return 1; | 
 | } | 
 |  | 
 | #undef JPEG_APP1 | 
 | #undef JPEG_APP2 | 
 |  | 
 | // ----------------------------------------------------------------------------- | 
 | // JPEG decoding | 
 |  | 
 | struct my_error_mgr { | 
 |   struct jpeg_error_mgr pub; | 
 |   jmp_buf setjmp_buffer; | 
 | }; | 
 |  | 
 | static void my_error_exit(j_common_ptr dinfo) { | 
 |   struct my_error_mgr* myerr = (struct my_error_mgr*)dinfo->err; | 
 |   dinfo->err->output_message(dinfo); | 
 |   longjmp(myerr->setjmp_buffer, 1); | 
 | } | 
 |  | 
 | typedef struct { | 
 |   struct jpeg_source_mgr pub; | 
 |   const uint8_t* data; | 
 |   size_t data_size; | 
 | } JPEGReadContext; | 
 |  | 
 | static void ContextInit(j_decompress_ptr cinfo) { | 
 |   JPEGReadContext* const ctx = (JPEGReadContext*)cinfo->src; | 
 |   ctx->pub.next_input_byte = ctx->data; | 
 |   ctx->pub.bytes_in_buffer = ctx->data_size; | 
 | } | 
 |  | 
 | static boolean ContextFill(j_decompress_ptr cinfo) { | 
 |   // we shouldn't get here. | 
 |   ERREXIT(cinfo, JERR_FILE_READ); | 
 |   return FALSE; | 
 | } | 
 |  | 
 | static void ContextSkip(j_decompress_ptr cinfo, long jump_size) { | 
 |   JPEGReadContext* const ctx = (JPEGReadContext*)cinfo->src; | 
 |   size_t jump = (size_t)jump_size; | 
 |   if (jump > ctx->pub.bytes_in_buffer) {  // Don't overflow the buffer. | 
 |     jump = ctx->pub.bytes_in_buffer; | 
 |   } | 
 |   ctx->pub.bytes_in_buffer -= jump; | 
 |   ctx->pub.next_input_byte += jump; | 
 | } | 
 |  | 
 | static void ContextTerm(j_decompress_ptr cinfo) { | 
 |   (void)cinfo; | 
 | } | 
 |  | 
 | static void ContextSetup(volatile struct jpeg_decompress_struct* const cinfo, | 
 |                          JPEGReadContext* const ctx) { | 
 |   cinfo->src = (struct jpeg_source_mgr*)ctx; | 
 |   ctx->pub.init_source = ContextInit; | 
 |   ctx->pub.fill_input_buffer = ContextFill; | 
 |   ctx->pub.skip_input_data = ContextSkip; | 
 |   ctx->pub.resync_to_restart = jpeg_resync_to_restart; | 
 |   ctx->pub.term_source = ContextTerm; | 
 |   ctx->pub.bytes_in_buffer = 0; | 
 |   ctx->pub.next_input_byte = NULL; | 
 | } | 
 |  | 
 | int ReadJPEG(const uint8_t* const data, size_t data_size, | 
 |              WebPPicture* const pic, int keep_alpha, | 
 |              Metadata* const metadata) { | 
 |   volatile int ok = 0; | 
 |   int width, height; | 
 |   int64_t stride; | 
 |   volatile struct jpeg_decompress_struct dinfo; | 
 |   struct my_error_mgr jerr; | 
 |   uint8_t* volatile rgb = NULL; | 
 |   JSAMPROW buffer[1]; | 
 |   JPEGReadContext ctx; | 
 |  | 
 |   if (data == NULL || data_size == 0 || pic == NULL) return 0; | 
 |  | 
 |   (void)keep_alpha; | 
 |   memset(&ctx, 0, sizeof(ctx)); | 
 |   ctx.data = data; | 
 |   ctx.data_size = data_size; | 
 |  | 
 |   memset((j_decompress_ptr)&dinfo, 0, sizeof(dinfo));   // for setjmp sanity | 
 |   dinfo.err = jpeg_std_error(&jerr.pub); | 
 |   jerr.pub.error_exit = my_error_exit; | 
 |  | 
 |   if (setjmp(jerr.setjmp_buffer)) { | 
 |  Error: | 
 |     MetadataFree(metadata); | 
 |     jpeg_destroy_decompress((j_decompress_ptr)&dinfo); | 
 |     goto End; | 
 |   } | 
 |  | 
 |   jpeg_create_decompress((j_decompress_ptr)&dinfo); | 
 |   ContextSetup(&dinfo, &ctx); | 
 |   if (metadata != NULL) SaveMetadataMarkers((j_decompress_ptr)&dinfo); | 
 |   jpeg_read_header((j_decompress_ptr)&dinfo, TRUE); | 
 |  | 
 |   dinfo.out_color_space = JCS_RGB; | 
 |   dinfo.do_fancy_upsampling = TRUE; | 
 |  | 
 |   jpeg_start_decompress((j_decompress_ptr)&dinfo); | 
 |  | 
 |   if (dinfo.output_components != 3) { | 
 |     goto Error; | 
 |   } | 
 |  | 
 |   width = dinfo.output_width; | 
 |   height = dinfo.output_height; | 
 |   stride = (int64_t)dinfo.output_width * dinfo.output_components * sizeof(*rgb); | 
 |  | 
 |   if (stride != (int)stride || | 
 |       !ImgIoUtilCheckSizeArgumentsOverflow(stride, height)) { | 
 |     goto Error; | 
 |   } | 
 |  | 
 |   rgb = (uint8_t*)malloc((size_t)stride * height); | 
 |   if (rgb == NULL) { | 
 |     goto Error; | 
 |   } | 
 |   buffer[0] = (JSAMPLE*)rgb; | 
 |  | 
 |   while (dinfo.output_scanline < dinfo.output_height) { | 
 |     if (jpeg_read_scanlines((j_decompress_ptr)&dinfo, buffer, 1) != 1) { | 
 |       goto Error; | 
 |     } | 
 |     buffer[0] += stride; | 
 |   } | 
 |  | 
 |   if (metadata != NULL) { | 
 |     ok = ExtractMetadataFromJPEG((j_decompress_ptr)&dinfo, metadata); | 
 |     if (!ok) { | 
 |       fprintf(stderr, "Error extracting JPEG metadata!\n"); | 
 |       goto Error; | 
 |     } | 
 |   } | 
 |  | 
 |   jpeg_finish_decompress((j_decompress_ptr)&dinfo); | 
 |   jpeg_destroy_decompress((j_decompress_ptr)&dinfo); | 
 |  | 
 |   // WebP conversion. | 
 |   pic->width = width; | 
 |   pic->height = height; | 
 |   ok = WebPPictureImportRGB(pic, rgb, (int)stride); | 
 |   if (!ok) goto Error; | 
 |  | 
 |  End: | 
 |   free(rgb); | 
 |   return ok; | 
 | } | 
 | #else  // !WEBP_HAVE_JPEG | 
 | int ReadJPEG(const uint8_t* const data, size_t data_size, | 
 |              struct WebPPicture* const pic, int keep_alpha, | 
 |              struct Metadata* const metadata) { | 
 |   (void)data; | 
 |   (void)data_size; | 
 |   (void)pic; | 
 |   (void)keep_alpha; | 
 |   (void)metadata; | 
 |   fprintf(stderr, "JPEG support not compiled. Please install the libjpeg " | 
 |           "development package before building.\n"); | 
 |   return 0; | 
 | } | 
 | #endif  // WEBP_HAVE_JPEG | 
 |  | 
 | // ----------------------------------------------------------------------------- |