场景描述

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

其中滤镜效果分为两类:

  1. 系统提供的滤镜(亮度、对比度、裁剪等)。
  2. 自定义滤镜效果(例如黑白滤镜)。

效果展示:

方案描述

  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");
    1. 添加系统自带的亮度滤镜:
      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);
      }
    2. 添加自定义滤镜:
      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);

     

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐