Android14显示系统 - 图形库canvas\HWUI\skia

文章目录

Android 图形库体系回顾

canvas和skia是Android图形库体系中的一部分,硬件渲染才会通过HWUI使用skia,对于图形库,我们把握框架即可

1、应用程序调用什么接口进行绘图?

复制代码
在Android应用程序中是通过Canvas API来绘制UI元素的。在硬件加速渲染环境中,这些Canvas API调用最终会转化为OpenGL API调用(转化过程对应用程序来说是透明的)。由于OpenGL API调用要求发生在Open GL环境中,因此在每当有新的Activity窗口启动时,系统都会为其初始化好OpenGL环境。

2、canvas

1)canva是java层的一个绘图工具(提供java应用层的绘图接口 - 矩形、三角形),native层实现是skia;

2)Android官方定义canva:The Canvas class holds the draw calls,是一个将"图像数据"写入Bitmap中的一个工具集;

3)canva和Skia支撑着整个Android的上层视觉呈现;

3、2D图形引擎库-skia

1)资料快车

注意skia的资料相对匮乏!

skia的学习与研究

https://zhuanlan.zhihu.com/p/27494299049

深入理解Flutter的图形图像绘制原理------图形库skia剖析

https://mp.weixin.qq.com/s/NwW4SKe1uFYkPh94pAsprA

Skia 2D引擎开发知识分享:

https://blog.csdn.net/liulun/article/details/145071567

Skia编译及实践:

https://segmentfault.com/a/1190000044507663#item-2-1

Google官网: https://code.google.com/p/skia

2)概况

Skia 是一个跨平台的 2D 图形渲染库(百万代码行级别),由 Google 开发并维护,在 Android 系统中,Skia 是底层图形渲染的核心引擎,

SKia - 需要用到GPU? 可选用硬件GPU 或 软件CPU方式处理

Skia 提供以下核心功能:

复制代码
Skia 提供统一的图形 API,屏蔽底层硬件差异
动画实现(animator目录)、图片界面、文件绘制、支持多种特效(effects目录)

1. 2D 图形绘制
   - 几何图形:矩形、圆形、路径、文本等
   - 图像处理:解码、编码、缩放、旋转等
   - 效果处理:模糊、阴影、渐变、滤镜等

2. 渲染后端支持 - Skia 通过 EGL/Vulkan 等标准接口与底层驱动交互
   - **软件渲染**:使用 CPU 进行像素级绘制(`SkBitmapDevice`)
   - **硬件加速**:使用 GPU 进行加速渲染(`GrDirectContext`)
     - OpenGL ES 后端(`GrGLGpu`)
     - Vulkan 后端(`GrVkGpu`)

3. 图像编解码
   - 支持多种图像格式:JPEG、PNG、WebP、BMP、ICO 等
   - 提供 `SkCodec` 接口进行图像解码
   
4. 文本渲染
   - 字体管理(`SkFontMgr`)
   - 文本布局和渲染
   - 支持复杂文本效果

3)Android4.0 - skia框架

4)Skia外部组件依赖

1、最新架构中SKGPUDevice被替换为GrDirectContext方式;

5)Skia 软件层次分析

1、Skia在结构上大致分为三层:画步层,渲染设备层和封装适配层

6)SKia图形库架构分析

复制代码
1.缩略词
GrGpu -Graphics GPU
GrGL - Graphics OpenGL
GrContext - Graphics Context

2.Skia的基础组件:
- `SkCanvas` - 绘制画布
- `SkPaint` - 画笔
- `SkPath` - 路径
- `SkImage` - 图像
- `SkShader` - 着色器


3.示例程序
1)一个简单的使用skia例子
/android/external/skia/example/HelloWorld.cpp
2)CPU/GPU方式skia编程例子
Skia编译及实践:
https://segmentfault.com/a/1190000044507663#item-2-1


4.源码目录结构
external/skia/
├── include/              # 公共头文件
│   ├── core/            # 核心 API (SkCanvas, SkSurface, SkPaint 等)
│   ├── gpu/             # GPU 相关 API (GrDirectContext, GrGpu 等)
│   ├── effects/         # 效果 API (模糊、渐变等)
│   └── android/         # Android 特定接口
├── src/                 # 源代码
│   ├── core/            # 核心实现
│   │   ├── SkCanvas.cpp  //定义了SKCanvas类,它是所有图形渲染操作的基础
│   │   ├── SkSurface.cpp
│   │   ├── SkBitmapDevice.cpp  # 软件渲染设备
│   │   └── SkDraw.cpp          # 底层绘制引擎
│   ├── gpu/             # GPU 后端实现
│   │   ├── GrDirectContext.cpp  //定义了GrDirectContext类,它是Skia的GPU渲染上下文
│   │   ├── gl/          # OpenGL ES 后端
│   │   │   ├── GrGLGpu.cpp
│   │   │   └── GrGLInterface.cpp
│   │   └── vk/          # Vulkan 后端
│   │       └── GrVkGpu.cpp
│   ├── codec/           # 图像编解码
│   └── effects/         # 效果实现
│   └── fronts/          # 文字渲染
│   └── image/           # 图像处理
│   └── pdf/             # 支持图形渲染为PDF文档,渲染结果不是输出到显示器的画面,而是输出为pdf文件。
│   └── svg/             # Scalable Vector Graphics,可缩放矢量图形渲染
|   └── tools/skiatest.cpp  //包含了一系列Skia测试用例,可以用于验证Skia的功能。
└── Android.bp           # Android 构建文件


5. 核心类关系
SkCanvas (绘制接口)
    ↓
SkBaseDevice (设备基类)
    ├── SkBitmapDevice (软件渲染设备)
    └── GrBaseDevice (GPU 设备基类)
        └── GrSurfaceDrawContext (GPU 渲染设备)

SkSurface (渲染表面)
    ├── SkSurface_Raster (软件渲染表面)
    └── SkSurface_Gpu (GPU 渲染表面)

GrDirectContext (GPU 上下文)
    ↓
GrGpu (GPU 抽象基类)
    ├── GrGLGpu (OpenGL ES 后端)
    ├── GrVkGpu (Vulkan 后端)
    └── GrMtlGpu (Metal 后端)


6.skia在Android GUI 架构层次

┌─────────────────────────────────────────────────────────┐
│  应用层 (Application)                                    │
│  - View.onDraw(Canvas)                                  │
│  - Canvas.drawRect(), drawText() 等                     │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│  Android Framework 层                                     │
│  - Canvas.java (Java API)                               │
│  - ThreadedRenderer.java                                 │
│  - HardwareRenderer.java                                │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│  HWUI 层 (Hardware UI)                                   │
│  - SkiaCanvas (Skia 的薄包装层)                          │
│  - RecordingCanvas (硬件加速记录)                         │
│  - RenderProxy, CanvasContext                           │
│  - DisplayList, RenderNode                              │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│  Skia 层 (本文档分析)                                    │
│  - SkCanvas (绘制接口)                                   │
│  - SkSurface (渲染表面)                                  │
│  - SkBitmapDevice (软件渲染)                             │
│  - GrDirectContext (GPU 上下文)                          │
│  - GrGLGpu / GrVkGpu (GPU 后端)                          │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│  图形驱动层                                               │
│  - EGL (OpenGL ES 接口层)                                │
│  - Vulkan (Vulkan API)                                   │
│  - Mesa / Panfrost (GPU 驱动)                            │
└─────────────────────────────────────────────────────────┘
                        ↓
┌─────────────────────────────────────────────────────────┐
│  内核层 (Kernel)                                         │
│  - DRM/KMS (Direct Rendering Manager)                   │
│  - GPU 硬件                                              │
└─────────────────────────────────────────────────────────┘


7.对外提供的核心API接口

1)SkCanvas - 绘制接口

1、头文件include/core/SkCanvas.h`
- 提供所有绘制操作:`drawRect()`, `drawText()`, `drawPath()`, `drawImage()` 等
- 管理绘制状态:变换矩阵、裁剪区域、Paint 属性
- 支持图层操作:`save()`, `restore()`, `saveLayer()`


2、SkCanvas类
class SkCanvas {
public:
    // 绘制操作
    void drawRect(const SkRect& r, const SkPaint& paint);
    void drawText(const void* text, size_t byteLength, SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint);
    void drawPath(const SkPath& path, const SkPaint& paint);
    void drawImage(sk_sp<const SkImage> image, SkScalar x, SkScalar y, const SkSamplingOptions&, const SkPaint* paint);
    
    // 状态管理
    int save();
    void restore();
    void restoreToCount(int count);
    
    // 变换和裁剪
    void translate(SkScalar dx, SkScalar dy);
    void scale(SkScalar sx, SkScalar sy);
    void rotate(SkScalar degrees);
    void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias);
};


2)SkSurface - 渲染表面

1、头文件include/core/SkSurface.h
- 创建和管理渲染表面(CPU 内存或 GPU 纹理)
- 提供 `getCanvas()` 获取绘制接口
- 支持离屏渲染和屏幕渲染

2、SkSurface类
class SkSurface {
public:
    // 创建软件渲染表面
    static sk_sp<SkSurface> MakeRaster(const SkImageInfo& imageInfo, size_t rowBytes, const SkSurfaceProps* props);
    static sk_sp<SkSurface> MakeRasterDirect(const SkImageInfo& imageInfo, void* pixels, size_t rowBytes, const SkSurfaceProps* props);
    
    // 创建 GPU 渲染表面
    static sk_sp<SkSurface> MakeRenderTarget(GrRecordingContext* context, SkBudgeted budgeted, const SkImageInfo& imageInfo, int sampleCount, GrSurfaceOrigin origin, const SkSurfaceProps* props, bool shouldCreateWithMips);
    
    // 获取绘制接口
    SkCanvas* getCanvas();
    
    // 图像操作
    sk_sp<SkImage> makeImageSnapshot();
    bool readPixels(const SkPixmap& pm, int srcX, int srcY);
};

3) GrDirectContext - GPU 上下文

1、头文件:include/gpu/GrDirectContext.h`
功能
- 管理 GPU 资源和上下文
- 提供 GPU 加速的渲染能力
- 支持 OpenGL ES、Vulkan、Metal 等后端

2、GrDirectContext类
class GrDirectContext : public GrRecordingContext {
public:
    // 创建 OpenGL ES 上下文
    static sk_sp<GrDirectContext> MakeGL(sk_sp<const GrGLInterface> glInterface, const GrContextOptions& options);
    
    // 创建 Vulkan 上下文
    static sk_sp<GrDirectContext> MakeVulkan(const GrVkBackendContext& backendContext, const GrContextOptions& options);
    
    // 资源管理
    void flush();
    void submit(bool syncCpu);
    void resetGLTextureBindings();
    
    // 查询能力
    const GrCaps* caps() const;
};


4)SkImage - 图像接口
1、头文件include/core/SkImage.h`

2、功能
- 表示图像数据(位图、纹理、编码图像等)
- 支持图像变换、缩放、裁剪
- 支持从文件、内存、GPU 纹理创建图像

5)SkPaint - 绘制属性
1、头文件:include/core/SkPaint.h
2、功能
- 定义绘制属性:颜色、样式、字体、效果等
- 支持各种绘制效果:模糊、阴影、渐变、滤镜等

7)使用skia

复制代码
当使用软件渲染时应用程序可以直接调用skia的API,使用硬件GPU渲染时一般借助hwui方便地使用skia

- 软件绘制:`SkiaCanvas` 包装 `SkCanvas(bitmap)` → `SkBitmapDevice`
- 硬件加速:`SkiaPipeline` 使用 `GrDirectContext` → `GrGLGpu` / `GrVkGpu`

这里介绍硬件渲染

1)HWUI (Hardware UI)
frameworks/base/libs/hwui/

1、View System使用HWUI
1)Android Framework Canvas API
frameworks/base/core/java/android/graphics/Canvas.java`

- Java 层通过JNI调用HWUI的SkiaCanvas
- 最终调用Skia的SkCanvas API

流程图
Canvas.java.drawRect()
    ↓ [JNI]
android_graphics_Canvas.cpp::drawRect()
    ↓
SkiaCanvas::drawRect()
    ↓
SkCanvas::drawRect()


2、HWUI创建SKCanvas
- SkiaCanvas:直接使用 `SkCanvas` 进行绘制
frameworks/base/libs/hwui/SkiaCanvas.cpp
Canvas* Canvas::create_canvas(const SkBitmap& bitmap) {
    return new SkiaCanvas(bitmap);
}

3、使用GrDirectContext进行 GPU 加速渲染
// frameworks/base/libs/hwui/pipeline/skia/SkiaPipeline.cpp
// 创建 GPU 上下文和表面
sk_sp<GrDirectContext> context = GrDirectContext::MakeGL(...);
sk_sp<SkSurface> surface = SkSurface::MakeRenderTarget(context.get(), ...);
  
4.图形绘制接口
void SkiaCanvas::drawRect(float left, float top, float right, float bottom, const Paint& paint) {
  mCanvas->drawRect({left, top, right, bottom}, p);
}

8)调用流程示例

1、EGL 接口层

位置 :系统库 libEGL.so

功能

  • 提供 OpenGL ES 与窗口系统的接口
  • 管理渲染上下文(Context)
  • 管理渲染表面(Surface)

Skia 使用方式

// Skia 通过 EGL 创建 OpenGL ES 上下文

EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

EGLContext context = eglCreateContext(display, config, ...);

// 然后通过 GrGLInterface 封装 OpenGL ES 调用

2、软件绘制流程
复制代码
View.onDraw(Canvas)
    → Canvas.java.drawRect()
    → SkiaCanvas::drawRect()
    → SkCanvas::drawRect()
    → SkBitmapDevice::drawRect()
    → SkDraw::drawRect()
    → CPU 内存操作
3、硬件加速流程
复制代码
View.onDraw(Canvas)
    → RecordingCanvas (记录命令)
    → DisplayList
    → RenderThread
    → SkiaPipeline::draw()
    → SkCanvas::drawRect() (GPU Surface)
    → GrDirectContext
    → GrGLGpu::drawRect()
    → OpenGL ES API
    → EGL
    → Mesa/Panfrost
    → GPU 硬件

4、Android上使用canvas和skia

复制代码
1.Canvas
/android/frameworks/base/graphics/java/android/graphics/Canvas.java
public class Canvas extends BaseCanvas {
    Bitmap mBitmap;
    DrawFilter mDrawFilter;
    

    scale(float sx, float sy);
    rotate(float degrees, float px, float py);
    clipRect(float left, float top, float right, float bottom);
    // ---------------- Draw Methods -------------------
    drawArc();
    drawCircle();
    drawLine();

}

2.Canvas的创建
/android/frameworks/base/core/java/android/view/Surface.java
lockCanvas(Rect inOutDirty)
--nativeLockCanvas(mNativeObject, mCanvas, inOutDirty); //mCanvas = new CompatibleCanvas();
/android/frameworks/base/core/jni/android_view_Surface.cpp
----sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject)); //C++层的Surface对象
----surface->lock(&buffer, dirtyRectPtr); //获得一个可用的buffer
----graphics::Canvas canvas(env, canvasObj);
----canvas.setBuffer(&buffer); //设置canvas的buffer

3.ANativeWindow_Buffer
/android/frameworks/native/libs/nativewindow/include/android/native_window.h
typedef struct ANativeWindow_Buffer {
    int32_t width;
    int32_t height;
    int32_t stride;
    int32_t format;
    void* bits;
}ANativeWindow_Buffer;

4.drawCircle() -软件绘制
/android/frameworks/base/graphics/java/android/graphics/Canvas.java
drawCircle()
/android/frameworks/base/graphics/java/android/graphics/BaseCanvas.java
--super.drawCircle()
/android/external/skia/src/core/SkBitmapDevice.cpp
void SkBitmapDevice::drawRect(const SkRect& r, const SkPaint& paint) {
    BDDraw(this).drawRect(r, paint);
}

5.drawCircle() - 硬件绘制
/android/frameworks/base/graphics/java/android/graphics/Canvas.java
drawCircle()
/android/frameworks/base/graphics/java/android/graphics/BaseCanvas.java
--super.drawCircle()
/android/frameworks/base/libs/hwui/jni/android_graphics_Canvas.cpp
----nDrawCircle()
------CanvasJNI::drawCircle()
--------get_canvas(canvasHandle)->drawCircle(cx, cy, radius, *paint);
/android/frameworks/base/libs/hwui/SkiaCanvas.cpp
----------mCanvas->drawCircle(x, y, radius, p) //最终陷入到skia中去

5、HWUI介绍

1.Android HWUI 介绍:

https://zhuanlan.zhihu.com/p/1914686827882853373

6、HWUI (Hardware UI) 架构分析

1)HWUI 概述

1、HWUI (Hardware UI)是 Android的硬件加速渲染库,即使用GPU渲染,负责对接 Android Java层视图系统(View System)到 native的图形库,在android低版本上通过libhwui调用OpenGL api来渲染, Android P上libhwui 会调用skia,再调用GLES相关的API进行渲染。

2)核心功能

复制代码
1)显示列表记录:将 View 的绘制命令记录为 DisplayList,再次渲染只更新DisplayList中的部分区域
2)渲染线程管理:在独立线程中执行 GPU 渲染,避免阻塞 UI 线程
3)Skia 集成:基于 Skia 图形库实现底层绘制
4)OpenGL/Vulkan 支持:支持 OpenGL ES 和 Vulkan 两种渲染后端

3)在 Android GUI 架构中的位置

1、View体系到GPU的调用路径
复制代码
┌─────────────────────────────────────────────────────────┐
│  应用层 (Application)                                    │
│  - Activity, Fragment, View                              │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  View 系统层 (View System)                               │
│  - ViewRootImpl                                          │
│  - ThreadedRenderer (Java)                               │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  HWUI 层 (Hardware UI Library) ← 当前层级                │
│  - RenderNode, RecordingCanvas                           │
│  - RenderProxy, CanvasContext                            │
│  - SkiaPipeline                                          │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  Skia 图形库层                                           │
│  - SkCanvas, SkPaint, SkPath                            │
│  - Skia GPU Backend                                      │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  OpenGL ES / Vulkan 驱动层                               │
│  - EGL / Vk                                              │
│  - Mesa/Panfrost (开源驱动)                              │
└─────────────────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────────────────┐
│  GPU 硬件层                                              │
│  - Mali, Adreno, PowerVR 等 GPU                         │
└─────────────────────────────────────────────────────────┘
​```



3.2 调用路径示例
应用代码
    ↓
View.onDraw(Canvas canvas)
    ↓
RecordingCanvas (记录绘制命令)
    ↓
RenderNode (存储 DisplayList)
    ↓
ViewRootImpl.performTraversals() - JAVA
    ↓
ThreadedRenderer.draw() - JAVA
    ↓ [JNI]
RenderProxy.syncAndDrawFrame()  ← HWUI 入口
    ↓
CanvasContext.draw()
    ↓
SkiaPipeline.draw()
    ↓
SkCanvas (Skia)
    ↓
OpenGL-ES/Vulkan -mesa3D
    ↓
Panfrost (开源用户态驱动) - libdrm
    ↓    
GPU driver - DRM/KMS (开源内核态驱动)
    ↓
GPU 硬件

7、HWUI 核心组件分析

复制代码
1)主要目录结构
hwui/
├── jni/                    # JNI 接口层 - 同时也是对外接口层
│   ├── android_graphics_HardwareRenderer.cpp
│   ├── android_graphics_RenderNode.cpp
│   └── android_graphics_Canvas.cpp
│
├── renderthread/           # 渲染线程管理
│   ├── RenderProxy.cpp     # UI 线程和渲染线程的代理
│   ├── RenderThread.cpp    # 渲染线程主循环
│   ├── CanvasContext.cpp   # 渲染上下文管理
│   └── DrawFrameTask.cpp   # 绘制帧任务
│
├── pipeline/               # 渲染管线
│   └── skia/
│       ├── SkiaPipeline.cpp        # Skia 渲染管线
│       ├── SkiaOpenGLPipeline.cpp   # OpenGL 后端
│       └── SkiaVulkanPipeline.cpp   # Vulkan 后端
│
├── RenderNode.cpp          # 渲染节点(存储 DisplayList)
├── RecordingCanvas.cpp     # 记录画布(记录绘制命令)
└── ...
​```

2)核心类说明

2.1 RenderNode
- **功能**:存储视图的绘制命令和属性
- **位置**:`RenderNode.h/cpp`
- **作用**:
  - 存储 DisplayList(显示列表)
  - 存储 RenderProperties(渲染属性:变换、裁剪等)
  - 支持硬件层(Hardware Layer)

2.2 RecordingCanvas
- **功能**:记录绘制命令到 DisplayList
- **位置**:`RecordingCanvas.h/cpp`
- **作用**:
  - 将 View.onDraw() 中的绘制命令记录为 DisplayList
  - 不立即执行绘制,而是记录命令序列

2.3 RenderProxy
- **功能**:UI 线程和渲染线程之间的代理
- **位置**:`renderthread/RenderProxy.h/cpp`
- **作用**:
  - 管理线程间通信
  - 同步 UI 线程和渲染线程
  - 提交绘制任务到渲染线程

2.4 CanvasContext
- **功能**:管理渲染上下文和 EGL/Vulkan
- **位置**:`renderthread/CanvasContext.h/cpp`
- **作用**:
  - 管理 EGL 上下文或 Vulkan 设备
  - 管理渲染表面(Surface)
  - 执行实际的绘制操作

2.5 SkiaPipeline
- **功能**:基于 Skia 的渲染管线实现
- **位置**:`pipeline/skia/SkiaPipeline.h/cpp`
- **作用**:
  - 将 DisplayList 转换为 Skia 绘制命令
  - 调用 Skia API 进行绘制
  - 支持 OpenGL 和 Vulkan 两种后端


3)HWUI 工作流程

完整渲染流程

1. UI 线程:View.onDraw(Canvas)
   └─> RecordingCanvas 记录绘制命令
       └─> 存储到 RenderNode 的 DisplayList

2. UI 线程:ViewRootImpl.performTraversals()
   └─> ThreadedRenderer.draw()
       └─> updateRootDisplayList() 更新所有 View 的 DisplayList
       └─> syncAndDrawFrame() [JNI 调用]

3. JNI 层:android_graphics_HardwareRenderer.cpp
   └─> RenderProxy.syncAndDrawFrame()
       └─> 提交 DrawFrameTask 到渲染线程

4. 渲染线程:RenderThread
   └─> DrawFrameTask.run()
       └─> CanvasContext.draw()
           └─> SkiaPipeline.draw()
               └─> 遍历 RenderNode 树
                   └─> 执行 DisplayList 中的绘制命令
                       └─> 调用 Skia API

5. Skia 层
   └─> SkCanvas 绘制操作
       └─> Skia GPU Backend
           └─> 生成 OpenGL/Vulkan 命令

6. 驱动层
   └─> OpenGL ES / Vulkan API
       └─> EGL / Vulkan 驱动
           └─> Mesa/Panfrost
               └─> GPU 硬件执行

8、典型的硬件渲染过程

相关推荐
holidaypenguin3 天前
【转】跨浏览器 Canvas 图像解码终极方案:让大图渲染也能丝滑不卡顿
前端·canvas
普兰店拉马努金7 天前
【Canvas与旗帜】标准比例挪威国旗
canvas·旗帜·挪威
德育处主任9 天前
在小程序做海报的话,Painter就很给力
前端·微信小程序·canvas
熊猫钓鱼>_>13 天前
解决Web游戏Canvas内容在服务器部署时的显示问题
服务器·前端·游戏·canvas·cors·静态部署·资源路径
Lsx_15 天前
案例+图解带你一文读懂Svg、Canvas、Css、Js动画🔥🔥(4k+字)
前端·javascript·canvas
狂龙骄子15 天前
uniapp圆形时钟
小程序·uniapp·canvas·clock·圆盘时钟
若梦plus16 天前
Canvas基础
前端·canvas
若梦plus16 天前
Canvas的未来之AI绘图、生成式视觉与XR
前端·canvas
1024肥宅17 天前
综合项目实践:可视化技术核心实现与应用优化
svg·webgl·canvas