HarmonyOS 鸿蒙Next实现图片裁剪,头像裁剪

发布于 1周前 作者 itying888 来自 鸿蒙OS

HarmonyOS 鸿蒙Next实现图片裁剪,头像裁剪

image-cropper.gif

简介

利用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(()=&gt;{
              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) =&gt; {
          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>&nbsp; &nbsp;布局:使用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(()=&gt;{
              <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(() =&gt; {
          <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) =&gt; {
          <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 &lt; <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(() =&gt; {
          <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 &gt;= <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 &lt;= 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(() =&gt; {
          <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) =&gt; {
          <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) =&gt; {
          <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(() =&gt; {
          <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>) =&gt; {
              <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 &amp;&amp; <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) =&gt; {
              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吧 大佬

求完整源码啊,万分感谢🙏
回到顶部