最近开发一款鸿蒙应用,需要用到图片裁剪,系统的图片编辑功能如今没有对外开放API调用,以是需要本身整一套裁剪的功能。由于刚接触这个ArkTS语言,探索的过程还是挺费劲。
废话不多说,先直接看效果:
支持自由裁剪和固定比例裁剪。
官方也提供了图片编辑功能的实现:
图片编辑-HarmonyOS NEXT-Codelabs-华为开发者同盟 (huawei.com)
官方效果:
这种方式功能比力简朴,裁剪是手动点击的,大小是固定的,并不能满足我的需求。
于是向官方求助,给了一个实现的demo,大致代码如下:
- model/Bean.ets
- export interface RectPosition {
- x: number;
- y: number;
- height: number;
- width: number;
- }
- export enum ActionType {
- topLeft,
- topRight,
- bottomLeft,
- bottomRight,
- move
- }
- export interface Position {
- x: number;
- y: number;
- }
- export interface InitPosition {
- x: number;
- y: number;
- width: number;
- height: number;
- }
- pages/ImageCropPage.ets
- import image from '@ohos.multimedia.image';
- import { resourceManager } from '@kit.LocalizationKit';
- import { RectPosition, ActionType, Position, InitPosition } from '../model/Bean'
- const TAG = "IMAGE_CROPPING"
- @Entry
- @Component
- struct ImageCropPage {
- @Provide pixelMap: image.PixelMap | undefined = undefined;
- @Provide pixelMapBackUp: image.PixelMap | undefined = undefined;
- @Provide imageInfo: image.ImageInfo | undefined = undefined;
- private settings: RenderingContextSettings = new RenderingContextSettings(true);
- private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
- private settings2: RenderingContextSettings = new RenderingContextSettings(true);
- private canvasContext2: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings2);
- private settings3: RenderingContextSettings = new RenderingContextSettings(true);
- private canvasContext3: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings3);
- private actionType: ActionType = ActionType.move;
- private rotateOn: boolean = false
- private sw: number = 266; //图片展示框固定宽度
- private sh: number = 390; //图片展示框固定高度
- @State clipImageWidth: number = 0;
- @State clipImageHeight: number = 0;
- @State imageArea: RectPosition = {
- x: 0,
- y: 0,
- width: 0,
- height: 0
- };
- private touchPosition: Position = {
- x: 0,
- y: 0,
- };
- @State initPosition: InitPosition = {
- x: 0,
- y: 0,
- width: 0,
- height: 0,
- }
- @State isCrop: boolean = false
- @State cropImageInfo: image.ImageInfo | undefined = undefined;
- @State pixelMapChange: boolean = false
- @State @Watch('drawMask') clipRect: RectPosition = {
- x: 0,
- y: 0,
- height: 0,
- width: 0
- };
- build() {
- Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
- if (this.isCrop) {
- if (this.pixelMapChange) {
- Image(this.pixelMap)
- .width(this.clipImageWidth)
- .height(this.clipImageHeight)
- .margin({ top: '10%' })
- .objectFit(ImageFit.Fill)
- } else {
- Image(this.pixelMap)
- .width(this.clipImageWidth)
- .height(this.clipImageHeight)
- .margin({ top: '10%' })
- .objectFit(ImageFit.Fill)
- }
- } else {
- Canvas(this.canvasContext)
- .width(this.sw)
- .height(this.sh)
- .onReady(() => {
- this.drawImage()
- })
- .onAreaChange((value: Area, newVal: Area) => {
- // 获取图片位置xy
- this.initPosition.x = Math.round(newVal.position.x as number)
- this.initPosition.y = Math.round(newVal.position.y as number)
- })
- // 蒙层
- Canvas(this.canvasContext3)
- .position({
- x: this.initPosition.x,
- y: this.initPosition.y
- })
- .width(this.sw)
- .height(this.sh)
- // 裁剪框
- Canvas(this.canvasContext2)
- .position({
- x: this.clipRect.x,
- y: this.clipRect.y
- })
- .width(this.clipRect.width)
- .height(this.clipRect.height)
- .onReady(() => {
- this.drawClipImage()
- })
- .onTouch(event => {
- if (event.type === TouchType.Down) {
- this.isMove(event.target.area, event.touches[0]);
- this.touchPosition = {
- x: event.touches[0].screenX,
- y: event.touches[0].screenY
- }
- } else if (event.type === TouchType.Move) {
- let moveX = event.changedTouches[0].screenX - this.touchPosition.x;
- let moveY = event.changedTouches[0].screenY - this.touchPosition.y;
- this.touchPosition = {
- x: event.changedTouches[0].screenX,
- y: event.changedTouches[0].screenY
- }
- this.moveClipCanvas(moveX, moveY);
- }
- })
- }
- Row() {
- Image($rawfile('rotate.png'))
- .width(40)
- .height(40)
- .onClick(() => {
- this.rotateImage()
- })
- }
- .margin({ top: 50 })
- .height('7%')
- .width('100%')
- .padding(30)
- Row() {
- Image($rawfile('reset.png'))
- .width(40)
- .height(40)
- .onClick(() => {
- this.cancel()
- })
- Image($rawfile('crop.png'))
- .width(40)
- .height(40)
- .onClick(() => {
- this.clipImage()
- })
- }
- .margin({ top: 10 })
- .width('100%')
- .height('7%')
- .padding(30)
- .justifyContent(FlexAlign.SpaceBetween)
- }
- .width('100%')
- .height('100%')
- .backgroundColor('#000000')
- }
- // 旋转图片
- async rotateImage() {
- if (this.rotateOn) {
- await this.pixelMap?.rotate(90)
- const info = await this.pixelMap?.getImageInfo()
- this.cropImageInfo = info
- if (this.pixelMapChange) {
- this.pixelMapChange = false
- } else {
- this.pixelMapChange = true
- }
- }
- }
- // 取消剪切
- cancel() {
- this.pixelMap = this.pixelMapBackUp
- this.isCrop = false
- this.rotateOn = false
- }
- // 判断操作类型
- isMove(area: Area, touch: TouchObject) {
- if (touch.x < 60 && touch.y < 60) { // 左上角
- this.actionType = ActionType.topLeft
- } else if (touch.x < 60 && touch.y > (Number(area.height) - 60)) { // 左下
- this.actionType = ActionType.bottomLeft
- } else if (touch.x > Number(area.width) - 60 && touch.y < 60) { // 右上
- this.actionType = ActionType.topRight
- } else if (touch.x > Number(area.width) - 60 && touch.y > (Number(area.height) - 60)) { // 右下
- this.actionType = ActionType.bottomRight
- } else {
- this.actionType = ActionType.move
- }
- }
- // 绘制背景图
- async drawImage() {
- await this.initData('test.jpg')
- if (this.imageInfo != undefined) {
- // let width = px2vp(this.imageInfo.size.width);
- // let height = px2vp(this.imageInfo.size.height)
- // this.canvasContext.drawImage(this.pixelMap, 0, 0, px2vp(this.imageInfo.size.width),
- // px2vp(this.imageInfo.size.height));
- this.canvasContext.drawImage(this.pixelMap,0,0,this.imageInfo.size.width,this.imageInfo.size.height,0,0,this.sw, this.sh)
- this.canvasContext.save();
- }
- }
- // 绘制蒙层
- drawMask() {
- this.canvasContext3.clearRect(0, 0, this.sw, this.sh);
- this.canvasContext3.fillStyle = 'rgba(0,0,0,0.7)';
- this.canvasContext3.fillRect(0, 0, this.sw, this.sh);
- this.canvasContext3.clearRect(this.clipRect.x - this.initPosition.x, this.clipRect.y - this.initPosition.y,
- this.clipRect.width, this.clipRect.height);
- }
- // 绘制裁剪框
- drawClipImage() {
- this.canvasContext2.clearRect(0, 0, this.clipRect.width, this.clipRect.height);
- this.canvasContext2.lineWidth = 6
- this.canvasContext2.strokeStyle = '#ff6be038'
- this.canvasContext2.beginPath()
- this.canvasContext2.moveTo(0, 20)
- this.canvasContext2.lineTo(0, 0);
- this.canvasContext2.lineTo(20, 0);
- this.canvasContext2.moveTo(this.clipRect.width - 20, 0);
- this.canvasContext2.lineTo(this.clipRect.width, 0);
- this.canvasContext2.lineTo(this.clipRect.width, 20);
- this.canvasContext2.moveTo(0, this.clipRect.height - 20);
- this.canvasContext2.lineTo(0, this.clipRect.height);
- this.canvasContext2.lineTo(20, this.clipRect.height);
- this.canvasContext2.moveTo(this.clipRect.width - 20, this.clipRect.height);
- this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height);
- this.canvasContext2.lineTo(this.clipRect.width, this.clipRect.height - 20);
- this.canvasContext2.stroke()
- this.canvasContext2.beginPath();
- this.canvasContext2.lineWidth = 0.5;
- let height = Math.round(this.clipRect.height / 3);
- for (let index = 0; index <= 3; index++) {
- let y = index === 3 ? this.clipRect.height : height * index;
- this.canvasContext2.moveTo(0, y);
- this.canvasContext2.lineTo(this.clipRect.width, y);
- }
- let width = Math.round(this.clipRect.width / 3);
- for (let index = 0; index <= 3; index++) {
- let x = index === 3 ? this.clipRect.width : width * index;
- this.canvasContext2.moveTo(x, 0);
- this.canvasContext2.lineTo(x, this.clipRect.height);
- }
- this.canvasContext2.stroke();
- }
- // 获取pixelMap与imageInfo
- async initData(fileName: string) {
- const context: Context = getContext(this);
- const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
- const fileData = await resourceMgr.getRawFileContent(fileName);
- const buffer = fileData.buffer;
- // const imageSource: image.ImageSource = image.createImageSource(buffer);
- const imageSource: image.ImageSource = image.createImageSource(getContext().filesDir + "/picture2.jpg");
- const pixelMap: image.PixelMap = await imageSource.createPixelMap()
- this.pixelMap = pixelMap
- this.pixelMapBackUp = pixelMap
- const imageInfo = await pixelMap.getImageInfo()
- this.imageInfo = imageInfo
- // 裁剪框初始位置
- this.initPosition.width = px2vp(Math.round(this.imageInfo.size.width))
- this.initPosition.height = px2vp(Math.round(this.imageInfo.size.height))
- this.clipRect.height = this.sh
- this.clipRect.width = this.sw
- this.clipRect.x = this.initPosition.x
- this.clipRect.y = this.initPosition.y
- }
- // 裁剪图片
- async clipImage() {
- let ratioX = 1;
- let ratioY = 1;
- if (this.imageInfo != undefined) {
- ratioX = this.imageInfo.size.width / this.sw;
- ratioY = this.imageInfo.size.height / this.sh;
- }
- let x = this.clipRect.x - this.initPosition.x;
- let y = this.clipRect.y - this.initPosition.y;
- console.log('clipImage() x= ' + x + ' y = ' + y + ' height = ' + this.clipRect.height + ' width = ' + this.clipRect.width)
- await this.pixelMap?.crop({
- x: vp2px(x),
- y: vp2px(y),
- size: {
- height: this.clipRect.height * ratioY,
- width: this.clipRect.width * ratioX
- }
- })
- this.cropImageInfo = await this.pixelMap?.getImageInfo();
- this.isCrop = true
- this.rotateOn = true
- this.setClipImageSize();
- }
- setClipImageSize(){
- let maxWidth: number = 300;
- let maxHeight: number = 500;
- if(this.cropImageInfo!=null){
- let aspectRatio: number = this.cropImageInfo.size.height / this.cropImageInfo.size.width;
- if(this.cropImageInfo.size.width > px2vp(maxWidth)){ //宽度固定,计算高度
- this.clipImageWidth = maxWidth;
- if(maxWidth * aspectRatio > maxHeight){
- this.clipImageHeight = maxHeight;
- }else{
- this.clipImageHeight = maxWidth * aspectRatio;
- }
- }else{
- if(this.cropImageInfo.size.height > px2vp(maxHeight)){ //高度固定,计算宽度
- this.clipImageHeight = maxHeight;
- this.clipImageWidth = maxWidth / aspectRatio;
- }else{
- this.clipImageWidth = px2vp(this.cropImageInfo.size.width);
- this.clipImageHeight = px2vp(this.cropImageInfo.size.height);
- }
- }
- }
- }
- // 裁剪框位置和大小变化 初始位置为图片的初始坐标 移动的坐标
- moveClipCanvas(moveX: number, moveY: number) {
- let clipRect: RectPosition = {
- x: this.clipRect.x,
- y: this.clipRect.y,
- width: this.clipRect.width,
- height: this.clipRect.height
- }
- switch (this.actionType) {
- case ActionType.move:
- clipRect.x += moveX;
- clipRect.y += moveY;
- break;
- case ActionType.topLeft:
- clipRect.x += moveX;
- clipRect.y += moveY;
- clipRect.width += -moveX;
- clipRect.height += -moveY;
- break;
- case ActionType.topRight:
- clipRect.y += moveY;
- clipRect.width += moveX;
- clipRect.height += -moveY;
- break;
- case ActionType.bottomLeft:
- clipRect.x += moveX;
- clipRect.width += -moveX;
- clipRect.height += moveY;
- break;
- case ActionType.bottomRight:
- clipRect.width += moveX;
- clipRect.height += moveY;
- break;
- default:
- break;
- }
- // 偏移坐标小于初始位置
- if (clipRect.x < this.initPosition.x) {
- clipRect.x = this.initPosition.x;
- }
- if (clipRect.y < this.initPosition.y) {
- clipRect.y = this.initPosition.y;
- }
- // 横坐标限制位置
- if (clipRect.width + clipRect.x > this.sw + this.initPosition.x) {
- if (this.actionType === ActionType.move) {
- clipRect.x = this.sw + this.initPosition.x - clipRect.width;
- } else {
- clipRect.width = this.sw + this.initPosition.x - clipRect.x;
- }
- }
- // 纵坐标限制
- if (clipRect.height + clipRect.y > this.sh + this.initPosition.y) {
- if (this.actionType === ActionType.move) {
- clipRect.y = this.sh + this.initPosition.y - clipRect.height;
- } else {
- clipRect.height = this.sh + this.initPosition.y - clipRect.y;
- }
- }
- this.clipRect = {
- x: Math.round(clipRect.x),
- y: Math.round(clipRect.y),
- width: Math.round(clipRect.width),
- height: Math.round(clipRect.height)
- };
- }
- }
- pages/Index.ets
- import picker from '@ohos.multimedia.cameraPicker'
- import camera from '@ohos.multimedia.camera';
- import common from '@ohos.app.ability.common';
- import { BusinessError } from '@ohos.base';
- import fileuri from '@ohos.file.fileuri';
- import fs from '@ohos.file.fs';
- import photoAccessHelper from '@ohos.file.photoAccessHelper';
- import { router } from '@kit.ArkUI';
- let context = getContext(this) as common.Context;
- class CameraPosition {
- cameraPosition: camera.CameraPosition
- saveUri: string
- constructor(cameraPosition: camera.CameraPosition, saveUri: string) {
- this.cameraPosition = cameraPosition
- this.saveUri = saveUri
- }
- }
- let pathDir = getContext().filesDir;
- console.log('保存路径为'+pathDir)
- let filePath = pathDir + '/picture.jpg'
- fs.createRandomAccessFileSync(filePath, fs.OpenMode.CREATE);
- let uri = fileuri.getUriFromPath(filePath);
- async function photo() {
- try {
- let pickerProfile = new CameraPosition(camera.CameraPosition.CAMERA_POSITION_BACK, uri)
- let pickerResult: picker.PickerResult = await picker.pick(context,
- [picker.PickerMediaType.PHOTO, picker.PickerMediaType.VIDEO], pickerProfile);
- console.log("the pick pickerResult is:" + JSON.stringify(pickerResult));
- } catch (error) {
- let err = error as BusinessError;
- console.error(`the pick call failed. error code: ${err.code}`);
- }
- }
- async function picture() {
- let PhotoSelectOptions = new photoAccessHelper.PhotoSelectOptions();
- PhotoSelectOptions.MIMEType = photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE;
- PhotoSelectOptions.maxSelectNumber = 1;
- PhotoSelectOptions.isSearchSupported = false;
- PhotoSelectOptions.isEditSupported = false;
- PhotoSelectOptions.isPreviewForSingleSelectionSupported = false;
- let photoPicker = new photoAccessHelper.PhotoViewPicker();
- photoPicker.select(PhotoSelectOptions).then((PhotoSelectResult: photoAccessHelper.PhotoSelectResult) => {
- let photouri: Array<string> = PhotoSelectResult.photoUris
- let file = fs.openSync(photouri[0], fs.OpenMode.READ_ONLY)
- let file2 = fs.openSync(pathDir+'/picture2.jpg', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE)
- fs.copyFileSync(file.fd, file2.fd)
- fs.closeSync(file);
- fs.closeSync(file2);
- router.pushUrl({
- url: "pages/ImageCropPage"
- })
- })
- }
- @Entry
- @Component
- struct Index {
- build() {
- Column() {
- Button('选择并编辑').onClick(() => {
- picture()
- })
- }
- .width('100%')
- .height('100%')
- .alignItems(HorizontalAlign.Center)
- .justifyContent(FlexAlign.Center)
- }
- }
复制代码 实现的效果:
这一效果大致可以满足我的需求,但我在测试的时候发现裁剪框滑动的时候有问题,裁剪框和图片位置没做处理惩罚,有大神可以本身处理惩罚的,可以利用这个去修改。
以我的性格就是继续探索新的方案。
经过不停查找,终于找到了一个满足需求的demo,并且功能很齐全,给大家看看效果:

但这个demo项目部分利用的代码是TS语言,NEXT版需要改造才能用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |