/* * 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_hook.h" #include "jpeg_writer.h" #include "error_codes.h" #include #include JpegWriter::JpegWriter() : mInfo(), mErrorManager(), mScanlineBuf(NULL), mScanlineIter(NULL), mScanlineBuflen(0), mScanlineBytesRemaining(0), mFormat(), mFinished(false), mSetup(false) {} JpegWriter::~JpegWriter() { if (reset() != J_SUCCESS) { LOGE("Failed to destroy compress object, may leak memory."); } } const int32_t JpegWriter::DEFAULT_X_DENSITY = 300; const int32_t JpegWriter::DEFAULT_Y_DENSITY = 300; const int32_t JpegWriter::DEFAULT_DENSITY_UNIT = 1; int32_t JpegWriter::setup(JNIEnv *env, jobject out, int32_t width, int32_t height, Jpeg_Config::Format format, int32_t quality) { if (mFinished || mSetup) { return J_ERROR_FATAL; } if (env->ExceptionCheck()) { return J_EXCEPTION; } if (height <= 0 || width <= 0 || quality <= 0 || quality > 100) { return J_ERROR_BAD_ARGS; } // Setup error handler SetupErrMgr(reinterpret_cast(&mInfo), &mErrorManager); // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } // Setup cinfo struct jpeg_create_compress(&mInfo); // Setup global java refs int32_t flags = MakeDst(&mInfo, env, out); if (flags != J_SUCCESS) { return flags; } // Initialize width, height, and color space mInfo.image_width = width; mInfo.image_height = height; const int components = (static_cast(format) & 0xff); switch (components) { case 1: mInfo.input_components = 1; mInfo.in_color_space = JCS_GRAYSCALE; break; case 3: case 4: mInfo.input_components = 3; mInfo.in_color_space = JCS_RGB; break; default: return J_ERROR_BAD_ARGS; } // Set defaults jpeg_set_defaults(&mInfo); mInfo.density_unit = DEFAULT_DENSITY_UNIT; // JFIF code for pixel size units: // 1 = in, 2 = cm mInfo.X_density = DEFAULT_X_DENSITY; // Horizontal pixel density mInfo.Y_density = DEFAULT_Y_DENSITY; // Vertical pixel density // Set compress quality jpeg_set_quality(&mInfo, quality, TRUE); mFormat = format; // Setup scanline buffer mScanlineBuflen = width * components; mScanlineBytesRemaining = mScanlineBuflen; mScanlineBuf = (JSAMPLE *) (mInfo.mem->alloc_small)( reinterpret_cast(&mInfo), JPOOL_PERMANENT, mScanlineBuflen * sizeof(JSAMPLE)); mScanlineIter = mScanlineBuf; // Start compression jpeg_start_compress(&mInfo, TRUE); mSetup = true; return J_SUCCESS; } int32_t JpegWriter::write(int8_t* bytes, int32_t length) { if (!mSetup) { return J_ERROR_FATAL; } if (mFinished) { return 0; } // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } if (length < 0 || bytes == NULL) { return J_ERROR_BAD_ARGS; } int32_t total_length = length; JSAMPROW row_pointer[1]; while (mInfo.next_scanline < mInfo.image_height) { if (length < mScanlineBytesRemaining) { // read partial scanline and return memcpy((void*) mScanlineIter, (void*) bytes, length * sizeof(int8_t)); mScanlineBytesRemaining -= length; mScanlineIter += length; return total_length; } else if (length > 0) { // read full scanline memcpy((void*) mScanlineIter, (void*) bytes, mScanlineBytesRemaining * sizeof(int8_t)); bytes += mScanlineBytesRemaining; length -= mScanlineBytesRemaining; mScanlineBytesRemaining = 0; } // Do in-place pixel formatting formatPixels(static_cast(mScanlineBuf), mScanlineBuflen); row_pointer[0] = mScanlineBuf; // Do compression if (jpeg_write_scanlines(&mInfo, row_pointer, 1) != 1) { return J_ERROR_FATAL; } // Reset scanline buffer mScanlineBytesRemaining = mScanlineBuflen; mScanlineIter = mScanlineBuf; } jpeg_finish_compress(&mInfo); mFinished = true; return total_length - length; } // Does in-place pixel formatting void JpegWriter::formatPixels(uint8_t* buf, int32_t len) { // Assumes len is a multiple of 4 for RGBA and ABGR pixels. assert((len % 4) == 0); uint8_t* d = buf; switch (mFormat) { case Jpeg_Config::FORMAT_RGBA: { // Strips alphas for (int i = 0; i < len / 4; ++i, buf += 4) { *d++ = buf[0]; *d++ = buf[1]; *d++ = buf[2]; } break; } case Jpeg_Config::FORMAT_ABGR: { // Strips alphas and flips endianness if (len / 4 >= 1) { *d++ = buf[3]; uint8_t tmp = *d; *d++ = buf[2]; *d++ = tmp; } for (int i = 1; i < len / 4; ++i, buf += 4) { *d++ = buf[3]; *d++ = buf[2]; *d++ = buf[1]; } break; } default: { // Do nothing break; } } } void JpegWriter::updateEnv(JNIEnv *env) { UpdateDstEnv(&mInfo, env); } int32_t JpegWriter::reset() { // Set jump address for error handling if (setjmp(mErrorManager.setjmp_buf)) { return J_ERROR_FATAL; } // Clean up global java references CleanDst(&mInfo); // Wipe compress struct, free memory pools jpeg_destroy_compress(&mInfo); mFinished = false; mSetup = false; return J_SUCCESS; }