1 /*
2  * Copyright (c) 2021-2022 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include "snapshot_utils.h"
17 
18 #include <cerrno>
19 #include <climits>
20 #include <cstdio>
21 #include <cstdlib>
22 #include <ctime>
23 #include <getopt.h>
24 #include <hitrace_meter.h>
25 #include <image_type.h>
26 #include <iostream>
27 #include <ostream>
28 #include <csetjmp>
29 #include <pixel_map.h>
30 #include <securec.h>
31 #include <string>
32 #include <sys/time.h>
33 
34 #include "image_packer.h"
35 #include "jpeglib.h"
36 
37 using namespace OHOS::Rosen;
38 
39 namespace OHOS {
40 constexpr int MAX_TIME_STR_LEN = 40;
41 constexpr int YEAR_SINCE = 1900;
42 constexpr int32_t RGB565_PIXEL_BYTES = 2;
43 constexpr int32_t RGB888_PIXEL_BYTES = 3;
44 constexpr int32_t RGBA8888_PIXEL_BYTES = 4;
45 constexpr uint8_t B_INDEX = 0;
46 constexpr uint8_t G_INDEX = 1;
47 constexpr uint8_t R_INDEX = 2;
48 constexpr uint8_t SHIFT_2_BIT = 2;
49 constexpr uint8_t SHITF_3_BIT = 3;
50 constexpr uint8_t SHIFT_5_BIT = 5;
51 constexpr uint8_t SHIFT_8_BIT = 8;
52 constexpr uint8_t SHIFT_11_BIT = 11;
53 constexpr uint8_t SHIFT_16_BIT = 16;
54 
55 constexpr uint16_t RGB565_MASK_BLUE = 0xF800;
56 constexpr uint16_t RGB565_MASK_GREEN = 0x07E0;
57 constexpr uint16_t RGB565_MASK_RED = 0x001F;
58 constexpr uint32_t RGBA8888_MASK_BLUE = 0x000000FF;
59 constexpr uint32_t RGBA8888_MASK_GREEN = 0x0000FF00;
60 constexpr uint32_t RGBA8888_MASK_RED = 0x00FF0000;
61 
62 constexpr uint8_t PACKER_QUALITY = 75;
63 constexpr uint32_t PACKER_SUCCESS = 0;
64 struct MissionErrorMgr : public jpeg_error_mgr {
65     jmp_buf environment;
66 };
67 
mission_error_exit(j_common_ptr cinfo)68 void mission_error_exit(j_common_ptr cinfo)
69 {
70     if (cinfo == nullptr || cinfo->err == nullptr) {
71         std::cout << __func__ << ": param is invalid." << std::endl;
72         return;
73     }
74     auto err = (MissionErrorMgr*)cinfo->err;
75     longjmp(err->environment, 1);
76 }
77 
78 const char *VALID_SNAPSHOT_PATH = "/data/local/tmp";
79 const char *DEFAULT_SNAPSHOT_PREFIX = "/snapshot";
80 const char *VALID_SNAPSHOT_SUFFIX = ".jpeg";
81 
PrintUsage(const std::string & cmdLine)82 void SnapShotUtils::PrintUsage(const std::string &cmdLine)
83 {
84     std::cout << "usage: " << cmdLine.c_str() <<
85         " [-i displayId] [-f output_file] [-w width] [-h height] [-m]" << std::endl;
86 }
87 
GenerateFileName(int offset)88 std::string SnapShotUtils::GenerateFileName(int offset)
89 {
90     timeval tv;
91     std::string fileName = VALID_SNAPSHOT_PATH;
92 
93     fileName += DEFAULT_SNAPSHOT_PREFIX;
94     if (gettimeofday(&tv, nullptr) == 0) {
95         tv.tv_sec += offset; // add offset second
96         struct tm *tmVal = localtime(&tv.tv_sec);
97         if (tmVal != nullptr) {
98             char timeStr[MAX_TIME_STR_LEN] = { 0 };
99             snprintf_s(timeStr, sizeof(timeStr), sizeof(timeStr) - 1,
100                 "_%04d-%02d-%02d_%02d-%02d-%02d",
101                 tmVal->tm_year + YEAR_SINCE, tmVal->tm_mon + 1, tmVal->tm_mday,
102                 tmVal->tm_hour, tmVal->tm_min, tmVal->tm_sec);
103             fileName += timeStr;
104         }
105     }
106     fileName += VALID_SNAPSHOT_SUFFIX;
107     return fileName;
108 }
109 
CheckFileNameValid(const std::string & fileName)110 bool SnapShotUtils::CheckFileNameValid(const std::string &fileName)
111 {
112     if (fileName.length() <= strlen(VALID_SNAPSHOT_SUFFIX)) {
113         std::cout << "error: fileName " << fileName.c_str() << " invalid, file length too short!" << std::endl;
114         return false;
115     }
116     // check file path
117     std::string fileDir = fileName;
118     auto pos = fileDir.find_last_of("/");
119     if (pos != std::string::npos) {
120         fileDir.erase(pos + 1);
121     } else {
122         fileDir = "."; // current work dir
123     }
124     char resolvedPath[PATH_MAX] = { 0 };
125     char *realPath = realpath(fileDir.c_str(), resolvedPath);
126     if (realPath == nullptr) {
127         std::cout << "error: fileName " << fileName.c_str() << " invalid, realpath nullptr!" << std::endl;
128         return false;
129     }
130     if (strncmp(realPath, VALID_SNAPSHOT_PATH, strlen(VALID_SNAPSHOT_PATH)) != 0) {
131         std::cout << "error: fileName " << fileName.c_str() << " invalid, realpath "
132             << realPath << " must dump at dir: " << VALID_SNAPSHOT_PATH << std::endl;
133         return false;
134     }
135 
136     // check file suffix
137     const char *fileNameSuffix = fileName.c_str() + (fileName.length() - strlen(VALID_SNAPSHOT_SUFFIX));
138     if (strncmp(fileNameSuffix, VALID_SNAPSHOT_SUFFIX, strlen(VALID_SNAPSHOT_SUFFIX)) == 0) {
139         return true; // valid suffix
140     }
141 
142     std::cout << "error: fileName " << fileName.c_str() <<
143         " invalid, suffix must be " << VALID_SNAPSHOT_SUFFIX << std::endl;
144     return false;
145 }
146 
CheckWHValid(int32_t param)147 bool SnapShotUtils::CheckWHValid(int32_t param)
148 {
149     return (param > 0) && (param <= DisplayManager::MAX_RESOLUTION_SIZE_SCREENSHOT);
150 }
151 
CheckWidthAndHeightValid(int32_t w,int32_t h)152 bool SnapShotUtils::CheckWidthAndHeightValid(int32_t w, int32_t h)
153 {
154     return CheckWHValid(w) && CheckWHValid(h);
155 }
156 
CheckParamValid(const WriteToJpegParam & param)157 bool SnapShotUtils::CheckParamValid(const WriteToJpegParam &param)
158 {
159     switch (param.format) {
160         case Media::PixelFormat::RGBA_8888:
161             if (param.stride != param.width * RGBA8888_PIXEL_BYTES) {
162                 return false;
163             }
164             break;
165         case Media::PixelFormat::RGB_565:
166             if (param.stride != param.width * RGB565_PIXEL_BYTES) {
167                 return false;
168             }
169             break;
170         case Media::PixelFormat::RGB_888:
171             if (param.stride != param.width * RGB888_PIXEL_BYTES) {
172                 return false;
173             }
174             break;
175         default:
176             std::cout << __func__ << ": unsupported pixel format: " <<
177                 static_cast<uint32_t>(param.format) << std::endl;
178             return false;
179     }
180     if (!CheckWidthAndHeightValid(param.width, param.height)) {
181         return false;
182     }
183     if (param.data == nullptr) {
184         return false;
185     }
186     return true;
187 }
188 
RGBA8888ToRGB888(const uint8_t * rgba8888Buf,uint8_t * rgb888Buf,int32_t size)189 bool SnapShotUtils::RGBA8888ToRGB888(const uint8_t* rgba8888Buf, uint8_t *rgb888Buf, int32_t size)
190 {
191     if (rgba8888Buf == nullptr || rgb888Buf == nullptr || size <= 0) {
192         std::cout << __func__ << ": params are invalid." << std::endl;
193         return false;
194     }
195     const uint32_t* rgba8888 = reinterpret_cast<const uint32_t*>(rgba8888Buf);
196     for (int32_t i = 0; i < size; i++) {
197         rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgba8888[i] & RGBA8888_MASK_RED) >> SHIFT_16_BIT;
198         rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgba8888[i] & RGBA8888_MASK_GREEN) >> SHIFT_8_BIT;
199         rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = rgba8888[i] & RGBA8888_MASK_BLUE;
200     }
201     return true;
202 }
203 
RGB565ToRGB888(const uint8_t * rgb565Buf,uint8_t * rgb888Buf,int32_t size)204 bool SnapShotUtils::RGB565ToRGB888(const uint8_t* rgb565Buf, uint8_t *rgb888Buf, int32_t size)
205 {
206     if (rgb565Buf == nullptr || rgb888Buf == nullptr || size <= 0) {
207         std::cout << __func__ << ": params are invalid." << std::endl;
208         return false;
209     }
210     const uint16_t* rgb565 = reinterpret_cast<const uint16_t*>(rgb565Buf);
211     for (int32_t i = 0; i < size; i++) {
212         rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgb565[i] & RGB565_MASK_RED);
213         rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgb565[i] & RGB565_MASK_GREEN) >> SHIFT_5_BIT;
214         rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = (rgb565[i] & RGB565_MASK_BLUE) >> SHIFT_11_BIT;
215         rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] <<= SHITF_3_BIT;
216         rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] <<= SHIFT_2_BIT;
217         rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] <<= SHITF_3_BIT;
218     }
219     return true;
220 }
221 
222 // The method will NOT release file.
WriteRgb888ToJpeg(FILE * file,uint32_t width,uint32_t height,const uint8_t * data)223 bool SnapShotUtils::WriteRgb888ToJpeg(FILE* file, uint32_t width, uint32_t height, const uint8_t* data)
224 {
225     if (data == nullptr) {
226         std::cout << "error: data error, nullptr!" << std::endl;
227         return false;
228     }
229 
230     if (file == nullptr) {
231         std::cout << "error: file is null" << std::endl;
232         return false;
233     }
234 
235     struct jpeg_compress_struct jpeg;
236     struct MissionErrorMgr jerr;
237     jpeg.err = jpeg_std_error(&jerr);
238     jerr.error_exit = mission_error_exit;
239     if (setjmp(jerr.environment)) {
240         jpeg_destroy_compress(&jpeg);
241         std::cout << "error: lib jpeg exit with error!" << std::endl;
242         return false;
243     }
244 
245     jpeg_create_compress(&jpeg);
246     jpeg.image_width = width;
247     jpeg.image_height = height;
248     jpeg.input_components = RGB888_PIXEL_BYTES;
249     jpeg.in_color_space = JCS_RGB;
250     jpeg_set_defaults(&jpeg);
251 
252     constexpr int32_t quality = 75;
253     jpeg_set_quality(&jpeg, quality, TRUE);
254 
255     jpeg_stdio_dest(&jpeg, file);
256     jpeg_start_compress(&jpeg, TRUE);
257     JSAMPROW rowPointer[1];
258     for (uint32_t i = 0; i < jpeg.image_height; i++) {
259         rowPointer[0] = const_cast<uint8_t *>(data + i * jpeg.image_width * RGB888_PIXEL_BYTES);
260         (void)jpeg_write_scanlines(&jpeg, rowPointer, 1);
261     }
262 
263     jpeg_finish_compress(&jpeg);
264     jpeg_destroy_compress(&jpeg);
265     return true;
266 }
267 
WriteToJpeg(const std::string & fileName,const WriteToJpegParam & param)268 bool SnapShotUtils::WriteToJpeg(const std::string &fileName, const WriteToJpegParam &param)
269 {
270     bool ret = false;
271     if (!CheckFileNameValid(fileName)) {
272         return ret;
273     }
274     if (!CheckParamValid(param)) {
275         std::cout << "error: invalid param." << std::endl;
276         return ret;
277     }
278     HITRACE_METER_FMT(HITRACE_TAG_WINDOW_MANAGER, "snapshot:WriteToJpeg(%s)", fileName.c_str());
279 
280     FILE *file = fopen(fileName.c_str(), "wb");
281     if (file == nullptr) {
282         std::cout << "error: open file [" << fileName.c_str() << "] error, " << errno << "!" << std::endl;
283         return ret;
284     }
285     std::cout << "snapshot: pixel format is: " << static_cast<uint32_t>(param.format) << std::endl;
286     if (param.format == Media::PixelFormat::RGBA_8888) {
287         int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGBA8888_PIXEL_BYTES;
288         uint8_t *rgb888 = new uint8_t[rgb888Size];
289         ret = RGBA8888ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
290         if (ret) {
291             std::cout << "snapshot: convert rgba8888 to rgb888 successfully." << std::endl;
292             ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
293         }
294         delete[] rgb888;
295     } else if (param.format == Media::PixelFormat::RGB_565) {
296         int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGB565_PIXEL_BYTES;
297         uint8_t *rgb888 = new uint8_t[rgb888Size];
298         ret = RGB565ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
299         if (ret) {
300             std::cout << "snapshot: convert rgb565 to rgb888 successfully." << std::endl;
301             ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
302         }
303         delete[] rgb888;
304     } else if (param.format == Media::PixelFormat::RGB_888) {
305         ret = WriteRgb888ToJpeg(file, param.width, param.height, param.data);
306     } else {
307         std::cout << "snapshot: invalid pixel format." << std::endl;
308     }
309     if (fclose(file) != 0) {
310         std::cout << "error: close file failed!" << std::endl;
311         ret = false;
312     }
313     return ret;
314 }
315 
WriteToJpeg(int fd,const WriteToJpegParam & param)316 bool SnapShotUtils::WriteToJpeg(int fd, const WriteToJpegParam &param)
317 {
318     bool ret = false;
319     if (!CheckParamValid(param)) {
320         std::cout << "error: invalid param." << std::endl;
321         return ret;
322     }
323 
324     FILE *file = fdopen(fd, "wb");
325     if (file == nullptr) {
326         return ret;
327     }
328     std::cout << "snapshot: pixel format is: " << static_cast<uint32_t>(param.format) << std::endl;
329     if (param.format == Media::PixelFormat::RGBA_8888) {
330         int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGBA8888_PIXEL_BYTES;
331         uint8_t *rgb888 = new uint8_t[rgb888Size];
332         ret = RGBA8888ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
333         if (ret) {
334             std::cout << "snapshot: convert rgba8888 to rgb888 successfully." << std::endl;
335             ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
336         }
337         delete[] rgb888;
338     } else if (param.format == Media::PixelFormat::RGB_565) {
339         int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGB565_PIXEL_BYTES;
340         uint8_t *rgb888 = new uint8_t[rgb888Size];
341         ret = RGB565ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
342         if (ret) {
343             std::cout << "snapshot: convert rgb565 to rgb888 successfully." << std::endl;
344             ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
345         }
346         delete[] rgb888;
347     } else if (param.format == Media::PixelFormat::RGB_888) {
348         ret = WriteRgb888ToJpeg(file, param.width, param.height, param.data);
349     } else {
350         std::cout << "snapshot: invalid pixel format." << std::endl;
351     }
352     if (fclose(file) != 0) {
353         std::cout << "error: close file failed!" << std::endl;
354         ret = false;
355     }
356     return ret;
357 }
358 
SaveSnapShot(const std::string & fileName,Media::PixelMap & pixelMap)359 bool SnapShotUtils::SaveSnapShot(const std::string &fileName, Media::PixelMap &pixelMap)
360 {
361     OHOS::Media::ImagePacker imagePacker;
362     OHOS::Media::PackOption option;
363     option.format = "image/jpeg";
364     option.quality = PACKER_QUALITY;
365     option.numberHint = 1;
366     std::set<std::string> formats;
367     auto ret = imagePacker.GetSupportedFormats(formats);
368     if (ret) {
369         std::cout << "error: get supported formats error" << std::endl;
370         return false;
371     }
372 
373     imagePacker.StartPacking(fileName, option);
374     imagePacker.AddImage(pixelMap);
375     int64_t packedSize = 0;
376     uint32_t res = imagePacker.FinalizePacking(packedSize);
377     if (res != PACKER_SUCCESS) {
378         std::cout << "error:FinalizePacking error" << std::endl;
379         return false;
380     }
381     return true;
382 }
383 
WriteToJpegWithPixelMap(const std::string & fileName,Media::PixelMap & pixelMap)384 bool SnapShotUtils::WriteToJpegWithPixelMap(const std::string &fileName, Media::PixelMap &pixelMap)
385 {
386     if (pixelMap.GetAllocatorType() == Media::AllocatorType::DMA_ALLOC) {
387         return SaveSnapShot(fileName, pixelMap);
388     }
389     WriteToJpegParam param;
390     param.width = static_cast<uint32_t>(pixelMap.GetWidth());
391     param.height = static_cast<uint32_t>(pixelMap.GetHeight());
392     param.data = pixelMap.GetPixels();
393     param.stride = static_cast<uint32_t>(pixelMap.GetRowBytes());
394     param.format = pixelMap.GetPixelFormat();
395     return SnapShotUtils::WriteToJpeg(fileName, param);
396 }
397 
WriteToJpegWithPixelMap(int fd,Media::PixelMap & pixelMap)398 bool SnapShotUtils::WriteToJpegWithPixelMap(int fd, Media::PixelMap &pixelMap)
399 {
400     WriteToJpegParam param;
401     param.width = static_cast<uint32_t>(pixelMap.GetWidth());
402     param.height = static_cast<uint32_t>(pixelMap.GetHeight());
403     param.data = pixelMap.GetPixels();
404     param.stride = static_cast<uint32_t>(pixelMap.GetRowBytes());
405     param.format = pixelMap.GetPixelFormat();
406     return SnapShotUtils::WriteToJpeg(fd, param);
407 }
408 
ProcessDisplayId(Rosen::DisplayId & displayId,bool isDisplayIdSet)409 bool SnapShotUtils::ProcessDisplayId(Rosen::DisplayId &displayId, bool isDisplayIdSet)
410 {
411     if (!isDisplayIdSet) {
412         displayId = DisplayManager::GetInstance().GetDefaultDisplayId();
413     } else {
414         bool validFlag = false;
415         auto displayIds = DisplayManager::GetInstance().GetAllDisplayIds();
416         for (auto id: displayIds) {
417             if (displayId == id) {
418                 validFlag = true;
419                 break;
420             }
421         }
422         if (!validFlag) {
423             std::cout << "error: displayId " << static_cast<int64_t>(displayId) << " invalid!" << std::endl;
424             std::cout << "tips: supported displayIds:" << std::endl;
425             for (auto dispId: displayIds) {
426                 std::cout << "\t" << dispId << std::endl;
427             }
428             return false;
429         }
430     }
431     return true;
432 }
433 
ProcessArgs(int argc,char * const argv[],CmdArgments & cmdArgments)434 bool SnapShotUtils::ProcessArgs(int argc, char * const argv[], CmdArgments &cmdArgments)
435 {
436     int opt = 0;
437     const struct option longOption[] = {
438         { "id", required_argument, nullptr, 'i' },
439         { "width", required_argument, nullptr, 'w' },
440         { "height", required_argument, nullptr, 'h' },
441         { "file", required_argument, nullptr, 'f' },
442         { "help", required_argument, nullptr, 'm' },
443         { nullptr, 0, nullptr, 0 }
444     };
445     while ((opt = getopt_long(argc, argv, "i:w:h:f:m", longOption, nullptr)) != -1) {
446         switch (opt) {
447             case 'i': // display id
448                 cmdArgments.displayId = static_cast<DisplayId>(atoll(optarg));
449                 cmdArgments.isDisplayIdSet = true;
450                 break;
451             case 'w': // output width
452                 cmdArgments.width = atoi(optarg);
453                 cmdArgments.isWidthSet = true;
454                 break;
455             case 'h': // output height
456                 cmdArgments.height = atoi(optarg);
457                 cmdArgments.isHeightSet = true;
458                 break;
459             case 'f': // output file name
460                 cmdArgments.fileName = optarg;
461                 break;
462             case 'm': // help
463             default:
464                 SnapShotUtils::PrintUsage(argv[0]);
465                 return false;
466         }
467     }
468 
469     if (!ProcessDisplayId(cmdArgments.displayId, cmdArgments.isDisplayIdSet)) {
470         return false;
471     }
472 
473     if (cmdArgments.fileName == "") {
474         cmdArgments.fileName = GenerateFileName();
475         std::cout << "process: set filename to " << cmdArgments.fileName.c_str() << std::endl;
476     }
477 
478     // check fileName
479     if (!SnapShotUtils::CheckFileNameValid(cmdArgments.fileName)) {
480         std::cout << "error: filename " << cmdArgments.fileName.c_str() << " invalid!" << std::endl;
481         return false;
482     }
483     return true;
484 }
485 }
486