Sui 从基础到编码实战

简介: 这篇文章详细介绍了Sui区块链的基础知识和实战应用,包括交易类型、共识引擎、Sui的优势、智能合约开发、安装Sui、包布局和语法、对象分类、NFT创建、对象包装、动态字段以及集合等概念。

目录

Sui实战

交易

共识引擎

Sui优势

Sui 智能合约

安装sui

Sui 包布局和语法

对象

Functions

Capabilities

对象分类

对象和 NFT

在 Sui Move 中创建 NFT

对象包装

动态字段

NFT

合约开发

合约部署

测试 Sui Move 包

发布Sui Move 包

调用Sui Move 函数


本系列的范围从 Sui 的基础知识到有关构建智能合约和使用 Sui Move 中的对象的教程。

Sui实战

本文介绍了 Sui 基础设施的基础知识。涵盖了不同类型的交易以及它们如何提高可扩展性、共识引擎的组件以及 Sui 的关键优势。

交易

Sui将交易分为两类:简单的和复杂的。

对于简单的交易,例如将代币从一个账户发送到另一个账户或铸造 NFT,交易可能会绕过共识协议。这使得 Sui 具有可扩展性,并通过允许不相关的资产几乎立即达到最终结果而增加了区块链的吞吐量,而无需经过相对更长和更昂贵的共识过程。

对于复杂的交易,例如流动资金池、订单簿或任何其他使用共享对象的无数 DeFi 用例,交易会通过 Sui 新颖的基于Narwhal 和 Bullshark 有向无环图 (DAG) 的内存池和高效的拜占庭容错 (BFT) 共识.

由于 Sui 的以对象为中心的观点,以及 Move 的强所有权类型,依赖性被显式编码。结果,Sui 同意并并行地在许多对象上执行事务。

共识引擎

DAG 本质上是一个永不循环的有向图。它由边和顶点组成,每条边都从一个顶点指向另一个顶点,这样沿着这些方向永远不会形成闭环。

有向无环图 (DAG) 由链接的边和顶点组成,这些边和顶点的排列方式永远不会形成闭环。

Sui 以其内存池引擎 Narwhal 的形式使用 DAG,它与其共识引擎 Bullshark 分离。通过将交易传播与共识分离,Sui 能够实现非常高的吞吐量。

Bullshark 是一种最先进的共识引擎,与其前身不同,无论集合中是否存在较弱的验证器,它都允许始终如一的高每秒交易量。

Sui 使用面向对象的数据模型。Sui 上的不同对象包括硬币余额、NFT 实例和智能合约。该数据模型允许智能合约表达对对象的计算。这也意味着交易会根据它们所针对的对象自然地组织成组。

Sui优势

通过水平扩展、可组合性、稀疏重放和链上存储等特性,Sui 的架构解决了第一代区块链的常见痛点。

水平缩放

在 Sui 网络上,每组交易都是并行处理的,这与早期区块链中由于各种对象、资源、账户和其他组件之间缺乏区分而出现的瓶颈相反。

可组合性

在 Sui 中,与大多数其他区块链不同,可以直接将资产(例如 NFT)直接传递到函数参数中。Sui 的以对象为中心的方法还允许更深奥的数据结构,以及将资产存储在此类数据结构内或资产本身中的能力。

稀疏重放

自然地,区块链提供每笔交易的账本。对于特定于 Sui 的示例,游戏开发者不需要跟踪与不相关的 dApp 交互的交易。因为查询链上数据可能会很昂贵,所以 Sui 上的产品将能够跟随这个游戏中对象的演变,而无需从 Merkle 树中挖掘出数据

链上存储

由于资产直接作为对象存储在 Sui 区块链上,因此它们永远不会受到 Merkle 树索引的约束。直接在链上存储资产与 IPFS 等传统方式结合使用,以解决链上存储的问题,因为直接在链上更新资产要便宜得多。

注意:本文已从其原始形式更新,以更正关于共识模型中更高延迟的句子。虽然最初的 Narwhal 论文做出了这一发现,但 Sui 网络已经过改进以提供低延迟。

Sui 智能合约

安装sui

安装 Sui 二进制文件和任何必要先决条件的最简单方法是访问Install Sui to Build文档。在这里,您将找到分步说明和所有下载的链接。如果您刚刚起步,访问此页面还有助于您熟悉 Sui 文档

Sui 包布局和语法

下载二进制文件后,您将能够使用命令**sui move new [package name]**在当前路径目录中创建一个新的 Sui Move 包。运行该命令应该在工作区的根目录中创建两个项目:一个**Move.toml**文件和一个名为**sources**. 编译 Sui Move 代码需要**Move.toml**包清单文件和**sources**子目录。其他可选的子目录包括:**examples****scripts****tests**

Move 代码组织(和分发)的主要单位是包。包由一组模块组成,这些模块定义在具有**.move**扩展名的单独文件中。这些文件包括 Move 函数和类型定义。包必须包含**Move.toml**描述包配置的清单文件,例如包元数据或包依赖项。

对象

与许多其他存储以账户为中心并且每个账户包含一个键值存储的区块链相比,Sui 的基本存储单元是一个对象。对象是 Sui Move 中编程的构建块,它们的特点是具有以下两个属性:

  • 所有对象都**has key**在其结构声明之后使用关键字进行注释。
  • 所有对象都将**id: UID**其作为第一个字段。
struct Car has key {
    id: UID,
    speed: u8,
    acceleration: u8,
    handling: u8
}
                   _Sui Move 中的这个简单声明将一个对象命名为“Car”。_ 

由地址拥有: Move 对象在 Move 代码中创建后,可以转移到地址。转移后,该地址拥有该对象。地址拥有的对象只能由该所有者地址签名的交易使用(即作为 Move 调用参数传递)。拥有的对象可以以下三种形式中的任何一种作为 Move 调用参数传递:只读引用 ( **&T**)、可变引用 ( **&mut T**) 和按值 ( **T**)。

由另一个对象拥有:当一个对象由另一个对象拥有时,它不会被包装。子对象仍然作为顶级对象独立存在,可以直接在 Sui 存储中访问。我们将在第四节讨论动态字段时更详细地介绍这一点。

不可变的:你不能改变不可变的对象,不可变的对象没有独占所有者。任何人都可以在 Move 调用中使用不可变对象。所有 Move 包都是不可变对象:您无法在发布 Move 包后对其进行更改。您可以使用该操作将 Move 对象转换为不可变对象**freeze_object**。您只能在 Move 调用中将不可变对象作为只读引用 ( **&T**) 传递。

Shared:一个对象是可以共享的,也就是说任何人都可以读或者写这个对象。与可变拥有的对象(单写者)相比,共享对象需要共识来排序读取和写入。在其他区块链中,每个对象都是共享的。但是,Sui 程序员通常可以选择使用共享对象、拥有对象或组合来实现特定用例。这种选择可能会对性能、安全性和实施复杂性产生影响。

Functions

多个可见性修饰符限制或限制对 Sui Move 功能的访问。**public**functions 允许其他模块导入特定的函数。**public(friend)**functions 允许模块获得明确的权限来导入特定的函数。入口函数允许直接调用函数,例如在事务中。一个名为的特殊函数**init**仅在其关联模块发布时执行一次。它始终具有相同的签名并且只有一个参数:**ctx: &mut TxContext**.

Capabilities

能力是 Sui Move 中的一种特殊模式,可以控制对某些功能的访问。Capabilities 可以与 function 结合使用,**init**以确保永远只存在一个 capabilities,并将其发送给模块发布者。

struct AdminCapability has key { 
    id: UID 
}

fun init(ctx: &mut TxContext) {
    transfer::transfer(AdminCapability {
        id: object::new(ctx),
    }, tx_context::sender(ctx))
}
   AdminCapability 对象被实例化并传输到模块发布者的地址。这确保永远只存在一个 AdminCapability。

对象分类

地址拥有的对象

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectA</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_object_owned_by_an_address</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
    <span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">transfer</span><span style="color:#cccccc">(</span><span style="color:#cccccc">{</span>
        <span style="color:#f8c555">ObjectA</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span>
    <span style="color:#cccccc">}</span><span style="color:#cccccc">,</span> <span style="color:#e2777a">tx_context<span style="color:#cccccc">::</span></span><span style="color:#f08d49">sender</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

对象拥有的对象

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectB</span> has key<span style="color:#cccccc">,</span> store <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_object_owned_by_an_object</span><span style="color:#cccccc">(</span>parent<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">ObjectA</span><span style="color:#cccccc">,</span> ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
    <span style="color:#cc99cd">let</span> child <span style="color:#67cdcc">=</span> <span style="color:#f8c555">ObjectB</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">;</span>
    <span style="color:#e2777a">ofield<span style="color:#cccccc">::</span></span><span style="color:#f08d49">add</span><span style="color:#cccccc">(</span><span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> parent<span style="color:#cccccc">.</span>id<span style="color:#cccccc">,</span> <span style="color:#7ec699">b"child"</span><span style="color:#cccccc">,</span> child<span style="color:#cccccc">)</span><span style="color:#cccccc">;</span>
<span style="color:#cccccc">}</span></code></span></span>

共享对象

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectC</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_shared_object</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
    <span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">share_object</span><span style="color:#cccccc">(</span><span style="color:#f8c555">ObjectC</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

不可变对象

<span style="background-color:#2d2d2d"><span style="color:#cccccc"><code class="language-rust"><span style="color:#cc99cd">struct</span> <span style="color:#f8c555">ObjectD</span> has key <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#f8c555">UID</span> <span style="color:#cccccc">}</span>

public entry fun <span style="color:#f08d49">create_immutable_object</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">:</span> <span style="color:#67cdcc">&</span><span style="color:#cc99cd">mut</span> <span style="color:#f8c555">TxContext</span><span style="color:#cccccc">)</span> <span style="color:#cccccc">{</span>
    <span style="color:#e2777a">transfer<span style="color:#cccccc">::</span></span><span style="color:#f08d49">freeze_object</span><span style="color:#cccccc">(</span><span style="color:#f8c555">ObjectD</span> <span style="color:#cccccc">{</span> id<span style="color:#cccccc">:</span> <span style="color:#e2777a">object<span style="color:#cccccc">::</span></span><span style="color:#f08d49">new</span><span style="color:#cccccc">(</span>ctx<span style="color:#cccccc">)</span> <span style="color:#cccccc">}</span><span style="color:#cccccc">)</span>
<span style="color:#cccccc">}</span></code></span></span>

对象和 NFT

从技术上讲,Sui 上的对象和 NFT 之间没有区别。让我们使用下面的代码片段检查option.move中 Sui 标准库对唯一标识符 (UID) 的定义。

/// Globally unique IDs that define an object's ID in storage. Any Sui object, that is a struct
/// with the `key` ability, must have `id: UID` as its first field.
/// These are globally unique in the sense that no two values of type `UID` are ever equal, in
/// other words for any two values `id1: UID` and `id2: UID`, `id1` != `id2`.
/// This is a privileged type that can only be derived from a `TxContext`.
/// `UID` doesn't have the `drop` ability, so deleting a `UID` requires a call to `delete`.
struct UID has store {
    id: ID,
}

因为 Sui 每次创建一个新对象时都会生成一个全局唯一的 ID,所以没有两个对象是真正可互换的,即使它们的其余字段是相同的。

在 Sui Move 中创建 NFT

由于 Sui 以对象为中心的方法,创建 NFT 相对简单,如下面的代码片段所示。

struct NFT has key, store {
    id: UID,
    name: String,
    description: String,
    url: Url,
    // ... Additional attributes for various use cases (i.e. game, social profile, etc.)
}

上面显示了一个基本示例。Sui 独特的编程模型允许几乎无限的用例。

对象包装

在 Sui 中对象可以拥有其他对象,但是一个对象包裹另一个对象是一个不同的概念

struct A has key {
    id: UID,
    b: B
}

struct B has key, store { id: UID }

在上面的示例中,类型 B 的对象被包装到类型 A 的对象中。通过对象包装,包装的对象(在我们的示例中为对象 B)不会存储为顶级对象。因此,它不能通过其对象 ID 访问,因此与该对象交互的任何 API(即通过前端)将无法直接访问对象 B。

这种方法有一些缺点:

  • 对象具有有限数量的字段。这些是结构声明中的字段。
  • 包裹其他物体的物体会变得非常大,从而导致更高的汽油费。对象的大小也有上限。
  • 不可能通过对象包装来存储异构对象(不同类型的对象)的集合。因为 Move 向量类型必须用单一类型 T 实例化,所以不支持使用向量的异构集合。

动态字段

动态场解决了上述问题。动态字段具有任意名称,并且是临时添加和删除的。关于gas,您只需为您访问的内容付费,而不是为您可以访问的内容付费。动态字段还支持异构类型,从而可以在某个对象中存储不同的类型。

动态字段有两种不同的类型:字段和对象字段。字段可以用 store 存储任何值。这些字段不是独立的对象,无法通过外部 API 的 ID 进行访问。另一方面,对象字段必须在其结构声明之后有 key,store。后者的优点是对象保持相同的对象 ID,并且仍然可以从外部 API 读取。

NFT

动态字段引入了一种扩展现有对象的方法,而集合则更进一步。集合建立在动态字段之上,它们额外支持计算它们包含的条目数,并防止意外的非空删除。

集合支持同质和异质值。同构集合类型为Table。存储在 Table 中的所有值必须属于同一类型。异构集合类型是Bag。Bag 中存储的值可以是任何类型。与动态字段和动态对象字段类似,table 和 bag 也提供了仅对象版本:object_table 和 object_bag。table 和 bag 的所有值必须只有 store,object_table 和 object_bag 的所有值必须有 key,store。

合约开发

由于 Sui Move 的以对象为中心的编程模型及其可扩展性,Sui 有望成为第一个真正将 web2 体验交付到 web3 的区块链。这种体验的最前沿包括游戏。游戏的编程本质上都很复杂,并且需要强大的基础设施来确保玩家的无缝体验。得益于上述两点,Sui 能够应对挑战。

以下开始编写

/// Our hero!
struct Hero has key, store {
    id: UID,
    /// Hit points. If they go to zero, the hero can't do anything
    hp: u64,
    /// Experience of the hero. Begins at zero
    experience: u64,
    /// The hero's minimal inventory
    sword: Option<Sword>,
    /// An ID of the game user is playing
    game_id: ID,
}

上面的代码定义了我们的可玩角色。从它的领域可以看出,这个英雄可以与角色扮演游戏中的其他角色相媲美。它具有生命值 (HP)、经验和库存。

/// The hero's trusty sword
struct Sword has key, store {
    id: UID,
    /// Constant set at creation. Acts as a multiplier on sword's strength.
    /// Swords with high magic are rarer (because they cost more).
    magic: u64,
    /// Sword grows in strength as we use it
    strength: u64,
    /// An ID of the game
    game_id: ID,
}

上面的代码显示了我们英雄的剑。请注意,这把剑具有钥匙和存储能力。回顾一下本系列之前的课程,key 意味着它是一种可拥有的资产,可以存在于顶级存储中。此类别中的移动对象也可以从外部 API 访问,从而创造了 Sui 在多个游戏中使用项目的独特可能性。而存储意味着这个对象是可以自由包装和转移的。

/// A creature that the hero can slay to level up
struct Boar has key {
    id: UID,
    /// Hit points before the boar is slain
    hp: u64,
    /// Strength of this particular boar
    strength: u64,
    /// An ID of the game
    game_id: ID,
}

上面,我们在游戏中定义了野猪、不可玩角色 (NPC) 或敌人。与该类型的其他游戏类似,我们可以为我们的英雄创建 NPC 来战斗和获得经验,或者购买物品和接受任务。

动作描述

/// Slay the `boar` with the `hero`'s sword, get experience.
/// Aborts if the hero has 0 HP or is not strong enough to slay the boar
public entry fun slay(
    game: &GameInfo, hero: &mut Hero, boar: Boar, ctx: &TxContext
) {
    check_id(game, hero.game_id);
    check_id(game, boar.game_id);
    let Boar { id: boar_id, strength: boar_strength, hp, game_id: _ } = boar;
    let hero_strength = hero_strength(hero);
    let boar_hp = hp;
    let hero_hp = hero.hp;
    // attack the boar with the sword until its HP goes to zero
    while (boar_hp > hero_strength) {
        // first, the hero attacks
        boar_hp = boar_hp - hero_strength;
        // then, the boar gets a turn to attack. if the boar would kill
        // the hero, abort--we can't let the boar win!
        assert!(hero_hp >= boar_strength , EBOAR_WON);
        hero_hp = hero_hp - boar_strength;
    };
    // hero takes their licks
    hero.hp = hero_hp;
    // hero gains experience proportional to the boar, sword grows in
    // strength by one (if hero is using a sword)
    hero.experience = hero.experience + hp;
    if (option::is_some(&hero.sword)) {
        level_up_sword(option::borrow_mut(&mut hero.sword), 1)
    };
    // let the world know about the hero's triumph by emitting an event!
    event::emit(BoarSlainEvent {
        slayer_address: tx_context::sender(ctx),
        hero: object::uid_to_inner(&hero.id),
        boar: object::uid_to_inner(&boar_id),
        game_id: id(game)
    });
    object::delete(boar_id);
}

上面代码中显示的动作描述了 slay 函数。在高层次上,此函数首先检查以确保 Hero 和 Boar 都属于同一个游戏实例。然后英雄和野猪之间的决斗发生,并进行检查以确保英雄的 HP 不会降为零。决斗完成后,英雄获得的经验值与野猪成正比,英雄的剑的强度增加一倍(如果英雄正在挥舞着剑)。最后,该函数发出一个事件 BoarSlayEvent。Sui Move 中的事件让索引器跟踪链上操作,这是实现普遍认可的对象状态的重要手段。

上面的代码示例简要摘录了 Sam 的 hero.move 代码。此代码为 Sui 上的游戏开发者提供了一个有价值的示例,并且由于它是开源的,请随时分叉存储库并构建您自己的游戏!

合约部署

测试 Sui Move 包

一旦我们编写了代码,我们就想测试它的功能。Move 有两种类型的测试框架,一种通用的 Move 测试框架和一种 Sui 特定的测试框架。由于 Sui Move 包含核心 Move 中不存在的几个功能,因此我们将重点关注后者。

特定于 Sui 的测试可以在sui::test_scenario 模块中找到。test_scenario 模块提供了一个环境,构建者可以在其中通过一系列事务测试他们的代码。Sui 分类账的所有更新都通过交易发生,Sui 中的 Move 调用封装在交易中。构建者可以创建模拟事务来查看多个不同事务之间的交互(即一个事务创建一个对象,另一个事务传输该对象,另一个事务改变该对象)。

发布Sui Move 包

现在我们已经测试了我们的代码,我们可以实际发布它了!只有在相应的包发布到 Sui 网络后才能调用 Sui Move 函数,在 Sui 网络中它被表示为 Sui 分类账上的不可变对象。要发布包,请导航到包目录,然后从命令行界面 (CLI) 调用 sui client publish --gas-budget (即 of 2000)。如果成功,您将把您的包发布到 Sui 网络!

调用Sui Move 函数

在 Sui 网络上调用我们的代码可以让模块相互交互,从而在游戏中创建动作。在我们视频中的示例中,我们使用了 Sui Explorer 来调用代码,这样可以更轻松地可视化课程。我们可以使用 Sui CLI 调用相同的函数。创建自己的 dApp 时,您可能希望为用户提供一个漂亮的前端,而不是让他们使用 Sui Explorer 或 CLI。Sui JSON-RPC API可让您将智能合约连接到前端。

相关文章
|
4月前
|
JSON Java Android开发
Android 开发者必备秘籍:轻松攻克 JSON 格式数据解析难题,让你的应用更出色!
【8月更文挑战第18天】在Android开发中,解析JSON数据至关重要。JSON以其简洁和易读成为首选的数据交换格式。开发者可通过多种途径解析JSON,如使用内置的`JSONObject`和`JSONArray`类直接操作数据,或借助Google提供的Gson库将JSON自动映射为Java对象。无论哪种方法,正确解析JSON都是实现高效应用的关键,能帮助开发者处理网络请求返回的数据,并将其展示给用户,从而提升应用的功能性和用户体验。
114 1
|
7月前
|
编解码 JavaScript 安全
JS逆向过程中中文编解码的小案例详解
JS逆向过程中中文编解码的小案例详解
52 1
|
7月前
|
存储 XML 缓存
前端知识笔记(一)———Base64图片是什么?原理是什么?优缺点是什么?
前端知识笔记(一)———Base64图片是什么?原理是什么?优缺点是什么?
192 0
|
7月前
|
机器学习/深度学习 TensorFlow 语音技术
【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码和演示视频)
【Android +Tensroflow Lite】实现从基于机器学习语音中识别指令讲解及实战(超详细 附源码和演示视频)
76 0
|
7月前
|
存储 XML 缓存
前端知识笔记(三十九)———Base64图片是什么?原理是什么?优缺点是什么?
前端知识笔记(三十九)———Base64图片是什么?原理是什么?优缺点是什么?
134 0
|
传感器 存储 编解码
即时通讯音视频开发(二十):一文读懂视频的颜色模型转换和色域转换
本文将以通俗易懂的文字,引导你理解视频是如何从采集开始,历经各种步骤,最终通过颜色模型转换和不同的色域转换,让你看到赏心悦目的视频结果的。
86 0
|
前端开发
前端知识学习案例22-code sand box线上编码平台
前端知识学习案例22-code sand box线上编码平台
90 0
前端知识学习案例22-code sand box线上编码平台
|
API 语音技术 Python
Python 技术篇-用base64库对音频、图片等文件进行base64编码和解码实例演示
Python 技术篇-用base64库对音频、图片等文件进行base64编码和解码实例演示
1010 0
Python 技术篇-用base64库对音频、图片等文件进行base64编码和解码实例演示
|
JavaScript 前端开发 数据可视化
地球引擎初级教程——JavaScript 简介(一文读懂如何使用GEE)
地球引擎初级教程——JavaScript 简介(一文读懂如何使用GEE)
288 0
|
JSON Dart 数据格式
本文将向您展示如何在 Flutter 中编码/解码 JSON
本文将向您展示如何在 Flutter 中编码/解码 JSON。 导入dart:convert库:
285 0