Drawing a sine wave without going all gothic - Page 1
Forum section: electronics / projects

author: Renate
author_rank: Super Contributor
timestamp: February 21, 2022, 07:30:27 pm
content: So you want to draw a sine wave, for an icon or a graphic.You've seen enough of where somebody thought two semi-circles was close enough to a sine wave.So you dig out your trig book, y=sin(x), Hmm...But you want it bolder. So you make it thicker. And it goes all gothic on you!On the plots below, the red is your basic sin(x).The first plot shows what a thicker pen would do as it extends an offset perpendicular to the trace.The second plot show just using a vertical offset.So how do you show off a sine wave in a way that doesn't offend the eye?

author: SilverSolder
author_rank: Super Contributor
timestamp: February 21, 2022, 07:39:24 pm
content: For an icon, simpler is better, just due to the small size...

author: Benta
author_rank: Super Contributor
timestamp: February 21, 2022, 07:43:55 pm
content: Something's wrong with your "thicker pen" plot. Pen thickness change should not create sharp points on the curve. How did you make it?

author: Renate
author_rank: Super Contributor
timestamp: February 21, 2022, 07:47:23 pm
content: Quote from: Benta on February 21, 2022, 07:43:55 pmSure it can. Imagine a round pen drawing a square. The outside edge is rounded, the inside edge is sharp.

author: electr_peter
author_rank: Unknown
timestamp: February 21, 2022, 08:32:26 pm
content: Make a pen narrower near sin(x) = 0 and broader near peaks. Try linear, sqrt(x), x^2 or cos(x) dependancy, see what you like. Logic is that rate of change*width = constant

author: Benta
author_rank: Super Contributor
timestamp: February 21, 2022, 09:00:26 pm
content: Quote from: Renate on February 21, 2022, 07:47:23 pmNo. A sine is always smooth. The problem is your pen step size. You're moving from a continuous function to a discontinuous one by digitizing it.That's what's creating your inside "nipples". Increase your pen step resolution.Yes, the inside corner gets sharper, but is never a corner.

author: RoGeorge
author_rank: Super Contributor
timestamp: February 21, 2022, 09:48:14 pm
content: I don't think it's possible to turn a thick line without "going gothic" when the thickness is wider than twice the radius of the turn. If you follow a circle of radius R with a pen of thickness 2R, the inner circle will be reduced to a single point no matter what.How to draw a thick sin(x) that looks good in an icon, that's subjective.It's all up to you what looks "good" to the eye and what doesn't. Make a few icons with different line thickness, then look at them at their expected icon size, and see which one you like most. For very high quality graphics of the same icon in different sizes, you might need to tune the drawing individually, for each icon size.

author: Renate
author_rank: Super Contributor
timestamp: February 21, 2022, 10:40:35 pm
content: Quote from: electr_peter on February 21, 2022, 08:32:26 pmThat's actually the opposite of what we need. The problem is a wide pen near acute areas.Quote from: Benta on February 21, 2022, 09:00:26 pmI would have thought that that third plot of the ball would make it clear.Here's another with an even wider pen, drawn at a greater density.Quote from: RoGeorge on February 21, 2022, 09:48:14 pmRight and also the point of this little exercise."It's Slinky, it's Slinky! Fun for a girl and a boy!"

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 21, 2022, 11:55:45 pm
content: In parametric form, we can define the sine curve along the positive \$x\$ axis as$$\left\lbrace \begin{aligned}x(t) &= t \\y(t) &= \sin(t) \\\end{aligned} \right.$$The tangent of the curve at point \$t\$ is$$\left\lbrace \begin{aligned}\frac{d x(t)}{d t} &= 1 \\\frac{d y(t)}{d t} &= \cos(t) \\\end{aligned} \right.$$and normalizing the tangent to unit length and rotating it ninety degrees, we have$$\left\lbrace \begin{aligned}n_x(t) &= \frac{-\cos(t)}{\sqrt{1 + \bigl(\cos(t)\bigr)^2}} \\n_y(t) &= \frac{1}{\sqrt{1 + \bigl(\cos(t)\bigr)^2}} \\\end{aligned} \right.$$The curve at (signed) offset \$r\$ from the sine is at \$x(t) + r n_x(t), y(t) + r n_y(t)\$.The offset curves have stationary points (tangent vanishes) whenever \$-r \sin(t) = \left(1 + \bigl(\cos(t)\bigr)^2\right)^{3/2}\$. The offset curves therefore can have sharp peaks for certain values of \$r\$. (Basically, for larger magnitudes of \$r\$ the offset curve makes loops.)Or is this too gothic?

author: Renate
author_rank: Super Contributor
timestamp: February 22, 2022, 02:37:35 am
content: Well, I must have knownsomeof that to draw those plots.Still, I didn't knowallof that.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 22, 2022, 03:32:05 am
content: In my defense, I've occasionally dabbled withoffset curves, for both parametric curves and polygons.Note that the normalization factor, the reciprocal of the length of the tangent vector, is \$1 / \sqrt{2} \le 1 / \sqrt{1 + \bigl(\cos(t)\bigr)^2} \le 1\$, which is minimum on the \$x\$ axis, and maximum at \$\pm 1\$. This means that if you approximate the offset curve for the sine using$$\left\lbrace \begin{aligned}x_A(t) &= t - r \cos(t) \\y_A(t) &= r + \sin(t) \\\end{aligned} \right.$$you get an offset curve that is slightly thicker (wider) at the \$x\$ axis. For \$r = \pm 0.1\$:It just so happens that$$\left\lbrace \begin{aligned}x_A(t) &= t - \frac{r}{2} \cos(t) \\y_A(t) &= r + \sin(t) \\\end{aligned} \right.$$gets you extremely close. In the image above, no more than a pixel or two from the blue (correct offset curve) lines.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 22, 2022, 03:32:52 am
content: It's a problem you can encounter with any kind of curve, actually.You can either choose parameters so it doesn't happen - but that will restrict the "thickness" of the lines to certain values - or just artificially round the sharp corners. It will not be a pure sine function anymore, but will look better than either of the examples you gave. Nominal just gave a nice example of this for a sine function.

author: Renate
author_rank: Super Contributor
timestamp: February 22, 2022, 10:52:14 am
content: Quote from: Nominal Animal on February 22, 2022, 03:32:05 amThat's a nice Wikipedia article with some good illustrations.In my defence, I've occasionally dabbled in G Code for vertical milling machines and multiple passes.

author: RoGeorge
author_rank: Super Contributor
timestamp: February 22, 2022, 11:42:47 am
content: 1. - A pen footprint in the shape of a circle may create sharp corners - as in OP fig perp.png2. - A pen footprint in the shape of a vertical line segment will look too thin in thin around x axis crossings - as in OP fig vert.png3. - My next try would be a pen footprint like a vertical ellipse, so to keep inner turns smooth as in 2, yet to thicken the lines around zero crossings in a controlled way. The ratio between major/minor axis of the ellipse should be choose in relation with the ratio between amplitude/period of the sinusoidal function.I hope this ellipse footprint pen is not the same with theNominal Animal's solution.4. - Yet another way might be to alter only the inner thickening, and to keep the outer thickening constant, so a pen with a footprint in the shape of an egg, and to rotate the pen while drawing, one rotation per each sin period.Are any intensity levels allowed for the pen trace?

author: emece67
author_rank: Frequent Contributor
timestamp: February 22, 2022, 11:45:28 am
content: .

author: RoGeorge
author_rank: Super Contributor
timestamp: February 22, 2022, 12:10:00 pm
content: Doh, I've accidentally made a swimming bacteria:Code:import pygame, sysscreen = pygame.display.set_mode((1024, 640))running = True#let's create a surface to hold our ellipse:surface = pygame.Surface((320, 240))red = (180, 50, 50)size = (0, 0, 30, 200)#drawing an ellipse onto theellipse = pygame.draw.ellipse(surface, red, size)#new surface variable for clarity (could use our existing though)#we use the pygame.transform module to rotate the original surface by 45°surface2 = pygame.transform.rotate(surface, 45)angle = 0while running:angle += 0.05surface2 = pygame.transform.rotate(surface, angle)# comment out the .fill line to see a bacteria swimming pattern# :o)# screen.fill((255, 250, 200))for event in pygame.event.get():if event.type == pygame.QUIT:pygame.quit()sys.exit()screen.blit(surface2, (100, 100))pygame.display.update()

author: Renate
author_rank: Super Contributor
timestamp: February 22, 2022, 12:50:53 pm
content: There's something to be said for a vertical (i.e. "skinny") ellipse pen.In some ways, it gives you the middle ground between a round pen and a vertical offset (i.e. a degenerate vertical ellipse pen).You can make your ellipse pen "pointy" enough that it follows the tight curves on the peaks.It still shows a bit of skinnyness on the slopes.A round pen and a normal are exactly the same (until it comes to crossover).An ellipse pen does not scribe a point on the normal because the two tangent points are not opposite to each other.That means to just draw the lines (versus the ellipses) means a tad more calculation.(Sorry, RoGeorge, Python and me don't play well together. I'm always missing some dependency.)

author: LaserSteve
author_rank: Super Contributor
timestamp: February 22, 2022, 03:23:53 pm
content: Humor < Well this thread explains the Keysight Logo> End Humor.Steve

author: RoGeorge
author_rank: Super Contributor
timestamp: February 22, 2022, 07:52:30 pm
content: Quote from: Renate on February 22, 2022, 12:50:53 pmFor that example, 'sys' module should already be present if you have python, the other module used there is 'pygame'. The easiest way to install python modules is to use 'pip'.First, install python3 and pip (sometimes called pip3, depending on the OS). Then to install the 'pygame' module, in a terminal type "pip3 install pygame".In Ubuntu or other Debian based distros to install python3, pip3 and the pygame module, in a terminal type:Code:sudo apt install python3sudo apt install python3-pippip3 install pygamePython is an interpreted language. No need to compile, just create a text file with the extension .py, let's say "demo.py", and put in it the code to be tested. To run it, from the command line type "python3 demo.py", or from GUI right click the "demo.py" and run with python.If you prefer a minimal IDE with GUI and debugger, install IDLE (IIRC in Windows IDLE comes bundled with Python3), to install IDLE in a Debian based Linux, in a terminal typeCode:sudo apt install idle3

author: Someone
author_rank: Super Contributor
timestamp: February 22, 2022, 10:01:42 pm
content: Quote from: Renate on February 21, 2022, 10:40:35 pmThe simple answer is what looks good is no longer sinusoidal, the usual solution being a continuous composite Bézier curve adjusted to taste. Why? Those are the primitives of vector formats for display/printing and widely supported/understood.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 23, 2022, 12:28:52 am
content: Human perception is a funky thing.For example, A-frame houses were usually built with a slight curve upwards (convex, not concave), because a straight beam at the apex will look like it sags a bit; in many Asian countries, this ended up being the aesthetic, and lead to the curving roof style.A couple of percent of divergence is absolutely imperceptible to the unaided eye. For things like vertical to horizontal aspect ratio in a square/cube, 10%-20% error is not perceivable at all. (Even those who claim they have a "calibrated eye", tend to fail when presented with multiple such objects at once.) We can detect small changes in a repeating pattern, yes, but absolute scale or aspect ratio in an individual object is something we're quite bad at.Artistic freedom and stylizing is okay for stuff that represents an idea or a concept, instead of a measurement.(You have no idea how much time I've spent with this with regarding to the results of molecular dynamics simulations. Atoms are not marbles, and bonds are not sticks. Evenmetaballswould represent molecules much better.)If you haveGnuplotinstalled, try the following command:Code:H=-0.2; V=0.3; set parametric; set size ratio -1; set samples 2000; set yrange [-1.1-V:1.1+V ]; set trange [0:6*pi]; plot t,sin(t) notitle w lines lc black, t-0.5*H*cos(t),sin(t)+V notitle w lines lc rgb '#ff0000', t+0.5*H*cos(t),sin(t)-V notitle w lines lc rgb '#ff0000'Pressing Up and Home will let you edit the line. Modify theHandVcoefficients; they are therin my previous post, but split between the horizontal and vertical components. KeepV ≥ 0, butHcan be either positive or negative. WhenH = V, you have a pretty close approximation of the offset curve to the sine curve at distanceH = V.I could also make a web page tool so one could experiment with this even easier. It should also show the Bézier approximation for the curves (at a given scale). But, I feel that would be killing a gnat with a hand grenade, since Gnuplot provides a tool to visually experiment with already, and one can usewxMaximaor Octave to calculate the Bézier curve control vectors.

author: Cerebus
author_rank: Super Contributor
timestamp: February 23, 2022, 01:32:31 am
content: Another way of looking at this mathematically is that the finished drawn curve is theMinkowski sumof a polygon representing the pen, and the line representing the curve. The polygon can be any shape, from a line (of any length, at any fixed angle) representing a calligraphy style pen, to a circle (of any diameter) representing a felt pen (or a milling cutter).In determining what to 'fill in' with 'ink' in presenting the finished graphic one has to account for thewinding numberof the two 'outside' curves created by the Minkowski sum depending on whether one wants to see the loops that may be formed or not.

author: RoGeorge
author_rank: Super Contributor
timestamp: February 23, 2022, 03:38:27 am
content: The reverso of the inner gothically pointy sine: outer rounded corners.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 23, 2022, 04:03:07 am
content: If anyone is using Inkscape or similar vector tools to draw the sine wave, I can dig out the cubic Bézier approximations for a quarter and a half wave. IIRC the error is quite low, just a few percent at most; definitely not perceptible. Just lemme know.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 23, 2022, 04:16:42 am
content: Quote from: RoGeorge on February 23, 2022, 03:38:27 amThis is something you're used to when dealing with photo-tracing and a circular aperture.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 23, 2022, 05:08:08 am
content: When doing contour fills to an arbitrary polygon or curve, things get really interesting when the contour curve (offset curve) intersects itself.If you start each contour with the base polygon or curve having a specific direction (say, counterclockwise), then you only need to remove the parts of the contour curve that are drawn in the other direction (between intersection points; or the entire contour curve, if there are no self-intersections).Another way is to test a single point in the contour curve (for the entire contour if no self-intersections, or a point per segment between two consecutive self-intersections) for the distance to the base polygon or curve. If the distance is less than the contour offset, then one removes that contour curve segment.At one point, I worked out the math needed for offsetting vinyl cutter blades for polygon shapes. (The blade drags behind the actual device point/head, but rotates freely.)I even wrote a script in Awk to do that for HPGL input and output, and it is used with a certain vinyl cutting software. (The author is a friend, who I haven't been able to contact at all in a year and a half; about a third of a world away.)For just drawing this kind of stuff, I warmly recommend Inkscape. (Any other vector graphics application works fine, though.)They all tend to use piecewise cubic Bézier curves for, well, curves; because that is the one that tends to be accelerated and widely used, from PostScript to SVG.Obviously, there you can simply choose how you wish to stroke the curve. The easiest way to control the shape of the circle/ellipse of the stroking "pen", is to simply do it with a circular pen and then scale the rectangular region containing the curve to match the circle aspect ratio. If the stroke shape is angled, rotate the curve first by the stroke angle in the opposite direction, then scale, and then rotate back.If one wanted to do this in SVG by hand, the coordinates are not difficult to calculate. The only thing is to how to split the sine curve into cubic Béziers: quarter or half-waves, or something else. The definition of the "best" approximation is the difficult thing, because one can measure the deviation in several different ways: most commonly the deviation is only measured along one axis only (since that then corresponds to the error of a single variable function at that point), but distance (numerically) or distance squared (linear least squares fitting) can also be used. Since the SVG format internally supports linear transformations on a per-element basis, one could trivially support circular or ellipse shaped stroke "pens" at different stroke angles and radiuses.

author: Renate
author_rank: Super Contributor
timestamp: February 23, 2022, 10:51:30 am
content: When I'm generating static images I don't worry much about efficiency. All of the images in this thread were generated with code that uses SVG line segments for all the curves. I used to use Inkscape export to render to PNG. Just to be contradictory, I do begrudge how long an invocation on the command line of Inkscape takes. It also generates a 32 bit PNG with alpha, even when no alpha is required. I wrapped up Window's Direct2D renderer in a command and use my own PNG output routines to output minimal PNG files. Direct2D support of SVG is minimal, as in, it doesn't support <text>.

author: Nominal Animal
author_rank: Super Contributor
timestamp: February 23, 2022, 01:52:15 pm
content: In Inkscape, I convert all texts to paths, by selecting a text thingy, thenEdit>Select Same>Object type, thenPath>Object to path, thenObject>Ungroup, thenEdit>Select Same>Fill and Stroke, and finallyPath>Combine.A lot of generated SVG files can be optimized in a similar fashion, by first repeatedly ungrouping all groups until no groups are left, then doing the above for all non-Path/Circle/Rectangle objects. I've written some Inkscape extensions, but automatizing the above does not really work, because it also changes the element display order.For batch conversion of SVG files to PNG, I recommend ImageMagick'sconvert, optionally followed bypngcrush -brute.In Linux, I often use NetPBM tools to optimize PNG images:pngtopnm -background '#ffffff' -mixsource.png| pnmquantN| pnmtopng -compress 9 > temp.pngpngcrush -brute temp.pngresult.png>/dev/null 2>/dev/nullrm -f temp.png(via a script). This also removes any unnecessary PNG chunks and the alpha channel. Allitalicparts vary as needed. Powers of twoNwork best in PNG, but sometimes the color quantizer needs a couple of extra entries to use the full power of twoN. Even with the pngcrush brute-force optimization stage, running it a few different times with differentNand comparing the result to the original, is fast enough for me.My scripts use an autodeleted temporary directory, viaCode:#!/bin/bashWork=$(mkdir -d) || exit 1trap "rm -f '$Work'" EXIT# use "$Work/" for temporary files, it will be autodeletedso I can trivially run a couple of different conversions in parallel.