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