#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. 状态管理

    • 使用 @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. 组件初始化

    • 通过引入必要的模块和工具函数,准备了 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. 创建与设置 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 文件。

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