#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画 原创

蒙娜丽宁
发布于 2024-7-31 16:43
浏览
0收藏

本文会详细介绍如何借用混合开发的方式使用HarmonyOS Next和gif.js制作GIF动画,下图是App的主界面。

#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画-鸿蒙开发者社区

下图是设置GIF属性:
#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画-鸿蒙开发者社区

下图是预览添加的静态图片:
#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画-鸿蒙开发者社区

下图是设置拉伸效果的窗口
#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画-鸿蒙开发者社区

下面详细描述app的实现过程:

首先看一下拉伸效果窗口的组件,代码如下:

代码详解与中文注释

这段代码实现了一个用于选择图片拉伸方式的用户界面。用户可以通过单选按钮选择不同的图片拉伸方式,并点击“确定”按钮将选择的拉伸方式应用。以下是对代码的逐行中文注释和实现原理的详细解释。

// 引入常量,用于设置面板和按钮的背景颜色,保证界面的一致性
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const';

// 引入与文档尺寸相关的常量,通常用于处理文档的尺寸设置
import { DocumentSize, DocumentSizes } from '../data/Documents';

// 引入 EntryAbility 模块,提供应用程序的全局路径信息
import EntryAbility from '../entryability/EntryAbility';

// 引入文件处理相关的工具函数,检查文档是否存在和创建文档目录
import { documentExists, createDocumentDir } from '../common/common';

// 使用 @Entry 装饰器标记这个组件为应用的入口组件,表示它会在应用启动时自动加载并显示
@Entry

// 使用 @Component 装饰器定义一个组件,名为 ScalePanel,包含了组件的逻辑和 UI 布局
@Component
export struct ScalePanel {

  // 定义一个可选的回调函数,用于在用户选择了拉伸方式后,将选中的拉伸方式传递出去
  scaleSelected?: (scale: string) => void;

  // 从 EntryAbility 模块获取应用程序的根路径,通常用于处理文件路径等操作
  rootPath: string = EntryAbility.rootPath;

  // 使用 @State 定义多个状态变量,用于存储不同拉伸方式的选中状态
  @State startInside: boolean = true;  // 初始值设为 true,表示默认选中 'startInside'
  @State fitXY: boolean = false;       // 初始值设为 false,表示未选中 'fitXY'
  @State centerCrop: boolean = false;  // 初始值设为 false,表示未选中 'centerCrop'
  @State centerInside: boolean = false;// 初始值设为 false,表示未选中 'centerInside'
  @State formatName: string = '';      // 存储格式名称的状态变量,初始值为空字符串

  // build() 方法是组件的核心方法,用于定义组件的 UI 布局和行为
  build() {
    // 使用 Column 组件创建一个垂直布局的容器,子组件将按照顺序从上到下排列
    Column() {

      // 创建一个 Row 组件,用于水平排列拉伸方式 'startInside' 的单选按钮和文本
      Row() {

        // 创建一个 Radio 单选按钮,值为 'startInside',属于 'radioGroup' 组
        Radio({ value: 'startInside', group: 'radioGroup' })
          .radioStyle({
            checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
          })
          .checked(this.startInside) // 将单选按钮的选中状态与 startInside 变量绑定
          .height(25) // 设置单选按钮的高度为 25
          .width(25)  // 设置单选按钮的宽度为 25
          .onChange((isChecked: boolean) => { // 设置状态变化的回调函数
            this.startInside = isChecked; // 当单选按钮状态变化时,更新 startInside 的值
          });

        // 创建一个文本组件,显示 'startInside',并设置字体大小和颜色
        Text('startInside').fontSize(20).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.startInside = !this.startInside; // 点击文本时,切换 startInside 的选中状态
          });
      }

      // 创建一个 Row 组件,用于水平排列拉伸方式 'fitXY' 的单选按钮和文本
      Row() {

        // 创建一个 Radio 单选按钮,值为 'fitXY',属于 'radioGroup' 组
        Radio({ value: 'fitXY', group: 'radioGroup' })
          .radioStyle({
            checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
          })
          .checked(this.fitXY) // 将单选按钮的选中状态与 fitXY 变量绑定
          .height(25) // 设置单选按钮的高度为 25
          .width(25)  // 设置单选按钮的宽度为 25
          .onChange((isChecked: boolean) => { // 设置状态变化的回调函数
            this.fitXY = isChecked; // 当单选按钮状态变化时,更新 fitXY 的值
          });
          
        // 创建一个文本组件,显示 'fitXY',并设置字体大小和颜色
        Text('fitXY').fontSize(20).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.fitXY = !this.fitXY; // 点击文本时,切换 fitXY 的选中状态
          });
      }

      // 创建一个 Row 组件,用于水平排列拉伸方式 'centerCrop' 的单选按钮和文本
      Row() {

        // 创建一个 Radio 单选按钮,值为 'centerCrop',属于 'radioGroup' 组
        Radio({ value: 'centerCrop', group: 'radioGroup' })
          .radioStyle({
            checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
          })
          .checked(this.centerCrop) // 将单选按钮的选中状态与 centerCrop 变量绑定
          .height(25) // 设置单选按钮的高度为 25
          .width(25)  // 设置单选按钮的宽度为 25
          .onChange((isChecked: boolean) => { // 设置状态变化的回调函数
            this.centerCrop = isChecked; // 当单选按钮状态变化时,更新 centerCrop 的值
          });

        // 创建一个文本组件,显示 'centerCrop',并设置字体大小和颜色
        Text('centerCrop').fontSize(20).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.centerCrop = !this.centerCrop; // 点击文本时,切换 centerCrop 的选中状态
          });
      }

      // 创建一个 Row 组件,用于水平排列拉伸方式 'centerInside' 的单选按钮和文本
      Row() {

        // 创建一个 Radio 单选按钮,值为 'centerInside',属于 'radioGroup' 组
        Radio({ value: 'centerInside', group: 'radioGroup' })
          .radioStyle({
            checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
          })
          .checked(this.centerInside) // 将单选按钮的选中状态与 centerInside 变量绑定
          .height(25) // 设置单选按钮的高度为 25
          .width(25)  // 设置单选按钮的宽度为 25
          .onChange((isChecked: boolean) => { // 设置状态变化的回调函数
            this.centerInside = isChecked; // 当单选按钮状态变化时,更新 centerInside 的值
          });

        // 创建一个文本组件,显示 'centerInside',并设置字体大小和颜色
        Text('centerInside').fontSize(20).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.centerInside = !this.centerInside; // 点击文本时,切换 centerInside 的选中状态
          });
      }

      // 创建一个“确定”按钮,用于提交用户选择的拉伸方式
      Button({ type: ButtonType.Normal }) {
        // 在按钮内显示的文本内容为“确定”
        Text('确定')
          .fontSize(16) // 设置字体大小
          .fontColor('#FFFFFF') // 设置字体颜色
          .width('100%') // 设置文本的宽度为 100%
          .height('100%') // 设置文本的高度为 100%
          .textAlign(TextAlign.Center); // 设置文本居中对齐
      }
      .borderRadius(5) // 设置按钮的圆角半径,使按钮看起来更圆滑
      .width('100%') // 设置按钮的宽度为 100%
      .height(30) // 设置按钮的高度为 30
      .margin({ bottom: 10, top: 10 }) // 设置按钮的上下边距
      .onClick(() => {
        // 设置按钮的点击事件,当用户点击“确定”按钮时,检查各个拉伸方式的选中状态
        let scale: string = '';
        if (this.startInside) {
          scale = 'startInside'; // 如果 'startInside' 被选中,将其值赋给 scale
        } else if (this.fitXY) {
          scale = 'fitXY'; // 如果 'fitXY' 被选中,将其值赋给 scale
        } else if (this.centerCrop) {
          scale = 'centerCrop'; // 如果 'centerCrop' 被选中,将其值赋给 scale
        } else if (this.centerInside) {
          scale = 'centerInside'; // 如果 'centerInside' 被选中,将其值赋给 scale
        }

        // 如果 scaleSelected 回调函数存在,则调用该函数并传递选中的拉伸方式


        if (this.scaleSelected) {
          this.scaleSelected(scale);
        }
      });
    }
    .backgroundColor(PANEL_BACKGROUND_COLOR) // 设置背景颜色
    .border({ width: 0, color: '#000000', radius: 10 }) // 设置边框属性
    .width('100%') // 设置宽度为 100%,占据整个父容器的宽度
    .height('100%') // 设置高度为 100%,占据整个父容器的高度
    .padding({ top: 10, left: 10, right: 10 }) // 设置内边距
    .alignItems(HorizontalAlign.Start); // 设置子组件的水平对齐方式
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.

实现原理和步骤详解

  1. 组件初始化

    • 在组件定义时,引入了一些必要的常量、工具函数和模块,设置了状态变量以跟踪用户对不同图片拉伸方式的选择。
  2. 状态管理

    • 使用 @State 装饰器定义多个布尔类型的状态变量,如 startInsidefitXYcenterCropcenterInside,用于记录当前选中的拉伸方式。
    • 这些状态变量初始值分别为 truefalse,表示默认选中状态。
  3. UI 构建

    • build() 方法中,使用 ArkTS 提供的 UI 组件(如 ColumnRowRadioButton)构建用户界面。
    • 使用 Column 组件作为外层容器,内部包含多个 Row 组件,每个 Row 组件包含一个单选按钮 (Radio) 和对应的文本 (Text)。
  4. 单选按钮实现

    • 每个单选按钮绑定到相应的状态变量,并通过 onChange 事件处理函数动态更新选中状态。
    • 通过 group 属性将所有单选按钮归为一组,确保在同一时间只能选中一个选项。
  5. 确定按钮实现

    • 当用户点击“确定”按钮时,代码会检查当前选中的拉伸方式,并将其值存储在 scale 变量中。
    • 如果定义了 scaleSelected 回调函数,则调用该函数并传递选中的拉伸方式。这个过程可以将用户选择的结果传递给外部处理逻辑。
  6. 界面样式设置

    • 设置了整体界面的样式,包括背景颜色、边框属性、内边距和对齐方式,确保界面美观且易于使用。

下面再继续介绍设置窗口的实现,这个窗口是通过SettingsConfig组件实现的,下面是对这个组件的详细介绍。

代码详解与中文注释

这段代码实现了一个设置面板(SettingsPanel),用于用户配置一些图像处理的参数,如延迟时间、工作线程数、图像质量、图像宽度和高度、循环次数、是否防止抖动等。用户可以通过此面板进行参数设置,并点击“确定”或“取消”按钮提交或取消设置。以下是对代码的逐行中文注释和实现原理的详细解释。

// 引入常量,用于设置面板和按钮的背景颜色,保证界面的一致性
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const';

// 引入与文档尺寸相关的常量,通常用于处理文档的尺寸设置
import { DocumentSize, DocumentSizes } from '../data/Documents';

// 引入 EntryAbility 模块,提供应用程序的全局路径信息
import EntryAbility from '../entryability/EntryAbility';

// 引入文件处理相关的工具函数,检查文档是否存在,创建文档目录,以及获取反色颜色
import { documentExists, createDocumentDir, getInverseColor } from '../common/common';

// 定义一个设置配置类,包含多种配置项
export class SettingsConfig {
  workers: number = 2; // 工作线程数
  quality: number = 10; // 图像质量
  width?: number; // 图像宽度(可选)
  delay: number = 500; // 延迟时间
  height?: number; // 图像高度(可选)
  infiniteLoop: boolean = true; // 是否无限循环
  repeat: number = 0; // 动画循环次数
  dither: boolean = true; // 是否处理抖动
  effects: string[] = []; // 图像效果列表
  scaleType: string = 'startInside'; // 图像缩放类型
}

// 使用 @Component 装饰器定义一个组件,名为 SettingsPanel,包含了组件的逻辑和 UI 布局
@Component
export struct SettingsPanel {

  // 定义两个可选的回调函数,分别用于设置确定和取消的处理逻辑
  settingsOK?: (config: SettingsConfig) => void;
  settingsCancel?: () => void;

  // 使用 @State 定义一个状态变量,存储用户配置的设置项
  @State config: SettingsConfig = new SettingsConfig();

  // build() 方法是组件的核心方法,用于定义组件的 UI 布局和行为
  build() {
    // 使用 Column 组件创建一个垂直布局的容器,子组件将按照顺序从上到下排列
    Column() {

      // 创建一个文本组件,用于显示“延迟时间”标签
      Text('延迟时间')
        .fontSize(16) // 设置字体大小为 16
        .fontColor('#FFFFFF') // 设置字体颜色为白色
        .width('100') // 设置宽度为 100
        .height('30') // 设置高度为 30
        .textAlign(TextAlign.Center) // 设置文本居中对齐
        .margin({ bottom: 5, top: 10 }); // 设置上下边距

      // 创建一个文本输入框,用于输入延迟时间
      TextInput({ text: `${this.config.delay}`, placeholder: '请输入延迟时间' })
        .fontSize(16) // 设置字体大小为 16
        .width(200) // 设置宽度为 200
        .height(40) // 设置高度为 40
        .margin({ top: 10 }) // 设置上边距
        .fontColor('#000000') // 设置字体颜色为黑色
        .backgroundColor('#FFFFFF') // 设置背景颜色为白色
        .type(InputType.Number) // 设置输入框的类型为数字
        .onChange((value: string) => {
          this.config.delay = parseInt(value); // 当输入框内容变化时,更新延迟时间
        });

      // 创建一个滑动条组件,用于选择工作线程数
      Slider({
        value: this.config.workers, // 绑定到状态变量 config.workers
        min: 1, // 设置最小值为 1
        max: 5, // 设置最大值为 5
        step: 1, // 设置滑动步长为 1
        style: SliderStyle.OutSet // 设置滑动条的样式
      })
        .margin({ left: 10, right: 10 }) // 设置左右边距
        .onChange((value: number, mode: SliderChangeMode) => {
          this.config.workers = Math.round(value); // 当滑动条变化时,更新工作线程数
        });

      // 创建一个 Row 组件,用于水平排列图像质量标签和当前数值
      Row() {
        // 创建一个文本组件,显示“图像质量”标签
        Text('图像质量:')
          .fontSize(16) // 设置字体大小为 16
          .fontColor('#FFFFFF') // 设置字体颜色为白色
          .width('80') // 设置宽度为 80
          .height('30') // 设置高度为 30
          .textAlign(TextAlign.Center); // 设置文本居中对齐

        // 创建一个文本组件,显示当前的图像质量数值
        Text(`${this.config.quality}`)
          .fontSize(16) // 设置字体大小为 16
          .fontColor('#FFFFFF') // 设置字体颜色为白色
          .width('50') // 设置宽度为 50
          .height('30') // 设置高度为 30
          .textAlign(TextAlign.Start); // 设置文本左对齐
      }
      .margin({ top: 10 }); // 设置上边距

      // 创建一个滑动条组件,用于选择图像质量
      Slider({
        value: this.config.quality, // 绑定到状态变量 config.quality
        min: 1, // 设置最小值为 1
        max: 10, // 设置最大值为 10
        step: 1, // 设置滑动步长为 1
        style: SliderStyle.OutSet // 设置滑动条的样式
      })
        .margin({ left: 10, right: 10 }) // 设置左右边距
        .onChange((value: number, mode: SliderChangeMode) => {
          this.config.quality = Math.round(value); // 当滑动条变化时,更新图像质量
        });

      // 创建一个文本组件,用于显示“图像宽度”标签
      Text('图像宽度')
        .fontSize(16) // 设置字体大小为 16
        .fontColor('#FFFFFF') // 设置字体颜色为白色
        .width('100') // 设置宽度为 100
        .height('30') // 设置高度为 30
        .textAlign(TextAlign.Center) // 设置文本居中对齐
        .margin({ bottom: 5, top: 10 }); // 设置上下边距

      // 创建一个文本输入框,用于输入图像宽度
      TextInput({ placeholder: '请输入图像宽度' })
        .fontSize(16) // 设置字体大小为 16
        .width(200) // 设置宽度为 200
        .height(40) // 设置高度为 40
        .margin({ top: 10 }) // 设置上边距
        .fontColor('#000000') // 设置字体颜色为黑色
        .backgroundColor('#FFFFFF') // 设置背景颜色为白色
        .type(InputType.Number) // 设置输入框的类型为数字
        .onChange((value: string) => {
          this.config.width = parseInt(value); // 当输入框内容变化时,更新图像宽度
        });

      // 创建一个文本组件,用于显示“图像高度”标签
      Text('图像高度')
        .fontSize(16) // 设置字体大小为 16
        .fontColor('#FFFFFF') // 设置字体颜色为白色
        .width('100') // 设置宽度为 100
        .height('30') // 设置高度为 30
        .textAlign(TextAlign.Center) // 设置文本居中对齐
        .margin({ bottom: 5, top: 10 }); // 设置上下边距

      // 创建一个文本输入框,用于输入图像高度
      TextInput({ placeholder: '请输入图像高度' })
        .fontSize(16) // 设置字体大小为 16
        .width(200) // 设置宽度为 200
        .height(40) // 设置高度为 40
        .margin({ top: 10 }) // 设置上边距
        .fontColor('#000000') // 设置字体颜色为黑色
        .backgroundColor('#FFFFFF') // 设置背景颜色为白色
        .type(InputType.Number) // 设置输入框的类型为数字
        .onChange((value: string) => {
          this.config.height = parseInt(value); // 当输入框内容变化时,更新图像高度
        });

      // 创建一个 Flex 容器,用于布局无限循环的复选框和标签
      Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        // 创建一个复选框组件,用于选择是否无限循环
        Checkbox({ name: '

infiniteLoop' })
          .selectedColor(0x39a2db) // 设置选中颜色为蓝色
          .select(this.config.infiniteLoop) // 绑定到状态变量 config.infiniteLoop
          .shape(CheckBoxShape.ROUNDED_SQUARE) // 设置复选框形状为圆角矩形
          .onChange((value: boolean) => {
            this.config.infiniteLoop = value; // 当复选框状态变化时,更新无限循环的值
          })
          .width(25) // 设置复选框宽度为 25
          .height(25); // 设置复选框高度为 25

        // 创建一个文本组件,显示“无限循环”标签
        Text('无限循环').fontSize(16).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.config.infiniteLoop = !this.config.infiniteLoop; // 点击文本时,切换无限循环的状态
          });
      }
      .margin({ top: 10, left: 10 }); // 设置上边距和左边距

      // 创建一个文本组件,用于显示“循环次数”标签
      Text('循环次数')
        .fontSize(16) // 设置字体大小为 16
        .fontColor('#FFFFFF') // 设置字体颜色为白色
        .width('200') // 设置宽度为 200
        .height('30') // 设置高度为 30
        .textAlign(TextAlign.Center) // 设置文本居中对齐
        .margin({ bottom: 5, top: 10 }); // 设置上下边距

      // 创建一个提示文本,解释循环次数的设置
      Text('-1:不循环,0:无限循环')
        .fontSize(16) // 设置字体大小为 16
        .fontColor('#FFFFFF') // 设置字体颜色为白色
        .width('200') // 设置宽度为 200
        .height('30') // 设置高度为 30
        .textAlign(TextAlign.Center) // 设置文本居中对齐
        .margin({ bottom: 5, top: 4 }); // 设置上下边距

      // 创建一个文本输入框,用于输入动画的循环次数
      TextInput({ text: `${this.config.repeat}`, placeholder: '请输入Gif动画循环次数' })
        .fontSize(16) // 设置字体大小为 16
        .width(200) // 设置宽度为 200
        .height(40) // 设置高度为 40
        .margin({ top: 10 }) // 设置上边距
        .fontColor('#000000') // 设置字体颜色为黑色
        .backgroundColor('#FFFFFF') // 设置背景颜色为白色
        .onChange((value: string) => {
          this.config.repeat = parseInt(value); // 当输入框内容变化时,更新循环次数
        });

      // 创建一个 Flex 容器,用于布局防止抖动的复选框和标签
      Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
        // 创建一个复选框组件,用于选择是否防止抖动
        Checkbox({ name: 'dither' })
          .selectedColor(0x39a2db) // 设置选中颜色为蓝色
          .select(this.config.dither) // 绑定到状态变量 config.dither
          .shape(CheckBoxShape.ROUNDED_SQUARE) // 设置复选框形状为圆角矩形
          .onChange((value: boolean) => {
            this.config.dither = value; // 当复选框状态变化时,更新防止抖动的值
          })
          .width(25) // 设置复选框宽度为 25
          .height(25); // 设置复选框高度为 25

        // 创建一个文本组件,显示“防止抖动”标签
        Text('防止抖动').fontSize(16).foregroundColor('#FFFFFF')
          .onClick(() => {
            this.config.dither = !this.config.dither; // 点击文本时,切换防止抖动的状态
          });
      }
      .margin({ top: 10, left: 10 }); // 设置上边距和左边距

      // 创建一个 Row 容器,用于布局“确定”和“取消”按钮
      Row() {
        // 创建“确定”按钮
        Button({ type: ButtonType.Normal }) {
          Text('确定')
            .fontSize(16) // 设置字体大小为 16
            .fontColor('#FFFFFF') // 设置字体颜色为白色
            .width('100%') // 设置文本宽度为 100%
            .height('30') // 设置文本高度为 30
            .textAlign(TextAlign.Center); // 设置文本居中对齐
        }
        .borderRadius(5) // 设置按钮的圆角半径
        .width(100) // 设置按钮宽度为 100
        .height(30) // 设置按钮高度为 30
        .margin({ right: 10 }) // 设置右边距
        .onClick(() => {
          if (this.settingsOK) {
            this.settingsOK(this.config); // 当点击按钮时,调用 settingsOK 回调函数,并传递当前的配置
          }
        });

        // 创建“取消”按钮
        Button({ type: ButtonType.Normal }) {
          Text('取消')
            .fontSize(16) // 设置字体大小为 16
            .fontColor('#FFFFFF') // 设置字体颜色为白色
            .width('100%') // 设置文本宽度为 100%
            .height('30') // 设置文本高度为 30
            .textAlign(TextAlign.Center); // 设置文本居中对齐
        }
        .borderRadius(5) // 设置按钮的圆角半径
        .width(100) // 设置按钮宽度为 100
        .height(30) // 设置按钮高度为 30
        .onClick(() => {
          if (this.settingsCancel) {
            this.settingsCancel(); // 当点击按钮时,调用 settingsCancel 回调函数
          }
        })
        .margin({ left: 10 }); // 设置左边距
      }
      .margin({ top: 20, left: 10, right: 10 }); // 设置容器的上边距、左边距和右边距
    }
    .backgroundColor(PANEL_BACKGROUND_COLOR) // 设置背景颜色
    .border({ width: 0, color: '#000000', radius: 10 }) // 设置边框属性
    .width('100%') // 设置宽度为 100%,占据整个父容器的宽度
    .height('100%'); // 设置高度为 100%,占据整个父容器的高度
  }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.

代码实现原理和步骤详解

  1. 组件初始化

    • 通过引入必要的模块和工具函数,准备了 UI 布局所需的常量,并定义了配置类 SettingsConfig,该类包含了多个图像处理相关的配置项。
  2. 状态管理

    • 使用 @State 装饰器定义了 config 状态变量,存储用户设置的配置信息。config 是一个 SettingsConfig 对象,包含了所有可配置的参数,如工作线程数、图像质量、图像宽度和高度等。
  3. UI 布局

    • 使用 ArkTS 提供的 UI 组件,如 ColumnRowTextTextInputSliderCheckboxButton 等,构建了设置面板的用户界面。
    • 各种输入组件如文本输入框和滑动条分别用于获取用户输入的数值,并通过 onChange 事件实时更新 config 对象中的相应配置项。
  4. 设置项的实现

    • 文本输入框(TextInput)用于获取用户输入的延迟时间、图像宽度、高度和循环次数等。
    • 滑动条(Slider)用于选择工作线程数和图像质量。滑动条的变化通过 onChange 事件实时更新对应的配置项。
    • 复选框(Checkbox)用于设置是否无限循环和是否防止抖动,这些布尔值直接影响配置对象中的相关属性。
  5. 确定和取消按钮

    • 通过创建“确定”和“取消”按钮,允许用户保存或放弃当前的配置。点击“确定”按钮时,会将当前的 config 对象传递给 settingsOK 回调函数,以便外部处理。点击“取消”按钮时,调用 settingsCancel 回调函数以取消设置。
  6. 样式设置

    • 使用 .backgroundColor.border.width.height 等方法设置组件的样式属性,保证整个面板的外观一致性和用户体验。

通过这些实现步骤,这段代码成功构建了一个配置面板,用户可以通过该面板设置各种图像处理参数,并通过点击按钮提交或取消这些设置。这个面板设计简洁、功能全面,可以适用于多种图像处理应用场景。

现在基本的设置完成了,然后可以将添加的静态图片导出成动画gif,这是使用了gif.js,下面是对这个库的详细描述:

概述:
GIF.js 是一个 JavaScript 库,用于在浏览器中创建和处理 GIF 动画。它允许用户通过代码生成高质量的 GIF 动画,并支持对 GIF 的每一帧进行精细控制。

主要功能:

  1. 创建 GIF 动画: GIF.js 允许用户将一系列图像或 Canvas 画布的内容合成为一个 GIF 动画。
  2. 帧控制: 可以控制每一帧的延迟时间、添加的顺序以及是否重复播放。
  3. 高效编码: GIF.js 使用了基于 Web Workers 的并行编码方式,以提高 GIF 生成的性能,特别是在处理较大的 GIF 文件时。
  4. Canvas 集成: 该库可以直接从 HTML5 Canvas 元素捕获图像数据,这使得它非常适合与 HTML5 Canvas 结合使用,生成动态的、基于浏览器的 GIF 动画。
  5. 压缩和质量设置: 支持调整输出 GIF 的质量和压缩率,以平衡文件大小和图像质量。
  6. 透明度支持: GIF.js 支持处理透明图像,能够生成具有透明背景的 GIF 动画。

应用场景:

  • 动态内容生成: GIF.js 非常适合在网页上生成基于 Canvas 动画或用户绘制内容的动态 GIF 动画。
  • 游戏开发: 在 Web 游戏中,可以使用 GIF.js 捕获和生成游戏回放或动画 GIF,以便玩家分享或保存。
  • 社交媒体集成: 允许用户直接在网页应用中创建并分享 GIF 动画,适用于社交媒体平台和聊天应用程序。
  • 数据可视化: 可用于将动态图表或数据可视化内容导出为 GIF 格式,以便在不支持动态内容的环境中展示。

本文的核心是createGIF函数,该函数利用了gif.js、fabric.js等技术,将多个静态图片保存为一个动画gif文件,下面是对这个函数的介绍。

代码中文注释与详细解释

function createGIF(config) {

    return new Promise((resolve, reject) => {
        // 创建一个新的 Canvas 元素,用于绘制图像
        const canvas = document.createElement("canvas");
        // 获取 Canvas 的 2D 渲染上下文
        const ctx = canvas.getContext("2d");

        let gif; // 将保存生成的 GIF 对象
        var firstImage; // 保存第一张图像对象

        // 使用 fabric.js 从第一个图像的 URL 加载图像
        fabric.Image.fromURL(imagesArray[0], (img) => {
            if (imagesArray.length === 0) {
                return; // 如果没有图像,直接返回
            }
            if (!firstImage) {
                firstImage = img; // 保存第一张图像
                if (!config.width || !config.height) {
                    // 如果没有指定宽度或高度,则使用第一张图像的尺寸
                    config.width = firstImage.width;
                    config.height = firstImage.height;
                }
                // 设置 Canvas 的宽度和高度
                canvas.width = config.width;
                canvas.height = config.height;

                // 初始化 GIF 对象
                gif = new GIF({
                    workers: config.workers || 2, // 设置使用的 Web Worker 数量
                    quality: config.quality || 10, // 设置图像质量,值越低质量越高
                    workerScript: workerScriptURL, // 指定 worker 脚本的 URL
                    width: config.width,
                    height: config.height,
                    repeat: config.repeat || 0, // 设置动画的重复次数(0 为无限循环)
                    dither: config.dither || false, // 是否启用抖动处理
                });
            }
            // 加载并处理所有图像,确保顺序
            Promise.all(
                imagesArray.map((src) => {

                    return new Promise((resolveImg, rejectImg) => {
                        // 使用 fabric.js 从每个图像的 URL 加载图像
                        fabric.Image.fromURL(
                            src,
                            (img) => {
                                // 应用图像效果
                                applyImageEffects(img, config.effects)
                                    .then(() => {
                                        resolveImg(img); // 确保效果应用后再解析
                                    })
                                    .catch(rejectImg);
                            },
                            { crossOrigin: "anonymous" } // 允许跨域加载图像
                        );
                    });
                })
            )
            .then((processedImages) => {
                // 处理加载并应用效果后的所有图像
                processedImages.forEach((img) => {
                    ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布以防止图像重叠
                    processImage(img.getElement(), config, ctx); // 处理图像(例如缩放或裁剪)

                    // 将处理后的图像添加为 GIF 的一帧
                    gif.addFrame(ctx.canvas, {
                        delay: config.delay || 500, // 设置帧延迟时间
                        copy: true, // 是否复制帧数据
                    });
                });

                // 当 GIF 生成完成时触发的事件处理函数
                gif.on("finished", function (blob) {
                    const reader = new FileReader();
                    // 当读取操作完成时触发的事件处理函数
                    reader.onloadend = () => {
                        // 将生成的 GIF 转换为 base64 编码
                        previewGif = reader.result;
                        // 解析 Promise,并返回生成的 GIF 的 base64 数据
                        resolve(reader.result);
                    };

                    // 将 Blob 对象读作一个 base64 编码的 Data URL
                    reader.readAsDataURL(blob);
                });

                // 开始渲染 GIF
                gif.render();
            })
            .catch((error) => {
                // 如果处理图像时出现错误,则拒绝 Promise 并返回错误信息
                reject("Error processing images: " + error);
            });
        });
    });
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.

代码原理与详细解释

1. 创建与设置 Canvas

  • 首先通过 document.createElement("canvas") 创建了一个新的 Canvas 元素,并通过 canvas.getContext("2d") 获取其 2D 渲染上下文。这将用于在 Canvas 上绘制图像。

2. 加载第一张图像

  • 代码使用 fabric.Image.fromURL 方法从给定的 URL 加载第一张图像,并检查是否提供了 config 中的宽度和高度参数。如果没有指定宽度和高度,则使用第一张图像的尺寸作为 GIF 的宽度和高度。

3. 初始化 GIF 对象

  • GIF 对象通过 new GIF() 初始化,并传递了多个配置参数,包括:
    • workers:用于指定生成 GIF 时使用的 Web Workers 数量,以提高性能。
    • quality:图像的质量设置,值越低质量越高,文件大小越小。
    • widthheight:设置生成 GIF 的宽度和高度。
    • repeat:指定 GIF 动画的重复次数。
    • dither:是否启用图像的抖动处理。

4. 加载和处理图像

  • 使用 Promise.all 方法并结合 fabric.Image.fromURL 加载所有图像,并对每一张图像应用指定的效果。加载完成并应用效果后,解析每张图像对象。
  • applyImageEffects 函数中,图像的各种效果(如灰度、模糊、亮度调整等)被应用到图像对象上。

5. 生成 GIF 帧

  • 对每一张处理后的图像,首先清空 Canvas,然后根据配置对图像进行缩放或裁剪(通过 processImage 函数),最后将图像添加为 GIF 动画的一帧。

6. 完成 GIF 生成

  • 在 GIF 生成完成后,触发 gif.on("finished") 事件,将生成的 GIF Blob 对象通过 FileReader 转换为 base64 编码的 Data URL,并最终通过 resolve 返回给调用者。

7. 渲染 GIF

  • 调用 gif.render() 方法开始渲染 GIF 动画。渲染过程中,GIF.js 会使用 Web Workers 进行图像编码,最终生成完整的 GIF 文件。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
标签
收藏
回复
举报


回复
    相关推荐
    这个用户很懒,还没有个人简介
    觉得TA不错?点个关注精彩不错过
    帖子
    视频
    声望
    粉丝
    社区精华内容