Skip to content

LED显示屏画布工具(led-screen-canvas)

led-screen-canvas 用于绘制 LED显示屏布局, 包括机箱、面板、像素、围栏等;

  • 交互支持:缩放、平移、拖拽、激活 等常用功能;
  • 轻量性:组件本身基于 原生canvas + 原生js 实现(没有任何外部依赖),外部封装为 vue3 组件,也可以很方便的封装为 react 或者 直接原生使用;
  • 独立对象性能:支持 25万(500x500) 独立对象 流畅渲染;
  • 规则矩阵性能:支持 16K显示屏像素矩阵(15360 × 8640) 流畅渲染;

git地址https://gitee.com/ericfang/led-screen-canvas

安装

使用以下命令安装 led-screen-canvas

bash
npm install led-screen-canvas
# 或
yarn add led-screen-canvas

导入组件

全局导入

main.js 中全局引入 led-screen-canvas

ts
import {createApp} from "vue";
import App from "./App.vue";
import LedScreenCanvas from "led-screen-canvas";

createApp(App).use(LedScreenCanvas).mount("#app");

按需导入

在需要的组件中导入 led-screen-canvas

vue

<script setup lang="ts">
  import {LedScreenCanvas} from "led-screen-canvas";
</script>

<template>
  <led-screen-canvas/>
</template>

操作

  1. 鼠标拖拽 ,即可拖拽元素;
  2. 鼠标滚轮,即可缩放元素;
  3. 按住ctrl键 + 鼠标拖拽,即可平移画布;

基本使用

可拖拽、可激活元素添加到画布中, 并点击设置激活状态;

  • 以下是 500x500 = 250000 Rectangle 物体的渲染演示;

TIP

放大显示文字,点击激活元素,拖转元素时自动将元素置顶;

由于受到前端性能限制,25万 可能是个极限,理论上可以渲染更多的物体,但是渲染效率上可能无法接受;

如果是规则矩阵渲染,可以使用 Matrix 对象,性能上会提高很多;

点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Rectangle } from "led-screen-canvas";
import { ref } from "vue";

const canvasRef = ref();

const onReady = () => {
  const rects = [];
  const size = 100;
  for (let r = 0; r < 500; r++) {
    for (let c = 0; c < 500; c++) {
      const rect = new Rectangle(c * size, r * size, size, size);
      rect.canActive = true;
      rect.canMove = true;
      rect.text = `Panel(${r},${c})`;
      rects.push(rect);
    }
  }
  canvasRef.value.addShapes(rects);
};
</script>

<template>
  <a-space direction="vertical" style="width: 100%">
    <a-space wrap>
      <a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
      <a-button type="primary" status="danger" @click="canvasRef.stop()"
        >暂停渲染(避免多个画布导致浏览器卡顿)</a-button
      >
      <a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
      <a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
    </a-space>
    <led-screen-canvas
      style="height: 400px; background-color: #dddddd"
      ref="canvasRef"
      @on-ready="onReady"
      @on-click="(shape, e) => shape?.setActive(!shape.isActive(e), e)"
      @on-shape-move-start="shape => shape.setZIndexTop()"
    />
  </a-space>
</template>

<style scoped lang="less"></style>

矩阵渲染

由于矩阵存在明显规律性,因此针对矩阵渲染做了大量优化,可以让矩阵渲染变得极为流畅;

  • 以下是 15360 × 8640 = 1.32亿 像素矩阵的渲染演示;

TIP

模拟曲面屏像素点最差布局(面板大小100x100, 斜角一半有灯珠,一半没有灯珠的情况);

实际最差布局为全部棋盘格点亮,但实际显示屏布局没有这种极限情况;

点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Matrix, Rectangle } from "led-screen-canvas";
import { ref } from "vue";

const canvasRef = ref();

const onReady = () => {
  const [w, h, size] = [15360, 8640, 50];
  const matrix = new Matrix(0, 0, w, h, size, size);
  matrix.canMove = false;
  matrix.canHover = true;
  for (let ofx = 0; ofx < w; ofx += 100) {
    for (let ofy = 0; ofy < h; ofy += 100) {
      for (let y = 0; y < 100; y++) {
        for (let x = 0; x <= y; x++) {
          const [tx, ty] = [ofx + (100 - x - 1), ofy + y];
          matrix.setActiveGrid(true, tx, ty);
        }
      }
    }
  }
  canvasRef.value.addShapes([matrix]);
};
</script>

<template>
  <a-space direction="vertical" style="width: 100%" wrap>
    <a-space>
      <a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
      <a-button type="primary" status="danger" @click="canvasRef.stop()"
        >暂停渲染(避免多个画布导致浏览器卡顿)</a-button
      >
      <a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
      <a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
    </a-space>
    <led-screen-canvas
      style="height: 400px; background-color: #dddddd"
      ref="canvasRef"
      @on-ready="onReady"
      @on-click="(shape, e) => shape?.setActive(!shape.isActive(e), e)"
    />
  </a-space>
</template>

<style scoped lang="less"></style>

多边形渲染和操作

可用于绘制图像围栏等操作;

  • 以下是一个可拖拽顶点六边拖拽演示

TIP

本质上是将多个 Rectangle Line 对象添加到画布中,并通过 Polygon 对象进行集中管理;

点击展开源码
vue
<script setup lang="ts">
import { LedScreenCanvas, Matrix, Polygon, Rectangle } from "led-screen-canvas";
import { ref } from "vue";

const canvasRef = ref();

const onReady = () => {
  // 一个六边形
  const polygon = new Polygon(
    [
      [0, 0],
      [100, 0],
      [50, 87], // 60度角的顶点
      [-50, 87], // 120度角的顶点
      [-100, 0], // 180度角的顶点
      [-50, -87], // 240度角的顶点
    ],
    4,
    15
  );
  canvasRef.value.addShapes([polygon]);
};
</script>

<template>
  <a-space direction="vertical" style="width: 100%" wrap>
    <a-space>
      <a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
      <a-button type="primary" status="danger" @click="canvasRef.stop()"
        >暂停渲染(避免多个画布导致浏览器卡顿)</a-button
      >
      <a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
      <a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
    </a-space>
    <led-screen-canvas
      style="height: 400px; background-color: #dddddd"
      ref="canvasRef"
      @on-ready="onReady"
    />
  </a-space>
</template>

<style scoped lang="less"></style>

高级用法(自定义形状)

理论上可以实现任意形状的自定义绘制,只需继承 Shape 类,并实现 draw 方法即可;

  • 下面是自定义圆形DEMO

TIP

因为LED显示屏没有圆形布局,本插件默认没有圆形,但可以自行实现;

点击展开源码
vue
<script setup lang="ts">
import { BoundingBox, LedScreenCanvas, Matrix, Polygon, Rectangle, Shape } from "led-screen-canvas";
import { ref } from "vue";
import { ShapeType } from "led-screen-canvas/dist/led-screen-canvas/shapes";
import { LedScreenController } from "led-screen-canvas/dist/led-screen-canvas/controller";

const canvasRef = ref();

// 定义圆形对象
class Circle extends Shape {
  constructor(
    public x: number,
    public y: number,
    public radius: number,
    public borderWidth: number
  ) {
    super("circle" as ShapeType);
  }

  addToController(controller: LedScreenController): void {
    controller.shapes.push(this);
  }

  clearHover(): void {
    this.hover = false;
  }

  /**
   * 实现图形绘制接口
   */
  async draw(): Promise<boolean> {
    let { ctx, theme, scale } = this.controller;
    theme = this.getActiveTheme(theme);
    let colors = theme.default;
    if (this.active) colors = theme.active;
    else if (this.hover) colors = theme.hover;
    ctx.beginPath();
    const [cx, cy] = this.controller.cvs2win(this.x, this.y);
    const radius = this.radius * scale;
    ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
    ctx.fillStyle = colors.fillColor;
    ctx.strokeStyle = colors.borderColor;
    ctx.lineWidth = this.borderWidth * scale;
    ctx.fill();
    ctx.stroke();
    ctx.closePath();
    return true;
  }

  getBoundingBox(): BoundingBox {
    return BoundingBox.fromXywh(
      this.x - this.radius,
      this.y - this.radius,
      this.radius * 2,
      this.radius * 2
    );
  }

  isActive(e: MouseEvent): boolean {
    return this.active;
  }

  moveTo(x: number, y: number, start: number[], e: MouseEvent): void {
    this.x = x;
    this.y = y;
  }

  setActive(active: boolean, e: MouseEvent): void {
    this.active = active;
  }

  toHover(cvsX: number, cvsY: number): void {
    const [dx, dy] = [cvsX - this.x, cvsY - this.y];
    this.hover = dx * dx + dy * dy < this.radius * this.radius;
  }
}

const onReady = () => {
  const circle1 = new Circle(100, 100, 50, 5);
  circle1.canMove = true;
  const circle2 = new Circle(300, 100, 50, 5);
  circle2.canMove = true;
  canvasRef.value.addShapes([circle1, circle2]);
};
</script>

<template>
  <a-space direction="vertical" style="width: 100%" wrap>
    <a-space>
      <a-button type="primary" @click="canvasRef.start()">开始渲染</a-button>
      <a-button type="primary" status="danger" @click="canvasRef.stop()"
        >暂停渲染(避免多个画布导致浏览器卡顿)</a-button
      >
      <a-button @click="canvasRef.resetZoom()">缩放到最适合的大小</a-button>
      <a-button @click="canvasRef.zoom2RealSize()">缩放到原始尺寸</a-button>
    </a-space>
    <led-screen-canvas
      style="height: 400px; background-color: #dddddd"
      ref="canvasRef"
      @on-ready="onReady"
      @on-click="shape => (shape.active = !shape.active)"
      @on-shape-move-start="shape => shape.setZIndexTop()"
    />
  </a-space>
</template>

<style scoped lang="less"></style>

MIT Licensed | fangjc1986@qq.com