橡皮筋算法
程序员,可以选择研究技术,也可以选择赚钱。但是以赚钱的心态去做程序员,会品尝不到技术的很多快乐。
1.橡皮筋算法原理
橡皮筋算法,指的是什么,我们都应该很清楚了。以直线为例,就是直线的一端固定,拉着直线的另一个端点,不断调整直线的位置,直到找到合适的位置后,直线才真正的画出来,前面的线不保留。
按照我们一般的逻辑思维,就是不断的擦除刚画过的线,只保留最后的一根直线。但在计算机绘图中,没有擦除的方法,而是用反色覆盖掉原来的图像。也就是不断的用反色重画前面画出的线。
2. 橡皮筋算法实现
unitFrmElastic;
interface
uses
Winapi.Windows, Winapi.Messages,System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs,Vcl.StdCtrls;
type
TFormElastic = class(TForm)
procedure FormMouseDown(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure FormMouseMove(Sender: TObject;Shift: TShiftState; X, Y: Integer);
procedure FormMouseUp(Sender: TObject;Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
ptOrigin: TPoint; //原始点,用于记录鼠标按下时的点坐标
ptTarget: TPoint; //目标点,用于记录鼠标移动或弹起时的点坐标
public
{ Public declarations }
end;
var
FormElastic: TFormElastic;
implementation
{$R*.dfm}
//画线(橡皮筋算法)
procedureDrawElasticLine(Canvas: TCanvas; ptBegin, ptOldEnd, ptNewEnd: TPoint);
begin
//画笔模式,适用pmNotXor来画橡皮筋,因为两次取反可以还原背景色。
Canvas.Pen.Mode := pmNotXor;
Canvas.pen.Style := psSolid; //直线画笔
Canvas.Pen.Color := clBlack; //黑色画笔
//画刷模式
Canvas.Brush.style := bsClear; //画刷不填充
//画旧线(相当于擦除),此时旧线所在位置的背景色被线的颜色覆盖
Canvas.MoveTo(ptBegin.X, ptBegin.Y);
Canvas.LineTo(ptOldEnd.X, ptOldEnd.Y);
//画新线,此时新线所在位置的背景色还是原来的背景色
Canvas.MoveTo(ptBegin.X, ptBegin.Y);
Canvas.LineTo(ptNewEnd.X, ptNewEnd.Y);
end;
procedureTFormElastic.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
inherited;
//记录鼠标按下时的原始点坐标
ptOrigin.x := X;
ptOrigin.y := Y;
//初始化目标点坐标
ptTarget := ptOrigin;
end;
procedureTFormElastic.FormMouseMove(Sender: TObject; Shift: TShiftState; X,
Y: Integer);
var
ptNewEnd: TPoint;
begin
inherited;
//如果是在鼠标左键按下的同时,进行鼠标移动操作,则进行画线操作
if ssLeft in Shift then
begin
//记录新的目标点坐标
ptNewEnd.X := X;
ptNewEnd.Y := Y;
//画线(橡皮筋算法)
DrawElasticLine(Canvas, ptOrigin, ptTarget,ptNewEnd);
//记录新的目标点坐标
ptTarget := ptNewEnd;
end;
end;
procedureTFormElastic.FormMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
ptNewEnd: TPoint;
begin
inherited;
//记录新的目标点坐标
ptNewEnd.X := X;
ptNewEnd.Y := Y;
//画线(橡皮筋算法)
DrawElasticLine(Canvas, ptOrigin, ptTarget,ptNewEnd);
end;
end.
3.研究一下画笔对象(TPen)
知其然,还要知其所以然,编程不只是为了实现,更应该是一种兴趣,一种爱好。
Windows中定义的绘图方式也影响显示器上所画线的外观。设想画这样一条直线,它的色彩由画笔色彩和画线区域原来的色彩共同决定。设想用同一种画笔在白色表面上画出黑线而在黑色表面上画出白线,而且不用知道表面是什么色彩。这样的功能对您有用吗?通过绘图方式的设定,这些都可以实作。当Windows使用画笔来画线时,它实际上执行画笔图素与目标位置处原来图素之间的某种位元布尔运算。
使用 TPen 类可以描述Windows 的笔(Pen)属性,应用程序常用TPen对象在画布Canvas上绘制各种图形。所有和线条有关的绘图函数都会受TPen影响,如LineTo、Ellipse、Polygon、PolyLine、Rectangle等函数。可以利用Canvas.Pen来读写Pen,借此修改Pen的属性,画笔的颜色在Color 属性中定义,线段宽度在Width 属性中定义,类型和模式则分别在Style和Mode 属性中定义。
Color 属性控制线的颜色,如clBlack(黑色)、clBlue(蓝色)和clGreen(绿色)等。
Style 属性确定线的式样,如psSolid(实线)和psDash(短线)等,详见下表。
取值 | 含义 |
pSolid | 画实线段 |
pSDash | 画由下划线组成的线段 |
pSDot | 画由点组成的线段 |
psDashDot | 画点划线 |
psDashDotDot | 画双点划线 |
psClear | 画看不见的线段 |
psInsideFrame | 画边界的矩形线框 |
Mode用来确定画笔与屏幕上原有点的颜色混合方式。可结合当前的颜色、屏幕的颜色或它们的反转值,对线段的颜色重新定义。但不改变Color属性属性,详见下表。
取值 | 含义 |
pmBlack | 像素始终为黑色 |
pmWhite | 像素始终为白色 |
pmNop | 像素保持不变 |
pmNot | 像素为屏幕颜色的反色(这样可以覆盖掉上次的绘图,自动擦除上次绘制的图形) |
pmCopy | 像素为笔的颜色(即Color 属性中的颜色) |
pmNotCopy | 像素为笔颜色的反色 |
下面是当前画笔的颜色和屏幕色的组合运算得到的绘图模式 | |
pmMergePenNot | pmCopy和pmNot的并集(即像素为笔颜色或屏幕颜色反色) |
pmMaskPenNot | pmCopy和pmNot的交集(即像素为笔颜色与屏幕颜色反色) |
pmMergeNotPen | pmNotCopy和屏幕像素值的并集(即像素为笔颜色反色或屏幕颜色) |
pmMaskNotPen | pmNotCopy和屏幕像素值的并集(即像素为笔颜色反色与屏幕颜色) |
pmMerge | pmCopy和屏幕像素值的并集(即像素为笔颜色或屏幕颜色) |
pmNotMerge | pmMerge的反色 |
pmMask | pmCopy和屏幕像素值的交集(即像素为笔颜色与屏幕颜色) |
pmNotMask | pmMask的反色 |
pmXor | pmCopy和屏幕像素值的异或(像素为笔颜色异或屏幕颜色,连续异或两次会变为原来颜色) |
pmNotXor | pmXor的反色 |
4.再研究一下画线的橡皮筋算法
已知条件:
假定画布的颜色为a,画笔的颜色为b,画出来的直线颜色为c
如果a=b,则相当于用a色的画笔在a色的画布上画东西,是什么结果也看不出来的。
1).在画布上画出一条c=黑色的直线,而不管画布是什么颜色a。
分析:
如果画布的颜色a=黑色,那么画出来的c=黑色的直线是看不见的。
如果画布的颜色a<>黑色,那么可以有两种选择:
1.1.设置Mode= pmBlack,此时不管画笔b是什么颜色,画出来的线都是c=黑色的;
1.2.设置Mode=pmCopy,并设置画笔的颜色为b=黑色,此时,不管画布的颜色是什么,画出来的直线也都是c=黑色的。
总结:
此时最适合使用Mode= pmBlack颜色混合模式,不去管画布颜色和画笔颜色,画出来的直线都是c=黑色。效率也是最高的。
2).擦掉第一个问题中画出来的c=黑色的直线
分析:
想擦掉这条直线,我们可以使用画布的颜色,再重新画一遍这条直线。即让画笔的颜色b=a,此时如果已知了画布的颜色a,就很简单了。但我们不知道画布的颜色。但是我们知道以下关系
2.1.c=a^b(用b色画笔在a色的画布上画出了c的一条直线,此时直线位置的画布颜色变成了a=c)
2.2.c^b=(a^b)^b=a^(b^b)=a^1=a(用b色的画笔在c色的画布上画出了c=a色的直线)
总结:
不管画笔的颜色b是什么,经过和画布颜色a的两次亦或运算后,就得到了原始的画布颜色a,所以此时适合于使用Mode= pmXor颜色混合模式。
但存在一个问题,如果a^b以后的结果c1,凑巧c1=a,那么将永远不能画出直线来。如果c1颜色和背景色相反,我们知道一定能画出直线来。所以我们为了既能画出直线来,又能擦除直线,最适合使用Mode= pmNotXor颜色混合模式。最常用的场景是在白色的背景上画各种颜色的图。此时,我们再实现这个算法
//画线擦除线
procedureDrawEraseLine(Canvas: TCanvas; ptBegin, ptEnd: TPoint);
begin
//画笔模式,适用pmNotXor,因为两次取反可以还原背景色。
Canvas.Pen.Mode := pmNotXor;
Canvas.pen.Style := psSolid; //直线画笔
Canvas.Pen.Color := clBlack; //黑色画笔
//画刷模式
Canvas.Brush.style := bsClear; //画刷不填充
//画线
Canvas.MoveTo(ptBegin.X, ptBegin.Y);
Canvas.LineTo(ptEnd.X, ptEnd.Y);
//擦线(此时旧线所在位置的背景色被线的颜色覆盖,用该颜色亦或画笔的颜色)
Canvas.MoveTo(ptBegin.X, ptBegin.Y);
Canvas.LineTo(ptEnd.X, ptEnd.Y);
end;