* Copyright (C) 2022-2026 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 "image_packer.h"
#include "buffer_packer_stream.h"
#include "file_packer_stream.h"
#include "image/abs_image_encoder.h"
#include "image_log.h"
#include "image_mime_type.h"
#include "image_trace.h"
#include "image_utils.h"
#include "media_errors.h"
#include "ostream_packer_stream.h"
#include "plugin_server.h"
#include "string_ex.h"
#if defined(SUPPORT_LIBTIFF)
#include "tiff_encoder.h"
#endif
#include "hdr_helper.h"
#if defined(ANDROID_PLATFORM) || defined(IOS_PLATFORM)
#include "include/jpeg_encoder.h"
#endif
#ifdef HEIF_HW_ENCODE_ENABLE
#include "image/v2_1/icodec_image.h"
#include "image/v2_1/codec_image_type.h"
#include "v4_0/codec_types.h"
#include "v4_0/icodec_component_manager.h"
#endif
#undef LOG_DOMAIN
#define LOG_DOMAIN LOG_TAG_DOMAIN_ID_IMAGE
#undef LOG_TAG
#define LOG_TAG "ImagePacker"
namespace OHOS {
namespace Media {
using namespace ImagePlugin;
using namespace MultimediaPlugin;
static constexpr uint8_t QUALITY_MAX = 100;
const static std::string EXTENDED_ENCODER = "image/jpeg,image/png,image/webp";
static constexpr size_t SIZE_ZERO = 0;
static constexpr uint8_t BITS_PER_BYTE = 8;
PluginServer &ImagePacker::pluginServer_ = ImageUtils::GetPluginServer();
#ifdef HEIF_HW_ENCODE_ENABLE
static bool IsEncodeSecureMode(const std::string &name)
{
std::string prefix = ".secure";
if (name.length() <= prefix.length()) {
return false;
}
return name.rfind(prefix) == (name.length() - prefix.length());
}
#endif
static bool IsSupportHeifEncode()
{
#ifdef HEIF_HW_ENCODE_ENABLE
sptr<HDI::Codec::Image::V2_1::ICodecImage> image =
HDI::Codec::Image::V2_1::ICodecImage::Get(false);
if (image == nullptr) {
return false;
}
std::vector<HDI::Codec::Image::V2_1::CodecImageCapability> capList;
int32_t ret = image->GetImageCapability(capList);
if (ret != HDF_SUCCESS || capList.empty()) {
return false;
}
for (const auto& cap : capList) {
if (cap.role == HDI::Codec::Image::V2_1::CODEC_IMAGE_HEIF &&
cap.type == HDI::Codec::Image::V2_1::CODEC_IMAGE_TYPE_ENCODER && !IsEncodeSecureMode(cap.name)) {
return true;
}
}
#endif
return false;
}
uint32_t ImagePacker::GetSupportedFormats(std::set<std::string> &formats)
{
formats.clear();
std::vector<ClassInfo> classInfos;
uint32_t ret =
pluginServer_.PluginServerGetClassInfo<AbsImageEncoder>(AbsImageEncoder::SERVICE_DEFAULT, classInfos);
CHECK_ERROR_RETURN_RET_LOG(ret != SUCCESS, ret,
"get class info from plugin server failed, ret:%{public}u.", ret);
for (auto &info : classInfos) {
std::map<std::string, AttrData> &capbility = info.capabilities;
auto iter = capbility.find(IMAGE_ENCODE_FORMAT);
if (iter == capbility.end()) {
continue;
}
AttrData &attr = iter->second;
std::string format;
if (attr.GetValue(format) != SUCCESS) {
IMAGE_LOGE("attr data get format failed.");
continue;
}
std::vector<std::string> splitedVector;
SplitStr(format, ",", splitedVector);
for (std::string item : splitedVector) {
formats.insert(item);
}
}
static bool isSupportHeif = IsSupportHeifEncode();
if (isSupportHeif) {
formats.insert(ImageUtils::GetEncodedHeifFormat());
}
return SUCCESS;
}
uint32_t ImagePacker::StartPackingImpl(const PackOption &option)
{
if (packerStream_ == nullptr || packerStream_.get() == nullptr) {
IMAGE_LOGE("make buffer packer stream failed.");
return ERR_IMAGE_DATA_ABNORMAL;
}
if (!GetEncoderPlugin(option)) {
IMAGE_LOGE("StartPackingImpl get encoder plugin failed.");
return ERR_IMAGE_MISMATCHED_FORMAT;
}
encodeToSdr_ = ((option.desiredDynamicRange == EncodeDynamicRange::SDR) ||
(option.format != IMAGE_JPEG_FORMAT && option.format != IMAGE_HEIF_FORMAT &&
option.format != IMAGE_HEIC_FORMAT));
format_ = option.format;
PlEncodeOptions plOpts;
CopyOptionsToPlugin(option, plOpts);
return DoEncodingFunc([this, &plOpts](ImagePlugin::AbsImageEncoder* encoder) {
return encoder->StartEncode(*packerStream_.get(), plOpts);
});
}
uint32_t ImagePacker::StartPacking(uint8_t *outputData, uint32_t maxSize, const PackOption &option)
{
ImageTrace imageTrace("ImagePacker::StartPacking by outputData");
if (!IsPackOptionValid(option)) {
IMAGE_LOGE("array startPacking option invalid %{public}s, %{public}u.", option.format.c_str(),
option.quality);
return ERR_IMAGE_INVALID_PARAMETER;
}
CHECK_ERROR_RETURN_RET_LOG(outputData == nullptr,
ERR_IMAGE_INVALID_PARAMETER, "output buffer is null.");
BufferPackerStream *stream = new (std::nothrow) BufferPackerStream(outputData, maxSize);
CHECK_ERROR_RETURN_RET_LOG(stream == nullptr,
ERR_IMAGE_DATA_ABNORMAL, "make buffer packer stream failed.");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<BufferPackerStream>(stream);
return StartPackingImpl(option);
}
uint32_t ImagePacker::StartPacking(const std::string &filePath, const PackOption &option)
{
ImageTrace imageTrace("ImagePacker::StartPacking by filePath");
if (!IsPackOptionValid(option)) {
IMAGE_LOGE("filepath startPacking option invalid %{public}s, %{public}u.", option.format.c_str(),
option.quality);
return ERR_IMAGE_INVALID_PARAMETER;
}
FilePackerStream *stream = new (std::nothrow) FilePackerStream(filePath);
CHECK_ERROR_RETURN_RET_LOG(stream == nullptr,
ERR_IMAGE_DATA_ABNORMAL, "make file packer stream failed.");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<FilePackerStream>(stream);
return StartPackingImpl(option);
}
uint32_t ImagePacker::StartPacking(const int &fd, const PackOption &option)
{
ImageTrace imageTrace("ImagePacker::StartPacking by fd");
if (!IsPackOptionValid(option)) {
IMAGE_LOGE("fd startPacking option invalid %{public}s, %{public}u.", option.format.c_str(), option.quality);
return ERR_IMAGE_INVALID_PARAMETER;
}
FilePackerStream *stream = new (std::nothrow) FilePackerStream(fd);
bool cond = (stream == nullptr);
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_DATA_ABNORMAL, "make file packer stream failed");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<FilePackerStream>(stream);
return StartPackingImpl(option);
}
uint32_t ImagePacker::StartPacking(std::ostream &outputStream, const PackOption &option)
{
ImageTrace imageTrace("ImagePacker::StartPacking by outputStream");
if (!IsPackOptionValid(option)) {
IMAGE_LOGE("outputStream startPacking option invalid %{public}s, %{public}u.", option.format.c_str(),
option.quality);
return ERR_IMAGE_INVALID_PARAMETER;
}
OstreamPackerStream *stream = new (std::nothrow) OstreamPackerStream(outputStream);
CHECK_ERROR_RETURN_RET_LOG(stream == nullptr,
ERR_IMAGE_DATA_ABNORMAL, "make ostream packer stream failed.");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<OstreamPackerStream>(stream);
return StartPackingImpl(option);
}
uint32_t ImagePacker::StartPackingAdapter(PackerStream &outputStream, const PackOption &option)
{
FreeOldPackerStream();
packerStream_ = std::unique_ptr<PackerStream>(&outputStream);
bool cond = !IsPackOptionValid(option);
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"packer stream option invalid %{public}s, %{public}u.",
option.format.c_str(), option.quality);
return StartPackingImpl(option);
}
uint32_t ImagePacker::AddImage(PixelMap &pixelMap)
{
ImageUtils::DumpPixelMapBeforeEncode(pixelMap);
ImageTrace imageTrace("ImagePacker::AddImage by pixelMap");
bool cond = pixelMap.GetPixelFormat() == PixelFormat::Y8 && format_ != IMAGE_TIFF_FORMAT;
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"Y8 format only supported via TIFF plugin.");
return DoEncodingFunc([this, &pixelMap](ImagePlugin::AbsImageEncoder* encoder) {
return encoder->AddImage(pixelMap);
});
}
uint32_t ImagePacker::AddImage(ImageSource &source)
{
ImageTrace imageTrace("ImagePacker::AddImage by imageSource");
return AddImage(source, 0);
}
uint32_t ImagePacker::AddImage(ImageSource &source, uint32_t index)
{
ImageTrace imageTrace("ImagePacker::AddImage by imageSource and index %u", index);
uint32_t ret = SUCCESS;
DecodeOptions decodeOpts;
decodeOpts.desiredDynamicRange = encodeToSdr_ ? DecodeDynamicRange::SDR : DecodeDynamicRange::AUTO;
bool isHdr = source.IsDecodeHdrImage(decodeOpts);
#if !defined(CROSS_PLATFORM)
if (isHdr && source.CheckHdrType() == ImageHdrType::HDR_VIVID_DUAL) {
if (picture_ != nullptr) {
picture_.reset();
}
DecodingOptionsForPicture decodeOptsForPicture;
decodeOptsForPicture.desiredPixelFormat = PixelFormat::NV12;
decodeOptsForPicture.desireAuxiliaryPictures = { AuxiliaryPictureType::GAINMAP };
picture_ = source.CreatePicture(decodeOptsForPicture, ret);
if (ret == SUCCESS && picture_ != nullptr && picture_.get() != nullptr) {
IMAGE_LOGD("image source create picture success.");
return AddPicture(*picture_.get());
}
}
#endif
if (pixelMap_ != nullptr) {
pixelMap_.reset();
}
pixelMap_ = source.CreatePixelMapEx(index, decodeOpts, ret);
CHECK_ERROR_RETURN_RET_LOG(ret != SUCCESS, ret,
"image source create pixel map failed.");
bool cond = pixelMap_ == nullptr || pixelMap_.get() == nullptr;
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_MALLOC_ABNORMAL, "create the pixel map unique_ptr fail.");
return AddImage(*pixelMap_.get());
}
#if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM)
uint32_t ImagePacker::AddPicture(Picture &picture)
{
Picture::DumpPictureIfDumpEnabled(picture, "picture_encode_before");
return DoEncodingFunc([this, &picture](ImagePlugin::AbsImageEncoder* encoder) {
return encoder->AddPicture(picture);
});
}
#endif
uint32_t ImagePacker::FinalizePacking()
{
return DoEncodingFunc([](ImagePlugin::AbsImageEncoder* encoder) {
auto res = encoder->FinalizeEncode();
if (res != SUCCESS) {
IMAGE_LOGE("FinalizePacking failed %{public}d.", res);
}
return res;
}, false);
}
uint32_t ImagePacker::FinalizePacking(int64_t &packedSize)
{
uint32_t ret = FinalizePacking();
if (packerStream_ != nullptr) {
packerStream_->Flush();
}
packedSize = (packerStream_ != nullptr) ? packerStream_->BytesWritten() : 0;
return ret;
}
static ImagePlugin::AbsImageEncoder* GetEncoder(PluginServer &pluginServer, std::string format)
{
std::map<std::string, AttrData> capabilities;
capabilities.insert(std::map<std::string, AttrData>::value_type(IMAGE_ENCODE_FORMAT, AttrData(format)));
return pluginServer.CreateObject<AbsImageEncoder>(AbsImageEncoder::SERVICE_DEFAULT, capabilities);
}
bool ImagePacker::GetEncoderPlugin(const PackOption &option)
{
encoders_.clear();
IMAGE_LOGD("GetEncoderPlugin current encoder plugin size %{public}zu.", encoders_.size());
auto encoder = GetEncoder(pluginServer_, EXTENDED_ENCODER);
if (encoder != nullptr) {
encoders_.emplace_back(std::unique_ptr<ImagePlugin::AbsImageEncoder>(encoder));
} else {
IMAGE_LOGE("GetEncoderPlugin get ext_encoder plugin failed.");
}
encoder = GetEncoder(pluginServer_, option.format);
if (encoder != nullptr) {
encoders_.emplace_back(std::unique_ptr<ImagePlugin::AbsImageEncoder>(encoder));
} else {
IMAGE_LOGD("GetEncoderPlugin get %{public}s plugin failed, use ext_encoder plugin",
option.format.c_str());
}
return encoders_.size() != SIZE_ZERO;
}
void ImagePacker::CopyOptionsToPlugin(const PackOption &opts, PlEncodeOptions &plOpts)
{
plOpts.delayTimes = opts.delayTimes;
plOpts.loop = opts.loop;
plOpts.numberHint = opts.numberHint;
plOpts.quality = opts.quality;
plOpts.format = opts.format;
plOpts.disposalTypes = opts.disposalTypes;
plOpts.needsPackProperties = opts.needsPackProperties;
plOpts.needsPackDfxData = opts.needsPackDfxData;
plOpts.desiredDynamicRange = opts.desiredDynamicRange;
plOpts.isEditScene = opts.isEditScene;
plOpts.maxEmbedThumbnailDimension = opts.maxEmbedThumbnailDimension;
plOpts.backgroundColor = opts.backgroundColor;
plOpts.sizeLimit.maxSize = opts.sizeLimit.maxSize;
plOpts.sizeLimit.antiAliasingLevel = opts.sizeLimit.antiAliasingLevel;
plOpts.needsPackGPS = opts.needsPackGPS;
plOpts.astcPackingOption.enableGPUEncode = opts.astcPackingOption.enableGPUEncode;
if (opts.format == IMAGE_TIFF_FORMAT) {
CopyTiffPackingOptions(opts.tiffPackingOption, plOpts.tiffPackingOption);
}
}
void ImagePacker::CopyTiffPackingOptions(const PackingOptionsForTiff &src,
ImagePlugin::PlPackingOptionsForTiff &dst)
{
dst.compression = src.compression;
dst.orientation = src.orientation;
dst.xResolution = src.xResolution;
dst.yResolution = src.yResolution;
dst.resolutionUnit = src.resolutionUnit;
}
void ImagePacker::FreeOldPackerStream()
{
if (packerStream_ != nullptr) {
packerStream_.reset();
}
}
bool ImagePacker::IsPackOptionValid(const PackOption &option)
{
return !(option.quality > QUALITY_MAX || option.format.empty());
}
uint32_t ImagePacker::DoEncodingFunc(std::function<uint32_t(ImagePlugin::AbsImageEncoder*)> func, bool forAll)
{
if (encoders_.size() == SIZE_ZERO) {
IMAGE_LOGE("DoEncodingFunc encoders is empty.");
return ERR_IMAGE_DECODE_ABNORMAL;
}
std::vector<uint32_t> rets;
rets.resize(SIZE_ZERO);
bool isSuccessOnce = false;
for (size_t i = SIZE_ZERO; i < encoders_.size(); i++) {
if (!forAll && isSuccessOnce) {
IMAGE_LOGD("DoEncodingFunc encoding successed, reset other encoder.");
encoders_.at(i).reset();
continue;
}
auto iterRes = func(encoders_.at(i).get());
rets.emplace_back(iterRes);
if (iterRes == SUCCESS) {
isSuccessOnce = true;
}
if (!forAll && !isSuccessOnce) {
IMAGE_LOGD("DoEncodingFunc failed.");
}
}
if (isSuccessOnce) {
return SUCCESS;
}
return (rets.size() == SIZE_ZERO)?ERR_IMAGE_DECODE_ABNORMAL:rets.front();
}
#if defined(SUPPORT_LIBTIFF)
uint32_t ImagePacker::ValidateBinaryImageBufferInfo(const PixelBufferInfo &bufferInfo, const char *funcName)
{
bool cond = (bufferInfo.data == nullptr) || (bufferInfo.dataSize == 0);
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, invalid buffer data", funcName);
cond = (bufferInfo.width == 0) || (bufferInfo.height == 0);
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, invalid dimensions: width or height cannot be zero", funcName);
uint64_t minRowBytes = (static_cast<uint64_t>(bufferInfo.width) + BITS_PER_BYTE - 1) / BITS_PER_BYTE;
uint64_t rowBytes = minRowBytes;
if (bufferInfo.bytesPerRow > 0) {
cond = bufferInfo.bytesPerRow < minRowBytes;
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, bytesPerRow too small: %{public}u < required %{public}llu",
funcName, bufferInfo.bytesPerRow, static_cast<unsigned long long>(minRowBytes));
rowBytes = bufferInfo.bytesPerRow;
}
CHECK_ERROR_RETURN_RET_LOG(rowBytes > std::numeric_limits<uint64_t>::max() / bufferInfo.height,
ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, dimensions cause overflow: %{public}ux%{public}u",
funcName, bufferInfo.width, bufferInfo.height);
uint64_t requiredSize = rowBytes * bufferInfo.height;
static constexpr uint64_t maxDataSize = std::numeric_limits<uint32_t>::max();
cond = bufferInfo.dataSize < requiredSize;
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, dataSize too small: %{public}llu < required %{public}llu",
funcName, static_cast<unsigned long long>(bufferInfo.dataSize),
static_cast<unsigned long long>(requiredSize));
cond = bufferInfo.dataSize > maxDataSize;
CHECK_ERROR_RETURN_RET_LOG(cond, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] %{public}s failed, dataSize too large: %{public}llu > max %{public}llu",
funcName, static_cast<unsigned long long>(bufferInfo.dataSize),
static_cast<unsigned long long>(maxDataSize));
return SUCCESS;
}
uint32_t ImagePacker::EncodeBinaryImageToTiffStream(const PixelBufferInfo &bufferInfo,
PackerStream &packerStream,
const PackingOptionsForTiff &option,
const char *funcName)
{
auto tiffEncoder = std::make_unique<TiffEncoder>();
CHECK_ERROR_RETURN_RET_LOG((tiffEncoder == nullptr), ERR_IMAGE_ENCODE_FAILED, "make TiffEncoder failed!");
ImagePlugin::PlPackingOptionsForTiff tiffOption;
CopyTiffPackingOptions(option, tiffOption);
uint32_t ret = tiffEncoder->EncodeBinaryImageToTiff(&bufferInfo, packerStream, tiffOption);
if (ret != SUCCESS) {
IMAGE_LOGE("[ImagePacker] %{public}s failed, EncodeBinaryImageToTiff error: %{public}u", funcName, ret);
return ret;
}
return SUCCESS;
}
#endif
uint32_t ImagePacker::PackBinaryImageToTiffFile(const PixelBufferInfo &bufferInfo, const int &fd,
const PackingOptionsForTiff &option)
{
#if defined(SUPPORT_LIBTIFF)
uint32_t ret = ValidateBinaryImageBufferInfo(bufferInfo, "PackBinaryImageToTiffFile");
CHECK_ERROR_RETURN_RET(ret != SUCCESS, ret);
CHECK_ERROR_RETURN_RET_LOG(fd < 0, ERR_IMAGE_INVALID_PARAMETER,
"[ImagePacker] PackBinaryImageToTiffFile failed, invalid fd: %{public}d", fd);
FilePackerStream *packerStream = new (std::nothrow) FilePackerStream(fd);
CHECK_ERROR_RETURN_RET_LOG(!packerStream, ERR_IMAGE_INVALID_PARAMETER, "make file packer stream failed");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<FilePackerStream>(packerStream);
ret = EncodeBinaryImageToTiffStream(bufferInfo, *packerStream_, option, "PackBinaryImageToTiffFile");
CHECK_ERROR_RETURN_RET(ret != SUCCESS, ret);
return SUCCESS;
#else
IMAGE_LOGE("[ImagePacker] PackBinaryImageToTiffFile failed, TIFF encoding is not supported");
return ERR_IMAGE_ENCODE_FAILED;
#endif
}
uint32_t ImagePacker::PackBinaryImageToTiffData(const PixelBufferInfo &bufferInfo,
const PackingOptionsForTiff &option,
uint8_t *outputData, uint32_t &outputSize)
{
#if defined(SUPPORT_LIBTIFF)
uint32_t ret = ValidateBinaryImageBufferInfo(bufferInfo, "PackBinaryImageToTiffData");
if (ret != SUCCESS) {
return ret;
}
if (outputData == nullptr || outputSize == 0) {
IMAGE_LOGE("[ImagePacker] PackBinaryImageToTiffData failed, invalid output buffer");
return ERR_IMAGE_INVALID_PARAMETER;
}
BufferPackerStream *packerStream = new (std::nothrow) BufferPackerStream(outputData, outputSize);
CHECK_ERROR_RETURN_RET_LOG(!packerStream, ERR_IMAGE_INVALID_PARAMETER, "make buffer packer stream failed");
FreeOldPackerStream();
packerStream_ = std::unique_ptr<BufferPackerStream>(packerStream);
ret = EncodeBinaryImageToTiffStream(bufferInfo, *packerStream_, option, "PackBinaryImageToTiffData");
if (ret != SUCCESS) {
return ret;
}
size_t actualSize = 0;
packerStream_->GetRealWrittenSize(actualSize);
outputSize = static_cast<uint32_t>(actualSize);
IMAGE_LOGD("[ImagePacker] PackBinaryImageToTiffData success, outputSize: %{public}u", outputSize);
return SUCCESS;
#else
IMAGE_LOGE("[ImagePacker] PackBinaryImageToTiffData failed, TIFF encoding is not supported");
return ERR_IMAGE_ENCODE_FAILED;
#endif
}
ImagePacker::ImagePacker()
{}
ImagePacker::~ImagePacker()
{}
}
}