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 
JpegCompressor()24 JpegCompressor::JpegCompressor() {}
25 
~JpegCompressor()26 JpegCompressor::~JpegCompressor() {}
27 
CompressImage(const void * image,int width,int height,int quality,const void * app1Buffer,unsigned int app1Size)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 
GetCompressedImagePtr()45 const void* JpegCompressor::GetCompressedImagePtr() {
46   return result_buffer_.data();
47 }
48 
GetCompressedImageSize()49 size_t JpegCompressor::GetCompressedImageSize() {
50   return result_buffer_.size();
51 }
52 
InitDestination(j_compress_ptr cinfo)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 
EmptyOutputBuffer(j_compress_ptr cinfo)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 
TerminateDestination(j_compress_ptr cinfo)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 
OutputErrorMessage(j_common_ptr cinfo)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 
Encode(const void * inYuv,int width,int height,int jpegQuality,const void * app1Buffer,unsigned int app1Size)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 
SetJpegDestination(jpeg_compress_struct * cinfo)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 
SetJpegCompressStruct(int width,int height,int quality,jpeg_compress_struct * cinfo)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 
Compress(jpeg_compress_struct * cinfo,const uint8_t * yuv)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