HarmonyOS 鸿蒙Next实现图片裁剪,头像裁剪
HarmonyOS 鸿蒙Next实现图片裁剪,头像裁剪
简介
利用Canvas组件、Image组件、file.picker、手势事件实现了一个图片裁剪组件,用来对图片进行裁剪生成一张新的图片,用于头像裁剪上传等实际业务场景。
功能介绍
- 支持缩放图片。
- 支持缩放图片时,对图片超过裁剪框的边界控制。
- 支持自定义裁剪框大小和样式。
- 支持保存时指定图片格式。
- 支持指定图片质量。
- 支持指定最大缩放系数。
- 支持双击重置图片。
实现细节
1. 图片选择
在点击事件中调用file.picker,在相册中选择图片,选择成功以后获取URI,传到裁剪页面
let URI:string = ‘’;
let PhotoSelectOptions = new picker.PhotoSelectOptions();
PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.maxSelectNumber = 1;
const photoViewPicker = new picker.PhotoViewPicker();
photoViewPicker.select(PhotoSelectOptions).then((photoSelectResult) => {
console.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ’ + JSON.stringify(photoSelectResult));
Logger.info('PhotoViewPicker.select successfully, PhotoSelectResult uri: ’ + JSON.stringify(photoSelectResult));
URI = photoSelectResult.photoUris[<span class="hljs-number"><span class="hljs-number">0</span></span>];
Logger.info(`${URI}`)
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span>(URI){
setTimeout(()=>{
router.pushUrl({
url: <span class="hljs-string"><span class="hljs-string">'pages/Example'</span></span>,
params: {
uri: URI
}
})
}, <span class="hljs-number"><span class="hljs-number">300</span></span>)
}
}).catch((err:BusinessError) => {
console.error(<span class="hljs-string"><span class="hljs-string">'PhotoViewPicker.select failed with err: '</span></span> + err);
Logger.error(<span class="hljs-string"><span class="hljs-string">'PhotoViewPicker.select failed with err: '</span></span> + err);
});</code><button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button></pre> <p></p> <p>2. 图片裁剪布局</p> <p> 布局:使用RelativeContainer布局,分别为画布、要操作的图片、遮罩、裁剪进行定位布局</p> <pre style="position: relative;"><code class="language-javascript hljs hljs "> RelativeContainer() {
<span class="hljs-comment"><span class="hljs-comment">// 画布</span></span>
Canvas(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.context)
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxHeight + <span class="hljs-string"><span class="hljs-string">'px'</span></span>)
.backgroundColor(<span class="hljs-string"><span class="hljs-string">'#333'</span></span>)
.alignRules({
top: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: VerticalAlign.Top },
left: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: HorizontalAlign.Start }
})
.id(<span class="hljs-string"><span class="hljs-string">"canvas"</span></span>)
<span class="hljs-comment"><span class="hljs-comment">// 显示的操作图片</span></span>
Row(){
Image(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pixelMap)
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.objectFit(ImageFit.Fill)
}
.offset({ x: px2vp(<span class="hljs-built_in"><span class="hljs-built_in">Math</span></span>.floor(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgLeft)), y: px2vp(<span class="hljs-built_in"><span class="hljs-built_in">Math</span></span>.floor(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgTop)) })
.width(px2vp(<span class="hljs-built_in"><span class="hljs-built_in">Math</span></span>.floor(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgWidth)))
.height(px2vp(<span class="hljs-built_in"><span class="hljs-built_in">Math</span></span>.floor(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgHeight)))
.alignRules({
top: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: VerticalAlign.Top },
left: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: HorizontalAlign.Start }
})
.id(<span class="hljs-string"><span class="hljs-string">"imageWrap"</span></span>)
<span class="hljs-comment"><span class="hljs-comment">// 遮罩</span></span>
Canvas(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.contextMask)
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxHeight + <span class="hljs-string"><span class="hljs-string">'px'</span></span>)
.alignRules({
top: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: VerticalAlign.Top },
left: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: HorizontalAlign.Start }
})
.id(<span class="hljs-string"><span class="hljs-string">"mask"</span></span>)
.onReady(()=>{
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.drawMask()
})
<span class="hljs-comment"><span class="hljs-comment">// 裁剪框</span></span>
Row(){}
.width(px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperWidth))
.height(px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperHeight))
.border({ width: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.lineWidth, color: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.lineColor })
.offset({x: px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperLeft), y: px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperTop)})
.alignRules({
top: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: VerticalAlign.Top },
left: { anchor: <span class="hljs-string"><span class="hljs-string">'__container__'</span></span>, align: HorizontalAlign.Start }
})
.id(<span class="hljs-string"><span class="hljs-string">"box"</span></span>)
}
.width(<span class="hljs-string"><span class="hljs-string">'100%'</span></span>)
.height(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxHeight + <span class="hljs-string"><span class="hljs-string">'px'</span></span>)</code><button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button></pre> <p>3. 裁剪的实现</p> <p>首先通过手势对Image进行操作,控制边界和放大缩小,并记录相应位置。</p> <pre style="position: relative;"><code class="language-javascript hljs hljs ">.gesture(GestureGroup(GestureMode.Exclusive,
PinchGesture({ fingers: <span class="hljs-number"><span class="hljs-number">2</span></span> })
.onActionStart(() => {
<span class="hljs-comment"><span class="hljs-comment">// 上次的位置和大小信息</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetX = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgLeft
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetY = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgTop
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgWidth
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgHeight = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgHeight
})
.onActionUpdate((event?: GestureEvent) => {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (event) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale = <span class="hljs-built_in"><span class="hljs-built_in">Number</span></span>(event.scale.toFixed(<span class="hljs-number"><span class="hljs-number">2</span></span>))
<span class="hljs-comment"><span class="hljs-comment">// 如果缩放后小于最大缩放尺寸</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale < <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxMaxImgWidth) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgLeft = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetX - (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale / <span class="hljs-number"><span class="hljs-number">2</span></span> - <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth / <span class="hljs-number"><span class="hljs-number">2</span></span>)
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgTop = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetY - (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgHeight * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale / <span class="hljs-number"><span class="hljs-number">2</span></span> - <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgHeight / <span class="hljs-number"><span class="hljs-number">2</span></span>)
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgWidth = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgHeight = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgHeight * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale
}
}
})
.onActionEnd(() => {
<span class="hljs-comment"><span class="hljs-comment">// 图片的宽高比 大于 裁剪框的宽高比</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">const</span></span> isImageWider = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgRatio >= <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperRatio;
<span class="hljs-keyword"><span class="hljs-keyword">const</span></span> dimensionToCheck = isImageWider ? <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgHeight : <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgWidth;
<span class="hljs-keyword"><span class="hljs-keyword">const</span></span> cropperDimension = isImageWider ? <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperHeight : <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperWidth;
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (dimensionToCheck * <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale <= cropperDimension) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.resetImg();
} <span class="hljs-keyword"><span class="hljs-keyword">else</span></span> {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.currentScale = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgScale;
}
}),
PanGesture()
.onActionStart(() => {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetX = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgLeft
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.preCtxImgOffsetY = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgTop
})
.onActionUpdate((event?: GestureEvent) => {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (event) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.updateImagePosition(event)
}
}),
<span class="hljs-comment"><span class="hljs-comment">// 双击还原</span></span>
TapGesture({ count: <span class="hljs-number"><span class="hljs-number">2</span></span> })
.onAction((event?: GestureEvent) => {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.resetImg()
})
))</code><button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button></pre> <p>在点击保存的时候,将图片的数据绘制到canvas画布上,重点是要根据相应的放大缩小和位置信息,获取指定区域的pixelMap,根据压缩参数进行压缩,并转换为base64,返回给调用页面。</p> <pre style="position: relative;"><code class="language-javascript hljs hljs ">Button(<span class="hljs-string"><span class="hljs-string">'保存'</span></span>)
.onClick(() => {
<span class="hljs-keyword"><span class="hljs-keyword">if</span></span> (<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.pixelMap) {
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.context.clearRect(<span class="hljs-number"><span class="hljs-number">0</span></span>, <span class="hljs-number"><span class="hljs-number">0</span></span>, px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxWidth), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxHeight))
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.context.drawImage(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgObj.img, <span class="hljs-number"><span class="hljs-number">0</span></span>, <span class="hljs-number"><span class="hljs-number">0</span></span>, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgWidth, <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.imgHeight, px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgLeft), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgTop), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgWidth), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxImgHeight))
<span class="hljs-comment"><span class="hljs-comment">//获取画布上指定区域的pixelMap</span></span>
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.savePixelMap = <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.context.getPixelMap(px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperLeft), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperTop), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperWidth), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.cropperHeight))
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> imagePackerApi: image.ImagePacker = image.createImagePacker();
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> packOpts: image.PackingOption = { format: `image/${<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.fileType}`, quality: <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.quality };
imagePackerApi.packing(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.savePixelMap, packOpts).then((readBuffer: <span class="hljs-built_in"><span class="hljs-built_in">ArrayBuffer</span></span>) => {
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> bufferArr = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> <span class="hljs-built_in"><span class="hljs-built_in">Uint8Array</span></span>(readBuffer);
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> help = <span class="hljs-keyword"><span class="hljs-keyword">new</span></span> util.Base64Helper;
<span class="hljs-keyword"><span class="hljs-keyword">let</span></span> base64 = help.encodeToStringSync(bufferArr);
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.context.clearRect(<span class="hljs-number"><span class="hljs-number">0</span></span>, <span class="hljs-number"><span class="hljs-number">0</span></span>, px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxWidth), px2vp(<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.ctxHeight));
<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.onConfirm && <span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.onConfirm(`data:image/${<span class="hljs-keyword"><span class="hljs-keyword">this</span></span>.fileType};base64,` + base64);
}).catch((err: BusinessError) => {
Logger.error(`packing err: ${err}}`)
})
}
})</code><button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button></pre></div></div><br><br>关于HarmonyOS 鸿蒙Next实现图片裁剪,头像裁剪的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。
18 回复
大佬可以分享代码参考一下吗
您可以看一下,代码贴上去了
好的,谢谢大佬
有完整的源码吗?
同求,学习
找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17
大佬可以贴一下完整源码的地址吗?
帖子代码不全,我自己实现了一个,已经发布到仓库了:https://ohpm.openharmony.cn/#/cn/detail/@xinyansoft%2Foh-crop。
有需要的可以试试看,个人水平有限,有bug轻喷,可以在GitHub issue反馈: https://github.com/sahooz/oh-crop
支持把取景框改成圆形的吗?
支持图片拖拽大小吗?
支持,可以看一下演示的动图。代码在帖子里面
代码有缺失,能给个全点的吗?
大佬源码能提供下吗,万分感谢
就不能把完整代码发出来吗,写了等于没写
大佬源码能提供下吗,万分感谢
求完整源码啊,万分感谢🙏
把这个上传git吧 大佬
回到顶部