HarmonyOS 鸿蒙Next 自定义相机预览拉伸问题

发布于 1周前 作者 vueper 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 自定义相机预览拉伸问题

自定义相机的时候,想要实现竖屏全屏预览,但是预览画面总是被拉伸。

设备的屏幕宽高为 1260 * 2720

previewProfilesArray 获取的当前设备所支持的预览分辨率都是宽大于高的,如何设置,可以使预览画面不拉伸

以下是获取到当前设备所支持的预览分辨率(宽 * 高)

640 * 640
864 * 480
1088 * 1080
1088 * 1088
1136 * 480
1280 * 592
1280 * 720
1280 * 960
1440 * 1080
1920 * 1080
1920 * 1440
2048 * 1536
2336 * 1080
2560 * 1080
2592 * 1944


更多关于HarmonyOS 鸿蒙Next 自定义相机预览拉伸问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

相机的预览和拍照的分辨率是有固定的范围的,他的宽高比相当于屏幕的高宽比。一般都是以相机的为准。然后就是XComponent的宽高比和相机一样就行,当设置XComponent的尺寸为满屏,setXComponentSurfaceRect的宽高大于屏幕,可以了满足需求了 参考如下demo,全屏显示,没有变形


import { camera } from '@kit.CameraKit';

import { BusinessError } from '@kit.BasicServicesKit';

import { image } from '@kit.ImageKit';

import { buffer } from '@kit.ArkTS';

import { photoAccessHelper } from '@kit.MediaLibraryKit';

import { abilityAccessCtrl, PermissionRequestResult, Permissions } from '@kit.AbilityKit';

import { display, promptAction } from '@kit.ArkUI';

import fs from '@ohos.file.fs';

const TAG = '[CameraDemo]';

@Entry

@Component

struct CameraDemo {

  context: Context = getContext(this) as Context;

  @State pixelMap: image.PixelMap | undefined = undefined;

  @State finalPixelMap: image.PixelMap | undefined = undefined;

  @State buffer: ArrayBuffer | undefined = undefined;

  @State surfaceId: string = '';

  @State hasPicture: boolean = false;

  @State fileNames: string[] = [];

  @State imageSize: image.Size = { width: 1920, height: 1080 };

  @State saveButtonOptions: SaveButtonOptions = {

    icon: SaveIconStyle.FULL_FILLED,

    text: SaveDescription.SAVE_IMAGE,

    buttonType: ButtonType.Capsule

  } // 设置安全控件按钮属性

  private mXComponentController: XComponentController = new XComponentController;

  private cameraManager: camera.CameraManager | undefined = undefined;

  private cameraSession: camera.PhotoSession | undefined = undefined;

  private photoOutput: camera.PhotoOutput | undefined = undefined;

  private cameraInput: camera.CameraInput | undefined = undefined;

  private previewOutput: camera.PreviewOutput | undefined = undefined;

  private previewOutput2: camera.PreviewOutput | undefined = undefined;

  private imageReceiver: image.ImageReceiver | undefined = undefined;

  @State ca :number  = 0

  @State screenWidth :number  = 0

  @State screenHeight :number  = 0

  @State base64Str :string  = ''

  aboutToAppear(): void {

    let permissions: Array<Permissions> = [

      'ohos.permission.CAMERA',

      'ohos.permission.WRITE_MEDIA',

      'ohos.permission.READ_MEDIA',

      'ohos.permission.MEDIA_LOCATION',

    ];

    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();

    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗

    atManager.requestPermissionsFromUser(this.context, permissions).then((data: PermissionRequestResult) => {

      let grantStatus: Array<number> = data.authResults;

      let length: number = grantStatus.length;

      for (let i = 0; i < length; i++) {

        if (grantStatus[i] != 0) {

          // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限

          return;

        }

      }

      console.info(`${TAG} Success to request permissions from user. authResults is ${grantStatus}.`);

    }).catch((err: BusinessError) => {

      console.error(`${TAG} Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);

    })

    let displaydef = display.getDefaultDisplaySync()

    this.screenWidth = displaydef.width

    this.screenHeight = displaydef.height

  }

  createCameraManager() {

    // 创建CameraManager对象

    let cameraManager: camera.CameraManager = camera.getCameraManager(this.context);

    if (!cameraManager) {

      console.error('CameraDemo camera.getCameraManager error');

      return;

    }

    this.cameraManager = cameraManager;

    // 监听相机状态变化

    this.cameraManager.on('cameraStatus', (err: BusinessError, cameraStatusInfo: camera.CameraStatusInfo) => {

      console.info(`CameraDemo camera: ${cameraStatusInfo.camera.cameraId}, status: ${cameraStatusInfo.status}`);

    });

  }

  async onPageShow(){

    await this.prepareCamera(this.ca);

  }

  build() {

    Column() {

      Stack(){

        XComponent({

          id: '',

          type: 'surface',

          libraryname: '',

          controller: this.mXComponentController

        })

          .onLoad(() => {

            // 设置Surface宽高(1920*1080),预览尺寸设置参考前面 previewProfilesArray 获取的当前设备所支持的预览分辨率大小去设置

            // 预览流与录像输出流的分辨率的宽高比要保持一致

            this.mXComponentController.setXComponentSurfaceRect({offsetX:0,offsetY:0, surfaceWidth: this.screenHeight*1080/1920.0, surfaceHeight: this.screenHeight});

            // 获取Surface ID

            this.surfaceId = this.mXComponentController.getXComponentSurfaceId();

            setTimeout(async () => {

              await this.prepareCamera(this.ca);

            }, 500);

          })

          .width('100%')

          .height('100%')

          .backgroundColor(Color.Gray)

        Column(){

          Button('拍照').width(60).height(60).margin({ right: 10 }).borderRadius(30)

            .onClick(async () => {

              let photoCaptureSetting: camera.PhotoCaptureSetting = {

                quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高

                rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0

              }

              // 1、通过拍照流实现:点击拍照

              await this.photoOutput?.capture(photoCaptureSetting).catch((error: BusinessError) => {

                console.error(`CameraDemo Failed to capture the photo ${error.message}`); //不符合条件则进入

              })

              this.hasPicture = true;

            })

          Row(){

            Button('前置摄像头')

              .size({ width: 100, height: 30 }).margin({ right: 10 })

              .onClick(async () => {

                this.ca=1

                console.debug('打开前置摄像头')

                await this.prepareCamera(this.ca);

              })

            Button('后置摄像头')

              .size({ width: 100, height: 30 }).margin({ left: 10 })

              .onClick(async () => {

                this.ca=0

                console.debug('打开后置摄像头')

                await this.prepareCamera(this.ca);

              })

          }

        }

      }

    }

    .width('100%')

    .height('100%')

    .backgroundColor('#FFFFFF')

  }

  async prepareCamera(ca:Number) {

    this.releaseCamera()

    this.createCameraManager();

    if (!this.cameraManager) {

      console.error('CameraDemo cameraManager is undefined.')

      return;

    }

    // 获取支持指定的相机设备对象

    let cameraDevices: Array<camera.CameraDevice> = [];

    let CameraDevice:camera.CameraDevice

    try {

      cameraDevices = this.cameraManager.getSupportedCameras();

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo The getSupportedCameras call failed. error: ${JSON.stringify(err)}`)

    }

    cameraDevices.forEach((cameraDevice: camera.CameraDevice) => {

      console.info(`CameraDemo cameraId: ${cameraDevice.cameraId}, cameraPosition: ${cameraDevice.cameraPosition.toString()}, cameraType: ${cameraDevice.cameraType.toString()}, connectionType: ${cameraDevice.connectionType.toString()}`)

    })

    // 创建相机输入流

    try {

      if (ca==0) {

        CameraDevice = cameraDevices[0]

      }else {

        CameraDevice = cameraDevices[1]

      }

      this.cameraInput = this.cameraManager.createCameraInput(CameraDevice);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo createCaptureSession error. error: ${JSON.stringify(err)}`);

      return

    }

    // 监听cameraInput错误信息

    this.cameraInput.on('error', CameraDevice, (error: BusinessError) => {

      console.error(`CameraDemo Camera input error: ${JSON.stringify(error)}`);

    });

    // 打开相机

    try {

      await this.cameraInput.open();

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo cameraInput open error. error: ${JSON.stringify(err)}`);

    }

    // 获取指定的相机设备对象支持的模式

    let cameraSceneModes: Array<camera.SceneMode> = [];

    try {

      cameraSceneModes = this.cameraManager.getSupportedSceneModes(CameraDevice);

      cameraSceneModes.forEach((cameraSceneMode: camera.SceneMode) => {

        console.info(`CameraDemo cameraSceneMode: ${cameraSceneMode.toString()}`)

      })

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo The getSupportedSceneModes call failed. error: ${JSON.stringify(err)}`)

    }

    // 获取相机设备支持的输出流能力

    let cameraOutputCapability: camera.CameraOutputCapability = this.cameraManager.getSupportedOutputCapability(CameraDevice, camera.SceneMode.NORMAL_PHOTO)

    if (!cameraOutputCapability) {

      console.error('CameraDemo cameraManager.getSupportedOutputCapability error');

      return;

    }

    this.printCameraOutputCapability(cameraOutputCapability);

    let previewProfile = cameraOutputCapability.previewProfiles[0];

    cameraOutputCapability.previewProfiles.forEach((profile) => {

      if (profile.size.width == this.imageSize.width && profile.size.height == this.imageSize.height) {

        previewProfile = profile;

        return;

      }

    })

    this.imageSize = previewProfile.size;

    // 创建相机预览输出流

    try {

      this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.surfaceId);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo createCaptureSession error. error: ${JSON.stringify(err)}`);

      return

    }

    // 监听previewOutput错误信息

    this.previewOutput.on('error', (error: BusinessError) => {

      console.error(`CameraDemo previewOutput error: ${JSON.stringify(error)}`);

    });

    // 创建拍照输出流

    try {

      let photoProfile = cameraOutputCapability.photoProfiles[0];

      cameraOutputCapability.photoProfiles.forEach((profile) => {

        if (profile.size.width == this.imageSize.width && profile.size.height == this.imageSize.height) {

          photoProfile = profile;

          return;

        }

      })

      this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo createPhotoOutput error ${JSON.stringify(err)}`);

    }

    if (this.photoOutput === undefined) {

      console.error('CameraDemo photoOutput is undefined.');

      return;

    }

    //调用上面的回调函数来保存图片

    this.setPhotoOutputCb(this.photoOutput);

    // 创建相机会话

    try {

      this.cameraSession = this.cameraManager.createSession<camera.PhotoSession>(camera.SceneMode.NORMAL_PHOTO);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo createCaptureSession error. error: ${JSON.stringify(err)}`);

      return

    }

    // 监听session错误信息

    this.cameraSession.on('error', (error: BusinessError) => {

      console.error(`CameraDemo Capture session error: ${JSON.stringify(error)}`);

    });

    // 开始会话配置

    try {

      this.cameraSession.beginConfig()

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo beginConfig error. error: ${JSON.stringify(err)}`);

    }

    // 向会话中添加相机输入流

    try {

      this.cameraSession.addInput(this.cameraInput)

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo addInput error. error: ${JSON.stringify(err)}`);

    }

    // 向会话中添加预览输出流

    try {

      this.cameraSession.addOutput(this.previewOutput)

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo add previewOutput error. error: ${JSON.stringify(err)}`);

    }

    // 向会话中添加拍照输出流

    try {

      this.cameraSession.addOutput(this.photoOutput);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo add photoOutput error. error: ${JSON.stringify(err)}`);

    }

    // 提交会话配置

    try {

      await this.cameraSession.commitConfig();

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo captureSession commitConfig error: ${JSON.stringify(err)}`);

    }

    // 启动会话

    try {

      await this.cameraSession.start();

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo captureSession start error: ${JSON.stringify(err)}`);

    }

    // 配置相机的参数可以调整拍照的一些功能,包括闪光灯、变焦、焦距等。

    this.configuringSession(this.cameraSession)

  }

  configuringSession(photoSession: camera.PhotoSession): void {

    // 判断设备是否支持闪光灯

    let flashStatus: boolean = false;

    try {

      flashStatus = photoSession.hasFlash();

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo Failed to hasFlash. error: ${JSON.stringify(err)}`);

    }

    console.info(`CameraDemo Returned with the flash light support status: ${flashStatus}`);

    if (flashStatus) {

      // 判断是否支持自动闪光灯模式

      let flashModeStatus: boolean = false;

      try {

        let status: boolean = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO);

        flashModeStatus = status;

      } catch (error) {

        let err = error as BusinessError;

        console.error(`CameraDemo Failed to check whether the flash mode is supported. error: ${JSON.stringify(err)}`);

      }

      if (flashModeStatus) {

        // 设置自动闪光灯模式

        try {

          photoSession.setFlashMode(camera.FlashMode.FLASH_MODE_AUTO);

        } catch (error) {

          let err = error as BusinessError;

          console.error(`CameraDemo Failed to set the flash mode. error: ${JSON.stringify(err)}`);

        }

      }

    }

    // 判断是否支持连续自动变焦模式

    let focusModeStatus: boolean = false;

    try {

      let status: boolean = photoSession.isFocusModeSupported(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);

      focusModeStatus = status;

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo Failed to check whether the focus mode is supported. error: ${JSON.stringify(err)}`);

    }

    if (focusModeStatus) {

      // 设置连续自动变焦模式

      try {

        photoSession.setFocusMode(camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO);

      } catch (error) {

        let err = error as BusinessError;

        console.error(`CameraDemo Failed to set the focus mode. error: ${JSON.stringify(err)}`);

      }

    }

    // 获取相机支持的可变焦距比范围

    let zoomRatioRange: Array<number> = [];

    try {

      zoomRatioRange = photoSession.getZoomRatioRange();

      zoomRatioRange.forEach(( index: number) => {

        console.info(`zoomRatioRange 支持的焦距: [${index}]`);

      })

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo Failed to get the zoom ratio range. error: ${JSON.stringify(err)}`);

    }

    if (zoomRatioRange.length <= 0) {

      return;

    }

    // 设置可变焦距比

    try {

      photoSession.setZoomRatio(1);

    } catch (error) {

      let err = error as BusinessError;

      console.error(`CameraDemo Failed to set the zoom ratio value. error: ${JSON.stringify(err)}`);

    }

  }

  setPhotoOutputCb(photoOutput: camera.PhotoOutput) {

    //设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中

    photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {

      console.info(`CameraDemo getPhoto start. err: ${JSON.stringify(errCode)}`);

      if (errCode || photo === undefined || photo.main === undefined) {

        console.error('CameraDemo getPhoto failed');

        return;

      }

      let imageObj = photo.main;

      imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component): Promise<void> => {

        console.info('CameraDemo getComponent start');

        if (errCode || component === undefined) {

          console.error('CameraDemo getComponent failed');

          return;

        }

        let imageBuffer: ArrayBuffer;

        if (component.byteBuffer) {

          imageBuffer = component.byteBuffer;

          this.buffer = imageBuffer;

          this.base64Str = "data:image/jpeg;base64,"+ buffer.from(imageBuffer).toString('base64')

          let sourceOptions: image.SourceOptions = {

            sourceDensity: 0, // 在不确定当前密度时传0

            sourcePixelFormat: image.PixelMapFormat.RGBA_8888,

            sourceSize: this.imageSize

          }

          let imageSource: image.ImageSource = image.createImageSource(imageBuffer, sourceOptions);

          let opts: image.InitializationOptions = {

            editable: false,

            pixelFormat: image.PixelMapFormat.RGBA_8888,

            size: this.imageSize

          }

          let pixelMap = await imageSource.createPixelMap(opts);

          this.finalPixelMap = pixelMap;

        } else {

          console.error('CameraDemo byteBuffer is null');

          return;

        }

      });

    });

  }

  printCameraOutputCapability(cameraOutputCapability: camera.CameraOutputCapability) {

    let previewProfileArr: Array<camera.Profile> = cameraOutputCapability.previewProfiles;

    let photoProfileArr: Array<camera.Profile> = cameraOutputCapability.photoProfiles;

    let videoProfileArr: Array<camera.VideoProfile> = cameraOutputCapability.videoProfiles;

    let supportedMetadataObjectTypeArr: Array<camera.MetadataObjectType> = cameraOutputCapability.supportedMetadataObjectTypes;

    previewProfileArr.forEach((value: camera.Profile, index: number) => {

      console.info(`CameraDemo 支持的预览尺寸: [${value.size.width},${value.size.height}]`);

    })

    photoProfileArr.forEach((value: camera.Profile, index: number) => {

      console.info(`CameraDemo 支持的拍照尺寸: [${value.size.width},${value.size.height}]`);

    })

    videoProfileArr.forEach((value: camera.VideoProfile, index: number) => {

      console.info(`CameraDemo 支持的录像尺寸: [${value.size.width},${value.size.height}], 支持的帧率范围: [${value.frameRateRange.min},${value.frameRateRange.max}]`);

    })

    supportedMetadataObjectTypeArr.forEach((value: camera.MetadataObjectType, index: number) => {

      console.info(`CameraDemo 支持的metadata流类型: ${value}`);

    })

  }

  releaseCamera() {

    if (this.cameraInput) {

      this.cameraInput.close()

    }

    if (this.previewOutput) {

      this.previewOutput.release()

    }

    if (this.photoOutput) {

      this.photoOutput.release()

    }

    if (this.cameraSession) {

      this.cameraSession.release()

    }

  }

}

更多关于HarmonyOS 鸿蒙Next 自定义相机预览拉伸问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next系统中,处理自定义相机预览拉伸问题通常涉及调整预览画面的Aspect Ratio(宽高比)和裁剪策略。以下是一些核心步骤:

  1. 获取相机预览尺寸:首先,通过相机管理器获取当前相机预览支持的尺寸列表,选择适合应用需求的尺寸。

  2. 设置预览Surface尺寸:根据选定的预览尺寸,设置预览SurfaceView或TextureView的尺寸,确保预览画面不会因尺寸不匹配而拉伸。

  3. 调整预览画面Aspect Ratio:在预览Surface上,根据相机预览的宽高比设置相应的布局参数,如使用FrameLayout包裹预览Surface,并通过设置LayoutParams的宽高比来保持预览画面的原始比例。

  4. 裁剪预览画面:如果预览Surface的宽高比与设备屏幕的宽高比不一致,可通过设置CameraCaptureSession的输出裁剪区域,确保预览画面在屏幕上正确显示,不产生拉伸效果。

  5. 处理旋转和缩放:考虑设备方向变化对预览画面的影响,通过调整预览画面的旋转角度和缩放比例,确保预览效果的一致性和正确性。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部