diff --git a/frameworks/innerkitsimpl/codec/src/image_source.cpp b/frameworks/innerkitsimpl/codec/src/image_source.cpp index 00d6ecdac7ca00423211b5fdbbc90ae86debac12..abd43962d5f8aadabe7577c6c9c96037e4ef12fd 100644 --- a/frameworks/innerkitsimpl/codec/src/image_source.cpp +++ b/frameworks/innerkitsimpl/codec/src/image_source.cpp @@ -920,6 +920,56 @@ static bool IsSupportConvertToArgb(PixelMap *pixelMap) return pixelMap != nullptr && !pixelMap->IsHdr() && pixelMap->GetAllocatorType() != AllocatorType::DMA_ALLOC; } +bool ImageSource::CheckAllocatorTypeValid(const DecodeOptions &opts) +{ + if (opts.isAppUseAllocator && opts.allocatorType == AllocatorType::SHARE_MEM_ALLOC && IsDecodeHdrImage(opts)) { + IMAGE_LOGE("HDR image can't use SHARE_MEM_ALLOC"); + return false; + } else if (!IsDecodeHdrImage(opts) && opts.allocatorType == AllocatorType::DMA_ALLOC && opts.desiredPixelFormat == PixelFormat::ARGB_8888) { + IMAGE_LOGE("%{public}s SDR image can't set ARGB_8888 and DMA_ALLOC at the same time!", __func__); + return false; + } + return true; +} + +bool IsSrcRectContainsDistRect(const Rect &srcRect, const Rect &distRect) +{ + if (srcRect.left < 0 || srcRect.top < 0 || srcRect.width <= 0 || srcRect.height <= 0) { + return false; + } + if (distRect.left < 0 || distRect.top < 0 || distRect.width <= 0 || distRect.height <= 0) { + return false; + } + return srcRect.left <= distRect.left && srcRect.top <= distRect.top && + (srcRect.left + srcRect.width) >= (distRect.left + distRect.width) && + (srcRect.top + srcRect.height) >= (distRect.top + distRect.height); +} + +bool ImageSource::CheckCropRectValid(const DecodeOptions &opts) +{ + Rect srcRect = {0, 0, 0, 0}; + if (opts.cropAndScaleStrategy == CropAndScaleStrategy::DEFAULT) { + return true; + } + ImageInfo info; + if (GetImageInfo(FIRST_FRAME, info) != SUCCESS) { + return false; + } + srcRect.width = info.size.width; + srcRect.height = info.size.height; + if (opts.cropAndScaleStrategy == CropAndScaleStrategy::SCALE_FIRST && + (opts.desiredSize.width != 0 || opts.desiredSize.height != 0)) { + srcRect.width = opts.desiredSize.width; + srcRect.height = opts.desiredSize.height; + } + return IsSrcRectContainsDistRect(srcRect, opts.CropRect); +} + +bool ImageSource::CheckDecodeOptions(const DecodeOptions &opts) +{ + return CheckAllocatorTypeValid(opts) && CheckCropRectValid(opts); +} + unique_ptr ImageSource::CreatePixelMapExtended(uint32_t index, const DecodeOptions &opts, uint32_t &errorCode) { ImageEvent imageEvent; @@ -929,14 +979,10 @@ unique_ptr ImageSource::CreatePixelMapExtended(uint32_t index, const D ImageInfo info; errorCode = GetImageInfo(FIRST_FRAME, info); ParseHdrType(); - if (opts_.isAppUseAllocator && opts_.allocatorType == AllocatorType::SHARE_MEM_ALLOC && IsDecodeHdrImage(opts)) { - IMAGE_LOGE("HDR image can't use SHARE_MEM_ALLOC"); + if (!CheckDecodeOptions(opts)) { + IMAGE_LOGI("CheckDecodeOptions failed."); errorCode = ERR_MEDIA_INVALID_OPERATION; return nullptr; - } else if (!IsDecodeHdrImage(opts) && opts_.allocatorType == AllocatorType::DMA_ALLOC && - opts_.desiredPixelFormat == PixelFormat::ARGB_8888) { - IMAGE_LOGE("%{public}s SDR image can't set ARGB_8888 and DMA_ALLOC at the same time!", __func__); - return nullptr; } #ifdef IMAGE_QOS_ENABLE if (ImageUtils::IsSizeSupportDma(info.size) && getpid() != gettid()) { @@ -1010,7 +1056,7 @@ unique_ptr ImageSource::CreatePixelMapExtended(uint32_t index, const D return pixelMap; } -static void GetValidCropRect(const Rect &src, ImagePlugin::PlImageInfo &plInfo, Rect &dst) +static void GetValidCropRect(const Rect &src, const Size& size, Rect &dst) { dst.top = src.top; dst.left = src.left; @@ -1018,11 +1064,11 @@ static void GetValidCropRect(const Rect &src, ImagePlugin::PlImageInfo &plInfo, dst.height = src.height; int32_t dstBottom = dst.top + dst.height; int32_t dstRight = dst.left + dst.width; - if (dst.top >= 0 && dstBottom > 0 && dstBottom > plInfo.size.height) { - dst.height = plInfo.size.height - dst.top; + if (dst.top >= 0 && dstBottom > 0 && dstBottom > size.height) { + dst.height = size.height - dst.top; } - if (dst.left >= 0 && dstRight > 0 && dstRight > plInfo.size.width) { - dst.width = plInfo.size.width - dst.left; + if (dst.left >= 0 && dstRight > 0 && dstRight > size.width) { + dst.width = size.width - dst.left; } } @@ -1145,11 +1191,17 @@ unique_ptr ImageSource::CreatePixelMapByInfos(ImagePlugin::PlImageInfo // 3. density // 4. rotate // 5. format + if (opts_.cropAndScaleStrategy == CropAndScaleStrategy::SCALE_FIRST) { + if(!(ResizePixelMap(pixelMap, imageId_, opts_))) { + IMAGE_LOGE("[ImageSource]Resize pixelmap fail."); + return nullptr; + } + } const static string SUPPORT_CROP_KEY = "SupportCrop"; if (!mainDecoder_->HasProperty(SUPPORT_CROP_KEY) && opts_.CropRect.width > INT_ZERO && opts_.CropRect.height > INT_ZERO) { Rect crop; - GetValidCropRect(opts_.CropRect, plInfo, crop); + GetValidCropRect(opts_.CropRect, {pixelMap->GetWidth(), pixelMap->GetHeight()}, crop); errorCode = pixelMap->crop(crop); if (errorCode != SUCCESS) { IMAGE_LOGE("[ImageSource]CropRect pixelmap fail, ret:%{public}u.", errorCode); @@ -1165,7 +1217,8 @@ unique_ptr ImageSource::CreatePixelMapByInfos(ImagePlugin::PlImageInfo } else if (opts_.rotateNewDegrees != INT_ZERO) { pixelMap->rotate(opts_.rotateNewDegrees); } - if (!(ResizePixelMap(pixelMap, imageId_, opts_))) { + if (opts_.cropAndScaleStrategy != CropAndScaleStrategy::SCALE_FIRST && + !(ResizePixelMap(pixelMap, imageId_, opts_))) { IMAGE_LOGE("[ImageSource]Resize pixelmap fail."); return nullptr; } @@ -2570,6 +2623,7 @@ void ImageSource::CopyOptionsToPlugin(const DecodeOptions &opts, PixelDecodeOpti } plOpts.plDesiredColorSpace = opts.desiredColorSpaceInfo; plOpts.plReusePixelmap = opts.reusePixelmap; + plOpts.cropAndScaleStrategy = opts.cropAndScaleStrategy; } void ImageSource::CopyOptionsToProcOpts(const DecodeOptions &opts, DecodeOptions &procOpts, PixelMap &pixelMap) diff --git a/frameworks/innerkitsimpl/test/fuzztest/imageplugin_fuzzer/src/image_plugin_fuzz.cpp b/frameworks/innerkitsimpl/test/fuzztest/imageplugin_fuzzer/src/image_plugin_fuzz.cpp index a84c33d237eb656ee9690bcc030ee9dde749637f..48d2abc5214c55b3ad6c1b489ce851386b4ab968 100644 --- a/frameworks/innerkitsimpl/test/fuzztest/imageplugin_fuzzer/src/image_plugin_fuzz.cpp +++ b/frameworks/innerkitsimpl/test/fuzztest/imageplugin_fuzzer/src/image_plugin_fuzz.cpp @@ -37,6 +37,11 @@ namespace OHOS { namespace Media { using namespace OHOS::ImagePlugin; +namespace { + const uint8_t* g_data = nullptr; + size_t g_size = 0; + size_t g_pos = 0; +} // namespace static const std::string JPEG_HW_PATH = "/data/local/tmp/test_hw.jpg"; static const std::string JPEG_SW_PATH = "/data/local/tmp/test-tree-420.jpg"; @@ -45,6 +50,26 @@ static const std::string HDR_PATH = "/data/local/tmp/HEIFISOMultiChannelBaseColo static const std::string WEBP_PATH = "/data/local/tmp/test.webp"; static const std::string GIF_PATH = "/data/local/tmp/test.gif"; +/* + * describe: get data from outside untrusted data(g_data) which size is according to sizeof(T) + * tips: only support basic type + */ +template +T GetData() +{ + T object {}; + size_t objectSize = sizeof(object); + if (g_data == nullptr || objectSize > g_size - g_pos) { + return object; + } + errno_t ret = memcpy_s(&object, objectSize, g_data + g_pos, objectSize); + if (ret != EOK) { + return {}; + } + g_pos += objectSize; + return object; +} + void ExtDecoderFuncTest001(const std::string& filename) { IMAGE_LOGI("%{public}s IN path: %{public}s", __func__, filename.c_str()); @@ -131,6 +156,65 @@ void SvgDecoderFuncTest001(const std::string& filename) IMAGE_LOGI("%{public}s SUCCESS", __func__); } +void ExtDecoderRegionFuncTest001(const std::string& filename) +{ + IMAGE_LOGI("%{public}s IN path: %{public}s", __func__, filename.c_str()); + SourceOptions srcOpts; + uint32_t errorCode; + auto imageSource = ImageSource::CreateImageSource(filename, srcOpts, errorCode); + if (imageSource == nullptr) { + return; + } + int32_t x = GetData(); + int32_t y = GetData(); + int32_t width = GetData(); + int32_t height = GetData(); + Media::DecodeOptions dopts; + dopts.desiredRegion.left = x; + dopts.desiredRegion.top = y; + dopts.desiredRegion.width = width; + dopts.desiredRegion.height = height; + dopts.cropAndScaleStrategy = CropAndScaleStrategy::CROP_FIRST; + imageSource->CreatePixelMap(dopts, errorCode); + auto extDecoder = static_cast((imageSource->mainDecoder_).get()); + if (extDecoder == nullptr || !extDecoder->DecodeHeader()) { + return; + } + DecodeContext context; + extDecoder->HeifYUVMemAlloc(context); + int dWidth; + int dHeight; + float scale; + extDecoder->GetScaledSize(dWidth, dHeight, scale); + extDecoder->GetHardwareScaledSize(dWidth, dHeight, scale); + extDecoder->IsSupportScaleOnDecode(); + PixelDecodeOptions plOpts; + PlImageInfo plInfo; + extDecoder->SetDecodeOptions(0, plOpts, plInfo); + PixelFormat dstFormat = PixelFormat::UNKNOWN; + extDecoder->PreDecodeCheckYuv(0, dstFormat); + extDecoder->DoHardWareDecode(context); + extDecoder->GetJpegYuvOutFmt(dstFormat); + extDecoder->DecodeToYuv420(0, context); + extDecoder->CheckContext(context); + extDecoder->HardWareDecode(context); + SkAlphaType alphaType; + AlphaType outputType; + extDecoder->ConvertInfoToAlphaType(alphaType, outputType); + SkColorType format; + PixelFormat outputFormat; + extDecoder->ConvertInfoToColorType(format, outputFormat); + std::string key = "ImageWidth"; + std::string value = "500"; + int32_t valInt = 0; + extDecoder->GetImagePropertyInt(0, key, valInt); + extDecoder->GetImagePropertyString(0, key, value); + extDecoder->GetMakerImagePropertyString(key, value); + extDecoder->DoHeifToYuvDecode(context); + IMAGE_LOGI("%{public}s SUCCESS", __func__); +} + + void PixelMapTest001(PixelMap* pixelMap) { IMAGE_LOGI("%{public}s IN", __func__); @@ -330,6 +414,7 @@ void ImagePluginFuzzTest001(const uint8_t* data, size_t size) GifTest001(GIF_PATH); GifTest001(WEBP_PATH); HdrTest001(); + ExtDecoderRegionFuncTest001(filename); } } // namespace Media } // namespace OHOS diff --git a/frameworks/innerkitsimpl/test/unittest/image_source_native_ndk_test.cpp b/frameworks/innerkitsimpl/test/unittest/image_source_native_ndk_test.cpp index a4619dbd768e428fd335d3855fae5269ca58c398..f183d7a1cbc1c7a22f6bc47cd94750372558e555 100644 --- a/frameworks/innerkitsimpl/test/unittest/image_source_native_ndk_test.cpp +++ b/frameworks/innerkitsimpl/test/unittest/image_source_native_ndk_test.cpp @@ -48,6 +48,7 @@ static const std::string IMAGE_JPEG_PATH = "/data/local/tmp/image/test_picture.j static const std::string IMAGE_JPEG_HDR_PATH = "/data/local/tmp/image/test_jpeg_hdr.jpg"; static const std::string IMAGE_HEIF_PATH = "/data/local/tmp/image/test_allocator_heif.heic"; static const std::string IMAGE_HEIF_HDR_PATH = "/data/local/tmp/image/test_heif_hdr.heic"; +static const std::string IMAGE_PNG_PATH = "/data/local/tmp/image/test_picture_png.png"; static const int32_t MAX_BUFFER_SIZE = 256; static const int32_t INVALID_INDEX = 1000; @@ -323,6 +324,426 @@ HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmap, TestSize.Level GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmap end"; } +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest001 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest001 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest001, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest001 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {1920, 1080, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 1920); + ASSERT_EQ(desiredRegion.y, 1080); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest001 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest002 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest002 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest002, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest002 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 1920, 2160}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 2160); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest002 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest003 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest003 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest003, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest003 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 3840, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest003 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest004 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest004 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest004, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest004 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {1920, 1080, 3840, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 1920); + ASSERT_EQ(desiredRegion.y, 1080); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest004 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest005 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest005 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest005, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest005 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {3840, 2160, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 3840); + ASSERT_EQ(desiredRegion.y, 2160); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest005 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest006 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest006 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest006, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest006 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {1920, 1080, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 1920); + ASSERT_EQ(desiredRegion.y, 1080); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest006 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest007 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest007 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest007, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest007 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 1920, 2160}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 2160); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest007 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest008 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest008 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest008, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest008 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 3840, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest008 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest009 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest009 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest009, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest009 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {1920, 1080, 3840, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 1920); + ASSERT_EQ(desiredRegion.y, 1080); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest009 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest010 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest010 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest010, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest010 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {3840, 2160, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 3840); + ASSERT_EQ(desiredRegion.y, 2160); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest010 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest011 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest011 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest011, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest011 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {960, 540, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 960); + ASSERT_EQ(desiredRegion.y, 540); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest011 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest012 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest012 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest012, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest012 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {960, 540, 1920, 1080}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 960); + ASSERT_EQ(desiredRegion.y, 540); + ASSERT_EQ(desiredRegion.width, 1920); + ASSERT_EQ(desiredRegion.height, 1080); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest012 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest013 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest013 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest013, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest013 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_JPEG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 3840, 2160}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 2160); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest013 end"; +} + +/** + * @tc.name: OH_ImageSourceNative_CreatePixelmapTest014 + * @tc.desc: test OH_ImageSourceNative_CreatePixelmapTest014 + * @tc.type: FUNC + */ +HWTEST_F(ImagSourceNdk2Test, OH_ImageSourceNative_CreatePixelmapTest014, TestSize.Level3) +{ + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest014 start"; + OH_ImageSourceNative *imageSource = CreateImageSourceNative(IMAGE_PNG_PATH); + ASSERT_NE(imageSource, nullptr); + OH_DecodingOptions* ops = nullptr; + Image_Region desiredRegion = {0, 0, 0, 0}; + Image_Region desiredRegion2 = {0, 0, 3840, 2160}; + OH_DecodingOptions_Create(&ops); + OH_DecodingOptions_SetDesiredRegion(ops, &desiredRegion2); + OH_DecodingOptions_GetDesiredRegion(ops, &desiredRegion); + ASSERT_EQ(desiredRegion.x, 0); + ASSERT_EQ(desiredRegion.y, 0); + ASSERT_EQ(desiredRegion.width, 3840); + ASSERT_EQ(desiredRegion.height, 2160); + OH_DecodingOptions_SetCropAndScaleStrategy(ops, 2); + OH_PixelmapNative* resPixMap = nullptr; + Image_ErrorCode ret = OH_ImageSourceNative_CreatePixelmap(imageSource, ops, &resPixMap); + EXPECT_EQ(ret, IMAGE_SUCCESS); + OH_ImageSourceNative_Release(imageSource); + OH_DecodingOptions_Release(ops); + OH_PixelmapNative_Release(resPixMap); + GTEST_LOG_(INFO) << "ImagSourceNdk2Test: OH_ImageSourceNative_CreatePixelmapTest014 end"; +} + /** * @tc.name: OH_ImageSourceNative_CreatePixelmapUsingAllocatorTest001 * @tc.desc: Test create Pixelmap use DMA. diff --git a/frameworks/kits/js/common/image_source_napi.cpp b/frameworks/kits/js/common/image_source_napi.cpp index fb986f5a907a10c22df13b9f705454507c680326..cbe75eb5727d40dfed6ad78a03b16172f7db0fb7 100644 --- a/frameworks/kits/js/common/image_source_napi.cpp +++ b/frameworks/kits/js/common/image_source_napi.cpp @@ -71,6 +71,7 @@ napi_ref ImageSourceNapi::componentTypeRef_ = nullptr; napi_ref ImageSourceNapi::decodingDynamicRangeRef_ = nullptr; napi_ref ImageSourceNapi::decodingResolutionQualityRef_ = nullptr; napi_ref ImageSourceNapi::decodingAllocatorTypeRef_ = nullptr; +napi_ref ImageSourceNapi::cropAndScaleStrategyRef_ = nullptr; static std::mutex imageSourceCrossThreadMutex_; @@ -384,6 +385,11 @@ enum class DecodeAllocatorType : int32_t { SHARE_MEMORY = 2 }; +static std::vector sCropAndScaleStrategyMap = { + {"SCALE_FIRST", 1, ""}, + {"CROP_FIRST", 2, ""}, +}; + static const std::map ERROR_CODE_MAP = { {ERR_IMAGE_INVALID_PARAMETER, Image_ErrorCode::IMAGE_BAD_PARAMETER}, {COMMON_ERR_INVALID_PARAMETER, Image_ErrorCode::IMAGE_BAD_PARAMETER}, @@ -927,6 +933,8 @@ napi_value ImageSourceNapi::Init(napi_env env, napi_value exports) CreateEnumTypeObject(env, napi_number, &decodingResolutionQualityRef_, sDecodingResolutionQualityMap)), DECLARE_NAPI_PROPERTY("AllocatorType", CreateEnumTypeObject(env, napi_number, &decodingAllocatorTypeRef_, sAllocatorType)), + DECLARE_NAPI_PROPERTY("CropAndScaleStrategy", + CreateEnumTypeObject(env, napi_number, &cropAndScaleStrategyRef_, sCropAndScaleStrategyMap)), }; struct ImageConstructorInfo info = { @@ -1166,6 +1174,12 @@ static PixelFormat ParsePixlForamt(int32_t val) return PixelFormat::UNKNOWN; } +static inline bool IsCropStrategyVaild(int32_t strategy) +{ + return strategy >= static_cast(CropAndScaleStrategy::SCALE_FIRST) && + strategy <= static_cast(CropAndScaleStrategy::CROP_FIRST); +} + static AllocatorType ConvertAllocatorType(std::shared_ptr imageSource, DecodeAllocatorType allocatorType, DecodeOptions decodeOpts) { @@ -1236,6 +1250,14 @@ static bool ParseDecodeOptions2(napi_env env, napi_value root, DecodeOptions* op return false; } + int32_t cropAndScaleNum = 0; + if (GET_INT32_BY_NAME(root, "cropAndScaleStrategy", cropAndScaleNum) && IsCropStrategyVaild(cropAndScaleNum)) { + IMAGE_LOGI("The strategy has taken effect"); + opts->cropAndScaleStrategy = CropAndScaleStrategy(cropAndScaleNum); + } else { + IMAGE_LOGI("default cropAndScaleStrategy"); + } + if (!GET_INT32_BY_NAME(root, "fitDensity", opts->fitDensity)) { IMAGE_LOGD("no fitDensity"); } diff --git a/frameworks/kits/js/common/ndk/image_source_native.cpp b/frameworks/kits/js/common/ndk/image_source_native.cpp index ab14b2fe40d12f2903d95268e8809e1565deb2db..8c7ade0875d441aa747d14bbc3db09b17bf0bc94 100644 --- a/frameworks/kits/js/common/ndk/image_source_native.cpp +++ b/frameworks/kits/js/common/ndk/image_source_native.cpp @@ -61,6 +61,7 @@ struct OH_DecodingOptions { struct Image_Size desiredSize; struct Image_Region desiredRegion; int32_t desiredDynamicRange = IMAGE_DYNAMIC_RANGE_SDR; + int32_t cropAndScaleStrategy; }; struct OH_ImageSource_Info { @@ -175,6 +176,27 @@ Image_ErrorCode OH_DecodingOptions_SetPixelFormat(OH_DecodingOptions *options, return IMAGE_SUCCESS; } +Image_ErrorCode OH_DecodingOptions_GetCropAndScaleStrategy(OH_DecodingOptions *options, + int32_t *cropAndScaleStrategy) +{ + if (options == nullptr || cropAndScaleStrategy == nullptr) { + return IMAGE_BAD_PARAMETER; + } + *cropAndScaleStrategy = options->cropAndScaleStrategy; + return IMAGE_SUCCESS; +} + +Image_ErrorCode OH_DecodingOptions_SetCropAndScaleStrategy(OH_DecodingOptions *options, + int32_t cropAndScaleStrategy) +{ + if (options == nullptr) + { + return IMAGE_BAD_PARAMETER; + } + options->cropAndScaleStrategy = cropAndScaleStrategy; + return IMAGE_SUCCESS; +} + MIDK_EXPORT Image_ErrorCode OH_DecodingOptions_GetIndex(OH_DecodingOptions *options, uint32_t *index) { @@ -383,10 +405,10 @@ static void ParseDecodingOps(DecodeOptions &decOps, struct OH_DecodingOptions *o decOps.rotateNewDegrees = ops->rotate; decOps.desiredSize.width = static_cast(ops->desiredSize.width); decOps.desiredSize.height = static_cast(ops->desiredSize.height); - decOps.desiredRegion.left = static_cast(ops->desiredRegion.x); - decOps.desiredRegion.top = static_cast(ops->desiredRegion.y); - decOps.desiredRegion.width = static_cast(ops->desiredRegion.width); - decOps.desiredRegion.height = static_cast(ops->desiredRegion.height); + decOps.CropRect.left = static_cast(ops->desiredRegion.x); + decOps.CropRect.top = static_cast(ops->desiredRegion.y); + decOps.CropRect.width = static_cast(ops->desiredRegion.width); + decOps.CropRect.height = static_cast(ops->desiredRegion.height); decOps.desiredDynamicRange = ParseImageDynamicRange(ops->desiredDynamicRange); switch (static_cast(ops->pixelFormat)) { case FORMAT_0: @@ -404,6 +426,13 @@ static void ParseDecodingOps(DecodeOptions &decOps, struct OH_DecodingOptions *o default: decOps.desiredPixelFormat = PixelFormat::UNKNOWN; } + OHOS::Media::CropAndScaleStrategy cropAndScaleStrategy = static_cast(ops->cropAndScaleStrategy); + if (cropAndScaleStrategy == OHOS::Media::CropAndScaleStrategy::SCALE_FIRST || + cropAndScaleStrategy == OHOS::Media::CropAndScaleStrategy::CROP_FIRST) { + decOps.cropAndScaleStrategy = cropAndScaleStrategy; + } else { + decOps.cropAndScaleStrategy = OHOS::Media::CropAndScaleStrategy::DEFAULT; + } } static void ParseImageSourceInfo(struct OH_ImageSource_Info *source, const ImageInfo &info) diff --git a/interfaces/innerkits/include/image_source.h b/interfaces/innerkits/include/image_source.h index 85778a6aa3658ea27b69b7b0e9b65152c21f6a24..9338761622cf05010b9cb53c5a5ee937af926bb3 100644 --- a/interfaces/innerkits/include/image_source.h +++ b/interfaces/innerkits/include/image_source.h @@ -375,6 +375,10 @@ private: void SetSrcFd(const int& fd); void SetSrcFilePath(const std::string& pathName); void SetSrcBuffer(const uint8_t* buffer, uint32_t size); + bool CheckDecodeOptions(const DecodeOptions &opts); + bool CheckAllocatorTypeValid(const DecodeOptions &opts); + bool CheckCropRectValid(const DecodeOptions &opts); + #if !defined(IOS_PLATFORM) && !defined(ANDROID_PLATFORM) void SetHdrMetadataForPicture(std::unique_ptr &picture); void DecodeHeifAuxiliaryPictures(const std::set &auxTypes, std::unique_ptr &picture, diff --git a/interfaces/innerkits/include/image_type.h b/interfaces/innerkits/include/image_type.h index 6dd524152feb33bc3a37eafcf7bff47cce99a32f..c49517607773be626218ef61bf434822c8aa841e 100644 --- a/interfaces/innerkits/include/image_type.h +++ b/interfaces/innerkits/include/image_type.h @@ -304,6 +304,18 @@ struct SVGDecodeOptions { SVGResize SVGResize; }; +enum class CropAndScaleStrategy : int32_t { + DEFAULT = 0, + /** + * First scale, then crop; this is the default value to maintain interface compatiblity. + */ + SCALE_FIRST = 1, + /** + * Perform region decoding first, then scaling. + */ + CROP_FIRST = 2 +}; + class PixelMap; struct DecodeOptions { int32_t fitDensity = 0; @@ -336,6 +348,7 @@ struct DecodeOptions { // CreatePixelMapUsingAllocatorType is true, CreatePixelMap is false. bool isAppUseAllocator = false; std::shared_ptr reusePixelmap = nullptr; + CropAndScaleStrategy cropAndScaleStrategy = CropAndScaleStrategy::DEFAULT; }; enum class ScaleMode : int32_t { diff --git a/interfaces/kits/js/common/include/image_source_napi.h b/interfaces/kits/js/common/include/image_source_napi.h index 5ae703195d3b50237313474569138512fc9a8334..574678be48635e9c6c3a939f2dc5899662bbdaca 100644 --- a/interfaces/kits/js/common/include/image_source_napi.h +++ b/interfaces/kits/js/common/include/image_source_napi.h @@ -97,6 +97,7 @@ private: static napi_ref decodingDynamicRangeRef_; static napi_ref decodingResolutionQualityRef_; static napi_ref decodingAllocatorTypeRef_; + static napi_ref cropAndScaleStrategyRef_; napi_env env_ = nullptr; bool isRelease = false; diff --git a/interfaces/kits/native/include/image/image_source_native.h b/interfaces/kits/native/include/image/image_source_native.h index 323776839b7a48a3229944fad78f535a622a751c..3f5bc899caa8a140545b1d2f8b4cd3ff64efeac9 100644 --- a/interfaces/kits/native/include/image/image_source_native.h +++ b/interfaces/kits/native/include/image/image_source_native.h @@ -117,6 +117,22 @@ typedef enum { IMAGE_ALLOCATOR_TYPE_SHARE_MEMORY = 2, } IMAGE_ALLOCATOR_TYPE; +/** + * @brief Confirm the enumeration type for decoding and scaling order of the region. + * + * @since 16 + */ +typedef enum { + /* + * First scale, then crop. + */ + SCALE_FIRST = 1, + /* + * Perform region decoding first, then scaling. + */ + CROP_FIRST = 2 +} CROP_SCALE_STRATEGY; + /** * @brief Create a pointer for OH_ImageSource_Info struct. * @@ -217,6 +233,28 @@ Image_ErrorCode OH_DecodingOptions_GetPixelFormat(OH_DecodingOptions *options, Image_ErrorCode OH_DecodingOptions_SetPixelFormat(OH_DecodingOptions *options, int32_t pixelFormat); +/** + * @brief Get strategy number for OH_DecodingOptions struct. + * + * @param options The OH_DecodingOptions pointer will be operated. + * @param cropAndScaleStrategy the number of Scaling and cropping strategy. + * @return Returns {@link Image_ErrorCode} + * @since 16 + */ +Image_ErrorCode OH_DecodingOptions_GetCropAndScaleStrategy(OH_DecodingOptions *options, + int32_t *cropAndScaleStrategy); + +/** + * @brief Set strategy number for OH_DecodingOptions struct. + * + * @param options The OH_DecodingOptions pointer will be operated. + * @param cropAndScaleStrategy the number of Scaling and cropping strategy. + * @return Returns {@link Image_ErrorCode} + * @since 16 + */ +Image_ErrorCode OH_DecodingOptions_SetCropAndScaleStrategy(OH_DecodingOptions *options, + int32_t cropAndScaleStrategy); + /** * @brief Get index number for DecodingOptions struct. * diff --git a/plugins/common/libs/image/libextplugin/BUILD.gn b/plugins/common/libs/image/libextplugin/BUILD.gn index 31d2470cd9d199a82533c7220ee9d1de3aaeb3e9..accbbfabd2a0e4e469f56d461465cef0c4b13769 100644 --- a/plugins/common/libs/image/libextplugin/BUILD.gn +++ b/plugins/common/libs/image/libextplugin/BUILD.gn @@ -162,6 +162,7 @@ ohos_shared_library("extplugin") { "$image_subsystem/frameworks/innerkitsimpl/accessor/src/tiff_parser.cpp", "$image_subsystem/frameworks/innerkitsimpl/accessor/src/webp_exif_metadata_accessor.cpp", "src/ext_decoder.cpp", + "src/ext_ohoscodec.cpp", "src/ext_encoder.cpp", "src/ext_pixel_convert.cpp", "src/ext_stream.cpp", @@ -290,6 +291,9 @@ ohos_shared_library("extplugin") { external_deps += [ "drivers_interface_codec:libimage_proxy_2.0" ] } } + if (is_ohos) { + defines += [ "SK_ENABLE_OHOS_CODEC" ] + } cflags = [ "-DIMAGE_COLORSPACE_FLAG", "-O3", diff --git a/plugins/common/libs/image/libextplugin/include/ext_decoder.h b/plugins/common/libs/image/libextplugin/include/ext_decoder.h index 4a160db152951e750748e659fa6cf0d588b4dbca..8c7a731c422c8b25b64086f58eeb181ac7bfddd3 100644 --- a/plugins/common/libs/image/libextplugin/include/ext_decoder.h +++ b/plugins/common/libs/image/libextplugin/include/ext_decoder.h @@ -95,6 +95,7 @@ private: bool IsSupportScaleOnDecode(); bool GetScaledSize(int &dWidth, int &dHeight, float &scale); bool GetHardwareScaledSize(int &dWidth, int &dHeight, float &scale); + int GetSoftwareScaledSize(int dwidth, int dheight); bool IsSupportCropOnDecode(); bool IsSupportCropOnDecode(SkIRect &target); bool IsSupportHardwareDecode(); @@ -133,6 +134,8 @@ private: FrameCacheInfo InitFrameCacheInfo(const uint64_t rowStride, SkImageInfo info); bool FrameCacheInfoIsEqual(FrameCacheInfo& src, FrameCacheInfo& dst); uint32_t UpdateHardWareDecodeInfo(DecodeContext &context); + bool IsRegionDecodeSupported(uint32_t index, const PixelDecodeOptions &opts, PlImageInfo &info); + SkCodec::Result DoRegionDecode(DecodeContext &context); ImagePlugin::InputDataStream *stream_ = nullptr; uint32_t streamOff_ = 0; @@ -165,7 +168,10 @@ private: static constexpr uint32_t ALIGN_8 = 8; static constexpr uint32_t ALIGN_16 = 16; #endif + + OHOS::Media::Size RegiondesiredSize_; + bool SupportRegionFlag_; //Yuv OHOS::Media::Size desiredSizeYuv_; diff --git a/plugins/common/libs/image/libextplugin/include/ext_ohoscodec.h b/plugins/common/libs/image/libextplugin/include/ext_ohoscodec.h new file mode 100644 index 0000000000000000000000000000000000000000..bc4bf6bc0417a326b2fd9458e371fb3c5c25c5b2 --- /dev/null +++ b/plugins/common/libs/image/libextplugin/include/ext_ohoscodec.h @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2023 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. + */ + + #ifndef PLUGINS_COMMON_LIBS_IMAGE_LIBEXTPLUGIN_INCLUDE_EXT_OHOSCODEC_H + #define PLUGINS_COMMON_LIBS_IMAGE_LIBEXTPLUGIN_INCLUDE_EXT_OHOSCODEC_H + + #include "include/codec/SkCodec.h" + #include "include/core/SkEncodedImageFormat.h" + #include "include/core/SkStream.h" + #include "include/core/SkTypes.h" + + class SK_API SkOHOSCodec : SkNoncopyable { + public: + enum class ExifOrientationBehavior { + kIgnore, + kRespect, + }; + + static std::unique_ptr MakeFromCodec(std::unique_ptr); + + static std::unique_ptr MakeFromStream(std::unique_ptr, + SkPngChunkReader* = nullptr); + + static std::unique_ptr MakeFromData(sk_sp, SkPngChunkReader* = nullptr); + + virtual ~SkOHOSCodec(); + + const SkImageInfo& getInfo() const { return fInfo; } + + const skcms_ICCProfile* getICCProfile() const { + return fCodec->callGetEncodedInfo().profile(); + } + + SkEncodedImageFormat getEncodedFormat() const { return fCodec->getEncodedFormat(); } + + SkColorType computeOutputColorType(SkColorType requestedColorType); + + SkAlphaType computeOutputAlphaType(bool requestedUnpremul); + + sk_sp computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace = nullptr); + + int computeSampleSize(SkISize* size) const; + + SkISize getSampledDimensions(int sampleSize) const; + + bool getSupportedSubset(SkIRect* desiredSubset) const; + + SkISize getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const; + struct OHOSOptions : public SkCodec::Options { + OHOSOptions() + : SkCodec::Options() + , fSampleSize(1) + {} + + int fSampleSize; + }; + + SkCodec::Result getOHOSPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const OHOSOptions* options); + + SkCodec::Result getOHOSPixels(const SkImageInfo& info, void* pixels, size_t rowBytes); + + SkCodec::Result getPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { + return this->getOHOSPixels(info, pixels, rowBytes); + } + + SkCodec* codec() const { return fCodec.get(); } + + protected: + SkOHOSCodec(SkCodec*); + + virtual SkISize onGetSampledDimensions(int sampleSize) const = 0; + + virtual bool onGetSupportedSubset(SkIRect* desiredSubset) const = 0; + + virtual SkCodec::Result onGetOHOSPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const OHOSOptions& options) = 0; + + private: + const SkImageInfo fInfo; + std::unique_ptr fCodec; + }; + + class SkOHOSCodecAdapter : public SkOHOSCodec { + public: + + explicit SkOHOSCodecAdapter(SkCodec*); + + ~SkOHOSCodecAdapter() override {} + + protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override; + + SkCodec::Result onGetOHOSPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const OHOSOptions& options) override; + + private: + + using INHERITED = SkOHOSCodec; + }; + + class SkOHOSSampledCodec : public SkOHOSCodec { + public: + explicit SkOHOSSampledCodec(SkCodec*); + + ~SkOHOSSampledCodec() override {} + + protected: + + SkISize onGetSampledDimensions(int sampleSize) const override; + + bool onGetSupportedSubset(SkIRect* desiredSubset) const override { return true; } + + SkCodec::Result onGetOHOSPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, + const OHOSOptions& options) override; + + private: + SkISize accountForNativeScaling(int* sampleSize, int* nativeSampleSize = nullptr) const; + + SkCodec::Result sampledDecode(const SkImageInfo& info, void* pixels, size_t rowBytes, + const OHOSOptions& options); + + using INHERITED = SkOHOSCodec; + }; + + #endif // SkOHOSCodec_DEFINED \ No newline at end of file diff --git a/plugins/common/libs/image/libextplugin/src/ext_decoder.cpp b/plugins/common/libs/image/libextplugin/src/ext_decoder.cpp index bdf1513ddeb255db8810d7f8911b30fa3732edd7..b3bb52170c658915327ca1756056a079391df526 100644 --- a/plugins/common/libs/image/libextplugin/src/ext_decoder.cpp +++ b/plugins/common/libs/image/libextplugin/src/ext_decoder.cpp @@ -27,6 +27,7 @@ #include "src/codec/SkJpegCodec.h" #include "src/codec/SkJpegDecoderMgr.h" #include "ext_pixel_convert.h" +#include "extohoscodec.h" #include "image_log.h" #include "image_format_convert.h" #include "image_mime_type.h" @@ -36,6 +37,7 @@ #endif #include "image_system_properties.h" #include "image_utils.h" +#include "image_func_timer.h" #include "media_errors.h" #include "native_buffer.h" #include "securec.h" @@ -93,6 +95,10 @@ namespace { constexpr static size_t SIZE_4 = 4; constexpr static int HARDWARE_MIN_DIM = 1024; constexpr static int HARDWARE_MAX_DIM = 8192; + constexpr static int DEFAULT_SCALE_SIZE = 1; + constexpr static int DOUBLE_SCALE_SIZE = 2; + constexpr static int FOURTH_SCALE_SIZE = 4; + constexpr static int MAX_SCALE_SIZE = 8; constexpr static float HALF = 0.5; constexpr static float QUARTER = 0.25; constexpr static float ONE_EIGHTH = 0.125; @@ -513,6 +519,29 @@ bool ExtDecoder::GetHardwareScaledSize(int &dWidth, int &dHeight, float &scale) } #endif +int ExtDecoder::GetSoftwareScaledSize(int dwidth, int dheight) { + if (dstSubset_.isEmpty() && !DecodeHeader()) { + IMAGE_LOGE("DecodeHeader failed in GetSoftwareScaledSize!"); + return DEFAULT_SCALE_SIZE; + } + int softSampleSize; + int oriWidth = dstSubset_.width(); + int oriHeight = dstSubset_.height(); + float finalScale = Max(static_cast(dwidth) / oriWidth, + static_cast(dheight) / oriHeight); + // calculate sample size and dst size for hardware decode + if (finalScale > HALF) { + softSampleSize = DEFAULT_SCALE_SIZE; + } else if (finalScale > QUARTER) { + softSampleSize = DOUBLE_SCALE_SIZE; + } else if (finalScale > ONE_EIGHTH) { + softSampleSize = FOURTH_SCALE_SIZE; + } else { + softSampleSize = MAX_SCALE_SIZE; + } + return softSampleSize; +} + bool ExtDecoder::IsSupportScaleOnDecode() { constexpr float HALF_SCALE = 0.5f; @@ -536,6 +565,9 @@ bool ExtDecoder::IsSupportCropOnDecode(SkIRect &target) if (info_.isEmpty() && !DecodeHeader()) { return false; } + if (SupportRegionFlag_) { + return true; + } SkIRect orgbounds = info_.bounds(); SkIRect source = target; if (orgbounds.contains(target) && codec_->getValidSubset(&target)) { @@ -612,11 +644,15 @@ uint32_t ExtDecoder::CheckDecodeOptions(uint32_t index, const PixelDecodeOptions dstInfo_.width(), dstInfo_.height()); return ERR_IMAGE_INVALID_PARAMETER; } + IMAGE_LOGI("%{public}s IN, opts.CropRect: xy [%{public}d x %{public}d] wh [%{public}d x %{public}d]", + __func__, opts.CropRect.left, opts.CropRect.top, opts.CropRect.width, opts.CropRect.height); if (!IsValidCrop(opts.CropRect, info_, dstSubset_)) { IMAGE_LOGE("Invalid crop rect xy [%{public}d x %{public}d], wh [%{public}d x %{public}d]", dstSubset_.left(), dstSubset_.top(), dstSubset_.width(), dstSubset_.height()); return ERR_IMAGE_INVALID_PARAMETER; } + IMAGE_LOGI("%{public}s IN, dstSubset_: xy [%{public}d x %{public}d] wh [%{public}d x %{public}d]", + __func__, dstSubset_.left(), dstSubset_.top(), dstSubset_.width(), dstSubset_.height()); size_t tempSrcByteCount = info_.computeMinByteSize(); size_t tempDstByteCount = dstInfo_.computeMinByteSize(); if (SkImageInfo::ByteSizeOverflowed(tempSrcByteCount) || SkImageInfo::ByteSizeOverflowed(tempDstByteCount)) { @@ -633,6 +669,21 @@ uint32_t ExtDecoder::CheckDecodeOptions(uint32_t index, const PixelDecodeOptions return SUCCESS; } +bool ExtDecoder::IsRegionDecodeSupported(uint32_t index, const PixelDecodeOptions &opts, PlImageInfo &info) +{ + if (PreDecodeCheck(index) != SUCCESS) { + return false; + } + if (static_cast(opts.desiredPixelFormat) > static_cast(PixelFormat::BGRA_8888)) { + return false; + } + if (opts.cropAndScaleStrategy == OHOS::Media::CropAndScaleStrategy::CROP_FIRST) { + return codec_->getEncodedFormat() == SkEncodedImageFormat::kJPEG || + codec_->getEncodedFormat() == SkEncodedImageFormat::kPNG; + } + return false; +} + uint32_t ExtDecoder::SetDecodeOptions(uint32_t index, const PixelDecodeOptions &opts, PlImageInfo &info) { if (!CheckIndexValied(index)) { @@ -660,6 +711,8 @@ uint32_t ExtDecoder::SetDecodeOptions(uint32_t index, const PixelDecodeOptions & info.pixelFormat = opts.desiredPixelFormat; } } + RegiondesiredSize_.width = opts.desiredSize.width; + RegiondesiredSize_.height = opts.desiredSize.height; // SK only support low down scale int dstWidth = opts.desiredSize.width; int dstHeight = opts.desiredSize.height; @@ -689,6 +742,7 @@ uint32_t ExtDecoder::SetDecodeOptions(uint32_t index, const PixelDecodeOptions & if (resCode != SUCCESS) { return resCode; } + SupportRegionFlag_ = IsRegionDecodeSupported(index, opts, info); info.size.width = static_cast(dstInfo_.width()); info.size.height = static_cast(dstInfo_.height()); @@ -711,13 +765,13 @@ uint32_t ExtDecoder::SetContextPixelsBuffer(uint64_t byteCount, DecodeContext &c static void DebugInfo(SkImageInfo &info, SkImageInfo &dstInfo, SkCodec::Options &opts) { - IMAGE_LOGD("Decode source info: WH[%{public}d x %{public}d], A %{public}d, C %{public}d.", + IMAGE_LOGI("Decode source info: WH[%{public}d x %{public}d], A %{public}d, C %{public}d.", info.width(), info.height(), info.alphaType(), info.colorType()); - IMAGE_LOGD("Decode dst info: WH[%{public}d x %{public}d], A %{public}d, C %{public}d.", + IMAGE_LOGI("Decode dst info: WH[%{public}d x %{public}d], A %{public}d, C %{public}d.", dstInfo.width(), dstInfo.height(), dstInfo.alphaType(), dstInfo.colorType()); if (opts.fSubset != nullptr) { - IMAGE_LOGD("Decode dstOpts sub: (%{public}d, %{public}d), WH[%{public}d x %{public}d]", + IMAGE_LOGI("Decode dstOpts sub: (%{public}d, %{public}d), WH[%{public}d x %{public}d]", opts.fSubset->fLeft, opts.fSubset->fTop, opts.fSubset->width(), opts.fSubset->height()); } @@ -971,8 +1025,96 @@ uint32_t ExtDecoder::DoHeifSharedMemDecode(DecodeContext &context) #endif } +SkCodec::Result ExtDecoder::DoRegionDecode(DecodeContext &context) +{ + ImageFuncTimer imageFuncTimer("%s, dstSubset_XYWH: %d, %d, %d, %d, srcSize: %d, %d, alloctype: %d", __func__, + dstSubset_.left(), dstSubset_.top(), dstSubset_.width(), dstSubset_.height(), info_.width(), info_.height(), + context.allocatorType); + auto SkOHOSCodec = SkOHOSCodec::MakeFromCodec(std::move(codec_)); + // Ask the codec for a scaled subset + SkIRect decodeSubset = dstSubset_; + if (!SkOHOSCodec->getSupportedSubset(&decodeSubset)) { + IMAGE_LOGE("Error: Could not get subset.\n"); + return SkCodec::kErrorInInput; + } + int dstWidth = RegiondesiredSize_.width; + int dstHeight = RegiondesiredSize_.height; + int sampleSize = GetSoftwareScaledSize(dstWidth, dstHeight); + SkISize scaledSize = SkOHOSCodec->getSampledSubsetDimensions(sampleSize, decodeSubset); + SkImageInfo decodeInfo = dstInfo_.makeWH(scaledSize.width(), scaledSize.height()); + + uint64_t byteCount = decodeInfo.computeMinByteSize(); + if (context.allocatorType == Media::AllocatorType::DMA_ALLOC) { + uint32_t res = DmaMemAlloc(context, byteCount, decodeInfo); + if (res != SUCCESS) { + IMAGE_LOGE("do region decode failed, SetContextPixelsBuffer failed"); + return SkCodec::kErrorInInput; + } + } else if (context.allocatorType == Media::AllocatorType::SHARE_MEM_ALLOC) { + ShareMemAlloc(context, byteCount); + } + + uint8_t* dstBuffer = static_cast(context.pixelsBuffer.buffer); + uint64_t rowStride = decodeInfo.minRowBytes64(); + if (context.allocatorType == Media::AllocatorType::DMA_ALLOC) { + SurfaceBuffer* sbBuffer = reinterpret_cast (context.pixelsBuffer.context); + if (sbBuffer == nullptr) { + IMAGE_LOGE("%{public}s: surface buffer is nullptr", __func__); + return SkCodec::kErrorInInput; + } + rowStride = static_cast(sbBuffer->GetStride()); + IMAGE_LOGI("sbBuffer.size=%{public}d", sbBuffer->GetSize()); + } + + IMAGE_LOGI("decodeInfo.size=%{public}d, %{public}d, byteCount=%{public}lu", decodeInfo.width(), decodeInfo.height(), + byteCount); + + // Decode into the destination bitmap + SkOHOSCodec::OHOSOptions options; + options.fSampleSize = sampleSize; + options.fSubset = &decodeSubset; + IMAGE_LOGI("decodeSubset_XYWH: %{public}d, %{public}d, %{public}d, %{public}d", + decodeSubset.left(), decodeSubset.top(), decodeSubset.width(), decodeSubset.height()); + // options.fZeroInitialized = zeroInit; + SkCodec::Result result = SkOHOSCodec->getOHOSPixels(decodeInfo, dstBuffer, rowStride, &options); + + if (result == SkCodec::kSuccess) { + context.info.pixelFormat = PixelFormat::NV21; + } + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + case SkCodec::kErrorInInput: + context.outInfo.size.width = static_cast(decodeInfo.width()); + context.outInfo.size.height = static_cast(decodeInfo.height()); + return SkCodec::kSuccess; + default: + SkCodecPrintf("Error: Could not get pixels with message \"%s\".\n", + SkCodec::ResultToString(result)); + return result; + } +} + uint32_t ExtDecoder::Decode(uint32_t index, DecodeContext &context) { + if (SupportRegionFlag_) { + Debuginfo(info_, dstInfo_, dstOptions_); + SkCodec::Result regionDecodeRes = DoRegionDecode(context); + ResetCodec(); + if (SkCodec::kSuccess == regionDecodeRes) { + IMAGE_LOGI("%{public}s IN, do region decode success", __func__); + if (context.allocatorType == Media::AllocatorType::DMA_ALLOC) { + SurfaceBuffer* sbBuffer = reinterpret_cast (context.pixelsBuffer.context); + ImageUtils::DumpDataIfDumpEnabled(reinterpret_cast(context.pixelsBuffer.buffer), sbBuffer->GetSize()); + } else if (context.allocatorType == Media::AllocatorType::SHARE_MEM_ALLOC) { + ImageUtils::DumpDataIfDumpEnabled(reinterpret_cast(context.pixelsBuffer.buffer), context.pixelsBuffer.bufferSize); + } + return SUCCESS; + } else { + IMAGE_LOGE("%{public}s IN, do region decode failed", __func__); + return ERR_IMAGE_DECODE_FAILED; + } + } #ifdef JPEG_HW_DECODE_ENABLE if (IsAllocatorTypeSupportHwDecode(context) && IsSupportHardwareDecode() && DoHardWareDecode(context) == SUCCESS) { context.isHardDecode = true; diff --git a/plugins/common/libs/image/libextplugin/src/ext_ohoscodec.cpp b/plugins/common/libs/image/libextplugin/src/ext_ohoscodec.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09ab603649071e0f981ad935b1e309e36fe01673 --- /dev/null +++ b/plugins/common/libs/image/libextplugin/src/ext_ohoscodec.cpp @@ -0,0 +1,656 @@ +/* + * Copyright (C) 2023 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 "ext_ohoscodec.h" + #include "src/codec/SkSampler.h" + #include "include/codec/SkCodec.h" + #include "include/core/SkPixmap.h" + #include "src/codec/SkCodecPriv.h" + #include "src/codec/SkSampledCodec.h" + #include "src/core/SkMathPriv.h" + + static bool is_valid_sample_size(int sampleSize) { + return sampleSize > 0; + } + + static void load_gamut(SkPoint rgb[], const skcms_Matrix3x3& xyz) { + // rx = rX / (rX + rY + rZ) + // ry = rY / (rX + rY + rZ) + // gx, gy, bx, and gy are calulcated similarly. + for (int rgbIdx = 0; rgbIdx < 3; rgbIdx++) { + float sum = xyz.vals[rgbIdx][0] + xyz.vals[rgbIdx][1] + xyz.vals[rgbIdx][2]; + rgb[rgbIdx].fX = xyz.vals[rgbIdx][0] / sum; + rgb[rgbIdx].fY = xyz.vals[rgbIdx][1] / sum; + } + } + + static float calculate_area(SkPoint abc[]) { + SkPoint a = abc[0]; + SkPoint b = abc[1]; + SkPoint c = abc[2]; + return 0.5f * SkTAbs(a.fX*b.fY + b.fX*c.fY - a.fX*c.fY - c.fX*b.fY - b.fX*a.fY); + } + + static constexpr float kSRGB_D50_GamutArea = 0.084f; + + static bool is_wide_gamut(const skcms_ICCProfile& profile) { + if (profile.has_toXYZD50) { + SkPoint rgb[3]; + load_gamut(rgb, profile.toXYZD50); + return calculate_area(rgb) > kSRGB_D50_GamutArea; + } + + return false; + } + + static bool supports_any_down_scale(const SkCodec* codec) { + return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP; + } + + static inline bool smaller_than(const SkISize& a, const SkISize& b) { + return a.width() < b.width() || a.height() < b.height(); + } + + static inline bool strictly_bigger_than(const SkISize& a, const SkISize& b) { + return a.width() > b.width() && a.height() > b.height(); + } + + SkOHOSCodec::SkOHOSCodec(SkCodec* codec) + : fInfo(codec->getInfo()) + , fCodec(codec) + {} + + SkOHOSCodec::~SkOHOSCodec() {} + + std::unique_ptr SkOHOSCodec::MakeFromStream(std::unique_ptr stream, + SkPngChunkReader* chunkReader) { + auto codec = SkCodec::MakeFromStream(std::move(stream), nullptr, chunkReader); + return MakeFromCodec(std::move(codec)); + } + + std::unique_ptr SkOHOSCodec::MakeFromCodec(std::unique_ptr codec) { + if (nullptr == codec) { + return nullptr; + } + + switch ((SkEncodedImageFormat)codec->getEncodedFormat()) { + case SkEncodedImageFormat::kPNG: + case SkEncodedImageFormat::kICO: + case SkEncodedImageFormat::kJPEG: + #ifndef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: + #endif + case SkEncodedImageFormat::kBMP: + case SkEncodedImageFormat::kWBMP: + case SkEncodedImageFormat::kHEIF: + case SkEncodedImageFormat::kAVIF: + return std::make_unique(codec.release()); + #ifdef SK_HAS_WUFFS_LIBRARY + case SkEncodedImageFormat::kGIF: + #endif + #ifdef SK_CODEC_DECODES_WEBP + case SkEncodedImageFormat::kWEBP: + #endif + #ifdef SK_CODEC_DECODES_RAW + case SkEncodedImageFormat::kDNG: + #endif + #if defined(SK_CODEC_DECODES_WEBP) || defined(SK_CODEC_DECODES_RAW) || defined(SK_HAS_WUFFS_LIBRARY) + return std::make_unique(codec.release()); + #endif + + default: + return nullptr; + } + } + + std::unique_ptr SkOHOSCodec::MakeFromData(sk_sp data, + SkPngChunkReader* chunkReader) { + if (!data) { + return nullptr; + } + + return MakeFromStream(SkMemoryStream::Make(std::move(data)), chunkReader); + } + + SkColorType SkOHOSCodec::computeOutputColorType(SkColorType requestedColorType) { + bool highPrecision = fCodec->callGetEncodedInfo().bitsPerComponent() > 8; + switch (requestedColorType) { + case kARGB_4444_SkColorType: + return kN32_SkColorType; + case kN32_SkColorType: + break; + case kAlpha_8_SkColorType: + // Fall through to kGray_8. Before kGray_8_SkColorType existed, + // we allowed clients to request kAlpha_8 when they wanted a + // grayscale decode. + case kGray_8_SkColorType: + if (kGray_8_SkColorType == this->getInfo().colorType()) { + return kGray_8_SkColorType; + } + break; + case kRGB_565_SkColorType: + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kRGB_565_SkColorType; + } + break; + case kRGBA_F16_SkColorType: + return kRGBA_F16_SkColorType; + default: + break; + } + + return highPrecision ? kRGBA_F16_SkColorType : kN32_SkColorType; + } + + SkAlphaType SkOHOSCodec::computeOutputAlphaType(bool requestedUnpremul) { + if (kOpaque_SkAlphaType == this->getInfo().alphaType()) { + return kOpaque_SkAlphaType; + } + return requestedUnpremul ? kUnpremul_SkAlphaType : kPremul_SkAlphaType; + } + + sk_sp SkOHOSCodec::computeOutputColorSpace(SkColorType outputColorType, + sk_sp prefColorSpace) { + switch (outputColorType) { + case kRGBA_F16_SkColorType: + case kRGB_565_SkColorType: + case kRGBA_8888_SkColorType: + case kBGRA_8888_SkColorType: { + if (prefColorSpace) { + return prefColorSpace; + } + + const skcms_ICCProfile* encodedProfile = fCodec->callGetEncodedInfo().profile(); + if (encodedProfile) { + if (auto encodedSpace = SkColorSpace::Make(*encodedProfile)) { + return encodedSpace; + } + + if (is_wide_gamut(*encodedProfile)) { + return SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDisplayP3); + } + } + + return SkColorSpace::MakeSRGB(); + } + default: + return nullptr; + } + } + + int SkOHOSCodec::computeSampleSize(SkISize* desiredSize) const { + SkASSERT(desiredSize); + + const auto origDims = fCodec->dimensions(); + if (!desiredSize || *desiredSize == origDims) { + return 1; + } + + if (smaller_than(origDims, *desiredSize)) { + *desiredSize = origDims; + return 1; + } + + if (desiredSize->width() < 1 || desiredSize->height() < 1) { + *desiredSize = SkISize::Make(std::max(1, desiredSize->width()), + std::max(1, desiredSize->height())); + } + + if (supports_any_down_scale(fCodec.get())) { + return 1; + } + + int sampleX = origDims.width() / desiredSize->width(); + int sampleY = origDims.height() / desiredSize->height(); + int sampleSize = std::min(sampleX, sampleY); + auto computedSize = this->getSampledDimensions(sampleSize); + if (computedSize == *desiredSize) { + return sampleSize; + } + + if (computedSize == origDims || sampleSize == 1) { + *desiredSize = computedSize; + return 1; + } + + if (strictly_bigger_than(computedSize, *desiredSize)) { + while (true) { + auto smaller = this->getSampledDimensions(sampleSize + 1); + if (smaller == *desiredSize) { + return sampleSize + 1; + } + if (smaller == computedSize || smaller_than(smaller, *desiredSize)) { + *desiredSize = computedSize; + return sampleSize; + } + + sampleSize++; + computedSize = smaller; + } + + SkASSERT(false); + } + + if (!smaller_than(computedSize, *desiredSize)) { + *desiredSize = computedSize; + return sampleSize; + } + + while (sampleSize > 2) { + auto bigger = this->getSampledDimensions(sampleSize - 1); + if (bigger == *desiredSize || !smaller_than(bigger, *desiredSize)) { + *desiredSize = bigger; + return sampleSize - 1; + } + sampleSize--; + } + + *desiredSize = origDims; + return 1; + } + + SkISize SkOHOSCodec::getSampledDimensions(int sampleSize) const { + if (!is_valid_sample_size(sampleSize)) { + return {0, 0}; + } + + if (1 == sampleSize) { + return fCodec->dimensions(); + } + + return this->onGetSampledDimensions(sampleSize); + } + + bool SkOHOSCodec::getSupportedSubset(SkIRect* desiredSubset) const { + if (!desiredSubset || !is_valid_subset(*desiredSubset, fCodec->dimensions())) { + return false; + } + + return this->onGetSupportedSubset(desiredSubset); + } + + SkISize SkOHOSCodec::getSampledSubsetDimensions(int sampleSize, const SkIRect& subset) const { + if (!is_valid_sample_size(sampleSize)) { + return {0, 0}; + } + + SkIRect copySubset = subset; + if (!this->getSupportedSubset(©Subset) || copySubset != subset) { + return {0, 0}; + } + + if (fCodec->dimensions() == subset.size()) { + return this->getSampledDimensions(sampleSize); + } + + return {get_scaled_dimension(subset.width(), sampleSize), + get_scaled_dimension(subset.height(), sampleSize)}; + } + + SkCodec::Result SkOHOSCodec::getOHOSPixels(const SkImageInfo& requestInfo, + void* requestPixels, size_t requestRowBytes, const OHOSOptions* options) { + if (!requestPixels) { + return SkCodec::kInvalidParameters; + } + if (requestRowBytes < requestInfo.minRowBytes()) { + return SkCodec::kInvalidParameters; + } + + OHOSOptions defaultOptions; + if (!options) { + options = &defaultOptions; + } else { + if (options->fSubset) { + if (!is_valid_subset(*options->fSubset, fCodec->dimensions())) { + return SkCodec::kInvalidParameters; + } + + if (SkIRect::MakeSize(fCodec->dimensions()) == *options->fSubset) { + defaultOptions = *options; + defaultOptions.fSubset = nullptr; + options = &defaultOptions; + } + } + } + auto getPixelsFn = [&](const SkImageInfo& info, void* pixels, size_t rowBytes, + const SkCodec::Options& opts, int requiredFrame + ) -> SkCodec::Result { + SkOHOSCodec::OHOSOptions prevFrameOptions( + reinterpret_cast(opts)); + prevFrameOptions.fFrameIndex = requiredFrame; + return this->getOHOSPixels(info, pixels, rowBytes, &prevFrameOptions); + }; + auto result = fCodec->callHandleFrameIndex(requestInfo, requestPixels, requestRowBytes, + *options, getPixelsFn); + if (result != SkCodec::kSuccess) { + return result; + } + + return this->onGetOHOSPixels(requestInfo, requestPixels, requestRowBytes, *options); + } + + SkCodec::Result SkOHOSCodec::getOHOSPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes) { + return this->getOHOSPixels(info, pixels, rowBytes, nullptr); + } + + SkOHOSCodecAdapter::SkOHOSCodecAdapter(SkCodec* codec) + : INHERITED(codec) + {} + + SkISize SkOHOSCodecAdapter::onGetSampledDimensions(int sampleSize) const { + float scale = get_scale_from_sample_size(sampleSize); + return this->codec()->getScaledDimensions(scale); + } + + bool SkOHOSCodecAdapter::onGetSupportedSubset(SkIRect* desiredSubset) const { + return this->codec()->getValidSubset(desiredSubset); + } + + SkCodec::Result SkOHOSCodecAdapter::onGetOHOSPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const OHOSOptions& options) { + return this->codec()->getPixels(info, pixels, rowBytes, &options); + } + + SkOHOSSampledCodec::SkOHOSSampledCodec(SkCodec* codec) + : INHERITED(codec) + {} + + SkISize SkOHOSSampledCodec::accountForNativeScaling(int* sampleSizePtr, int* nativeSampleSize) const { + SkISize preSampledSize = this->codec()->dimensions(); + int sampleSize = *sampleSizePtr; + SkASSERT(sampleSize > 1); + + if (nativeSampleSize) { + *nativeSampleSize = 1; + } + + if (this->codec()->getEncodedFormat() == SkEncodedImageFormat::kJPEG) { + switch (sampleSize) { + case 2: + case 4: + case 8: + *sampleSizePtr = 1; + return this->codec()->getScaledDimensions(get_scale_from_sample_size(sampleSize)); + default: + break; + } + + int ohosRemainder; + const int ohosSampleSizes[] = { 8, 4, 2 }; + for (int supportedSampleSize : ohosSampleSizes) { + int ohosActualSampleSize; + SkTDivMod(sampleSize, supportedSampleSize, &ohosActualSampleSize, &ohosRemainder); + if (0 == ohosRemainder) { + float scale = get_scale_from_sample_size(supportedSampleSize); + + preSampledSize = this->codec()->getScaledDimensions(scale); + + *sampleSizePtr = ohosActualSampleSize; + if (nativeSampleSize) { + *nativeSampleSize = supportedSampleSize; + } + break; + } + } + } + + return preSampledSize; + } + + SkISize SkOHOSSampledCodec::onGetSampledDimensions(int sampleSize) const { + const SkISize size = this->accountForNativeScaling(&sampleSize); + return SkISize::Make(get_scaled_dimension(size.width(), sampleSize), + get_scaled_dimension(size.height(), sampleSize)); + } + + SkCodec::Result SkOHOSSampledCodec::onGetOHOSPixels(const SkImageInfo& info, void* pixels, + size_t rowBytes, const OHOSOptions& options) { + const SkIRect* subset = options.fSubset; + if (!subset || subset->size() == this->codec()->dimensions()) { + if (this->codec()->callDimensionsSupported(info.dimensions())) { + return this->codec()->getPixels(info, pixels, rowBytes, &options); + } + + return this->sampledDecode(info, pixels, rowBytes, options); + } + + int sampleSize = options.fSampleSize; + SkISize scaledSize = this->getSampledDimensions(sampleSize); + if (!this->codec()->callDimensionsSupported(scaledSize)) { + return this->sampledDecode(info, pixels, rowBytes, options); + } + + int scaledSubsetX = subset->x() / sampleSize; + int scaledSubsetY = subset->y() / sampleSize; + int scaledSubsetWidth = info.width(); + int subsetScaledHeight = info.height(); + + const SkImageInfo scaledInfo = info.makeDimensions(scaledSize); + + OHOSOptions subsetOptions = options; + { + SkIRect incrementalSubset = SkIRect::MakeXYWH(scaledSubsetX, scaledSubsetY, + scaledSubsetWidth, subsetScaledHeight); + subsetOptions.fSubset = &incrementalSubset; + const SkCodec::Result startIncrementalResult = this->codec()->startIncrementalDecode( + scaledInfo, pixels, rowBytes, &subsetOptions); + if (SkCodec::kSuccess == startIncrementalResult) { + int decodedRows = 0; + const SkCodec::Result incrementalResult = this->codec()->incrementalDecode(&decodedRows); + if (incrementalResult == SkCodec::kSuccess) { + return SkCodec::kSuccess; + } + SkASSERT(incrementalResult == SkCodec::kIncompleteInput || incrementalResult == SkCodec::kErrorInInput); + + this->codec()->callFillIncompleteImage(scaledInfo, pixels, rowBytes, + options.fZeroInitialized, subsetScaledHeight, decodedRows); + return incrementalResult; + } else if (startIncrementalResult != SkCodec::kUnimplemented) { + return startIncrementalResult; + } + } + + SkIRect scanlineSubset = SkIRect::MakeXYWH(scaledSubsetX, 0, scaledSubsetWidth, + scaledSize.height()); + subsetOptions.fSubset = &scanlineSubset; + + SkCodec::Result result = this->codec()->startScanlineDecode(scaledInfo, + &subsetOptions); + if (SkCodec::kSuccess != result) { + return result; + } + + SkASSERT(this->codec()->getScanlineOrder() == SkCodec::kTopDown_SkScanlineOrder); + if (!this->codec()->skipScanlines(scaledSubsetY)) { + this->codec()->callFillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + subsetScaledHeight, 0); + return SkCodec::kIncompleteInput; + } + + int decodedLines = this->codec()->getScanlines(pixels, subsetScaledHeight, rowBytes); + if (decodedLines != subsetScaledHeight) { + return SkCodec::kIncompleteInput; + } + return SkCodec::kSuccess; + } + + SkCodec::Result SkOHOSSampledCodec::sampledDecode(const SkImageInfo& info, void* pixels, + size_t rowBytes, const OHOSOptions& options) { + SkASSERT(options.fSampleSize > 1); + + int sampleSize = options.fSampleSize; + int nativeSampleSize; + SkISize ohosNativeSize = this->accountForNativeScaling(&sampleSize, &nativeSampleSize); + + SkIRect subset; + int ohosSubsetY = 0; + int ohosSubsetWidth = ohosNativeSize.width(); + int ohosSubsetHeight = ohosNativeSize.height(); + if (options.fSubset) { + const SkIRect* subsetPtr = options.fSubset; + + const int subsetX = subsetPtr->x() / nativeSampleSize; + ohosSubsetY = subsetPtr->y() / nativeSampleSize; + + ohosSubsetWidth = get_scaled_dimension(subsetPtr->width(), nativeSampleSize); + ohosSubsetHeight = get_scaled_dimension(subsetPtr->height(), nativeSampleSize); + + subset.setXYWH(subsetX, 0, ohosSubsetWidth, ohosNativeSize.height()); + } + + const int ohosSampleX = ohosSubsetWidth / info.width(); + const int ohosSampleY = ohosSubsetHeight / info.height(); + + const int ohosSamplingOffsetY = get_start_coord(ohosSampleY); + const int ohosStartY = ohosSamplingOffsetY + ohosSubsetY; + const int ohosDstHeight = info.height(); + + const SkImageInfo nativeInfo = info.makeDimensions(ohosNativeSize); + + { + OHOSOptions incrementalOptions = options; + SkIRect incrementalSubset; + if (options.fSubset) { + incrementalSubset.fTop = ohosSubsetY; + incrementalSubset.fBottom = ohosSubsetY + ohosSubsetHeight; + incrementalSubset.fLeft = subset.fLeft; + incrementalSubset.fRight = subset.fRight; + incrementalOptions.fSubset = &incrementalSubset; + } + const SkCodec::Result startResult = this->codec()->startIncrementalDecode(nativeInfo, + pixels, rowBytes, &incrementalOptions); + if (SkCodec::kSuccess == startResult) { + SkSampler* ohosSampler = this->codec()->callGetSampler(true); + if (!ohosSampler) { + return SkCodec::kUnimplemented; + } + + if (ohosSampler->setSampleX(ohosSampleX) != info.width()) { + return SkCodec::kInvalidScale; + } + if (get_scaled_dimension(ohosSubsetHeight, ohosSampleY) != info.height()) { + return SkCodec::kInvalidScale; + } + + ohosSampler->setSampleY(ohosSampleY); + + int rowsDecoded = 0; + const SkCodec::Result incrementalResult = this->codec()->incrementalDecode(&rowsDecoded); + if (incrementalResult == SkCodec::kSuccess) { + return SkCodec::kSuccess; + } + SkASSERT(incrementalResult == SkCodec::kIncompleteInput || incrementalResult == SkCodec::kErrorInInput); + + SkASSERT(rowsDecoded <= info.height()); + this->codec()->callFillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + info.height(), rowsDecoded); + return incrementalResult; + } else if (startResult == SkCodec::kIncompleteInput + || startResult == SkCodec::kErrorInInput) { + return SkCodec::kInvalidInput; + } else if (startResult != SkCodec::kUnimplemented) { + return startResult; + } + } + + OHOSOptions ohosSampledOptions = options; + if (options.fSubset) { + ohosSampledOptions.fSubset = ⊂ + } + SkCodec::Result startScanlineResult = this->codec()->startScanlineDecode(nativeInfo, + &ohosSampledOptions); + if (SkCodec::kIncompleteInput == startScanlineResult || SkCodec::kErrorInInput == startScanlineResult) { + return SkCodec::kInvalidInput; + } else if (SkCodec::kSuccess != startScanlineResult) { + return startScanlineResult; + } + + SkSampler* ohosSampler = this->codec()->callGetSampler(true); + if (!ohosSampler) { + return SkCodec::kInternalError; + } + + if (ohosSampler->setSampleX(ohosSampleX) != info.width()) { + return SkCodec::kInvalidScale; + } + if (get_scaled_dimension(ohosSubsetHeight, ohosSampleY) != info.height()) { + return SkCodec::kInvalidScale; + } + + switch(this->codec()->getScanlineOrder()) { + case SkCodec::kTopDown_SkScanlineOrder: { + if (!this->codec()->skipScanlines(ohosStartY)) { + this->codec()->callFillIncompleteImage(info, pixels, rowBytes, options.fZeroInitialized, + ohosDstHeight, 0); + return SkCodec::kIncompleteInput; + } + void* pixelPtr = pixels; + for (int y = 0; y < ohosDstHeight; y++) { + if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { + this->codec()->callFillIncompleteImage(info, pixels, rowBytes, + options.fZeroInitialized, ohosDstHeight, y + 1); + return SkCodec::kIncompleteInput; + } + if (y < ohosDstHeight - 1) { + if (!this->codec()->skipScanlines(ohosSampleY - 1)) { + this->codec()->callFillIncompleteImage(info, pixels, rowBytes, + options.fZeroInitialized, ohosDstHeight, y + 1); + return SkCodec::kIncompleteInput; + } + } + pixelPtr = SkTAddOffset(pixelPtr, rowBytes); + } + return SkCodec::kSuccess; + } + case SkCodec::kBottomUp_SkScanlineOrder: { + SkASSERT(0 == ohosSubsetY && ohosNativeSize.height() == ohosSubsetHeight); + int y; + for (y = 0; y < ohosNativeSize.height(); y++) { + int srcY = this->codec()->nextScanline(); + if (is_coord_necessary(srcY, ohosSampleY, ohosDstHeight)) { + void* pixelPtr = SkTAddOffset(pixels, + rowBytes * get_dst_coord(srcY, ohosSampleY)); + if (1 != this->codec()->getScanlines(pixelPtr, 1, rowBytes)) { + break; + } + } else { + if (!this->codec()->skipScanlines(1)) { + break; + } + } + } + + if (ohosNativeSize.height() == y) { + return SkCodec::kSuccess; + } + + const SkImageInfo ohosFillInfo = info.makeWH(info.width(), 1); + for (; y < ohosNativeSize.height(); y++) { + int srcY = this->codec()->outputScanline(y); + if (!is_coord_necessary(srcY, ohosSampleY, ohosDstHeight)) { + continue; + } + + void* rowPtr = SkTAddOffset(pixels, rowBytes * get_dst_coord(srcY, ohosSampleY)); + SkSampler::Fill(ohosFillInfo, rowPtr, rowBytes, options.fZeroInitialized); + } + return SkCodec::kIncompleteInput; + } + default: + SkASSERT(false); + return SkCodec::kUnimplemented; + } + } \ No newline at end of file diff --git a/plugins/manager/include/image/abs_image_decoder.h b/plugins/manager/include/image/abs_image_decoder.h index 90d66cf2445b48053b47bb068310ba2d020b8984..92597e452d67832b3374b1407c7e707bd8d1fe01 100644 --- a/plugins/manager/include/image/abs_image_decoder.h +++ b/plugins/manager/include/image/abs_image_decoder.h @@ -123,6 +123,7 @@ struct PixelDecodeOptions { OHOS::Media::SVGResize plSVGResize; std::shared_ptr plDesiredColorSpace = nullptr; std::shared_ptr plReusePixelmap = nullptr; + OHOS::Media::CropAndScaleStrategy CropAndScaleStrategy = OHOS::Media::CropAndScaleStrategy::DEFAULT; }; class AbsImageDecoder { diff --git a/test/resource/image/images/test_picture_png.png b/test/resource/image/images/test_picture_png.png new file mode 100644 index 0000000000000000000000000000000000000000..7620c26f1007323f0da2bb7e47c79ecb5bc8a41c Binary files /dev/null and b/test/resource/image/images/test_picture_png.png differ diff --git a/test/resource/image/ohos_test.xml b/test/resource/image/ohos_test.xml index d9796bf77ab967f0735a601a60ae2c422f6859e5..b26e92ca9d970c23f51f0be40ce616a535ea60c6 100644 --- a/test/resource/image/ohos_test.xml +++ b/test/resource/image/ohos_test.xml @@ -349,6 +349,7 @@