diff --git a/OAT.xml b/OAT.xml index a859db12f89c5a9c27affe4176ee471b5ac41b19..15f8f74c9447036a7605d31acd60e2019de66e17 100644 --- a/OAT.xml +++ b/OAT.xml @@ -424,7 +424,14 @@ Note:If the text contains special characters, please escape them according to th - + + + + + + + + @@ -843,6 +850,12 @@ Note:If the text contains special characters, please escape them according to th + + + + + + diff --git a/code/DocsSample/Media/Image/ImageEffect/.gitignore b/code/DocsSample/Media/Image/ImageEffect/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b8017deb0acccd6154f5770a667148c7a495787c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/.gitignore @@ -0,0 +1,13 @@ +/node_modules +/oh_modules +/local.properties +/.idea +**/build +/.hvigor +.cxx +/.clangd +/.clang-format +/.clang-tidy +**/.test +/oh-package-lock.json5 +/entry/oh-package-lock.json5 \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/AppScope/app.json5 b/code/DocsSample/Media/Image/ImageEffect/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f54b2635e34a955355ee0523b717a62c4528661f --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/AppScope/app.json5 @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "app": { + "bundleName": "com.samples.ImageEffect", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/element/string.json b/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..da03b5b8382de77f478084eaa2e0d0f13e682e80 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "ImageEffect" + } + ] +} diff --git a/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/media/app_icon.png b/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/AppScope/resources/base/media/app_icon.png differ diff --git a/code/DocsSample/Media/Image/ImageEffect/README.md b/code/DocsSample/Media/Image/ImageEffect/README.md new file mode 100644 index 0000000000000000000000000000000000000000..36618eae529d165a706da9672a602ceaf93929b2 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/README.md @@ -0,0 +1,95 @@ +# ImageEffect + +## 介绍 + +本示例依照指南 媒体->Image Kit(图片处理服务)->图片开发指导(C/C++)->[使用ImageEffect编辑图片](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/media/image/image-effect-guidelines.md)进行编写。 + +本示例主要功能如下: +- 通过ImageEffect提供的Native API接口添加滤镜或滤镜链,对输入图像应用滤镜效果。 +- 注册实现了自定义亮度滤镜与自定义裁剪滤镜。 +- 通过ImageEffect提供的Native API接口快速实现单个滤镜的处理效果。 +- 通过ImageEffect提供的Native API接口查询滤镜能力信息。 + +## 效果预览 + +| 主界面 | 设置滤镜 | 选择输入类型 | 查询滤镜信息 | +|------------------------------------------------------------------|--------------------------------------------------------------------------|--------------------------------------------------------------------------------|----------------------------------------------------------------------| +| ![ImageEffect_MainPage](./screenshots/ImageEffect_MainPage.jpeg) | ![ImageEffect_ImageEffect_Filter](./screenshots/ImageEffect_Filter.jpeg) | ![ImageEffect_ImageEffect_InputType](./screenshots/ImageEffect_InputType.jpeg) | ![ImageEffect_FilterInfo](./screenshots/ImageEffect_FilterInfo.jpeg) | + + +使用说明: +1. 输入类型选择:在参数设置页面中,选择输入类型,显示区域将切换为对应的输入类型。 +2. 调整滤镜参数:在参数设置页面中,选择所需的滤镜选项,并通过拖动滑动条来调节各个滤镜算子的参数。 +3. 滤镜算子选择:可以选择裁剪、缩放、旋转等内置的算子,或选择自定义亮度、自定义剪裁滤镜。本示例允许同时选择多个滤镜以形成滤镜链。 +4. 确认与保存设置:调整完毕后,点击确认按钮以保存所设置的滤镜参数。 +5. 应用滤镜效果:返回图片展示页面后,点击Apply按钮,系统将展现经过滤镜处理后的图片效果。 +6. 重置图片效果:如需撤销所做改动,点击Reset按钮,图片将恢复至调整前状态。 +7. 查看滤镜详细信息:在参数设置页面,点击滑块旁的搜索图标,系统将展示一个详细信息页面,提供所选滤镜相关信息。 +8. 查询滤镜信息:点击查询按钮并选择查询参数来获取滤镜信息页面,该页面将显示所应用的滤镜个数和名称信息。 + +## 工程目录 + +``` +ImageEffect +entry/src/main/cpp/ +├── CMakeLists.txt(CMake构建配置文件) +├── backend +│   ├── image_edit.cpp(图片编辑) +│   └── image_edit.h +├── logging.h(Log相关定义声明) +├── napi_init.cpp(图片处理功能注册) +└── utils + ├── common_utils.cpp(字符处理工具函数) + ├── common_utils.h + ├── json_utils.cpp(json格式处理工具函数) + ├── json_utils.h + ├── pixelmap_helper.cpp(图片解码) + └── pixelmap_helper.h +entry/src/main/ets/ +├── pages +│   └── ImageEditPage.ets(图片显示、设置页面) +└── utils + └── ImageUtils.ets(图片资源获取处理) +entry/src/ohosTest/ets/ +└── test + ├── Ability.test.ets (UI测试代码) + └── List.test.ets (测试套件列表) +``` + +## 具体实现 + ++ 图片编辑功能在ImageEditPage中实现,源码参考ImageEditPage.ets: + + 输入类型选择:在图片展示页面点击设置按钮弹出设置页面,选择输入类型。 + + 滤镜选择:在图片展示页面点击设置按钮弹出设置页面,选择滤镜并设置参数,点击确认按钮保存滤镜参数选择。 + + 滤镜生效:点击Apply按钮,展示经过滤镜处理后的图片效果,点击Reset按钮,图片恢复至调整前状态。 + + 滤镜查询:点击滑动条后面的查询图标可查询单个滤镜信息,点击下方查询按钮可查询对应条件的滤镜信息。 + ++ native接口在image_edit中实现,源码参考image_edit.cpp: + - 应用滤镜:应用滤镜处理需要加载libimage_effect.so,对传入的输入类型进行处理,使用Brightness对图像进行亮度处理使用Contrast对图像进行对比度处理,使用Crop对图像进行裁剪处理,也可以自定义滤镜对图像进行处理。 + - 滤镜查询:接口实现对滤镜的查询功能,通过OH_EffectFilter_LookupFilterInfo接口方法查询单个滤镜的详细信息,通过OH_EffectFilter_LookupFilters接口方法查询指定条件下的滤镜数量以及信息。 + +## 相关权限 + +[ohos.permission.CAMERA](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/permissions-for-all-user.md#ohospermissioncamera) + +## 依赖 + +不涉及。 + +## 约束和限制 + +1. 本示例支持标准系统上运行,支持设备:RK3568; +2. 本示例支持API12版本SDK,版本号:5.0.0.71; +3. 本示例已支持使DevEco Studio 5.0.1 Release (构建版本:5.0.5.306,构建 2024年12月6日)编译运行 + +## 下载 + +如需单独下载本工程,执行如下命令: + +``` +git init +git config core.sparsecheckout true +echo code/DocsSample/Media/Image/ImageEffect/ > .git/info/sparse-checkout +git remote add origin OpenHarmony/applications_app_samples +git pull origin master +``` \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/build-profile.json5 b/code/DocsSample/Media/Image/ImageEffect/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e6d9cfeb040286c33ab736dc93b6c5d8ba4034bd --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/build-profile.json5 @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "app": { + "products": [ + { + "name": "default", + "signingConfig": "default", + "compileSdkVersion": 12, + "compatibleSdkVersion": 12, + "targetSdkVersion": 12, + "runtimeOS": "OpenHarmony" + } + ], + "buildModeSet": [ + { + "name": "debug" + }, + { + "name": "release" + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/.gitignore b/code/DocsSample/Media/Image/ImageEffect/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/build-profile.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..eef3f858b97a349c18ff7fce6b39d369041ff4ef --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/build-profile.json5 @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "apiType": "stageMode", + "buildOption": { + "externalNativeOptions": { + "path": "./src/main/cpp/CMakeLists.txt", + "arguments": "", + "cppFlags": "", + "abiFilters": ["armeabi-v7a","arm64-v8a"] + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": true, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + }, + "nativeLib": { + "debugSymbol": { + "strip": true, + "exclude": [] + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/hvigorfile.ts b/code/DocsSample/Media/Image/ImageEffect/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..8774588471ede4c1563f09d9a1d22f764bb1fd9e --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/hvigorfile.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 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. + */ +import { hapTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/obfuscation-rules.txt b/code/DocsSample/Media/Image/ImageEffect/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..a1dfa0bd175984dc49e641436aa67b1de1b8abeb --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/obfuscation-rules.txt @@ -0,0 +1,22 @@ +# Define project specific obfuscation rules here. +# You can include the obfuscation configuration files in the current module's build-profile.json5. +# +# For more details, see +# https://gitee.com/openharmony/arkcompiler_ets_frontend/blob/master/arkguard/README.md + +# Obfuscation options: +# -disable-obfuscation: disable all obfuscations +# -enable-property-obfuscation: obfuscate the property names +# -enable-toplevel-obfuscation: obfuscate the names in the global scope +# -compact: remove unnecessary blank spaces and all line feeds +# -remove-log: remove all console.* statements +# -print-namecache: print the name cache that contains the mapping from the old names to new names +# -apply-namecache: reuse the given cache file + +# Keep options: +# -keep-property-name: specifies property names that you want to keep +# -keep-global-name: specifies names that you want to keep in the global scope +-enable-property-obfuscation +-enable-toplevel-obfuscation +-enable-filename-obfuscation +-enable-export-obfuscation \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/oh-package.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5b21e253af246edab8b6ef4f10938f4417e8bc25 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/oh-package.json5 @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "libentry.so": "file:./src/main/cpp/types/libentry" + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/CMakeLists.txt b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..c8672e7c40f5069317db457233f11a07962da4da --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/CMakeLists.txt @@ -0,0 +1,30 @@ +# the minimum version of CMake. +cmake_minimum_required(VERSION 3.5.0) +project(ImageEffect) + +set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +include_directories(${NATIVERENDER_ROOT_PATH} + ${NATIVERENDER_ROOT_PATH}/include + ${NATIVERENDER_ROOT_PATH}/backend + ${NATIVERENDER_ROOT_PATH}/utils +) + +add_library(entry SHARED + napi_init.cpp + utils/common_utils.cpp + utils/json_utils.cpp + utils/pixelmap_helper.cpp + backend/image_edit.cpp +) +target_link_libraries(entry PUBLIC + libace_napi.z.so + libpixelmap_ndk.z.so + libimage_packer.so + libimage_source.so + libhilog_ndk.z.so + libimage_effect.so + libpixelmap.so + libnative_window.so + libnative_buffer.so +) \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.cpp b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.cpp new file mode 100644 index 0000000000000000000000000000000000000000..73bfd8b5f71447a67fc5a9c9ec6852f056e05f8f --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.cpp @@ -0,0 +1,878 @@ +/* + * Copyright (c) 2025 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 +#include +#include +#include +#include +#include +#include +#include "logging.h" +#include "image_edit.h" +#include "utils/common_utils.h" +#include "utils/pixelmap_helper.h" +#include "utils/json_utils.h" + +struct FilterArrayData { + std::string name; + int value; +}; + +struct PixelmapInfo { + uint32_t width = 0; + uint32_t height = 0; + int32_t format = PIXEL_FORMAT::PIXEL_FORMAT_UNKNOWN; + uint32_t rowStride = 0; +}; + +static ImageEffect_FilterDelegate delegateBrightness; +static ImageEffect_FilterDelegate delegateCrop; + +std::vector> GetFilters(napi_env env, napi_value arg); + +OH_EffectFilter *AddFilter(OH_ImageEffect *imageEffect, const char *filterName); +napi_value SetFilterValue(OH_EffectFilter *filter, const char *filterName, float filterValue, + OH_PixelmapNative *pixelmap); +void AddFilterSingle(const char *filterName, float filterValue, OH_PixelmapNative *inputPixelmap, + OH_PixelmapNative *outputPixelmap); +PixelmapInfo GetPixelmapInfo(OH_PixelmapNative *pixelmap); +napi_value SetCropFilterValue(OH_EffectFilter *filter, float filterValue); + +const double PI = 3.14159265; + +// 滤镜数据范围 +const float_t DATA_VALUE_MIN = -100.0; +const float_t DATA_VALUE_MAX = 100.0; + +// 预期参数数量 +constexpr int EXPECTED_ARGS_ZERO = 0; +constexpr int EXPECTED_ARGS_ONE = 1; +constexpr int EXPECTED_ARGS_TWO = 2; + +// AREA信息索引下标 +constexpr int AREA_INFO_ZERO = 0; +constexpr int AREA_INFO_ONE = 1; +constexpr int AREA_INFO_TWO = 2; +constexpr int AREA_INFO_THREE = 3; + +// RBGA8888格式,一个像素点由四个字节组成 +constexpr int RGB_IDX_ZERO = 0; +constexpr int RGB_IDX_THREE = 3; + +// RGB颜色范围 +constexpr int RBG_MIN = 0; +constexpr int RBG_MAX = 255; + +OH_ImageEffect* ImageEdit::imageEffect_ = nullptr; + +ImageEdit::~ImageEdit() +{ + if (imageEffect_ != nullptr) { + OH_ImageEffect_Release(imageEffect_); + imageEffect_ = nullptr; + } +} + +napi_value ImageEdit::PixelMapFilterStart(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_boolean(env, false, &result); + + size_t argc = EXPECTED_ARGS_TWO; + napi_value args[EXPECTED_ARGS_TWO] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + + std::string path = CommonUtils::GetStringArgument(env, args[EXPECTED_ARGS_ZERO]); + std::vector> filters = GetFilters(env, args[EXPECTED_ARGS_ONE]); + + OH_ImageEffect *imageEffect = OH_ImageEffect_Create("imageEdit"); + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "OH_ImageEffect_Create fail!"); + std::shared_ptr imageEffectPtr( + imageEffect, [](OH_ImageEffect *imageEffect) { OH_ImageEffect_Release(imageEffect); }); + + std::shared_ptr pixelmapNativePtr = PixelMapHelper::Decode(path); + CHECK_AND_RETURN_RET_LOG(pixelmapNativePtr != nullptr, result, "Decode path fail! path=%{public}s", path.c_str()); + + if (filters.size() == 1 && (strcmp(filters[0][0].name.c_str(), OH_EFFECT_BRIGHTNESS_FILTER) == 0 || + strcmp(filters[0][0].name.c_str(), OH_EFFECT_CONTRAST_FILTER) == 0)) { + std::shared_ptr outputpixelmapNativePtr = PixelMapHelper::Decode(path); + CHECK_AND_RETURN_RET_LOG(outputpixelmapNativePtr != nullptr, result, "Decode path fail! path=%{public}s", + path.c_str()); + + AddFilterSingle(filters[0][0].name.c_str(), filters[0][0].value, pixelmapNativePtr.get(), + outputpixelmapNativePtr.get()); + + bool encodeRes = PixelMapHelper::Encode(outputpixelmapNativePtr.get(), path); + CHECK_AND_RETURN_RET_LOG(encodeRes, result, "Encode path fail! path=%{public}s", path.c_str()); + + napi_get_boolean(env, true, &result); + return result; + } + + for (int i = 0; i < filters.size(); i++) { + OH_EffectFilter *filter = AddFilter(imageEffectPtr.get(), filters[i][0].name.c_str()); + CHECK_AND_RETURN_RET_LOG(filter != nullptr, result, "OH_ImageEffect_AddFilter fail!"); + LOG_I("%{public}s : %{public}d", filters[i][0].name.c_str(), filters[i][0].value); + SetFilterValue(filter, filters[i][0].name.c_str(), filters[i][0].value, pixelmapNativePtr.get()); + } + + ImageEffect_ErrorCode errorCode = OH_ImageEffect_SetInputPixelmap(imageEffectPtr.get(), pixelmapNativePtr.get()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetInputPixelMap fail! errorCode = %{public}d", errorCode); + + // 设置输出的Pixelmap(可选),不调用该接口时会在输入Pixelmap上直接生效滤镜效果。 + errorCode = OH_ImageEffect_SetOutputPixelmap(imageEffectPtr.get(), pixelmapNativePtr.get()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetOutputPixelmap fail!"); + + errorCode = OH_ImageEffect_Start(imageEffectPtr.get()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_Start fail! errorCode = %{public}d", errorCode); + + // (可选 序列化效果器) + char *imageinfo = nullptr; + errorCode = OH_ImageEffect_Save(imageEffect, &imageinfo); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, "OH_ImageEffect_Save fail!"); + + bool encodeRes = PixelMapHelper::Encode(pixelmapNativePtr.get(), path); + CHECK_AND_RETURN_RET_LOG(encodeRes, result, "Encode path fail! path=%{public}s", path.c_str()); + + napi_get_boolean(env, true, &result); + return result; +} + +napi_value ImageEdit::NativeBufferFilterStart(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_boolean(env, false, &result); + + size_t argc = EXPECTED_ARGS_ONE; + napi_value args[EXPECTED_ARGS_ONE] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + + std::vector> filters = GetFilters(env, args[EXPECTED_ARGS_ZERO]); + + OH_ImageEffect *imageEffect = OH_ImageEffect_Create("imageEdit"); + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "OH_ImageEffect_Create fail!"); + std::shared_ptr imageEffectPtr(imageEffect, [](OH_ImageEffect *imageEffect) { + ImageEffect_ErrorCode errorCode = OH_ImageEffect_Release(imageEffect); + CHECK_AND_RETURN_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, "OH_ImageEffect_Release fail!"); + }); + + OH_NativeBuffer_Config config{ + .width = 0x100, + .height = 0x100, + .format = NATIVEBUFFER_PIXEL_FMT_RGBA_8888, + .usage = NATIVEBUFFER_USAGE_ALIGNMENT_512, + }; + + for (int i = 0; i < filters.size(); i++) { + OH_EffectFilter *filter = AddFilter(imageEffectPtr.get(), filters[i][0].name.c_str()); + CHECK_AND_RETURN_RET_LOG(filter != nullptr, result, "OH_ImageEffect_AddFilter fail!"); + SetFilterValue(filter, filters[i][0].name.c_str(), filters[i][0].value, nullptr); + } + + OH_NativeBuffer *inputNativeBuffer = OH_NativeBuffer_Alloc(&config); + CHECK_AND_RETURN_RET_LOG(inputNativeBuffer != nullptr, result, "OH_NativeBuffer_Alloc Failed!"); + + OH_NativeBuffer *outputNativeBuffer = inputNativeBuffer; + + // 设置输入的NativeBuffer。 + ImageEffect_ErrorCode errorCode = OH_ImageEffect_SetInputNativeBuffer(imageEffect, inputNativeBuffer); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetInputNativeBuffer fail!"); + + // 设置输出的NativeBuffer(可选),不调用该接口时会在输入NativeBuffer上直接生效滤镜效果。 + errorCode = OH_ImageEffect_SetOutputNativeBuffer(imageEffect, outputNativeBuffer); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetOutputNativeBuffer fail!"); + + errorCode = OH_ImageEffect_Start(imageEffectPtr.get()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_Start fail! errorCode = %{public}d", errorCode); + + // (可选 序列化效果器) + char *imageinfo = nullptr; + errorCode = OH_ImageEffect_Save(imageEffect, &imageinfo); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, "OH_ImageEffect_Save fail!"); + + napi_get_boolean(env, true, &result); + return result; +} + +napi_value ImageEdit::URIFilterStart(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_boolean(env, false, &result); + + size_t argc = EXPECTED_ARGS_TWO; + napi_value args[EXPECTED_ARGS_TWO] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + + std::string path = CommonUtils::GetStringArgument(env, args[EXPECTED_ARGS_ZERO]); + std::vector> filters = GetFilters(env, args[EXPECTED_ARGS_ONE]); + + OH_ImageEffect *imageEffect = OH_ImageEffect_Create("imageEdit"); + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "OH_ImageEffect_Create fail!"); + std::shared_ptr imageEffectPtr(imageEffect, [](OH_ImageEffect *imageEffect) { + ImageEffect_ErrorCode errorCode = OH_ImageEffect_Release(imageEffect); + CHECK_AND_RETURN_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, "OH_ImageEffect_Release fail!"); + }); + + std::shared_ptr pixelmapNativePtr = PixelMapHelper::Decode(path); + CHECK_AND_RETURN_RET_LOG(pixelmapNativePtr != nullptr, result, "Decode path fail! path=%{public}s", path.c_str()); + + for (int i = 0; i < filters.size(); i++) { + OH_EffectFilter *filter = AddFilter(imageEffectPtr.get(), filters[i][0].name.c_str()); + CHECK_AND_RETURN_RET_LOG(filter != nullptr, result, "OH_ImageEffect_AddFilter fail!"); + SetFilterValue(filter, filters[i][0].name.c_str(), filters[i][0].value, pixelmapNativePtr.get()); + } + + // 设置输入的URI。 + ImageEffect_ErrorCode errorCode = OH_ImageEffect_SetInputUri(imageEffectPtr.get(), path.c_str()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetInputPixelMap fail! errorCode = %{public}d", errorCode); + + // 设置输出的URI(可选),不调用该接口时会在输入URI上直接生效滤镜效果。 + errorCode = OH_ImageEffect_SetOutputUri(imageEffect, path.c_str()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetOutputUri fail!"); + + errorCode = OH_ImageEffect_Start(imageEffectPtr.get()); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_Start fail! errorCode = %{public}d", errorCode); + + // (可选 序列化效果器) + char *imageinfo = nullptr; + errorCode = OH_ImageEffect_Save(imageEffect, &imageinfo); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, "OH_ImageEffect_Save fail!"); + + napi_get_boolean(env, true, &result); + return result; +} + +napi_value ImageEdit::SurfaceFilterStart(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_boolean(env, false, &result); + + size_t argc = EXPECTED_ARGS_ONE; + napi_value args[EXPECTED_ARGS_ONE] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + + std::vector> filters = GetFilters(env, args[EXPECTED_ARGS_ZERO]); + + OH_ImageEffect *imageEffect = ImageEdit::imageEffect_; + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "imageEffect is nullptr!"); + + for (int i = 0; i < filters.size(); i++) { + OH_EffectFilter *filter = AddFilter(imageEffect, filters[i][0].name.c_str()); + CHECK_AND_RETURN_RET_LOG(filter != nullptr, result, "OH_ImageEffect_AddFilter fail!"); + SetFilterValue(filter, filters[i][0].name.c_str(), filters[i][0].value, nullptr); + } + + // 执行生效滤镜效果。 + ImageEffect_ErrorCode errorCode = OH_ImageEffect_Start(imageEffect); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_Start fail! %{public}d", errorCode); + napi_get_boolean(env, true, &result); + return result; +} + +napi_value ImageEdit::SurfaceFilterStop(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_boolean(env, false, &result); + + OH_ImageEffect *imageEffect = ImageEdit::imageEffect_; + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "imageEffect is nullptr!"); + + // 停止生效滤镜效果。 + ImageEffect_ErrorCode errorCode = OH_ImageEffect_Stop(imageEffect); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, "OH_ImageEffect_Stop fail!"); + + napi_get_boolean(env, true, &result); + return result; +} + +OH_EffectFilter *AddFilter(OH_ImageEffect *imageEffect, const char *filterName) +{ + OH_EffectFilter *filter = OH_ImageEffect_AddFilter(imageEffect, filterName); + CHECK_AND_RETURN_RET_LOG(filter != nullptr, filter, "OH_ImageEffect_AddFilter fail!"); + return filter; +} + +void AddFilterSingle(const char *filterName, float filterValue, OH_PixelmapNative *inputPixelmap, + OH_PixelmapNative *outputPixelmap) +{ + // 创建滤镜。比如:创建对比度效果器。 + OH_EffectFilter *filter = OH_EffectFilter_Create(filterName); + + // 设置滤镜参数, 滤镜强度设置为50。 + ImageEffect_Any value = {.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT, + .dataValue.floatValue = filterValue}; + ImageEffect_ErrorCode errorCode = OH_EffectFilter_SetValue(filter, OH_EFFECT_FILTER_INTENSITY_KEY, &value); + CHECK_AND_RETURN_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, "OH_EffectFilter_SetValue fail!"); + + // 生效滤镜效果。 + errorCode = OH_EffectFilter_Render(filter, inputPixelmap, outputPixelmap); + + // 销毁滤镜实例。 + errorCode = OH_EffectFilter_Release(filter); +} + +PixelmapInfo GetPixelmapInfo(OH_PixelmapNative *pixelmap) +{ + OH_Pixelmap_ImageInfo *imageInfo = nullptr; + OH_PixelmapImageInfo_Create(&imageInfo); + OH_PixelmapNative_GetImageInfo(pixelmap, imageInfo); + PixelmapInfo info; + OH_PixelmapImageInfo_GetWidth(imageInfo, &info.width); + OH_PixelmapImageInfo_GetHeight(imageInfo, &info.height); + OH_PixelmapImageInfo_GetPixelFormat(imageInfo, &info.format); + OH_PixelmapImageInfo_GetRowStride(imageInfo, &info.rowStride); + OH_PixelmapImageInfo_Release(imageInfo); + + return info; +} + +napi_value SetFilterValue(OH_EffectFilter *filter, const char *filterName, float filterValue, + OH_PixelmapNative *pixelmap) +{ + napi_value result; + ImageEffect_Any value; + std::string key; + + if (strcmp(filterName, OH_EFFECT_CROP_FILTER) == 0) { + CHECK_AND_RETURN_RET_LOG(pixelmap != nullptr, result, "pixelmap nullptr!"); + PixelmapInfo pixelMapInfo = GetPixelmapInfo(pixelmap); + uint32_t *areaInfo = new uint32_t[4]; + CHECK_AND_RETURN_RET_LOG(areaInfo, result, "areaInfo fail!"); + areaInfo[AREA_INFO_ZERO] = pixelMapInfo.width / 100.f * (100.f - static_cast(filterValue)); + areaInfo[AREA_INFO_ONE] = pixelMapInfo.height / 100.f * (100.f - static_cast(filterValue)); + areaInfo[AREA_INFO_TWO] = pixelMapInfo.width; + areaInfo[AREA_INFO_THREE] = pixelMapInfo.height; + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_PTR; + value.dataValue.ptrValue = areaInfo; + key = OH_EFFECT_FILTER_REGION_KEY; + } else if (strcmp(filterName, "CustomBrightness") == 0) { + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT; + value.dataValue.floatValue = filterValue; + key = "brightness"; + } else if (strcmp(filterName, "CustomCrop") == 0) { + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT; + value.dataValue.floatValue = filterValue; + key = "crop"; + } else { + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT; + value.dataValue.floatValue = filterValue; + key = OH_EFFECT_FILTER_INTENSITY_KEY; + } + ImageEffect_ErrorCode errorCode = OH_EffectFilter_SetValue(filter, key.c_str(), &value); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, nullptr, + "OH_EffectFilter_SetValue fail! errorCode = %{public}d", errorCode); + return result; +} + +std::pair GetNapiArrayLength(napi_env env, napi_value element) +{ + uint32_t length = 0; + napi_status status = napi_get_array_length(env, element, &length); + return std::make_pair(status, length); +} + +std::string HandleStringType(napi_env env, napi_value childElement, napi_status &status) +{ + std::string name; + + size_t bufferLength = 0; + status = napi_get_value_string_utf8(env, childElement, nullptr, 0, &bufferLength); + CHECK_AND_RETURN_RET_LOG(status == napi_ok && bufferLength > 0, name, + "GetFilters napi_get_value_string_utf8 fail! status = %{public}d", status); + char *buffer = nullptr; + buffer = reinterpret_cast(malloc((bufferLength + 1) * sizeof(char))); + status = napi_get_value_string_utf8(env, childElement, buffer, bufferLength + 1, &bufferLength); + if (status == napi_ok) { + name = buffer; + } + free(buffer); + return name; +} + +int HandleNumberType(napi_env env, napi_value childElement, napi_status &status) +{ + int32_t result = 0; + status = napi_get_value_int32(env, childElement, &result); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "GetFilters napi_get_value_int32 fail! status = %{public}d", + status); + return result; +} + +std::vector> GetFilters(napi_env env, napi_value arg) +{ + std::vector> data; + napi_status status; + + bool is_array; + status = napi_is_array(env, arg, &is_array); + CHECK_AND_RETURN_RET_LOG(is_array == true, data, "GetFilters napi_is_array fail! status=%{public}d", status); + + auto array_length = GetNapiArrayLength(env, arg); + CHECK_AND_RETURN_RET_LOG(array_length.first == napi_ok, data, + "GetFilters napi_get_array_length fail! status=%{public}d", array_length.first); + + for (uint32_t i = 0; i < array_length.second; i++) { + napi_value element; + status = napi_get_element(env, arg, i, &element); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, data, "GetFilters napi_get_element fail! status=%{public}d", + status); + + auto child_length = GetNapiArrayLength(env, element); + CHECK_AND_RETURN_RET_LOG(child_length.first == napi_ok, data, + "GetFilters child napi_get_array_length fail! status=%{public}d", child_length.first); + + std::vector row; + FilterArrayData filterArrayData; + for (uint32_t j = 0; j < child_length.second; j++) { + napi_value childElement; + status = napi_get_element(env, element, j, &childElement); + + napi_valuetype valueType; + status = napi_typeof(env, childElement, &valueType); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, data, + "GetFilters child napi_typeof fail! status=%{public}d, value=%{public}d", status, + valueType); + + if (valueType == napi_string) { + filterArrayData.name = HandleStringType(env, childElement, status); + } else if (valueType == napi_number) { + filterArrayData.value = HandleNumberType(env, childElement, status); + } + } + row.push_back(filterArrayData); + data.push_back(row); + } + + return data; +} + +napi_value ImageEdit::LookupFilterInfo(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_undefined(env, &result); + size_t argc = EXPECTED_ARGS_ONE; + napi_value args[EXPECTED_ARGS_ONE] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + std::string filterName = CommonUtils::GetStringArgument(env, args[EXPECTED_ARGS_ZERO]); + + OH_EffectFilterInfo *effectInfo = OH_EffectFilterInfo_Create(); + // 示例代码: 传入nullptr的format, 获取OH_Formats的size + ImageEffect_ErrorCode errorCode = OH_EffectFilter_LookupFilterInfo(filterName.c_str(), effectInfo); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_EffectFilter_LookupFilterInfo fail! errorCode = %{public}d", errorCode); + + char *name = nullptr; + OH_EffectFilterInfo_GetFilterName(effectInfo, &name); + + uint32_t supportedBufferTypesCount = 0; + ImageEffect_BufferType *bufferTypeArray = nullptr; + OH_EffectFilterInfo_GetSupportedBufferTypes(effectInfo, &supportedBufferTypesCount, &bufferTypeArray); + + uint32_t supportedFormatsCount = 0; + ImageEffect_Format *formatArray = nullptr; + OH_EffectFilterInfo_GetSupportedFormats(effectInfo, &supportedFormatsCount, &formatArray); + + LOG_I("LookupFilterInfo: name=%{public}s, bufferTypesCount=%{public}d, formatsCount=%{public}d", name, + supportedBufferTypesCount, supportedFormatsCount); + + std::string infoStr = CommonUtils::EffectInfoToString(effectInfo); + LOG_I("LookupFilterInfo:%{public}s", infoStr.c_str()); + status = napi_create_string_utf8(env, infoStr.c_str(), strlen(infoStr.c_str()), &result); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_create_string_utf8 fail!"); + + OH_EffectFilterInfo_Release(effectInfo); + return result; +} + +// 图像信息结构体。 +struct EffectBufferInfo { + void *addr = nullptr; + int32_t width = 0; + int32_t height = 0; + int32_t rowSize = 0; + ImageEffect_Format format = ImageEffect_Format::EFFECT_PIXEL_FORMAT_UNKNOWN; +}; + +void ApplyCustomBrightnessAlgo(OH_EffectFilter *filter, EffectBufferInfo inputBufferInfo) +{ + ImageEffect_Any value; + ImageEffect_ErrorCode errorCode = OH_EffectFilter_GetValue(filter, "brightness", &value); + CHECK_AND_RETURN_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, + "OH_EffectFilter_GetValue fail! 11 %{public}d", errorCode); + + float brightnessIncrement = value.dataValue.floatValue; + + // 获取图像的宽度和高度 + int32_t width = inputBufferInfo.width; + int32_t height = inputBufferInfo.height; + int32_t rowSize = inputBufferInfo.rowSize; + ImageEffect_Format format = inputBufferInfo.format; + + // 检查图片格式是否为RGBA8888 + CHECK_AND_RETURN_LOG(format == ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888, + "Unsupported image format: %{public}d", format); + + // 获取图像数据指针 + uint8_t *pixelData = static_cast(inputBufferInfo.addr); + // 遍历每个像素 + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // 计算当前像素的起始地址 + uint8_t *pixel = pixelData + y * rowSize + x * 4; + // 增加亮度值,最小值为0,最大值为255 + for (int i = RGB_IDX_ZERO; i < RGB_IDX_THREE; ++i) { + int tempPixel = static_cast(pixel[i]) + static_cast(brightnessIncrement); + pixel[i] = std::max(RBG_MIN, std::min(RBG_MAX, tempPixel)); + } + } + } +} + +bool RenderBrightness(OH_EffectFilter *filter, OH_EffectBufferInfo *info, OH_EffectFilterDelegate_PushData pushData) +{ + // 获取图像信息具体参数。 + EffectBufferInfo inputBufferInfo; + OH_EffectBufferInfo_GetAddr(info, &inputBufferInfo.addr); + OH_EffectBufferInfo_GetWidth(info, &inputBufferInfo.width); + OH_EffectBufferInfo_GetHeight(info, &inputBufferInfo.height); + OH_EffectBufferInfo_GetRowSize(info, &inputBufferInfo.rowSize); + OH_EffectBufferInfo_GetEffectFormat(info, &inputBufferInfo.format); + + // 调用自定义滤镜算法。 + ApplyCustomBrightnessAlgo(filter, inputBufferInfo); + + // 编辑完成后调用pushData直接传递原图。 + pushData(filter, info); + return true; +} + +void ApplyCustomCropAlgo(OH_EffectFilter *filter, EffectBufferInfo inputBufferInfo, + EffectBufferInfo &outputBufferInfo) +{ + ImageEffect_Any value; + ImageEffect_ErrorCode errorCode = OH_EffectFilter_GetValue(filter, "crop", &value); + CHECK_AND_RETURN_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, + "OH_EffectFilter_GetValue fail! 22 %{public}d", errorCode); + + float cropIncrement = value.dataValue.floatValue; + + // 计算新的高度 + int32_t newHeight = static_cast(inputBufferInfo.height * cropIncrement / 100); + + // 分配新的内存来存储裁剪后的图像数据 + uint8_t *croppedData = new uint8_t[newHeight * inputBufferInfo.rowSize]; + + // 复制裁剪后的图像数据 + for (int y = 0; y < newHeight; ++y) { + uint8_t *src = static_cast(inputBufferInfo.addr) + y * inputBufferInfo.rowSize; + uint8_t *dst = croppedData + y * inputBufferInfo.rowSize; + for (int x = 0; x < inputBufferInfo.rowSize; ++x) { + dst[x] = src[x]; + } + } + + // 设置输出缓冲区的信息 + outputBufferInfo.addr = croppedData; + outputBufferInfo.width = inputBufferInfo.width; + outputBufferInfo.height = newHeight; + outputBufferInfo.rowSize = inputBufferInfo.rowSize; + outputBufferInfo.format = inputBufferInfo.format; +} + +bool RenderCrop(OH_EffectFilter *filter, OH_EffectBufferInfo *info, OH_EffectFilterDelegate_PushData pushData) +{ + // 获取图像信息具体参数。 + EffectBufferInfo inputBufferInfo; + OH_EffectBufferInfo_GetAddr(info, &inputBufferInfo.addr); + OH_EffectBufferInfo_GetWidth(info, &inputBufferInfo.width); + OH_EffectBufferInfo_GetHeight(info, &inputBufferInfo.height); + OH_EffectBufferInfo_GetRowSize(info, &inputBufferInfo.rowSize); + OH_EffectBufferInfo_GetEffectFormat(info, &inputBufferInfo.format); + + // 创建输出像素信息。 + EffectBufferInfo outputBufferInfo; + + // 调用自定义滤镜算法。 + ApplyCustomCropAlgo(filter, inputBufferInfo, outputBufferInfo); + + // 生成outputOhInfo。 + OH_EffectBufferInfo *outputOhInfo = OH_EffectBufferInfo_Create(); + OH_EffectBufferInfo_SetAddr(outputOhInfo, outputBufferInfo.addr); + OH_EffectBufferInfo_SetWidth(outputOhInfo, outputBufferInfo.width); + OH_EffectBufferInfo_SetHeight(outputOhInfo, outputBufferInfo.height); + OH_EffectBufferInfo_SetRowSize(outputOhInfo, outputBufferInfo.rowSize); + OH_EffectBufferInfo_SetEffectFormat(outputOhInfo, outputBufferInfo.format); + + // 编辑完成后调用pushData传递outputOhInfo。 + pushData(filter, outputOhInfo); + + // 释放资源。 + OH_EffectBufferInfo_Release(outputOhInfo); + + return true; +} + +bool SaveFilterBrightness(OH_EffectFilter *filter, char **info) +{ + // 获取自定义所设置的滤镜参数,其中"brightness"为自定义滤镜的Key,由开发者自己任意指定。 + ImageEffect_Any value; + ImageEffect_ErrorCode errorCode = OH_EffectFilter_GetValue(filter, "brightness", &value); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, false, + "OH_EffectFilter_GetValue fail! 33 %{public}d", errorCode); + + // 生成键值对信息。 + Json values; + values["brightness"] = value.dataValue.floatValue; + Json root; + root["name"] = "CustomBrightness"; + root["values"] = values; + + // 将json对象转成字符串infoStr + std::string infoStr = root.Dump(); + + // 对*info赋值序列化字符串地址。 + *info = strdup(infoStr.data()); + + return true; +} + +bool SaveFilterCrop(OH_EffectFilter *filter, char **info) +{ + // 获取自定义所设置的滤镜参数,其中"crop"为自定义滤镜的Key,由开发者自己任意指定。 + ImageEffect_Any value; + ImageEffect_ErrorCode errorCode = OH_EffectFilter_GetValue(filter, "crop", &value); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, false, + "OH_EffectFilter_GetValue fail! 44 %{public}d", errorCode); + + // 生成键值对信息。 + Json values; + values["crop"] = value.dataValue.floatValue; + Json root; + root["name"] = "CustomCrop"; + root["values"] = values; + + // 将json对象转成字符串infoStr + std::string infoStr = root.Dump(); + + // 对*info赋值序列化字符串地址。 + *info = strdup(infoStr.data()); + + return true; +} + +napi_value ImageEdit::RegisterCustomBrightness() +{ + napi_value result = nullptr; + // 自定义算子能力信息 + OH_EffectFilterInfo *effectInfo = OH_EffectFilterInfo_Create(); + OH_EffectFilterInfo_SetFilterName(effectInfo, "CustomBrightness"); + ImageEffect_BufferType bufferType = ImageEffect_BufferType::EFFECT_BUFFER_TYPE_PIXEL; + OH_EffectFilterInfo_SetSupportedBufferTypes(effectInfo, 1, &bufferType); + ImageEffect_Format format = ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888; + OH_EffectFilterInfo_SetSupportedFormats(effectInfo, 1, &format); + // 自定义算子实现接口 + delegateBrightness = { + .setValue = + [](OH_EffectFilter *filter, const char *key, const ImageEffect_Any *value) { + // 参数校验,校验成功时返回true,否则返回false。 + if (value->dataValue.floatValue >= DATA_VALUE_MIN && value->dataValue.floatValue <= DATA_VALUE_MAX) { + return true; + } else { + return false; + } + }, + .render = [](OH_EffectFilter *filter, OH_EffectBufferInfo *info, + OH_EffectFilterDelegate_PushData pushData) { return RenderBrightness(filter, info, pushData); }, + .save = SaveFilterBrightness, + .restore = [](const char *info) -> OH_EffectFilter * { + // 创建 OH_EffectFilter 实例,其中"CustomBrightness"为自定义滤镜的滤镜名。 + OH_EffectFilter *filter = OH_EffectFilter_Create("CustomBrightness"); + // 解析json字符串info获取key和value。 + std::map parsedJson = Json::Parse(info); + if (parsedJson.find("values") != parsedJson.end()) { + std::string valuesStr = parsedJson["values"]; + std::map valuesJson = Json::Parse(valuesStr); + if (valuesJson.find("brightness") != valuesJson.end()) { + float brightness = + std::stof(valuesJson["brightness"].substr(1, valuesJson["brightness"].size() - 2)); + ImageEffect_Any value; + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT; + value.dataValue.floatValue = brightness; + // 设置滤镜参数, value为info中按json解析出来的参数。 + LOG_E("brightness value: %{public}f ", value.dataValue.floatValue); + ImageEffect_ErrorCode errorCode = OH_EffectFilter_SetValue(filter, "brightness", &value); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, nullptr, + "OH_EffectFilter_SetValue fail!"); + } + } + return filter; + }}; + + ImageEffect_ErrorCode errorCode = OH_EffectFilter_Register(effectInfo, &delegateBrightness); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_EffectFilter_Register fail! errorCode = %{public}d", errorCode); + return result; +} + +napi_value ImageEdit::RegisterCustomCrop() +{ + napi_value result = nullptr; + // 自定义算子能力信息 + OH_EffectFilterInfo *effectInfo = OH_EffectFilterInfo_Create(); + OH_EffectFilterInfo_SetFilterName(effectInfo, "CustomCrop"); + ImageEffect_BufferType bufferType = ImageEffect_BufferType::EFFECT_BUFFER_TYPE_PIXEL; + OH_EffectFilterInfo_SetSupportedBufferTypes(effectInfo, 1, &bufferType); + ImageEffect_Format format = ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888; + OH_EffectFilterInfo_SetSupportedFormats(effectInfo, 1, &format); + // 自定义算子实现接口 + delegateCrop = { + .setValue = + [](OH_EffectFilter *filter, const char *key, const ImageEffect_Any *value) { + // 参数校验,校验成功时返回true,否则返回false。 + if (value->dataValue.floatValue >= DATA_VALUE_MIN && value->dataValue.floatValue <= DATA_VALUE_MAX) { + return true; + } else { + return false; + } + }, + .render = [](OH_EffectFilter *filter, OH_EffectBufferInfo *info, + OH_EffectFilterDelegate_PushData pushData) { return RenderCrop(filter, info, pushData); }, + .save = SaveFilterCrop, + .restore = [](const char *info) -> OH_EffectFilter * { + // 创建 OH_EffectFilter 实例,其中"CustomBrightness"为自定义滤镜的滤镜名。 + OH_EffectFilter *filter = OH_EffectFilter_Create("CustomCrop"); + // 解析json字符串info获取key和value。 + std::map parsedJson = Json::Parse(info); + if (parsedJson.find("values") != parsedJson.end()) { + std::string valuesStr = parsedJson["values"]; + std::map valuesJson = Json::Parse(valuesStr); + if (valuesJson.find("crop") != valuesJson.end()) { + float crop = std::stof(valuesJson["crop"].substr(1, valuesJson["crop"].size() - 2)); + ImageEffect_Any value; + value.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT; + value.dataValue.floatValue = crop; + // 设置滤镜参数, value为info中按json解析出来的参数。 + LOG_E("crop value: %{public}f ", value.dataValue.floatValue); + ImageEffect_ErrorCode errorCode = OH_EffectFilter_SetValue(filter, "crop", &value); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, nullptr, + "OH_EffectFilter_SetValue fail!"); + } + } + return filter; + }}; + + ImageEffect_ErrorCode errorCode = OH_EffectFilter_Register(effectInfo, &delegateCrop); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_EffectFilter_Register fail! errorCode = %{public}d", errorCode); + return result; +} + +napi_value ImageEdit::LookupFilters(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_undefined(env, &result); + size_t argc = EXPECTED_ARGS_ONE; + napi_value args[EXPECTED_ARGS_ONE] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + const char *key = CommonUtils::GetStringArgument(env, args[EXPECTED_ARGS_ZERO]); + + ImageEffect_FilterNames *filterNames = OH_EffectFilter_LookupFilters(key); + CHECK_AND_RETURN_RET_LOG(filterNames != nullptr, result, "OH_EffectFilter_LookupFilters fail!"); + + std::string res = "size: " + std::to_string(filterNames->size) + std::string(", name: "); + for (int i = 0; i < filterNames->size; i++) { + res += filterNames->nameList[i]; + if (i < filterNames->size - 1) { + res += " | "; + } + } + status = napi_create_string_utf8(env, res.c_str(), res.size(), &result); + // 释放FilterNames虚拟内存资源。 + OH_EffectFilter_ReleaseFilterNames(); + return result; +} + +napi_value ImageEdit::getSurfaceId(napi_env env, napi_callback_info info) +{ + napi_value result = nullptr; + napi_get_undefined(env, &result); + + size_t argc = EXPECTED_ARGS_ONE; + napi_value args[EXPECTED_ARGS_ONE] = {nullptr}; + napi_status status = napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); + CHECK_AND_RETURN_RET_LOG(status == napi_ok, result, "napi_get_cb_info fail! status = %{public}d", status); + + std::string surfaceId = CommonUtils::GetStringArgument(env, args[EXPECTED_ARGS_ZERO]); + // 根据SurfaceId创建NativeWindow,注意创建出来的NativeWindow在使用结束后需要主动调用OH_NativeWindow_DestoryNativeWindow进行释放。 + uint64_t iSurfaceId; + std::istrstream iss(surfaceId.c_str()); + iss >> iSurfaceId; + LOG_I("iSurfaceId %{public}llu", iSurfaceId); + OHNativeWindow *outputNativeWindow = nullptr; + int32_t res = OH_NativeWindow_CreateNativeWindowFromSurfaceId(iSurfaceId, &outputNativeWindow); + CHECK_AND_RETURN_RET_LOG(res == 0, result, "OH_NativeWindow_CreateNativeWindowFromSurfaceId fail!"); + + OH_ImageEffect *imageEffect = OH_ImageEffect_Create("imageEdit"); + CHECK_AND_RETURN_RET_LOG(imageEffect != nullptr, result, "OH_ImageEffect_Create fail!"); + + ImageEffect_Any runningType{.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_INT32, .dataValue.int32Value = 1}; + ImageEffect_ErrorCode errorCode = OH_ImageEffect_Configure(imageEffect, "runningType", &runningType); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_Configure fail!"); + + // 设置输出显示的Surface。 + errorCode = OH_ImageEffect_SetOutputSurface(imageEffect, outputNativeWindow); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_SetOutputSurface fail!"); + + // 获取输入的Surface。注意获取的inputNativeWindow在使用结束后需要主动调用OH_NativeWindow_DestoryNativeWindow进行释放。 + OHNativeWindow *inputNativeWindow = nullptr; + errorCode = OH_ImageEffect_GetInputSurface(imageEffect, &inputNativeWindow); + CHECK_AND_RETURN_RET_LOG(errorCode == ImageEffect_ErrorCode::EFFECT_SUCCESS, result, + "OH_ImageEffect_GetInputSurface fail!"); + + ImageEdit::imageEffect_ = imageEffect; + + // 从获取到输入的NativeWindow中获取SurfaceId。 + uint64_t inputSurfaceId = 0; + res = OH_NativeWindow_GetSurfaceId(inputNativeWindow, &inputSurfaceId); + CHECK_AND_RETURN_RET_LOG(res == 0, result, "OH_NativeWindow_GetSurfaceId fail!"); + + OH_NativeWindow_DestroyNativeWindow(outputNativeWindow); + OH_NativeWindow_DestroyNativeWindow(inputNativeWindow); + + std::string inputSurfaceIdStr = std::to_string(inputSurfaceId); + + status = napi_create_string_utf8(env, inputSurfaceIdStr.c_str(), inputSurfaceIdStr.length(), &result); + CHECK_AND_RETURN_RET_LOG(status == napi_status::napi_ok, result, "napi_create_string_utf8 fail!"); + return result; +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.h b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.h new file mode 100644 index 0000000000000000000000000000000000000000..ac307b69480cc92b21c1e286b0b2d1710073f1d2 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/backend/image_edit.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2025 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 IMAGE_EDIT_H +#define IMAGE_EDIT_H + +#include "napi/native_api.h" +#include + +class ImageEdit { +public: + ~ImageEdit(); + + static napi_value PixelMapFilterStart(napi_env env, napi_callback_info info); + + static napi_value NativeBufferFilterStart(napi_env env, napi_callback_info info); + + static napi_value URIFilterStart(napi_env env, napi_callback_info info); + + static napi_value SurfaceFilterStart(napi_env env, napi_callback_info info); + + static napi_value SurfaceFilterStop(napi_env env, napi_callback_info info); + + static napi_value LookupFilterInfo(napi_env env, napi_callback_info info); + + static napi_value LookupFilters(napi_env env, napi_callback_info info); + + static napi_value RegisterCustomBrightness(); + + static napi_value RegisterCustomCrop(); + + static napi_value getSurfaceId(napi_env env, napi_callback_info info); + +private: + static OH_ImageEffect *imageEffect_; +}; + +#endif //IMAGE_EDIT_H \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/logging.h b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/logging.h new file mode 100644 index 0000000000000000000000000000000000000000..7f4d83dbf144033a5ac84a58339835a01e24a39c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/logging.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2024 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 IMAGE_EFFECT_LOGGING_H +#define IMAGE_EFFECT_LOGGING_H + +#include + +#define EFFECT_LOG_TAG "ImageEffectSample" + +#define LOG_I(...) OH_LOG_Print(LOG_APP, LOG_INFO, 0xFF00, EFFECT_LOG_TAG, __VA_ARGS__) +#define LOG_W(...) OH_LOG_Print(LOG_APP, LOG_WARN, 0xFF00, EFFECT_LOG_TAG, __VA_ARGS__) +#define LOG_E(...) OH_LOG_Print(LOG_APP, LOG_ERROR, 0xFF00, EFFECT_LOG_TAG, __VA_ARGS__) + +#endif // IMAGE_EFFECT_LOGGING_H \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/napi_init.cpp b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/napi_init.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d71a307d809aae8c92a78df3b6de87dd24f30729 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/napi_init.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2025 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 "napi/native_api.h" +#include "image_edit.h" + +EXTERN_C_START +void RegisterCustomBrightness() { ImageEdit::RegisterCustomBrightness(); } +void RegisterCustomCrop() { ImageEdit::RegisterCustomCrop(); } + +static napi_value Init(napi_env env, napi_value exports) +{ + RegisterCustomBrightness(); + RegisterCustomCrop(); + + napi_property_descriptor desc[] = { + {"PixelMapFilterStart", nullptr, ImageEdit::PixelMapFilterStart, nullptr, nullptr, nullptr, + napi_default, nullptr}, + {"NativeBufferFilterStart", nullptr, ImageEdit::NativeBufferFilterStart, nullptr, nullptr, nullptr, + napi_default, nullptr}, + {"URIFilterStart", nullptr, ImageEdit::URIFilterStart, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"SurfaceFilterStart", nullptr, ImageEdit::SurfaceFilterStart, nullptr, nullptr, nullptr, + napi_default, nullptr}, + {"SurfaceFilterStop", nullptr, ImageEdit::SurfaceFilterStop, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"lookupFilterInfo", nullptr, ImageEdit::LookupFilterInfo, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"lookupFilters", nullptr, ImageEdit::LookupFilters, nullptr, nullptr, nullptr, napi_default, nullptr}, + {"getSurfaceId", nullptr, ImageEdit::getSurfaceId, nullptr, nullptr, nullptr, napi_default, nullptr}, + }; + napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); + return exports; +} +EXTERN_C_END + +static napi_module demoModule = { + .nm_version = 1, + .nm_flags = 0, + .nm_filename = nullptr, + .nm_register_func = Init, + .nm_modname = "entry", + .nm_priv = ((void *)0), + .reserved = {0}, +}; + +extern "C" __attribute__((constructor)) void RegisterEntryModule(void) { napi_module_register(&demoModule); } \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/index.d.ts b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/index.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..1ed00cddb9aeab2c5ff6b481bf244e2e9af61846 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/index.d.ts @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 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. + */ + +declare namespace ImageEffect { + const PixelMapFilterStart: (path: string, filterOptions: Array>) => boolean; + + const NativeBufferFilterStart: (filterOptions: Array>) => boolean; + + const URIFilterStart: (path: string, filterOptions: Array>) => boolean; + + const SurfaceFilterStart: (filterOptions: Array>) => boolean; + + const SurfaceFilterStop: () => boolean; + + const lookupFilterInfo: (name: String) => string; + + const lookupFilters: (key: String) => string; + + const getSurfaceId: (surfaceId: string) => string; +} + +export default ImageEffect; \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/oh-package.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..4b632d9ae4da85dcccb452b7ed9db62023c89901 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/types/libentry/oh-package.json5 @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "name": "libentry.so", + "types": "./index.d.ts", + "version": " ", + "description": "Please describe the basic information." +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.cpp b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b6c26226a7989dd29a3c9d2dc7454f8674b19084 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 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 "common_utils.h" +#include +#include "logging.h" + +const std::map formatToStr_ = { + {ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888, "RGBA8888"}, + {ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV12, "YUVNV12"}, + {ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV21, "YUVNV21"}, +}; + +const std::map bufferTypeToStr_ = { + {ImageEffect_BufferType::EFFECT_BUFFER_TYPE_PIXEL, "Pixel"}, + {ImageEffect_BufferType::EFFECT_BUFFER_TYPE_TEXTURE, "Texture"}, +}; + +const char *CommonUtils::GetStringArgument(napi_env env, napi_value value) +{ + char *buffer = nullptr; + size_t bufferLength = 0; + napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &bufferLength); + if (status == napi_ok && bufferLength > 0) { + buffer = reinterpret_cast(malloc((bufferLength + 1) * sizeof(char))); + if (buffer == nullptr) { + LOG_E("No memory"); + return nullptr; + } + + status = napi_get_value_string_utf8(env, value, buffer, bufferLength + 1, &bufferLength); + if (status != napi_ok) { + LOG_E("napi_get_value_string_utf8 fail"); + free(buffer); + buffer = nullptr; + } + } + return buffer; +} + +const char *GetBufferType(ImageEffect_BufferType &bufferType) +{ + auto it = bufferTypeToStr_.find(bufferType); + if (it == bufferTypeToStr_.end()) { + return "unknown"; + } + + return it->second; +} + +const char *GetFormat(ImageEffect_Format &ohFormat) +{ + auto it = formatToStr_.find(ohFormat); + if (it == formatToStr_.end()) { + return "unknown"; + } + + return it->second; +} + +std::string CommonUtils::EffectInfoToString(OH_EffectFilterInfo *info) +{ + std::string result = ""; + + char *name = nullptr; + OH_EffectFilterInfo_GetFilterName(info, &name); + result += "name:" + std::string(name) + ", "; + + uint32_t supportedBufferTypesSize = 0; + ImageEffect_BufferType *bufferTypeArray = nullptr; + OH_EffectFilterInfo_GetSupportedBufferTypes(info, &supportedBufferTypesSize, &bufferTypeArray); + result += "supportedBufferType: {"; + for (uint32_t i = 0;i < supportedBufferTypesSize; ++i) { + ImageEffect_BufferType bufferType = bufferTypeArray[i]; + result += GetBufferType(bufferType) + std::string(" "); + } + result += "}"; + + uint32_t supportedFormatsSize = 0; + ImageEffect_Format *formatArray = nullptr; + OH_EffectFilterInfo_GetSupportedFormats(info, &supportedFormatsSize, &formatArray); + result += "supportedFormat: {"; + for (uint32_t i = 0;i < supportedFormatsSize; ++i) { + ImageEffect_Format ohFormat = formatArray[i]; + result += GetFormat(ohFormat) + std::string(" "); + } + result += "}"; + + return result; +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.h b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..daa477206e3d2f8c4cecc9cbdd6d596e84012171 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/common_utils.h @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025 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 COMMON_UTILS_H +#define COMMON_UTILS_H + +#include +#include "napi/native_api.h" + +#include "logging.h" +#include + +#define CHECK_AND_RETURN_RET_LOG(cond, ret, fmt, ...) \ + do { \ + if (!(cond)) { \ + LOG_E(fmt, ##__VA_ARGS__); \ + return ret; \ + } \ + } while (0) + +#define CHECK_AND_RETURN_LOG(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + LOG_E(fmt, ##__VA_ARGS__); \ + return; \ + } \ + } while (0) + +#define CHECK_AND_NO_RETURN_LOG(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + LOG_E(fmt, ##__VA_ARGS__); \ + } \ + } while (0) + +#define CHECK_AND_RETURN_NO_RET(cond, fmt, ...) \ + do { \ + if (!(cond)) { \ + LOG_E(fmt, ##__VA_ARGS__); \ + return; \ + } \ + } while (0) + +class CommonUtils { +public: + static const char *GetStringArgument(napi_env env, napi_value value); + + static std::string EffectInfoToString(OH_EffectFilterInfo *info); +}; + +#endif // COMMON_UTILS_H \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.cpp b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c4e125e0cbdec0e42d9222776387e79f5c818896 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025 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 "json_utils.h" +#include +#include +#include + +// 表示小数点后保留的位数 +constexpr int PRECISION = 2; +// 构造函数 +Json::JsonValueProxy::JsonValueProxy(Json& parent, const std::string& key) : parent_(parent), key_(key) {} + +// 设置浮点值 +void Json::JsonValueProxy::operator=(const float value) +{ + parent_.Set(key_, value); +} + +// 设置const char*值 +void Json::JsonValueProxy::operator=(const char* value) +{ + parent_.Set(key_, value); +} + +// 设置字符串值 +void Json::JsonValueProxy::operator=(const std::string& value) +{ + parent_.Set(key_, value); +} + +// 设置json值 +void Json::JsonValueProxy::operator=(const Json& value) +{ + parent_.Set(key_, value); +} + +// 重载operator[]以返回代理对象 +Json::JsonValueProxy Json::operator[](const std::string& key) +{ + return JsonValueProxy(*this, key); +} + +// 设置浮点值 +void Json::Set(const std::string& key, float value) +{ + std::ostringstream oss; + // 将浮点数 value 转换为字符串时,保留小数点后两位 + oss << std::fixed << std::setprecision(PRECISION) << value; + values_[key] = oss.str(); +} + +// 设置字符串值 +void Json::Set(const std::string& key, const std::string& value) +{ + values_[key] = "\"" + value + "\""; +} + +// 设置const char*值 +void Json::Set(const std::string& key, const char* value) +{ + values_[key] = "\"" + std::string(value) + "\""; +} + +// 设置json值 +void Json::Set(const std::string& key, const Json& value) +{ + values_[key] = value.Dump(); +} + +// 将json对象转成字符串 +std::string Json::Dump() const +{ + std::ostringstream oss; + oss << "{"; + bool first = true; + for (const auto& pair : values_) { + if (!first) { + oss << ", "; + } + oss << "\"" << pair.first << "\": " << pair.second; + first = false; + } + oss << "}"; + return oss.str(); +} + +// 解析json字符串获取json格式的key和value +std::map Json::Parse(const std::string& jsonStr) +{ + std::map result; + std::string key; + std::string value; + bool inKey = false; + bool inValue = false; + for (size_t i = 0; i < jsonStr.size(); ++i) { + char c = jsonStr[i]; + if (c == '"') { + if (inKey) { + inKey = false; + } else if (inValue) { + inValue = false; + result[key] = value; + key.clear(); + value.clear(); + } else { + inKey = true; + } + } else if (c == ':') { + inKey = false; + inValue = true; + } else if (c == ',') { + inValue = false; + } else if (inKey) { + key += c; + } else if (inValue) { + value += c; + } + } + return result; +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.h b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..0b981ebc14bf433229bfe41c2a8761dd9ff00e92 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/json_utils.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025 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 JSON_UTILS_H +#define JSON_UTILS_H + +#include +#include + +class Json { +public: + // 代理类,用于设置值 + class JsonValueProxy { + public: + JsonValueProxy(Json& parent, const std::string& key); + void operator=(const float value); + void operator=(const char* value); + void operator=(const std::string& value); + void operator=(const Json& value); + + private: + Json& parent_; + std::string key_; + }; + + // 重载operator[]以返回代理对象 + JsonValueProxy operator[](const std::string& key); + + // 设置浮点值 + void Set(const std::string& key, float value); + // 设置字符串值 + void Set(const std::string& key, const std::string& value); + // 设置const char*值 + void Set(const std::string& key, const char* value); + // 设置json值 + void Set(const std::string& key, const Json& value); + + // 将json对象转成字符串 + std::string Dump() const; + + // 解析json字符串获取json格式的key和value + static std::map Parse(const std::string& jsonStr); + +private: + std::map values_; +}; + +#endif // JSON_UTILS_H diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.cpp b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9457ef85c625a2d05ead7b938694ced07fe3f80f --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025 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 "pixelmap_helper.h" +#include "common_utils.h" +#include +#include +#include +#include + +constexpr int EXPECTED_ONE_HUNDRED = 100; + +std::shared_ptr CreateDecodingOptions() +{ + OH_DecodingOptions *options = nullptr; + Image_ErrorCode errorCode = OH_DecodingOptions_Create(&options); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, nullptr, + "OH_DecodingOptions_Create fail! errorCode=%{public}d", errorCode); + + std::shared_ptr optionsPtr(options, [](OH_DecodingOptions *options) { + OH_DecodingOptions_Release(options); + }); + + return optionsPtr; +} + +std::shared_ptr PixelMapHelper::Decode(std::string &pathName) +{ + int fd = open(pathName.c_str(), O_RDWR); + CHECK_AND_RETURN_RET_LOG(fd != -1, nullptr, "Open path fail! pathName=%{public}s", pathName.c_str()); + + std::shared_ptr fdPtr(&fd, [](int *fd) { close(*fd); }); + OH_ImageSourceNative *imageSource = nullptr; + Image_ErrorCode errorCode = OH_ImageSourceNative_CreateFromFd(*fdPtr.get(), &imageSource); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, nullptr, + "OH_ImageSourceNative_CreateFromFd fail! errorCode=%{public}d", errorCode); + + std::shared_ptr imageSourcePtr(imageSource, [](OH_ImageSourceNative *imageSource) { + OH_ImageSourceNative_Release(imageSource); + }); + + std::shared_ptr optionsPtr = CreateDecodingOptions(); + CHECK_AND_RETURN_RET_LOG(optionsPtr != nullptr, nullptr, "CreateDecodingOptions fail!"); + + OH_PixelmapNative *pixelmap = nullptr; + errorCode = OH_ImageSourceNative_CreatePixelmap(imageSourcePtr.get(), optionsPtr.get(), &pixelmap); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, nullptr, + "OH_ImageSourceNative_CreatePixelmap fail! errorCode=%{public}d", errorCode); + + std::shared_ptr pixelmapPtr(pixelmap, [](OH_PixelmapNative *pixelmap) { + OH_PixelmapNative_Release(pixelmap); + }); + + return pixelmapPtr; +} + +std::shared_ptr CreatePackingOptions() +{ + OH_PackingOptions *options = nullptr; + Image_ErrorCode errorCode = OH_PackingOptions_Create(&options); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, nullptr, + "OH_PackingOptions_Create fail! errorCode=%{public}d", errorCode); + + std::shared_ptr optionsPtr(options, [](OH_PackingOptions *options) { + OH_PackingOptions_Release(options); + }); + + OH_PackingOptions_SetQuality(optionsPtr.get(), EXPECTED_ONE_HUNDRED); + Image_MimeType format = { .data = const_cast(MIME_TYPE_JPEG), .size = strlen(MIME_TYPE_JPEG) }; + OH_PackingOptions_SetMimeType(optionsPtr.get(), &format); + return optionsPtr; +} + +bool PixelMapHelper::Encode(OH_PixelmapNative *pixelmap, std::string &path) +{ + OH_ImagePackerNative *imagePacker = nullptr; + Image_ErrorCode errorCode = OH_ImagePackerNative_Create(&imagePacker); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, false, + "OH_ImagePackerNative_Create fail! errorCode=%{public}d", errorCode); + + std::shared_ptr imagePackerPtr(imagePacker, [](OH_ImagePackerNative *imagePacker) { + OH_ImagePackerNative_Release(imagePacker); + }); + + std::shared_ptr optionsPtr = CreatePackingOptions(); + CHECK_AND_RETURN_RET_LOG(optionsPtr != nullptr, false, "CreatePackingOptions fail!"); + + int fd = open(path.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + CHECK_AND_RETURN_RET_LOG(fd != -1, false, "Open path fail! path=%{public}s", path.c_str()); + errorCode = OH_ImagePackerNative_PackToFileFromPixelmap(imagePackerPtr.get(), optionsPtr.get(), pixelmap, fd); + close(fd); + CHECK_AND_RETURN_RET_LOG(errorCode == Image_ErrorCode::IMAGE_SUCCESS, false, + "OH_ImagePackerNative_PackToFileFromPixelmap fail! errorCode=%{public}d", errorCode); + + return true; +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.h b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.h new file mode 100644 index 0000000000000000000000000000000000000000..e5b74ab27b629dfabfc93742a15da48d183de3d4 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/cpp/utils/pixelmap_helper.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2025 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 IMAGEEFFECT_PIXELMAP_HELPER_H +#define IMAGEEFFECT_PIXELMAP_HELPER_H + +#include +#include +#include + +#include "napi/native_api.h" + +class PixelMapHelper { +public: + static std::shared_ptr Decode(std::string &pathName); + static bool Encode(OH_PixelmapNative *pixelmap, std::string &pathName); + + static std::shared_ptr ConvertPixelmap(NativePixelMap *nativePixelMap); + static napi_value ConvertPixelmap(OH_PixelmapNative *pixelmapNative, napi_env env); +}; + +#endif // IMAGEEFFECT_PIXELMAP_HELPER_H diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/entryability/EntryAbility.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..462b5f8efe4bca6f2b64b04d9b14491818d7083c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2025 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. + */ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { checkAndRequestPermissions, permissions } from '../utils/PermissionUtils'; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/ImageEditPage', (err, data) => { + checkAndRequestPermissions(permissions, this.context); + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground'); + } +}; diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/pages/ImageEditPage.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/pages/ImageEditPage.ets new file mode 100644 index 0000000000000000000000000000000000000000..9fc7adca86193321119a465ce3e90f89354ad67c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/pages/ImageEditPage.ets @@ -0,0 +1,788 @@ +/* + * Copyright (c) 2025 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. + */ + +import imageEffect from 'libentry.so'; +import image from '@ohos.multimedia.image'; +import { ImageUtils } from '../utils/ImageUtils'; +import { fileUri } from '@kit.CoreFileKit'; +import fs from '@ohos.file.fs'; +import { camera } from '@kit.CameraKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { promptAction } from '@kit.ArkUI'; + +const VALUE_MIN_MINUS = -100; +const VALUE_MIN = 1; +const VALUE_MAX = 100; + +@Entry +@Component +struct ImageEditPage { + settingBtn: Resource = $r('app.media.ic_public_settings'); + @Provide displayPixelMap: image.PixelMap | undefined = undefined; + @Provide displayPixelMapUri: image.PixelMap | undefined = undefined; + @State brightnessSetValue: number = 100; + @State brightnessSelect: boolean = false; + @State contrastSetValue: number = 0; + @State contrastSelect: boolean = false; + @State cropSetValue: number = 100; + @State cropSelect: boolean = false; + @State customBrightnessSetValue: number = 0; + @State customBrightnessSelect: boolean = false; + @State customCropSetValue: number = 50; + @State customCropSelect: boolean = false; + @State filterOptions: Array> = []; + @State filterInfo: string = ''; + mXComponentController: XComponentController = new XComponentController(); + @State mSurfaceId: string = ''; + @State inputType: 'PixelmapNative' | 'NativeBuffer' | 'URI' | 'NativeWindow' = 'PixelmapNative'; + @State index: number = 0; + cameraMgr: camera.CameraManager | undefined = undefined; + dialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogExample({ + cancel: this.onCancel, + confirm: this.onAccept, + filterOptions: $filterOptions, + brightnessSetValue: $brightnessSetValue, + brightnessSelect: $brightnessSelect, + contrastSetValue: $contrastSetValue, + contrastSelect: $contrastSelect, + cropSetValue: $cropSetValue, + cropSelect: $cropSelect, + customBrightnessSetValue: $customBrightnessSetValue, + customBrightnessSelect: $customBrightnessSelect, + customCropSetValue: $customCropSetValue, + customCropSelect: $customCropSelect, + filterInfo: $filterInfo, + inputType: $inputType, + index: $index, + }), + cancel: this.existApp, + autoCancel: true, + }); + private tag: string = '[Sample_ImageEdit]'; + + aboutToAppear(): void { + console.info(`${this.tag} aboutToAppear called`); + ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')).then(pixelMap => { + this.displayPixelMap = pixelMap; + }) + ImageUtils.getInstance().getPixelMap($r('app.media.ic_700x700')).then(pixelMap => { + this.displayPixelMapUri = pixelMap; + }) + console.info(`${this.tag} aboutToAppear done`); + } + + aboutToDisappear(): void { + console.info(`${this.tag} aboutToDisappear called`); + console.info(`${this.tag} aboutToDisappear done`); + } + + build() { + Column() { + Row() { + Row() { + Text($r('app.string.image_edit')) + .fontColor(Color.White) + .fontSize('app.float.title_font_size') + .margin({ left: $r('app.float.home_page_title_margin') }) + } + + Blank() + + Row() { + Button() { + Image(this.settingBtn) + .width($r('app.float.title_image_width')) + .height($r('app.float.title_image_height')) + .id('btn_setting') + } + .height('100%') + .type(ButtonType.Normal) + .backgroundColor(Color.Transparent) + .onClick(() => { + if (this.dialogController != undefined) { + console.info(`${this.tag} to open setting dialog`); + this.dialogController.open(); + } + }) + } + } + .width('100%') + .height($r('app.float.home_page_title_height')) + .margin({ + top: $r('app.float.home_page_title_margin'), + left: $r('app.float.home_page_title_margin'), + right: $r('app.float.home_page_title_margin') + }) + + Column() { + if (this.inputType == 'PixelmapNative') { + Image(this.displayPixelMap) + .objectFit(ImageFit.None) + } + if (this.inputType == 'NativeBuffer') { + Column() { + Text($r('app.string.native_buffer_description')) + .fontSize($r('app.float.title_size')) + } + .backgroundColor(Color.White) + } + if (this.inputType == 'URI') { + Image(this.displayPixelMapUri) + .objectFit(ImageFit.None) + } + if (this.inputType == 'NativeWindow') { + XComponent({ + id: 'xcomponentId', + type: XComponentType.SURFACE, + controller: this.mXComponentController, + libraryname: 'entry' + }) + .onLoad(async () => { + // 获取XComponent的SurfaceId。 + this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId(); + // 调用native接口获取输入SurfaceId。 + this.mSurfaceId = imageEffect.getSurfaceId(this.mSurfaceId); + // 调用相机接口启动预览,将获取到的输入SurfaceId传递给相机框架 + this.startPreview(this.mSurfaceId); + }) + .width('100%') + .height('100%') + } + } + .clip(true) + .justifyContent(FlexAlign.Center) + .width('100%') + .height('85%') + + Row() { + Button('Reset') + .id('btn_reset') + .onClick(() => { + this.pixelInit(); + }) + .width($r('app.float.button_width')) + + Button('Apply') + .id('btn_apply') + .onClick(() => { + this.doApply(); + }) + .width($r('app.float.button_width')) + } + .justifyContent(FlexAlign.SpaceEvenly) + .width('100%') + .height('6%') + .margin({ top: $r('app.float.home_page_title_margin') }) + } + .width('100%') + .height('100%') + .backgroundColor(Color.Grey) + } + + onCancel() { + console.info(`Callback when the cancel button is clicked`); + } + + onAccept() { + console.info(`Callback when the confirm button is clicked`); + } + + existApp() { + console.info(`click the callback in the blank area`); + } + + private async startPreview(surfaceId: string) { + let cameraManager = camera.getCameraManager(getContext(this)); + let cameras = this.getSupportedCameras(cameraManager); + let modes = this.getSupportedSceneModes(cameraManager, cameras[0]); + let cameraOutputCapability = cameraManager.getSupportedOutputCapability(cameras[0], modes[0]); + let previewOutput = this.getPreviewOutput(cameraManager, cameraOutputCapability, surfaceId); + if (previewOutput != undefined) { + await this.startPreviewOutput(cameraManager, previewOutput); + } + } + + private getSupportedCameras(cameraManager: camera.CameraManager): camera.CameraDevice[] { + let cameras: camera.CameraDevice[] = []; + try { + cameras = cameraManager.getSupportedCameras(); + } catch (error) { + let err = error as BusinessError; + console.error(`The getSupportedCameras call failed. error code: ${err.code}`); + } + return cameras; + } + + private getSupportedSceneModes(cameraManager: camera.CameraManager, + camera: camera.CameraDevice): camera.SceneMode[] { + let modes: camera.SceneMode[] = []; + try { + modes = cameraManager.getSupportedSceneModes(camera); + } catch (error) { + let err = error as BusinessError; + console.error(`The getSupportedSceneModes call failed. error code: ${err.code}`); + } + return modes; + } + + private getPreviewOutput(cameraManager: camera.CameraManager, cameraOutputCapability: camera.CameraOutputCapability, + surfaceId: string): camera.PreviewOutput | undefined { + let previewProfilesArray: camera.Profile[] = cameraOutputCapability.previewProfiles; + let previewOutput: camera.PreviewOutput | undefined = undefined; + try { + previewOutput = cameraManager.createPreviewOutput(previewProfilesArray[0], surfaceId); + } catch (error) { + let err = error as BusinessError; + console.error('Failed to create the PreviewOutput instance. error code: ' + err.code); + } + return previewOutput; + } + + private async startPreviewOutput(cameraManager: camera.CameraManager, + previewOutput: camera.PreviewOutput): Promise { + let cameraArray: camera.CameraDevice[] = []; + cameraArray = cameraManager.getSupportedCameras(); + if (cameraArray.length == 0) { + console.error('no camera.'); + return; + } + // 获取支持的模式类型 + let sceneModes: camera.SceneMode[] = cameraManager.getSupportedSceneModes(cameraArray[0]); + let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0; + if (!isSupportPhotoMode) { + console.error('photo mode not support'); + return; + } + let cameraInput: camera.CameraInput | undefined = undefined; + cameraInput = cameraManager.createCameraInput(cameraArray[0]); + if (cameraInput === undefined) { + console.error('cameraInput is undefined'); + return; + } + // 打开相机 + await cameraInput.open(); + let session: camera.PhotoSession = + cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession; + session.beginConfig(); + session.addInput(cameraInput); + session.addOutput(previewOutput); + await session.commitConfig(); + await session.start(); + } + + private async doSavePixel(): Promise { + let pixelMap: image.PixelMap | undefined = undefined; + if (this.inputType == 'PixelmapNative') { + pixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')); + } else { + pixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_700x700')); + } + const imagePackerApi = image.createImagePacker(); + const packOption: image.PackingOption = { + format: 'image/jpeg', + quality: 100 + }; + let filePath = ''; + if (this.inputType == 'PixelmapNative') { + filePath = getContext().filesDir + '/ic_1080x1920.jpg'; + } else { + filePath = getContext().filesDir + '/ic_700x700.jpg'; + } + console.info(`savePixel to ${filePath}`); + let uri = fileUri.getUriFromPath(filePath); + let imageData = await imagePackerApi.packing(pixelMap, packOption); + let file = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + let writeLen = fs.writeSync(file.fd, imageData); + fs.closeSync(file); + console.info(`write data to file succeed and size is ${writeLen}`); + } + + private confirmInfo() { + this.filterOptions = []; + if (this.brightnessSelect) { + let brightnessArray: (string | number)[] = []; + brightnessArray.push('Brightness', this.brightnessSetValue); + this.filterOptions.push(brightnessArray); + } + if (this.contrastSelect) { + let contrastArray: (string | number)[] = []; + contrastArray.push('Contrast', this.contrastSetValue); + this.filterOptions.push(contrastArray); + } + if (this.cropSelect && this.inputType != 'NativeBuffer' && this.inputType != 'NativeWindow') { + let cropArray: (string | number)[] = []; + cropArray.push('Crop', this.cropSetValue); + this.filterOptions.push(cropArray); + } + if (this.customBrightnessSelect) { + let customBrightnessArray: (string | number)[] = []; + customBrightnessArray.push('CustomBrightness', this.customBrightnessSetValue); + this.filterOptions.push(customBrightnessArray); + } + if (this.customCropSelect && this.inputType != 'NativeWindow') { + let customCropArray: (string | number)[] = []; + customCropArray.push('CustomCrop', this.customCropSetValue); + this.filterOptions.push(customCropArray); + } + } + + private async doApply(): Promise { + this.confirmInfo(); + if (this.filterOptions.toString() != '') { + let filePath: string = ''; + let result: boolean = true; + switch (this.inputType) { + case 'PixelmapNative': + await this.doSavePixel(); + filePath = getContext().filesDir + '/ic_1080x1920.jpg'; + result = imageEffect.PixelMapFilterStart(filePath, [...this.filterOptions]); + this.displayPixelMap = await ImageUtils.getInstance().getPixelMapByFilePath(filePath); + promptAction.showToast({ message: `apply PixelMapNative ${result == true ? 'succeed' : 'fail'}!` }); + break; + case 'NativeBuffer': + result = imageEffect.NativeBufferFilterStart([...this.filterOptions]); + promptAction.showToast({ message: `apply NativeBuffer ${result == true ? 'succeed' : 'fail'}!` }); + break; + case 'URI': + await this.doSavePixel(); + filePath = getContext().filesDir + '/ic_700x700.jpg'; + result = imageEffect.URIFilterStart(filePath, [...this.filterOptions]); + this.displayPixelMapUri = await ImageUtils.getInstance().getPixelMapByFilePath(filePath); + promptAction.showToast({ message: `apply URI ${result == true ? 'succeed' : 'fail'}!` }); + break; + case 'NativeWindow': + result = imageEffect.SurfaceFilterStart([...this.filterOptions]); + promptAction.showToast({ message: `apply NativeWindow ${result == true ? 'succeed' : 'fail'}!` }); + break; + } + } + } + + private async pixelInit(): Promise { + if (this.inputType == 'NativeWindow') { + imageEffect.SurfaceFilterStop(); + this.mSurfaceId = imageEffect.getSurfaceId(this.mSurfaceId); + this.startPreview(this.mSurfaceId); + console.info(`initCamera succeed`); + } + this.displayPixelMap?.release(); + this.displayPixelMap = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_1080x1920')); + this.displayPixelMapUri?.release(); + this.displayPixelMapUri = await ImageUtils.getInstance().getPixelMap($r('app.media.ic_700x700')); + } +} + +@CustomDialog +struct CustomDialogExample { + @Link brightnessSetValue: number; + @Link brightnessSelect: boolean; + @Link contrastSetValue: number; + @Link contrastSelect: boolean; + @Link cropSetValue: number; + @Link cropSelect: boolean; + @Link customBrightnessSetValue: number; + @Link customBrightnessSelect: boolean; + @Link customCropSetValue: number; + @Link customCropSelect: boolean; + @Link filterOptions: Array>; + @Link filterInfo: string; + @Link inputType: string; + @Link index: number; + inputList: SelectOption[] = [{ value: 'PixelmapNative' }, { value: 'NativeBuffer' }, + { value: 'URI' }, { value: 'NativeWindow' }]; + controller: CustomDialogController; + @State formatList: String[] = ['Format:rgba_8888', 'Format:nv21', 'Format:nv12']; + @State handlePopup: boolean = false; + cancel: () => void = () => { + } + confirm: () => void = () => { + } + + @Builder + filterInfoMenu() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Start, justifyContent: FlexAlign.Center }) { + Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Start }) { + Text(this.filterInfo).fontSize($r('app.float.filter_info_size')) + } + } + .width('60%').height('15%') + .opacity($r('app.float.filter_info_opacity')) + .id('filter_info_menu') + } + + build() { + Column() { + Select(this.inputList) + .value($r('app.string.select_description')) + .selected(this.index) + .onSelect((index: number) => { + this.index = index; + this.inputType = this.inputList[index].value.toString(); + }) + .id('select_input') + Divider().height($r('app.float.divider_height')).color(0xCCCCCC) + + Column() { + Column() { + Text('Filter') + .width('100%') + .fontSize($r('app.float.dialog_title_size')) + .margin({ bottom: $r('app.float.bottom_size') }) + + Row() { + Column() { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'brightnessCheckbox', group: 'filterCheckboxGroup' }) + .selectedColor(0x39a2db) + .select(this.brightnessSelect) + .onChange((value: boolean) => { + this.brightnessSelect = value; + }) + .width($r('app.float.check_box_width')) + .height($r('app.float.check_box_height')) + .id('checkbox_brightness') + Text('Brightness').fontSize($r('app.float.filter_text_size')).width('18%') + Slider({ + value: this.brightnessSetValue, + min: VALUE_MIN_MINUS, + max: VALUE_MAX, + style: SliderStyle.OutSet + }) + .showTips(true, this.brightnessSetValue.toFixed()) + .onChange((value: number, mode: SliderChangeMode) => { + this.brightnessSetValue = value; + console.info('value:' + value + 'mode:' + mode.toString()); + }) + .width('60%') + .id('slider_brightness') + // toFixed()将滑动条返回值处理为整数精度 + Column() { + Text(this.brightnessSetValue.toFixed()).fontSize($r('app.float.filter_number_size')) + }.width('8%') + + Column() { + Image($r('app.media.ic_public_search')) + .width('5%') + .height('3.7%') + }.bindMenu(this.filterInfoMenu, { + onAppear: () => { + this.doLookFilterInfo('Brightness'); + }, + onDisappear: () => { + this.filterInfo = ''; + } + }) + .margin({ left: $r('app.float.margin_left') }) + .id('btn_search_brightness') + } + + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'contrastCheckbox', group: 'filterCheckboxGroup' }) + .selectedColor(0x39a2db) + .select(this.contrastSelect) + .onChange((value: boolean) => { + this.contrastSelect = value; + }) + .width($r('app.float.check_box_width')) + .height($r('app.float.check_box_height')) + .id('checkbox_contrast') + Text('Contrast').fontSize($r('app.float.filter_text_size')).width('18%') + Slider({ + value: this.contrastSetValue, + min: VALUE_MIN_MINUS, + max: VALUE_MAX, + style: SliderStyle.OutSet + }) + .showTips(true, this.contrastSetValue.toFixed()) + .onChange((value: number, mode: SliderChangeMode) => { + this.contrastSetValue = value; + console.info('value:' + value + 'mode:' + mode.toString()); + }) + .width('60%') + .id('slider_contrast') + // toFixed()将滑动条返回值处理为整数精度 + Column() { + Text(this.contrastSetValue.toFixed()).fontSize($r('app.float.filter_number_size')) + }.width('8%') + + Column() { + Image($r('app.media.ic_public_search')) + .width('5%') + .height('3.7%') + }.bindMenu(this.filterInfoMenu, { + onAppear: () => { + this.doLookFilterInfo('Contrast'); + }, + onDisappear: () => { + this.filterInfo = ''; + } + }) + .margin({ left: $r('app.float.margin_left') }) + .id('btn_search_contrast') + } + + if (this.inputType != 'NativeBuffer' && this.inputType != 'NativeWindow') { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'cropCheckbox', group: 'filterCheckboxGroup' }) + .selectedColor(0x39a2db) + .select(this.cropSelect) + .onChange((value: boolean) => { + this.cropSelect = value; + }) + .width($r('app.float.check_box_width')) + .height($r('app.float.check_box_height')) + .id('checkbox_crop') + Text('Crop').fontSize($r('app.float.filter_text_size')).width('18%') + Slider({ + value: this.cropSetValue, + min: VALUE_MIN, + max: VALUE_MAX, + style: SliderStyle.OutSet + }) + .showTips(true, this.cropSetValue.toFixed()) + .onChange((value: number, mode: SliderChangeMode) => { + this.cropSetValue = value; + console.info('value:' + value + 'mode:' + mode.toString()); + }) + .width('60%') + .id('slider_crop') + // toFixed()将滑动条返回值处理为整数精度 + Column() { + Text(this.cropSetValue.toFixed()).fontSize($r('app.float.filter_number_size')) + }.width('8%') + + Column() { + Image($r('app.media.ic_public_search')) + .width('5%') + .height('3.7%') + }.bindMenu(this.filterInfoMenu, { + onAppear: () => { + this.doLookFilterInfo('Crop'); + }, + onDisappear: () => { + this.filterInfo = ''; + } + }) + .margin({ left: $r('app.float.margin_left') }) + .id('btn_search_crop') + } + } + + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'customBrightnessCheckbox', group: 'filterCheckboxGroup' }) + .selectedColor(0x39a2db) + .select(this.customBrightnessSelect) + .onChange((value: boolean) => { + this.customBrightnessSelect = value; + }) + .width($r('app.float.check_box_width')) + .height($r('app.float.check_box_height')) + .id('checkbox_custom_brightness') + Text('CustomBrightness').fontSize($r('app.float.filter_text_size')).width('18%') + Slider({ + value: this.customBrightnessSetValue, + min: VALUE_MIN_MINUS, + max: VALUE_MAX, + style: SliderStyle.OutSet + }) + .showTips(true, this.customBrightnessSetValue.toFixed()) + .onChange((value: number, mode: SliderChangeMode) => { + this.customBrightnessSetValue = value; + console.info('value:' + value + 'mode:' + mode.toString()); + }) + .width('60%') + .id('slider_custom_brightness') + // toFixed()将滑动条返回值处理为整数精度 + Column() { + Text(this.customBrightnessSetValue.toFixed()).fontSize($r('app.float.filter_number_size')) + }.width('8%') + + Column() { + Image($r('app.media.ic_public_search')) + .width('5%') + .height('3.7%') + }.bindMenu(this.filterInfoMenu, { + onAppear: () => { + this.doLookFilterInfo('CustomBrightness'); + }, + onDisappear: () => { + this.filterInfo = ''; + } + }) + .margin({ left: $r('app.float.margin_left') }) + .id('btn_search_custom_brightness') + } + + if (this.inputType != 'NativeWindow') { + Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { + Checkbox({ name: 'customCropCheckbox', group: 'filterCheckboxGroup' }) + .selectedColor(0x39a2db) + .select(this.customCropSelect) + .onChange((value: boolean) => { + this.customCropSelect = value; + }) + .width($r('app.float.check_box_width')) + .height($r('app.float.check_box_height')) + .id('checkbox_custom_crop') + Text('CustomCrop').fontSize($r('app.float.filter_text_size')).width('18%') + Slider({ + value: this.customCropSetValue, + min: VALUE_MIN, + max: VALUE_MAX, + style: SliderStyle.OutSet + }) + .showTips(true, this.customCropSetValue.toFixed()) + .onChange((value: number, mode: SliderChangeMode) => { + this.customCropSetValue = value; + console.info('value:' + value + 'mode:' + mode.toString()); + }) + .width('60%') + .id('slider_custom_crop') + // toFixed()将滑动条返回值处理为整数精度 + Column() { + Text(this.customCropSetValue.toFixed()).fontSize($r('app.float.filter_number_size')) + }.width('8%') + + Column() { + Image($r('app.media.ic_public_search')) + .width('5%') + .height('3.7%') + }.bindMenu(this.filterInfoMenu, { + onAppear: () => { + this.doLookFilterInfo('CustomCrop'); + }, + onDisappear: () => { + this.filterInfo = ''; + } + }) + .margin({ left: $r('app.float.margin_left') }) + .id('btn_search_custom_crop') + } + } + } + .width('100%') + } + } + }.margin({ bottom: $r('app.float.bottom_size') }) + + Column() { + Divider().height($r('app.float.divider_height')).color(0xCCCCCC); + Column() { + Text($r('app.string.look_up')) + .width('100%') + .fontSize($r('app.float.dialog_title_size')) + .margin({ bottom: $r('app.float.bottom_size') }) + Row() { + Column() { + Text($r('app.string.btn_search')) + .width('15%') + .fontSize($r('app.float.search_size')) + .margin({ left: '35%' }) + } + + Column() { + Image($r('app.media.ic_public_arrow_right')) + .fillColor(Color.Black) + .width('10%') + .height('4%') + } + } + .id('btn_search') + .width('100%') + .justifyContent(FlexAlign.Start) + .bindMenu(this.lookupCategoryMenuBuilder) + } + }.margin({ bottom: $r('app.float.bottom_size') }) + + Divider().height($r('app.float.divider_height')).color(0xCCCCCC) + + Flex({ justifyContent: FlexAlign.SpaceAround }) { + Button($r('app.string.btn_cancel')) + .onClick(() => { + this.controller.close(); + this.cancel(); + }).backgroundColor(0xffffff) + .fontColor(Color.Black) + .id('btn_dialog_cancel') + Button($r('app.string.btn_confirm')) + .onClick(() => { + this.controller.close(); + this.confirm(); + }).backgroundColor(0xffffff) + .fontColor(Color.Red) + .id('btn_dialog_confirm') + } + }.margin($r('app.float.margin')) + } + + @Builder + lookupCategoryMenuBuilder() { + Menu() { + MenuItem({ + content: 'Format', + builder: (): void => this.formatMenuBuilder() + }); + }.id('menu_category') + } + + @Builder + formatMenuBuilder() { + Menu() { + ForEach(this.formatList, (item: string) => { + LookupFilterMenuItem({ item: item }) + .id('menu_format') + }); + } + } + + private async doLookFilterInfo(name: String): Promise { + this.filterInfo = imageEffect.lookupFilterInfo(name); + } +} + +@Component +struct LookupFilterMenuItem { + @State item: string = ''; + dialogController: CustomDialogController = new CustomDialogController({ + builder: CustomDialogDetails({ + item: $item + }), + offset: { dx: $r('app.float.offset_x'), dy: $r('app.float.offset_y') }, + autoCancel: true, + }); + + build() { + MenuItem({ content: this.item }) + .onClick(() => { + if (this.dialogController != null) { + this.dialogController.open(); + } + }) + } +} + +@CustomDialog +struct CustomDialogDetails { + @Link item: string + controller?: CustomDialogController + + build() { + Column() { + Text('Filters:\n' + imageEffect.lookupFilters(this.item)) + .fontSize($r('app.float.filter_info_size')) + } + } +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/ImageUtils.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/ImageUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..0f690a1c37b8f98d1a3aedaa178ff7f5f41e446c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/ImageUtils.ets @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 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. + */ + +import image from '@ohos.multimedia.image'; + +export class ImageUtils { + private static instance: ImageUtils; + + public static getInstance(): ImageUtils { + if (!ImageUtils.instance) { + ImageUtils.instance = new ImageUtils(); + } + return ImageUtils.instance; + } + + async getPixelMap(resource: Resource): Promise { + const resourceStr = getContext(this).resourceManager; + let imageBuffer = await resourceStr.getMediaContent(resource); + const pixelMap = await image.createImageSource(imageBuffer.buffer as object as ArrayBuffer).createPixelMap(); + return pixelMap; + } + + async getPixelMapByFilePath(filePath: string): Promise { + const imageSource: image.ImageSource = image.createImageSource(filePath); + const pixelMap = await imageSource.createPixelMap(); + return pixelMap; + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/PermissionUtils.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/PermissionUtils.ets new file mode 100644 index 0000000000000000000000000000000000000000..73d8b7d303f3e5d0a7ff8821227267238c473870 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/ets/utils/PermissionUtils.ets @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2025 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. + */ + +import { abilityAccessCtrl, bundleManager, common, Permissions } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { promptAction } from '@kit.ArkUI'; + +export const permissions: Permissions[] = ['ohos.permission.CAMERA']; + +async function checkPermissionGrant(permission: Permissions): Promise { + // 1. 创建应用权限管理器 + let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); + let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED; + + // 2. 获取应用程序的accessTokenID + let tokenId: number = 0; + try { + let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf( + bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION); + let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo; + tokenId = appInfo.accessTokenId; + } catch (error) { + const err: BusinessError = error as BusinessError; + console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`); + } + + // 3. 校验应用是否被授予权限 + try { + grantStatus = await atManager.checkAccessToken(tokenId, permission); + } catch (error) { + const err: BusinessError = error as BusinessError; + console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`); + } + + return grantStatus; +} + + +export async function checkAndRequestPermissions(permissions: Permissions[], context: common.UIAbilityContext): + Promise { + let grantStatus: abilityAccessCtrl.GrantStatus = await checkPermissionGrant(permissions[0]); + + if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { + // 已经授权,可以继续访问目标操作 + promptAction.showToast({ + message: 'permission succeed!', // 显示文本 + }) + } else { + // 申请麦克风权限 + reqPermissionsFromUser(permissions, context); + } +} + +// 使用UIExtensionAbility:将common.UIAbilityContext 替换为common.UIExtensionContext +export function reqPermissionsFromUser(permissions: Permissions[], context: common.UIAbilityContext): void { + // 1. 创建应用权限管理器 + let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); + // 2. 拉起弹框请求用户授权 requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗 + atManager.requestPermissionsFromUser(context, permissions).then((data) => { + let grantStatus: number[] = data.authResults; + let length: number = grantStatus.length; + for (let i = 0; i < length; i++) { + if (grantStatus[i] === 0) { + // 用户授权,可以继续访问目标操作 + promptAction.showToast({ + message: 'permission succeed!', // 显示文本 + }) + } else { + // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限 + promptAction.showToast({ + message: 'user refused authorization!', // 显示文本 + }) + return; + } + } + // 授权成功 + }).catch((err: BusinessError) => { + console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); + }) +} + + + diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/module.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..901eac0ee2211b3c6bbd1c2f3bbcc75a074febc6 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/module.json5 @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "description": "$string:EntryAbility_desc", + "icon": "$media:icon", + "label": "$string:EntryAbility_label", + "startWindowIcon": "$media:startIcon", + "startWindowBackground": "$color:start_window_background", + "exported": true, + "skills": [ + { + "entities": [ + "entity.system.home" + ], + "actions": [ + "action.system.home" + ] + } + ] + } + ], + "requestPermissions":[ + { + "name" : "ohos.permission.CAMERA", + "reason": '$string:camera_reason', + "usedScene": { + "abilities": [ + "FormAbility" + ], + "when":"always" + } + } + ] + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/color.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/float.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..ab0a74ae696363379fac96661e7b22e4d1bebbf7 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/float.json @@ -0,0 +1,92 @@ +{ + "float": [ + { + "name": "title_font_size", + "value": "20fp" + }, + { + "name": "home_page_title_margin", + "value": "10" + }, + { + "name": "home_page_title_height", + "value": "38" + }, + { + "name": "title_image_width", + "value": "24vp" + }, + { + "name": "title_image_height", + "value": "24vp" + }, + { + "name": "title_size", + "value": "30" + }, + { + "name": "filter_text_size", + "value": "10" + }, + { + "name": "filter_number_size", + "value": "12" + }, + { + "name": "button_width", + "value": "100" + }, + { + "name": "filter_info_size", + "value": "16" + }, + { + "name": "filter_info_opacity", + "value": "0.8" + }, + { + "name": "divider_height", + "value": "2" + }, + { + "name": "dialog_title_size", + "value": "18" + }, + { + "name": "search_size", + "value": "20" + }, + { + "name": "bottom_size", + "value": "10" + }, + { + "name": "value_max", + "value": "100" + }, + { + "name": "check_box_width", + "value": "10" + }, + { + "name": "check_box_height", + "value": "14" + }, + { + "name": "margin_left", + "value": "2" + }, + { + "name": "margin", + "value": "24" + }, + { + "name": "offset_x", + "value": "0" + }, + { + "name": "offset_y", + "value": "220" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..7a8ea7c056bf0fead29f21fa68e789f2348f08b1 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/element/string.json @@ -0,0 +1,48 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ImageEffect" + }, + { + "name": "image_edit", + "value": "imageEdit" + }, + { + "name": "btn_search", + "value": "Search" + }, + { + "name": "look_up", + "value": "LookUp" + }, + { + "name": "btn_confirm", + "value": "btn_confirm" + }, + { + "name": "btn_cancel", + "value": "Cancel" + }, + { + "name": "camera_reason", + "value": "用于相机预览" + }, + { + "name": "native_buffer_description", + "value": "此场景无图片演示,请观察日志信息" + }, + { + "name": "select_description", + "value": "请选择输入场景" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_1080x1920.jpg b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_1080x1920.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0c68bf944bae012f5a2a6ece0e172cd45f5308ce Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_1080x1920.jpg differ diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_700x700.jpg b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_700x700.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5df7ffe5de575590f93a1370baadeaf5513ab6cf Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_700x700.jpg differ diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_arrow_right.svg b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_arrow_right.svg new file mode 100644 index 0000000000000000000000000000000000000000..48ed31efd130cec98c0df8b8093cb1071136608c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_arrow_right.svg @@ -0,0 +1,15 @@ + + + + Public/ic_public_arrow_right + Created with Sketch. + + + + + + + + + + \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_search.svg b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_search.svg new file mode 100644 index 0000000000000000000000000000000000000000..d3cbb73618e6909fd7f99f7b471ebfca626b020c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_search.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_search_filled + + + + + + + + + + \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_settings.svg b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_settings.svg new file mode 100644 index 0000000000000000000000000000000000000000..06e75695970159e41db7cc8d37f6c8f684ee25a5 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/ic_public_settings.svg @@ -0,0 +1,13 @@ + + + Public/ic_public_settings + + + + + + + + + + \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/icon.png b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/icon.png differ diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/startIcon.png b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..366f76459ffd4494ec40d0ddd5c59385b9c5da11 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/media/startIcon.png differ diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/profile/main_pages.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..000ae3dbfb3815af6ac343d515ad23a2b973082e --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/ImageEditPage" + ] +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/en_US/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..27653e87f7d312db10a08ed5c9fdcf343ffa58c2 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/en_US/element/string.json @@ -0,0 +1,40 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ImageEffect" + }, + { + "name": "btn_search", + "value": "Search" + }, + { + "name": "look_up", + "value": "LookUp" + }, + { + "name": "btn_confirm", + "value": "Confirm" + }, + { + "name": "btn_cancel", + "value": "Cancel" + }, + { + "name": "image_edit", + "value": "imageEdit" + }, + { + "name": "camera_reason", + "value": "For camera preview" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/zh_CN/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..3006b4b15ceda27ff80e500a00be05fd2725867e --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/main/resources/zh_CN/element/string.json @@ -0,0 +1,48 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "模块描述" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "ImageEffect" + }, + { + "name": "btn_search", + "value": "查询" + }, + { + "name": "look_up", + "value": "LookUp" + }, + { + "name": "btn_confirm", + "value": "确认" + }, + { + "name": "btn_cancel", + "value": "取消" + }, + { + "name": "native_buffer_description", + "value": "此场景无图片演示,请观察日志信息" + }, + { + "name": "camera_reason", + "value": "用于相机预览" + }, + { + "name": "image_edit", + "value": "imageEdit" + }, + { + "name": "select_description", + "value": "请选择输入场景" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/libentry.mock.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/libentry.mock.ets new file mode 100644 index 0000000000000000000000000000000000000000..82fa70b5693ddab96d237d2d17d943d866b61465 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/libentry.mock.ets @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2025 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. + */ +const NativeMock: Record = { + 'add': (a: number, b: number) => { + return a + b; + }, +}; + +export default NativeMock; \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/mock-config.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/mock-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..b9f11f82926f2ff0add26f1393f04785c4e250ab --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/mock/mock-config.json5 @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "libentry.so": { + "source": "src/mock/libentry.mock.ets" + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/Ability.test.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..d591c1910ee11325a122aae3027c7d5c6f25bf7c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2025 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. + */ + +import hilog from '@ohos.hilog'; +import { describe, it } from '@ohos/hypium'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import { Driver, ON } from '@ohos.UiTest'; +import { getString } from '../utils/ResourceUtil'; + +const TAG: string = '[Sample_ImageEffect]'; +const DOMAIN: number = 0xF811; +const BUNDLE: string = 'ImageEffect'; +const DELAY_LONG: number = 1000; +const DELAY_SHORT: number = 300; +const SLIDER_Y: number = 100; // 滑动组件向右滑动距离,单位像素点 + +async function openDialog() { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickSettingBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + let btSetting: string = getString($r('app.string.btn_setting')); + hilog.info(DOMAIN, TAG, BUNDLE + 'lsq:' + btSetting); + await driver.assertComponentExist(ON.id(btSetting)); + let setting = await driver.findComponent(ON.id(btSetting)); + await setting.click(); + await driver.delayMs(DELAY_SHORT); + let confirm: string = getString($r('app.string.btn_dialog_confirm')); + await driver.assertComponentExist(ON.id(confirm)); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickSettingBtn_001 end'); +} + +async function confirmDialog() { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickConfirmBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + let btnDialogConfirm: string = getString($r('app.string.btn_dialog_confirm')); + await driver.assertComponentExist(ON.id(btnDialogConfirm)); + let dialogConfirm = await driver.findComponent(ON.id(btnDialogConfirm)); + await dialogConfirm.click(); + await driver.delayMs(DELAY_SHORT); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickConfirmBtn_001 end'); +} + +async function apply() { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickApplyBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + let btnApply: string = getString($r('app.string.btn_apply')); + await driver.assertComponentExist(ON.id(btnApply)); + let apply = await driver.findComponent(ON.id(btnApply)); + await apply.click(); + await driver.delayMs(DELAY_SHORT); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickApplyBtn_001 end'); +} + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + /** + * @tc.number StartAbility_001 + * @tc.name StartAbility_001 + * @tc.desc 启动Ability + */ + it('StartAbility_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001 begin'); + let abilityDelegatorRegistry = AbilityDelegatorRegistry.getAbilityDelegator(); + await abilityDelegatorRegistry.executeShellCommand('aa start -a EntryAbility -b com.samples.ImageEffect'); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'StartAbility_001 end'); + }) + + /** + * @tc.number RequestPermissionFunction_001 + * @tc.name RequestPermissionFunction_001 + * @tc.desc 获取权限 + */ + it('RequestPermissionFunction_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'RequestPermissionFunction_001 begin'); + let driver: Driver = Driver.create(); + await driver.delayMs(DELAY_LONG); + let tipAllow: string = getString($r('app.string.allow')); + await driver.assertComponentExist(ON.text(tipAllow)); + let stack = await driver.findComponent(ON.text(tipAllow)); + await stack.click(); + await driver.assertComponentExist(ON.text(getString($r('app.string.Permission_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'RequestPermissionFunction_001 end'); + }) + + /** + * @tc.number ClickDialogCancelBtn_001 + * @tc.name ClickDialogCancelBtn_001 + * @tc.desc 点击dialog取消按钮,关闭参数页面 + */ + it('ClickDialogCancelBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickDialogCancelBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnCancel: string = getString($r('app.string.btn_dialog_cancel')); + await driver.assertComponentExist(ON.id(btnCancel)); + let cancel = await driver.findComponent(ON.id(btnCancel)); + await cancel.click(); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickDialogCancelBtn_001 end'); + }) + + /** + * @tc.number AdjustBrightness_001 + * @tc.name AdjustBrightness_001 + * @tc.desc Brightness滤镜 + */ + it('AdjustBrightness_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustBrightness_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let checkboxBrightnessStr: string = getString($r('app.string.checkbox_brightness')); + await driver.assertComponentExist(ON.id(checkboxBrightnessStr)); + let checkboxBrightness = await driver.findComponent(ON.id(checkboxBrightnessStr)); + await checkboxBrightness.click(); + await driver.delayMs(DELAY_SHORT); + let sliderBrightnessStr: string = getString($r('app.string.slider_brightness')); + await driver.assertComponentExist(ON.id(sliderBrightnessStr)); + let sliderBrightness = await driver.findComponent(ON.id(sliderBrightnessStr)); + let point = await sliderBrightness.getBoundsCenter(); + await driver.swipe(point.x, point.y, point.x + SLIDER_Y, point.y); + await driver.delayMs(DELAY_SHORT); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.PixelMapNative_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustBrightness_001 end'); + }) + + /** + * @tc.number AdjustContrast_001 + * @tc.name AdjustContrast_001 + * @tc.desc Contrast滤镜 + */ + it('AdjustContrast_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustContrast_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let checkboxContrastStr: string = getString($r('app.string.checkbox_contrast')); + await driver.assertComponentExist(ON.id(checkboxContrastStr)); + let checkboxContrast = await driver.findComponent(ON.id(checkboxContrastStr)); + await checkboxContrast.click(); + await driver.delayMs(DELAY_SHORT); + let sliderContrastStr: string = getString($r('app.string.slider_contrast')); + await driver.assertComponentExist(ON.id(sliderContrastStr)); + let sliderContrast = await driver.findComponent(ON.id(sliderContrastStr)); + let point = await sliderContrast.getBoundsCenter(); + await driver.swipe(point.x, point.y, point.x + SLIDER_Y, point.y); + await driver.delayMs(DELAY_SHORT); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.PixelMapNative_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustContrast_001 end'); + }) + + /** + * @tc.number AdjustCrop_001 + * @tc.name AdjustCrop_001 + * @tc.desc Crop滤镜 + */ + it('AdjustCrop_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCrop_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let checkboxCropStr: string = getString($r('app.string.checkbox_crop')); + await driver.assertComponentExist(ON.id(checkboxCropStr)); + let checkboxCrop = await driver.findComponent(ON.id(checkboxCropStr)); + await checkboxCrop.click(); + await driver.delayMs(DELAY_SHORT); + let sliderCropStr: string = getString($r('app.string.slider_crop')); + await driver.assertComponentExist(ON.id(sliderCropStr)); + let sliderCrop = await driver.findComponent(ON.id(sliderCropStr)); + let point = await sliderCrop.getBoundsCenter(); + await driver.swipe(point.x, point.y, point.x + SLIDER_Y, point.y); + await driver.delayMs(DELAY_SHORT); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.PixelMapNative_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCrop_001 end'); + }) + + /** + * @tc.number AdjustCustomBrightness_001 + * @tc.name AdjustCustomBrightness_001 + * @tc.desc CustomBrightness滤镜 + */ + it('AdjustCustomBrightness_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCustomBrightness_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let checkboxCustomStr: string = getString($r('app.string.checkbox_custom_brightness')); + await driver.assertComponentExist(ON.id(checkboxCustomStr)); + let checkboxCustom = await driver.findComponent(ON.id(checkboxCustomStr)); + await checkboxCustom.click(); + await driver.delayMs(DELAY_SHORT); + let sliderCustomStr: string = getString($r('app.string.slider_custom_brightness')); + await driver.assertComponentExist(ON.id(sliderCustomStr)); + let sliderCustom = await driver.findComponent(ON.id(sliderCustomStr)); + let point = await sliderCustom.getBoundsCenter(); + await driver.swipe(point.x, point.y, point.x + SLIDER_Y, point.y); + await driver.delayMs(DELAY_SHORT); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.PixelMapNative_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCustomBrightness_001 end'); + }) + + /** + * @tc.number AdjustCustomCrop_001 + * @tc.name AdjustCustomCrop_001 + * @tc.desc CustomCrop滤镜 + */ + it('AdjustCustomCrop_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCustomCrop_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let checkboxCustomStr: string = getString($r('app.string.checkbox_custom_crop')); + await driver.assertComponentExist(ON.id(checkboxCustomStr)); + let checkboxCustom = await driver.findComponent(ON.id(checkboxCustomStr)); + await checkboxCustom.click(); + await driver.delayMs(DELAY_SHORT); + let sliderCustomStr: string = getString($r('app.string.slider_custom_crop')); + await driver.assertComponentExist(ON.id(sliderCustomStr)); + let sliderCustom = await driver.findComponent(ON.id(sliderCustomStr)); + let point = await sliderCustom.getBoundsCenter(); + await driver.swipe(point.x, point.y, point.x + SLIDER_Y, point.y); + await driver.delayMs(DELAY_SHORT); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.PixelMapNative_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'AdjustCustomCrop_001 end'); + }) + + /** + * @tc.number ClickResetBtn_001 + * @tc.name ClickResetBtn_001 + * @tc.desc 点击Reset按钮,图像效果复原 + */ + it('ClickResetBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickResetBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + let btnReset: string = getString($r('app.string.btn_reset')); + await driver.assertComponentExist(ON.id(btnReset)); + let reset = await driver.findComponent(ON.id(btnReset)); + await reset.click(); + await driver.delayMs(DELAY_SHORT); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickResetBtn_001 end'); + }) + + /** + * @tc.number ClickBrightnessSearchBtn_001 + * @tc.name ClickBrightnessSearchBtn_001 + * @tc.desc Brightness查询按钮,显示Brightness滤镜信息 + */ + it('ClickBrightnessSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickBrightnessSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnBrightnessSearch: string = getString($r('app.string.btn_search_brightness')); + await driver.assertComponentExist(ON.id(btnBrightnessSearch)); + let brightnessSearch = await driver.findComponent(ON.id(btnBrightnessSearch)); + await brightnessSearch.click(); + await driver.delayMs(DELAY_SHORT); + let filterInfoStr: string = getString($r('app.string.filter_info_menu')); + await driver.assertComponentExist(ON.id(filterInfoStr)); + await driver.findComponent(ON.id(filterInfoStr)); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_brightness')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickBrightnessSearchBtn_001 end'); + }) + + /** + * @tc.number ClickContrastSearchBtn_001 + * @tc.name ClickContrastSearchBtn_001 + * @tc.desc Contrast查询按钮,显示Contrast滤镜信息 + */ + it('ClickContrastSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickContrastSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnContrastSearch: string = getString($r('app.string.btn_search_contrast')); + await driver.assertComponentExist(ON.id(btnContrastSearch)); + let contrastSearch = await driver.findComponent(ON.id(btnContrastSearch)); + await contrastSearch.click(); + await driver.delayMs(DELAY_SHORT); + let filterInfoStr: string = getString($r('app.string.filter_info_menu')); + await driver.assertComponentExist(ON.id(filterInfoStr)); + await driver.findComponent(ON.id(filterInfoStr)); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_contrast')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickContrastSearchBtn_001 end'); + }) + + /** + * @tc.number ClickCropSearchBtn_001 + * @tc.name ClickCropSearchBtn_001 + * @tc.desc Crop查询按钮,显示Crop滤镜信息 + */ + it('ClickCropSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCropSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnCropSearch: string = getString($r('app.string.btn_search_crop')); + await driver.assertComponentExist(ON.id(btnCropSearch)); + let cropSearch = await driver.findComponent(ON.id(btnCropSearch)); + await cropSearch.click(); + await driver.delayMs(DELAY_SHORT); + let filterInfoStr: string = getString($r('app.string.filter_info_menu')); + await driver.assertComponentExist(ON.id(filterInfoStr)); + await driver.findComponent(ON.id(filterInfoStr)); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_crop')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCropSearchBtn_001 end'); + }) + + /** + * @tc.number ClickCustomBrightnessSearchBtn_001 + * @tc.name ClickCustomBrightnessSearchBtn_001 + * @tc.desc CustomBrightness查询按钮,显示CustomBrightness滤镜信息 + */ + it('ClickCustomBrightnessSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCustomBrightnessSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnCustomSearch: string = getString($r('app.string.btn_search_custom_brightness')); + await driver.assertComponentExist(ON.id(btnCustomSearch)); + let customSearch = await driver.findComponent(ON.id(btnCustomSearch)); + await customSearch.click(); + await driver.delayMs(DELAY_SHORT); + let filterInfoStr: string = getString($r('app.string.filter_info_menu')); + await driver.assertComponentExist(ON.id(filterInfoStr)); + await driver.findComponent(ON.id(filterInfoStr)); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_custom_brightness')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCustomBrightnessSearchBtn_001 end'); + }) + + /** + * @tc.number ClickCustomCropSearchBtn_001 + * @tc.name ClickCustomCropSearchBtn_001 + * @tc.desc CustomCrop查询按钮,显示CustomBrightness滤镜信息 + */ + it('ClickCustomCropSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCustomCropSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnCustomSearch: string = getString($r('app.string.btn_search_custom_crop')); + await driver.assertComponentExist(ON.id(btnCustomSearch)); + let customSearch = await driver.findComponent(ON.id(btnCustomSearch)); + await customSearch.click(); + await driver.delayMs(DELAY_SHORT); + let filterInfoStr: string = getString($r('app.string.filter_info_menu')); + await driver.assertComponentExist(ON.id(filterInfoStr)); + await driver.findComponent(ON.id(filterInfoStr)); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_custom_crop')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickCustomCropSearchBtn_001 end'); + }) + + /** + * @tc.number ClickSearchBtn_001 + * @tc.name ClickSearchBtn_001 + * @tc.desc 点击查询按钮,显示滤镜滤镜信息 + */ + it('ClickSearchBtn_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickSearchBtn_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + let btnSearch: string = getString($r('app.string.btn_search')); + await driver.assertComponentExist(ON.id(btnSearch)); + let search = await driver.findComponent(ON.id(btnSearch)); + await search.click(); + await driver.delayMs(DELAY_SHORT); + let menuCategory: string = getString($r('app.string.menu_category')); + await driver.assertComponentExist(ON.id(menuCategory)); + let category = await driver.findComponent(ON.id(menuCategory)); + await category.click(); + await driver.delayMs(DELAY_SHORT); + let menuFormat: string = getString($r('app.string.menu_format')); + await driver.assertComponentExist(ON.id(menuFormat)); + let format = await driver.findComponent(ON.id(menuFormat)); + await format.click(); + await driver.delayMs(DELAY_LONG); + await driver.assertComponentExist(ON.text(getString($r('app.string.query_rgba_8888')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'ClickSearchBtn_001 end'); + }) + + /** + * @tc.number NativeBuffer_001 + * @tc.name NativeBuffer_001 + * @tc.desc 切换输入场景为NativeBuffer,应用滤镜 + */ + it('NativeBuffer_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'NativeBuffer_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + await driver.assertComponentExist(ON.id('select_input')); + let select = await driver.findComponent(ON.id('select_input')); + await select.click(); + await driver.delayMs(DELAY_SHORT); + let selectNativeBufferStr: string = getString($r('app.string.NativeBuffer')); + await driver.assertComponentExist(ON.text(selectNativeBufferStr)); + let selectNativeBuffer = await driver.findComponent(ON.text(selectNativeBufferStr)); + await selectNativeBuffer.click(); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.NativeBuffer_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'NativeBuffer_001 end'); + }) + + /** + * @tc.number URI_001 + * @tc.name URI_001 + * @tc.desc 切换输入场景为URI,应用滤镜 + */ + it('URI_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'URI_001 begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + await driver.assertComponentExist(ON.id('select_input')); + let select = await driver.findComponent(ON.id('select_input')); + await select.click(); + await driver.delayMs(DELAY_SHORT); + let selectURIStr: string = getString($r('app.string.URI')); + await driver.assertComponentExist(ON.text(selectURIStr)); + let selectURI = await driver.findComponent(ON.text(selectURIStr)); + await selectURI.click(); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.URI_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'URI_001 end'); + }) + + /** + * @tc.number NativeWindow_001 + * @tc.name NativeWindow_001 + * @tc.desc 切换输入场景为NativeWindow,应用滤镜 + */ + it('NativeWindow_001', 0, async (done: Function) => { + hilog.info(DOMAIN, TAG, BUNDLE + 'NativeWindow begin'); + let driver = Driver.create(); + await driver.delayMs(DELAY_SHORT); + await openDialog(); + await driver.assertComponentExist(ON.id('select_input')); + let select = await driver.findComponent(ON.id('select_input')); + await select.click(); + await driver.delayMs(DELAY_SHORT); + let selectNativeWindowStr: string = getString($r('app.string.NativeWindow')); + await driver.assertComponentExist(ON.text(selectNativeWindowStr)); + let selectNativeWindow = await driver.findComponent(ON.text(selectNativeWindowStr)); + await selectNativeWindow.click(); + await confirmDialog(); + await apply(); + await driver.delayMs(DELAY_SHORT); + await driver.assertComponentExist(ON.text(getString($r('app.string.NativeWindow_Succeed')))); + done(); + hilog.info(DOMAIN, TAG, BUNDLE + 'NativeWindow end'); + }) + }) +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/List.test.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1eac52fcebe8958e19a7b8fed2e8f39c520a3e42 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 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. + */ + +import abilityTest from './Ability.test'; + +export default function testsuite() { + abilityTest(); +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/TestAbility.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..29fff7b38662d6b311c35ee5d86741097085289c --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2025 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. + */ + +import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { abilityDelegatorRegistry } from '@kit.TestKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; + +export default class TestAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:' + JSON.stringify(launchParam) ?? ''); + let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); + let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments(); + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite); + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/pages/Index.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..4485363990eb700840c40779f2fa9b057c17c230 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 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. + */ + +@Entry +@Component +struct Index { + @State message: string = 'Hello World'; + + build() { + Row() { + Column() { + Text(this.message) + .fontWeight(FontWeight.Bold) + } + .width('100%') + } + .height('100%') + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets new file mode 100644 index 0000000000000000000000000000000000000000..c45af6753a33cb2f60908304a1da18f90a55d0c7 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ets @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025 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. + */ + +import { abilityDelegatorRegistry, TestRunner } from '@kit.TestKit'; +import { UIAbility, Want } from '@kit.AbilityKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { resourceManager } from '@kit.LocalizationKit'; +import { util } from '@kit.ArkTS'; + +let abilityDelegator: abilityDelegatorRegistry.AbilityDelegator; +let abilityDelegatorArguments: abilityDelegatorRegistry.AbilityDelegatorArgs; +let jsonPath: string = 'mock/mock-config.json'; +let tag: string = 'testTag'; + +async function onAbilityCreateCallback(data: UIAbility) { + hilog.info(0x0000, 'testTag', 'onAbilityCreateCallback, data: ${}', JSON.stringify(data)); +} + +async function addAbilityMonitorCallback(err: BusinessError) { + hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? ''); +} + +export default class OpenHarmonyTestRunner implements TestRunner { + constructor() { + } + + onPrepare() { + hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare'); + } + + async onRun() { + let tag = 'testTag'; + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun run'); + abilityDelegatorArguments = abilityDelegatorRegistry.getArguments() + abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator() + let moduleName = abilityDelegatorArguments.parameters['-m']; + let context = abilityDelegator.getAppContext().getApplicationContext().createModuleContext(moduleName); + let mResourceManager = context.resourceManager; + await checkMock(abilityDelegator, mResourceManager); + const bundleName = abilityDelegatorArguments.bundleName; + const testAbilityName: string = 'TestAbility'; + let lMonitor: abilityDelegatorRegistry.AbilityMonitor = { + abilityName: testAbilityName, + onAbilityCreate: onAbilityCreateCallback, + moduleName: moduleName + }; + abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback) + const want: Want = { + bundleName: bundleName, + abilityName: testAbilityName, + moduleName: moduleName + }; + abilityDelegator.startAbility(want, (err: BusinessError, data: void) => { + hilog.info(0x0000, tag, 'startAbility : err : %{public}s', JSON.stringify(err) ?? ''); + hilog.info(0x0000, tag, 'startAbility : data : %{public}s', JSON.stringify(data) ?? ''); + }) + hilog.info(0x0000, tag, '%{public}s', 'OpenHarmonyTestRunner onRun end'); + } +} + +async function checkMock(abilityDelegator: abilityDelegatorRegistry.AbilityDelegator, resourceManager: resourceManager.ResourceManager) { + let rawFile: Uint8Array; + try { + rawFile = resourceManager.getRawFileContentSync(jsonPath); + hilog.info(0x0000, tag, 'MockList file exists'); + let mockStr: string = util.TextDecoder.create("utf-8", { ignoreBOM: true }).decodeWithStream(rawFile); + let mockMap: Record = getMockList(mockStr); + try { + abilityDelegator.setMockList(mockMap) + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `abilityDelegator.setMockList failed, error code: ${code}, message: ${message}.`); + } + } catch (error) { + let code = (error as BusinessError).code; + let message = (error as BusinessError).message; + hilog.error(0x0000, tag, `ResourceManager:callback getRawFileContent failed, error code: ${code}, message: ${message}.`); + } +} + +function getMockList(jsonStr: string) { + let jsonObj: Record = JSON.parse(jsonStr); + let map: Map = new Map(Object.entries(jsonObj)); + let mockList: Record = {}; + map.forEach((value: object, key: string) => { + let realValue: string = value['source'].toString(); + mockList[key] = realValue; + }); + hilog.info(0x0000, tag, '%{public}s', 'mock-json value:' + JSON.stringify(mockList) ?? ''); + return mockList; +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/utils/ResourceUtil.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/utils/ResourceUtil.ets new file mode 100644 index 0000000000000000000000000000000000000000..2f9ce1c69f2ce87df5b40069123735ed279055f8 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/ets/utils/ResourceUtil.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 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. + */ + +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const delegator = abilityDelegatorRegistry.getAbilityDelegator(); + +export function getString(resourceData: Resource): string { + let manage = delegator.getAppContext().resourceManager; + return manage.getStringSync(resourceData) +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/module.json5 b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d01177ee217f3b90c539608d0e4ae624054e485d --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "default", + "tablet" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/color.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..6dc7d26630af72d76e9ccc7bfdf023db6a8a4905 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,197 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + }, + { + "name": "btn_setting", + "value": "btn_setting" + }, + { + "name": "btn_dialog_confirm", + "value": "btn_dialog_confirm" + }, + { + "name": "btn_dialog_cancel", + "value": "btn_dialog_cancel" + }, + { + "name": "checkbox_brightness", + "value": "checkbox_brightness" + }, + { + "name": "slider_brightness", + "value": "slider_brightness" + }, + { + "name": "btn_apply", + "value": "btn_apply" + }, + { + "name": "btn_reset", + "value": "btn_reset" + }, + { + "name": "checkbox_contrast", + "value": "checkbox_contrast" + }, + { + "name": "slider_contrast", + "value": "slider_contrast" + }, + { + "name": "checkbox_crop", + "value": "checkbox_crop" + }, + { + "name": "slider_crop", + "value": "slider_crop" + }, + { + "name": "checkbox_custom_brightness", + "value": "checkbox_custom_brightness" + }, + { + "name": "checkbox_custom_crop", + "value": "checkbox_custom_crop" + }, + { + "name": "slider_custom_brightness", + "value": "slider_custom_brightness" + }, + { + "name": "slider_custom_crop", + "value": "slider_custom_crop" + }, + { + "name": "btn_search_brightness", + "value": "btn_search_brightness" + }, + { + "name": "filter_info_menu", + "value": "filter_info_menu" + }, + { + "name": "btn_search_contrast", + "value": "btn_search_contrast" + }, + { + "name": "btn_search_crop", + "value": "btn_search_crop" + }, + { + "name": "btn_search_custom_brightness", + "value": "btn_search_custom_brightness" + }, + { + "name": "btn_search_custom_crop", + "value": "btn_search_custom_crop" + }, + { + "name": "slider_custom", + "value": "slider_custom" + }, + { + "name": "checkbox_custom", + "value": "checkbox_custom" + }, + { + "name": "btn_search", + "value": "btn_search" + }, + { + "name": "menu_category", + "value": "menu_category" + }, + { + "name": "menu_format", + "value": "menu_format" + }, + { + "name": "btn_search_custom", + "value": "btn_search_custom" + }, + { + "name": "allow", + "value": "允许" + }, + { + "name": "native_buffer_description", + "value": "此场景无图片演示,请观察日志信息" + }, + { + "name": "select_description", + "value": "请选择输入场景" + }, + { + "name": "PixelmapNative", + "value": "PixelmapNative" + }, + { + "name": "NativeBuffer", + "value": "NativeBuffer" + }, + { + "name": "URI", + "value": "URI" + }, + { + "name": "NativeWindow", + "value": "NativeWindow" + }, + { + "name": "PixelMapNative_Succeed", + "value": "apply PixelMapNative succeed!" + }, + { + "name": "NativeBuffer_Succeed", + "value": "apply NativeBuffer succeed!" + }, + { + "name": "URI_Succeed", + "value": "apply URI succeed!" + }, + { + "name": "NativeWindow_Succeed", + "value": "apply NativeWindow succeed!" + }, + { + "name": "Permission_Succeed", + "value": "permission succeed!" + }, + { + "name": "query_brightness", + "value": "name:Brightness, supportedBufferType: {Texture Pixel }supportedFormat: {YUVNV12 YUVNV21 RGBA8888 }" + }, + { + "name": "query_contrast", + "value": "name:Contrast, supportedBufferType: {Texture Pixel }supportedFormat: {YUVNV12 YUVNV21 RGBA8888 }" + }, + { + "name": "query_crop", + "value": "name:Crop, supportedBufferType: {Pixel }supportedFormat: {unknown RGBA8888 }" + }, + + { + "name": "query_custom_brightness", + "value": "name:CustomBrightness, supportedBufferType: {Pixel }supportedFormat: {RGBA8888 }" + }, + { + "name": "query_custom_crop", + "value": "name:CustomCrop, supportedBufferType: {Pixel }supportedFormat: {RGBA8888 }" + }, + { + "name": "query_rgba_8888", + "value": "Filters:\nsize: 5, name: Brightness | Contrast | Crop | CustomBrightness | CustomCrop" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/media/icon.png b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..cd45accb1dfd2fd0da16c732c72faa6e46b26521 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/profile/test_pages.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/en_US/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8be430b5e5bc7bb24f695a48e997f056b99cee36 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/en_US/element/string.json @@ -0,0 +1,120 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + }, + { + "name": "btn_setting", + "value": "btn_setting" + }, + { + "name": "btn_dialog_confirm", + "value": "btn_dialog_confirm" + }, + { + "name": "btn_dialog_cancel", + "value": "btn_dialog_cancel" + }, + { + "name": "checkbox_brightness", + "value": "checkbox_brightness" + }, + { + "name": "slider_brightness", + "value": "slider_brightness" + }, + { + "name": "btn_apply", + "value": "btn_apply" + }, + { + "name": "btn_reset", + "value": "btn_reset" + }, + { + "name": "checkbox_contrast", + "value": "checkbox_contrast" + }, + { + "name": "slider_contrast", + "value": "slider_contrast" + }, + { + "name": "checkbox_crop", + "value": "checkbox_crop" + }, + { + "name": "slider_crop", + "value": "slider_crop" + }, + { + "name": "checkbox_custom", + "value": "checkbox_custom" + }, + { + "name": "slider_custom", + "value": "slider_custom" + }, + { + "name": "btn_search_brightness", + "value": "btn_search_brightness" + }, + { + "name": "filter_info_menu", + "value": "filter_info_menu" + }, + { + "name": "btn_search_contrast", + "value": "btn_search_contrast" + }, + { + "name": "btn_search_crop", + "value": "btn_search_crop" + }, + { + "name": "btn_search_custom", + "value": "btn_search_custom" + }, + { + "name": "btn_search", + "value": "btn_search" + }, + { + "name": "menu_category", + "value": "menu_category" + }, + { + "name": "menu_format", + "value": "menu_format" + }, + { + "name": "query_brightness", + "value": "name:Brightness, supportedBufferType: {Texture Pixel }supportedFormat: {YUVNV12 YUVNV21 RGBA8888 }" + }, + { + "name": "query_contrast", + "value": "name:Contrast" + }, + { + "name": "query_crop", + "value": "name:Crop" + }, + { + "name": "query_custom_brightness", + "value": "name:CustomBrightness" + }, + { + "name": "query_custom_crop", + "value": "name:CustomCrop" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/zh_CN/element/string.json b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..8fc7ab33d2bb72c822d1b43a3c2fb5980099d3b7 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/ohosTest/resources/zh_CN/element/string.json @@ -0,0 +1,100 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + }, + { + "name": "btn_setting", + "value": "btn_setting" + }, + { + "name": "btn_dialog_confirm", + "value": "btn_dialog_confirm" + }, + { + "name": "btn_dialog_cancel", + "value": "btn_dialog_cancel" + }, + { + "name": "checkbox_brightness", + "value": "checkbox_brightness" + }, + { + "name": "slider_brightness", + "value": "slider_brightness" + }, + { + "name": "btn_apply", + "value": "btn_apply" + }, + { + "name": "btn_reset", + "value": "btn_reset" + }, + { + "name": "checkbox_contrast", + "value": "checkbox_contrast" + }, + { + "name": "slider_contrast", + "value": "slider_contrast" + }, + { + "name": "checkbox_crop", + "value": "checkbox_crop" + }, + { + "name": "slider_crop", + "value": "slider_crop" + }, + { + "name": "checkbox_custom", + "value": "checkbox_custom" + }, + { + "name": "slider_custom", + "value": "slider_custom" + }, + { + "name": "btn_search_brightness", + "value": "btn_search_brightness" + }, + { + "name": "filter_info_menu", + "value": "filter_info_menu" + }, + { + "name": "btn_search_contrast", + "value": "btn_search_contrast" + }, + { + "name": "btn_search_crop", + "value": "btn_search_crop" + }, + { + "name": "btn_search_custom", + "value": "btn_search_custom" + }, + { + "name": "btn_search", + "value": "btn_search" + }, + { + "name": "menu_category", + "value": "menu_category" + }, + { + "name": "menu_format", + "value": "menu_format" + } + ] +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/test/List.test.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..a60c87c5cbb0badf7c3fd8975034590e6fafa992 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2025 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. + */ +import localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/entry/src/test/LocalUnit.test.ets b/code/DocsSample/Media/Image/ImageEffect/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..67e83a9c66754d010effd79867127595c74aa569 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025 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. + */ +import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest',() => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/hvigor/hvigor-config.json5 b/code/DocsSample/Media/Image/ImageEffect/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3c65000c6222235ea087259854b7254b4a89d7f0 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/hvigor/hvigor-config.json5 @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "modelVersion": "5.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "default", /* Define the build analyze mode. Value: [ "default" | "verbose" | false ]. Default: "default" */ + // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */ + // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */ + // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */ + // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */ + }, + "logging": { + // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */ + }, + "debugging": { + // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */ + }, + "nodeOptions": { + // "maxOldSpaceSize": 4096 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process */ + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/hvigorfile.ts b/code/DocsSample/Media/Image/ImageEffect/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b365cacd0191d3b1178eb6b9807b1ae0add6271 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/hvigorfile.ts @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2025 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. + */ +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} diff --git a/code/DocsSample/Media/Image/ImageEffect/oh-package.json5 b/code/DocsSample/Media/Image/ImageEffect/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..76e2eed90601dd2a7bea71c509c77de015b347f5 --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/oh-package.json5 @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 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. + */ +{ + "modelVersion": "5.0.0", + "name": "imageeffect", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.15", + "@ohos/hamock": "1.0.0" + } +} \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/ohosTest.md b/code/DocsSample/Media/Image/ImageEffect/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..51908a3daf88aa5c3df6511f17bec58e74aa55ad --- /dev/null +++ b/code/DocsSample/Media/Image/ImageEffect/ohosTest.md @@ -0,0 +1,24 @@ +# ImageEffect测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 | +|--------------------| ---------------- | ------------------------------------------------------------ | ------------------------------ | -------- | -------- | +| 拉起应用 | 设备正常运行 | | 成功拉起应用 | 是 | Pass | +| 申请相机权限 | 进入主界面 | 点击允许按钮 | 成功获取相机权限 | 是 | Pass | +| 关闭参数设置页面 | 进入参数设置页面 | 点击dialog取消按钮 | 成功关闭参数设置页面 | 是 | Pass | +| 亮度滤镜功能 | 进入参数设置页面 | 点击Brightness按钮,滑动Brightness滑动条,点击确认与Apply按钮 | 成功调节图片亮度 | 是 | Pass | +| 对比度滤镜功能 | 进入参数设置页面 | 点击Contrast按钮,滑动Contrast滑动条,点击确认与Apply按钮 | 成功调节图片对比度 | 是 | Pass | +| 裁剪滤镜功能 | 进入参数设置页面 | 点击Crop按钮,滑动Crop滑动条,点击确认与Apply按钮 | 成功裁剪图片大小 | 是 | Pass | +| 自定义亮度滤镜功能 | 进入参数设置页面 | 点击CustomBright按钮,滑动CustomBright滑动条,点击确认与Apply按钮 | CustomBright滤镜效果应用成功 | 是 | Pass | +| 自定义裁剪滤镜功能 | 进入参数设置页面 | 点击CustomCrop按钮,滑动CustomCrop滑动条,点击确认与Apply按钮 | CustomCrop滤镜效果应用成功 | 是 | Pass | +| 重置滤镜功能 | 进入主界面 | 点击Reset按钮 | 图像效果成功复原 | 是 | Pass | +| 查询Brightness滤镜信息 | 进入参数设置页面 | 点击Brightness滑动条旁的查询按钮 | 显示Brightness滤镜信息 | 是 | Pass | +| 查询Contrast滤镜信息 | 进入参数设置页面 | 点击Contrast滑动条旁的查询按钮 | 显示Contrast滤镜信息 | 是 | Pass | +| 查询Crop滤镜信息 | 进入参数设置页面 | 点击Crop滑动条旁的查询按钮 | 显示Crop滤镜信息 | 是 | Pass | +| 查询CustomBright滤镜信息 | 进入参数设置页面 | 点击CustomBright滑动条旁的查询按钮 | 显示CustomBright滤镜信息 | 是 | Pass | +| 查询CustomCrop滤镜信息 | 进入参数设置页面 | 点击CustomCrop滑动条旁的查询按钮 | 显示CustomCrop滤镜信息 | 是 | Pass | +| 根据编码格式查询滤镜信息 | 进入参数设置页面 | 点击查询按钮,点击编码格式 | 显示对应编码格式支持的滤镜信息 | 是 | Pass | +| 测试NativeBuffer输入场景 | 进入参数设置页面 | 切换输入场景为NativeBuffer,点击确认与Apply按钮 | 在NativeBuffer场景下成功应用滤镜链 | 是 | Pass | +| 测试URI输入场景 | 进入参数设置页面 | 切换输入场景为URI,点击确认与Apply按钮 | 在URI场景下成功应用滤镜链 | 是 | Pass | +| 测试NativeWindow输入场景 | 进入参数设置页面 | 切换输入场景为NativeWindow,点击确认与Apply按钮 | 在NativeWindow场景下成功应用滤镜链 | 是 | Pass | \ No newline at end of file diff --git a/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_Filter.jpeg b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_Filter.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..7b6f3d799ac3b6fdb1d000d19ee1e3bbb26154a6 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_Filter.jpeg differ diff --git a/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_FilterInfo.jpeg b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_FilterInfo.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c934fcff381c08a18025b7046081e1fb70d508f2 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_FilterInfo.jpeg differ diff --git a/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_InputType.jpeg b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_InputType.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..6f2fe216f55d6797841319d678b29c47365b3001 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_InputType.jpeg differ diff --git a/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_MainPage.jpeg b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_MainPage.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..abb78cd3218c4d47461e5faf2912d9b786ff1217 Binary files /dev/null and b/code/DocsSample/Media/Image/ImageEffect/screenshots/ImageEffect_MainPage.jpeg differ