您的位置:首页 > Web前端 > HTML5

HTML5 Canvas 提高班(二) —— 光栅图形学(2)Bresenham算法画直线

2012-05-02 14:53 417 查看
// var circles = [];
var height, width, imageData;
var canvas, cxt, timerid, count = 10;
var linePi;
var fountainVy = -2;
var inter = -1;
var tick = 0;
//动画参数。
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}

if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};

if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());

function init() {
var div = document.getElementById("renderer");
div.innerHTML = "";
canvas = document.createElement("canvas");
canvas.style.width = "500px"
canvas.style.height = "500px"
canvas.width = 500;
canvas.height = 500;
width = 500;
height = 500;
linePi = width * 4;
div.appendChild(canvas);
cxt= canvas.getContext("2d");
cxt.clearRect(0, 0, width, height);
var data = cxt.getImageData(0, 0,width, height);
imageData = data.data;

var x,y,vx,vy,r;
var lines = [];
var circles = [];
animate();

function animate () {
tick ++;
if(tick % 30 == 0) {
fountainVy += inter * Math.random() * 3;
if(fountainVy < -10) {
inter = 1;
}
if(fountainVy > -2) {
inter = -1;
}
}
timerid = requestAnimationFrame(animate);
cxt.clearRect(0, 0, width, height);
data = cxt.getImageData(0, 0,width, height);
imageData = data.data;
drawLines();
}

function drawLines(){
//新的水柱
for(var i =0 ; i<30; i++) {
var x = Math.random() * 100 + 200;
var y = 500;
var vx = 0;
var vy = -Math.random() * 2 + fountainVy;
var life = Math.random() * 10 + 60;
lines.push({x: x, y: y, vx: vx, vy: vy, life: life});
}

//水柱衰减产生水滴。
for(var i =0 ; i 0 && line.vy < 0) {
Line_Bresenham(parseInt(line.x), parseInt(line.y), parseInt(line.x), parseInt(line.y)+10, 0);
}else {
//删除水柱。
lines[i] = lines[lines.length - 1];
lines.pop();
//产生水滴粒子。
for(var j = 0; j < 5; j++) {
var x = line.x;
var y = line.y;
var vx = Math.random() * 6 - 3;
var vy = line.vy;
var life = Math.random() * 60;
circles.push({x: x, y: y, vx: vx, vy: vy, life: life});
}
}
}

//粒子衰减
for(var i =0 ; i 0) {
drawC(parseInt(circle.x), parseInt(circle.y), 2, 0);
}else {
//删除粒子。
circles[i] = circles[circles.length - 1];
circles.pop();
}
}

cxt.putImageData(data, 0, 0);
}

// 中点画圆法
function drawC(x, y, r, color) {
var tx = 0, ty = r, d = 1 - r;

while(tx <= ty){
// 利用圆的八分对称性画点
putpixel(x + tx, y + ty, color);
putpixel(x + tx, y - ty, color);
putpixel(x - tx, y + ty, color);
putpixel(x - tx, y - ty, color);
putpixel(x + ty, y + tx, color);
putpixel(x + ty, y - tx, color);
putpixel(x - ty, y + tx, color);
putpixel(x - ty, y - tx, color);

if(d < 0){
d += 2 * tx + 3;
}else{
d += 2 * (tx - ty) + 5;
ty--;
}

tx++;
}
}

// 使用 Bresenham 算法画任意斜率的直线(包括起始点,不包括终止点)
function Line_Bresenham(x1, y1, x2, y2, color)
{
var x = x1;
var y = y1;
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var s1 = x2 > x1 ? 1 : -1;
var s2 = y2 > y1 ? 1 : -1;

var interchange = false; // 默认不互换 dx、dy
if (dy > dx) // 当斜率大于 1 时,dx、dy 互换
{
var temp = dx;
dx = dy;
dy = temp;
interchange = true;
}

var p = 2 * dy - dx;
for(var i = 0; i < dx; i++)
{
putpixel(x, y, color);
if (p >= 0)
{
if (!interchange) // 当斜率 < 1 时,选取上下象素点
y += s2;
else // 当斜率 > 1 时,选取左右象素点
x += s1;
p = p + 2 * dy - 2 * dx;
}
if (!interchange)
x += s1; // 当斜率 < 1 时,选取 x 为步长
else
y += s2; // 当斜率 > 1 时,选取 y 为步长
p += 2 * dy;
}
}

function putpixel(x, y, color){
if(x < 0 || y < 0 || x >= width || y >=width) {
return;
}
var index = getStartIndex(x, y);
for(var i = 0; i< 4; i++) {
if(i == 3) {
imageData[index + i] = 255;
}
else{
// var co = parseInt(Math.random() * 255)
imageData[index + i] = color;
}
}
}

function getStartIndex(x, y) {
return y * width * 4 + x * 4;
}

}

function stop() {
cancelAnimationFrame(timerid);
}
// ]]>
上次的随笔介绍了如何用中点画圆的算法提高Canvas绘图性能,感觉大家还是比较感兴趣的。

本节借助HTML5 canvas 强大的像素处理能力,重点给大家介绍计算机图形中-光栅学Bresenham算法;并实现两点画直线的程序。

光栅图形学(2)Bresenham算法画直线

Bresenham算法是计算机图形学典型的直线光栅化算法,其历史可以追溯到上个世界,由 Jack E. Bresenham 1962年在IBM工作时提出。算法的历史这里就不做更多的介绍了,有兴趣的同学可以看这个wikipedia的链接,里面有很详细的介绍,我们现在就关注其实现的理论。


1.Bresenham 算法的实现原理

用通俗的方法解释Bresenham 算法:由于计算机屏幕的特殊性,像素不可能有小数的显示,比如:我们无法显示0.5像素在屏幕上。这样我们可以假设一条线段的斜率为k,当这条直线在X方向增加(或减少)1个像素的同时;其Y方向只可能增加(或减少)1或是0,它取决于实际直线与最近光栅网格点的距离,这个距离的最大误差为0.5。有了感性的认识,下面我们在数学的世界中计算这个Y方向的改变量。

Bresenham 算法的数学实施:(为了能理解下面的内容可能需要一些基本的数学概念,如果已经忘了也没有关系我会尽可能简单的说明)

1。假设空间中有两点P1和P2,则我们可以求得斜率k,假设0<k<1,这样我们只需要考虑x方向每次递增1,y的方向每次是递增1或是0。

2。设: 直线方程为:y = kx + b。

直线当前点为(xi, y) —— 此点是未经过离散后的点。

直线当前光栅点为(xi, yi) —— 此点是经过离散后的点。

则: 下一个直线的点应为(xi+1, k(xi + 1) + b),

由于光栅点只可能位于yi,或yi+1的位置,我们用变量 d_upper 表示真实点的坐标与(yi + 1)离散点坐标的距离:d_upper = (yi + 1) - (k(xi + 1) + b);用d_lower 表示真实点的坐标与(yi)离散度坐标的距离:d_lower = (k(xi + 1) + b) - yi。

     这样一来我们就可以明确该如何选择点:如果d_upper > d_lower,则表示真实点距yi的位置更近我就选择yi为下一离散点的y坐标;反之选择yi + 1。

所以我们程序中需要求: d_lower - d_upper = 2k(xi + 1) - 2yi + 2b -1(化简后的结果,如果大家不相信可以自己算算哈)。(1)

设:p1,p2之间x方向的距离为 dx,y方向的距离为dy,则斜率k = dy / dx;

我们将所求的的算式(1)乘以系数dx,命名为变量 Pi(第i步的决策参数) = dx(d_lower - d_upper) = 2*dy*xi - 2*dx*yi + c 。(2)(c=2*dy + dx(2b - 1),c为一常数在程序计算时会省去)。

通过算式(2)我们可以得出Pi+1(第i + 1时的决策参数) = 2 * dy * Xi+1 - 2*dx * Yi+1 + c (3)

将算式(3)与算式(2)做差并化简后得 :


(4)(呼呼,终于推倒出我们要的算式了,不知道大家看懂推导过程了没,其实都很简单就是步骤多了些)

可以通过算式(2)得出p0 = 2 dy - dx。

之后我们就递归的调用计算决策参数的算法就可以啦,通过互换x、y的位置和坐标的位置可将其算法扩展到任意象限。

2.Bresenham的程序实现

// 使用 Bresenham 算法画任意斜率的直线(包括起始点,不包括终止点)
function Line_Bresenham(x1, y1, x2, y2, color)
{
var  x = x1;
var y = y1;
var dx = Math.abs(x2 - x1);
var dy = Math.abs(y2 - y1);
var s1 = x2 > x1 ? 1 : -1;
var s2 = y2 > y1 ? 1 : -1;

var interchange = false;    // 默认不互换 dx、dy
if (dy > dx)                // 当斜率大于 1 时,dx、dy 互换
{
var temp = dx;
dx = dy;
dy = temp;
interchange = true;
}

var p = 2 * dy - dx;
for(var i = 0; i < dx; i++)
{
putpixel(x, y, color);
if (p >= 0)
{
if (!interchange)        // 当斜率 < 1 时,选取上下象素点
y += s2;
else                    // 当斜率 > 1 时,选取左右象素点
x += s1;
p = p + 2 * dy - 2 * dx;
}
if (!interchange)
x += s1;                // 当斜率 < 1 时,选取 x 为步长
else
y += s2;                // 当斜率 > 1 时,选取 y 为步长
p += 2 * dy;
}
}


这段代码考虑到任意斜率的情况,大家可以调调看看对不同斜率的直线是如何做处理的。

下面这段简单的demo是对这两次随笔的一个综合应用(当然还很不完善,大家领会意思就好):

开始 停止

下次随笔预告:1.多边形绘制。

2.填充色。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: