blob: 54b184d2ef5b1649f6621afe6077e43676252bc6 [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
Dichen Zhang02dd0592023-02-10 20:16:57 +000017#include <jpegrecoverymap/jpegencoderhelper.h>
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000018
Dichen Zhangb27d06d2022-12-14 19:57:50 +000019#include <utils/Log.h>
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000020
21#include <errno.h>
22
23namespace android::recoverymap {
24
Dichen Zhang02dd0592023-02-10 20:16:57 +000025// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000026struct destination_mgr {
27public:
28 struct jpeg_destination_mgr mgr;
Dichen Zhang02dd0592023-02-10 20:16:57 +000029 JpegEncoderHelper* encoder;
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000030};
31
Dichen Zhang02dd0592023-02-10 20:16:57 +000032JpegEncoderHelper::JpegEncoderHelper() {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000033}
34
Dichen Zhang02dd0592023-02-10 20:16:57 +000035JpegEncoderHelper::~JpegEncoderHelper() {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000036}
37
Dichen Zhang02dd0592023-02-10 20:16:57 +000038bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
Dichen Zhanga4819142022-10-21 04:12:30 +000039 const void* iccBuffer, unsigned int iccSize,
40 bool isSingleChannel) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000041 if (width % 8 != 0 || height % 2 != 0) {
42 ALOGE("Image size can not be handled: %dx%d", width, height);
43 return false;
44 }
45
46 mResultBuffer.clear();
Dichen Zhanga4819142022-10-21 04:12:30 +000047 if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000048 return false;
49 }
50 ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
51 (width * height * 12) / 8, width, height, mResultBuffer.size());
52 return true;
53}
54
Dichen Zhang02dd0592023-02-10 20:16:57 +000055void* JpegEncoderHelper::getCompressedImagePtr() {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000056 return mResultBuffer.data();
57}
58
Dichen Zhang02dd0592023-02-10 20:16:57 +000059size_t JpegEncoderHelper::getCompressedImageSize() {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000060 return mResultBuffer.size();
61}
62
Dichen Zhang02dd0592023-02-10 20:16:57 +000063void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000064 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
65 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
66 buffer.resize(kBlockSize);
67 dest->mgr.next_output_byte = &buffer[0];
68 dest->mgr.free_in_buffer = buffer.size();
69}
70
Dichen Zhang02dd0592023-02-10 20:16:57 +000071boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000072 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
73 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
74 size_t oldsize = buffer.size();
75 buffer.resize(oldsize + kBlockSize);
76 dest->mgr.next_output_byte = &buffer[oldsize];
77 dest->mgr.free_in_buffer = kBlockSize;
78 return true;
79}
80
Dichen Zhang02dd0592023-02-10 20:16:57 +000081void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000082 destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
83 std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
84 buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
85}
86
Dichen Zhang02dd0592023-02-10 20:16:57 +000087void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000088 char buffer[JMSG_LENGTH_MAX];
89
90 /* Create the message */
91 (*cinfo->err->format_message) (cinfo, buffer);
92 ALOGE("%s\n", buffer);
93}
94
Dichen Zhang02dd0592023-02-10 20:16:57 +000095bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
Dichen Zhanga4819142022-10-21 04:12:30 +000096 const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +000097 jpeg_compress_struct cinfo;
98 jpeg_error_mgr jerr;
99
100 cinfo.err = jpeg_std_error(&jerr);
101 // Override output_message() to print error log with ALOGE().
102 cinfo.err->output_message = &outputErrorMessage;
103 jpeg_create_compress(&cinfo);
104 setJpegDestination(&cinfo);
105
Dichen Zhanga4819142022-10-21 04:12:30 +0000106 setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000107 jpeg_start_compress(&cinfo, TRUE);
108
109 if (iccBuffer != nullptr && iccSize > 0) {
110 jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
111 }
112
Dichen Zhanga4819142022-10-21 04:12:30 +0000113 if (!compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel)) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000114 return false;
115 }
116 jpeg_finish_compress(&cinfo);
117 jpeg_destroy_compress(&cinfo);
118 return true;
119}
120
Dichen Zhang02dd0592023-02-10 20:16:57 +0000121void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000122 destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
123 (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
124 dest->encoder = this;
125 dest->mgr.init_destination = &initDestination;
126 dest->mgr.empty_output_buffer = &emptyOutputBuffer;
127 dest->mgr.term_destination = &terminateDestination;
128 cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
129}
130
Dichen Zhang02dd0592023-02-10 20:16:57 +0000131void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
Dichen Zhanga4819142022-10-21 04:12:30 +0000132 jpeg_compress_struct* cinfo, bool isSingleChannel) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000133 cinfo->image_width = width;
134 cinfo->image_height = height;
Dichen Zhanga4819142022-10-21 04:12:30 +0000135 if (isSingleChannel) {
136 cinfo->input_components = 1;
137 cinfo->in_color_space = JCS_GRAYSCALE;
138 } else {
139 cinfo->input_components = 3;
140 cinfo->in_color_space = JCS_YCbCr;
141 }
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000142 jpeg_set_defaults(cinfo);
143
144 jpeg_set_quality(cinfo, quality, TRUE);
Dichen Zhanga4819142022-10-21 04:12:30 +0000145 jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000146 cinfo->raw_data_in = TRUE;
147 cinfo->dct_method = JDCT_IFAST;
148
Dichen Zhanga4819142022-10-21 04:12:30 +0000149 if (!isSingleChannel) {
150 // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
151 // source format is YUV420.
152 cinfo->comp_info[0].h_samp_factor = 2;
153 cinfo->comp_info[0].v_samp_factor = 2;
154 cinfo->comp_info[1].h_samp_factor = 1;
155 cinfo->comp_info[1].v_samp_factor = 1;
156 cinfo->comp_info[2].h_samp_factor = 1;
157 cinfo->comp_info[2].v_samp_factor = 1;
158 }
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000159}
160
Dichen Zhang02dd0592023-02-10 20:16:57 +0000161bool JpegEncoderHelper::compress(
Dichen Zhanga4819142022-10-21 04:12:30 +0000162 jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
163 if (isSingleChannel) {
164 return compressSingleChannel(cinfo, image);
165 }
166 return compressYuv(cinfo, image);
167}
168
Dichen Zhang02dd0592023-02-10 20:16:57 +0000169bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
Dichen Zhang4a66b3c2022-10-19 18:04:47 +0000170 JSAMPROW y[kCompressBatchSize];
171 JSAMPROW cb[kCompressBatchSize / 2];
172 JSAMPROW cr[kCompressBatchSize / 2];
173 JSAMPARRAY planes[3] {y, cb, cr};
174
175 size_t y_plane_size = cinfo->image_width * cinfo->image_height;
176 size_t uv_plane_size = y_plane_size / 4;
177 uint8_t* y_plane = const_cast<uint8_t*>(yuv);
178 uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
179 uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
180 std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
181 memset(empty.get(), 0, cinfo->image_width);
182
183 while (cinfo->next_scanline < cinfo->image_height) {
184 for (int i = 0; i < kCompressBatchSize; ++i) {
185 size_t scanline = cinfo->next_scanline + i;
186 if (scanline < cinfo->image_height) {
187 y[i] = y_plane + scanline * cinfo->image_width;
188 } else {
189 y[i] = empty.get();
190 }
191 }
192 // cb, cr only have half scanlines
193 for (int i = 0; i < kCompressBatchSize / 2; ++i) {
194 size_t scanline = cinfo->next_scanline / 2 + i;
195 if (scanline < cinfo->image_height / 2) {
196 int offset = scanline * (cinfo->image_width / 2);
197 cb[i] = u_plane + offset;
198 cr[i] = v_plane + offset;
199 } else {
200 cb[i] = cr[i] = empty.get();
201 }
202 }
203
204 int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
205 if (processed != kCompressBatchSize) {
206 ALOGE("Number of processed lines does not equal input lines.");
207 return false;
208 }
209 }
210 return true;
211}
212
Dichen Zhang02dd0592023-02-10 20:16:57 +0000213bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
Dichen Zhanga4819142022-10-21 04:12:30 +0000214 JSAMPROW y[kCompressBatchSize];
215 JSAMPARRAY planes[1] {y};
216
217 uint8_t* y_plane = const_cast<uint8_t*>(image);
218 std::unique_ptr<uint8_t[]> empty(new uint8_t[cinfo->image_width]);
219 memset(empty.get(), 0, cinfo->image_width);
220
221 while (cinfo->next_scanline < cinfo->image_height) {
222 for (int i = 0; i < kCompressBatchSize; ++i) {
223 size_t scanline = cinfo->next_scanline + i;
224 if (scanline < cinfo->image_height) {
225 y[i] = y_plane + scanline * cinfo->image_width;
226 } else {
227 y[i] = empty.get();
228 }
229 }
230 int processed = jpeg_write_raw_data(cinfo, planes, kCompressBatchSize);
231 if (processed != kCompressBatchSize / 2) {
232 ALOGE("Number of processed lines does not equal input lines.");
233 return false;
234 }
235 }
236 return true;
237}
238
Nick Deakinf6bca5a2022-11-04 10:43:43 -0400239} // namespace android