/* * Copyright (C) 2013 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 "jpeg_reader.h" #include "error_codes.h" #include "jpeg_hook.h" #include <setjmp.h> JpegReader::JpegReader() : mInfo(), mErrorManager(), mScanlineBuf(NULL), mScanlineIter(NULL), mScanlineBuflen(0), mScanlineUnformattedBuflen(0), mScanlineBytesRemaining(0), mFormat(), mFinished(false), mSetup(false) {} JpegReader::~JpegReader() { if (reset() != J_SUCCESS) { LOGE("Failed to destroy compress object, JpegReader may leak memory."); } } int32_t JpegReader::setup(JNIEnv *env, jobject in, int32_t* width, int32_t* height, Jpeg_Config::Format format) { if (mFinished || mSetup) { return J_ERROR_FATAL; } if (env->ExceptionCheck()) { return J_EXCEPTION; } // Setup error handler SetupErrMgr(reinterpret_cast<j_common_ptr>(&mInfo), &mErrorManager); // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } // Call libjpeg setup jpeg_create_decompress(&mInfo); // Setup our data source object, this allocates java global references int32_t flags = MakeSrc(&mInfo, env, in); if (flags != J_SUCCESS) { LOGE("Failed to make source with error code: %d ", flags); return flags; } // Reads jpeg file header jpeg_read_header(&mInfo, TRUE); jpeg_calc_output_dimensions(&mInfo); const int components = (static_cast<int>(format) & 0xff); // Do setup for input format switch (components) { case 1: mInfo.out_color_space = JCS_GRAYSCALE; mScanlineUnformattedBuflen = mInfo.output_width; break; case 3: case 4: mScanlineUnformattedBuflen = mInfo.output_width * components; if (mInfo.jpeg_color_space == JCS_CMYK || mInfo.jpeg_color_space == JCS_YCCK) { // Always use cmyk for output in a 4 channel jpeg. // libjpeg has a builtin cmyk->rgb decoder. mScanlineUnformattedBuflen = mInfo.output_width * 4; mInfo.out_color_space = JCS_CMYK; } else { mInfo.out_color_space = JCS_RGB; } break; default: return J_ERROR_BAD_ARGS; } mScanlineBuflen = mInfo.output_width * components; mScanlineBytesRemaining = mScanlineBuflen; mScanlineBuf = (JSAMPLE *) (mInfo.mem->alloc_small)( reinterpret_cast<j_common_ptr>(&mInfo), JPOOL_PERMANENT, mScanlineUnformattedBuflen * sizeof(JSAMPLE)); mScanlineIter = mScanlineBuf; jpeg_start_decompress(&mInfo); // Output image dimensions if (width != NULL) { *width = mInfo.output_width; } if (height != NULL) { *height = mInfo.output_height; } mFormat = format; mSetup = true; return J_SUCCESS; } int32_t JpegReader::read(int8_t* bytes, int32_t offset, int32_t count) { if (!mSetup) { return J_ERROR_FATAL; } if (mFinished) { return J_DONE; } // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } if (count <= 0) { return J_ERROR_BAD_ARGS; } int32_t total_length = count; while (mInfo.output_scanline < mInfo.output_height) { if (count < mScanlineBytesRemaining) { // read partial scanline and return if (bytes != NULL) { // Treat NULL bytes as a skip memcpy((void*) (bytes + offset), (void*) mScanlineIter, count * sizeof(int8_t)); } mScanlineBytesRemaining -= count; mScanlineIter += count; return total_length; } else if (count > 0) { // read full scanline if (bytes != NULL) { // Treat NULL bytes as a skip memcpy((void*) (bytes + offset), (void*) mScanlineIter, mScanlineBytesRemaining * sizeof(int8_t)); bytes += mScanlineBytesRemaining; } count -= mScanlineBytesRemaining; mScanlineBytesRemaining = 0; } // Scanline buffer exhausted, read next scanline if (jpeg_read_scanlines(&mInfo, &mScanlineBuf, 1) != 1) { // Always read full scanline, no IO suspension return J_ERROR_FATAL; } // Do in-place pixel formatting formatPixels(static_cast<uint8_t*>(mScanlineBuf), mScanlineUnformattedBuflen); // Reset iterators mScanlineIter = mScanlineBuf; mScanlineBytesRemaining = mScanlineBuflen; } // Read all of the scanlines jpeg_finish_decompress(&mInfo); mFinished = true; return total_length - count; } void JpegReader::updateEnv(JNIEnv *env) { UpdateSrcEnv(&mInfo, env); } // Does in-place pixel formatting void JpegReader::formatPixels(uint8_t* buf, int32_t len) { uint8_t *iter = buf; // Do cmyk->rgb conversion if necessary switch (mInfo.out_color_space) { case JCS_CMYK: // Convert CMYK to RGB int r, g, b, c, m, y, k; for (int i = 0; i < len; i += 4) { c = buf[i + 0]; m = buf[i + 1]; y = buf[i + 2]; k = buf[i + 3]; // Handle fmt for weird photoshop markers if (mInfo.saw_Adobe_marker) { r = (k * c) / 255; g = (k * m) / 255; b = (k * y) / 255; } else { r = (255 - k) * (255 - c) / 255; g = (255 - k) * (255 - m) / 255; b = (255 - k) * (255 - y) / 255; } *iter++ = r; *iter++ = g; *iter++ = b; } break; case JCS_RGB: iter += (len * 3 / 4); break; case JCS_GRAYSCALE: default: return; } // Do endianness and alpha for output format if (mFormat == Jpeg_Config::FORMAT_RGBA) { // Set alphas to 255 uint8_t* end = buf + len - 1; for (int i = len - 1; i >= 0; i -= 4) { buf[i] = 255; buf[i - 1] = *--iter; buf[i - 2] = *--iter; buf[i - 3] = *--iter; } } else if (mFormat == Jpeg_Config::FORMAT_ABGR) { // Reverse endianness and set alphas to 255 uint8_t* end = buf + len - 1; int r, g, b; for (int i = len - 1; i >= 0; i -= 4) { b = *--iter; g = *--iter; r = *--iter; buf[i] = r; buf[i - 1] = g; buf[i - 2] = b; buf[i - 3] = 255; } } } int32_t JpegReader::reset() { // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } // Clean up global java references CleanSrc(&mInfo); // Wipe decompress struct, free memory pools jpeg_destroy_decompress(&mInfo); mFinished = false; mSetup = false; return J_SUCCESS; }