鸿蒙媒体开发【拼图】拍照和图片

拼图

介绍

该示例通过@ohos.multimedia.image和@ohos.file.photoAccessHelper接口实现获取图片,以及图片裁剪分割的功能。

效果预览

使用说明:

  1. 使用预置相机拍照后启动应用,应用首页会读取设备内的图片文件并展示获取到的第一个图片,没有图片时图片位置显示空白;
  2. 点击开始按钮开始后,时间开始倒计时,在规定时间内未完成拼图则游戏结束。在游戏中,玩家点击重新开始进行游戏重置;
  3. 点击开始游戏后,玩家可以根据上方的大图,点击黄格周围的图片移动,点击后图片和黄格交换位置,最终拼成完整的图片;
  4. 不在游戏中时,玩家可以点击上方大图,选择自定义图片来进行拼图游戏。

具体实现

  • 游戏中图片裁剪分割的效果实现在ImageModel中,源码参考[ImageModel]:

    /*

    • Copyright (c) 2022 Huawei Device Co., Ltd.
    • Licensed under the Apache License, Version 2.0 (the "License");
    • you may not use this file except in compliance with the License.
    • You may obtain a copy of the License at
    • http://www.apache.org/licenses/LICENSE-2.0
      
    • Unless required by applicable law or agreed to in writing, software
    • distributed under the License is distributed on an "AS IS" BASIS,
    • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    • See the License for the specific language governing permissions and
    • limitations under the License.
      */

    import { image } from '@kit.ImageKit';
    import { photoAccessHelper } from '@kit.MediaLibraryKit';
    import { dataSharePredicates } from '@kit.ArkData';
    import { Context } from '@kit.AbilityKit';
    import { fileIo } from '@kit.CoreFileKit';
    import Logger from './Logger';
    import PictureItem from './PictureItem';
    import { CommonConstants as Common } from '../common/CommonConstants';

    const TAG = '[ImageModel]';

    export default class ImageModel {
    private phAccessHelper: photoAccessHelper.PhotoAccessHelper | null = null;

    constructor(context: Context) {
      this.phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    }
    
    async getAllImg(): Promise<photoAccessHelper.PhotoAsset[]> {
      Logger.info('getAllImg');
      let photoList: Array<photoAccessHelper.PhotoAsset> = [];
      if (this.phAccessHelper === null) {
        Logger.info('phAccessHelper fail');
        return photoList;
      }
      let fileKeyType = photoAccessHelper.PhotoKeys.PHOTO_TYPE;
      let predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
      Logger.info(fileKeyType);
      let fetchOptions: photoAccessHelper.FetchOptions = {
        fetchColumns: [],
        predicates: predicates
      };
    
      try {
        let fetchResult: photoAccessHelper.FetchResult<photoAccessHelper.PhotoAsset> =
          await this.phAccessHelper.getAssets(fetchOptions);
        if (fetchResult != undefined) {
          Logger.info('fetchResult success');
          let photoAsset: Array<photoAccessHelper.PhotoAsset> = await fetchResult.getAllObjects();
          if (photoAsset != undefined && photoAsset.length > 0) {
            for (let i = 0; i < photoAsset.length; i++) {
              if (photoAsset[i].photoType === 1) {
                photoList.push(photoAsset[i]);
              }
            }
          }
        }
      } catch (err) {
        Logger.error('getAssets failed, message = ', err);
      }
      Logger.info('photoList success');
      return photoList;
    }
    
    async splitPic(index: number): Promise<PictureItem[]> {
      let imagePixelMap: PictureItem[] = [];
      let imagesData: Array<photoAccessHelper.PhotoAsset> = await this.getAllImg();
      let imagePackerApi = image.createImagePacker();
      fileIo.open(imagesData[index].uri, fileIo.OpenMode.READ_ONLY).then(async (file: fileIo.File) => {
        let fd: number = file.fd;
        let imageSource = image.createImageSource(fd);
        let imageInfo = await imageSource.getImageInfo();
        Logger.info(TAG, `sizeImg createImageSource ${JSON.stringify(imageSource)}`);
        let height = imageInfo.size.height / Common.SPLIT_COUNT;
        for (let i = 0; i < Common.SPLIT_COUNT; i++) {
          for (let j = 0; j < Common.SPLIT_COUNT; j++) {
            let picItem: PictureItem;
            if (i === Common.SPLIT_COUNT - 1 && j === Common.SPLIT_COUNT - 1) {
              picItem = new PictureItem(Common.PICTURE_ITEM_NUMBER, {} as image.PixelMap);
              imagePixelMap.push(picItem);
            } else {
              Logger.info(TAG, `sizeImg x = ${imageInfo.size.width / Common.SPLIT_COUNT} y = ${height}`);
              let decodingOptions: image.DecodingOptions = {
                desiredRegion: {
                  size: {
                    height: height,
                    width: imageInfo.size.width / Common.SPLIT_COUNT
                  }, x: j * imageInfo.size.width / Common.SPLIT_COUNT, y: i * height
                }
              }
              imagePixelMap.push(
                new PictureItem(i * Common.SPLIT_COUNT + j, await imageSource.createPixelMap(decodingOptions)));
            }
          }
        }
        imagePackerApi.release();
        fileIo.closeSync(fd);
      })
      return imagePixelMap;
    }
    

    }

  • 获取本地图片:首先使用getPhotoAccessHelper获取相册管理模块实例,然后使用getAssets方法获取文件资源,最后使用getAllObjects获取检索结果中的所有文件资产方便展示;

  • 裁剪图片准备:裁剪图片需要使用@ohos.multimedia.image接口,裁剪前需要申请图片编辑权限,使用requestPermissionsFromUser申请,源码参考[Index.ets];

    /*

    • Copyright (c) 2022-2023 Huawei Device Co., Ltd.
    • Licensed under the Apache License, Version 2.0 (the "License");
    • you may not use this file except in compliance with the License.
    • You may obtain a copy of the License at
    • http://www.apache.org/licenses/LICENSE-2.0
      
    • Unless required by applicable law or agreed to in writing, software
    • distributed under the License is distributed on an "AS IS" BASIS,
    • WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    • See the License for the specific language governing permissions and
    • limitations under the License.
      */

    import { mediaquery } from '@kit.ArkUI';
    import { Permissions, abilityAccessCtrl } from '@kit.AbilityKit';
    import { emitter } from '@kit.BasicServicesKit';
    import { photoAccessHelper } from '@kit.MediaLibraryKit';
    import GameRules from '../model/GameRules';
    import ImagePicker from '../common/ImagePicker';
    import Logger from '../model/Logger';
    import PictureItem from '../model/PictureItem';
    import ImageModel from '../model/ImageModel';
    import { CommonConstants as Common } from '../common/CommonConstants';

    const PERMISSIONS: Array<Permissions> = [
    'ohos.permission.READ_MEDIA',
    'ohos.permission.WRITE_MEDIA',
    'ohos.permission.MEDIA_LOCATION',
    'ohos.permission.MANAGE_MISSIONS'
    ];

    @Entry
    @Component
    struct Index {
    @State numArray: PictureItem[] = [];
    @State imgData: Array<photoAccessHelper.PhotoAsset> = [];
    @State @Watch('onTimeOver') gameTime: number = Common.GAME_TIME;
    @State @Watch('onImageChange') index: number = Common.ZERO;
    @State isLand: boolean = false;
    @StorageLink('isGameStart') isGameStart: boolean = false;
    private listener = mediaquery.matchMediaSync('screen and (min-aspect-ratio: 1.5) or (orientation: landscape)');
    private ImageModel: ImageModel = new ImageModel(getContext(this));
    private game: GameRules = new GameRules();
    private timer: number = -1;
    private isRefresh: boolean = false;

    onLand = (mediaQueryResult: mediaquery.MediaQueryResult) => {
      Logger.info(`[eTSMediaQuery.Index]onLand: mediaQueryResult.matches=${mediaQueryResult.matches}`);
      this.isLand = mediaQueryResult.matches;
    };
    over = () => {
      Logger.info(Common.TAG, 'emitter on , eventID = 0');
      for (let i = Common.ZERO; i < Common.LOOP; i++) {
        this.numArray[i].index = i;
      }
      this.gameOver();
    };
    timeEnd = () => {
      Logger.info(Common.TAG, 'emitter on , eventID = 1');
      this.gameTime = Common.ZERO;
    };
    
    async aboutToAppear() {
      this.listener.on('change', this.onLand);
      await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this), PERMISSIONS);
      this.imgData = await this.ImageModel.getAllImg();
      Logger.info(Common.TAG, `images = ${this.imgData.length}`);
      this.numArray = await this.ImageModel.splitPic(this.index);
      // Test case. The game is simulated successfully.
      emitter.on({ eventId: Common.ZERO, priority: Common.ZERO }, this.over);
      // Test case. End of simulation time.
      emitter.on({ eventId: Common.ONE, priority: Common.ZERO }, this.timeEnd);
    }
    
    onTimeOver() {
      if (this.gameTime === Common.ZERO) {
        this.isGameStart = false;
        AlertDialog.show({ message: 'TimeOver' });
        clearInterval(this.timer);
      }
    }
    
    async onImageChange() {
      this.isRefresh = true;
      this.dialogController.close();
      this.numArray = [];
      this.numArray = await this.ImageModel.splitPic(this.index);
      this.init();
      this.isGameStart = false;
      this.isRefresh = false;
    }
    
    init() {
      this.gameTime = Common.GAME_TIME;
      clearInterval(this.timer);
    }
    
    gameOver() {
      let count = Common.ZERO;
      for (let i = Common.ZERO; i < Common.LOOP; i++) {
        if (this.numArray[i].index === i) {
          count++;
        } else {
          count = Common.ZERO;
          break;
        }
      }
      if (count === Common.LOOP) {
        this.isGameStart = false;
        AlertDialog.show({ message: $r('app.string.congratulations') });
        clearInterval(this.timer);
        this.gameTime = Common.GAME_TIME;
      }
    }
    
    start() {
      this.init();
      this.timer = setInterval(() => {
        this.gameTime--;
      }, Common.TIME_NUMBER)
    }
    
    dialogController: CustomDialogController = new CustomDialogController({
      builder: ImagePicker({
        imagesData: this.imgData,
        index: $index
      }),
      autoCancel: true,
      gridCount: Common.GRID_COUNT
    })
    
    @Builder
    ImageShow() {
      Image(this.imgData[this.index].uri)
        .id(Common.IMAGE_SHOW)
        .width(Common.IMAGE_WIDTH)
        .height($r('app.float.imageShow_height'))
        .objectFit(ImageFit.Fill)
        .onClick(async () => {
          if (this.isRefresh) {
            return;
          }
          this.imgData = await this.ImageModel.getAllImg();
          setTimeout(() => {
            this.dialogController.open();
          }, Common.TIME);
        })
    }
    
    @Builder
    ImageGrid(leftMargin: number, topMargin: number) {
      Grid() {
        ForEach(this.numArray, (item: PictureItem, index) => {
          GridItem() {
            Image(item.pixelMap)
              .width(Common.GRID_IMAGE_WIDTH)
              .objectFit(ImageFit.Fill)
              .height($r('app.float.grid_image_height'))
          }
          .id(`image${index}`)
          .backgroundColor(item.pixelMap === undefined ? $r('app.color.blank_picture_background') : $r('app.color.picture_background'))
          .onClick(() => {
            if (this.isRefresh) {
              return;
            }
            if (this.isGameStart) {
              this.isRefresh = true;
              this.numArray = this.game.gameInit(index, this.numArray);
              this.gameOver();
              this.isRefresh = false;
            }
          })
        }, (item: PictureItem) => JSON.stringify(item))
      }
      .id(Common.IMAGE_GRID)
      .columnsTemplate(Common.COLUMN_TEMPLATE)
      .columnsGap($r('app.float.gap'))
      .rowsGap($r('app.float.gap'))
      .width(Common.IMAGE_WIDTH)
      .height($r('app.float.grid_height'))
      .margin({ left: leftMargin, top: topMargin })
    }
    
    build() {
      Column() {
        Row() {
          Text(`Time:0${Math.floor(this.gameTime / Common.SECOND)}:${this.gameTime % Common.SECOND < Common.TEN
            ? Common.STRING_ZERO + this.gameTime % Common.SECOND : this.gameTime % Common.SECOND}`)
            .id(Common.TIME_STRING)
            .margin({ top: Common.MARGIN, bottom: Common.MARGIN })
        }
    
        if (this.imgData.length > Common.ZERO) {
          if (this.isLand) {
            Row() {
              this.ImageShow()
              this.ImageGrid(Common.TEN, Common.ZERO)
            }
            .margin({ top: Common.MARGIN })
          } else {
            Column() {
              this.ImageShow()
              this.ImageGrid(Common.ZERO, Common.FIVE)
            }
            .margin({ top: Common.MARGIN })
          }
        }
        Button($r('app.string.start'), { type: ButtonType.Capsule, stateEffect: true })
          .id(Common.START_BUTTON)
          .height($r('app.float.button_height'))
          .width(Common.FULL_WIDTH)
          .fontSize($r('app.float.button_font_size'))
          .margin({ top: Common.MARGIN })
          .backgroundColor(this.isGameStart ? $r('app.color.forbid') : $r('app.color.allow'))
          .enabled(!this.isGameStart)
          .onClick(() => {
            this.isGameStart = true;
            this.start();
            this.numArray = this.game.gameBegin(this.numArray);
          })
    
        Button($r('app.string.restart'), { type: ButtonType.Capsule, stateEffect: true })
          .id(Common.RESTART_BUTTON)
          .height($r('app.float.button_height'))
          .width(Common.FULL_WIDTH)
          .fontSize($r('app.float.button_font_size'))
          .margin({ top: Common.MARGIN })
          .backgroundColor(this.isGameStart ? $r('app.color.allow') : $r('app.color.forbid'))
          .enabled(this.isGameStart)
          .onClick(() => {
            this.isGameStart = true;
            this.start();
            this.numArray = this.game.gameBegin(this.numArray);
          })
      }
      .width(Common.FULL_WIDTH)
      .height(Common.FULL_HEIGHT)
      .padding({ left: Common.PERCENT, right: Common.PERCENT })
    }
    

    }

  • 图片编辑:首先使用createImagePacker创建ImagePacker实例,然后使用fs.open打开文件,调用createImageSource接口创建图片源实例方便操作图片,接下来使用getImageInfo方法获取图片大小便于分割,最后使用createPixelMap方法传入每一份的尺寸参数完成图片裁剪。

以上就是本篇文章所带来的鸿蒙开发中一小部分技术讲解;想要学习完整的鸿蒙全栈技术。可以在结尾找我可全部拿到!

下面是鸿蒙的完整学习路线 ,展示如下:

除此之外,根据这个学习鸿蒙全栈学习路线,也附带一整套完整的学习【文档+视频】,内容包含如下

内容包含了:(ArkTS、ArkUI、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、鸿蒙南向开发、鸿蒙项目实战)等技术知识点。帮助大家在学习鸿蒙路上快速成长!

鸿蒙【北向应用开发+南向系统层开发】文档

鸿蒙【基础+实战项目】视频

鸿蒙面经

为了避免大家在学习过程中产生更多的时间成本,对比我把以上内容全部放在了↓↓↓想要的可以自拿喔!谢谢大家观看!

相关推荐
JavaPub-rodert1 小时前
鸿蒙生态崛起:开发者的机遇与挑战
华为·harmonyos
yilylong4 小时前
鸿蒙(Harmony)实现滑块验证码
华为·harmonyos·鸿蒙
baby_hua4 小时前
HarmonyOS第一课——DevEco Studio的使用
华为·harmonyos
HarmonyOS_SDK5 小时前
融合虚拟与现实,AR Engine为用户提供沉浸式交互体验
harmonyos
- 羊羊不超越 -6 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
Industio_触觉智能7 小时前
OpenHarmony4.1蓝牙芯片如何适配?触觉智能RK3568主板SBC3568演示
openharmony·rk3568·开源鸿蒙·鸿蒙开发板·触觉智能
长弓三石8 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
嚣张农民8 小时前
推荐3个实用的760°全景框架
前端·vue.js·程序员
梓羽玩Python9 小时前
推荐一款用了5年的全能下载神器:Motrix!全平台支持,不限速下载网盘文件就靠它!
程序员·开源·github
梓羽玩Python9 小时前
这款一站式AI体验平台值得收藏起来!GPT-4o、GPT-4o Mini、Claude 3.5 Sonnet免费使用!
人工智能·程序员·设计