Drawing binary trees

10 Feb 2009
Posted by Michael
Binary tree screenshot

This project started with my (by the way pretty awesome) math teacher showing me his own program to draw binary trees (in VB). He challenged me to do it better! Of course, I took up the challenge and started coding right away.

While his program still draws the trees faster, my program has a lot more features and settings to play with. I did think about doing the drawing in DirectDraw, which would have been very quick, but it would take too much time to figure out how to do that. I just draw the tree on a regular tbitmap – with or without updating the screen.

Recursive functions
The program uses a recursive function – actually, it uses two, but only for the cause of speed. One for drawing the tree with “Update screen and abort with ESC” enabled and one for the same, just disabled.

Branch colors
The only thing not completely working in this program is the color code. The function for calculating the branch color will sometimes output a weird color. That piece of code needs some love.

Animation
The program has a tab for creating frames for an animation. I just draws a batch of images, and saves each of them in a folder as BMP. Make sure the folder exists (there is no code for checking that)!.
I have included 2 videos - see them under attached files.

  1. unit Unit1;
  2.  
  3. interface
  4.  
  5. uses
  6. Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  7. Dialogs, ExtCtrls, StdCtrls, Spin, Math, XPMan, ComCtrls, ExtDlgs, FileCtrl;
  8.  
  9. type
  10. TForm1 = class(TForm)
  11. Button1: TButton;
  12. XPManifest1: TXPManifest;
  13. ColorDialog1: TColorDialog;
  14. Bevel1: TBevel;
  15. PageControl1: TPageControl;
  16. TabSheet1: TTabSheet;
  17. TabSheet2: TTabSheet;
  18. SpinEdit1: TSpinEdit;
  19. Label1: TLabel;
  20. Label2: TLabel;
  21. SpinEdit2: TSpinEdit;
  22. Label3: TLabel;
  23. Edit1: TEdit;
  24. Label4: TLabel;
  25. SpinEdit3: TSpinEdit;
  26. CheckBox2: TCheckBox;
  27. Label5: TLabel;
  28. Label6: TLabel;
  29. Shape1: TShape;
  30. Shape2: TShape;
  31. Label7: TLabel;
  32. Shape3: TShape;
  33. RadioButton1: TRadioButton;
  34. RadioButton2: TRadioButton;
  35. Label8: TLabel;
  36. Label9: TLabel;
  37. CheckBox1: TCheckBox;
  38. TabSheet3: TTabSheet;
  39. Image1: TImage;
  40. Button2: TButton;
  41. SaveDialog1: TSaveDialog;
  42. RadioButton3: TRadioButton;
  43. RadioButton4: TRadioButton;
  44. Label10: TLabel;
  45. SpinEdit4: TSpinEdit;
  46. SpinEdit5: TSpinEdit;
  47. Label11: TLabel;
  48. SpinEdit6: TSpinEdit;
  49. SpinEdit7: TSpinEdit;
  50. Label12: TLabel;
  51. Label13: TLabel;
  52. Edit2: TEdit;
  53. Button3: TButton;
  54. Button4: TButton;
  55. Label14: TLabel;
  56. procedure FormCreate(Sender: TObject);
  57. procedure Button4Click(Sender: TObject);
  58. procedure Button3Click(Sender: TObject);
  59. procedure Button2Click(Sender: TObject);
  60. procedure RadioButton1Click(Sender: TObject);
  61. procedure RadioButton2Click(Sender: TObject);
  62. procedure Shape3MouseUp(Sender: TObject; Button: TMouseButton;
  63. Shift: TShiftState; X, Y: Integer);
  64. procedure Shape2MouseUp(Sender: TObject; Button: TMouseButton;
  65. Shift: TShiftState; X, Y: Integer);
  66. procedure Shape1MouseUp(Sender: TObject; Button: TMouseButton;
  67. Shift: TShiftState; X, Y: Integer);
  68. procedure Button1Click(Sender: TObject);
  69. private
  70. { Private declarations }
  71. public
  72. { Public declarations }
  73. end;
  74.  
  75. var
  76. Form1: TForm1;
  77.  
  78. implementation
  79.  
  80. {$R *.dfm}
  81.  
  82. // Calculate the color of a specific branch
  83. Function GetColor(FromColor, ToColor:TColor; TotalColors, ReturnColorNumber:Integer): TColor;
  84. var
  85. FR, FG, FB, TR, TG, TB: byte;
  86. ReturnR, ReturnG, ReturnB: Byte;
  87. begin
  88. if ReturnColorNumber = 1 then // No reason to calculate the "FromColor"
  89. Result := FromColor
  90. else
  91. if ReturnColorNumber = TotalColors then // No reason to calculate the "ToColor"
  92. Result := ToColor
  93. else
  94. begin // If the branch is not 1 or the last one:
  95.  
  96. FR := GetRValue(FromColor); // Take the red value
  97. FG := GetGValue(FromColor); // Take the green value
  98. FB := GetBValue(FromColor); // etc.
  99. TR := GetRValue(ToColor);
  100. TG := GetGValue(ToColor);
  101. TB := GetBValue(ToColor);
  102.  
  103. // Calculate the color - it's not fully working!
  104. ReturnR := round(((TR-FR)/(TotalColors-1))*(ReturnColorNumber-1) +TR);
  105. ReturnG := round(((TG-FG)/(TotalColors-1))*(ReturnColorNumber-1) +TG);
  106. ReturnB := round(((TB-FB)/(TotalColors-1))*(ReturnColorNumber-1) +TB);
  107.  
  108. Result := RGB(ReturnR, ReturnG, ReturnB); // Convert to a color
  109. end;
  110. end;
  111.  
  112. // Disable controls while drawing the tree
  113. procedure DisableControls(disable: boolean);
  114. begin
  115. if disable = true then
  116. with Form1 do
  117. begin
  118. SpinEdit1.Enabled := False;
  119. SpinEdit2.Enabled := False;
  120. SpinEdit3.Enabled := False;
  121. SpinEdit4.Enabled := False;
  122. SpinEdit5.Enabled := False;
  123. SpinEdit6.Enabled := False;
  124. SpinEdit7.Enabled := False;
  125. Edit1.Enabled := False;
  126. Edit2.Enabled := False;
  127. CheckBox2.Enabled := False;
  128. CheckBox1.Enabled := False;
  129. RadioButton1.Enabled := False;
  130. RadioButton2.Enabled := False;
  131. RadioButton3.Enabled := False;
  132. RadioButton4.Enabled := False;
  133. Shape1.Enabled := False;
  134. Shape2.Enabled := False;
  135. Shape3.Enabled := False;
  136. Button1.Enabled := False;
  137. Button2.Enabled := False;
  138. Button3.Enabled := False;
  139. Button4.Enabled := False;
  140. end
  141. else
  142. with Form1 do
  143. begin
  144. SpinEdit1.Enabled := True;
  145. SpinEdit2.Enabled := True;
  146. SpinEdit3.Enabled := True;
  147. SpinEdit4.Enabled := True;
  148. SpinEdit5.Enabled := True;
  149. SpinEdit6.Enabled := True;
  150. SpinEdit7.Enabled := True;
  151. Edit1.Enabled := True;
  152. Edit2.Enabled := True;
  153. CheckBox2.Enabled := True;
  154. if RadioButton1.Checked then
  155. CheckBox1.Enabled := True;
  156. RadioButton1.Enabled := True;
  157. RadioButton2.Enabled := True;
  158. RadioButton3.Enabled := True;
  159. RadioButton4.Enabled := True;
  160. Shape1.Enabled := True;
  161. Shape2.Enabled := True;
  162. Shape3.Enabled := True;
  163. Button1.Enabled := True;
  164. Button2.Enabled := True;
  165. Button3.Enabled := True;
  166. Button4.Enabled := True;
  167. end;
  168. end;
  169.  
  170.  
  171. // To draw an antialiased line.
  172. // Thanks to http://www.delphi3000.com/articles/article_1566.asp
  173. procedure AALine(x1,y1,x2,y2 : single; color : tcolor; canvas : tcanvas);
  174. function CrossFadeColor(FromColor,ToColor : TColor; Rate : Single) : TColor;
  175. var r,g,b : byte;
  176. begin
  177. r:=Round(GetRValue(FromColor)*Rate+GetRValue(ToColor)*(1-Rate));
  178. g:=Round(GetGValue(FromColor)*Rate+GetGValue(ToColor)*(1-Rate));
  179. b:=Round(GetBValue(FromColor)*Rate+GetBValue(ToColor)*(1-Rate));
  180. Result:=RGB(r,g,b);
  181. end;
  182. procedure hpixel(x : single; y : integer);
  183. var FadeRate : single;
  184. begin
  185. FadeRate:=x-trunc(x);
  186. with canvas do
  187. begin
  188. pixels[trunc(x),y]:=CrossFadeColor(Color,Pixels[Trunc(x),y],1-FadeRate);
  189. pixels[trunc(x)+1,y]:=CrossFadeColor(Color,Pixels[Trunc(x)+1,y],FadeRate);
  190. end;
  191. end;
  192.  
  193. procedure vpixel(x : integer; y : single);
  194. var FadeRate : single;
  195. begin
  196. FadeRate:=y-trunc(y);
  197. with canvas do
  198. begin
  199. pixels[x,trunc(y)]:=CrossFadeColor(Color,Pixels[x,Trunc(y)],1-FadeRate);
  200. pixels[x,trunc(y)+1]:=CrossFadeColor(Color,Pixels[x,Trunc(y)+1],FadeRate);
  201. end;
  202. end;
  203.  
  204. var i : integer;
  205. ly,lx,currentx,currenty,deltax,deltay,l,skipl : single;
  206. begin
  207. if (x1<>x2) or (y1<>y2) then
  208. begin
  209. currentx:=x1;
  210. currenty:=y1;
  211. lx:=abs(x2-x1);
  212. ly:=abs(y2-y1);
  213.  
  214. if lx>ly then
  215. begin
  216. l:=trunc(lx);
  217. deltay:=(y2-y1)/l;
  218. if x1>x2 then
  219. begin
  220. deltax:=-1;
  221. skipl:=(currentx-trunc(currentx));
  222. end else
  223. begin
  224. deltax:=1;
  225. skipl:=1-(currentx-trunc(currentx));
  226. end;
  227. end else
  228. begin
  229. l:=trunc(ly);
  230. deltax:=(x2-x1)/l;
  231. if y1>y2 then
  232. begin
  233. deltay:=-1;
  234. skipl:=(currenty-trunc(currenty));
  235. end else
  236. begin
  237. deltay:=1;
  238. skipl:=1-(currenty-trunc(currenty));
  239. end;
  240. end;
  241.  
  242. currentx:=currentx+deltax*skipl;
  243. currenty:=currenty+deltay*skipl;
  244.  
  245. for i:=1 to trunc(l) do
  246. begin
  247. if lx>ly then vpixel(trunc(currentx),currenty) else hpixel(currentx,trunc(currenty));
  248. currentx:=currentx+deltax;
  249. currenty:=currenty+deltay;
  250. end;
  251. end;
  252. end;
  253.  
  254. // Draw a branch, and do not check for ESC press (recursive function)
  255.  
  256.  
  257.  
  258. procedure DrawBranchNoESC(TheCanvas:TCanvas;Depth:Integer; X, Y, Length, Theta, LenghtScale, dtheta:Single);
  259. var
  260. x1, y1: Integer;
  261. TheColor: TColor;
  262. begin
  263. x1 := round(X + length * cos(theta)); // Calculate the next point
  264. y1 := round(Y + length * sin(theta)); //
  265.  
  266. // Decide the branch color
  267. TheColor := GetColor(Form1.Shape2.Brush.Color,Form1.Shape1.Brush.Color,Form1.SpinEdit1.Value,Depth);
  268.  
  269. if Form1.RadioButton2.Checked then // Is antialised line checked?
  270. AALine(x,y,x1,y1,TheColor,TheCanvas) // Then draw an antialiased line
  271. else
  272. begin // Draw regular jaggy line
  273. if Form1.CheckBox1.Checked then // Is variable thickness checked?
  274. TheCanvas.Pen.Width := Depth // Then the pen width is equal the branch depth
  275. else
  276. TheCanvas.Pen.Width := 1; // Otherwise, the pen width is 1
  277. TheCanvas.Pen.Color := TheColor;
  278. TheCanvas.MoveTo(round(x),round(y));
  279. TheCanvas.LineTo(x1,y1);
  280. end;
  281.  
  282. if Depth > 1 then // if there are more branches to draw...
  283. begin
  284. // Call this very same code again twice - one going to the left and one to the right
  285. DrawBranchNoESC(TheCanvas, Depth-1, x1, y1, Length*LenghtScale, Theta+dtheta, LenghtScale, dtheta);
  286. DrawBranchNoESC(TheCanvas, Depth-1, x1, y1, Length*LenghtScale, Theta-dtheta, LenghtScale, dtheta);
  287. end;
  288.  
  289. end;
  290.  
  291.  
  292. // Draw a branch, and DO check for ESC press (recursive function)
  293. procedure DrawBranchCheckESC(TheCanvas:TCanvas;Depth:Integer; X, Y, Length, Theta, LenghtScale, dtheta:Single);
  294. var
  295. x1, y1: Integer;
  296. TheColor: TColor;
  297. begin
  298. // The following three lines are the only difference (compared to the code above)
  299. Application.ProcessMessages; // Take a breath!
  300. if GetKeyState(VK_Escape) and 128 = 128 then // Is ESC pressed?
  301. Exit; // Stop drawing
  302.  
  303. x1 := round(X + length * cos(theta));
  304. y1 := round(Y + length * sin(theta));
  305.  
  306. TheColor := GetColor(Form1.Shape2.Brush.Color,Form1.Shape1.Brush.Color,Form1.SpinEdit1.Value,Depth);
  307.  
  308. if Form1.RadioButton2.Checked then
  309. begin
  310. AALine(x,y,x1,y1,TheColor,TheCanvas)
  311. end
  312. else
  313. begin
  314. if Form1.CheckBox1.Checked then
  315. TheCanvas.Pen.Width := Depth
  316. else
  317. TheCanvas.Pen.Width := 1;
  318. TheCanvas.Pen.Color := TheColor;
  319. TheCanvas.MoveTo(round(x),round(y));
  320. TheCanvas.LineTo(x1,y1);
  321. end;
  322.  
  323. if Depth > 1 then
  324. begin
  325. DrawBranchCheckESC(TheCanvas, Depth-1, x1, y1, Length*LenghtScale, Theta+dtheta, LenghtScale, dtheta);
  326. DrawBranchCheckESC(TheCanvas, Depth-1, x1, y1, Length*LenghtScale, Theta-dtheta, LenghtScale, dtheta);
  327. end;
  328. end;
  329.  
  330. procedure TForm1.Button1Click(Sender: TObject);
  331. var
  332. BeginTime: Integer;
  333. begin
  334. DisableControls(True); // Disable controls.
  335. Label14.Caption := ''; // Reset the "number of ms" label
  336. BeginTime := GetTickCount; // Start the stopwatch! (inaccuracy: about +/- 16 ms)
  337.  
  338. // Reset the image
  339. Image1.Picture.Bitmap.Height := Image1.Height;
  340. Image1.Picture.Bitmap.Width := Image1.Width;
  341. Image1.Canvas.Brush.Color := Shape3.Brush.Color;
  342. Image1.Canvas.Rectangle(0,0,Image1.Width+1, Image1.Height+1);
  343.  
  344. // Is "Update screen and abort with ESC" checked?
  345. if CheckBox2.Checked then
  346. DrawBranchCheckESC(Image1.Canvas,SpinEdit1.Value,Image1.Width div 2,Image1.Height,SpinEdit3.Value,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value))
  347. else
  348. DrawBranchNoESC(Image1.Canvas,SpinEdit1.Value,Image1.Width div 2,Image1.Height,SpinEdit3.Value,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value));
  349. // Drawing is done!
  350. // Write the draw time to label
  351. Label14.Caption := 'Drawing time: ' + IntToStr(GetTickCount-BeginTime)+' ms';
  352.  
  353. DisableControls(False); // Re-enable controls
  354. end;
  355.  
  356. // To select the color
  357. procedure TForm1.Shape1MouseUp(Sender: TObject; Button: TMouseButton;
  358. Shift: TShiftState; X, Y: Integer);
  359. begin
  360. ColorDialog1.Color := Shape1.Brush.Color;
  361. If ColorDialog1.Execute then
  362. Shape1.Brush.Color := ColorDialog1.Color;
  363. end;
  364.  
  365. // To select the color
  366. procedure TForm1.Shape2MouseUp(Sender: TObject; Button: TMouseButton;
  367. Shift: TShiftState; X, Y: Integer);
  368. begin
  369. ColorDialog1.Color := Shape2.Brush.Color;
  370. If ColorDialog1.Execute then
  371. Shape2.Brush.Color := ColorDialog1.Color;
  372. end;
  373.  
  374. // To select the color
  375. procedure TForm1.Shape3MouseUp(Sender: TObject; Button: TMouseButton;
  376. Shift: TShiftState; X, Y: Integer);
  377. begin
  378. ColorDialog1.Color := Shape3.Brush.Color;
  379. If ColorDialog1.Execute then
  380. Shape3.Brush.Color := ColorDialog1.Color;
  381. end;
  382.  
  383. procedure TForm1.RadioButton2Click(Sender: TObject);
  384. begin
  385. if RadioButton2.Checked then
  386. CheckBox1.Enabled := False
  387. else
  388. CheckBox1.Enabled := True;
  389. end;
  390.  
  391. procedure TForm1.RadioButton1Click(Sender: TObject);
  392. begin
  393. if RadioButton1.Checked then
  394. CheckBox1.Enabled := True
  395. else
  396. CheckBox1.Enabled := False;
  397. end;
  398.  
  399. // To save the binary tree
  400. procedure TForm1.Button2Click(Sender: TObject);
  401. begin
  402. if SaveDialog1.Execute then
  403. Image1.Picture.SaveToFile(SaveDialog1.FileName);
  404. end;
  405.  
  406. procedure TForm1.Button3Click(Sender: TObject);
  407. var
  408. Dir: string;
  409. begin
  410. Dir := 'C:';
  411. if SelectDirectory('Select an output folder:', 'C:', Dir) then
  412. Edit2.Text := Dir;
  413. end;
  414.  
  415. // This makes and saves all the frames required for animation
  416. procedure TForm1.Button4Click(Sender: TObject);
  417. var
  418. i: Integer;
  419. begin
  420. DisableControls(True);
  421. if RadioButton3.Checked then // Different branch length
  422. begin
  423. for i := SpinEdit4.Value to SpinEdit5.Value do
  424. begin
  425. Image1.Picture.Bitmap.Height := Image1.Height;
  426. Image1.Picture.Bitmap.Width := Image1.Width;
  427. Image1.Canvas.Brush.Color := Shape3.Brush.Color;
  428. Image1.Canvas.Rectangle(0,0,Image1.Width+1, Image1.Height+1);
  429.  
  430. if CheckBox2.Checked then
  431. DrawBranchCheckESC(Image1.Canvas,SpinEdit1.Value,Image1.Width div 2,Image1.Height,i,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value))
  432. else
  433. DrawBranchNoESC(Image1.Canvas,SpinEdit1.Value,Image1.Width div 2,Image1.Height,i,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value));
  434. Image1.Picture.SaveToFile(IncludeTrailingBackslash(Edit2.Text)+'BinaryTree_'+IntToStr(i)+'.bmp');
  435. end;
  436.  
  437.  
  438. end;
  439. if RadioButton4.Checked then // Different branch depth
  440. begin
  441. for i := SpinEdit6.Value to SpinEdit7.Value do
  442. begin
  443. Image1.Picture.Bitmap.Height := Image1.Height;
  444. Image1.Picture.Bitmap.Width := Image1.Width;
  445. Image1.Canvas.Brush.Color := Shape3.Brush.Color;
  446. Image1.Canvas.Rectangle(0,0,Image1.Width+1, Image1.Height+1);
  447.  
  448. if CheckBox2.Checked then
  449. DrawBranchCheckESC(Image1.Canvas,i,Image1.Width div 2,Image1.Height,SpinEdit3.Value,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value))
  450. else
  451. DrawBranchNoESC(Image1.Canvas,i,Image1.Width div 2,Image1.Height,SpinEdit3.Value,degtorad(270),StrToFloat(Edit1.Text),degtorad(SpinEdit2.Value));
  452.  
  453. Image1.Picture.SaveToFile(IncludeTrailingBackslash(Edit2.Text)+'BinaryTree_'+IntToStr(i)+'.bmp');
  454. end;
  455. end;
  456.  
  457. DisableControls(False);
  458. end;
  459.  
  460. // Select the first tab on program start
  461. procedure TForm1.FormCreate(Sender: TObject);
  462. begin
  463. PageControl1.ActivePageIndex := 0;
  464. end;
  465.  
  466. end.
AttachmentSize
Animation (.wmv) - growing size149.85 KB
Animation (.wmv) - increasing depth125.59 KB