BLADES: 8 Curve Types Now with Any Color lazarus / 39-Graphics ---------------------------------------------------------------------- Boleeman · 2024-03-05 10:35 8 Curve Types Now. Scroll down to the last thread to get latest version. Hi All. I converted some VB6 code of mine that creates "Venetian Blind" type Blades. (I also made something similar in Scratch here https://scratch.mit.edu/projects/947286929/  ) Got the basics to work on a TImage. There are 7 curve types in total so far. The curves all produce Red Green or Blue blade curves and 3 other colors (see 3rd post). Also some curves (radial) have slight imperfections. How can we make the curves any color? Any help would be greatly appreciated. Here is what I have so far. Enjoy. ---------------------------------------------------------------------- Boleeman · 2024-03-06 07:30 Yeah, got the Red, Green and Blue to work for all six curves. The Leaf curve would make a good opening for an Intro. Screen. Kinda like an Intro. to a James Bond 007 Movie. Just imagine! Now, how to make any colour? ---------------------------------------------------------------------- Boleeman · 2024-03-06 10:51 Getting closer. Now 6 colors work only. This now works for RGB color components that are exactly equal to 0 or 255. Red=RGB(255,0,0) Green=RGB(0,255,0) Blue=RGB(0,0,255) Yellow= RGB(255,255,0) cyan=RGB(0,255,255) magenta=RGB(255,0,255) Seems like it does not work for orange = (255,128,0), brown=RGB(128,64,0), etc. so when one or more RGB color components is not exactly equal to 0 or 255. ---------------------------------------------------------------------- Dzandaa · 2024-03-06 17:07 Hi, @Boleeman I didn't look at your code, but I think that when calculating the intermediate colors, you need to take into account the RGB channels of the selected colors. Ex: Selected: R = 255 G =128 B = 128 Black: R = 0 G = 0 B = 0 if at a moment R = 127 G and B must be 63 (same proportion as R for the range of color) B-> ---------------------------------------------------------------------- wp · 2024-03-06 18:45 I don't fully understand the problem, I guess you mean that the colors are not the same from blade to blade? A blade always seems to make a transition from a given color to black, and you use some positional coordinate in the image to define the gradient factor (called "Intensity" in your code). For a single blade the "Intensity" should be between zero and one (0 --> blade color, 1 --> black). But you do not take care of that. In your project the "Intensity" factor grows from 0 at the top to 8 at the bottom. In the "InterpolateColors" interpolation this leads to numerous overflows of the byte range, and finally results in the false colors. What to do? Make sure that the "Intensity" passed to InterpolateColors is a number between 0 and 1! The easiest way to achieve this is by taking the fraction of the expression that InterpolateColors gets as last argument: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   case CurveType of    0: begin         for y := 0 to myHeight - 1 do         begin           patternColor := InterpolateColor(BladeColor, clBlack, frac(PercentCoeff * y / myHeight));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, y, myWidth, y);       end;       end; ---------------------------------------------------------------------- Boleeman · 2024-03-07 05:58 Many thanks Dzandaa and WP for replying. WP, your advice worked. Can't believe how that little change fixed those inconsistently colored blades. A great many thanks for that advice, as I was getting into HSL to try to solve the problem and was going round in circles. Still have a few curves (diagonal and Radial) with a few gaps in them. I added a save to png and also an ending color select (each blade has a starting color and and end color). ---------------------------------------------------------------------- Boleeman · 2024-03-07 07:19 Dark to Light color and Light color to dark color comparison of the Lazarus version. Now I also want to try to add a XOR type band/stripe effect, like I did for my vb6 program. (see attached picture below). ---------------------------------------------------------------------- wp · 2024-03-07 19:04 One more remark about your code: you have a groupbox (gbCurveType) with 6 individual radiobuttons, and you need mulitple "if" statements in order to get the "CurveType" from the checked radiobutton (in MakeCurve). A simpler way would be to use a TRadioGroup (rather than a TGroupBox) and add the radiobuttons by means of the Items. Then you simply can determine the selected radio button from the RadioGroup's ItemIndex: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.MakeCurve(Sender: TObject);...  CurveType := Radiogroup1.ItemIndex;... ---------------------------------------------------------------------- Boleeman · 2024-03-08 09:03 Thanks WP for that advice on the TRadiogroup. I fixed the diagonal pattern by maintaining a square image size and using the settings in Object Inspector for the TImage stretchin, stretchout and proportional. Now, 1.  How to resize and save the png proportionally? Saving always saves as 750x750 px 2.  How to fix the small glitch in the radial pattern? Strangely I do not get that in the VB6 version. Perhaps the Lazarus version has a rounding error? Interesting to note when the png is saved the identical horizontal pattern is 6kb in size and the vertical pattern is 12 kb in size. Looks like horizontal is compressed better than vertical for png format. ---------------------------------------------------------------------- domasz · 2024-03-08 10:33 Quote from: Boleeman on March 08, 2024, 10:03:44 amInteresting to note when the png is saved the identical horizontal pattern is 6kb in size and the vertical pattern is 12 kb in size. Looks like horizontal is compressed better than vertical for png format. Yes, PNG saves top to bottom looking for patterns. ---------------------------------------------------------------------- wp · 2024-03-08 10:35 Quote from: Boleeman on March 08, 2024, 10:03:44 am1.  How to resize and save the png proportionally? Saving always saves as 750x750 px Modify your code so that it can draw on any canvas, not just on that of the TImage. Create a png image of the requested size, call your drawing code with the canvas of the png as parameter. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.CreateBlades(ACanvas: TCanvas; myWidth, myHeight: Integer; PercentCoeff: Single;  SumRotator, CurvedOffset: Integer; CurveType: Integer; BladeColor: TColor);var  x, y, x2: Integer;  patternColor, endColor: TColor;begin  ACanvas.Brush.Color := clWhite;  ACanvas.FillRect(0, 0, myWidth, myHeight);...end; procedure TForm1.MakeCurve(Sender: TObject);begin  ...  CreateBlades(Image1.Canvas, myWidth, myHeight, PercentCoeff, SumRotator, CurvedOffset, CurveType, cbBladecolor.ButtonColor);end; procedure TForm1.SaveToPNG(AFileName: String; ASize: Integer);var  png: TCustomBitmap;begin  png := TPortableNetworkgraphic.Create;  try    png.Width := ASize;    png.Height := ASize;    ...    CreateBlades(png.Canvas, ASize, ASize, PercentCoeff, SumRotator, CurvedOffset, CurveType, cbBladecolor.ButtonColor);    png.SaveToFile(AFileName);  finally    png.Free;  end;  Quote from: Boleeman on March 08, 2024, 10:03:44 am2.  How to fix the small glitch in the radial pattern? I did not look into your code in detail, but I noticed an asymmetry in the radial case 2 of CreateBlades: x and y run up to myWidth-1 and myHeight-1, but the lines are drawn to mywidth and myheight without the -1. Therefore I guess that you are not drawing the white pixels at all (when I set the Canvas.Brush.Color at the top of the procedure to clYellow they do become yellow). I fixed it by subtracting 1 also from myWidth and myHeight inside the loop (don't know if the same is necessary also in the other cases): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i case CurveType of...      2: begin         for x := 0 to myWidth - 1 do       begin           y := x;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * x / myWidth));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, myWidth - x - 1, myHeight - 1);         end;          for y := 0 to myHeight - 2 do         begin           x := y;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, y, myWidth - 1, myHeight - y - 1);         end;       end; ---------------------------------------------------------------------- Boleeman · 2024-03-08 11:28 Thanks WP once again. I had this for the glitch fix, which seemed to work: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i       2: begin         for x := 0 to myWidth - 1 do         begin           y := x;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * x / myWidth));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, myWidth - x - 1, myHeight);         end;          for y := 0 to myHeight - 1 do         begin           x := y;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));           Image.Canvas.Pen.Color := patternColor;         Image.Canvas.Line(0, y, myWidth - 1, myHeight - y);         end;     end;       but was a little off from your one (which is the better solution): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i       2: begin         for x := 0 to myWidth - 1 do         begin           y := x;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * x / myWidth));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, myWidth - x - 1, myHeight - 1);         end;          for y := 0 to myHeight - 2 do       begin           x := y;           patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));           Image.Canvas.Pen.Color := patternColor;         Image.Canvas.Line(0, y, myWidth - 1, myHeight - y - 1);         end;       end; ---------------------------------------------------------------------- Boleeman · 2024-03-08 12:02 GAP Error when selecting from other curves, then resizing and then selecting DiagP shape. Don't seem to get the GAP error when switching and resizing between all the other curves. When I select Radial, then resize form horizontally and then select DiagP I get a gap in the top right corner (as shown in picture below). Could that be from a similar sizing bug for the Radial curve or is it because the TImage is not perfectly square? Kinda stumped on working out what is doing it, but figure it is a fault in the code below somewhere. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      3: begin         for x := 0 to myWidth - 1 do         begin           y := x;           patternColor := InterpolateColor(BladeColor, endColor, Frac((PercentCoeff * x / myWidth)));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, x, 2 * myWidth - x, 2 * myHeight);         end;          for y := 0 to myHeight - 1 do       begin           x := y;            patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));            Image.Canvas.Pen.Color := patternColor;         Image.Canvas.Line(y, 0, 2 * myWidth, 2 * myHeight - y);         end;          for x := 0 to myWidth - 1 do         begin           y := x;            patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * x / myWidth));            Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, x, 2 * myWidth - x, 2 * myHeight);         end;          for y := 0 to myHeight - 1 do         begin           x := y;            patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));            Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(y, 0, 2 * myWidth, 2 * myHeight - y);         end;       end; Actually I tried this but still get that gap, unless I resize the form to be square and then change from Radial to DiagP and resize. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i       3: begin         for x := 0 to myWidth - 1 do         begin           y := x;           patternColor := InterpolateColor(BladeColor, endColor, Frac((PercentCoeff * x / myWidth)));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, x, 2 * (myWidth - 1) - x, 2 * (myHeight - 1));         end;          for y := 0 to myHeight - 2 do         begin           x := y;            patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));            Image.Canvas.Pen.Color := patternColor;         Image.Canvas.Line(y, 0, 2 * (myWidth -1), 2 * (myHeight - 1) - y);       end;          for x := 0 to myWidth - 1 do         begin         y := x;            patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * x / myWidth));            Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(0, x, 2 * (myWidth - 1) - x, 2 * (myHeight - 1));         end;          for y := 0 to myHeight - 2 do         begin           x := y;          patternColor := InterpolateColor(BladeColor, endColor, Frac(PercentCoeff * y / myHeight));            Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(y, 0, 2 * (myWidth - 1), 2 * (myHeight - 1) - y);         end;       end; ---------------------------------------------------------------------- wp · 2024-03-08 18:09 I don't understand this code. There is a loop with variable x but it is used at a position where y should be? And why are there so many loops to create the diagonal blades? Trying to understand what is happening, I commented out all loops (in case 3) except for the first one and looked at the output - it filled the lower left triangle covering all y values along the left edge of the image - yes, this is clear when x should be renamed to y.  And the second loop covers the upper right triangle. And the third and fourth loops are just duplicates. Let me simplify this. I'd like to run x from 0 all over the horizontal edge: for x := 0 to mywidth-1. Next, I've been twisting my brain to understand the endpoint of your line command... To simplify I just draw a diagonal for x=0: canvas.line(0, 0, mywidth, myheight); then when x advances we add x to the x coordinates: canvas.Line(x, 0, mywidth+x, myheight): Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i { case 3}       for x :=0 to mywidth-1 do         begin           patternColor := InterpolateColor(BladeColor, endColor, frac(PercentCoeff * x / mywidth));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, mywidth + x, myHeight);         end;OK. But this draws only the upper right triangle if the image. How to get the lower left triangle? You are doing this by introducing a second loop, now running vertically. But this complicates the situation because the factor for InterpolateColors must be correctly chosen (I think here is reason for the empty triangle in your screenshot). But why don't we just extend the current x loop to the left and have x run from -(myWidth-1) to +(mywidth-1). A good part of the drawing is outside the canvas limits, but this is not harmful because the canvas clips the lines at its edges. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i          for x := -mywidth+1 to mywidth-1 do         begin           patternColor := InterpolateColor(BladeColor, endColor, frac(PercentCoeff * x / mywidth));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, mywidth + x, myHeight);         endThis looks good. Except for one difference to your screenshot in which the center blade running from top/left to bottom right is "complete", or "symmetric" (hard to describe...). But when x is negative in the first part of the drawing the factor for InterpolateColor is negative, too, and this screws up the color interpolation which wants a factor between 0 and 1. Well, the easiest way to make a negative number positive is by calling the abs() function which just removes the sign of the number: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i          for x := -mywidth+1 to mywidth-1 do         begin           patternColor := InterpolateColor(BladeColor, endColor, abs(frac(PercentCoeff * x / mywidth)));           Image.Canvas.Pen.Color := patternColor;           Image.Canvas.Line(x, 0, mywidth + x, myHeight);         endAlas! That's it... ---------------------------------------------------------------------- Boleeman · 2024-03-08 21:51 WP, Many thanks for looking into that loopy Diagonal code. This probably means that the other curves could probably be better coded as well, with fewer loops. I originally made it for VB6 years ago by "blindly experimenting and altering bits and pieces" until I got some interesting patterns happening. Then the other day I tried converting the vb6 code to Lazarus. I suspected that I was overdoing it on the Loops, but was going round in circular loops trying to fix it. I will look at how you approached the problem as this will help me to understand the code better. I have learnt quite a few things in this project with your help. 1.  Use a frac in frac(PercentCoeff * x / mywidth) 2.  Using a TRadiogroup and initializing the options. 3.  Loops (simplifying and debugging) WP.    A HUGE thanks for breaking down the solution process, as I needed to understand it better. ---------------------------------------------------------------------- Boleeman · 2024-03-09 01:40 Added a "Reverse Dark" Tcheckbox to make the blades appear that they are opening the other way (for horizontal and vertical only). Also added WP's just only diagonal blades without the big bar in the middle (but now realized now that the color breaks into 2 for other non primary or secondary colors , negative coord. in the InterpolateColor ?) With WP's previous fix for the big bar in the middle of the diagonals, I also found a slightly different way as shown below: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i          3: begin        for x :=0 to mywidth-1 do        begin          patternColor := InterpolateColor(BladeColor, endColor, frac(PercentCoeff * x / mywidth));          Image.Canvas.Pen.Color := patternColor;          Image.Canvas.Line(x, 0, mywidth + x, myHeight);          Image.Canvas.Line(-x, 0, mywidth - x, myHeight);      end       end; ---------------------------------------------------------------------- wp · 2024-03-09 10:33 Here is an example what happens in the InterpolateColors method when the mixing factor is negative: Suppose the first color is R=0, G=128, B=64 (chosen to avoid byte overflows which make the calculation a bit more complicated), and the second is black (RGB = 0). Mixing with a positive factor 0.5 results in R = 0 + (0-0)*0.5 = 0 + 0 = 0G = 128 + (128-0)*0.5 = 128 + 64 = 192B = 64 + (64-0)*0.5 = 64 + 32 = 96With a negative factor, -0.5, however, the sign of the second number is reversed, and we have the result R = 0 - 0 = 0G = 128 - 64 = 64B = 64 - 32 = 32 ---------------------------------------------------------------------- Boleeman · 2024-03-09 12:16 WP.  WP. I ended up going oversize and the translate left with the diagonals to get past that InterpolateColors color change. My problem with that original diagonal code was programmatically getting that negative factor to a positive at the color change without that extra thick bar in the middle. Tried a few things but still had that extra bar. Need to have a think about it. At the moment I just went oversize for a temporary fix. Now the program has 8 curves. The new ones are: Vent Curve (Like air conditioner vent) and Plain Diagonals ---------------------------------------------------------------------- wp · 2024-03-09 19:00 Want psychedelic blades? Use additonal models, in the attachment HLS (hue-lightness-saturation). RGB-to-HLS conversion routines can be found in the GraphUtil units, some more models in mbColorLib (must be installed via OPM). ---------------------------------------------------------------------- Boleeman · 2024-03-09 23:01 WP. That's so nice and colorful. Makes the color cones on my eyes get that extra stimulation.       What would be nice would be to animate by cycling those colors, like color morphing, with a varying TSlider speed. I was going to ask how to add HSL, as I was trying to implement it using some procedures that I found on this forum. Also tried GraphicsUtil in uses and experimented, but kept on getting endless errors with the variable types. Someone also suggested I use HSL to fix that color break up problem, but I was not sure how it could be used for that. I noticed it is HLS, where I have always known it as HSL. Thanks WP for including HLS in the Blades program. I like how some of the colors break up to make some colorful effects. The png file sizes certainly get bigger with all those extra colors. Here in Melbourne, Australia we are having a heat wave, so it's time to cool down a bit. Thank God for the long Labour Day weekend. ---------------------------------------------------------------------- wp · 2024-03-09 23:57 Quote from: Boleeman on March 10, 2024, 12:01:10 amWhat would be nice would be to animate by cycling those colors, like color morphing, with a varying TSlider speed. Add a TTimer. In its OnTimer event convert the blade and end colors to HLS, increment the hue (H) by some value, but keep the L and S, and then convert back to RGB and assign it to the ColorBotton's ButtonColor. Basically you should then call MakeCurve but this is already done by the OnColorChange event of the TColorbuttons. Adjust the timer's interval and the hue increment to get the desired speed. Be careful, it might be easy to lock your machine. Quote from: Boleeman on March 10, 2024, 12:01:10 amI noticed it is HLS, where I have always known it as HSL. Correct. It's probably mis-named in the GraphUtils, but cannot be changed any more without breaking somebody's code. ---------------------------------------------------------------------- Boleeman · 2024-03-11 05:36 Thanks WP. I experimented with your HLS=HSL and got a gold and a Blue-metal type effect, and other cool effects. Kinda really intense colors. I will also try that animation technique. I did some thing similar years ago using VB6 with RGB color cycling and the rate changinge controlled by a slider and a timer. I stopped this Blades program for a while, as I was trying to convert a CSharp Towers of Hanoi Game to Lazarus. Got some bits working, but the drag and drop (snap to position) has stumped me. I have never really done much with drag and drop so I need to read up a bit.