diff options
Diffstat (limited to 'jni_jpegstream/src/jpeg_reader.cpp')
-rw-r--r-- | jni_jpegstream/src/jpeg_reader.cpp | 254 |
1 files changed, 254 insertions, 0 deletions
diff --git a/jni_jpegstream/src/jpeg_reader.cpp b/jni_jpegstream/src/jpeg_reader.cpp new file mode 100644 index 000000000..4726b6426 --- /dev/null +++ b/jni_jpegstream/src/jpeg_reader.cpp @@ -0,0 +1,254 @@ +/* + * 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; +} + |