> 技术文档 > 【入门到精通】鸿蒙next开发:基于ImageEffect为相机预览添加滤镜_鸿蒙next c++ 实现相机功能,打开动态图片设置,预览卡住

【入门到精通】鸿蒙next开发:基于ImageEffect为相机预览添加滤镜_鸿蒙next c++ 实现相机功能,打开动态图片设置,预览卡住


往期鸿蒙5.0全套实战文章必看:(文中附带全栈鸿蒙5.0学习资料)

  • 鸿蒙开发核心知识点,看这篇文章就够了

  • 最新版!鸿蒙HarmonyOS Next应用开发实战学习路线

  • 鸿蒙HarmonyOS NEXT开发技术最全学习路线指南

  • 鸿蒙应用开发实战项目,看这一篇文章就够了(部分项目附源码)


基于ImageEffect为相机预览添加滤镜

场景描述:

使用系统ImageEffect提供的图片编辑能力,模拟系统相机滤镜功能,实现自定义相机预览过程中动态开启关闭的滤镜效果。

其中滤镜效果分为两类:

1、系统提供的滤镜(亮度、对比度、裁剪等)。

2、自定义滤镜效果(例如黑白滤镜)。

效果展示:

57.png

方案描述:

1、自定义相机预览场景中XComponent组件为相机预览流提供SurfaceId,调用相机初始化的napi接口传入到native侧。

2、创建ImageEffect对象,向滤镜链路中添加系统自带的滤镜以及自定义滤镜。

3、在native c++层将SurfaceId转换成OHNativeWindow,并调OH_ImageEffect_SetOutputSurface设置输出显示的OHNativeWindow。

4、从ImageEffect中获取输入的OHNativeWindow,再从OHNativeWindow中获取到新的SurfaceId。

5、用获取到的新SurfaceId创建相机预览流,完成将数据链路由 相机 -> XComponent改为相机->ImageEffect 滤镜链路 -> XComponent。

步骤及关键代码:

1、ArkTS侧从XComponent中获取surfaceId,传入native侧:

XComponent(this.options) .onLoad(async () => { Logger.info(TAG, \'onLoad is called\'); this.surfaceId = this.mXComponentController.getXComponentSurfaceId(); Logger.info(TAG, `onLoad surfaceId: ${this.surfaceId}`); Logger.info(TAG, `initCamera start`); cameraNapi.initCamera(this.surfaceId, this.settingDataObj.focusMode, this.cameraDeviceIndex); Logger.info(TAG, `initCamera end`); })

2、创建ImageEffect对象,并添加滤镜到滤镜链中:

imageEffect = OH_ImageEffect_Create(\"imageEdit\");

a. 添加系统自带的亮度滤镜:

static void AddSystemFilter() { OH_EffectFilter *filter = OH_ImageEffect_AddFilter(imageEffect, OH_EFFECT_BRIGHTNESS_FILTER); // 设置滤镜参数, 例如:滤镜强度设置为50。 ImageEffect_Any value = {.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_FLOAT, .dataValue.floatValue = 50.f}; OH_EffectFilter_SetValue(filter, OH_EFFECT_FILTER_INTENSITY_KEY, &value); }

b. 添加自定义滤镜:

bool OnApplyRGBA8888(OH_EffectBufferInfo *src) { void *addr = nullptr; OH_EffectBufferInfo_GetAddr(src, &addr); auto *srcRgb = (unsigned char *)addr; int32_t width = 0; OH_EffectBufferInfo_GetWidth(src, &width); int32_t height = 0; OH_EffectBufferInfo_GetHeight(src, &height); int32_t rowStride = 0; OH_EffectBufferInfo_GetRowSize(src, &rowStride); for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { for (int i = 0; i < 4; ++i) {  uint32_t index = rowStride * y + x * sizeof(int) + i;  srcRgb[index] = (i == 3) ? 255 : srcRgb[index]; } } } return true; } bool OnApplyYUVNV(OH_EffectBufferInfo *src) { void *buffer = nullptr; OH_EffectBufferInfo_GetAddr(src, &buffer); int32_t width = 0; OH_EffectBufferInfo_GetWidth(src, &width); int32_t height = 0; OH_EffectBufferInfo_GetHeight(src, &height); int32_t rowStride = 0; OH_EffectBufferInfo_GetRowSize(src, &rowStride); memset((unsigned char *)buffer + rowStride * height, 0, rowStride * height / 2); return true; } static bool Apply(OH_EffectFilter *filter, OH_EffectBufferInfo *src, OH_EffectFilterDelegate_PushData pushData) { ImageEffect_Format format = ImageEffect_Format::EFFECT_PIXEL_FORMAT_UNKNOWN; OH_EffectBufferInfo_GetEffectFormat(src, &format); LOG_E(\"ccy: format %{public}d\", format); bool result = true; switch (format) { case ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888: result = OnApplyRGBA8888(src); break; case ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV12: case ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV21: result = OnApplyYUVNV(src); break; default: LOG_E(\"format not support! format=%{public}d\", format); result = false; break; } pushData(filter, src); return result; } static OH_EffectFilter *Restore(const char *info) { return nullptr; } static void AddCustomFilter() { // 自定义算子能力信息 OH_EffectFilterInfo *filterInfo = OH_EffectFilterInfo_Create(); OH_EffectFilterInfo_SetFilterName(filterInfo, \"CustomEFilter\"); ImageEffect_BufferType bufferType = ImageEffect_BufferType::EFFECT_BUFFER_TYPE_PIXEL; OH_EffectFilterInfo_SetSupportedBufferTypes(filterInfo, sizeof(bufferType) / sizeof(ImageEffect_BufferType), &bufferType); ImageEffect_Format format[] = { ImageEffect_Format::EFFECT_PIXEL_FORMAT_RGBA8888, ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV12, ImageEffect_Format::EFFECT_PIXEL_FORMAT_NV21, }; OH_EffectFilterInfo_SetSupportedFormats(filterInfo, sizeof(format) / sizeof(ImageEffect_Format), format); // 自定义算子实现接口 delegate = { .setValue = [](OH_EffectFilter *filter, const char *key, const ImageEffect_Any *value) { return true; }, .render = [](OH_EffectFilter *filter, OH_EffectBufferInfo *src, OH_EffectFilterDelegate_PushData pushData) { return Apply(filter, src, pushData); }, .save = [](OH_EffectFilter *filter, char **info) { return true; }, .restore = [](const char *info) { return Restore(info); }}; OH_EffectFilter_Register(filterInfo, &delegate); OH_EffectFilterInfo_Release(filterInfo); OH_ImageEffect_AddFilter(imageEffect, \"CustomEFilter\"); }

3、native侧获取XComponent组件提供的surfaceId,并调用ImageEffect接口获取新的surfaceId

static napi_value GetImageEffectSurfaceId(napi_env env, napi_callback_info info) { size_t argc = 1; napi_value args[1] = {nullptr}; napi_value result; size_t surfaceIdLen = 0; char *xComponentSurfaceId = nullptr; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); napi_get_value_string_utf8(env, args[0], nullptr, 0, &surfaceIdLen); xComponentSurfaceId = new char[surfaceIdLen + 1]; napi_get_value_string_utf8(env, args[0], xComponentSurfaceId, surfaceIdLen + 1, &surfaceIdLen); ImageEffect_ErrorCode errorCode; imageEffect = OH_ImageEffect_Create(\"effectDemo\"); ImageEffect_Any runningType{.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_INT32, .dataValue.int32Value = 2}; errorCode = OH_ImageEffect_Configure(imageEffect, \"runningType\", &runningType); // 添加滤镜,获取 OH_EffectFilter 实例。多次调用该接口可以添加多个滤镜,组成滤镜链。 AddSystemFilter(); AddCustomFilter(); // 根据SurfaceId创建NativeWindow,注意创建出来的NativeWindow在使用结束后需要主动调用OH_NativeWindow_DestoryNativeWindow进行释放。 uint64_t outputSurfaceId; std::istrstream iss(xComponentSurfaceId); iss >> outputSurfaceId; OHNativeWindow *outputNativeWindow = nullptr; OH_NativeWindow_CreateNativeWindowFromSurfaceId(outputSurfaceId, &outputNativeWindow); // 设置输出显示的Surface。 errorCode = OH_ImageEffect_SetOutputSurface(imageEffect, outputNativeWindow); ReleaseNativeWindow(outputNativeWindow); // 获取输入的Surface。注意获取的inputNativeWindow在使用结束后需要主动调用OH_NativeWindow_DestoryNativeWindow进行释放。 OHNativeWindow *inputNativeWindow = nullptr; errorCode = OH_ImageEffect_GetInputSurface(imageEffect, &inputNativeWindow); // 从获取到输入的NativeWindow中获取SurfaceId。 uint64_t inputSurfaceId = 0; OH_NativeWindow_GetSurfaceId(inputNativeWindow, &inputSurfaceId); ReleaseNativeWindow(inputNativeWindow); // 将SurfaceId转成字符串进行返回。 std::string inputSurfaceIdStr = std::to_string(inputSurfaceId); napi_create_string_utf8(env, inputSurfaceIdStr.c_str(), inputSurfaceIdStr.length(), &result); return result; }

4、将获取到的SurfaceId通过构造函数传递给相机框架,调用相机接口启动预览:

NDKCamera::NDKCamera(char *str, uint32_t focusMode, uint32_t cameraDeviceIndex) : previewSurfaceId_(str), cameras_(nullptr), focusMode_(focusMode), cameraDeviceIndex_(cameraDeviceIndex), cameraOutputCapability_(nullptr), cameraInput_(nullptr), captureSession_(nullptr), size_(0), isCameraMuted_(nullptr), profile_(nullptr), photoSurfaceId_(nullptr), previewOutput_(nullptr), photoOutput_(nullptr), metaDataObjectType_(nullptr), metadataOutput_(nullptr), isExposureModeSupported_(false), isFocusModeSupported_(false), exposureMode_(EXPOSURE_MODE_LOCKED), minExposureBias_(0), maxExposureBias_(0), step_(0), ret_(CAMERA_OK) { valid_ = false; ReleaseCamera(); Camera_ErrorCode ret = OH_Camera_GetCameraManager(&cameraManager_); if (cameraManager_ == nullptr || ret != CAMERA_OK) { OH_LOG_ERROR(LOG_APP, \"Get CameraManager failed.\"); } ret = OH_CameraManager_CreateCaptureSession(cameraManager_, &captureSession_); if (captureSession_ == nullptr || ret != CAMERA_OK) { OH_LOG_ERROR(LOG_APP, \"Create captureSession failed.\"); } CaptureSessionRegisterCallback(); GetSupportedCameras(); GetSupportedOutputCapability(); CreatePreviewOutput(); CreateCameraInput(); CameraInputOpen(); CameraManagerRegisterCallback(); SessionFlowFn(); valid_ = true; } Camera_ErrorCode NDKCamera::CreatePreviewOutput(void) { profile_ = cameraOutputCapability_->previewProfiles[0]; if (profile_ == nullptr) { OH_LOG_ERROR(LOG_APP, \"Get previewProfiles failed.\"); return CAMERA_INVALID_ARGUMENT; } ret_ = OH_CameraManager_CreatePreviewOutput(cameraManager_, profile_, previewSurfaceId_, &previewOutput_); if (previewSurfaceId_ == nullptr || previewOutput_ == nullptr || ret_ != CAMERA_OK) { OH_LOG_ERROR(LOG_APP, \"CreatePreviewOutput failed.\"); return CAMERA_INVALID_ARGUMENT; } return ret_; }

5、点击开启/关闭按钮,进行滤镜效果的开启和关闭:

Image($r(\'app.media.camera_filter\')) .width(px2vp(Constants.ICON_SIZE)) .height(px2vp(Constants.ICON_SIZE)) .onClick(async () => { if (!this.filterIsOpen) { cameraDemo.startImageEffect(); } else { cameraDemo.stopImageEffect(); } this.filterIsOpen = !this.filterIsOpen; })static napi_value StartImageEffect(napi_env env, napi_callback_info info) { napi_value result; ImageEffect_ErrorCode errCode = OH_ImageEffect_Start(imageEffect); int ret = errCode != ImageEffect_ErrorCode::EFFECT_SUCCESS ? -1 : 0; napi_create_int32(env, ret, &result); return result; } static napi_value StopImageEffect(napi_env env, napi_callback_info info) { napi_value result; ImageEffect_ErrorCode errCode = OH_ImageEffect_Stop(imageEffect); int ret = errCode != ImageEffect_ErrorCode::EFFECT_SUCCESS ? -1 : 0; napi_create_int32(env, ret, &result); return result; }

注意事项

在使用提亮滤镜和自定义滤镜的过程中,经验证发现:

1、在滤镜链中仅加入提亮滤镜时,需要使用如下代码配置ImageEffect的runningtype,使其通过cpu执行,否则会在相机预览surface模式下,画面卡死:

OH_EffectFilter *filter = OH_ImageEffect_AddFilter(imageEffect, OH_EFFECT_BRIGHTNESS_FILTER);

2、在滤镜链中同时存在自定义滤镜和提亮滤镜的时候,不进行上述配置,先添加提亮滤镜,再添加自定义滤镜,画面卡死。只有先添加自定义滤镜,再添加提亮滤镜,画面正常:

ImageEffect_Any runningType{.dataType = ImageEffect_DataType::EFFECT_DATA_TYPE_INT32, .dataValue.int32Value = 2}; errorCode = OH_ImageEffect_Configure(imageEffect, \"runningType\", &runningType);