0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

HarmonyOS开发案例:【图片编辑】

jf_46214456 来源:jf_46214456 作者:jf_46214456 2024-04-22 16:42 次阅读

介绍

基于canvas组件、图片编解码,介绍了图片编辑实现过程。主要包含以下功能:

  1. 图片的解码和绘制。
  2. 使用PixelMap进行图片编辑,如裁剪、旋转、亮度调节、透明度调节、饱和度调节等操作。

相关概念

  • [canvas组件]:提供画布组件。用于自定义绘制图形。
  • [图片处理]:提供图片处理效果,包括通过属性创建PixelMap、读取图像像素数据、读取区域内的图片数据等。

相关权限

本篇Codelab使用了媒体文件存储能力,需要在配置文件config.json里添加媒体文件读写权限:

  • ohos.permission.MEDIA_LOCATION
  • ohos.permission.READ_MEDIA
  • ohos.permission.WRITE_MEDIA

环境搭建

鸿蒙开发指导文档:[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md]

搜狗高速浏览器截图20240326151344.png

软件要求

  • [DevEco Studio]版本:DevEco Studio 3.1 Release及以上版本。
  • OpenHarmony SDK版本:API version 9及以上版本。

硬件要求

  • 开发板类型:[润和RK3568开发板]。
  • OpenHarmony系统:3.2 Release及以上版本。

环境搭建

完成本篇Codelab我们首先要完成开发环境的搭建,本示例以RK3568开发板为例,参照以下步骤进行:

  1. [获取OpenHarmony系统版本]:标准系统解决方案(二进制)。以3.2 Release版本为例:
  2. 搭建烧录环境。
    1. [完成DevEco Device Tool的安装]
    2. [完成RK3568开发板的烧录]
  3. 搭建开发环境。
    1. 开始前请参考[工具准备],完成DevEco Studio的安装和开发环境配置。
    2. 开发环境配置完成后,请参考[使用工程向导]创建工程(模板选择“Empty Ability”)。
    3. 工程创建完成后,选择使用[真机进行调测]。

代码结构解读

本篇Codelab只对核心代码进行讲解,对于完整代码,我们会在gitee中提供。

├──entry/src/main/js	              // 代码区
│  └──MainAbility
│     ├──common
│     │  ├──bean
│     │  │  └──messageItem.js         // 多线程封装消息
│     │  ├──constant
│     │  │  └──commonConstants.js     // 常量
│     │  ├──images                    // 图片资源
│     │  └──utils
│     │     ├──adjustUtil.js          // 饱和度、亮度调节工具
│     │     ├──imageUtil.js           // 图片获取、打包工具
│     │     ├──logger.js              // 日志工具             
│     │     ├──opacityUtil.js         // 透明度调节工具
│     │     └──rotateUtil.js          // 旋转工具
│     ├──i18n		                  // 国际化中英文
│     │  ├──en-US.json			
│     │  └──zh-CN.json		
│     ├──model
│     │  └──cropModel.js              // 裁剪数据处理
│     ├──pages
│     │  └──index
│     │     ├──index.css              // 首页样式文件	
│     │     ├──index.hml              // 首页布局文件
│     │     └──index.js               // 首页业务处理文件
│     ├──workers
│     │  ├──adjustBrightnessWork.js   // 亮度异步调节
│     │  └──adjustSaturationWork.js   // 饱和度异步调节
│     └──app.js                       // 程序入口
└──entry/src/main/resources           // 应用资源目录

图片解码

本章节将介绍如何将图片解码,并显示在canvas组件上。需要完成以下功能:

  1. 获取图片的PixelMap对象。
  2. 在canvas组件上绘制第一步获取的PixelMap对象。

在index.js文件的onInit生命周期中初始化canvas画布,具体有以下步骤:

  1. 调用imageUtil工具类的getImageFd方法,根据资源文件获取图片fd。
  2. 调用imageUtil工具类的getImagePixelMap方法,将获取的fd创建成图片实例,通过实例获取其PixelMap。
  3. 通过canvas id获取CanvasRenderingContext2D对象。
  4. 将获取的PixelMap绘制到canvas组件上。
// index.js
export default {
  onInit() {
    ...
    this.initCanvas().then(() = > {
      this.postState = false;
    });
  },
  async initCanvas() {
    ...
    // 获取图片fd
    this.imageFd = await getImageFd(CommonConstants.IMAGE_NAME);
    // 获取图片pixelMap
    this.imagePixelMapAdjust = await getImagePixelMap(this.imageFd);
    ...
    // 获取canvas对象
    const canvasOne = this.$element('canvasOne');
    this.canvasContext = canvasOne.getContext('2d');
    ...
    // 在canvas组件上绘图
    this.drawToCanvas(this.imagePixelMapAdjust, this.drawImageLeft, this.drawImageTop,
      this.drawWidth, this.drawHeight);
  }
}

// imageUtil.js
export async function getImageFd(imageName) {
  let mResourceManager = await resourceManager.getResourceManager();
  let rawImageDescriptor = await mResourceManager.getRawFd(imageName);
  let fd = rawImageDescriptor?.fd;
  return fd;
}
export async function getImagePixelMap(fd) {
  let imageSource = image.createImageSource(fd);
  if (!imageSource) {
    return;
  }
  let pixelMap = await imageSource.createPixelMap({
    editable: true,
    desiredPixelFormat: CommonConstants.PIXEL_FORMAT
  });
  return pixelMap;
}

图片裁剪

本篇Codelab提供四种裁剪比例,全图裁剪、1:1裁剪、16:9裁剪、4:3裁剪。需要完成以下步骤实现裁剪功能:

  1. 根据裁剪比例,获取canvas画布上需要绘制的裁剪框宽高。
  2. 根据裁剪比例,获取原图需要裁剪的宽高。
  3. 根据第二步获取的原图需要裁剪的宽高,对图片进行裁剪。
  4. 根据裁剪后原图宽高,适配屏幕大小,重新绘制。

// index.js
export default {
  // 任意点击四种裁剪比例
  cropClick(clickIndex) {
    this.cropClickIndex = clickIndex;
    switch (clickIndex) {
      // 全图裁剪
      case CommonConstants.CropType.ORIGINAL:
        cropOriginal(this);
        break;
      // 1:1裁剪
      case CommonConstants.CropType.ONE_TO_ONE:
        cropSquareImage(this);
        break;
      // 16:9裁剪
      case CommonConstants.CropType.SIXTEEN_TO_NINE:
        cropRectangleImage(this);
        break;
      // 4:3裁剪
      case CommonConstants.CropType.FOUR_TO_THREE:
        cropBannerImage(this);
        break;
      default:
        break;
    }
    drawScreenSelection(this, this.canvasCropContext);
  }
}

以1:1裁剪为例,调用cropSquareImage方法,获取原图需要裁剪的宽高以及裁剪框的宽高。点击切换编辑类型或保存,调用cropDrawImage方法裁剪图片,最后适配屏幕重新绘制。

// cropModel.js
export function cropSquareImage(context) {
  ...
  let length = Math.min(context.originalImage.width, context.originalImage.height);
  // 原图需要裁剪的宽高
  context.cropWidth = length;
  context.cropHeight = length;
  let drawLength = Math.min(context.drawWidth, context.drawHeight);
  // 裁剪框宽高
  context.cropDrawWidth = drawLength;
  context.cropDrawHeight = drawLength;
}
export async function cropDrawImage(context) {
  ...
  let imagePixel = context.imagePixelMapAdjust;
  let diffX = (context.originalImage.width - context.cropWidth) / CommonConstants.HALF;
  let diffY = (context.originalImage.height - context.cropHeight) / CommonConstants.HALF;
  context.cropLeft = Math.floor(diffX * accuracy) / accuracy;
  context.cropTop = Math.floor(diffY * accuracy) / accuracy;
  // 裁剪图片
  await imagePixel.crop({ x: context.cropLeft, y: context.cropTop,
    size: {
      height: context.cropHeight,
      width: context.cropWidth
    }
  });
  // 裁剪后原图宽高
  context.originalImage.width = context.cropWidth;
  context.originalImage.height = context.cropHeight;
  context.imagePixelMapAdjust = imagePixel;
}

// index.js
// 选择裁剪框后,裁剪并适应屏幕显示
async crop() {
  await cropDrawImage(this);
  // 适配屏幕
  this.adjustSize();
  this.canvasCropContext.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
  // 重新绘制
  this.drawToCanvas(this.imagePixelMapAdjust, this.drawImageLeft, this.drawImageTop, this.drawWidth,
    this.drawHeight);
  cropOriginal(this);
}

图片旋转

本篇Codelab提供逆时针旋转、顺时针旋转两种方式,每次旋转角度为90度。在index.html文件中,使用两个image组件实现逆时针旋转、顺时针旋转按钮。点击对应图片时,触发onclick事件并回调onRotate方法。

< !-- index.html -- >
< div class="space-around-row adjust-width crop-height" >
    < !-- 逆时针旋转 -- >
    < image src="https://www.elecfans.com/images/chaijie_default.png" class="edit-image" onclick="onRotate(-90)" >< /image >
    < !-- 顺时针旋转 -- >
    < image src="https://www.elecfans.com/images/chaijie_default.png" class="edit-image" onclick="onRotate(90)" >< /image >
< /div >

在index.js文件中,实现onRotate方法。根据方法入参angle,调用PixelMap接口提供的rotate方法,完成图片旋转功能。

// index.js
export default {
  // 点击逆时针旋转
  onRotate(angle) {
    let that = this;
    this.postState = true;
    rotate(this.imagePixelMapAdjust, angle, () = > {
      that.exchange();
    });
  }
}
// rotateUtil.js
export async function rotate(pixelMap, angle, callback) {
  if (!pixelMap) {
    return;
  }
  await pixelMap.rotate(angle);
  callback();
}

图片色域调节

本篇Codelab的色域调节是使用色域模型RGB-HSV来实现的。

  • RGB:是我们接触最多的颜色空间,分别为红色(R)、绿色(G)和蓝色(B)。
  • HSV:是用色相H,饱和度S,明亮度V来描述颜色的变化
    • H:色相H取值范围为0°~360°,从红色开始按逆时针方向计算,红色为0°,绿色为120°,蓝色为240°。
    • S:饱和度S越高,颜色则深而艳。光谱色的白光成分为0,饱和度达到最高。通常取值范围为0%~100%,值越大,颜色越饱和。
    • V:明度V表示颜色明亮的程度,对于光源色,明度值与发光体的光亮度有关;对于物体色,此值和物体的透射比或反射比有关。通常取值范围为0%(黑)到100%(白)。

亮度调节

完成以下步骤实现亮度调节:

  1. 将PixelMap转换成ArrayBuffer。
  2. 将生成好的ArrayBuffer发送到worker线程。
  3. 对每一个像素点的亮度值按倍率计算。
  4. 将计算好的ArrayBuffer发送回主线程。
  5. 将ArrayBuffer写入PixelMap,重新绘图。

说明: 当前亮度调节是在UI层面实现的,未实现细节优化算法,只做简单示例。调节后的图片会有色彩上的失真。

// index.js
export default {
  // pixelMap转换ArrayBuffer及发送ArrayBuffer到worker,
  postToWorker(type, value, workerName) {
    let sliderValue = type === CommonConstants.AdjustId.BRIGHTNESS ? this.brightnessValue : this.saturationValue;
    this.workerInstance = new worker.ThreadWorker(workerName);
    const bufferArray = new ArrayBuffer(this.imagePixelMapAdjust.getPixelBytesNumber());
    this.imagePixelMapAdjust.readPixelsToBuffer(bufferArray).then(() = > {
      let message = new MessageItem(bufferArray, sliderValue, value);
      this.workerInstance.postMessage(message);
      this.postState = true;
      // 收到worker线程完成的消息
      this.workerInstance.onmessage = this.updatePixelMap.bind(this);
      this.workerInstance.onexit = () = > {
        if (type === CommonConstants.AdjustId.BRIGHTNESS) {
          this.brightnessValue = Math.floor(value);
        } else {
          this.saturationValue = Math.floor(value);
        }
      }
    });
  }
}
// AdjustBrightnessWork.js
// worker线程处理部分
workerPort.onmessage = function (event) {
  let bufferArray = event.data.buffer;
  let lastValue = event.data.lastValue;
  let currentValue = event.data.currentValue;
  let buffer = adjustImageValue(bufferArray, lastValue, currentValue);
  workerPort.postMessage(buffer);
}
// adjustUtil.js
// 倍率计算部分
export function adjustImageValue(bufferArray, last, cur) {
  return execColorInfo(bufferArray, last, cur, CommonConstants.HSVIndex.VALUE);
}

透明度调节

PixelMap接口提供了图片透明度调节的功能。拖动滑块调节透明度,回调setOpacityValue方法,获取需要调节的透明度,最后调用opacity方法完成透明度调节。

// index.js
export default {
  setOpacityValue(event) {
    let slidingOpacityValue = event.value;
    let slidingMode = event.mode;
    if (slidingMode === CommonConstants.SLIDER_MODE_END || slidingMode === CommonConstants.SLIDER_MODE_CLICK) {
      adjustOpacity(this.imagePixelMapAdjust, slidingOpacityValue).then(pixelMap = > {
        this.imagePixelMapAdjust = pixelMap;
        this.drawToCanvas(this.imagePixelMapAdjust, this.drawImageLeft, this.drawImageTop,
          this.drawWidth, this.drawHeight);
        this.opacityValue = Math.floor(slidingOpacityValue);
      });
    }
  }
}
// opacityUtil.js
export async function adjustOpacity(pixelMap, value) {
  if (!pixelMap) {
    return;
  }
  pixelMap.opacity(parseInt(value) / CommonConstants.SLIDER_MAX_VALUE).catch(err = > {
    Logger.error(`opacity err ${JSON.stringify(err)}`);
  });
  return pixelMap;
}

饱和度调节

饱和度调节与亮度调节步骤类似:

  1. 将PixelMap转换成ArrayBuffer。
  2. 将生成好的ArrayBuffer发送到worker线程。
  3. 对每一个像素点的饱和度值按倍率计算。
  4. 将计算好的ArrayBuffer发送回主线程。
  5. 将ArrayBuffer写入PixelMap,重新绘图。

说明: 当前饱和度调节是在UI层面实现的,未实现细节优化算法,只做简单示例。调节后的图片会有色彩上的失真。

// index.js
export default {
  // pixelMap转换ArrayBuffer及发送ArrayBuffer到worker,
  postToWorker(type, value, workerName) {
    let sliderValue = type === CommonConstants.AdjustId.BRIGHTNESS ? this.brightnessValue : this.saturationValue;
    this.workerInstance = new worker.ThreadWorker(workerName);
    const bufferArray = new ArrayBuffer(this.imagePixelMapAdjust.getPixelBytesNumber());
    this.imagePixelMapAdjust.readPixelsToBuffer(bufferArray).then(() = > {
      let message = new MessageItem(bufferArray, sliderValue, value);
      this.workerInstance.postMessage(message);
      this.postState = true;
      // 收到worker线程完成的消息
      this.workerInstance.onmessage = this.updatePixelMap.bind(this);
      this.workerInstance.onexit = () = > {
        if (type === CommonConstants.AdjustId.BRIGHTNESS) {
          this.brightnessValue = Math.floor(value);
        } else {
          this.saturationValue = Math.floor(value);
        }
      }
    });
  }
}
// adjustSaturationWork.js
// worker线程处理部分
workerPort.onmessage = function (event) {
  let bufferArray = event.data.buffer;
  let lastValue = event.data.lastValue;
  let currentValue = event.data.currentValue;
  let buffer = adjustSaturation(bufferArray, lastValue, currentValue)
  workerPort.postMessage(buffer);
}
// adjustUtil.js
// 倍率计算部分
export function adjustSaturation(bufferArray, last, cur) {
  return execColorInfo(bufferArray, last, cur, CommonConstants.HSVIndex.SATURATION);
}

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Canvas
    +关注

    关注

    0

    文章

    16

    浏览量

    10981
  • 鸿蒙
    +关注

    关注

    57

    文章

    2339

    浏览量

    42805
  • HarmonyOS
    +关注

    关注

    79

    文章

    1973

    浏览量

    30143
  • OpenHarmony
    +关注

    关注

    25

    文章

    3713

    浏览量

    16254
收藏 人收藏

    评论

    相关推荐

    TL3588-视频开发案

    TL3588-视频开发案
    的头像 发表于 01-24 16:29 674次阅读
    TL3588-视频<b class='flag-5'>开发案</b>例

    3568F-视频开发案

    3568F-视频开发案
    的头像 发表于 04-12 13:51 888次阅读
    3568F-视频<b class='flag-5'>开发案</b>例

    51单片机应用开发案例精选(代码及图片

    《51单片机应用开发案例精选》分为3部分。第1部分(第1章)讲解单片机开发的预备知识,简要介绍了单片机的开发流程、开发工具和最小系统;第2部分(第2章~第3章)讲解单片机
    发表于 01-09 16:16

    HarmonyOS IoT 硬件开发案例分享

    ``许思维老师HiSpark Wi-Fi IoT 开发案例分享:案例一:AHT20温湿度传感器开发、调试;案例二:oled屏驱动库移植,调试;案例三:用OLED屏播放视频,Wi-Fi 和 TCP/IP 综合应用。 ``
    发表于 10-27 17:30

    HarmonyOS HiSpark Wi-Fi IoT套件】HarmonyOS IoT 硬件开发案例分享

    HiSpark Wi-Fi IoT 开发案例分享:案例一:AHT20温湿度传感器开发、调试;案例二:oled屏驱动库移植,调试;案例三:用OLED屏播放视频,Wi-Fi 和 TCP/IP 综合应用。
    发表于 10-27 19:12

    【润和直播课预告@华为开发者学院】HarmonyOS设备开发基础课程|HiSpark WiFi-IoT 智能小车套件开发案

    `【润和直播课预告@华为开发者学院】HarmonyOS设备开发基础课程|HiSparkWiFi-IoT 智能小车套件开发案例,3月18日(周四) 19:00-21:00,让你的
    发表于 03-16 15:01

    HarmonyOS教程—基于图片处理能力,实现一个图片编辑模板

    :界面UI和图片编辑器。模板界面UI部分主要为开发者提供了:图片编辑界面的设计参考,以及HarmonyO
    发表于 08-31 10:13

    51单片机应用开发案例精选-源代码

    本内容提供了51单片机应用开发案例精选-源代码及开发图片
    发表于 08-10 09:40 537次下载
    51单片机应用<b class='flag-5'>开发案</b>例精选-源代码

    许思维老师HarmonyOS IoT硬件开发案例分享

    许思维老师HiSpark Wi-Fi IoT 开发案例分享:案例一:AHT20温湿度传感器开发、调试;案例二:oled屏驱动库移植,调试;案例三:用OLED屏播放视频,Wi-Fi 和 TCP/IP 综合应用。
    发表于 10-29 10:39 39次下载
    许思维老师<b class='flag-5'>HarmonyOS</b> IoT硬件<b class='flag-5'>开发案</b>例分享

    华为开发HarmonyOS零基础入门:Word图片资源支持预览效果

    华为开发HarmonyOS零基础入门:Word图片资源支持预览效果,list主键函数可以做布局,呈现多个堆叠显示效果。
    的头像 发表于 10-23 10:12 1493次阅读
    华为<b class='flag-5'>开发</b>者<b class='flag-5'>HarmonyOS</b>零基础入门:Word<b class='flag-5'>图片</b>资源支持预览效果

    华为开发者分论坛HarmonyOS学生公开课-OpenHarmony Codelabs开发案

    2021华为开发者分论坛HarmonyOS学生公开课-OpenHarmony Codelabs开发案
    的头像 发表于 10-24 11:25 1913次阅读
    华为<b class='flag-5'>开发</b>者分论坛<b class='flag-5'>HarmonyOS</b>学生公开课-OpenHarmony Codelabs<b class='flag-5'>开发案</b>例

    OpenHarmony上实现图片编辑功能

    图片编辑是在应用中经常用到的功能,比如相机拍完照片后可以对照片进行编辑;截图后可以对截图进行编辑;可以对图库中的图片进行
    的头像 发表于 06-25 15:17 1218次阅读
    OpenHarmony上实现<b class='flag-5'>图片</b><b class='flag-5'>编辑</b>功能

    RK3568---NPU开发案

    RK3568---NPU开发案
    的头像 发表于 01-19 13:50 914次阅读
    RK3568---NPU<b class='flag-5'>开发案</b>例

    HarmonyOS开发实例:【图片编辑应用】

    通过动态设置元素样式的方式,实现几种常见的图片操作,包括裁剪、旋转、缩放和镜像。
    的头像 发表于 04-23 09:42 435次阅读
    <b class='flag-5'>HarmonyOS</b><b class='flag-5'>开发</b>实例:【<b class='flag-5'>图片</b><b class='flag-5'>编辑</b>应用】

    HarmonyOS开发案例:【图片编辑

    基于ArkTS的声明式开发范式的样例,主要介绍了图片编辑实现过程。
    的头像 发表于 04-23 20:54 386次阅读
    <b class='flag-5'>HarmonyOS</b><b class='flag-5'>开发案</b>例:【<b class='flag-5'>图片</b><b class='flag-5'>编辑</b>】