1 /* 2 * Copyright 2017 The Chromium OS Authors. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 7 #include "arc/jpeg_compressor.h" 8 9 #include <memory> 10 11 #include "arc/common.h" 12 13 namespace arc { 14 15 // The destination manager that can access |result_buffer_| in JpegCompressor. 16 struct destination_mgr { 17 public: 18 struct jpeg_destination_mgr mgr; 19 JpegCompressor* compressor; 20 }; 21 22 JpegCompressor::JpegCompressor() {} 23 24 JpegCompressor::~JpegCompressor() {} 25 26 bool JpegCompressor::CompressImage(const void* image, int width, int height, 27 int quality, const void* app1Buffer, 28 unsigned int app1Size) { 29 if (width % 8 != 0 || height % 2 != 0) { 30 LOGF(ERROR) << "Image size can not be handled: " << width << "x" << height; 31 return false; 32 } 33 34 result_buffer_.clear(); 35 if (!Encode(image, width, height, quality, app1Buffer, app1Size)) { 36 return false; 37 } 38 LOGF(INFO) << "Compressed JPEG: " << (width * height * 12) / 8 << "[" << width 39 << "x" << height << "] -> " << result_buffer_.size() << " bytes"; 40 return true; 41 } 42 43 const void* JpegCompressor::GetCompressedImagePtr() { 44 return result_buffer_.data(); 45 } 46 47 size_t JpegCompressor::GetCompressedImageSize() { 48 return result_buffer_.size(); 49 } 50 51 void JpegCompressor::InitDestination(j_compress_ptr cinfo) { 52 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); 53 std::vector<JOCTET>& buffer = dest->compressor->result_buffer_; 54 buffer.resize(kBlockSize); 55 dest->mgr.next_output_byte = &buffer[0]; 56 dest->mgr.free_in_buffer = buffer.size(); 57 } 58 59 boolean JpegCompressor::EmptyOutputBuffer(j_compress_ptr cinfo) { 60 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); 61 std::vector<JOCTET>& buffer = dest->compressor->result_buffer_; 62 size_t oldsize = buffer.size(); 63 buffer.resize(oldsize + kBlockSize); 64 dest->mgr.next_output_byte = &buffer[oldsize]; 65 dest->mgr.free_in_buffer = kBlockSize; 66 return true; 67 } 68 69 void JpegCompressor::TerminateDestination(j_compress_ptr cinfo) { 70 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest); 71 std::vector<JOCTET>& buffer = dest->compressor->result_buffer_; 72 buffer.resize(buffer.size() - dest->mgr.free_in_buffer); 73 } 74 75 void JpegCompressor::OutputErrorMessage(j_common_ptr cinfo) { 76 char buffer[JMSG_LENGTH_MAX]; 77 78 /* Create the message */ 79 (*cinfo->err->format_message)(cinfo, buffer); 80 LOGF(ERROR) << buffer; 81 } 82 83 bool JpegCompressor::Encode(const void* inYuv, int width, int height, 84 int jpegQuality, const void* app1Buffer, 85 unsigned int app1Size) { 86 jpeg_compress_struct cinfo; 87 jpeg_error_mgr jerr; 88 89 cinfo.err = jpeg_std_error(&jerr); 90 // Override output_message() to print error log with ALOGE(). 91 cinfo.err->output_message = &OutputErrorMessage; 92 jpeg_create_compress(&cinfo); 93 SetJpegDestination(&cinfo); 94 95 SetJpegCompressStruct(width, height, jpegQuality, &cinfo); 96 jpeg_start_compress(&cinfo, TRUE); 97 98 if (app1Buffer != nullptr && app1Size > 0) { 99 jpeg_write_marker(&cinfo, JPEG_APP0 + 1, 100 static_cast<const JOCTET*>(app1Buffer), app1Size); 101 } 102 103 if (!Compress(&cinfo, static_cast<const uint8_t*>(inYuv))) { 104 return false; 105 } 106 jpeg_finish_compress(&cinfo); 107 return true; 108 } 109 110 void JpegCompressor::SetJpegDestination(jpeg_compress_struct* cinfo) { 111 destination_mgr* dest = 112 static_cast<struct destination_mgr*>((*cinfo->mem->alloc_small)( 113 (j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(destination_mgr))); 114 dest->compressor = this; 115 dest->mgr.init_destination = &InitDestination; 116 dest->mgr.empty_output_buffer = &EmptyOutputBuffer; 117 dest->mgr.term_destination = &TerminateDestination; 118 cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest); 119 } 120 121 void JpegCompressor::SetJpegCompressStruct(int width, int height, int quality, 122 jpeg_compress_struct* cinfo) { 123 cinfo->image_width = width; 124 cinfo->image_height = height; 125 cinfo->input_components = 3; 126 cinfo->in_color_space = JCS_YCbCr; 127 jpeg_set_defaults(cinfo); 128 129 jpeg_set_quality(cinfo, quality, TRUE); 130 jpeg_set_colorspace(cinfo, JCS_YCbCr); 131 cinfo->raw_data_in = TRUE; 132 cinfo->dct_method = JDCT_IFAST; 133 134 // Configure sampling factors. The sampling factor is JPEG subsampling 420 135 // because the source format is YUV420. 136 cinfo->comp_info[0].h_samp_factor = 2; 137 cinfo->comp_info[0].v_samp_factor = 2; 138 cinfo->comp_info[1].h_samp_factor = 1; 139 cinfo->comp_info[1].v_samp_factor = 1; 140 cinfo->comp_info[2].h_samp_factor = 1; 141 cinfo->comp_info[2].v_samp_factor = 1; 142 } 143 144 bool JpegCompressor::Compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) { 145 JSAMPROW y[kCompressBatchSize]; 146 JSAMPROW cb[kCompressBatchSize / 2]; 147 JSAMPROW cr[kCompressBatchSize / 2]; 148 JSAMPARRAY planes[3]{y, cb, cr}; 149 150 size_t y_plane_size = cinfo->image_width * cinfo->image_height; 151 size_t uv_plane_size = y_plane_size / 4; 152 uint8_t* y_plane = const_cast<uint8_t*>(yuv); 153 uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size); 154 uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size); 155 std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]); 156 memset(empty.get(), 0, cinfo->image_width); 157 158 while (cinfo->next_scanline < cinfo->image_height) { 159 for (int i = 0; i < kCompressBatchSize; ++i) { 160 size_t scanline = cinfo->next_scanline + i; 161 if (scanline < cinfo->image_height) { 162 y[i] = y_plane + scanline * cinfo->image_width; 163 } else { 164 y[i] = empty.get(); 165 } 166 } 167 // cb, cr only have half scanlines 168 for (int i = 0; i < kCompressBatchSize / 2; ++i) { 169 size_t scanline = cinfo->next_scanline / 2 + i; 170 if (scanline < cinfo->image_height / 2) { 171 int offset = scanline * (cinfo->image_width / 2); 172 cb[i] = u_plane + offset; 173 cr[i] = v_plane + offset; 174 } else { 175 cb[i] = cr[i] = empty.get(); 176 } 177 } 178 179 int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize); 180 if (processed != kCompressBatchSize) { 181 LOGF(ERROR) << "Number of processed lines does not equal input lines."; 182 return false; 183 } 184 } 185 return true; 186 } 187 188 } // namespace arc 189