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 
JpegWriter()24 JpegWriter::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 
~JpegWriter()34 JpegWriter::~JpegWriter() {
35     if (reset() != J_SUCCESS) {
36         LOGE("Failed to destroy compress object, may leak memory.");
37     }
38 }
39 
40 const int32_t JpegWriter::DEFAULT_X_DENSITY = 300;
41 const int32_t JpegWriter::DEFAULT_Y_DENSITY = 300;
42 const int32_t JpegWriter::DEFAULT_DENSITY_UNIT = 1;
43 
setup(JNIEnv * env,jobject out,int32_t width,int32_t height,Jpeg_Config::Format format,int32_t quality)44 int32_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 
write(int8_t * bytes,int32_t length)116 int32_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
formatPixels(uint8_t * buf,int32_t len)166 void 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 
updateEnv(JNIEnv * env)202 void JpegWriter::updateEnv(JNIEnv *env) {
203     UpdateDstEnv(&mInfo, env);
204 }
205 
reset()206 int32_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