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