blob: 4f7891778ff00f0f8fd5529303a1c22c9dd39cba [file] [log] [blame]
/*
* 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 <setjmp.h>
#include <assert.h>
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<j_common_ptr>(&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<int>(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<j_common_ptr>(&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<uint8_t*>(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;
}