blob: b5d3a063ca597d124421aabe5bb792fc9c1d4383 [file] [log] [blame]
Dichen Zhang4a66b3c2022-10-19 18:04:47 +00001/*
2 * Copyright 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <jpegrecoverymap/jpegencoder.h>
18
19#include <cutils/log.h>
20
21#include <errno.h>
22
23namespace android::recoverymap {
24
25// The destination manager that can access |mResultBuffer| in JpegEncoder.
26struct destination_mgr {
27public:
28 struct jpeg_destination_mgr mgr;
29 JpegEncoder* encoder;
30};
31
32JpegEncoder::JpegEncoder() {
33}
34
35JpegEncoder::~JpegEncoder() {
36}
37
38bool JpegEncoder::compressImage(const void* image, int width, int height, int quality,
39 const void* iccBuffer, unsigned int iccSize) {
40 if (width % 8 != 0 || height % 2 != 0) {
41 ALOGE("Image size can not be handled: %dx%d", width, height);
42 return false;
43 }
44
45 mResultBuffer.clear();
46 if (!encode(image, width, height, quality, iccBuffer, iccSize)) {
47 return false;
48 }
49 ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
50 (width * height * 12) / 8, width, height, mResultBuffer.size());
51 return true;
52}
53
54const void* JpegEncoder::getCompressedImagePtr() {
55 return mResultBuffer.data();
56}
57
58size_t JpegEncoder::getCompressedImageSize() {
59 return mResultBuffer.size();
60}
61
62void JpegEncoder::initDestination(j_compress_ptr cinfo) {
63 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
64 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
65 buffer.resize(kBlockSize);
66 dest->mgr.next_output_byte = &buffer[0];
67 dest->mgr.free_in_buffer = buffer.size();
68}
69
70boolean JpegEncoder::emptyOutputBuffer(j_compress_ptr cinfo) {
71 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
72 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
73 size_t oldsize = buffer.size();
74 buffer.resize(oldsize + kBlockSize);
75 dest->mgr.next_output_byte = &buffer[oldsize];
76 dest->mgr.free_in_buffer = kBlockSize;
77 return true;
78}
79
80void JpegEncoder::terminateDestination(j_compress_ptr cinfo) {
81 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
82 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
83 buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
84}
85
86void JpegEncoder::outputErrorMessage(j_common_ptr cinfo) {
87 char buffer[JMSG_LENGTH_MAX];
88
89 /* Create the message */
90 (*cinfo->err->format_message) (cinfo, buffer);
91 ALOGE("%s\n", buffer);
92}
93
94bool JpegEncoder::encode(const void* inYuv, int width, int height, int jpegQuality,
95 const void* iccBuffer, unsigned int iccSize) {
96 jpeg_compress_struct cinfo;
97 jpeg_error_mgr jerr;
98
99 cinfo.err = jpeg_std_error(&jerr);
100 // Override output_message() to print error log with ALOGE().
101 cinfo.err->output_message = &outputErrorMessage;
102 jpeg_create_compress(&cinfo);
103 setJpegDestination(&cinfo);
104
105 setJpegCompressStruct(width, height, jpegQuality, &cinfo);
106 jpeg_start_compress(&cinfo, TRUE);
107
108 if (iccBuffer != nullptr && iccSize > 0) {
109 jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
110 }
111
112 if (!compress(&cinfo, static_cast<const uint8_t*>(inYuv))) {
113 return false;
114 }
115 jpeg_finish_compress(&cinfo);
116 jpeg_destroy_compress(&cinfo);
117 return true;
118}
119
120void JpegEncoder::setJpegDestination(jpeg_compress_struct* cinfo) {
121 destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
122 (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
123 dest->encoder = this;
124 dest->mgr.init_destination = &initDestination;
125 dest->mgr.empty_output_buffer = &emptyOutputBuffer;
126 dest->mgr.term_destination = &terminateDestination;
127 cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
128}
129
130void JpegEncoder::setJpegCompressStruct(int width, int height, int quality,
131 jpeg_compress_struct* cinfo) {
132 cinfo->image_width = width;
133 cinfo->image_height = height;
134 cinfo->input_components = 3;
135 cinfo->in_color_space = JCS_YCbCr;
136 jpeg_set_defaults(cinfo);
137
138 jpeg_set_quality(cinfo, quality, TRUE);
139 jpeg_set_colorspace(cinfo, JCS_YCbCr);
140 cinfo->raw_data_in = TRUE;
141 cinfo->dct_method = JDCT_IFAST;
142
143 // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
144 // source format is YUV420.
145 cinfo->comp_info[0].h_samp_factor = 2;
146 cinfo->comp_info[0].v_samp_factor = 2;
147 cinfo->comp_info[1].h_samp_factor = 1;
148 cinfo->comp_info[1].v_samp_factor = 1;
149 cinfo->comp_info[2].h_samp_factor = 1;
150 cinfo->comp_info[2].v_samp_factor = 1;
151}
152
153bool JpegEncoder::compress(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
154 JSAMPROW y[kCompressBatchSize];
155 JSAMPROW cb[kCompressBatchSize / 2];
156 JSAMPROW cr[kCompressBatchSize / 2];
157 JSAMPARRAY planes[3] {y, cb, cr};
158
159 size_t y_plane_size = cinfo->image_width * cinfo->image_height;
160 size_t uv_plane_size = y_plane_size / 4;
161 uint8_t* y_plane = const_cast<uint8_t*>(yuv);
162 uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
163 uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
164 std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
165 memset(empty.get(), 0, cinfo->image_width);
166
167 while (cinfo->next_scanline < cinfo->image_height) {
168 for (int i = 0; i < kCompressBatchSize; ++i) {
169 size_t scanline = cinfo->next_scanline + i;
170 if (scanline < cinfo->image_height) {
171 y[i] = y_plane + scanline * cinfo->image_width;
172 } else {
173 y[i] = empty.get();
174 }
175 }
176 // cb, cr only have half scanlines
177 for (int i = 0; i < kCompressBatchSize / 2; ++i) {
178 size_t scanline = cinfo->next_scanline / 2 + i;
179 if (scanline < cinfo->image_height / 2) {
180 int offset = scanline * (cinfo->image_width / 2);
181 cb[i] = u_plane + offset;
182 cr[i] = v_plane + offset;
183 } else {
184 cb[i] = cr[i] = empty.get();
185 }
186 }
187
188 int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
189 if (processed != kCompressBatchSize) {
190 ALOGE("Number of processed lines does not equal input lines.");
191 return false;
192 }
193 }
194 return true;
195}
196
197} // namespace android