• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 <ultrahdr/jpegencoderhelper.h>
18 
19 #include <utils/Log.h>
20 
21 #include <errno.h>
22 
23 namespace android::ultrahdr {
24 
25 #define ALIGNM(x, m)  ((((x) + ((m) - 1)) / (m)) * (m))
26 
27 // The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
28 struct destination_mgr {
29 public:
30     struct jpeg_destination_mgr mgr;
31     JpegEncoderHelper* encoder;
32 };
33 
JpegEncoderHelper()34 JpegEncoderHelper::JpegEncoderHelper() {
35 }
36 
~JpegEncoderHelper()37 JpegEncoderHelper::~JpegEncoderHelper() {
38 }
39 
compressImage(const void * image,int width,int height,int quality,const void * iccBuffer,unsigned int iccSize,bool isSingleChannel)40 bool JpegEncoderHelper::compressImage(const void* image, int width, int height, int quality,
41                                    const void* iccBuffer, unsigned int iccSize,
42                                    bool isSingleChannel) {
43     mResultBuffer.clear();
44     if (!encode(image, width, height, quality, iccBuffer, iccSize, isSingleChannel)) {
45         return false;
46     }
47     ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes",
48         (width * height * 12) / 8, width, height, mResultBuffer.size());
49     return true;
50 }
51 
getCompressedImagePtr()52 void* JpegEncoderHelper::getCompressedImagePtr() {
53     return mResultBuffer.data();
54 }
55 
getCompressedImageSize()56 size_t JpegEncoderHelper::getCompressedImageSize() {
57     return mResultBuffer.size();
58 }
59 
initDestination(j_compress_ptr cinfo)60 void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
61     destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
62     std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
63     buffer.resize(kBlockSize);
64     dest->mgr.next_output_byte = &buffer[0];
65     dest->mgr.free_in_buffer = buffer.size();
66 }
67 
emptyOutputBuffer(j_compress_ptr cinfo)68 boolean JpegEncoderHelper::emptyOutputBuffer(j_compress_ptr cinfo) {
69     destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
70     std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
71     size_t oldsize = buffer.size();
72     buffer.resize(oldsize + kBlockSize);
73     dest->mgr.next_output_byte = &buffer[oldsize];
74     dest->mgr.free_in_buffer = kBlockSize;
75     return true;
76 }
77 
terminateDestination(j_compress_ptr cinfo)78 void JpegEncoderHelper::terminateDestination(j_compress_ptr cinfo) {
79     destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
80     std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
81     buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
82 }
83 
outputErrorMessage(j_common_ptr cinfo)84 void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
85     char buffer[JMSG_LENGTH_MAX];
86 
87     /* Create the message */
88     (*cinfo->err->format_message) (cinfo, buffer);
89     ALOGE("%s\n", buffer);
90 }
91 
encode(const void * image,int width,int height,int jpegQuality,const void * iccBuffer,unsigned int iccSize,bool isSingleChannel)92 bool JpegEncoderHelper::encode(const void* image, int width, int height, int jpegQuality,
93                          const void* iccBuffer, unsigned int iccSize, bool isSingleChannel) {
94     jpeg_compress_struct cinfo;
95     jpeg_error_mgr jerr;
96 
97     cinfo.err = jpeg_std_error(&jerr);
98     // Override output_message() to print error log with ALOGE().
99     cinfo.err->output_message = &outputErrorMessage;
100     jpeg_create_compress(&cinfo);
101     setJpegDestination(&cinfo);
102 
103     setJpegCompressStruct(width, height, jpegQuality, &cinfo, isSingleChannel);
104     jpeg_start_compress(&cinfo, TRUE);
105 
106     if (iccBuffer != nullptr && iccSize > 0) {
107         jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
108     }
109 
110     bool status = compress(&cinfo, static_cast<const uint8_t*>(image), isSingleChannel);
111     jpeg_finish_compress(&cinfo);
112     jpeg_destroy_compress(&cinfo);
113 
114     return status;
115 }
116 
setJpegDestination(jpeg_compress_struct * cinfo)117 void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
118     destination_mgr* dest = static_cast<struct destination_mgr *>((*cinfo->mem->alloc_small) (
119             (j_common_ptr) cinfo, JPOOL_PERMANENT, sizeof(destination_mgr)));
120     dest->encoder = this;
121     dest->mgr.init_destination = &initDestination;
122     dest->mgr.empty_output_buffer = &emptyOutputBuffer;
123     dest->mgr.term_destination = &terminateDestination;
124     cinfo->dest = reinterpret_cast<struct jpeg_destination_mgr*>(dest);
125 }
126 
setJpegCompressStruct(int width,int height,int quality,jpeg_compress_struct * cinfo,bool isSingleChannel)127 void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
128                                         jpeg_compress_struct* cinfo, bool isSingleChannel) {
129     cinfo->image_width = width;
130     cinfo->image_height = height;
131     if (isSingleChannel) {
132         cinfo->input_components = 1;
133         cinfo->in_color_space = JCS_GRAYSCALE;
134     } else {
135         cinfo->input_components = 3;
136         cinfo->in_color_space = JCS_YCbCr;
137     }
138     jpeg_set_defaults(cinfo);
139 
140     jpeg_set_quality(cinfo, quality, TRUE);
141     jpeg_set_colorspace(cinfo, isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr);
142     cinfo->raw_data_in = TRUE;
143     cinfo->dct_method = JDCT_IFAST;
144 
145     if (!isSingleChannel) {
146         // Configure sampling factors. The sampling factor is JPEG subsampling 420 because the
147         // source format is YUV420.
148         cinfo->comp_info[0].h_samp_factor = 2;
149         cinfo->comp_info[0].v_samp_factor = 2;
150         cinfo->comp_info[1].h_samp_factor = 1;
151         cinfo->comp_info[1].v_samp_factor = 1;
152         cinfo->comp_info[2].h_samp_factor = 1;
153         cinfo->comp_info[2].v_samp_factor = 1;
154     }
155 }
156 
compress(jpeg_compress_struct * cinfo,const uint8_t * image,bool isSingleChannel)157 bool JpegEncoderHelper::compress(
158         jpeg_compress_struct* cinfo, const uint8_t* image, bool isSingleChannel) {
159     if (isSingleChannel) {
160         return compressSingleChannel(cinfo, image);
161     }
162     return compressYuv(cinfo, image);
163 }
164 
compressYuv(jpeg_compress_struct * cinfo,const uint8_t * yuv)165 bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yuv) {
166     JSAMPROW y[kCompressBatchSize];
167     JSAMPROW cb[kCompressBatchSize / 2];
168     JSAMPROW cr[kCompressBatchSize / 2];
169     JSAMPARRAY planes[3] {y, cb, cr};
170 
171     size_t y_plane_size = cinfo->image_width * cinfo->image_height;
172     size_t uv_plane_size = y_plane_size / 4;
173     uint8_t* y_plane = const_cast<uint8_t*>(yuv);
174     uint8_t* u_plane = const_cast<uint8_t*>(yuv + y_plane_size);
175     uint8_t* v_plane = const_cast<uint8_t*>(yuv + y_plane_size + uv_plane_size);
176     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
177     memset(empty.get(), 0, cinfo->image_width);
178 
179     const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
180     const bool is_width_aligned = (aligned_width == cinfo->image_width);
181     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
182     uint8_t* y_plane_intrm = nullptr;
183     uint8_t* u_plane_intrm = nullptr;
184     uint8_t* v_plane_intrm = nullptr;
185     JSAMPROW y_intrm[kCompressBatchSize];
186     JSAMPROW cb_intrm[kCompressBatchSize / 2];
187     JSAMPROW cr_intrm[kCompressBatchSize / 2];
188     JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
189     if (!is_width_aligned) {
190         size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
191         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
192         y_plane_intrm = buffer_intrm.get();
193         u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
194         v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
195         for (int i = 0; i < kCompressBatchSize; ++i) {
196             y_intrm[i] = y_plane_intrm + i * aligned_width;
197             memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
198         }
199         for (int i = 0; i < kCompressBatchSize / 2; ++i) {
200             int offset_intrm = i * (aligned_width / 2);
201             cb_intrm[i] = u_plane_intrm + offset_intrm;
202             cr_intrm[i] = v_plane_intrm + offset_intrm;
203             memset(cb_intrm[i] + cinfo->image_width / 2, 0,
204                    (aligned_width - cinfo->image_width) / 2);
205             memset(cr_intrm[i] + cinfo->image_width / 2, 0,
206                    (aligned_width - cinfo->image_width) / 2);
207         }
208     }
209 
210     while (cinfo->next_scanline < cinfo->image_height) {
211         for (int i = 0; i < kCompressBatchSize; ++i) {
212             size_t scanline = cinfo->next_scanline + i;
213             if (scanline < cinfo->image_height) {
214                 y[i] = y_plane + scanline * cinfo->image_width;
215             } else {
216                 y[i] = empty.get();
217             }
218             if (!is_width_aligned) {
219                 memcpy(y_intrm[i], y[i], cinfo->image_width);
220             }
221         }
222         // cb, cr only have half scanlines
223         for (int i = 0; i < kCompressBatchSize / 2; ++i) {
224             size_t scanline = cinfo->next_scanline / 2 + i;
225             if (scanline < cinfo->image_height / 2) {
226                 int offset = scanline * (cinfo->image_width / 2);
227                 cb[i] = u_plane + offset;
228                 cr[i] = v_plane + offset;
229             } else {
230                 cb[i] = cr[i] = empty.get();
231             }
232             if (!is_width_aligned) {
233                 memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
234                 memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
235             }
236         }
237         int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
238                                             kCompressBatchSize);
239         if (processed != kCompressBatchSize) {
240             ALOGE("Number of processed lines does not equal input lines.");
241             return false;
242         }
243     }
244     return true;
245 }
246 
compressSingleChannel(jpeg_compress_struct * cinfo,const uint8_t * image)247 bool JpegEncoderHelper::compressSingleChannel(jpeg_compress_struct* cinfo, const uint8_t* image) {
248     JSAMPROW y[kCompressBatchSize];
249     JSAMPARRAY planes[1] {y};
250 
251     uint8_t* y_plane = const_cast<uint8_t*>(image);
252     std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
253     memset(empty.get(), 0, cinfo->image_width);
254 
255     const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
256     bool is_width_aligned = (aligned_width == cinfo->image_width);
257     std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
258     uint8_t* y_plane_intrm = nullptr;
259     uint8_t* u_plane_intrm = nullptr;
260     JSAMPROW y_intrm[kCompressBatchSize];
261     JSAMPARRAY planes_intrm[]{y_intrm};
262     if (!is_width_aligned) {
263         size_t mcu_row_size = aligned_width * kCompressBatchSize;
264         buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
265         y_plane_intrm = buffer_intrm.get();
266         for (int i = 0; i < kCompressBatchSize; ++i) {
267             y_intrm[i] = y_plane_intrm + i * aligned_width;
268             memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
269         }
270     }
271 
272     while (cinfo->next_scanline < cinfo->image_height) {
273         for (int i = 0; i < kCompressBatchSize; ++i) {
274             size_t scanline = cinfo->next_scanline + i;
275             if (scanline < cinfo->image_height) {
276                 y[i] = y_plane + scanline * cinfo->image_width;
277             } else {
278                 y[i] = empty.get();
279             }
280             if (!is_width_aligned) {
281                 memcpy(y_intrm[i], y[i], cinfo->image_width);
282             }
283         }
284         int processed = jpeg_write_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
285                                             kCompressBatchSize);
286         if (processed != kCompressBatchSize / 2) {
287             ALOGE("Number of processed lines does not equal input lines.");
288             return false;
289         }
290     }
291     return true;
292 }
293 
294 } // namespace ultrahdr
295