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