|  | /* | 
|  | * Copyright 2022 The Android Open Source Project | 
|  | * | 
|  | * Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | * you may not use this file except in compliance with the License. | 
|  | * You may obtain a copy of the License at | 
|  | * | 
|  | *      http://www.apache.org/licenses/LICENSE-2.0 | 
|  | * | 
|  | * Unless required by applicable law or agreed to in writing, software | 
|  | * distributed under the License is distributed on an "AS IS" BASIS, | 
|  | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | * See the License for the specific language governing permissions and | 
|  | * limitations under the License. | 
|  | */ | 
|  |  | 
|  | #include <jpegrecoverymap/jpegdecoder.h> | 
|  |  | 
|  | #include <cutils/log.h> | 
|  |  | 
|  | #include <errno.h> | 
|  | #include <setjmp.h> | 
|  | #include <string> | 
|  |  | 
|  | using namespace std; | 
|  |  | 
|  | namespace android::recoverymap { | 
|  |  | 
|  | const uint32_t kAPP0Marker = JPEG_APP0;      // JFIF | 
|  | const uint32_t kAPP1Marker = JPEG_APP0 + 1;  // EXIF, XMP | 
|  | const uint32_t kAPP2Marker = JPEG_APP0 + 2;  // ICC | 
|  |  | 
|  | const std::string kXmpNameSpace = "http://ns.adobe.com/xap/1.0/"; | 
|  | const std::string kExifIdCode = "Exif"; | 
|  |  | 
|  | struct jpegr_source_mgr : jpeg_source_mgr { | 
|  | jpegr_source_mgr(const uint8_t* ptr, int len); | 
|  | ~jpegr_source_mgr(); | 
|  |  | 
|  | const uint8_t* mBufferPtr; | 
|  | size_t mBufferLength; | 
|  | }; | 
|  |  | 
|  | struct jpegrerror_mgr { | 
|  | struct jpeg_error_mgr pub; | 
|  | jmp_buf setjmp_buffer; | 
|  | }; | 
|  |  | 
|  | static void jpegr_init_source(j_decompress_ptr cinfo) { | 
|  | jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); | 
|  | src->next_input_byte = static_cast<const JOCTET*>(src->mBufferPtr); | 
|  | src->bytes_in_buffer = src->mBufferLength; | 
|  | } | 
|  |  | 
|  | static boolean jpegr_fill_input_buffer(j_decompress_ptr /* cinfo */) { | 
|  | ALOGE("%s : should not get here", __func__); | 
|  | return FALSE; | 
|  | } | 
|  |  | 
|  | static void jpegr_skip_input_data(j_decompress_ptr cinfo, long num_bytes) { | 
|  | jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src); | 
|  |  | 
|  | if (num_bytes > static_cast<long>(src->bytes_in_buffer)) { | 
|  | ALOGE("jpegr_skip_input_data - num_bytes > (long)src->bytes_in_buffer"); | 
|  | } else { | 
|  | src->next_input_byte += num_bytes; | 
|  | src->bytes_in_buffer -= num_bytes; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void jpegr_term_source(j_decompress_ptr /*cinfo*/) {} | 
|  |  | 
|  | jpegr_source_mgr::jpegr_source_mgr(const uint8_t* ptr, int len) : | 
|  | mBufferPtr(ptr), mBufferLength(len) { | 
|  | init_source = jpegr_init_source; | 
|  | fill_input_buffer = jpegr_fill_input_buffer; | 
|  | skip_input_data = jpegr_skip_input_data; | 
|  | resync_to_restart = jpeg_resync_to_restart; | 
|  | term_source = jpegr_term_source; | 
|  | } | 
|  |  | 
|  | jpegr_source_mgr::~jpegr_source_mgr() {} | 
|  |  | 
|  | static void jpegrerror_exit(j_common_ptr cinfo) { | 
|  | jpegrerror_mgr* err = reinterpret_cast<jpegrerror_mgr*>(cinfo->err); | 
|  | longjmp(err->setjmp_buffer, 1); | 
|  | } | 
|  |  | 
|  | JpegDecoder::JpegDecoder() { | 
|  | mExifPos = 0; | 
|  | } | 
|  |  | 
|  | JpegDecoder::~JpegDecoder() { | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decompressImage(const void* image, int length, bool decodeToRGBA) { | 
|  | if (image == nullptr || length <= 0) { | 
|  | ALOGE("Image size can not be handled: %d", length); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | mResultBuffer.clear(); | 
|  | mXMPBuffer.clear(); | 
|  | if (!decode(image, length, decodeToRGBA)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void* JpegDecoder::getDecompressedImagePtr() { | 
|  | return mResultBuffer.data(); | 
|  | } | 
|  |  | 
|  | size_t JpegDecoder::getDecompressedImageSize() { | 
|  | return mResultBuffer.size(); | 
|  | } | 
|  |  | 
|  | void* JpegDecoder::getXMPPtr() { | 
|  | return mXMPBuffer.data(); | 
|  | } | 
|  |  | 
|  | size_t JpegDecoder::getXMPSize() { | 
|  | return mXMPBuffer.size(); | 
|  | } | 
|  |  | 
|  | void* JpegDecoder::getEXIFPtr() { | 
|  | return mEXIFBuffer.data(); | 
|  | } | 
|  |  | 
|  | size_t JpegDecoder::getEXIFSize() { | 
|  | return mEXIFBuffer.size(); | 
|  | } | 
|  |  | 
|  | size_t JpegDecoder::getDecompressedImageWidth() { | 
|  | return mWidth; | 
|  | } | 
|  |  | 
|  | size_t JpegDecoder::getDecompressedImageHeight() { | 
|  | return mHeight; | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decode(const void* image, int length, bool decodeToRGBA) { | 
|  | jpeg_decompress_struct cinfo; | 
|  | jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); | 
|  | jpegrerror_mgr myerr; | 
|  |  | 
|  | cinfo.err = jpeg_std_error(&myerr.pub); | 
|  | myerr.pub.error_exit = jpegrerror_exit; | 
|  |  | 
|  | if (setjmp(myerr.setjmp_buffer)) { | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return false; | 
|  | } | 
|  | jpeg_create_decompress(&cinfo); | 
|  |  | 
|  | jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); | 
|  | jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); | 
|  | jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); | 
|  |  | 
|  | cinfo.src = &mgr; | 
|  | jpeg_read_header(&cinfo, TRUE); | 
|  |  | 
|  | // Save XMP data and EXIF data. | 
|  | // Here we only handle the first XMP / EXIF package. | 
|  | // The parameter pos is used for capturing start offset of EXIF, which is hacky, but working... | 
|  | // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package), | 
|  | // two bytes of package length which is stored in marker->original_length, and the real data | 
|  | // which is stored in marker->data. The pos is adding up all previous package lengths ( | 
|  | // 4 bytes marker and length, marker->original_length) before EXIF appears. Note that here we | 
|  | // we are using marker->original_length instead of marker->data_length because in case the real | 
|  | // package length is larger than the limitation, jpeg-turbo will only copy the data within the | 
|  | // limitation (represented by data_length) and this may vary from original_length / real offset. | 
|  | // A better solution is making jpeg_marker_struct holding the offset, but currently it doesn't. | 
|  | bool exifAppears = false; | 
|  | bool xmpAppears = false; | 
|  | size_t pos = 2;  // position after SOI | 
|  | for (jpeg_marker_struct* marker = cinfo.marker_list; | 
|  | marker && !(exifAppears && xmpAppears); | 
|  | marker = marker->next) { | 
|  |  | 
|  | pos += 4; | 
|  | pos += marker->original_length; | 
|  |  | 
|  | if (marker->marker != kAPP1Marker) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const unsigned int len = marker->data_length; | 
|  | if (!xmpAppears && | 
|  | len > kXmpNameSpace.size() && | 
|  | !strncmp(reinterpret_cast<const char*>(marker->data), | 
|  | kXmpNameSpace.c_str(), | 
|  | kXmpNameSpace.size())) { | 
|  | mXMPBuffer.resize(len+1, 0); | 
|  | memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len); | 
|  | xmpAppears = true; | 
|  | } else if (!exifAppears && | 
|  | len > kExifIdCode.size() && | 
|  | !strncmp(reinterpret_cast<const char*>(marker->data), | 
|  | kExifIdCode.c_str(), | 
|  | kExifIdCode.size())) { | 
|  | mEXIFBuffer.resize(len, 0); | 
|  | memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); | 
|  | exifAppears = true; | 
|  | mExifPos = pos - marker->original_length; | 
|  | } | 
|  | } | 
|  |  | 
|  | mWidth = cinfo.image_width; | 
|  | mHeight = cinfo.image_height; | 
|  |  | 
|  | if (decodeToRGBA) { | 
|  | if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { | 
|  | // We don't intend to support decoding grayscale to RGBA | 
|  | return false; | 
|  | } | 
|  | // 4 bytes per pixel | 
|  | mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4); | 
|  | cinfo.out_color_space = JCS_EXT_RGBA; | 
|  | } else { | 
|  | if (cinfo.jpeg_color_space == JCS_YCbCr) { | 
|  | // 1 byte per pixel for Y, 0.5 byte per pixel for U+V | 
|  | mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0); | 
|  | } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { | 
|  | mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0); | 
|  | } | 
|  | cinfo.out_color_space = cinfo.jpeg_color_space; | 
|  | cinfo.raw_data_out = TRUE; | 
|  | } | 
|  |  | 
|  | cinfo.dct_method = JDCT_IFAST; | 
|  |  | 
|  | jpeg_start_decompress(&cinfo); | 
|  |  | 
|  | if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()), | 
|  | cinfo.jpeg_color_space == JCS_GRAYSCALE)) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | jpeg_finish_decompress(&cinfo); | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // TODO (Fyodor/Dichen): merge this method with getCompressedImageParameters() since they have | 
|  | // similar functionality. Yet Dichen is not familiar with who's calling | 
|  | // getCompressedImageParameters(), looks like it's used by some pending CLs. | 
|  | bool JpegDecoder::extractEXIF(const void* image, int length) { | 
|  | jpeg_decompress_struct cinfo; | 
|  | jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); | 
|  | jpegrerror_mgr myerr; | 
|  |  | 
|  | cinfo.err = jpeg_std_error(&myerr.pub); | 
|  | myerr.pub.error_exit = jpegrerror_exit; | 
|  |  | 
|  | if (setjmp(myerr.setjmp_buffer)) { | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return false; | 
|  | } | 
|  | jpeg_create_decompress(&cinfo); | 
|  |  | 
|  | jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF); | 
|  | jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); | 
|  | jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); | 
|  |  | 
|  | cinfo.src = &mgr; | 
|  | jpeg_read_header(&cinfo, TRUE); | 
|  |  | 
|  | bool exifAppears = false; | 
|  | size_t pos = 2;  // position after SOI | 
|  | for (jpeg_marker_struct* marker = cinfo.marker_list; | 
|  | marker && !exifAppears; | 
|  | marker = marker->next) { | 
|  |  | 
|  | pos += 4; | 
|  | pos += marker->original_length; | 
|  |  | 
|  | if (marker->marker != kAPP1Marker) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const unsigned int len = marker->data_length; | 
|  | if (!exifAppears && | 
|  | len > kExifIdCode.size() && | 
|  | !strncmp(reinterpret_cast<const char*>(marker->data), | 
|  | kExifIdCode.c_str(), | 
|  | kExifIdCode.size())) { | 
|  | mEXIFBuffer.resize(len, 0); | 
|  | memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len); | 
|  | exifAppears = true; | 
|  | mExifPos = pos - marker->original_length; | 
|  | } | 
|  | } | 
|  |  | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest, | 
|  | bool isSingleChannel) { | 
|  | if (isSingleChannel) { | 
|  | return decompressSingleChannel(cinfo, dest); | 
|  | } | 
|  | if (cinfo->out_color_space == JCS_EXT_RGBA) | 
|  | return decompressRGBA(cinfo, dest); | 
|  | else | 
|  | return decompressYUV(cinfo, dest); | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::getCompressedImageParameters(const void* image, int length, | 
|  | size_t *pWidth, size_t *pHeight, | 
|  | std::vector<uint8_t> *iccData , std::vector<uint8_t> *exifData) { | 
|  | jpeg_decompress_struct cinfo; | 
|  | jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length); | 
|  | jpegrerror_mgr myerr; | 
|  | cinfo.err = jpeg_std_error(&myerr.pub); | 
|  | myerr.pub.error_exit = jpegrerror_exit; | 
|  |  | 
|  | if (setjmp(myerr.setjmp_buffer)) { | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return false; | 
|  | } | 
|  | jpeg_create_decompress(&cinfo); | 
|  |  | 
|  | jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF); | 
|  | jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF); | 
|  |  | 
|  | cinfo.src = &mgr; | 
|  | if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) { | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | *pWidth = cinfo.image_width; | 
|  | *pHeight = cinfo.image_height; | 
|  |  | 
|  | //TODO: Parse iccProfile | 
|  | (void)iccData; | 
|  |  | 
|  | if (exifData != nullptr) { | 
|  | bool exifAppears = false; | 
|  | for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears; | 
|  | marker = marker->next) { | 
|  | if (marker->marker != kAPP1Marker) { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | const unsigned int len = marker->data_length; | 
|  | if (len >= kExifIdCode.size() && | 
|  | !strncmp(reinterpret_cast<const char*>(marker->data), kExifIdCode.c_str(), | 
|  | kExifIdCode.size())) { | 
|  | exifData->resize(len, 0); | 
|  | memcpy(static_cast<void*>(exifData->data()), marker->data, len); | 
|  | exifAppears = true; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | jpeg_destroy_decompress(&cinfo); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) { | 
|  | JSAMPLE* decodeDst = (JSAMPLE*) dest; | 
|  | uint32_t lines = 0; | 
|  | // TODO: use batches for more effectiveness | 
|  | while (lines < cinfo->image_height) { | 
|  | uint32_t ret = jpeg_read_scanlines(cinfo, &decodeDst, 1); | 
|  | if (ret == 0) { | 
|  | break; | 
|  | } | 
|  | decodeDst += cinfo->image_width * 4; | 
|  | lines++; | 
|  | } | 
|  | return lines == cinfo->image_height; | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) { | 
|  |  | 
|  | JSAMPROW y[kCompressBatchSize]; | 
|  | JSAMPROW cb[kCompressBatchSize / 2]; | 
|  | JSAMPROW cr[kCompressBatchSize / 2]; | 
|  | JSAMPARRAY planes[3] {y, cb, cr}; | 
|  |  | 
|  | size_t y_plane_size = cinfo->image_width * cinfo->image_height; | 
|  | size_t uv_plane_size = y_plane_size / 4; | 
|  | uint8_t* y_plane = const_cast<uint8_t*>(dest); | 
|  | uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size); | 
|  | uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size); | 
|  | std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]); | 
|  | memset(empty.get(), 0, cinfo->image_width); | 
|  |  | 
|  | while (cinfo->output_scanline < cinfo->image_height) { | 
|  | for (int i = 0; i < kCompressBatchSize; ++i) { | 
|  | size_t scanline = cinfo->output_scanline + i; | 
|  | if (scanline < cinfo->image_height) { | 
|  | y[i] = y_plane + scanline * cinfo->image_width; | 
|  | } else { | 
|  | y[i] = empty.get(); | 
|  | } | 
|  | } | 
|  | // cb, cr only have half scanlines | 
|  | for (int i = 0; i < kCompressBatchSize / 2; ++i) { | 
|  | size_t scanline = cinfo->output_scanline / 2 + i; | 
|  | if (scanline < cinfo->image_height / 2) { | 
|  | int offset = scanline * (cinfo->image_width / 2); | 
|  | cb[i] = u_plane + offset; | 
|  | cr[i] = v_plane + offset; | 
|  | } else { | 
|  | cb[i] = cr[i] = empty.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); | 
|  | if (processed != kCompressBatchSize) { | 
|  | ALOGE("Number of processed lines does not equal input lines."); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool JpegDecoder::decompressSingleChannel(jpeg_decompress_struct* cinfo, const uint8_t* dest) { | 
|  | JSAMPROW y[kCompressBatchSize]; | 
|  | JSAMPARRAY planes[1] {y}; | 
|  |  | 
|  | uint8_t* y_plane = const_cast<uint8_t*>(dest); | 
|  | std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]); | 
|  | memset(empty.get(), 0, cinfo->image_width); | 
|  |  | 
|  | while (cinfo->output_scanline < cinfo->image_height) { | 
|  | for (int i = 0; i < kCompressBatchSize; ++i) { | 
|  | size_t scanline = cinfo->output_scanline + i; | 
|  | if (scanline < cinfo->image_height) { | 
|  | y[i] = y_plane + scanline * cinfo->image_width; | 
|  | } else { | 
|  | y[i] = empty.get(); | 
|  | } | 
|  | } | 
|  |  | 
|  | int processed = jpeg_read_raw_data(cinfo, planes, kCompressBatchSize); | 
|  | if (processed != kCompressBatchSize / 2) { | 
|  | ALOGE("Number of processed lines does not equal input lines."); | 
|  | return false; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | } // namespace android |