1/* 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; 30using namespace jpegutil; 31 32template <typename T> 33void safeDelete(T& t) { 34 if (t != nullptr) { 35 delete t; 36 t = nullptr; 37 } 38} 39 40template <typename T> 41void safeDeleteArray(T& t) { 42 if (t != nullptr) { 43 delete[] t; 44 t = nullptr; 45 } 46} 47 48jpegutil::Transform::Transform(int orig_x, int orig_y, int one_x, int one_y) 49 : orig_x_(orig_x), orig_y_(orig_y), one_x_(one_x), one_y_(one_y) { 50 if (orig_x == one_x || orig_y == one_y) { 51 // Handle the degenerate case of cropping to a 0x0 rectangle. 52 mat00_ = 0; 53 mat01_ = 0; 54 mat10_ = 0; 55 mat11_ = 0; 56 return; 57 } 58 59 if (one_x > orig_x && one_y > orig_y) { 60 // 0-degree rotation 61 mat00_ = 1; 62 mat01_ = 0; 63 mat10_ = 0; 64 mat11_ = 1; 65 output_width_ = abs(one_x - orig_x); 66 output_height_ = abs(one_y - orig_y); 67 } else if (one_x < orig_x && one_y > orig_y) { 68 // 90-degree CCW rotation 69 mat00_ = 0; 70 mat01_ = -1; 71 mat10_ = 1; 72 mat11_ = 0; 73 output_width_ = abs(one_y - orig_y); 74 output_height_ = abs(one_x - orig_x); 75 } else if (one_x > orig_x && one_y < orig_y) { 76 // 270-degree CCW rotation 77 mat00_ = 0; 78 mat01_ = 1; 79 mat10_ = -1; 80 mat11_ = 0; 81 output_width_ = abs(one_y - orig_y); 82 output_height_ = abs(one_x - orig_x); 83 } else if (one_x < orig_x && one_y < orig_y) { 84 // 180-degree CCW rotation 85 mat00_ = -1; 86 mat01_ = 0; 87 mat10_ = 0; 88 mat11_ = -1; 89 output_width_ = abs(one_x - orig_x); 90 output_height_ = abs(one_y - orig_y); 91 } 92} 93 94jpegutil::Transform jpegutil::Transform::ForCropFollowedByRotation( 95 int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90) { 96 // The input crop-region excludes cropRight and cropBottom, so transform the 97 // crop rect such that it defines the entire valid region of pixels 98 // inclusively. 99 cropRight -= 1; 100 cropBottom -= 1; 101 102 int cropXLow = min(cropLeft, cropRight); 103 int cropYLow = min(cropTop, cropBottom); 104 int cropXHigh = max(cropLeft, cropRight); 105 int cropYHigh = max(cropTop, cropBottom); 106 rot90 %= 4; 107 if (rot90 == 0) { 108 return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1); 109 } else if (rot90 == 1) { 110 return Transform(cropXHigh, cropYLow, cropXLow - 1, cropYHigh + 1); 111 } else if (rot90 == 2) { 112 return Transform(cropXHigh, cropYHigh, cropXLow - 1, cropYLow - 1); 113 } else if (rot90 == 3) { 114 return Transform(cropXLow, cropYHigh, cropXHigh + 1, cropYLow - 1); 115 } 116 // Impossible case. 117 return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1); 118} 119 120bool jpegutil::Transform::operator==(const Transform& other) const { 121 return other.orig_x_ == orig_x_ && // 122 other.orig_y_ == orig_y_ && // 123 other.one_x_ == one_x_ && // 124 other.one_y_ == one_y_; 125} 126 127/** 128 * Transforms the input coordinates. Coordinates outside the cropped region 129 * are clamped to valid values. 130 */ 131void jpegutil::Transform::Map(int x, int y, int* x_out, int* y_out) const { 132 x = max(x, 0); 133 y = max(y, 0); 134 x = min(x, output_width() - 1); 135 y = min(y, output_height() - 1); 136 *x_out = x * mat00_ + y * mat01_ + orig_x_; 137 *y_out = x * mat10_ + y * mat11_ + orig_y_; 138} 139 140int jpegutil::Compress(int img_width, int img_height, 141 jpegutil::RowIterator<16>& y_row_generator, 142 jpegutil::RowIterator<8>& cb_row_generator, 143 jpegutil::RowIterator<8>& cr_row_generator, 144 unsigned char* out_buf, size_t out_buf_capacity, 145 std::function<void(size_t)> flush, int quality) { 146 // libjpeg requires the use of setjmp/longjmp to recover from errors. Since 147 // this doesn't play well with RAII, we must use pointers and manually call 148 // delete. See POSIX documentation for longjmp() for details on why the 149 // volatile keyword is necessary. 150 volatile jpeg_compress_struct cinfov; 151 152 jpeg_compress_struct& cinfo = 153 *const_cast<struct jpeg_compress_struct*>(&cinfov); 154 155 JSAMPROW* volatile yArr = nullptr; 156 JSAMPROW* volatile cbArr = nullptr; 157 JSAMPROW* volatile crArr = nullptr; 158 159 JSAMPARRAY imgArr[3]; 160 161 // Error handling 162 163 struct my_error_mgr { 164 struct jpeg_error_mgr pub; 165 jmp_buf setjmp_buffer; 166 } err; 167 168 cinfo.err = jpeg_std_error(&err.pub); 169 170 // Default error_exit will call exit(), so override 171 // to return control via setjmp/longjmp. 172 err.pub.error_exit = [](j_common_ptr cinfo) { 173 my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err); 174 175 (*cinfo->err->output_message)(cinfo); 176 177 // Return control to the setjmp point (see call to setjmp()). 178 longjmp(myerr->setjmp_buffer, 1); 179 }; 180 181 cinfo.err = (struct jpeg_error_mgr*)&err; 182 183 // Set the setjmp point to return to in case of error. 184 if (setjmp(err.setjmp_buffer)) { 185 // If libjpeg hits an error, control will jump to this point (see call to 186 // longjmp()). 187 jpeg_destroy_compress(&cinfo); 188 189 safeDeleteArray(yArr); 190 safeDeleteArray(cbArr); 191 safeDeleteArray(crArr); 192 193 return -1; 194 } 195 196 // Create jpeg compression context 197 jpeg_create_compress(&cinfo); 198 199 // Stores data needed by our c-style callbacks into libjpeg 200 struct ClientData { 201 unsigned char* out_buf; 202 size_t out_buf_capacity; 203 std::function<void(size_t)> flush; 204 int totalOutputBytes; 205 } clientData{out_buf, out_buf_capacity, flush, 0}; 206 207 cinfo.client_data = &clientData; 208 209 // Initialize destination manager 210 jpeg_destination_mgr dest; 211 212 dest.init_destination = [](j_compress_ptr cinfo) { 213 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data); 214 215 cinfo->dest->next_output_byte = cdata.out_buf; 216 cinfo->dest->free_in_buffer = cdata.out_buf_capacity; 217 }; 218 219 dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean { 220 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data); 221 222 size_t numBytesInBuffer = cdata.out_buf_capacity; 223 cdata.flush(numBytesInBuffer); 224 cdata.totalOutputBytes += numBytesInBuffer; 225 226 // Reset the buffer 227 cinfo->dest->next_output_byte = cdata.out_buf; 228 cinfo->dest->free_in_buffer = cdata.out_buf_capacity; 229 230 return true; 231 }; 232 233 dest.term_destination = [](j_compress_ptr cinfo) { 234 // do nothing to terminate the output buffer 235 }; 236 237 cinfo.dest = &dest; 238 239 // Set jpeg parameters 240 cinfo.image_width = img_width; 241 cinfo.image_height = img_height; 242 cinfo.input_components = 3; 243 244 // Set defaults based on the above values 245 jpeg_set_defaults(&cinfo); 246 247 jpeg_set_quality(&cinfo, quality, true); 248 249 cinfo.dct_method = JDCT_IFAST; 250 251 cinfo.raw_data_in = true; 252 253 jpeg_set_colorspace(&cinfo, JCS_YCbCr); 254 255 cinfo.comp_info[0].h_samp_factor = 2; 256 cinfo.comp_info[0].v_samp_factor = 2; 257 cinfo.comp_info[1].h_samp_factor = 1; 258 cinfo.comp_info[1].v_samp_factor = 1; 259 cinfo.comp_info[2].h_samp_factor = 1; 260 cinfo.comp_info[2].v_samp_factor = 1; 261 262 jpeg_start_compress(&cinfo, true); 263 264 yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE]; 265 cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE]; 266 crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE]; 267 268 imgArr[0] = const_cast<JSAMPARRAY>(yArr); 269 imgArr[1] = const_cast<JSAMPARRAY>(cbArr); 270 imgArr[2] = const_cast<JSAMPARRAY>(crArr); 271 272 for (int y = 0; y < img_height; y += DCTSIZE * 2) { 273 std::array<unsigned char*, 16> yData = y_row_generator.LoadAt(y); 274 std::array<unsigned char*, 8> cbData = cb_row_generator.LoadAt(y / 2); 275 std::array<unsigned char*, 8> crData = cr_row_generator.LoadAt(y / 2); 276 277 for (int row = 0; row < DCTSIZE * 2; row++) { 278 yArr[row] = yData[row]; 279 } 280 for (int row = 0; row < DCTSIZE; row++) { 281 cbArr[row] = cbData[row]; 282 crArr[row] = crData[row]; 283 } 284 285 jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2); 286 } 287 288 jpeg_finish_compress(&cinfo); 289 290 int numBytesInBuffer = cinfo.dest->next_output_byte - out_buf; 291 292 flush(numBytesInBuffer); 293 294 clientData.totalOutputBytes += numBytesInBuffer; 295 296 safeDeleteArray(yArr); 297 safeDeleteArray(cbArr); 298 safeDeleteArray(crArr); 299 300 jpeg_destroy_compress(&cinfo); 301 302 return clientData.totalOutputBytes; 303} 304 305int jpegutil::Compress( 306 /** Input image dimensions */ 307 int width, int height, 308 /** Y Plane */ 309 unsigned char* yBuf, int yPStride, int yRStride, 310 /** Cb Plane */ 311 unsigned char* cbBuf, int cbPStride, int cbRStride, 312 /** Cr Plane */ 313 unsigned char* crBuf, int crPStride, int crRStride, 314 /** Output */ 315 unsigned char* outBuf, size_t outBufCapacity, 316 /** Jpeg compression parameters */ 317 int quality, 318 /** Crop */ 319 int cropLeft, int cropTop, int cropRight, int cropBottom, 320 /** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree 321 * rotation. */ 322 int rot90) { 323 int finalWidth; 324 int finalHeight; 325 finalWidth = cropRight - cropLeft; 326 finalHeight = cropBottom - cropTop; 327 328 rot90 %= 4; 329 // for 90 and 270-degree rotations, flip the final width and height 330 if (rot90 == 1) { 331 finalWidth = cropBottom - cropTop; 332 finalHeight = cropRight - cropLeft; 333 } else if (rot90 == 3) { 334 finalWidth = cropBottom - cropTop; 335 finalHeight = cropRight - cropLeft; 336 } 337 338 const Plane yP = {width, height, yBuf, yPStride, yRStride}; 339 const Plane cbP = {width / 2, height / 2, cbBuf, cbPStride, cbRStride}; 340 const Plane crP = {width / 2, height / 2, crBuf, crPStride, crRStride}; 341 342 auto flush = [](size_t numBytes) { 343 // do nothing 344 }; 345 346 // Round up to the nearest multiple of 64. 347 int y_row_length = (finalWidth + 16 + 63) & ~63; 348 int cb_row_length = (finalWidth / 2 + 16 + 63) & ~63; 349 int cr_row_length = (finalWidth / 2 + 16 + 63) & ~63; 350 351 Transform yTrans = Transform::ForCropFollowedByRotation( 352 cropLeft, cropTop, cropRight, cropBottom, rot90); 353 354 Transform chromaTrans = Transform::ForCropFollowedByRotation( 355 cropLeft / 2, cropTop / 2, cropRight / 2, cropBottom / 2, rot90); 356 357 RowIterator<16> yIter(yP, yTrans, y_row_length); 358 RowIterator<8> cbIter(cbP, chromaTrans, cb_row_length); 359 RowIterator<8> crIter(crP, chromaTrans, cr_row_length); 360 361 return Compress(finalWidth, finalHeight, yIter, cbIter, crIter, outBuf, 362 outBufCapacity, flush, quality); 363} 364