HarmonyOS APP实战-基于Image Kit的图像处理APP - 第5篇:图片旋转与翻转 HarmonyOS APP实战-基于Image Kit的图像处理APP - 第5篇旋转翻转效果1. 开篇上一篇我们完成了图片的缩放与裁剪功能通过滑块控制缩放比例、拖动选择裁剪区域并利用PixelMap.scale和ImageBitmap的裁剪能力实现了用户交互。现在图片已经能按需调整尺寸和内容。但实际编辑中方向调整同样常见拍歪了的照片需要旋转回正或者水平/垂直翻转来创造镜像效果。本篇我们基于PixelMap.rotate和PixelMap.flip实现0°、90°、180°、270°旋转以及水平、垂直翻转同时为每次操作添加平滑的动画过渡让视觉反馈更自然。所有功能仍将在之前的ImageProcessPage基础上扩展保证项目连续。2. 核心实现2.1 基础配置 — 导入模块与工具类封装首先在common/utils/PixelMapUtils.ets中封装旋转与翻转的静态方法统一处理PixelMap变换。注意PixelMap.rotate的原型为rotate(angle: number, pivotX?: number, pivotY?: number)默认旋转中心为图片中心PixelMap.flip的原型为flip(horizontal: boolean, vertical: boolean)。// common/utils/PixelMapUtils.etsimport{image}fromkit.ImageKit;exportclassPixelMapUtils{/** * 旋转PixelMap指定角度90的整数倍 * param pixelMap 源像素图 * param angle 旋转角度仅支持0、90、180、270实际会取模360 * returns 旋转后的新PixelMap原地修改并返回自身 */staticrotate(pixelMap:image.PixelMap,angle:number):image.PixelMap{// 将角度归一化到0~360constnormalizedAngle((angle%360)360)%360;// 计算旋转中心图片宽高中点constpivotXpixelMap.getImageInfoSync().size.width/2;constpivotYpixelMap.getImageInfoSync().size.height/2;// 执行旋转第一个参数为角度后两个为可选旋转中心pixelMap.rotate(normalizedAngle,pivotX,pivotY);returnpixelMap;}/** * 水平翻转 * param pixelMap 源像素图 * returns 翻转后的PixelMap原地修改并返回 */staticflipHorizontal(pixelMap:image.PixelMap):image.PixelMap{pixelMap.flip(true,false);// horizontaltrue, verticalfalsereturnpixelMap;}/** * 垂直翻转 * param pixelMap 源像素图 * returns 翻转后的PixelMap原地修改并返回 */staticflipVertical(pixelMap:image.PixelMap):image.PixelMap{pixelMap.flip(false,true);// horizontalfalse, verticaltruereturnpixelMap;}/** * 同时水平垂直翻转等效180°旋转但方向不同 */staticflipBoth(pixelMap:image.PixelMap):image.PixelMap{pixelMap.flip(true,true);returnpixelMap;}}关键点说明PixelMap.rotate的angle单位是度顺时针为正。文档未限定必须为90的倍数但实用中常用90、180、270。PixelMap.flip(horizontal, vertical)两个布尔参数分别控制水平和垂直方向是否翻转。true表示该方向翻转。所有方法都直接修改原PixelMap对象并返回自身便于链式调用。注意不要重复引用同一个PixelMap做多次操作除非你明确需要累积效果。getImageInfoSync()用于获取宽高必须在调用前确保PixelMap有效。2.2 核心逻辑 — 带动画过渡的旋转翻转操作在页面中我们需要为每次操作添加动画过渡。设计思路点击旋转按钮时首先使用animateTo让Image组件的rotate属性从0°变化到目标角度例如90°同时异步执行PixelMapUtils.rotate。动画完成后将Image的rotate重置为0并更新显示用的PixelMap这样用户看到的是平滑的旋转动画而最终的像素确实被改变。翻转操作类似但动画可以使用scale缩放模拟翻转效果此处我们统一采用旋转动画翻转操作直接更新PixelMap后配合淡入过渡。// pages/ImageProcessPage.ets 中的旋转翻转相关方法部分import{image}fromkit.ImageKit;import{PixelMapUtils}from../common/utils/PixelMapUtils;// 省略其他状态定义.../** * 旋转操作带动画 * param targetAngle 要旋转的角度90/180/270 */asyncrotateWithAnimation(targetAngle:number):Promisevoid{// 1. 动画旋转Image组件awaitthis.animateImageRotation(targetAngle);// 2. 实际修改PixelMapif(this.currentPixelMap){PixelMapUtils.rotate(this.currentPixelMap,targetAngle);// 更新Image的源触发组件重新渲染this.updateSource();}}/** * 翻转操作带淡入动画 * param horizontal 是否水平翻转 * param vertical 是否垂直翻转 */asyncflipWithAnimation(horizontal:boolean,vertical:boolean):Promisevoid{if(!this.currentPixelMap)return;// 执行翻转同步if(horizontalvertical){PixelMapUtils.flipBoth(this.currentPixelMap);}elseif(horizontal){PixelMapUtils.flipHorizontal(this.currentPixelMap);}elseif(vertical){PixelMapUtils.flipVertical(this.currentPixelMap);}// 使用animateTo产生一个淡入效果视觉上平滑过渡animateTo({duration:300,curve:curves.EaseInOut},(){this.imageOpacity0.3;// 先变半透明}).then((){// 更新source后会重新显示此时再恢复不透明度this.updateSource();animateTo({duration:300,curve:curves.EaseInOut},(){this.imageOpacity1.0;});});}关键点说明animateTo是HarmonyOS提供的显式动画API返回一个Promise可用于异步等待动画结束。旋转动画采用Image组件的rotate属性该属性接受一个{ x?: number, y?: number, z?: number }对象我们只设置z轴旋转。翻转动画没有对应的UI属性我们选择先对一个中间的PixelMap进行翻转然后通过透明度动画过渡。也可以使用scale的负值来实现UI上的翻转动画但为了保持像素真正翻转我们直接在PixelMap上操作。updateSource方法将更新的PixelMap重新赋值给State pixelMapUri触发Image重新加载。以下是updateSource的实现updateSource():void{if(this.currentPixelMap){// 将PixelMap转换为临时URI供Image显示实际项目中应使用PixelMap直接赋值// 这里简单重新赋值同一个PixelMapImage组件在API 11支持直接绑定PixelMapthis.pixelMapForDisplaythis.currentPixelMap;}}2.3 完整页面 — 集成旋转翻转按钮与动画下面给出ImageProcessPage.ets的完整代码它继承了上一篇的缩放裁剪UI并在底部新增旋转翻转工具栏。为节省篇幅上一节的缩放裁剪相关代码已保留但标注了省略。// pages/ImageProcessPage.etsimport{image}fromkit.ImageKit;import{curves}fromkit.ArkUI;import{PixelMapUtils}from../common/utils/PixelMapUtils;EntryComponentstruct ImageProcessPage{StateoriginalPixelMap:image.PixelMap|nullnull;// 原始未修改的图StatecurrentPixelMap:image.PixelMap|nullnull;// 当前编辑状态的图StatepixelMapForDisplay:image.PixelMap|nullnull;// 用于Image绑定的PixelMapStateimageOpacity:number1.0;// 旋转动画辅助变量StateimageRotation:number0;// 上一篇的缩放裁剪状态...略aboutToAppear():void{// 假设从上一页面传入了一个PixelMap// 实际项目中从PhotoViewPicker获取后解码得到}build(){Column(){// 图片显示区域Image(this.pixelMapForDisplay).width(100%).height(400).objectFit(ImageFit.Contain).rotate({z:this.imageRotation}).opacity(this.imageOpacity).transition({type:TransitionType.Replace,opacity:0.8})// 表情替换过渡// 缩放裁剪控件上一篇实现略// --- 旋转翻转工具栏 ---Row(){Button(左旋90°).onClick(()this.handleRotate(-90))Button(右旋90°).onClick(()this.handleRotate(90))Button(旋转180°).onClick(()this.handleRotate(180))Button(水平翻转).onClick(()this.flipWithAnimation(true,false))Button(垂直翻转).onClick(()this.flipWithAnimation(false,true))}.justifyContent(FlexAlign.SpaceEvenly).width(100%).padding(10)}.width(100%).height(100%).padding(10)}// 处理旋转带动画asynchandleRotate(deltaAngle:number):Promisevoid{if(!this.currentPixelMap)return;// 累计旋转角度UI动画用constcurrentRotationthis.imageRotation;consttargetRotationcurrentRotationdeltaAngle;// 1. 执行UI旋转动画awaitanimateTo({duration:400,curve:curves.EaseInOut},(){this.imageRotationtargetRotation;});// 2. 实际修改PixelMap注意deltaAngle是相对于原始方向而PixelMap rotate会累积但我们每次基于当前PixelMap旋转deltaAnglePixelMapUtils.rotate(this.currentPixelMap,deltaAngle);// 更新显示源this.updateSource();// 3. 重置UI旋转属性因为PixelMap已经变了UI不需要再额外旋转awaitanimateTo({duration:0},(){this.imageRotation0;});}// 翻转操作带淡入动画asyncflipWithAnimation(horizontal:boolean,vertical:boolean):Promisevoid{if(!this.currentPixelMap)return;// 执行翻转同步if(horizontalvertical){PixelMapUtils.flipBoth(this.currentPixelMap);}elseif(horizontal){PixelMapUtils.flipHorizontal(this.currentPixelMap);}elseif(vertical){PixelMapUtils.flipVertical(this.currentPixelMap);}// 淡出动画awaitanimateTo({duration:200},(){this.imageOpacity0.3;});// 更新图片源this.updateSource();// 淡入动画awaitanimateTo({duration:300},(){this.imageOpacity1.0;});}updateSource():void{// 直接赋值PixelMapAPI 11支持Image绑定PixelMapthis.pixelMapForDisplaythis.currentPixelMap;}}关键点说明Image组件的rotate属性用于UI动画我们在handleRotate中先让UI旋转然后更新PixelMap最后将imageRotation归零。这样实际像素已经改变UI不再需要额外旋转。翻转动画采用透明度淡入淡出简单有效。也可以使用transition或scale的翻转动画但为了代码简洁选择了透明度。调用handleRotate时deltaAngle可以是负值左旋PixelMapUtils.rotate已处理角度归一化所以支持负角度。注意Image组件在HarmonyOS API 11可以直接绑定PixelMap对象无需转换为uri。如果使用低版本需要先通过image.Packer编码为文件再读取。本篇基于API 11。3. 运行验证点击“右旋90°”按钮图片会先平滑旋转90度UI动画然后图片内容刷新为旋转后的像素UI旋转归零。再点击“水平翻转”图片经过半透明过渡后镜像翻转。多个按钮组合使用例如先旋转再翻转所有操作都正确叠加。底部工具栏按钮响应迅速动画流畅。注意如果连续快速点击旋转按钮由于动画队列可能产生冲突需要在实际项目中加入防抖或状态锁本例中已通过animateTo的Promise串行化管理连续点击会按顺序执行不会错乱。4. 小结与预告本篇我们为图片编辑APP增加了旋转90°、180°、270°和水平/垂直翻转功能使用PixelMap.rotate和PixelMap.flip真正修改了像素数据并通过animateTo实现了视觉上的动画过渡让操作反馈更具质感。目前我们已具备缩放、裁剪、旋转、翻转等基础编辑能力。下一篇将进入颜色调整领域利用PixelMap的色彩矩阵实现滤镜灰度、怀旧、冷色等让图片风格一键切换敬请期待。