mirror of
				https://github.com/webmproject/libwebp.git
				synced 2025-10-31 10:25:46 +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_) { | ||||
|       ok = ReadPNG(in_file, pic, keep_alpha); | ||||
|     } else if (format == JPEG_) { | ||||
|       ok = ReadJPEG(in_file, pic); | ||||
|       ok = ReadJPEG(in_file, pic, metadata); | ||||
|     } else if (format == TIFF_) { | ||||
|       ok = ReadTIFF(filename, pic, keep_alpha); | ||||
|     } | ||||
|   | ||||
| @@ -22,6 +22,178 @@ | ||||
| #include <string.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 jpeg_error_mgr pub; | ||||
| @@ -34,7 +206,7 @@ static void my_error_exit(j_common_ptr dinfo) { | ||||
|   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 stride, width, height; | ||||
|   struct jpeg_decompress_struct dinfo; | ||||
| @@ -47,12 +219,14 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | ||||
|  | ||||
|   if (setjmp(jerr.setjmp_buffer)) { | ||||
|  Error: | ||||
|     MetadataFree(metadata); | ||||
|     jpeg_destroy_decompress(&dinfo); | ||||
|     goto End; | ||||
|   } | ||||
|  | ||||
|   jpeg_create_decompress(&dinfo); | ||||
|   jpeg_stdio_src(&dinfo, in_file); | ||||
|   if (metadata != NULL) SaveMetadataMarkers(&dinfo); | ||||
|   jpeg_read_header(&dinfo, TRUE); | ||||
|  | ||||
|   dinfo.out_color_space = JCS_RGB; | ||||
| @@ -82,6 +256,14 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | ||||
|     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_destroy_decompress(&dinfo); | ||||
|  | ||||
| @@ -89,15 +271,18 @@ int ReadJPEG(FILE* in_file, WebPPicture* const pic) { | ||||
|   pic->width = width; | ||||
|   pic->height = height; | ||||
|   ok = WebPPictureImportRGB(pic, rgb, stride); | ||||
|   if (!ok) goto Error; | ||||
|  | ||||
|  End: | ||||
|   free(rgb); | ||||
|   return ok; | ||||
| } | ||||
| #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)pic; | ||||
|   (void)metadata; | ||||
|   fprintf(stderr, "JPEG support not compiled. Please install the libjpeg " | ||||
|           "development package before building.\n"); | ||||
|   return 0; | ||||
|   | ||||
| @@ -17,12 +17,14 @@ | ||||
| extern "C" { | ||||
| #endif | ||||
|  | ||||
| struct Metadata; | ||||
| struct WebPPicture; | ||||
|  | ||||
| // Reads a JPEG from 'in_file', returning the decoded output in 'pic'. | ||||
| // The output is RGB. | ||||
| // 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) | ||||
| }    // extern "C" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user