1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "jpeg_hook.h"
18#include "jpeg_writer.h"
19#include "error_codes.h"
20
21#include <setjmp.h>
22#include <assert.h>
23
24JpegWriter::JpegWriter() : mInfo(),
25                           mErrorManager(),
26                           mScanlineBuf(NULL),
27                           mScanlineIter(NULL),
28                           mScanlineBuflen(0),
29                           mScanlineBytesRemaining(0),
30                           mFormat(),
31                           mFinished(false),
32                           mSetup(false) {}
33
34JpegWriter::~JpegWriter() {
35    if (reset() != J_SUCCESS) {
36        LOGE("Failed to destroy compress object, may leak memory.");
37    }
38}
39
40const int32_t JpegWriter::DEFAULT_X_DENSITY = 300;
41const int32_t JpegWriter::DEFAULT_Y_DENSITY = 300;
42const int32_t JpegWriter::DEFAULT_DENSITY_UNIT = 1;
43
44int32_t JpegWriter::setup(JNIEnv *env, jobject out, int32_t width, int32_t height,
45        Jpeg_Config::Format format, int32_t quality) {
46    if (mFinished || mSetup) {
47        return J_ERROR_FATAL;
48    }
49    if (env->ExceptionCheck()) {
50        return J_EXCEPTION;
51    }
52    if (height <= 0 || width <= 0 || quality <= 0 || quality > 100) {
53        return J_ERROR_BAD_ARGS;
54    }
55    // Setup error handler
56    SetupErrMgr(reinterpret_cast<j_common_ptr>(&mInfo), &mErrorManager);
57
58    // Set jump address for error handling
59    if (setjmp(mErrorManager.setjmp_buf)) {
60        return J_ERROR_FATAL;
61    }
62
63    // Setup cinfo struct
64    jpeg_create_compress(&mInfo);
65
66    // Setup global java refs
67    int32_t flags = MakeDst(&mInfo, env, out);
68    if (flags != J_SUCCESS) {
69        return flags;
70    }
71
72    // Initialize width, height, and color space
73    mInfo.image_width = width;
74    mInfo.image_height = height;
75    const int components = (static_cast<int>(format) & 0xff);
76    switch (components) {
77    case 1:
78        mInfo.input_components = 1;
79        mInfo.in_color_space = JCS_GRAYSCALE;
80        break;
81    case 3:
82    case 4:
83        mInfo.input_components = 3;
84        mInfo.in_color_space = JCS_RGB;
85        break;
86    default:
87        return J_ERROR_BAD_ARGS;
88    }
89
90    // Set defaults
91    jpeg_set_defaults(&mInfo);
92    mInfo.density_unit = DEFAULT_DENSITY_UNIT; // JFIF code for pixel size units:
93                             // 1 = in, 2 = cm
94    mInfo.X_density = DEFAULT_X_DENSITY; // Horizontal pixel density
95    mInfo.Y_density = DEFAULT_Y_DENSITY; // Vertical pixel density
96
97    // Set compress quality
98    jpeg_set_quality(&mInfo, quality, TRUE);
99
100    mFormat = format;
101
102    // Setup scanline buffer
103    mScanlineBuflen = width * components;
104    mScanlineBytesRemaining = mScanlineBuflen;
105    mScanlineBuf = (JSAMPLE *) (mInfo.mem->alloc_small)(
106            reinterpret_cast<j_common_ptr>(&mInfo), JPOOL_PERMANENT,
107            mScanlineBuflen * sizeof(JSAMPLE));
108    mScanlineIter = mScanlineBuf;
109
110    // Start compression
111    jpeg_start_compress(&mInfo, TRUE);
112    mSetup = true;
113    return J_SUCCESS;
114}
115
116int32_t JpegWriter::write(int8_t* bytes, int32_t length) {
117    if (!mSetup) {
118        return J_ERROR_FATAL;
119    }
120    if (mFinished) {
121        return 0;
122    }
123    // Set jump address for error handling
124    if (setjmp(mErrorManager.setjmp_buf)) {
125        return J_ERROR_FATAL;
126    }
127    if (length < 0 || bytes == NULL) {
128        return J_ERROR_BAD_ARGS;
129    }
130
131    int32_t total_length = length;
132    JSAMPROW row_pointer[1];
133    while (mInfo.next_scanline < mInfo.image_height) {
134        if (length < mScanlineBytesRemaining) {
135            // read partial scanline and return
136            memcpy((void*) mScanlineIter, (void*) bytes,
137                    length * sizeof(int8_t));
138            mScanlineBytesRemaining -= length;
139            mScanlineIter += length;
140            return total_length;
141        } else if (length > 0) {
142            // read full scanline
143            memcpy((void*) mScanlineIter, (void*) bytes,
144                    mScanlineBytesRemaining * sizeof(int8_t));
145            bytes += mScanlineBytesRemaining;
146            length -= mScanlineBytesRemaining;
147            mScanlineBytesRemaining = 0;
148        }
149        // Do in-place pixel formatting
150        formatPixels(static_cast<uint8_t*>(mScanlineBuf), mScanlineBuflen);
151        row_pointer[0] = mScanlineBuf;
152        // Do compression
153        if (jpeg_write_scanlines(&mInfo, row_pointer, 1) != 1) {
154            return J_ERROR_FATAL;
155        }
156        // Reset scanline buffer
157        mScanlineBytesRemaining = mScanlineBuflen;
158        mScanlineIter = mScanlineBuf;
159    }
160    jpeg_finish_compress(&mInfo);
161    mFinished = true;
162    return total_length - length;
163}
164
165// Does in-place pixel formatting
166void JpegWriter::formatPixels(uint8_t* buf, int32_t len) {
167    //  Assumes len is a multiple of 4 for RGBA and ABGR pixels.
168    assert((len % 4) == 0);
169    uint8_t* d = buf;
170    switch (mFormat) {
171    case Jpeg_Config::FORMAT_RGBA: {
172        // Strips alphas
173        for (int i = 0; i < len / 4; ++i, buf += 4) {
174            *d++ = buf[0];
175            *d++ = buf[1];
176            *d++ = buf[2];
177        }
178        break;
179    }
180    case Jpeg_Config::FORMAT_ABGR: {
181        // Strips alphas and flips endianness
182        if (len / 4 >= 1) {
183            *d++ = buf[3];
184            uint8_t tmp = *d;
185            *d++ = buf[2];
186            *d++ = tmp;
187        }
188        for (int i = 1; i < len / 4; ++i, buf += 4) {
189            *d++ = buf[3];
190            *d++ = buf[2];
191            *d++ = buf[1];
192        }
193        break;
194    }
195    default: {
196        // Do nothing
197        break;
198    }
199    }
200}
201
202void JpegWriter::updateEnv(JNIEnv *env) {
203    UpdateDstEnv(&mInfo, env);
204}
205
206int32_t JpegWriter::reset() {
207    // Set jump address for error handling
208    if (setjmp(mErrorManager.setjmp_buf)) {
209        return J_ERROR_FATAL;
210    }
211    // Clean up global java references
212    CleanDst(&mInfo);
213    // Wipe compress struct, free memory pools
214    jpeg_destroy_compress(&mInfo);
215    mFinished = false;
216    mSetup = false;
217    return J_SUCCESS;
218}
219