HarmonyOS 自定义相机demo

有没有自定义相机的demo。

HarmonyOS
2024-11-13 10:52:12
1.8w浏览
收藏 0
回答 1
回答 1
按赞同
/
按时间
zbw_apple

​这边提供一个完整的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/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" 
} 
} 
]
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.

entryability页面代码:

import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 
import { hilog } from '@kit.PerformanceAnalysisKit'; 
import { window } from '@kit.ArkUI'; 
import { ImageKnife } from '@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'); 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.

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; 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.

ets文件夹下common文件夹下BrightNess页面代码:

import { 
  BrightnessFilterTransformation, 
  ImageKnifeComponent, 
  ScaleType, 
  ImageKnifeData, 
  ImageKnifeGlobal, 
  RequestOption 
} from '@ohos/imageknife'; 
import { BusinessError } from '@kit.BasicServicesKit'; 
import PictureUtil from '../utils/PictureUtil'; 
 
/** 
 * 亮度调节 
 */ 
@Component 
export struct BrightNess { 
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife(); 
  private mUrl = $r('app.media.pngSample'); 
  @State mBrightnessPixelMap?: PixelMap = undefined; 
  @State @Watch('brightnessPixelMap') brightNessValue: number = 0; 
  @State showUI: boolean = false 
  textTimerController: TextTimerController = new TextTimerController() 
  @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); 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.

ets文件夹下common文件夹下 Contrast页面代码:

import { 
  ContrastFilterTransformation, 
  ImageKnifeComponent, 
  ScaleType, 
  ImageKnifeData, 
  ImageKnifeGlobal, 
  RequestOption 
} from '@ohos/imageknife'; 
import { BusinessError } from '@kit.BasicServicesKit'; 
import PictureUtil from '../utils/PictureUtil'; 
 
/** 
 * 对比度调整 
 */ 
@Component 
export struct Contrast { 
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife(); 
  private mUrl = $r('app.media.pngSample'); 
  @State mBrightnessPixelMap?: PixelMap = undefined; 
  @State @Watch('contrastPixelMap') contrastValue: number = 0; 
  @State showUI: boolean = false 
  textTimerController: TextTimerController = new TextTimerController() 
  @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); 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.

CropImageComponent组件代码:

import { 
  CropOptions, 
  FileUtils, 
  ImageKnifeComponent, 
  ImageKnifeGlobal, 
  Options, 
  PixelMapCrop, 
  ScaleType 
} from '@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 
export struct CropImageComponent { 
  private settings: RenderingContextSettings = new RenderingContextSettings(true) 
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) 
  @State cropImage: ResourceStr = $r('app.media.pngSample') 
  @State options1: Options = new Options(); 
  @State cropTap: boolean = false; 
  @State _rotate: number = 0; 
  @State _scale: number = 1; 
  @State cropOptions: CropOptions = { 
    src: this.cropImage, 
    size: { 
      width: 300, 
      height: 300 
    } 
  }; 
  private context: common.UIAbilityContext = ImageKnifeGlobal.getInstance().getHapContext() as common.UIAbilityContext; 
 
  /** 
   * 初始化一个默认图片显示 
   * @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) 
    } 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.

TransformRotate 组件代码:

import { 
  ImageKnifeComponent, 
  ScaleType, 
  ImageKnifeData, 
  ImageKnifeGlobal, 
  RequestOption, 
  RotateImageTransformation 
} from '@ohos/imageknife'; 
import { BusinessError } from '@kit.BasicServicesKit'; 
import PictureUtil from '../utils/PictureUtil'; 
 
/** 
 * 图片旋转 
 */ 
@Component 
export struct TransformRotate { 
  private ImageKnife = ImageKnifeGlobal.getInstance().getImageKnife(); 
  private mUrl = $r('app.media.pngSample'); 
  @State mRotatePixelMap?: PixelMap = undefined; 
  @State @Watch('transformRotate') mRotate: number = 0; 
  @State showUI: boolean = false 
  textTimerController: TextTimerController = new TextTimerController() 
  @State format: string = 'mm:ss.SS' 
  @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); 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.

model文件夹下pages代码:

export class Pages { 
  names: string = "" 
  values: NavPathStack | null = null 
}
  • 1.
  • 2.
  • 3.
  • 4.

ets文件夹下model文件夹下tabar代码:

export class Tabar { 
  name: string = ''; 
  selectN: ResourceStr = ''; 
  selectY: ResourceStr = ''; 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

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 
@Component 
struct CapabilityList { 
  @State fontColor: string = '#182431' 
  @State selectedFontColor: string = '#007DFF' 
  @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 
  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) 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.

pages文件夹下index页面代码:

import { ImageKnifeComponent, ScaleType } from '@ohos/imageknife'; 
import { router } from '@kit.ArkUI'; 
import { localImageResource, netImageResource } from '../common/Constants'; 
import PictureUtil from '../utils/PictureUtil'; 
 
@Entry 
@Component 
struct Index { 
  @State imageData: ResourceStr = $r('app.media.jpgSample'); 
  @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack() 
  @State @Watch('localChange') btLIndex: number = 0; 
  @State @Watch('netChange') btNIndex: number = -1; 
  private btLNames: Array<string> = ['JPG', 'PNG', 'SVG', 'GIF', 'BMP', 'WEBP']; 
  private btNNames: Array<string> = ['JPG', 'PNG', 'DPG', 'GIF', 'BMP', 'WEBP'] 
 
  build() { 
    Column({ space: 15 }) { 
      Text('图片展示') 
        .fontSize(18) 
        .fontWeight(FontWeight.Medium) 
 
      ImageKnifeComponent({ 
        imageKnifeOption: { 
          loadSrc: this.imageData, 
          mainScaleType: ScaleType.FIT_CENTER, 
          placeholderSrc: $r('app.media.ic_loading'), 
          errorholderSrc: $r('app.media.ic_public_fail_filled'), 
          displayProgress:true, 
        } 
      }) 
        .width(300) 
        .height(300) 
        .borderWidth(1) 
        .borderColor('#E5E5E5') 
 
      Column() { 
        Text('本地图片') 
        Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) { 
          ForEach(this.btLNames, (item: string, index: number) => { 
            Button(item) 
              .controlSize(ControlSize.NORMAL) 
              .buttonStyle(this.btLIndex === index ? ButtonStyleMode.EMPHASIZED : ButtonStyleMode.NORMAL) 
              .type(ButtonType.Normal) 
              .width('30%') 
              .margin({ top: 5, bottom: 5 }) 
              .borderRadius(5) 
              .fontColor(this.btLIndex === index ? null : '#99000000') 
              .onClick(() => { 
                this.btLIndex = index 
              }) 
          }) 
        } 
      } 
      .borderWidth(1) 
      .borderColor('#E5E5E5') 
 
      Column() { 
        Text('网络图片') 
        Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceEvenly }) { 
          ForEach(this.btNNames, (item: string, index: number) => { 
            Button(item) 
              .controlSize(ControlSize.NORMAL) 
              .buttonStyle(this.btNIndex === index ? ButtonStyleMode.EMPHASIZED : ButtonStyleMode.NORMAL) 
              .type(ButtonType.Normal) 
              .width('30%') 
              .margin({ top: 5, bottom: 5 }) 
              .borderRadius(5) 
              .fontColor(this.btNIndex === index ? null : '#99000000') 
              .onClick(() => { 
                this.btNIndex = index 
              }) 
          }) 
        } 
      } 
      .borderWidth(1) 
      .borderColor('#E5E5E5') 
 
      Row() { 
        Button('拍照展示').onClick(async (event: ClickEvent) => { 
          let uri = await PictureUtil.takePhoto() 
          if (uri) { 
            this.imageData = uri; 
          } 
        }) 
          .width('49%') 
          .type(ButtonType.Normal) 
          .borderRadius(5) 
 
        Button('图库选图').onClick(async (event: ClickEvent) => { 
          let uri = await PictureUtil.getImageFromGallery() 
          if (uri) { 
            this.imageData = uri; 
          } 
        }) 
          .width('49%') 
          .type(ButtonType.Normal) 
          .borderRadius(5) 
      } 
      .width('100%') 
      .justifyContent(FlexAlign.SpaceBetween) 
 
      Button('更多能力').onClick((event: ClickEvent) => { 
        router.pushUrl({ url: 'pages/CapabilityList' }) 
      }) 
        .width('100%') 
        .type(ButtonType.Normal) 
        .borderRadius(5) 
    } 
    .width('100%') 
    .height('100%') 
    .padding(15) 
    .backgroundColor(Color.White) 
  } 
 
  localChange() { 
    if (this.btLIndex != -1) { 
      this.btNIndex = -1 
    } 
    console.log('lindex = ' + this.btLIndex) 
    this.imageData = localImageResource.get(this.btLIndex) 
  } 
 
  netChange() { 
    if (this.btNIndex != -1) { 
      this.btLIndex = -1 
    } 
    console.log('nindex = ' + this.btNIndex) 
    this.imageData = netImageResource.get(this.btNIndex) 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.

ets文件夹下utils文件夹下PermissionsUtil代码:

import { abilityAccessCtrl, bundleManager, common, PermissionRequestResult, Permissions } from '@kit.AbilityKit'; 
import { BusinessError } from '@kit.BasicServicesKit'; 
 
/** 
 * 权限管理工具类 
 * 能力:检查权限是否已存在、请求用户授权 
 */ 
class PermissionsUtil { 
 
  /** 
   * 校验应用是否被授予定位权限 
   * @param permissions 
   * @returns 
   */ 
  async checkPermissions(permissions: Array<Permissions>): Promise<void> { 
    let applyResult: boolean = false; 
    for (let permission of permissions) { 
      let grantStatus: abilityAccessCtrl.GrantStatus = await this.checkAccessToken(permission); 
      if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 
        applyResult = true; 
      } else { 
        applyResult = false; 
      } 
    } 
    if (!applyResult) { 
      this.requestPermissions(permissions); 
    } 
  } 
 
  async checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> { 
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED; 
 
    // 获取应用程序的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) { 
      let err: BusinessError = error as BusinessError; 
      console.error(`Failed to get bundle info for self. Code is ${err.code}, message is ${err.message}`); 
    } 
 
    // 校验应用是否被授予权限 
    try { 
      grantStatus = await atManager.checkAccessToken(tokenId, permission); 
    } catch (error) { 
      let err: BusinessError = error as BusinessError; 
      console.error(`Failed to check access token. Code is ${err.code}, message is ${err.message}`); 
    } 
 
    return grantStatus; 
  } 
 
  /** 
   * 申请用户授权 
   * @param permissions 
   */ 
  requestPermissions(permissions: Array<Permissions>): void { 
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 
    atManager.requestPermissionsFromUser(getContext() as common.UIAbilityContext, permissions) 
      .then((data: PermissionRequestResult) => { 
        console.info('request Permissions success') 
      }) 
      .catch((err: BusinessError) => { 
        console.error(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`); 
      }) 
  } 
} 
 
export default new PermissionsUtil();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.

util文件夹下pcitureutil代码:

import { common, Want } from '@kit.AbilityKit'; 
import { fileIo, fileUri } from '@kit.CoreFileKit'; 
import { BusinessError } from '@kit.BasicServicesKit'; 
import { BUNDLE_NAME, TAG } from '../common/Constants'; 
import { image } from '@kit.ImageKit'; 
import fs from '@ohos.file.fs'; 
import { resourceManager } from '@kit.LocalizationKit'; 
import { FileUtils } from '@ohos/imageknife'; 
 
class PictureUtil { 
  private LENGTH: number = 1; 
  private mContext: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; 
 
  /** 
   * 从图库选取一张图片 
   * @returns 
   */ 
  async getImageFromGallery(): Promise<string | undefined> { 
    let wantParam: Want = { 
      bundleName: 'com.huawei.hmos.photos', 
      abilityName: 'com.huawei.hmos.photos.MainAbility', 
      parameters: { 
        uri: 'multipleselect', 
        filterMediaType: 'FILTER_MEDIA_TYPE_IMAGE', 
        maxSelectCount: this.LENGTH 
      } 
    } 
 
    let abilityResult: common.AbilityResult = await this.mContext.startAbilityForResult(wantParam) 
    let wantResult: Want | undefined = abilityResult.want; 
    if (!wantResult || !wantResult.parameters) { 
      return undefined 
    } 
 
    let result: Array<string> = wantResult.parameters['select-item-list'] as Array<string>; 
    return result.length > 0 ? result[0] : undefined 
  } 
 
  /** 
   * 拍照并拿到照片uri 
   * @returns 
   */ 
  async takePhoto(): Promise<string | undefined> { 
    let bundleName: string = AppStorage.get('bundleName') ? AppStorage.get('bundleName') as string : BUNDLE_NAME 
    let wantParam: Want = { 
      action: 'ohos.want.action.imageCapture', 
      parameters: { 
        // 拍照完成后返回的应用BundleName 
        callBundleName: bundleName, 
        supportMultiMode: false 
      } 
    }; 
 
    let abilityResult: common.AbilityResult = await this.mContext.startAbilityForResult(wantParam) 
    let wantResult: Want | undefined = abilityResult.want; 
    if (!wantResult || !wantResult.parameters) { 
      return undefined 
    } 
 
    let uri: string = wantResult.parameters.resourceUri as string; 
    if (!uri) { 
      return undefined 
    } 
 
    return await this.saveImage(uri) 
  } 
 
  /** 
   * 保存照片并返回uri 
   * @param uri 
   * @returns 
   */ 
  async saveImage(uri: string): Promise<string | undefined> { 
    try { 
      let file = fileIo.openSync(uri, fileIo.OpenMode.READ_ONLY); 
      let prefix = uri.substring(uri.lastIndexOf('.') + 1); 
      let tempFileName = getContext(this).filesDir + '/' + new Date().getTime() + '.' + prefix; 
      let tempFile = fileIo.openSync(tempFileName, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); 
      fileIo.copyFileSync(file.fd, tempFile.fd); 
      let imageData = fileUri.getUriFromPath(tempFileName); 
      console.info(TAG, `resolve2Sandbox successful.` + imageData); 
      return imageData; 
    } catch (err) { 
      let error = err as BusinessError; 
      console.error(TAG, `resolve2Sandbox failed, err is ${JSON.stringify(error)}`); 
    } 
    return undefined 
  } 
 
  /** 
   * Async create pixel map. 
   * 
   * @return pixelMa. 
   */ 
  async getPixelMap(fileName: string) { 
    const fd = this.readImage(fileName); 
    const imageSourceApi = image.createImageSource(fd); 
    if (!imageSourceApi) { 
      console.error(TAG, 'imageSourceAPI created failed!'); 
      return; 
    } 
    const pixelMap = await imageSourceApi.createPixelMap({ 
      editable: true 
    }); 
    return pixelMap; 
  } 
 
  readImage(uri: string) { 
    const prefix: string = 'file:/'; 
    if (uri.startsWith('file://media')) { 
    } else if (prefix) { 
      uri = uri.substring(prefix.length); 
    } 
    // path参数为文件的应用沙箱路径或文件URI,所以使用沙箱路径时,需要去掉前缀 
    const file: fs.File = fs.openSync(uri, fs.OpenMode.READ_ONLY); 
    return file.fd; 
  } 
 
  /** 
   * 获取内置图片pixelMap 
   * @param fileName 
   * @returns 
   */ 
  async getResourcePixelMap(fileName: Resource) { 
    let resourceMgr = this.mContext.resourceManager as resourceManager.ResourceManager; 
    let imageBuffer = await resourceMgr.getMediaContent(fileName.id); 
    let arrayBuffer = FileUtils.getInstance().uint8ArrayToBuffer(imageBuffer); 
    let imageSourceApi: image.ImageSource = image.createImageSource(arrayBuffer); 
    const pixelMap = await imageSourceApi.createPixelMap({ 
      editable: true 
    }); 
    return pixelMap; 
  } 
 
  /** 
   * 保存pixelMap,返回路径 
   * @param pm 
   * @returns 
   */ 
  async savePixelMap(pm: PixelMap): Promise<string> { 
    if (pm === null) { 
      console.error(TAG, '传入的pm为空'); 
      return ''; 
    } 
    const imagePackerApi: image.ImagePacker = image.createImagePacker(); 
    let packOpts: image.PackingOption = { format: 'image/jpeg', quality: 30 }; 
    try { 
      this.packToFile(pm); 
      const data: ArrayBuffer = await imagePackerApi.packing(pm, packOpts); 
      return await this.saveFile(data); 
    } catch (err) { 
      console.error(TAG, '保存文件失败,err=' + JSON.stringify(err)); 
      return ''; 
    } 
  } 
 
  async packToFile(pixelMap: PixelMap) { 
    let fPath: string = this.mContext.cacheDir + '/' + this.getTimeStr() + '.jpg'; 
    let writeFd: fs.File = await fs.open(fPath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); 
 
    let opts : image.PackingOption = { format: "image/jpeg", quality: 100}; 
    const imagePacker = image.createImagePacker(); 
    await imagePacker.packToFile(pixelMap, writeFd.fd, opts); 
    fs.closeSync(writeFd.fd); 
  } 
 
  async saveFile(data: ArrayBuffer): Promise<string> { 
    let uri: string = this.mContext.filesDir + '/' + this.getTimeStr() + '.jpg'; 
    let file: fileIo.File = fs.openSync(uri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); 
    fs.writeSync(file.fd, data); 
    fs.closeSync(file); 
    // /data/storage/.... 加上file://前缀 
    uri = 'file:/' + uri; 
    return uri; 
  } 
 
  getTimeStr() { 
    const now: Date = new Date(); 
    const year: number = now.getFullYear(); 
    const month: number = now.getMonth() + 1; 
    const day: number = now.getDate(); 
    const hours: number = now.getHours(); 
    const minutes: number = now.getMinutes(); 
    const seconds: number = now.getSeconds(); 
    return `${year}${month}${day}_${hours}${minutes}${seconds}`; 
  } 
} 
export default new PictureUtil()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
分享
微博
QQ
微信
回复
2024-11-13 16:07:22


相关问题
HarmonyOS 自定义相机演示demo
745浏览 • 1回复 待解决
HarmonyOS 如何自定义相机
572浏览 • 1回复 待解决
HarmonyOS 自定义相机功能
649浏览 • 1回复 待解决
HarmonyOS 自定义相机预览问题
637浏览 • 1回复 待解决
HarmonyOS 关于自定义相机功能
704浏览 • 1回复 待解决
HarmonyOS 如何自定义相机背景
539浏览 • 1回复 待解决
hvigor自定义扩展demo
1462浏览 • 1回复 待解决
HarmonyOS 全局自定义弹窗demo
876浏览 • 1回复 待解决
HarmonyOS 自定义相机预览拉伸问题
509浏览 • 1回复 待解决
HarmonyOS 自定义原生日历demo
584浏览 • 1回复 待解决
HarmonyOS 使用自定义相机左边有间距
522浏览 • 1回复 待解决
HarmonyOS 自定义相机拍照不成功
735浏览 • 1回复 待解决
能够提供HarmonyOS自定义相机案例吗?
846浏览 • 1回复 待解决
HarmonyOS 自定义相机拍照后数据展示
1390浏览 • 1回复 待解决
HarmonyOS 请提供自定义组件封装demo
1010浏览 • 2回复 待解决
HarmonyOS 自定义相机前置摄像头变形
788浏览 • 1回复 待解决
【求助】自定义相机Camera2焦距异常
8536浏览 • 1回复 待解决