blob: 9bb65af4a849b54be16e01135379432c4d5a97b3 [file] [log] [blame]
Puneet Lall967b7822014-08-07 17:05:38 -07001/*
2 * Copyright (C) 2014 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#include "jpegutil.h"
17#include <memory.h>
18#include <array>
19#include <vector>
20#include <cstring>
21#include <cstdio>
22
23#include <setjmp.h>
24
25extern "C" {
26#include "jpeglib.h"
27}
28
29using namespace std;
30
31template <typename T>
32void safeDelete(T& t) {
33 if (t != nullptr) {
34 delete t;
35 t = nullptr;
36 }
37}
38
39template <typename T>
40void safeDeleteArray(T& t) {
41 if (t != nullptr) {
42 delete[] t;
43 t = nullptr;
44 }
45}
46
47jpegutil::Plane::RowIterator::RowIterator(const Plane* plane) : plane_(plane) {
48 // We must be able to supply up to 8 * 2 lines at a time to libjpeg.
49 // 8 = vertical size of blocks transformed with DCT.
50 // 2 = scaling factor for Y vs UV planes.
51 bufRowCount_ = 16;
52
53 // Rows must be padded to the next multiple of 16
54 // TODO OPTIMIZE Cb and Cr components only need to be padded to a multiple of
55 // 8.
56 rowPadding_ = (16 - (plane_->planeWidth_ % 16)) % 16;
57 bufRowStride_ = plane_->planeWidth_ + rowPadding_;
58
59 // Round up to the nearest multiple of 64 for cache alignment
60 bufRowStride_ = (bufRowStride_ + 63) & ~63;
61
62 // Allocate an extra 64 bytes to allow for cache alignment
63 size_t bufSize = bufRowStride_ * bufRowCount_ + 64;
64
65 // TODO OPTIMIZE if the underlying data has a pixel-stride of 1, and an image
66 // width which is a multiple of 16, we can avoid this allocation and simply
67 // return pointers into the underlying data in operator()(int) instead of
68 // copying the data.
69 buffer_ = unique_ptr<unsigned char[]>(new unsigned char[bufSize]);
70
71 // Find the start of the 64-byte aligned buffer we allocated.
72 size_t bufStart = reinterpret_cast<size_t>(&buffer_[0]);
73 size_t alignedBufStart = (bufStart + 63) & ~63;
74 alignedBuffer_ = reinterpret_cast<unsigned char*>(alignedBufStart);
75
76 bufCurRow_ = 0;
77}
78
79unsigned char* jpegutil::Plane::RowIterator::operator()(int y) {
80 unsigned char* bufCurRowPtr = alignedBuffer_ + bufRowStride_ * bufCurRow_;
81
82 unsigned char* srcPtr = &plane_->data_[y * plane_->rowStride_];
83 unsigned char* dstPtr = bufCurRowPtr;
84
85 // Use memcpy when possible.
86 if (plane_->pixelStride_ == 1) {
87 memcpy(dstPtr, srcPtr, plane_->planeWidth_);
88 } else {
89 int pixelStride = plane_->pixelStride_;
90
91 for (int i = 0; i < plane_->planeWidth_; i++) {
92 *dstPtr = *srcPtr;
93
94 srcPtr += pixelStride;
95 dstPtr++;
96 }
97 }
98
99 // Add padding to the right side by replicating the rightmost column of
100 // (actual) image values into the padding bytes.
101 memset(&bufCurRowPtr[plane_->planeWidth_],
102 bufCurRowPtr[plane_->planeWidth_ - 1], rowPadding_);
103
104 bufCurRow_++;
105 // Wrap within ring buffer.
106 bufCurRow_ %= bufRowCount_;
107
108 return bufCurRowPtr;
109}
110
111jpegutil::Plane::Plane(int imgWidth, int imgHeight, int planeWidth,
112 int planeHeight, unsigned char* data, int pixelStride,
113 int rowStride)
114 : imgWidth_(imgWidth),
115 imgHeight_(imgHeight),
116 planeWidth_(planeWidth),
117 planeHeight_(planeHeight),
118 data_(data),
119 rowStride_(rowStride),
120 pixelStride_(pixelStride) {}
121
122int jpegutil::compress(const Plane& yPlane, const Plane& cbPlane,
123 const Plane& crPlane, unsigned char* outBuf,
124 size_t outBufCapacity, std::function<void(size_t)> flush,
125 int quality) {
126 int imgWidth = yPlane.imgWidth();
127 int imgHeight = yPlane.imgHeight();
128
129 // libjpeg requires the use of setjmp/longjmp to recover from errors. Since
130 // this doesn't play well with RAII, we must use pointers and manually call
131 // delete. See POSIX documentation for longjmp() for details on why the
132 // volatile keyword is necessary.
133 volatile jpeg_compress_struct cinfov;
134
135 jpeg_compress_struct& cinfo =
136 *const_cast<struct jpeg_compress_struct*>(&cinfov);
137
138 JSAMPROW* volatile yArr = nullptr;
139 JSAMPROW* volatile cbArr = nullptr;
140 JSAMPROW* volatile crArr = nullptr;
141
142 Plane::RowIterator* volatile yRowGenerator = nullptr;
143 Plane::RowIterator* volatile cbRowGenerator = nullptr;
144 Plane::RowIterator* volatile crRowGenerator = nullptr;
145
146 JSAMPARRAY imgArr[3];
147
148 // Error handling
149
150 struct my_error_mgr {
151 struct jpeg_error_mgr pub;
152 jmp_buf setjmp_buffer;
153 } err;
154
155 cinfo.err = jpeg_std_error(&err.pub);
156
157 // Default error_exit will call exit(), so override
158 // to return control via setjmp/longjmp.
159 err.pub.error_exit = [](j_common_ptr cinfo) {
160 my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
161
162 (*cinfo->err->output_message)(cinfo);
163
164 // Return control to the setjmp point (see call to setjmp()).
165 longjmp(myerr->setjmp_buffer, 1);
166 };
167
168 cinfo.err = (struct jpeg_error_mgr*)&err;
169
170 // Set the setjmp point to return to in case of error.
171 if (setjmp(err.setjmp_buffer)) {
172 // If libjpeg hits an error, control will jump to this point (see call to
173 // longjmp()).
174 jpeg_destroy_compress(&cinfo);
175
176 safeDeleteArray(yArr);
177 safeDeleteArray(cbArr);
178 safeDeleteArray(crArr);
179 safeDelete(yRowGenerator);
180 safeDelete(cbRowGenerator);
181 safeDelete(crRowGenerator);
182
183 return -1;
184 }
185
186 // Create jpeg compression context
187 jpeg_create_compress(&cinfo);
188
189 // Stores data needed by our c-style callbacks into libjpeg
190 struct ClientData {
191 unsigned char* outBuf;
192 size_t outBufCapacity;
193 std::function<void(size_t)> flush;
194 int totalOutputBytes;
195 } clientData{outBuf, outBufCapacity, flush, 0};
196
197 cinfo.client_data = &clientData;
198
199 // Initialize destination manager
200 jpeg_destination_mgr dest;
201
202 dest.init_destination = [](j_compress_ptr cinfo) {
203 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
204
205 cinfo->dest->next_output_byte = cdata.outBuf;
206 cinfo->dest->free_in_buffer = cdata.outBufCapacity;
207 };
208
209 dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
210 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
211
212 size_t numBytesInBuffer = cdata.outBufCapacity;
213 cdata.flush(numBytesInBuffer);
214 cdata.totalOutputBytes += numBytesInBuffer;
215
216 // Reset the buffer
217 cinfo->dest->next_output_byte = cdata.outBuf;
218 cinfo->dest->free_in_buffer = cdata.outBufCapacity;
219
220 return true;
221 };
222
223 dest.term_destination = [](j_compress_ptr cinfo) {
224 // do nothing to terminate the output buffer
225 };
226
227 cinfo.dest = &dest;
228
229 // Set jpeg parameters
230 cinfo.image_width = imgWidth;
231 cinfo.image_height = imgHeight;
232 cinfo.input_components = 3;
233
234 // Set defaults based on the above values
235 jpeg_set_defaults(&cinfo);
236
237 jpeg_set_quality(&cinfo, quality, true);
238
239 cinfo.dct_method = JDCT_IFAST;
240
241 cinfo.raw_data_in = true;
242
243 jpeg_set_colorspace(&cinfo, JCS_YCbCr);
244
245 cinfo.comp_info[0].h_samp_factor = 2;
246 cinfo.comp_info[0].v_samp_factor = 2;
247 cinfo.comp_info[1].h_samp_factor = 1;
248 cinfo.comp_info[1].v_samp_factor = 1;
249 cinfo.comp_info[2].h_samp_factor = 1;
250 cinfo.comp_info[2].v_samp_factor = 1;
251
252 jpeg_start_compress(&cinfo, true);
253
254 yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
255 cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
256 crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
257
258 imgArr[0] = const_cast<JSAMPARRAY>(yArr);
259 imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
260 imgArr[2] = const_cast<JSAMPARRAY>(crArr);
261
262 yRowGenerator = new Plane::RowIterator(&yPlane);
263 cbRowGenerator = new Plane::RowIterator(&cbPlane);
264 crRowGenerator = new Plane::RowIterator(&crPlane);
265
266 Plane::RowIterator& yRG = *const_cast<Plane::RowIterator*>(yRowGenerator);
267 Plane::RowIterator& cbRG = *const_cast<Plane::RowIterator*>(cbRowGenerator);
268 Plane::RowIterator& crRG = *const_cast<Plane::RowIterator*>(crRowGenerator);
269
270 for (int y = 0; y < imgHeight; y += DCTSIZE * 2) {
271 for (int row = 0; row < DCTSIZE * 2; row++) {
272 yArr[row] = yRG(y + row);
273 }
274
275 for (int row = 0; row < DCTSIZE; row++) {
276 // The y-index within the subsampled chroma planes to send to libjpeg.
277 const int chY = y / 2 + row;
278
279 if (chY < imgHeight / 2) {
280 cbArr[row] = cbRG(chY);
281 crArr[row] = crRG(chY);
282 } else {
283 // When we have run out of rows in the chroma planes to compress, send
284 // the last row as padding.
285 cbArr[row] = cbRG(imgHeight / 2 - 1);
286 crArr[row] = crRG(imgHeight / 2 - 1);
287 }
288 }
289
290 jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
291 }
292
293 jpeg_finish_compress(&cinfo);
294
295 int numBytesInBuffer = cinfo.dest->next_output_byte - outBuf;
296
297 flush(numBytesInBuffer);
298
299 clientData.totalOutputBytes += numBytesInBuffer;
300
301 safeDeleteArray(yArr);
302 safeDeleteArray(cbArr);
303 safeDeleteArray(crArr);
304 safeDelete(yRowGenerator);
305 safeDelete(cbRowGenerator);
306 safeDelete(crRowGenerator);
307
308 return clientData.totalOutputBytes;
309}