* Copyright (c) 2021-2022 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "snapshot_utils.h"
#include <cerrno>
#include <climits>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <getopt.h>
#include <hitrace_meter.h>
#include <image_type.h>
#include <iostream>
#include <ostream>
#include <csetjmp>
#include <pixel_map.h>
#include <securec.h>
#include <string>
#include <sys/time.h>
#include "image_packer.h"
#include "jpeglib.h"
using namespace OHOS::Rosen;
namespace OHOS {
constexpr int MAX_TIME_STR_LEN = 40;
constexpr int YEAR_SINCE = 1900;
constexpr int32_t RGB565_PIXEL_BYTES = 2;
constexpr int32_t RGB888_PIXEL_BYTES = 3;
constexpr int32_t RGBA8888_PIXEL_BYTES = 4;
constexpr uint8_t B_INDEX = 0;
constexpr uint8_t G_INDEX = 1;
constexpr uint8_t R_INDEX = 2;
constexpr uint8_t SHIFT_2_BIT = 2;
constexpr uint8_t SHIFT_3_BIT = 3;
constexpr uint8_t SHIFT_5_BIT = 5;
constexpr uint8_t SHIFT_8_BIT = 8;
constexpr uint8_t SHIFT_11_BIT = 11;
constexpr uint8_t SHIFT_16_BIT = 16;
constexpr uint16_t RGB565_MASK_BLUE = 0xF800;
constexpr uint16_t RGB565_MASK_GREEN = 0x07E0;
constexpr uint16_t RGB565_MASK_RED = 0x001F;
constexpr uint32_t RGBA8888_MASK_BLUE = 0x000000FF;
constexpr uint32_t RGBA8888_MASK_GREEN = 0x0000FF00;
constexpr uint32_t RGBA8888_MASK_RED = 0x00FF0000;
constexpr uint8_t PNG_PACKER_QUALITY = 100;
constexpr uint8_t PACKER_QUALITY = 75;
constexpr uint32_t PACKER_SUCCESS = 0;
struct MissionErrorMgr : public jpeg_error_mgr {
jmp_buf environment;
};
void mission_error_exit(j_common_ptr cinfo)
{
if (cinfo == nullptr || cinfo->err == nullptr) {
std::cout << __func__ << ": param is invalid." << std::endl;
return;
}
auto err = reinterpret_cast<MissionErrorMgr*>(cinfo->err);
longjmp(err->environment, 1);
}
const char *VALID_SNAPSHOT_PATH = "/data/local/tmp";
const char *DEFAULT_SNAPSHOT_PREFIX = "/snapshot";
const char *VALID_SNAPSHOT_SUFFIX = ".jpeg";
const char *VALID_SNAPSHOT_PNG_SUFFIX = ".png";
void SnapShotUtils::PrintUsage(const std::string& cmdLine)
{
std::cout << "usage: " << cmdLine.c_str() <<
" [-i displayId] [-f output_file] [-w width] [-h height] [-t type] [-m]" << std::endl;
}
std::string SnapShotUtils::GenerateFileName(std::string fileType, int offset)
{
timeval tv;
std::string fileName = VALID_SNAPSHOT_PATH;
fileName += DEFAULT_SNAPSHOT_PREFIX;
if (gettimeofday(&tv, nullptr) == 0) {
tv.tv_sec += offset;
struct tm *tmVal = localtime(&tv.tv_sec);
if (tmVal != nullptr) {
char timeStr[MAX_TIME_STR_LEN] = { 0 };
snprintf_s(timeStr, sizeof(timeStr), sizeof(timeStr) - 1,
"_%04d-%02d-%02d_%02d-%02d-%02d",
tmVal->tm_year + YEAR_SINCE, tmVal->tm_mon + 1, tmVal->tm_mday,
tmVal->tm_hour, tmVal->tm_min, tmVal->tm_sec);
fileName += timeStr;
}
}
fileName += (fileType == "png") ? VALID_SNAPSHOT_PNG_SUFFIX : VALID_SNAPSHOT_SUFFIX;
return fileName;
}
bool SnapShotUtils::CheckFileNameValid(const std::string& fileName, std::string fileType)
{
std::cout << "fileType: " << fileType << std::endl;
size_t fileMinLength = (fileType == "png") ? strlen(VALID_SNAPSHOT_PNG_SUFFIX) : strlen(VALID_SNAPSHOT_SUFFIX);
if (fileName.length() <= fileMinLength) {
std::cout << "error: fileName " << fileName.c_str() << " invalid, file length too short!" << std::endl;
return false;
}
std::string fileDir = fileName;
auto pos = fileDir.find_last_of("/");
if (pos != std::string::npos) {
fileDir.erase(pos + 1);
} else {
fileDir = ".";
}
char resolvedPath[PATH_MAX] = { 0 };
char *realPath = realpath(fileDir.c_str(), resolvedPath);
if (realPath == nullptr) {
std::cout << "error: fileName " << fileName.c_str() << " invalid, realpath nullptr!" << std::endl;
return false;
}
if (strncmp(realPath, VALID_SNAPSHOT_PATH, strlen(VALID_SNAPSHOT_PATH)) != 0) {
std::cout << "error: fileName " << fileName.c_str() << " invalid, realpath "
<< realPath << " must dump at dir: " << VALID_SNAPSHOT_PATH << std::endl;
return false;
}
const char *fileNameSuffix = fileName.c_str() + (fileName.length() - fileMinLength);
const char *fileSuffix = (fileType == "png") ? VALID_SNAPSHOT_PNG_SUFFIX : VALID_SNAPSHOT_SUFFIX;
if (strncmp(fileNameSuffix, fileSuffix, fileMinLength) == 0) {
return true;
}
std::cout << "error: fileName " << fileName.c_str() << " invalid, suffix must be " << fileSuffix << std::endl;
return false;
}
bool SnapShotUtils::CheckWHValid(int32_t param)
{
return (param > 0) && (param <= DisplayManager::MAX_RESOLUTION_SIZE_SCREENSHOT);
}
bool SnapShotUtils::CheckWidthAndHeightValid(int32_t w, int32_t h)
{
return CheckWHValid(w) && CheckWHValid(h);
}
bool SnapShotUtils::CheckParamValid(const WriteToJpegParam& param)
{
switch (param.format) {
case Media::PixelFormat::RGBA_8888:
if (param.stride != param.width * RGBA8888_PIXEL_BYTES) {
return false;
}
break;
case Media::PixelFormat::RGB_565:
if (param.stride != param.width * RGB565_PIXEL_BYTES) {
return false;
}
break;
case Media::PixelFormat::RGB_888:
if (param.stride != param.width * RGB888_PIXEL_BYTES) {
return false;
}
break;
default:
std::cout << __func__ << ": unsupported pixel format: " <<
static_cast<uint32_t>(param.format) << std::endl;
return false;
}
if (!CheckWidthAndHeightValid(param.width, param.height)) {
return false;
}
if (param.data == nullptr) {
return false;
}
return true;
}
bool SnapShotUtils::RGBA8888ToRGB888(const uint8_t* rgba8888Buf, uint8_t* rgb888Buf, int32_t size)
{
if (rgba8888Buf == nullptr || rgb888Buf == nullptr || size <= 0) {
std::cout << __func__ << ": params are invalid." << std::endl;
return false;
}
const uint32_t* rgba8888 = reinterpret_cast<const uint32_t*>(rgba8888Buf);
for (int32_t i = 0; i < size; i++) {
rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgba8888[i] & RGBA8888_MASK_RED) >> SHIFT_16_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgba8888[i] & RGBA8888_MASK_GREEN) >> SHIFT_8_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = rgba8888[i] & RGBA8888_MASK_BLUE;
}
return true;
}
bool SnapShotUtils::RGB565ToRGB888(const uint8_t* rgb565Buf, uint8_t* rgb888Buf, int32_t size)
{
if (rgb565Buf == nullptr || rgb888Buf == nullptr || size <= 0) {
std::cout << __func__ << ": params are invalid." << std::endl;
return false;
}
const uint16_t* rgb565 = reinterpret_cast<const uint16_t*>(rgb565Buf);
for (int32_t i = 0; i < size; i++) {
rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] = (rgb565[i] & RGB565_MASK_RED);
rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] = (rgb565[i] & RGB565_MASK_GREEN) >> SHIFT_5_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] = (rgb565[i] & RGB565_MASK_BLUE) >> SHIFT_11_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + R_INDEX] <<= SHIFT_3_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + G_INDEX] <<= SHIFT_2_BIT;
rgb888Buf[i * RGB888_PIXEL_BYTES + B_INDEX] <<= SHIFT_3_BIT;
}
return true;
}
bool SnapShotUtils::WriteRgb888ToJpeg(FILE* file, uint32_t width, uint32_t height, const uint8_t* data)
{
if (data == nullptr) {
std::cout << "error: data error, nullptr!" << std::endl;
return false;
}
if (file == nullptr) {
std::cout << "error: file is null" << std::endl;
return false;
}
struct jpeg_compress_struct jpeg;
struct MissionErrorMgr jerr;
jpeg.err = jpeg_std_error(&jerr);
jerr.error_exit = mission_error_exit;
if (setjmp(jerr.environment)) {
jpeg_destroy_compress(&jpeg);
std::cout << "error: lib jpeg exit with error!" << std::endl;
return false;
}
jpeg_create_compress(&jpeg);
jpeg.image_width = width;
jpeg.image_height = height;
jpeg.input_components = RGB888_PIXEL_BYTES;
jpeg.in_color_space = JCS_RGB;
jpeg_set_defaults(&jpeg);
constexpr int32_t quality = 75;
jpeg_set_quality(&jpeg, quality, TRUE);
jpeg_stdio_dest(&jpeg, file);
jpeg_start_compress(&jpeg, TRUE);
JSAMPROW rowPointer[1];
for (uint32_t i = 0; i < jpeg.image_height; i++) {
rowPointer[0] = const_cast<uint8_t *>(data + i * jpeg.image_width * RGB888_PIXEL_BYTES);
(void)jpeg_write_scanlines(&jpeg, rowPointer, 1);
}
jpeg_finish_compress(&jpeg);
jpeg_destroy_compress(&jpeg);
return true;
}
bool SnapShotUtils::WriteToJpeg(const std::string& fileName, const WriteToJpegParam& param)
{
bool ret = false;
if (!CheckFileNameValid(fileName)) {
return ret;
}
if (!CheckParamValid(param)) {
std::cout << "error: invalid param." << std::endl;
return ret;
}
HITRACE_METER_FMT(HITRACE_TAG_WINDOW_MANAGER, "snapshot:WriteToJpeg(%s)", fileName.c_str());
FILE *file = fopen(fileName.c_str(), "wb");
if (file == nullptr) {
std::cout << "error: open file [" << fileName.c_str() << "] error, " << errno << "!" << std::endl;
return ret;
}
std::cout << "snapshot: pixel format is: " << static_cast<uint32_t>(param.format) << std::endl;
if (param.format == Media::PixelFormat::RGBA_8888) {
int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGBA8888_PIXEL_BYTES;
uint8_t *rgb888 = new uint8_t[rgb888Size];
ret = RGBA8888ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
if (ret) {
std::cout << "snapshot: convert rgba8888 to rgb888 successfully." << std::endl;
ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
}
delete[] rgb888;
} else if (param.format == Media::PixelFormat::RGB_565) {
int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGB565_PIXEL_BYTES;
uint8_t *rgb888 = new uint8_t[rgb888Size];
ret = RGB565ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
if (ret) {
std::cout << "snapshot: convert rgb565 to rgb888 successfully." << std::endl;
ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
}
delete[] rgb888;
} else if (param.format == Media::PixelFormat::RGB_888) {
ret = WriteRgb888ToJpeg(file, param.width, param.height, param.data);
} else {
std::cout << "snapshot: invalid pixel format." << std::endl;
}
if (fclose(file) != 0) {
std::cout << "error: close file failed!" << std::endl;
ret = false;
}
return ret;
}
bool SnapShotUtils::WriteToJpeg(int fd, const WriteToJpegParam& param)
{
bool ret = false;
if (!CheckParamValid(param)) {
std::cout << "error: invalid param." << std::endl;
return ret;
}
FILE *file = fdopen(fd, "wb");
if (file == nullptr) {
return ret;
}
std::cout << "snapshot: pixel format is: " << static_cast<uint32_t>(param.format) << std::endl;
if (param.format == Media::PixelFormat::RGBA_8888) {
int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGBA8888_PIXEL_BYTES;
uint8_t *rgb888 = new uint8_t[rgb888Size];
ret = RGBA8888ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
if (ret) {
std::cout << "snapshot: convert rgba8888 to rgb888 successfully." << std::endl;
ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
}
delete[] rgb888;
} else if (param.format == Media::PixelFormat::RGB_565) {
int32_t rgb888Size = param.stride * param.height * RGB888_PIXEL_BYTES / RGB565_PIXEL_BYTES;
uint8_t *rgb888 = new uint8_t[rgb888Size];
ret = RGB565ToRGB888(param.data, rgb888, rgb888Size / RGB888_PIXEL_BYTES);
if (ret) {
std::cout << "snapshot: convert rgb565 to rgb888 successfully." << std::endl;
ret = WriteRgb888ToJpeg(file, param.width, param.height, rgb888);
}
delete[] rgb888;
} else if (param.format == Media::PixelFormat::RGB_888) {
ret = WriteRgb888ToJpeg(file, param.width, param.height, param.data);
} else {
std::cout << "snapshot: invalid pixel format." << std::endl;
}
if (fclose(file) != 0) {
std::cout << "error: close file failed!" << std::endl;
ret = false;
}
return ret;
}
bool SnapShotUtils::SaveSnapShot(const std::string& fileName, Media::PixelMap& pixelMap, std::string fileType)
{
OHOS::Media::ImagePacker imagePacker;
OHOS::Media::PackOption option;
option.format = (fileType == "png") ? "image/png" : "image/jpeg";
option.quality = (fileType == "png") ? PNG_PACKER_QUALITY : PACKER_QUALITY;
option.numberHint = 1;
std::set<std::string> formats;
auto ret = imagePacker.GetSupportedFormats(formats);
if (ret) {
std::cout << "error: get supported formats error" << std::endl;
return false;
}
imagePacker.StartPacking(fileName, option);
imagePacker.AddImage(pixelMap);
int64_t packedSize = 0;
uint32_t res = imagePacker.FinalizePacking(packedSize);
if (res != PACKER_SUCCESS) {
std::cout << "error:FinalizePacking error" << std::endl;
return false;
}
return true;
}
bool SnapShotUtils::WriteToJpegWithPixelMap(const std::string& fileName, Media::PixelMap& pixelMap)
{
if (pixelMap.GetAllocatorType() == Media::AllocatorType::DMA_ALLOC) {
return SaveSnapShot(fileName, pixelMap);
}
WriteToJpegParam param;
param.width = static_cast<uint32_t>(pixelMap.GetWidth());
param.height = static_cast<uint32_t>(pixelMap.GetHeight());
param.data = pixelMap.GetPixels();
param.stride = static_cast<uint32_t>(pixelMap.GetRowBytes());
param.format = pixelMap.GetPixelFormat();
return SnapShotUtils::WriteToJpeg(fileName, param);
}
bool SnapShotUtils::WriteToJpegWithPixelMap(int fd, Media::PixelMap& pixelMap)
{
WriteToJpegParam param;
param.width = static_cast<uint32_t>(pixelMap.GetWidth());
param.height = static_cast<uint32_t>(pixelMap.GetHeight());
param.data = pixelMap.GetPixels();
param.stride = static_cast<uint32_t>(pixelMap.GetRowBytes());
param.format = pixelMap.GetPixelFormat();
return SnapShotUtils::WriteToJpeg(fd, param);
}
bool SnapShotUtils::ProcessDisplayId(Rosen::DisplayId& displayId, bool isDisplayIdSet)
{
if (!isDisplayIdSet) {
displayId = DisplayManager::GetInstance().GetDefaultDisplayId();
} else {
bool validFlag = false;
auto displayIds = DisplayManager::GetInstance().GetAllDisplayIds();
for (auto id : displayIds) {
if (displayId == id) {
validFlag = true;
break;
}
}
if (!validFlag) {
std::cout << "error: displayId " << static_cast<int64_t>(displayId) << " invalid!" << std::endl;
std::cout << "tips: supported displayIds:" << std::endl;
for (auto dispId : displayIds) {
std::cout << "\t" << dispId << std::endl;
}
return false;
}
}
return true;
}
bool SnapShotUtils::ProcessArgs(int argc, char* const argv[], CmdArguments& cmdArguments)
{
int opt = 0;
const struct option longOption[] = {
{ "id", required_argument, nullptr, 'i' },
{ "width", required_argument, nullptr, 'w' },
{ "height", required_argument, nullptr, 'h' },
{ "file", required_argument, nullptr, 'f' },
{ "type", required_argument, nullptr, 't' },
{ "help", required_argument, nullptr, 'm' },
{ nullptr, 0, nullptr, 0 }
};
while ((opt = getopt_long(argc, argv, "i:w:h:f:t:m", longOption, nullptr)) != -1) {
switch (opt) {
case 'i':
cmdArguments.displayId = static_cast<DisplayId>(atoll(optarg));
cmdArguments.isDisplayIdSet = true;
break;
case 'w':
cmdArguments.width = atoi(optarg);
cmdArguments.isWidthSet = true;
break;
case 'h':
cmdArguments.height = atoi(optarg);
cmdArguments.isHeightSet = true;
break;
case 'f':
cmdArguments.fileName = optarg;
break;
case 't':
cmdArguments.fileType = optarg;
break;
case 'm':
default:
SnapShotUtils::PrintUsage(argv[0]);
return false;
}
}
if (!ProcessDisplayId(cmdArguments.displayId, cmdArguments.isDisplayIdSet)) {
return false;
}
if (cmdArguments.fileName == "") {
cmdArguments.fileName = GenerateFileName(cmdArguments.fileType);
std::cout << "process: set filename to " << cmdArguments.fileName.c_str() << std::endl;
}
if (!SnapShotUtils::CheckFileNameValid(cmdArguments.fileName, cmdArguments.fileType)) {
std::cout << "error: filename " << cmdArguments.fileName.c_str() << " invalid!" << std::endl;
return false;
}
return true;
}
}