diff --git a/examples/cwebp.c b/examples/cwebp.c index 745380d4..21309cb8 100644 --- a/examples/cwebp.c +++ b/examples/cwebp.c @@ -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); } diff --git a/examples/jpegdec.c b/examples/jpegdec.c index d6f35c52..a9bb7793 100644 --- a/examples/jpegdec.c +++ b/examples/jpegdec.c @@ -22,6 +22,178 @@ #include #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' 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; diff --git a/examples/jpegdec.h b/examples/jpegdec.h index 22396200..afd54175 100644 --- a/examples/jpegdec.h +++ b/examples/jpegdec.h @@ -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"