前军教程网

中小站长与DIV+CSS网页布局开发技术人员的首选CSS学习平台

绘制不同线型直线段方法

在Bresenham画线算法及实践一文中介绍了如何绘制直线段,在诸如图表等可视化场景中,往往需要使用各类不同的线型,如虚线、点划线等,本文介绍一种实现绘制这类线型的方法,并使用TypeScript语言在HTML canvas画布中进行绘制。
这里借鉴OpenGL中的定义的线型函数glLineStripple(repeatFactor, pattern),其中pattern使用16bit位来定义线型属性,其中1表示对象像素打开,0表示像素关闭。参见下图,如pattern设置为0x1C47,bit为1则设置线的颜色值,为0则设置为背景色,重复这个模式就形成了点划线;同样地,0x00FF就表示了虚线、0xFFFF表示实线。另外一个参数repeatFactor为整数参数,表示每一个bit重复多少次才轮到下一个bit,这个参数可以调节线型疏密程度。

在原有的Bresenham画线算法的基础上,只需要在绘制每一个像素之前,先根据线型函数配置的参数确定该像素是否是打开状态,如打开则绘制像素为线的颜色值,关闭就绘制像素为背景颜色即可。判断像素是否打开,可以通过pattern循环与0x0001~0x8000进行逻辑与操作,结果不为0则表示像素是打开状态,以0x1C47为例,逻辑与操作的结果参见下图。

在下面的程序中,通过绘制点线、虚线、点划线、实线来展示了线属性函数的使用,生成的数据图见下图。

var canvas = <HTMLCanvasElement>document.getElementById("canvas001");
var webgl = new WebGL(canvas);
webgl.glClearColor(1, 1, 1, 1);
webgl.glBgColor(1, 1, 1);
webgl.glColor(0, 0, 0);

function linePlot(dataPts: Array<any>) {
    for (let k = 0; k < dataPts.length; k++) {
        webgl.glVertex2(dataPts[k][0], dataPts[k][1]);
    }
    webgl.drawArrays(GL_LINES_STRIP);
}

// 点划线
let data_points = [[0, 50], [230, 120], [440, 320], [660, 260], [900, 50]];
webgl.glLineStipple(3, 0x1C47);
linePlot(data_points);
// 实线
data_points = [[0, 10], [230, 80], [440, 280], [660, 220], [900, 10]];
webgl.glLineStipple(1, 0xFFFF);
linePlot(data_points);
// 虚线
data_points = [[0, 100], [220, 290], [430, 280], [710, 500], [900, 420]];
webgl.glLineStipple(1, 0x00FF);
linePlot(data_points);
// 点线
data_points = [[0, 200], [250, 240], [440, 390], [700, 300], [900, 520]];
webgl.glLineStipple(2, 0x0101);
linePlot(data_points);

完整参考代码如下:
app.ts

// 常量
const GL_POINTS = 0;
const GL_LINES = 1;
const GL_LINES_STRIP = 2;
const GL_LINES_LOOP = 3;
const GL_LINE_STIPPLE = 4;

class Vector2 {
    x: number;
    y: number;
    constructor(x: number, y: number) {
        this.x = x;
        this.y = y;
    }
}

class Color {
    r: number;
    g: number;
    b: number;
    a: number;
    constructor(r: number, g: number, b:number, a: number = 1) {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
    }
}

class WebGL {
    canvas: HTMLCanvasElement;
    ctx: CanvasRenderingContext2D;
    vertices: Array<Vector2>;
    attr_color: Color;
    attr_bg_color: Color;
    attr_repeatFactor: number;
    attr_pattern: number;
    attr_repeatFactor_index: number;
    attr_pattern_mask: number;

    // 构造函数
    constructor(canvas: HTMLCanvasElement) {
        this.canvas = canvas;
        this.ctx = canvas.getContext("2d");    
        this.vertices = [];
        this.attr_bg_color = new Color(1, 1, 1);
        this.attr_color = new Color(0, 0, 0);
        this.attr_repeatFactor = 1;
        this.attr_pattern = 0xFFFF;
        this.attr_pattern_mask = 0x0001;
        this.attr_repeatFactor_index = 0;
    }
    // 将canvas坐标系转换为常用的y轴向上的坐标系
    transToScreenXY(x: number, y: number) {
        return [x, this.canvas.height - y];
    }

    setFillStyle(r: number, g: number, b: number, a: number = 1) {
        this.ctx.fillStyle = "rgba(" + r*255 +  "," +  g*255 + "," +  b*255 + "," + a + ")";
    }

    glEnd() {
        this.vertices = [];
        this.attr_pattern_mask = 0x0001;
        this.attr_repeatFactor_index = 0;
    }

    // 设置颜色值 
    glColor(r: number, g: number, b: number, a: number = 1) {
        this.setFillStyle(r, g, b, a);
        this.attr_color = new Color(r, g, b, a);
    }

    // 设置背景色
    glBgColor(r: number, g: number, b: number, a: number = 1) {
        this.setFillStyle(r, g, b, a);
        this.attr_bg_color = new Color(r, g, b, a);
    }

    // 填充1x1像素的矩形(即一个像素大小)来实现单个像素颜色设置
    setPixel(x: number, y: number) {
        let [sx, sy] = this.transToScreenXY(x, y);
        this.ctx.fillRect(sx, sy, 1, 1);
    }

    // 填充矩形区域
    fillRect(x0: number, y0: number, x1: number, y1: number) {
        let [min_x, max_x] = [Math.min(x0, x1), Math.max(x0, x1)];
        let [min_y, max_y] = [Math.min(y0, y1), Math.max(y0, y1)];
        for (let i = min_x; i < max_x; i++) {
            for (let j = min_y; j < max_y; j++) {
                this.setPixel(i, j);
            }
        }
    }

    // rgba指定的颜色清空整个canvas
    glClearColor(r: number, g: number, b: number, a:number) {
        this.setFillStyle(r, g, b, a);
        this.ctx.fillRect(0, 0, this.canvas.clientWidth, this.canvas.clientHeight);
    }

    // 添加顶点坐标
    glVertex2(x: number, y: number) {
        this.vertices.push(new Vector2(x, y));
    }

    /* 线型设置
    repeatFactor: 每一位重复多少次再继续下一位
    pattern:  默认值为0xFFFF每一位均为1,表示绘制的是实现,为0的位绘制背景色
    */
    glLineStipple(repeatFactor: number, pattern: number = 0xFFFF) {
        this.attr_repeatFactor = repeatFactor;
        this.attr_pattern = pattern;        
    }

    // 获取像素的开关状态, true-表示像素打开、false-表示像素关闭
    glPixelSwitchStatus() {
        // 计算pattern与运算
        let logic_and = this.attr_pattern & this.attr_pattern_mask;
        // 每bit重复次数repeatFactor处理
        this.attr_repeatFactor_index = (this.attr_repeatFactor_index + 1) % this.attr_repeatFactor;
        if (this.attr_repeatFactor_index == 0) {
            // 如已完成重复bit次数,则参与与运算掩码左移一位
            this.attr_pattern_mask *= 2;
        }
        // 掩码到达最大值后重新开始
        if (this.attr_pattern_mask > 0x8000) {
            this.attr_pattern_mask = 0x0001;
        }        

        if (logic_and) {
            return true;
        } else {
            return false;
        }        
    }

    setPoint(x: number, y: number) {
        let r, g, b;
        if (this.glPixelSwitchStatus()) {
            [r, g, b] = [this.attr_color.r, this.attr_color.g, this.attr_color.b];
        } else {
            [r, g, b] = [this.attr_bg_color.r, this.attr_bg_color.g, this.attr_bg_color.b];
        }
        this.setFillStyle(r, g, b);
        this.setPixel(x, y);
    }

    // Bresenham画线算法
    lineBres(p0_x: number, p0_y: number, p1_x: number, p1_y: number) {
        let [int_p0_x, int_p0_y] = [Math.round(p0_x), Math.round(p0_y)];
        let [int_p1_x, int_p1_y] = [Math.round(p1_x), Math.round(p1_y)];
        let x, y;
        // 确定x0 < xEnd
        if (int_p0_x > int_p1_x) {
            x = int_p1_x;
            y = int_p1_y;
            int_p1_x = int_p0_x;
            int_p1_y = int_p0_y; 
        } else {
            x = int_p0_x;
            y = int_p0_y;
        }
        let dx = Math.abs(int_p1_x - x);
        let dy = Math.abs(int_p1_y - y);
        let p = 2 * dy - dx;
        let twoDy = 2 * dy;
        let twoDyminusDx = 2 * (dy - dx);
        let twoDx = 2 * dx;
        // 计算斜率
        let m = dy / dx;
        if (int_p1_y < y) {
            m = -m;
        }
        let real_line_size;
        // 斜率(0,1]
        if (m > 0 && m <= 1) {
            while (x < int_p1_x) {
                this.setPoint(x, y);
                x += 1;
                if (p < 0) {
                    p += twoDy;
                } else {
                    y += 1;
                    p +=  twoDyminusDx;
                }                
            }
        }

        // 斜率(1,+无穷)
        if (m > 1) {
            while (y < int_p1_y) {
                this.setPoint(x, y);
                y += 1;
                if (p < 0) {
                    p += twoDx;
                } else {
                    x += 1;
                    p -=  twoDyminusDx;
                }
            }
        }
        // 斜率(-1,0]
        if (m > -1 && m <= 0) {
            while (x < int_p1_x) {
                this.setPoint(x, y);
                x += 1;
                if (p < 0) {
                    p += twoDy;
                } else {
                    y -= 1;
                    p +=  twoDyminusDx;
                }
            }
        }
        // 斜率(-无穷,-1]
        if (m <= -1) {
            while (y > int_p1_y) {
                this.setPoint(x, y);
                y -= 1;
                if (p < 0) {
                    p += twoDx;
                } else {
                    x += 1;
                    p -=  twoDyminusDx;
                }
            }
        }
    }

    drawArrays(mode: number = GL_POINTS) {
        let r, g, b;
        if (mode == GL_LINES_STRIP) {
            for (let i = 0; i < this.vertices.length - 1; i++) {
                let p0 = this.vertices[i];
                let p1 = this.vertices[i+1];
                this.lineBres(p0.x, p0.y, p1.x, p1.y);
            }
        }
        this.glEnd();
    }
}

var canvas = <HTMLCanvasElement>document.getElementById("canvas001");
var webgl = new WebGL(canvas);
webgl.glClearColor(1, 1, 1, 1);
webgl.glBgColor(1, 1, 1);
webgl.glColor(0, 0, 0);

function linePlot(dataPts: Array<any>) {
    for (let k = 0; k < dataPts.length; k++) {
        webgl.glVertex2(dataPts[k][0], dataPts[k][1]);
    }
    webgl.drawArrays(GL_LINES_STRIP);
}

// 点划线
let data_points = [[0, 50], [230, 120], [440, 320], [660, 260], [900, 50]];
webgl.glLineStipple(3, 0x1C47);
linePlot(data_points);
// 实线
data_points = [[0, 10], [230, 80], [440, 280], [660, 220], [900, 10]];
webgl.glLineStipple(1, 0xFFFF);
linePlot(data_points);
// 虚线
data_points = [[0, 100], [220, 290], [430, 280], [710, 500], [900, 420]];
webgl.glLineStipple(1, 0x00FF);
linePlot(data_points);
// 点线
data_points = [[0, 200], [250, 240], [440, 390], [700, 300], [900, 520]];
webgl.glLineStipple(2, 0x0101);
linePlot(data_points);

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>线的属性</title>
</head>
<body>
    <canvas id="canvas001" width="900" height="600"></canvas>

    <script src="./dist/app.js"></script>
</body>
</html>

参考文献

[1]. 《计算机图形学》(第三版)Donald Hearn、M.PaulineBaker著,4.8.2 线型函数,P154页;
[2]. Bresenham画线算法及实践

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言