HarmonyOS 鸿蒙Next 有没有自定义相机的demo

发布于 1周前 作者 bupafengyu 最后一次编辑是 5天前 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 有没有自定义相机的demo

有没有自定义相机的demo

2 回复

这边提供一个完整的demo,包括图片裁剪,相机拍照,已经相册选图功能。

本项目基于OpenHarmony三方库 [ImageKnife] 进行场景开发使用:

支持不同类型的本地与网络图片展示。

支持磁拍照展示与图库照片选择展示。

支持进行图片变换: 支持图像像素源图片变换效果。

需要权限

1、ohos.permission.INTERNET

2、ohos.permission.MEDIA_LOCATION

3、ohos.permission.READ_MEDIA

4、ohos.permission.WRITE_MEDIA

下载安装

ohpm install [@ohos](/user/ohos)/imageknife

moudle.json 权限代码
 "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION",
        "reason": "$string:app_permission_MEDIA_LOCATION",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:app_permission_READ_MEDIA",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.WRITE_MEDIA",
        "reason": "$string:app_permission_WRITE_MEDIA",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
entryability页面代码:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { ImageKnife } from '[@ohos](/user/ohos)/imageknife'
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onCreate');
    AppStorage.setOrCreate('bundleName',this.context.abilityInfo.bundleName)
  }
  onDestroy(): void {
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onDestroy');
  }
  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x1314, 'ImageDemo', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x1314, 'ImageDemo', 'Succeeded in loading the content.');
    });
    // 初始化全局ImageKnife
    ImageKnife.with(this.context);
  }
  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onWindowStageDestroy');
  }
  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onForeground');
  }
  onBackground(): void {
    // Ability has back to background
    hilog.info(0x1314, 'ImageDemo', '%{public}s', 'Ability onBackground');
  }
}
ets下common文件夹下Constants代码1:
import { HashMap } from '@kit.ArkTS'
import { Tabar } from '../model/Tabar'
export const localImageResource: HashMap<number, ResourceStr> = new HashMap()
localImageResource.set(0, $r('app.media.jpgSample'))
localImageResource.set(1, $r('app.media.pngSample'))
localImageResource.set(2, $r('app.media.svgSample'))
localImageResource.set(3, $r('app.media.gifSample'))
localImageResource.set(4, $r('app.media.bmpSample'))
localImageResource.set(5, $r('app.media.webpSample'))
export const netImageResource: HashMap<number, ResourceStr> = new HashMap()
netImageResource.set(0, 'https://img-home.csdnimg.cn/images/20240327093300.jpg')
netImageResource.set(1, 'https://img-blog.csdnimg.cn/20191215043500229.png')
netImageResource.set(2, 'https://img13.360buyimg.com/n1/jfs/t1/220646/38/10395/30916/61d6e061E1a6d91c8/c0a9a67e726dd7a4.jpg.dpg')
netImageResource.set(3, 'https://gd-hbimg.huaban.com/e0a25a7cab0d7c2431978726971d61720732728a315ae-57EskW_fw658')
netImageResource.set(4, 'https://img-blog.csdn.net/20140514114029140')
netImageResource.set(5, 'https://hbimg.huabanimg.com/95a6d37a39aa0b70d48fa18dc7df8309e2e0e8e85571e-x4hhks_fw658/format/webp')
export const BUNDLE_NAME: string = 'com.harmonyos.image'
export const TAG: string = 'ImageDemo';
export const tabars: Array<Tabar> = [
  {
    name: '裁剪',
    selectN: $r('app.media.ic_pc_cut'),
    selectY: $r('app.media.ic_pc_cut_select')
  },
  {
    name: '亮度',
    selectN: $r('app.media.ic_public_brightness'),
    selectY: $r('app.media.ic_public_brightness_filled')
  },
  {
    name: '对比度',
    selectN: $r('app.media.contrast_ratio'),
    selectY: $r('app.media.contrast_ratio_selected')
  },
  {
    name: '旋转',
    selectN: $r('app.media.ic_switch'),
    selectY: $r('app.media.ic_switch_selected')
  }
]
export class Constants {
  /**
   * Title row width.
   */
  static readonly TITLE_ROW_WEIGHT: string = '50%';
  /**
   * Layout full screen.
   */
  static readonly LAYOUT_FULL_SCREEN: string = '100%';
  /**
   * Edit page height.
   */
  static readonly EDIT_PAGE_HEIGHT: string = '26%';
  /**
   * Image show height.
   */
  static readonly IMAGE_SHOW_HEIGHT: string = '68%';
  /**
   * Slider width.
   */
  static readonly SLIDER_WIDTH: string = '80%';
  /**
   * Loading width and height.
   */
  static readonly LOADING_WH: string = '30%';
/**
   * Clock wise.
   */
  static readonly CLOCK_WISE: number = 90;
  /**
   * Anti clock.
   */
  static readonly ANTI_CLOCK: number = -90;
  /**
   * Tab menu width.
   */
  static readonly TAB_MENU_WIDTH: number = 18;
  /**
   * Navigation height.
   */
  static readonly NAVIGATION_HEIGHT: number = 56;
  /**
   * Adjust slider value.
   */
  static readonly ADJUST_SLIDER_VALUE: number[] = [100, 100, 100];
  /**
   * Slider min.
   */
  static readonly SLIDER_MIN: number = 1;
  /**
   * Slider step.
   */
  static readonly SLIDER_MAX: number = 100;
  /**
   * Slider step.
   */
  static readonly SLIDER_STEP: number = 1;
  /**
   * Pixel step.
   */
  static readonly PIXEL_STEP: number = 4;
  /**
   * Decimal two.
   */
  static readonly DECIMAL_TWO: number = 2;
  /**
   * Color level max.
   */
  static readonly COLOR_LEVEL_MAX: number = 255;
  /**
   * Convert int.
   */
  static readonly CONVERT_INT: number = 100;
  /**
   * Angle 60.
   */
  static readonly ANGLE_60: number = 60;
  /**
   * Angle 120.
   */
  static readonly ANGLE_120: number = 120;
  /**
   * Angle 240.
   */
  static readonly ANGLE_240: number = 240;
  /**
   * Angle 300.
   */
  static readonly ANGLE_360: number = 360;
  /**
   * Angle 360.
   */
  static readonly MOD_2: number = 2;
  /**
   * Average height and width.
   */
  static readonly AVERAGE_WEIGHT_WIDTH: number = 2;
  /**
   * Crop rate 4:3.
   */
  static readonly CROP_RATE_4_3: number = 0.75;
  /**
   * Crop rate 16:9.
   */
  static readonly CROP_RATE_9_16: number = 9 / 16;
  /**
   * Encode quality.
   */
  static readonly ENCODE_QUALITY: number = 100;
  /**
   * Title space.
   */
  static readonly TITLE_SPACE: number = 0;
  /**
   * Slider mode click.
   */
  static readonly SLIDER_CLICK_MODE: number = 3;
  /**
   * Encode format.
   */
  static readonly ENCODE_FORMAT: string = 'image/jpeg';
  /**
   * Encode file permission.
   */
  static readonly ENCODE_FILE_PERMISSION: string = 'rw';
  /**
   * Brightness worker file.
   */
  static readonly BRIGHTNESS_WORKER_FILE = 'entry/ets/workers/AdjustBrightnessWork.ts';
  /**
   * Brightness worker file.
   */
  static readonly SATURATION_WORKER_FILE = 'entry/ets/workers/AdjustSaturationWork.ts';
  /**
   * Image name.
   */
  static readonly IMAGE_PREFIX = 'image';
  /**
   * Image format.
   */
  static readonly IMAGE_FORMAT = '.jpg';
  /**
   * Rawfile name.
   */
  static readonly RAW_FILE_NAME = 'low.jpg';
  /**
   * Cache dir file name.
   */
  static readonly RAW_FILE_NAME_TEST = 'low_test.jpg';
  /**
   * Free ratio.
   */
  static readonly DEFAULT_RATIO: number = -1;
  /**
   * Ratio 1:1.
   */
  static readonly RATIO_1_1: number = 1;
  /**
   * Ratio 16:9.
   */
  static readonly RATIO_16_9: number[] = [16, 9];
  /**
   * Ratio 9:16.
   */
  static readonly RATIO_9_16: number[] = [9, 16];
  /**
   * Ratio 4:3.
   */
  static readonly RATIO_4_3: number[] = [4, 3];
  /**
   * Ratio 3:4.
   */
  static readonly RATIO_3_4: number[] = [3, 4];
  /**
   * Ratio 3:2.
   */
  static readonly RATIO_3_2: number[] = [3, 2];
  /**
   * Ratio 2:3.
   */
  static readonly RATIO_2_3: number[] = [2, 3];
  /**
   * Screen display margin.
   */
  static readonly SCREEN_DISPLAY_MARGIN: number = 15;
  /**
   * Double.
   */
  static readonly DOUBLE: number = 2;
  /**
   * Edit screen can use scope.
   */
  static readonly EDIT_SCREEN_USAGE: number = 0.68;
  /**
   * Title height.
   */
  static readonly TITLE_HEIGHT: number = 56;
  /**
   * Screen manager key.
   */
  static readonly APP_KEY_SCREEN_MANAGER: string = 'app_key_screen_manager';
  /**
   * Whether full screen.
   */
  static readonly IS_FULL_SCREEN_KEY: string = 'isFullScreen';
  /**
   * Device type.
   */
  static readonly DEFAULT_DEVICE_TYPE: string = 'phone';
  /**
   * Status bar color.
   */
  static readonly STATUS_BAR_BACKGROUND_COLOR: string = '#F1F3F5';
  /**
   * Status bar text color.
   */
  static readonly STATUS_BAR_CONTENT_COLOR: string = '#000000';
  /**
   * Navigation height.
   */
  static readonly TOP_BAR_SIZE: number = 56;
  /**
   * Tool bar size.
   */
  static readonly TOOL_BAR_SIZE: number = 72;
  /**
   * Time out.
   */
  static readonly TIMEOUT: number = 50;
  /**
   * Rect line width inner.
   */
  static readonly DEFAULT_LINE_WIDTH: number = 0.4;
  /**
   * Rect line width outer.
   */
  static readonly DEFAULT_LINE_RECT_WIDTH: number = 0.8;
  /**
   * Rect Button line width.
   */
  static readonly DEFAULT_BUTTON_WIDTH: number = 2.3;
  /**
   * Rect Button line padding.
   */
  static readonly DEFAULT_BUTTON_PADDING: number = 1;
  /**
   * Rect Button line length.
   */
  static readonly DEFAULT_BUTTON_LENGTH: number = 20;
  /**
   * Rect line color.
   */
  static readonly DEFAULT_LINE_COLOR: string = '#80FFFFFF';
  /**
   * Rect Button line color outer.
   */
  static readonly DEFAULT_RECT_LINE_COLOR: string = '#FFFFFFFF';
  /**
   * Rect Button line color.
   */
  static readonly DEFAULT_BUTTON_COLOR: string = 'white';
  /**
   * Mask style.
   */
  static readonly DEFAULT_MASK_STYLE: string = 'rgba(0, 0, 0, 0.3)';
  /**
   * Equality threshold.
   */
  static readonly EQUALITY_THRESHOLD = 0.0001;
  /**
   * Min side length.
   */
  static readonly DEFAULT_MIN_SIDE_LENGTH: number = 90;
  /**
   * Touch move identification range.
   */
  static readonly DEFAULT_TOUCH_BOUND: number = 20;
  /**
   * Base scale value.
   */
  static readonly BASE_SCALE_VALUE: number = 1.0;
  /**
   * Default image ratio.
   */
  static readonly DEFAULT_IMAGE_RATIO: number = 1.0;
  /**
   * Rect min side length.
   */
  static readonly DEFAULT_MIN_SIDE_LENGTH_EDIT: number = 32;
  /**
   * Default margin length.
   */
  static readonly DEFAULT_MARGIN_LENGTH: number = 20;
  /**
   * Time out.
   */
  static readonly DEFAULT_TIMEOUT_MILLISECOND_1000: number = 1000;
  /**
   * Default split fraction.
   */
  static readonly DEFAULT_SPLIT_FRACTION: number = 3;
}
ets文件夹下common文件夹下BrightNess页面代码:
import {
  BrightnessFilterTransformation,
  ImageKnifeComponent,
  ScaleType,
  ImageKnifeData,
  ImageKnifeGlobal,
  RequestOption
} from '[@ohos](/user/ohos)/imageknife';
import { BusinessError } from '@kit.BasicServicesKit';
import PictureUtil from '../utils/PictureUtil';
/**
 * 亮度调节
 */
[@Component](/user/Component)
export struct BrightNess {
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife();
  private mUrl = $r('app.media.pngSample');
  [@State](/user/State) mBrightnessPixelMap?: PixelMap = undefined;
  [@State](/user/State) [@Watch](/user/Watch)('brightnessPixelMap') brightNessValue: number = 0;
  [@State](/user/State) showUI: boolean = false
  textTimerController: TextTimerController = new TextTimerController()
  [@State](/user/State) format: string = 'mm:ss.SS'
  async aboutToAppear(): Promise<void> {
    this.mBrightnessPixelMap = await PictureUtil.getResourcePixelMap($r('app.media.pngSample'))
  }
  build() {
    Column() {
      Stack() {
        ImageKnifeComponent({
          imageKnifeOption: {
            loadSrc: this.mBrightnessPixelMap,
            mainScaleType: ScaleType.FIT_CENTER,
          }
        })
          .width('100%')
          .height(400)
        Text(this.brightNessValue.toFixed(0))
          .fontSize(48)
          .fontColor(Color.White)
          .fontWeight(FontWeight.Bold)
          .visibility(this.showUI ? Visibility.Visible : Visibility.Hidden)
        TextTimer({ isCountDown: true, count: 1500, controller: this.textTimerController })
          .format(this.format)
          .fontColor(Color.Black)
          .fontSize(50)
          .onTimer((utc: number, elapsedTime: number) => {
            console.info('textTimer notCountDown utc is:' + utc + ', elapsedTime: ' + elapsedTime)
            if (elapsedTime >= 1500) {
              this.showUI = false;
            }
          })
          .visibility(Visibility.Hidden)
      }
      Slider({
        value: this.brightNessValue,
        min: -100,
        max: 100,
        style: SliderStyle.OutSet,
      })
        .blockColor(Color.White)
        .trackColor(Color.Gray)
        .selectedColor(Color.Gray)
        .onChange((value: number, mode: SliderChangeMode) => {
          this.brightNessValue = value
          this.showUI = true;
          if (mode === SliderChangeMode.End) {
            this.textTimerController.reset()
            this.textTimerController.start()
          }
          console.info('value:' + value + 'mode:' + mode.toString())
        })
      Text('参考"ets/component/BrightNess.ets"')
    }
    .height('100%')
    .width('100%')
    .padding(15)
    .justifyContent(FlexAlign.SpaceAround)
  }
  /**
   *亮度b
   */
  brightnessPixelMap() {
    let brightness = this.brightNessValue / 100;
    let imageKnifeOption = new RequestOption();
    new BrightnessFilterTransformation(brightness);
    imageKnifeOption.load(this.mUrl)
      .addListener({
        callback: (err: BusinessError | string, data: ImageKnifeData) => {
          this.mBrightnessPixelMap = data.drawPixelMap?.imagePixelMap as PixelMap;
          return false;
        }
      })
      .setImageViewSize({ width: vp2px(200), height: vp2px(200) })
      .skipMemoryCache(true)
      .enableGPU()
      .brightnessFilter(brightness)
    this.ImageKnife?.call(imageKnifeOption);
  }
}
ets文件夹下common文件夹下 Contrast页面代码:
import {
  ContrastFilterTransformation,
  ImageKnifeComponent,
  ScaleType,
  ImageKnifeData,
  ImageKnifeGlobal,
  RequestOption
} from '[@ohos](/user/ohos)/imageknife';
import { BusinessError } from '@kit.BasicServicesKit';
import PictureUtil from '../utils/PictureUtil';
/**
 * 对比度调整
 */
[@Component](/user/Component)
export struct Contrast {
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife();
  private mUrl = $r('app.media.pngSample');
  [@State](/user/State) mBrightnessPixelMap?: PixelMap = undefined;
  [@State](/user/State) [@Watch](/user/Watch)('contrastPixelMap') contrastValue: number = 0;
  [@State](/user/State) showUI: boolean = false
  textTimerController: TextTimerController = new TextTimerController()
  [@State](/user/State) format: string = 'mm:ss.SS'
  async aboutToAppear(): Promise<void> {
    this.mBrightnessPixelMap = await PictureUtil.getResourcePixelMap($r('app.media.pngSample'))
  }
  build() {
    Column() {
      Stack() {
        ImageKnifeComponent({
          imageKnifeOption: {
            loadSrc: this.mBrightnessPixelMap,
            mainScaleType: ScaleType.FIT_CENTER,
          }
        })
          .width('100%')
          .height(400)
        Text(this.contrastValue.toFixed(0))
          .fontSize(48)
          .fontColor(Color.White)
          .fontWeight(FontWeight.Bold)
          .visibility(this.showUI ? Visibility.Visible : Visibility.Hidden)
        TextTimer({ isCountDown: true, count: 1500, controller: this.textTimerController })
          .format(this.format)
          .fontColor(Color.Black)
          .fontSize(50)
          .onTimer((utc: number, elapsedTime: number) => {
            console.info('textTimer notCountDown utc is:' + utc + ', elapsedTime: ' + elapsedTime)
            if (elapsedTime >= 1500) {
              this.showUI = false;
            }
          })
          .visibility(Visibility.Hidden)
      }
      Slider({
        value: this.contrastValue,
        min: -100,
        max: 100,
        style: SliderStyle.OutSet,
      })
        .blockColor(Color.White)
        .trackColor(Color.Gray)
        .selectedColor(Color.Gray)
        .onChange((value: number, mode: SliderChangeMode) => {
          this.contrastValue = value
          this.showUI = true;
          if (mode === SliderChangeMode.End) {
            this.textTimerController.reset()
            this.textTimerController.start()
          }
          console.info('value:' + value + 'mode:' + mode.toString())
        })
      Text('参考"ets/component/Contrast.ets"')
    }
    .height('100%')
    .width('100%')
    .padding(15)
    .justifyContent(FlexAlign.SpaceAround)
  }
  /**
   *对比度
   */
  contrastPixelMap() {
    let imageKnifeOption = new RequestOption();
    let transformation = new ContrastFilterTransformation(this.contrastValue);
    imageKnifeOption.load(this.mUrl)
      .addListener({
        callback: (err: BusinessError | string, data: ImageKnifeData) => {
          this.mBrightnessPixelMap = data.drawPixelMap?.imagePixelMap as PixelMap;
          return false;
        }
      })
      .setImageViewSize({ width: vp2px(200), height: vp2px(200) })
      .skipMemoryCache(true)
      .enableGPU()
      .contrastFilter(this.contrastValue)
    this.ImageKnife?.call(imageKnifeOption);
  }
}
CropImageComponent组件代码:
import {
  CropOptions,
  FileUtils,
  ImageKnifeComponent,
  ImageKnifeGlobal,
  Options,
  PixelMapCrop,
  ScaleType
} from '[@ohos](/user/ohos)/imageknife';
import { Constants, TAG } from '../common/Constants';
import { common } from '@kit.AbilityKit';
import { resourceManager } from '@kit.LocalizationKit';
import { BusinessError } from '@kit.BasicServicesKit';
import Pictures from '../utils/PictureUtil';
import { image } from '@kit.ImageKit';
/**
 * 图片裁剪
 */
[@Component](/user/Component)
export struct CropImageComponent {
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  [@State](/user/State) cropImage: ResourceStr = $r('app.media.pngSample')
  [@State](/user/State) options1: Options = new Options();
  [@State](/user/State) cropTap: boolean = false;
  [@State](/user/State) _rotate: number = 0;
  [@State](/user/State) _scale: number = 1;
  [@State](/user/State) cropOptions: CropOptions = {
    src: this.cropImage,
    size: {
      width: 300,
      height: 300
    }
  };
  private context: common.UIAbilityContext = ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext;
  /**
   * 初始化一个默认图片显示
   * [@returns](/user/returns)
   */
  async aboutToAppear(): Promise<void> {
    const resourceMgr = this.context.resourceManager as resourceManager.ResourceManager;
    let imageBuffer = await resourceMgr.getMediaContent($r("app.media.pngSample").id);
    let arrayBuffer = FileUtils.getInstance().uint8ArrayToBuffer(imageBuffer);
    this.setOptions(arrayBuffer)
  }
  build() {
    Scroll() {
      Column({ space: 10 }) {
        PixelMapCrop({ options: $options1, cropTap: this.cropTap })
        Canvas(this.canvasContext)
          .width(this.cropOptions.size.width)
          .height(this.cropOptions.size.height)
          .rotate({
            z: 1,
            centerX: this.cropOptions.size.width / 2,
            centerY: this.cropOptions.size.height / 2,
            angle: this._rotate
          })
          .scale({ x: this._scale, y: this._scale, z: 1.0 })
          .gesture(GestureGroup(GestureMode.Parallel,
            RotationGesture({ fingers: 2 }).onActionUpdate((event?: GestureEvent) => {
              if (event != undefined) {
                this._rotate = event.angle;
              }
            }), PinchGesture({ fingers: 2 }).onActionUpdate((event?: GestureEvent) => {
              if (event != undefined) {
                this._scale = event.scale;
              }
            })))
        Row() {
          Row() {
            Button('点击裁剪图片').onClick(() => {
              this.cropTap = !this.cropTap;
            })
              .width('49%')
              .type(ButtonType.Normal)
            Button('图库选图').onClick(async (event: ClickEvent) => {
              let uri = await Pictures.getImageFromGallery()
              if (uri) {
                Pictures.getPixelMap(uri).then(async pixelMap => {
                  if (pixelMap) {
                    let packOpts : image.PackingOption = { format:"image/png", quality:100 };
                    const imagePackerApi = image.createImagePacker();
                    imagePackerApi.packing(pixelMap, packOpts).then( (data : ArrayBuffer) => {
                      this.setOptions(data)
                    }).catch((error : BusinessError) => {
                      console.error('Failed to pack the image. And the error is: ' + error);
                    })
                  }
                })
              }
            })
              .width('49%')
              .type(ButtonType.Normal)
          }
          .width('100%')
          .justifyContent(FlexAlign.SpaceBetween)
        }
        Text('参考"ets/component/CropImage.ets"')
      }
      .width('100%')
    }
    .height('100%')
    .padding(15)
  }
  setOptions(arrayBuffer: ArrayBuffer) {
    try {
      let optionx = new Options();
      optionx.setWidth(800).setHeight(800)
        .setCropFunction((err: BusinessError | string, pixelmap: PixelMap | null, sx: number, sy: number) => {
          console.log('PMC setCropFunction callback')
          if (err) {
            console.error('PMC crop err =' + err)
          } else {
            this.cropOptions.size.width = sx * px2vp(1);
            this.cropOptions.size.height = sy * px2vp(1);
            if (pixelmap != null) {
              this.cropOptions.src = pixelmap;
              this.canvasContext.drawImage(pixelmap, 0, 0, this.cropOptions.size.width, this.cropOptions.size.height)
            }
          }
        })
      optionx.loadBuffer(arrayBuffer, () => {
        this.options1 = optionx;
      })
    } catch (err) {
      console.log(err)
    }
  }
}
TransformRotate 组件代码:
import {
  ImageKnifeComponent,
  ScaleType,
  ImageKnifeData,
  ImageKnifeGlobal,
  RequestOption,
  RotateImageTransformation
} from '[@ohos](/user/ohos)/imageknife';
import { BusinessError } from '@kit.BasicServicesKit';
import PictureUtil from '../utils/PictureUtil';
/**
 * 图片旋转
 */
[@Component](/user/Component)
export struct TransformRotate {
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife();
  private mUrl = $r('app.media.pngSample');
  [@State](/user/State) mRotatePixelMap?: PixelMap = undefined;
  [@State](/user/State) [@Watch](/user/Watch)('transformRotate') mRotate: number = 0;
  [@State](/user/State) showUI: boolean = false
  textTimerController: TextTimerController = new TextTimerController()
  [@State](/user/State) format: string = 'mm:ss.SS'
  [@State](/user/State) savePath: string = ''
  async aboutToAppear(): Promise<void> {
    this.mRotatePixelMap = await PictureUtil.getResourcePixelMap($r('app.media.pngSample'))
  }
  build() {
    Scroll() {
      Column() {
        Stack() {
          ImageKnifeComponent({
            imageKnifeOption: {
              loadSrc: this.mRotatePixelMap,
              mainScaleType: ScaleType.FIT_CENTER,
            }
          })
            .width('100%')
            .height(400)
          Text(this.mRotate.toFixed(0))
            .fontSize(48)
            .fontColor(Color.White)
            .fontWeight(FontWeight.Bold)
            .visibility(this.showUI ? Visibility.Visible : Visibility.Hidden)
          TextTimer({ isCountDown: true, count: 1500, controller: this.textTimerController })
            .format(this.format)
            .fontColor(Color.Black)
            .fontSize(50)
            .onTimer((utc: number, elapsedTime: number) => {
              console.info('textTimer notCountDown utc is:' + utc + ', elapsedTime: ' + elapsedTime)
              if (elapsedTime >= 1500) {
                this.showUI = false;
              }
            })
            .visibility(Visibility.Hidden)
        }
        Column() {
          Text('固定角度')
          Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) {
            Button('左转45°')
              .controlSize(ControlSize.NORMAL)
              .buttonStyle(ButtonStyleMode.EMPHASIZED)
              .type(ButtonType.Normal)
              .width('30%')
              .margin({ top: 5, bottom: 5 })
              .borderRadius(5)
              .onClick(() => {
                this.mRotate = this.mRotate >= 45 ? this.mRotate - 45 : this.mRotate + 360 - 45;
              })
            Button('左转90°')
              .controlSize(ControlSize.NORMAL)
              .buttonStyle(ButtonStyleMode.EMPHASIZED)
              .type(ButtonType.Normal)
              .width('30%')
              .margin({ top: 5, bottom: 5 })
              .borderRadius(5)
              .onClick(() => {
                this.mRotate = this.mRotate >= 90 ? this.mRotate - 90 : this.mRotate + 360 - 90;
              })
            Button('旋转180°')
              .controlSize(ControlSize.NORMAL)
              .buttonStyle(ButtonStyleMode.EMPHASIZED)
              .type(ButtonType.Normal)
              .width('30%')
              .margin({ top: 5, bottom: 5 })
              .borderRadius(5)
              .onClick(() => {
                this.mRotate = this.mRotate + 180 >= 360 ? this.mRotate - 180 : this.mRotate + 180;
              })
            Button('右转45°')
              .controlSize(ControlSize.NORMAL)
              .buttonStyle(ButtonStyleMode.EMPHASIZED)
              .type(ButtonType.Normal)
              .width('30%')
              .margin({ top: 5, bottom: 5 })
              .borderRadius(5)
              .onClick(() => {
                this.mRotate = this.mRotate + 45 >= 360 ? this.mRotate + 45 - 360 : this.mRotate + 45;
              })
            Button('右转90°')
              .controlSize(ControlSize.NORMAL)
              .buttonStyle(ButtonStyleMode.EMPHASIZED)
              .type(ButtonType.Normal)
              .width('30%')
              .margin({ top: 5, bottom: 5 })
              .borderRadius(5)
              .onClick(() => {
                this.mRotate = this.mRotate + 90 >= 360 ? this.mRotate + 90 - 360 : this.mRotate + 90;
              })
          }
        }
        .borderWidth(1)
        .borderColor('#E5E5E5')
        Slider({
          value: this.mRotate,
          min: 0,
          max: 360,
          style: SliderStyle.OutSet,
        })
          .blockColor(Color.White)
          .trackColor(Color.Gray)
          .selectedColor(Color.Gray)
          .margin({ top: 15 })
          .onChange((value: number, mode: SliderChangeMode) => {
            this.mRotate = value
            this.showUI = true;
            if (mode === SliderChangeMode.End) {
              this.textTimerController.reset()
              this.textTimerController.start()
            }
            console.info('value:' + value + 'mode:' + mode.toString())
          })
        Button('保存')
          .onClick(async (event: ClickEvent) => {
            if (this.mRotatePixelMap) {
              this.savePath = await PictureUtil.savePixelMap(this.mRotatePixelMap)
            }
          })
          .width('100%')
          .type(ButtonType.Normal)
          .borderRadius(5)
          .margin({ top: 15 })
        if (this.savePath) {
          Text('保存后沙箱路径:' + this.savePath).margin({ top: 15 })
        }
        Text('参考"ets/component/TransformRotate.ets"').margin({ top: 15 })
      }
      .width('100%')
      .padding(15)
      .justifyContent(FlexAlign.SpaceAround)
    }
    .height('100%')
    .scrollBar(BarState.Off)
  }
  /**
   * 旋转
   */
  transformRotate() {
    let imageKnifeOption = new RequestOption();
    let transformation = new RotateImageTransformation(this.mRotate);
    imageKnifeOption.load(this.mUrl)
      .addListener({
        callback: (err: BusinessError | string, data: ImageKnifeData) => {
          this.mRotatePixelMap = data.drawPixelMap?.imagePixelMap as PixelMap;
          return false;
        }
      })
      .setImageViewSize({ width: vp2px(200), height: vp2px(200) })
      .skipMemoryCache(true)
      .rotateImage(this.mRotate)
    this.ImageKnife?.call(imageKnifeOption);
  }
}
model文件夹下pages代码:
export class Pages {
  names: string = ""
  values: NavPathStack | null = null
}
ets文件夹下model文件夹下tabar代码:
export class Tabar {
  name: string = '';
  selectN: ResourceStr = '';
  selectY: ResourceStr = '';
}
pages文件夹下CapabilityList代码:
import { Tabar } from '../model/Tabar'
import { tabars } from '../common/Constants';
import { CropImageComponent } from '../component/CropImage';
import { BrightNess } from '../component/BrightNess';
import { Contrast } from '../component/Contrast';
import { TransformRotate } from '../component/TransformRotate';
[@Entry](/user/Entry)
[@Component](/user/Component)
struct CapabilityList {
  [@State](/user/State) fontColor: string = '#182431'
  [@State](/user/State) selectedFontColor: string = '#007DFF'
  [@State](/user/State) currentIndex: number = 0
  private controller: TabsController = new TabsController()
  build() {
    Column() {
      Tabs({ barPosition: BarPosition.End, controller: this.controller }) {
        TabContent() {
          CropImageComponent()
        }
        .tabBar(this.tabBuilder(0, tabars[0]))
        .backgroundColor(Color.White)
        TabContent() {
          BrightNess()
        }
        .tabBar(this.tabBuilder(1, tabars[1]))
        .backgroundColor(Color.White)
        TabContent() {
          Contrast()
        }
        .tabBar(this.tabBuilder(2, tabars[2]))
        .backgroundColor(Color.White)
        TabContent() {
          TransformRotate()
        }
        .tabBar(this.tabBuilder(3, tabars[3]))
        .backgroundColor(Color.White)
      }
      .onChange((index: number) => {
        this.currentIndex = index
      })
      .backgroundColor('#F1F3F5')
      .scrollable(false)
    }
  }
  [@Builder](/user/Builder)
  tabBuilder(index: number, tabar: Tabar) {
    Column() {
      Image(this.currentIndex === index ? tabar.selectY : tabar.selectN)
        .width(24)
        .height(24)
        .margin({ bottom: 4 })
        .objectFit(ImageFit.Contain)
      Text(tabar.name)
        .fontColor(this.currentIndex === index ? this.selectedFontColor : this.fontColor)
        .fontSize(10)
        .fontWeight(500)
        .lineHeight(14)
    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

HarmonyOS 鸿蒙Next 自定义相机 Demo

HarmonyOS 鸿蒙Next确实支持自定义相机开发,并且有相关示例代码可供参考。以下是关于自定义相机Demo的简要说明:

HarmonyOS提供了CameraKit API,允许开发者自定义相机功能。基于CameraKit,开发者可以通过AVRecorder实现录像功能。示例代码中展示了如何通过CameraKit自定义相机,并通过AVRecorder进行录像。该示例包括了相机输入、预览输出流和录像输出流的创建与配置。

要运行此Demo,您需要确保开发环境满足以下条件:

  • 支持的设备:华为手机
  • HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上
  • DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上
  • HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上

您可以在HarmonyOS开发者官方文档中找到更详细的指导信息和示例代码。如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部