mirror of
				https://github.com/webmproject/libwebp.git
				synced 2025-10-31 02:15:42 +01:00 
			
		
		
		
	cwebp/jpegdec: add JPEG metadata extraction
the values of EXIF/XMP/ICC are unused and Extended XMP for payloads > 64k is unsupported. Change-Id: If721aa2009335ce090148b7ecd7ea8459f9b942d
This commit is contained in:
		| @@ -332,7 +332,7 @@ static int ReadPicture(const char* const filename, WebPPicture* const pic, | |||||||
|     if (format == PNG_) { |     if (format == PNG_) { | ||||||
|       ok = ReadPNG(in_file, pic, keep_alpha); |       ok = ReadPNG(in_file, pic, keep_alpha); | ||||||
|     } else if (format == JPEG_) { |     } else if (format == JPEG_) { | ||||||
|       ok = ReadJPEG(in_file, pic); |       ok = ReadJPEG(in_file, pic, metadata); | ||||||
|     } else if (format == TIFF_) { |     } else if (format == TIFF_) { | ||||||
|       ok = ReadTIFF(filename, pic, keep_alpha); |       ok = ReadTIFF(filename, pic, keep_alpha); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -22,6 +22,178 @@ | |||||||
| #include <string.h> | #include <string.h> | ||||||
|  |  | ||||||
| #include "webp/encode.h" | #include "webp/encode.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 (%zu) / count (%d) / sequence number (%d)" | ||||||
|  |                         " cannot be 0!\n", | ||||||
|  |                 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 my_error_mgr { | ||||||
|   struct jpeg_error_mgr pub; |   struct jpeg_error_mgr pub; | ||||||
| @@ -34,7 +206,7 @@ static void my_error_exit(j_common_ptr dinfo) { | |||||||
|   longjmp(myerr->setjmp_buffer, 1); |   longjmp(myerr->setjmp_buffer, 1); | ||||||
| } | } | ||||||
|  |  | ||||||
| int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | int ReadJPEG(FILE* in_file, WebPPicture* const pic, Metadata* const metadata) { | ||||||
|   int ok = 0; |   int ok = 0; | ||||||
|   int stride, width, height; |   int stride, width, height; | ||||||
|   struct jpeg_decompress_struct dinfo; |   struct jpeg_decompress_struct dinfo; | ||||||
| @@ -47,12 +219,14 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | |||||||
|  |  | ||||||
|   if (setjmp(jerr.setjmp_buffer)) { |   if (setjmp(jerr.setjmp_buffer)) { | ||||||
|  Error: |  Error: | ||||||
|  |     MetadataFree(metadata); | ||||||
|     jpeg_destroy_decompress(&dinfo); |     jpeg_destroy_decompress(&dinfo); | ||||||
|     goto End; |     goto End; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   jpeg_create_decompress(&dinfo); |   jpeg_create_decompress(&dinfo); | ||||||
|   jpeg_stdio_src(&dinfo, in_file); |   jpeg_stdio_src(&dinfo, in_file); | ||||||
|  |   if (metadata != NULL) SaveMetadataMarkers(&dinfo); | ||||||
|   jpeg_read_header(&dinfo, TRUE); |   jpeg_read_header(&dinfo, TRUE); | ||||||
|  |  | ||||||
|   dinfo.out_color_space = JCS_RGB; |   dinfo.out_color_space = JCS_RGB; | ||||||
| @@ -82,6 +256,14 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | |||||||
|     buffer[0] += stride; |     buffer[0] += stride; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   if (metadata != NULL) { | ||||||
|  |     ok = ExtractMetadataFromJPEG(&dinfo, metadata); | ||||||
|  |     if (!ok) { | ||||||
|  |       fprintf(stderr, "Error extracting JPEG metadata!\n"); | ||||||
|  |       goto Error; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   jpeg_finish_decompress(&dinfo); |   jpeg_finish_decompress(&dinfo); | ||||||
|   jpeg_destroy_decompress(&dinfo); |   jpeg_destroy_decompress(&dinfo); | ||||||
|  |  | ||||||
| @@ -89,15 +271,18 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | |||||||
|   pic->width = width; |   pic->width = width; | ||||||
|   pic->height = height; |   pic->height = height; | ||||||
|   ok = WebPPictureImportRGB(pic, rgb, stride); |   ok = WebPPictureImportRGB(pic, rgb, stride); | ||||||
|  |   if (!ok) goto Error; | ||||||
|  |  | ||||||
|  End: |  End: | ||||||
|   free(rgb); |   free(rgb); | ||||||
|   return ok; |   return ok; | ||||||
| } | } | ||||||
| #else  // !WEBP_HAVE_JPEG | #else  // !WEBP_HAVE_JPEG | ||||||
| int ReadJPEG(FILE* in_file, struct WebPPicture* const pic) { | int ReadJPEG(FILE* in_file, struct WebPPicture* const pic, | ||||||
|  |              struct Metadata* const metadata) { | ||||||
|   (void)in_file; |   (void)in_file; | ||||||
|   (void)pic; |   (void)pic; | ||||||
|  |   (void)metadata; | ||||||
|   fprintf(stderr, "JPEG support not compiled. Please install the libjpeg " |   fprintf(stderr, "JPEG support not compiled. Please install the libjpeg " | ||||||
|           "development package before building.\n"); |           "development package before building.\n"); | ||||||
|   return 0; |   return 0; | ||||||
|   | |||||||
| @@ -17,12 +17,14 @@ | |||||||
| extern "C" { | extern "C" { | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | struct Metadata; | ||||||
| struct WebPPicture; | struct WebPPicture; | ||||||
|  |  | ||||||
| // Reads a JPEG from 'in_file', returning the decoded output in 'pic'. | // Reads a JPEG from 'in_file', returning the decoded output in 'pic'. | ||||||
| // The output is RGB. | // The output is RGB. | ||||||
| // Returns true on success. | // Returns true on success. | ||||||
| int ReadJPEG(FILE* in_file, struct WebPPicture* const pic); | int ReadJPEG(FILE* in_file, struct WebPPicture* const pic, | ||||||
|  |              struct Metadata* const metadata); | ||||||
|  |  | ||||||
| #if defined(__cplusplus) || defined(c_plusplus) | #if defined(__cplusplus) || defined(c_plusplus) | ||||||
| }    // extern "C" | }    // extern "C" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user