
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第4篇缩放裁剪功能实现1. 开篇第3篇中我们实现了图片解码与信息提取功能。核心产出了ImageInfoPage组件它通过image.createImageSource创建图片源调用imageSource.getImageProperty获取图片宽度、高度等信息最终将PixelMap和图片元数据展示在界面上。现在我们有了可用的PixelMap像素数据下一步就是对这张图片进行操作——缩放和裁剪是图片处理中最基础也最常用的两种变换操作。本篇我们将开发PixelMapUtils工具类封装PixelMap.scale和PixelMap.crop两个核心API。同时构建ScalePage缩放页面和CropPage裁剪页面分别提供滑块控制缩放比例、矩形选框选择裁剪区域两种交互方式并实时预览效果。完成本篇后APP的图片编辑能力将真正可用。2. 核心实现2.1 基础配置在pages目录下创建ScalePage.ets和CropPage.ets同时创建utils/PixelMapUtils.ets。本模块需要先配置必要的导入和类型声明。// utils/PixelMapUtils.ets// 工具类封装PixelMap的常用操作方法import{image}fromkit.ImageKit;/** * 缩放参数接口 * 用于传递缩放比例支持X轴和Y轴独立缩放 */exportinterfaceScaleParams{scaleX:number;// X轴缩放比例1.0为原始大小scaleY:number;// Y轴缩放比例1.0为原始大小}/** * 裁剪区域接口 * 定义矩形选区的位置和尺寸 */exportinterfaceCropRegion{x:number;// 起始X坐标y:number;// 起始Y坐标width:number;// 选区宽度height:number;// 选区高度}关键点说明ScaleParams和CropRegion接口专门为本APP的缩放裁剪功能定义后续页面组件直接使用导入image模块是使用PixelMap及其方法的必要条件在HarmonyOS的ArkTS开发中所有图片处理API都封装在kit.ImageKit中2.2 核心逻辑PixelMapUtils工具类在PixelMapUtils.ets中实现缩放和裁剪两个静态方法分别调用PixelMap.scale和PixelMap.crop。两个方法都返回新的PixelMap对象不修改原始数据。// utils/PixelMapUtils.ets续// 完整的PixelMapUtils工具类提供缩放和裁剪功能exportclassPixelMapUtils{/** * 缩放PixelMap * param pixelMap 原始PixelMap对象 * param params 缩放参数scaleXscaleY * returns 缩放后的新PixelMap失败返回null */publicstaticasyncscale(pixelMap:PixelMap,params:ScaleParams):PromisePixelMap|null{try{// 校验输入缩放比例必须为正数否则保持原样if(params.scaleX0||params.scaleY0){console.warn(PixelMapUtils.scale: invalid scale params, using default 1.0);returnpixelMap;}// 调用PixelMap的scale方法进行缩放// 参数scaleX, scaleY为浮点数1.0表示原尺寸// 注意scale是直接修改PixelMap对象本身的像素数据awaitpixelMap.scale(params.scaleX,params.scaleY);// 返回缩放后的PixelMap原对象已被修改returnpixelMap;}catch(error){console.error(PixelMapUtils.scale error:${JSON.stringify(error)});returnnull;}}/** * 裁剪PixelMap * param pixelMap 原始PixelMap对象 * param region 裁剪区域x, y, width, height * returns 裁剪后的新PixelMap失败返回null */publicstaticasynccrop(pixelMap:PixelMap,region:CropRegion):PromisePixelMap|null{try{// 校验裁剪区域的有效性不能超出原图边界宽高必须大于0constimageInfopixelMap.getImageInfoSync();if(region.x0||region.y0||region.width0||region.height0||region.xregion.widthimageInfo.size.width||region.yregion.heightimageInfo.size.height){console.warn(PixelMapUtils.crop: invalid crop region);returnnull;}// 调用PixelMap的crop方法进行裁剪// 参数为Region对象包含x, y, width, height四个属性// crop会返回一个新的PixelMap不修改原始对象constcroppedPixelMap:PixelMapawaitpixelMap.crop(region);returncroppedPixelMap;}catch(error){console.error(PixelMapUtils.crop error:${JSON.stringify(error)});returnnull;}}}关键点说明PixelMap.scale是异步方法直接修改原始PixelMap的像素数据因此最终返回是同一个对象但像素数据已变PixelMap.crop也是异步方法但它不修改原始对象而是返回一个全新的PixelMap原始数据保留两个方法都有异常捕获因为图片处理涉及大量内存操作需要处理可能的失败场景裁剪区域需要校验合法性不能为负不能超出原图尺寸宽度高度必须为正2.3 完整模块CropPage裁剪页面裁剪页面提供矩形选框交互用户通过触摸拖拽选择一个矩形区域确认后调用PixelMap.crop裁剪并显示结果。缩放页面原理类似但交互更简单滑块控制比例这里重点展示裁剪页面的完整代码。// pages/CropPage.ets// 图片裁剪页面支持矩形选框选择裁剪区域import{image}fromkit.ImageKit;import{PixelMapUtils,CropRegion}from../utils/PixelMapUtils;import{router}fromkit.ArkUI;EntryComponentstruct CropPage{// 从上一页传递过来的原始PixelMapStateprivateoriginPixelMap:PixelMap|nullnull;// 裁剪后生成的PixelMapStateprivatecroppedPixelMap:PixelMap|nullnull;// 选框的起始坐标和尺寸在图片显示坐标系中StateprivaterectX:number0;StateprivaterectY:number0;StateprivaterectWidth:number100;StateprivaterectHeight:number100;// 是否正在拖拽选框privateisDragging:booleanfalse;// 图片在屏幕上的实际显示尺寸用于坐标映射StateprivatedisplayWidth:number0;StateprivatedisplayHeight:number0;// 页面返回携带裁剪结果回上一页privategoBackWithResult():void{if(this.croppedPixelMap){router.back({url:pages/EditPage,params:{pixelMap:this.croppedPixelMap}});}}build(){Column(){// 标题栏Row(){Button(返回).onClick(()router.back())Text(图片裁剪)Button(应用裁剪).onClick(async(){if(this.originPixelMapnull)return;// 将屏幕坐标映射到图片真实坐标// 假设图片等比例缩放显示需要计算比例因子constimageInfothis.originPixelMap.getImageInfoSync();constscaleWimageInfo.size.width/this.displayWidth;constscaleHimageInfo.size.height/this.displayHeight;// 使用较精确的缩放因子取平均或各自独立这里使用各自的constregion:CropRegion{x:Math.round(this.rectX*scaleW),y:Math.round(this.rectY*scaleH),width:Math.round(this.rectWidth*scaleW),height:Math.round(this.rectHeight*scaleH)};constresultawaitPixelMapUtils.crop(this.originPixelMap,region);if(result){this.croppedPixelMapresult;}})}.padding(10).width(100%).justifyContent(FlexAlign.SpaceBetween)// 显示图片的区域作为裁剪框的容器Stack(){// 原图预览Image(this.originPixelMap).width(100%).objectFit(ImageFit.Contain).onAreaChange((_,area){// 获取图片实际显示区域大小this.displayWidtharea.widthasnumber;this.displayHeightarea.heightasnumber;})// 裁剪矩形选框透明遮罩边框Stack(){// 半透明遮罩覆盖非选区Rect().width(100%).height(100%).fill(rgba(0,0,0,0.3))// 选框内部透明露出图片Rect().width(this.rectWidth).height(this.rectHeight).position({x:this.rectX,y:this.rectY}).fill(rgba(255,255,255,0)).strokeWidth(2).stroke(Color.White)}// 触摸事件处理拖拽选框或调整选框大小.onTouch((event:TouchEvent){if(event.touches.length0)return;consttouchevent.touches[0];switch(event.type){caseTouchType.Down:// 点击在选框区域内时开始拖拽if(touch.xthis.rectXtouch.xthis.rectXthis.rectWidthtouch.ythis.rectYtouch.ythis.rectYthis.rectHeight){this.isDraggingtrue;}break;caseTouchType.Move:if(this.isDragging){// 拖拽选框移动保持选框大小不变this.rectXMath.max(0,Math.min(this.displayWidth-this.rectWidth,touch.x-this.rectWidth/2));this.rectYMath.max(0,Math.min(this.displayHeight-this.rectHeight,touch.y-this.rectHeight/2));}else{// 一手指拖拽时计算新的选框起点简易实现从点击点开始this.rectXMath.min(touch.x,this.rectXthis.rectWidth);this.rectYMath.min(touch.y,this.rectYthis.rectHeight);this.rectWidthMath.abs(touch.x-this.rectX);this.rectHeightMath.abs(touch.y-this.rectY);}break;caseTouchType.Up:this.isDraggingfalse;break;}}).clip(true)}.width(100%).height(70%)// 底部操作区显示裁剪后效果缩略图提供确认按钮Row(){// 裁剪结果预览Image(this.croppedPixelMap).width(120).height(120).objectFit(ImageFit.Contain).border({width:1,color:#cccccc})Column(){Button(重新选择).onClick((){this.croppedPixelMapnull;})Button(返回编辑).onClick(()this.goBackWithResult())}}.padding(10).width(100%).justifyContent(FlexAlign.SpaceEvenly)}.width(100%).height(100%).onPageShow((){// 从路由参数获取原始PixelMapconstparamsrouter.getParams()asRecordstring,Object;if(paramsparams[pixelMap]){this.originPixelMapparams[pixelMap]asPixelMap;}})}}关键点说明裁剪交互分为两种点击选框内部拖拽移动选框位置点击选框外部重新绘制选框屏幕坐标与图片真实坐标的映射计算是通过displayWidth和图片真实尺寸的比例因子实现的保证了裁剪区域的精确性onAreaChange用于获取Image组件实际显示区域大小配合objectFit为ImageFit.Contain时图片可能留有空白边距裁剪结果通过router.back传回编辑页面完成闭环操作本页面使用了PixelMapUtils.crop工具方法并在UI层完成坐标映射和校验3. 运行验证启动APP后从首页选择一张图片进入编辑页面。点击“缩放”按钮进入ScalePage拖动滑块滑块值从0.1到3.0图片实时缩放缩放后的图片可以保存或返回继续编辑。点击“裁剪”按钮进入CropPage手指在图片上拖拽绘制矩形选框选框可以自由移动点击“应用裁剪”后出现裁剪结果缩略图确认后携带裁剪结果返回编辑页面。预期效果缩放滑块平滑图片实时响应裁剪选框边线清晰可拖拽裁剪边缘精确无偏移。运行过程无崩溃或异常弹窗。4. 小结与预告本篇完成了缩放和裁剪两大核心交互功能PixelMapUtils工具类封装了PixelMap.scale和PixelMap.crop两个关键API的调用逻辑CropPage和ScalePage页面负责接收用户交互参数调用工具类处理图片并实时显示处理结果。现在APP可以做到图片缩放任意比例和矩形区域裁剪这两个基础操作是一切图片编辑的基石。下一篇「旋转翻转效果」将使用PixelMap的rotate和flip方法实现图片以90°、180°、270°为单位旋转以及水平/垂直翻转。操作同样支持实时预览并会加入方向感知的图标按钮交互。