Home | History | Annotate | Download | only in arc
      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