【HarmonyOS】头像圆形裁剪功能之手势放大缩小,平移,双击缩放控制(三)

news2024/11/25 20:23:11

【HarmonyOS】头像裁剪之手势放大缩小,平移,双击缩放控制(三)

一、DEMO效果图:
在这里插入图片描述
在这里插入图片描述

二、开发思路:
使用矩阵变换控制图片的放大缩小和平移形态。

通过监听点击手势TapGesture,缩放手势PinchGesture,拖动手势PanGesture进行手势操作的功能实现。

通过对矩阵变换参数mMatrix的赋值,将矩阵变换参数赋值给image控件。实现手势操作和图片操作的同步。

在这里插入图片描述
该参数拥有四维坐标,只需要通过手势操作调整四个参数即可实现。通过.transform(this.mMatrix)赋值给image控件。

通过image的.onComplete(this.onLoadImgComplete)函数回调,获取图片控件的宽高和内容宽高等参数。以此为基准,手势操作调整的都是这些值。

三、DEMO示例代码:

import router from '@ohos.router';
import { CropMgr, ImageInfo } from '../manager/CropMgr';
import { image } from '@kit.ImageKit';
import Matrix4 from '@ohos.matrix4';
import FS from '@ohos.file.fs';

export class LoadResult {
  width: number = 0;
  height: number = 0;
  componentWidth: number = 0;
  componentHeight: number = 0;
  loadingStatus: number = 0;
  contentWidth: number = 0;
  contentHeight: number = 0;
  contentOffsetX: number = 0;
  contentOffsetY: number = 0;
}





export struct CropPage {
  private TAG: string = "CropPage";

  private mRenderingContextSettings: RenderingContextSettings = new RenderingContextSettings(true);
  private mCanvasRenderingContext2D: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mRenderingContextSettings);

  // 加载图片
   mImg: PixelMap | undefined = undefined;
  // 图片矩阵变换参数
   mMatrix: object = Matrix4.identity()
    .translate({ x: 0, y: 0 })
    .scale({ x: 1, y: 1});

   mImageInfo: ImageInfo = new ImageInfo();

  private tempScale = 1;
  private startOffsetX: number = 0;
  private startOffsetY: number = 0;

  aboutToAppear(): void {
    console.log(this.TAG, "aboutToAppear start");
    let temp = CropMgr.Ins().mSourceImg;
    console.log(this.TAG, "aboutToAppear temp: " + JSON.stringify(temp));
    this.mImg = temp;
    console.log(this.TAG, "aboutToAppear end");
  }

  private getImgInfo(){
    return this.mImageInfo;
  }

  onClickCancel = ()=>{
    router.back();
  }

  onClickConfirm = async ()=>{
    if(!this.mImg){
      console.error(this.TAG, " onClickConfirm mImg error null !");
      return;
    }

    // 存当前裁剪的图
	// ...
    router.back();
  }

  /**
   * 复制图片
   * @param pixel
   * @returns
   */
  async copyPixelMap(pixel: PixelMap): Promise<PixelMap> {
    const info: image.ImageInfo = await pixel.getImageInfo();
    const buffer: ArrayBuffer = new ArrayBuffer(pixel.getPixelBytesNumber());
    await pixel.readPixelsToBuffer(buffer);
    const opts: image.InitializationOptions = {
      editable: true,
      pixelFormat: image.PixelMapFormat.RGBA_8888,
      size: { height: info.size.height, width: info.size.width }
    };
    return image.createPixelMap(buffer, opts);
  }

  /**
   * 图片加载回调
   */
  private onLoadImgComplete = (msg: LoadResult) => {
    this.getImgInfo().loadResult = msg;
    this.checkImageScale();
  }

  /**
   * 绘制画布中的取景框
   */
  private onCanvasReady = ()=>{
    if(!this.mCanvasRenderingContext2D){
      console.error(this.TAG, "onCanvasReady error mCanvasRenderingContext2D null !");
      return;
    }
    let cr = this.mCanvasRenderingContext2D;
    // 画布颜色
    cr.fillStyle = '#AA000000';
    let height = cr.height;
    let width = cr.width;
    cr.fillRect(0, 0, width, height);

    // 圆形的中心点
    let centerX = width / 2;
    let centerY = height / 2;
    // 圆形半径
    let radius = Math.min(width, height) / 2 - px2vp(100);
    cr.globalCompositeOperation = 'destination-out'
    cr.fillStyle = 'white'
    cr.beginPath();
    cr.arc(centerX, centerY, radius, 0, 2 * Math.PI);
    cr.fill();

    cr.globalCompositeOperation = 'source-over';
    cr.strokeStyle = '#FFFFFF';
    cr.beginPath();
    cr.arc(centerX, centerY, radius, 0, 2 * Math.PI);
    cr.closePath();

    cr.lineWidth = 1;
    cr.stroke();
  }

  build() {
    RelativeContainer() {
      // 黑色底图
      Row().width("100%").height("100%").backgroundColor(Color.Black)

      // 用户图
      Image(this.mImg)
        .objectFit(ImageFit.Contain)
        .width('100%')
        .height('100%')
        .transform(this.mMatrix)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onComplete(this.onLoadImgComplete)

      // 取景框
      Canvas(this.mCanvasRenderingContext2D)
        .width('100%')
        .height('100%')
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .backgroundColor(Color.Transparent)
        .onReady(this.onCanvasReady)
        .clip(true)
        .backgroundColor("#00000080")

      Row(){
        Button("取消")
          .size({
            width: px2vp(450),
            height: px2vp(200)
          })
          .onClick(this.onClickCancel)

        Blank()

        Button("确定")
          .size({
            width: px2vp(450),
            height: px2vp(200)
          })
          .onClick(this.onClickConfirm)
      }
      .width("100%")
      .height(px2vp(200))
      .margin({ bottom: px2vp(500) })
      .alignRules({
        center: { anchor: '__container__', align: VerticalAlign.Bottom },
        middle: { anchor: '__container__', align: HorizontalAlign.Center }
      })
      .justifyContent(FlexAlign.Center)

    }
    .width("100%").height("100%")
    .priorityGesture(
      // 点击手势
      TapGesture({
        // 点击次数
        count: 2,
        // 一个手指
        fingers: 1
      }).onAction((event: GestureEvent)=>{
        console.log(this.TAG, "TapGesture onAction start");
        if(!event){
          return;
        }
        if(this.getImgInfo().scale != 1){
          this.getImgInfo().scale = 1;
          this.getImgInfo().offsetX = 0;
          this.getImgInfo().offsetY = 0;
          this.mMatrix = Matrix4.identity()
            .translate({
              x: this.getImgInfo().offsetX,
              y: this.getImgInfo().offsetY
            })
            .scale({
              x: this.getImgInfo().scale,
              y: this.getImgInfo().scale
            })
        }else{
          this.getImgInfo().scale = 2;
          this.mMatrix = Matrix4.identity()
            .translate({
              x: this.getImgInfo().offsetX,
              y: this.getImgInfo().offsetY
            })
            .scale({
              x: this.getImgInfo().scale,
              y: this.getImgInfo().scale
            })
        }

        console.log(this.TAG, "TapGesture onAction end");
      })
    )
    .gesture(GestureGroup(
      GestureMode.Parallel,
      // 缩放手势
      PinchGesture({
        // 两指缩放
        fingers: 2
      })
        .onActionStart(()=>{
          console.log(this.TAG, "PinchGesture onActionStart");
          this.tempScale = this.getImgInfo().scale;
        })
        .onActionUpdate((event)=>{
          console.log(this.TAG, "PinchGesture onActionUpdate" + JSON.stringify(event));
          if(event){
            this.getImgInfo().scale = this.tempScale * event.scale;
            this.mMatrix = Matrix4.identity()
              .translate({
                x: this.getImgInfo().offsetX,
                y: this.getImgInfo().offsetY
              })
              .scale({
                x: this.getImgInfo().scale,
                y: this.getImgInfo().scale
              })
          }
        })
        .onActionEnd(()=>{
          console.log(this.TAG, "PinchGesture onActionEnd");
 
        })
      ,
      // 拖动手势
      PanGesture()
        .onActionStart(()=>{
          console.log(this.TAG, "PanGesture onActionStart");
          this.startOffsetX = this.getImgInfo().offsetX;
          this.startOffsetY = this.getImgInfo().offsetY;
      })
        .onActionUpdate((event)=>{
          console.log(this.TAG, "PanGesture onActionUpdate" + JSON.stringify(event));
          if(event){
            let distanceX: number = this.startOffsetX + vp2px(event.offsetX) / this.getImgInfo().scale;
            let distanceY: number = this.startOffsetY + vp2px(event.offsetY) / this.getImgInfo().scale;
            this.getImgInfo().offsetX = distanceX;
            this.getImgInfo().offsetY = distanceY;
            this.mMatrix = Matrix4.identity()
              .translate({
                x: this.getImgInfo().offsetX,
                y: this.getImgInfo().offsetY
              })
              .scale({
                x: this.getImgInfo().scale,
                y: this.getImgInfo().scale
              })
          }
        })
        .onActionEnd(()=>{
          console.log(this.TAG, "PanGesture onActionEnd");
        })
    ))
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2124905.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

安泰功率放大器在压电马达中的应用实例

压电马达是一种利用压电效应的装置&#xff0c;通过在压电陶瓷上施加电场&#xff0c;引发其形变从而产生机械振动。在这个领域&#xff0c;功率放大器的应用为提高效率、精准控制和创新性应用提供了可能。下面将深入介绍功率放大器在压电马达中的实际应用例子。 压电效应是指在…

三维数字图像相关法(3D-DIC)用于复合材料力学性能测试

三维数字图像相关法&#xff08;3D-DIC技术&#xff09;&#xff0c;通过将物体表面随机分布的斑点或伪随机分布的人工散斑场作为变形信息载体&#xff0c;是应用于计算机视觉技术的一种图像测量方法&#xff0c;是一种非接触的&#xff0c;用于全场三维坐标、位移、应变及运动…

微信双开及多开!

在工作中大家可能会在pc端用到一台机器打开多个微信&#xff0c;但是现在windows更新之后&#xff0c;双击就只能打开一个了&#xff0c;以下有两种方式可以微信双开甚至多开。 第一种就是很简单的&#xff1a; ps: taskkill /f /im wechat.exe 这一步的用途就是先清除你电脑…

北京链家星河湾店·鸿鹄向阳杯羽毛球赛成功举办

9月10日&#xff0c;为了提高社区居民身体素质&#xff0c;促进社区居民间友好交流&#xff0c;北京链家星河湾店联合鸿鹄向阳俱乐部于在四季体育馆联合举办了羽毛球大赛&#xff0c;星河湾的社区居民积极参加&#xff0c;分组合作&#xff0c;在比赛中表现出团结合作不服输的精…

AbMole总RNA提取试剂 (免氯仿) AbMole Easy Trizol (Tcm Free)

AbMole Easy Trizol (Tcm Free) 是传统Trizol 的免氯仿升级版&#xff0c;广泛适用于从各类动物组织、植物材料、培养细胞、细菌等样品中提取Total RNA和Small RNA。与传统 Trizol 提取方法相比&#xff0c;本产品不需要使用氯仿进行分层&#xff0c;操作更简单&#xff0c;且全…

视频倒放怎么制作?4种方法教你制作倒放视频

视频倒放怎么制作&#xff1f;视频倒放&#xff0c;作为一种独具匠心的编辑手法&#xff0c;为视频创作开辟了新的维度。它不仅让常规画面变得非比寻常&#xff0c;还能以逆向视角展现时间流逝的奇幻效果&#xff0c;极大地丰富了视频的表现力和趣味性&#xff0c;是扩充视频创…

降本、创新、合作,谁才是连接器行业破除内卷的关键词?

如果用一个字来评价2024年的汽车行业&#xff0c;那就是「卷」。 ▲中国汽车保有量不断提升 图/Pixabay 长安汽车董事长朱华荣说&#xff1a;“汽车行业的卷&#xff0c;让中国品牌达到了新高度。” 吉利董事长李书福说&#xff1a;“中国汽车工业内卷程度全球第一&#xff0c;…

C++运算符重载实现日期类

目录 运算符重载 日期类头文件time.h time.h 日期类成员函数文件time.cpp 1.GetMonthDay函数 2.构造函数Date::Date(int year, int month, int day) 3.赋值函数Date& operator(const Date& d) 4.Date& Date::operator(int day)函数实现日期加天数的功能 5.前置…

用编程思想解决问题

1.是什么 分解法&#xff1a;将复杂的问题分解为一个个小又容易的问题&#xff0c;再逐一解决 先常后变&#xff1a;先使用常量来解决问题后使用变量来代替常量&#xff0c;使代码变得更灵活 例题一&#xff1a; 请你使用Switch分支结构根据输入的分数来定成绩所在区间&…

若依 ruoyi-vue 获取上一页路由 获取返回上一页路径 登录后跳转其他页面 登录进入后跳转至动态路由的第一个路由

参考文章:若依框架登录后跳转其他页面&获取不同的菜单&登录进入后跳转至动态路由的第一个路由 需求&#xff1a;登录成功&#xff0c;默认跳转至后端返回的动态路由的第一个路由 src/store/modules/permission.js 将动态路由的第一个路由存到缓存中 import cache …

多人开发小程序设置体验版的痛点

抛出痛点 在分配任务时,我们将需求分为三个分支任务,分别由前端A、B、C负责: 前端A: HCC-111-实现登录功能前端B: HCC-112-实现用户注册前端C: HCC-113-实现用户删除 相应地,我们创建三个功能分支: feature_HCC-111-实现登录功能feature_HCC-112-实现用户注册feature_HCC-1…

新手向教学,分分钟搭建个人定制化的 ChatgptGPT 聊天机器人

对话机器人变得越来越流行&#xff0c;它为用户提供了与技术互动的方式。OpenAI 的“GPT”模型让开发者能够创建复杂的对话机器人。 在本教程中&#xff0c;我们将使用 Python 和 OpenAI API 在运行 Ubuntu 的 DigitalOcean Droplet 上构建并部署你自己的终端 ChatGPT 机器人。…

基于SpringBoot+Vue+MySQL的校园食堂订餐

系统展示 用户前台界面 管理员后台界面 系统背景 随着信息技术的飞速发展和互联网的普及&#xff0c;传统校园食堂的运作模式已难以满足现代学生日益增长的便捷性、个性化需求。学生们希望能够在忙碌的学习生活中&#xff0c;通过更加高效、便捷的方式完成就餐选择&#xff0c;…

【算法篇】数组类(笔记)

目录 一、二分查找 1. 方法一 2. 方法二 二、移除元素 1. 暴力破解 2. 双指针法 三、有序数组的平方 双指针法 四、长度最小的子数组 1. 暴力破解 2. 滑动窗口 五、螺旋矩阵 II 一、二分查找 704. 二分查找 - 力扣&#xff08;LeetCode&#xff09;https://leet…

深入理解Java虚拟机:Jvm总结-虚拟机字节码执行引擎

第八章 虚拟机字节码执行引擎 8.1 意义 不受物理条件制约地定制指令集与执行引擎的结构体系&#xff0c;能够执行那些不被硬件直接支持的指令集格式。输入的是字节码二进制流&#xff0c;处理过程是字节码解析执行的等效过程&#xff0c;输出的是执行结果 8.2 运行时栈帧结构…

jupyter出错ImportError: cannot import name ‘np_utils‘ from ‘keras.utils‘ ,怎么解决?

文章前言 此篇文章主要是记录一下我遇到的问题以及我是如何解决的&#xff0c;希望下次遇到类似问题可以很快解决。此外&#xff0c;也希望能帮助到大家。 遇到的问题 出错&#xff1a;ImportError: cannot import name np_utils from keras.utils &#xff0c;如图&#xf…

Metasploit Pro 4.22.3-2024081901 (Linux, Windows) - 专业渗透测试框架

Metasploit Pro 4.22.3-2024081901 (Linux, Windows) - 专业渗透测试框架 Rapid7 Penetration testing, release Aug 19, 2024 请访问原文链接&#xff1a;https://sysin.org/blog/metasploit-pro-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页…

SonicWall SSL VPN曝出高危漏洞,可能导致防火墙崩溃

近日&#xff0c;有黑客利用 SonicWall SonicOS 防火墙设备中的一个关键安全漏洞入侵受害者的网络。 这个不当访问控制漏洞被追踪为 CVE-2024-40766&#xff0c;影响到第 5 代、第 6 代和第 7 代防火墙。SonicWall于8月22日对其进行了修补&#xff0c;并警告称其只影响防火墙的…

代码随想录27期|Python|Day51|​动态规划|​115.不同的子序列|​583. 两个字符串的删除操作​|72. 编辑距离

115. 不同的子序列 本题是在原来匹配子序列的基础上增加了统计所匹配的子序列个数&#xff0c;也就是dp数组的定义和更新公式和原来的有所区别。 1、dp数组的定义 dp[i][j]表示以i-1和j-1为末尾的字符串中&#xff0c;给定字符串s包含目标字符串t的个数。注意这里不是长度。…

vs2019成功连接数据库mysql

②在vs2019中创建新项目&#xff0c;注意x64 ③ 右击项目打开属性 ④添加include路径 ⑤添加lib路径 点击确定后点击应用 ⑥ 点击全部确定 ⑦ ⑧启动mysql 进入数据库&#xff1a; 在数据库中创建student的表 ⑨在va2019中输入下面代码测试 注意&#xff1a;密码换成自己…