Index: src/codec/SkJpegCodec.cpp |
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp |
index a342cd8b6a6e9def55440b7f970ab3abc7a2f486..e920de1956f4db76f75b66b5a087058d0ec25a03 100644 |
--- a/src/codec/SkJpegCodec.cpp |
+++ b/src/codec/SkJpegCodec.cpp |
@@ -29,6 +29,160 @@ bool SkJpegCodec::IsJpeg(const void* buffer, size_t bytesRead) { |
return bytesRead >= 3 && !memcmp(buffer, jpegSig, sizeof(jpegSig)); |
} |
+static uint32_t get_endian_int(const uint8_t* data, bool littleEndian) { |
+ if (littleEndian) { |
+ return (data[3] << 24) | (data[2] << 16) | (data[1] << 8) | (data[0]); |
+ } |
+ |
+ return (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | (data[3]); |
+} |
+ |
+const uint32_t kExifHeaderSize = 14; |
+const uint32_t kICCHeaderSize = 14; |
+const uint32_t kExifMarker = JPEG_APP0 + 1; |
+const uint32_t kICCMarker = JPEG_APP0 + 2; |
+ |
+static bool is_orientation_marker(jpeg_marker_struct* marker, SkCodec::Origin* orientation) { |
+ if (kExifMarker != marker->marker || marker->data_length < kExifHeaderSize) { |
+ return false; |
+ } |
+ |
+ const uint8_t* data = marker->data; |
+ static const uint8_t kExifSig[] { 'E', 'x', 'i', 'f', '\0' }; |
+ if (memcmp(data, kExifSig, sizeof(kExifSig))) { |
+ return false; |
+ } |
+ |
+ bool littleEndian; |
+ if (!is_valid_endian_marker(data + 6, &littleEndian)) { |
+ return false; |
+ } |
+ |
+ // Get the offset from the start of the marker. |
+ // Account for 'E', 'x', 'i', 'f', '\0', '<fill byte>'. |
+ uint32_t offset = get_endian_int(data + 10, littleEndian); |
+ offset += sizeof(kExifSig) + 1; |
+ |
+ // Require that the marker is at least large enough to contain the number of entries. |
+ if (marker->data_length < offset + 2) { |
+ return false; |
+ } |
+ uint32_t numEntries = get_endian_short(data + offset, littleEndian); |
+ |
+ // Tag (2 bytes), Datatype (2 bytes), Number of elements (4 bytes), Data (4 bytes) |
+ const uint32_t kEntrySize = 12; |
+ numEntries = SkTMin(numEntries, (marker->data_length - offset - 2) / kEntrySize); |
+ |
+ // Advance the data to the start of the entries. |
+ data += offset + 2; |
+ |
+ const uint16_t kOriginTag = 0x112; |
+ const uint16_t kOriginType = 3; |
+ for (uint32_t i = 0; i < numEntries; i++, data += kEntrySize) { |
+ uint16_t tag = get_endian_short(data, littleEndian); |
+ uint16_t type = get_endian_short(data + 2, littleEndian); |
+ uint32_t count = get_endian_int(data + 4, littleEndian); |
+ if (kOriginTag == tag && kOriginType == type && 1 == count) { |
+ uint16_t val = get_endian_short(data + 8, littleEndian); |
+ if (0 < val && val <= SkCodec::kLast_Origin) { |
+ *orientation = (SkCodec::Origin) val; |
+ return true; |
+ } |
+ } |
+ } |
+ |
+ return false; |
+} |
+ |
+static SkCodec::Origin get_exif_orientation(jpeg_decompress_struct* dinfo) { |
+ SkCodec::Origin orientation; |
+ for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { |
+ if (is_orientation_marker(marker, &orientation)) { |
+ return orientation; |
+ } |
+ } |
+ |
+ return SkCodec::kDefault_Origin; |
+} |
+ |
+static bool is_icc_marker(jpeg_marker_struct* marker) { |
+ if (kICCMarker != marker->marker || marker->data_length < kICCHeaderSize) { |
+ return false; |
+ } |
+ |
+ static const uint8_t kICCSig[] { 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0' }; |
+ return !memcmp(marker->data, kICCSig, sizeof(kICCSig)); |
+} |
+ |
+/* |
+ * ICC profiles may be stored using a sequence of multiple markers. We obtain the ICC profile |
+ * in two steps: |
+ * (1) Discover all ICC profile markers and verify that they are numbered properly. |
+ * (2) Copy the data from each marker into a contiguous ICC profile. |
+ */ |
+static sk_sp<SkColorSpace> get_icc_profile(jpeg_decompress_struct* dinfo) { |
+ // Note that 256 will be enough storage space since each markerIndex is stored in 8-bits. |
+ jpeg_marker_struct* markerSequence[256]; |
+ memset(markerSequence, 0, sizeof(markerSequence)); |
+ uint8_t numMarkers = 0; |
+ size_t totalBytes = 0; |
+ |
+ // Discover any ICC markers and verify that they are numbered properly. |
+ for (jpeg_marker_struct* marker = dinfo->marker_list; marker; marker = marker->next) { |
+ if (is_icc_marker(marker)) { |
+ // Verify that numMarkers is valid and consistent. |
+ if (0 == numMarkers) { |
+ numMarkers = marker->data[13]; |
+ if (0 == numMarkers) { |
+ SkCodecPrintf("ICC Profile Error: numMarkers must be greater than zero.\n"); |
+ return nullptr; |
+ } |
+ } else if (numMarkers != marker->data[13]) { |
+ SkCodecPrintf("ICC Profile Error: numMarkers must be consistent.\n"); |
+ return nullptr; |
+ } |
+ |
+ // Verify that the markerIndex is valid and unique. Note that zero is not |
+ // a valid index. |
+ uint8_t markerIndex = marker->data[12]; |
+ if (markerIndex == 0 || markerIndex > numMarkers) { |
+ SkCodecPrintf("ICC Profile Error: markerIndex is invalid.\n"); |
+ return nullptr; |
+ } |
+ if (markerSequence[markerIndex]) { |
+ SkCodecPrintf("ICC Profile Error: Duplicate value of markerIndex.\n"); |
+ return nullptr; |
+ } |
+ markerSequence[markerIndex] = marker; |
+ SkASSERT(marker->data_length >= kICCHeaderSize); |
+ totalBytes += marker->data_length - kICCHeaderSize; |
+ } |
+ } |
+ |
+ if (0 == totalBytes) { |
+ // No non-empty ICC profile markers were found. |
+ return nullptr; |
+ } |
+ |
+ // Combine the ICC marker data into a contiguous profile. |
+ SkAutoMalloc iccData(totalBytes); |
+ void* dst = iccData.get(); |
+ for (uint32_t i = 1; i <= numMarkers; i++) { |
+ jpeg_marker_struct* marker = markerSequence[i]; |
+ if (!marker) { |
+ SkCodecPrintf("ICC Profile Error: Missing marker %d of %d.\n", i, numMarkers); |
+ return nullptr; |
+ } |
+ |
+ void* src = SkTAddOffset<void>(marker->data, kICCHeaderSize); |
+ size_t bytes = marker->data_length - kICCHeaderSize; |
+ memcpy(dst, src, bytes); |
+ dst = SkTAddOffset<void>(dst, bytes); |
+ } |
+ |
+ return SkColorSpace::NewICC(iccData.get(), totalBytes); |
+} |
+ |
bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, |
JpegDecoderMgr** decoderMgrOut) { |
@@ -43,19 +197,32 @@ bool SkJpegCodec::ReadHeader(SkStream* stream, SkCodec** codecOut, |
// Initialize the decompress info and the source manager |
decoderMgr->init(); |
+ // Instruct jpeg library to save the markers that we care about. Since |
+ // the orientation and color profile will not change, we can skip this |
+ // step on rewinds. |
+ if (codecOut) { |
+ jpeg_save_markers(decoderMgr->dinfo(), kExifMarker, 0xFFFF); |
+ jpeg_save_markers(decoderMgr->dinfo(), kICCMarker, 0xFFFF); |
+ } |
+ |
// Read the jpeg header |
if (JPEG_HEADER_OK != jpeg_read_header(decoderMgr->dinfo(), true)) { |
return decoderMgr->returnFalse("read_header"); |
} |
- if (nullptr != codecOut) { |
+ if (codecOut) { |
// Recommend the color type to decode to |
const SkColorType colorType = decoderMgr->getColorType(); |
// Create image info object and the codec |
const SkImageInfo& imageInfo = SkImageInfo::Make(decoderMgr->dinfo()->image_width, |
decoderMgr->dinfo()->image_height, colorType, kOpaque_SkAlphaType); |
- *codecOut = new SkJpegCodec(imageInfo, stream, decoderMgr.release()); |
+ |
+ Origin orientation = get_exif_orientation(decoderMgr->dinfo()); |
+ sk_sp<SkColorSpace> colorSpace = get_icc_profile(decoderMgr->dinfo()); |
+ |
+ *codecOut = new SkJpegCodec(imageInfo, stream, decoderMgr.release(), colorSpace, |
+ orientation); |
} else { |
SkASSERT(nullptr != decoderMgrOut); |
*decoderMgrOut = decoderMgr.release(); |
@@ -76,8 +243,8 @@ SkCodec* SkJpegCodec::NewFromStream(SkStream* stream) { |
} |
SkJpegCodec::SkJpegCodec(const SkImageInfo& srcInfo, SkStream* stream, |
- JpegDecoderMgr* decoderMgr) |
- : INHERITED(srcInfo, stream) |
+ JpegDecoderMgr* decoderMgr, sk_sp<SkColorSpace> colorSpace, Origin origin) |
+ : INHERITED(srcInfo, stream, colorSpace, origin) |
, fDecoderMgr(decoderMgr) |
, fReadyState(decoderMgr->dinfo()->global_state) |
, fSwizzlerSubset(SkIRect::MakeEmpty()) |