目录
理论
已知两点P1 P2,来推导直线算法
如:y=kx+b
但其中会产生浮点数,我们知道在像素级别的绘制中是不能出现浮点数的,因此我们需要想办法消灭浮点数。
先说结论:Brensenham直线算法就是不断判断下图中d1和d2的距离,取其中的最小值来判断要绘制哪一个像素。
下面进行一些数学的推导:
由图中:
又(其中产生了浮点数)
其中为了方便设
则
考察第一个点:
带入
得:,
由此可以判断它的正负,若为负,说明d1-d2<0,即d1<d2,说明下面的点近一些,则选择下面的点进行像素着色。反之则是上面的像素点。
通过可以迭代:
整体的思路,就是说通过P1这个点,来消除浮点数的出现,然后判断P1的正负,来选择下一个像素是上面的还是下面的像素(看上面的图)
总结:
初始:
当(也就是d1>d2,选择上面的像素点):
否则(也就是d1<d2,选择下面的像素点):
--------------------------------------------以上是在斜率k<1的情况下-------------------------------------------------
如果k>1,则将dx与dy进行交换,用y进行”步进“
实现:画一条线
namespace GT {
void GT::Canvas::drawLine(intV2 pt1, intV2 pt2, RGBA _color){
int disX = abs(pt2.x - pt1.x);
int disY = abs(pt2.y - pt1.y);
int xNow = pt1.x;
int yNow = pt2.y;
int stepX = 0;
int stepY = 0;
//判断两个方向步进的正负
stepX = pt1.x < pt2.x ? 1 : -1;
stepY = pt1.y < pt2.y ? 1 : -1;
//对比xy偏移量,决定步进方向选取x or y
int sumStep = disX;
bool useXStep = true;
if (disX < disY) {
sumStep = disY;
useXStep = false;
SWAP_INT(disX, disY);
}
//初始化P
int p = 2 * disY - disX;
for (int i = 0; i < sumStep; i++) {
drawPoint(xNow, yNow, _color);
if (p >= 0) {
if (useXStep) {
yNow += stepY;
}
else {
xNow += stepX;
}
p = p - 2 * disX;
}
//步进主坐标
if (useXStep) {
xNow += stepX;
}
else {
yNow += stepY;
}
p = p + 2 * disY;
}
}
}
测试:
void Render() {
_canvas->clear();
逐像素绘制
//for (int i = 0; i < wWidth; i++) {
// for (int j = 0; j < wHeight; j++) {
// GT::RGBA _color(rand() % 255, rand() % 255, rand() % 255);
// _canvas->drawPoint(i, j, _color);
// }
//}
_canvas->drawLine(GT::intV2(100, 100), GT::intV2(150, 150),GT::RGBA(255,0,0));
//在这里画到设备上,hMem相当于缓冲区
BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}
实现:画一圈线
void Render() {
_canvas->clear();
GT::RGBA _color(rand() % 255, rand() % 255, 0);
GT::intV2 pt1(100, 100);
float r = 50;
for (int i = 0; i < 360; i += 20) {
float radian = DEG2RAD(i);
int x = r * sin(radian) + pt1.x;
int y = r * cos(radian) + pt1.y;
GT::intV2 pt2(x, y);
_canvas->drawLine(pt1, pt2, _color);
}
//在这里画到设备上,hMem相当于缓冲区
BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}
实现——画一圈彩色线:
//=========画线算法Brensenhem===
void drawLine(Point pt1,Point pt2);
//=========线性插值Lerp=========
inline RGBA colorLerp(RGBA _color1, RGBA _color2, float _scale) {
RGBA _color;
_color.m_r = _color.m_r + (float)(_color2.m_r - _color1.m_r) * _scale;
_color.m_g = _color.m_g + (float)(_color2.m_g - _color1.m_g) * _scale;
_color.m_b = _color.m_b + (float)(_color2.m_b - _color1.m_b) * _scale;
_color.m_a = _color.m_a + (float)(_color2.m_a - _color1.m_a) * _scale;
return _color;
}
#include "Canvas.h"
#include <math.h>
#include "GTMATH.hpp"
namespace GT {
void GT::Canvas::drawLine(Point pt1, Point pt2) {
int disX = abs(pt2.m_x - pt1.m_x);
int disY = abs(pt2.m_y - pt1.m_y);
int xNow = pt1.m_x;
int yNow = pt1.m_y;
int stepX = 0;
int stepY = 0;
//判断两个方向步进的正负
stepX = pt1.m_x < pt2.m_x ? 1 : -1;
stepY = pt1.m_y < pt2.m_y ? 1 : -1;
//对比xy偏移量,决定步进方向选取x or y
int sumStep = disX;
bool useXStep = true;
if (disX < disY) {
sumStep = disY;
useXStep = false;
SWAP_INT(disX, disY);
}
//初始化P
int p = 2 * disY - disX;
for (int i = 0; i < sumStep; i++) {
RGBA _color;
float _scale = 0;
if (useXStep) {
_scale = (float)(xNow - pt1.m_x) / (float)(pt2.m_x - pt1.m_x);
}
else {
_scale = (float)(yNow - pt1.m_y) / (float)(pt2.m_y - pt1.m_y);
}
_color = colorLerp(pt1.m_color, pt2.m_color, _scale);
drawPoint(xNow, yNow, _color);
if (p >= 0) {
if (useXStep) {
yNow += stepY;
}
else {
xNow += stepX;
}
p = p - 2 * disX;
}
//步进主坐标
if (useXStep) {
xNow += stepX;
}
else {
yNow += stepY;
}
p = p + 2 * disY;
}
}
}
void Render() {
_canvas->clear();
GT::Point pt1(100, 100, GT::RGBA(255, 0, 0));
float r = 100;
for (int i = 0; i < 360; i += 20) {
float radian = DEG2RAD(i);
int x = r * sin(radian) + pt1.m_x;
int y = r * cos(radian) + pt1.m_y;
GT::Point pt2(x, y, GT::RGBA(0, 255, 0));
_canvas->drawLine(pt1, pt2);
}
//在这里画到设备上,hMem相当于缓冲区
BitBlt(hDC, 0, 0, wWidth, wHeight, hMem, 0, 0, SRCCOPY);
}