Author Topic: Convert mouse input to DRAW  (Read 4734 times)

0 Members and 1 Guest are viewing this topic.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Convert mouse input to DRAW
« on: January 23, 2020, 06:58:59 pm »
Decided to tackle a problem Fellippe and Dajan were talking about, but not from scratch.

I started with the "curve smoother" code in Samples, and then removed some of the embarrassing stuff. I ignored 90% of what DRAW can do, and only kept TA and U. End result is, you can draw curves with the mouse they are auto-smoothed, and translated to DRAW instead of LINE. Hope someone finds it useful.

Code: QB64: [Select]
  1.  
  2. '
  3. ' Primary degrees of freedom:
  4. '
  5. rawresolution = 7.5 ' Raw curve resolution.
  6. targetpoints = 500 ' Number of points per curve.
  7. smoothiterations = 20 ' Magnitude of `smooth' effect.
  8.  
  9. ' ********** ********** ********** ********** **********
  10.  
  11. TYPE Vector
  12.     X AS DOUBLE
  13.     Y AS DOUBLE
  14.  
  15. DIM SHARED Center AS Vector
  16. Center.X = _WIDTH / 2
  17. Center.Y = _HEIGHT / 2
  18.  
  19. REDIM pointchainx(999, targetpoints)
  20. REDIM pointchainy(999, targetpoints)
  21. REDIM tempchainx(999, targetpoints)
  22. REDIM tempchainy(999, targetpoints)
  23.  
  24. curvenum = 0
  25. exitflag = 0
  26. xold = 999999
  27. yold = 999999
  28.  
  29. GOSUB refresh
  30.  
  31.     curvenum = curvenum + 1
  32.     numpoints = 0
  33.  
  34.     ' Gather raw data for one curve at a time.
  35.     ' Click+drag mouse button 1 to trace out a curve.
  36.     DO
  37.         DO WHILE _MOUSEINPUT
  38.             x = _MOUSEX
  39.             y = _MOUSEY
  40.             IF (x > 0) AND (x < _WIDTH) AND (y > 0) AND (y < _HEIGHT) THEN
  41.                 IF _MOUSEBUTTON(1) THEN
  42.                     x = x - Center.X
  43.                     y = -y + Center.Y
  44.                     delta = SQR((x - xold) ^ 2 + (y - yold) ^ 2)
  45.  
  46.                     ' Collect data only if the new point is sufficiently far away from the previous point.
  47.                     IF (delta > rawresolution) AND (numpoints < targetpoints - 1) THEN
  48.                         numpoints = numpoints + 1
  49.                         pointchainx(curvenum, numpoints) = x
  50.                         pointchainy(curvenum, numpoints) = y
  51.                         CALL CPset(x, y, 14)
  52.                         xold = x
  53.                         yold = y
  54.                     END IF
  55.                 END IF
  56.             END IF
  57.         LOOP
  58.  
  59.     LOOP UNTIL NOT _MOUSEBUTTON(1) AND numpoints > 1
  60.  
  61.     ' If the curve contains less than the minimum numer of points, use interpolation to fill in the gaps.
  62.     DO WHILE (numpoints < targetpoints)
  63.  
  64.         ' Determine the pair of neighboring points that have the greatest separation of all pairs.
  65.         rad2max = -1
  66.         kmax = -1
  67.         FOR k = 1 TO numpoints - 1
  68.             xfac = pointchainx(curvenum, k) - pointchainx(curvenum, k + 1)
  69.             yfac = pointchainy(curvenum, k) - pointchainy(curvenum, k + 1)
  70.             rad2 = xfac ^ 2 + yfac ^ 2
  71.             IF rad2 > rad2max THEN
  72.                 kmax = k
  73.                 rad2max = rad2
  74.             END IF
  75.         NEXT
  76.  
  77.         ' Starting next to kmax, create a `gap' by shifting all other points by one index.
  78.         FOR j = numpoints TO kmax + 1 STEP -1
  79.             pointchainx(curvenum, j + 1) = pointchainx(curvenum, j)
  80.             pointchainy(curvenum, j + 1) = pointchainy(curvenum, j)
  81.         NEXT
  82.  
  83.         ' Fill the gap with a new point whose position is determined by the average of its neighbors.
  84.         pointchainx(curvenum, kmax + 1) = (1 / 2) * (pointchainx(curvenum, kmax) + pointchainx(curvenum, kmax + 2))
  85.         pointchainy(curvenum, kmax + 1) = (1 / 2) * (pointchainy(curvenum, kmax) + pointchainy(curvenum, kmax + 2))
  86.  
  87.         numpoints = numpoints + 1
  88.     LOOP
  89.  
  90.     GOSUB refresh
  91.     SLEEP 1
  92.  
  93.     ' At this stage, the curve still has all of its sharp edges. Use a `relaxation method' to smooth.
  94.     ' The new position of a point is equal to the average position of its neighboring points.
  95.     FOR j = 1 TO smoothiterations
  96.         FOR k = 2 TO numpoints - 1
  97.             tempchainx(curvenum, k) = (1 / 2) * (pointchainx(curvenum, k - 1) + pointchainx(curvenum, k + 1))
  98.             tempchainy(curvenum, k) = (1 / 2) * (pointchainy(curvenum, k - 1) + pointchainy(curvenum, k + 1))
  99.         NEXT
  100.         FOR k = 2 TO numpoints - 1
  101.             pointchainx(curvenum, k) = tempchainx(curvenum, k)
  102.             pointchainy(curvenum, k) = tempchainy(curvenum, k)
  103.         NEXT
  104.     NEXT
  105.  
  106.     GOSUB refresh
  107.  
  108. LOOP UNTIL exitflag = 1
  109.  
  110. quitsequence:
  111.  
  112. refresh:
  113. PRINT "                  Drag the left mouse button to draw a curve."
  114. PRINT "                Single left-clicking generates straight lines."
  115. PRINT "             After drawing a curve, watch it smooth after 1 second."
  116. FOR w = 1 TO curvenum
  117.     z$ = ""
  118.     FOR k = 1 TO targetpoints - 1
  119.         x1 = pointchainx(w, k)
  120.         y1 = pointchainy(w, k)
  121.         x2 = pointchainx(w, k + 1)
  122.         y2 = pointchainy(w, k + 1)
  123.         'CALL CLine(x1, y1, x2, y2, 14)
  124.  
  125.         '''
  126.         ' Fellippe, this was it ...
  127.         dr = SQR((x2 - x1) ^ 2 + (y2 - y1) ^ 2)
  128.         ang = INT(180 / 3.14 * ATN((y2 - y1) / (x2 - x1)))
  129.         IF (x2 < x1) THEN
  130.             ang = 180 + ang
  131.         END IF
  132.         z$ = z$ + " TA" + STR$(ang - 90) + "U" + STR$(dr) + " "
  133.         '''
  134.  
  135.     NEXT
  136.     ' Make a point to get DRAW started.
  137.     CALL CPset(pointchainx(w, 1), pointchainy(w, 1), 15)
  138.     ' Draw replaces CLine.
  139.     DRAW z$
  140.  
  141. SUB CPset (x, y, col)
  142.     PSET (Center.X + x, -y + Center.Y), col
  143.  
  144. SUB CLine (x1, y1, x2, y2, col)
  145.     LINE (Center.X + x1, -y1 + Center.Y)-(Center.X + x2, -y2 + Center.Y), col
« Last Edit: January 23, 2020, 07:01:14 pm by STxAxTIC »
You're not done when it works, you're done when it's right.

FellippeHeitor

  • Guest
Re: Convert mouse input to DRAW
« Reply #1 on: January 23, 2020, 09:30:14 pm »
It's great how this translated from line to DRAW, man.

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Re: Convert mouse input to DRAW
« Reply #2 on: January 25, 2020, 01:05:22 pm »
There's not much difference between unsmoothed and smoothed curve, when your mouse move scans so many points. If I was allowed only click a few points, then the additional smoothing would stand out much more. Anyway, inspiring, thanks.

Offline STxAxTIC

  • Library Staff
  • Forum Resident
  • Posts: 1091
  • he lives
    • View Profile
Re: Convert mouse input to DRAW
« Reply #3 on: January 25, 2020, 01:19:09 pm »
It's all adjustable dajan - play with the top three numbers or draw really long curves for more effect to show. There are a handful of ways to decide how much smoothing to do, even dynamic ways - but I skipped that.
You're not done when it works, you're done when it's right.

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Convert mouse input to DRAW
« Reply #4 on: January 25, 2020, 02:24:30 pm »
There's not much difference between unsmoothed and smoothed curve, when your mouse move scans so many points. If I was allowed only click a few points, then the additional smoothing would stand out much more. Anyway, inspiring, thanks.

It's all adjustable dajan - play with the top three numbers or draw really long curves for more effect to show. There are a handful of ways to decide how much smoothing to do, even dynamic ways - but I skipped that.

Well guys, it just so happens that I modified STxAxTIC's Curve Smoother code to make a Toolbox SUB out of it. In the process I modified the hell out of the rest of STxAxTIC's demo code, so it was a piece of cake to modify it again to test single clicking which I was curious about too, so here is the test code for Toolbox SUB Smooth testing with single mouse clicks:
Code: QB64: [Select]
  1. _TITLE "Smooth SUB test code" ' B+ started 2020-01-24 adapted and modified from
  2. ' Curve smoother by STxAxTIC https://www.qb64.org/forum/index.php?topic=184.msg963#msg963
  3. ' I want a Toolbox Sub that will take an array of points for a curve and smooth them out.
  4. ' 2020-01-25 add getClick, fcirc and modify thic to test single click points data.
  5.  
  6. CONST maxPoints = 500
  7. TYPE XY
  8.     x AS SINGLE
  9.     y AS SINGLE
  10.  
  11. SCREEN _NEWIMAGE(800, 600, 32)
  12.  
  13. DIM numPoints, x, y, k
  14.     'reinitialize everything for next curve test of Smooth
  15.     clean
  16.     REDIM pts(0) AS XY
  17.     numPoints = 0
  18.     'get bunch of clicks until enter keypress
  19.     DO
  20.         x = -1
  21.         getClick x, y, k
  22.         IF x <> -1 THEN AppendXY pts(), x, y: numPoints = numPoints + 1: CIRCLE (x, y), 1, &HFFFF00FF
  23.         IF numPoints >= maxPoints THEN EXIT DO
  24.         IF k = 27 THEN END
  25.  
  26.     LOOP UNTIL k = 13 AND numPoints > 1
  27.     Smooth pts(), maxPoints, 5
  28.     drawcurve pts(), 10, &HFFFFFF00
  29.     SLEEP 5
  30.  
  31. '======================= Feature SUB =======================================================================
  32. ' This code takes a dynamic points array and adds and modifies points to smooth out the data,
  33. ' to be used as Toolbox SUB. b+ 2020-01-24 adapted and modified from:
  34. ' Curve smoother by STxAxTIC https://www.qb64.org/forum/index.php?topic=184.msg963#msg963
  35. SUB Smooth (arr() AS XY, targetPoints AS INTEGER, smoothIterations AS INTEGER)
  36.     'TYPE XY
  37.     '    x AS SINGLE
  38.     '    y AS SINGLE
  39.     'END TYPE
  40.     ' targetPoints is the number of points to be in finished smoothed out array
  41.     ' smoothIterations is number of times to try and round out corners
  42.  
  43.     DIM rad2Max, kmax, k, numPoints, xfac, yfac, rad2, j
  44.     numPoints = UBOUND(arr)
  45.     REDIM _PRESERVE arr(0 TO targetPoints) AS XY
  46.     REDIM temp(0 TO targetPoints) AS XY
  47.     DO
  48.         '
  49.         ' Determine the pair of neighboring points that have the greatest separation of all pairs.
  50.         '
  51.         rad2Max = -1
  52.         kmax = -1
  53.         FOR k = 1 TO numPoints - 1
  54.             xfac = arr(k).x - arr(k + 1).x
  55.             yfac = arr(k).y - arr(k + 1).y
  56.             rad2 = xfac ^ 2 + yfac ^ 2
  57.             IF rad2 > rad2Max THEN
  58.                 kmax = k
  59.                 rad2Max = rad2
  60.             END IF
  61.         NEXT
  62.         '
  63.         ' Starting next to kmax, create a `gap' by shifting all other points by one index.
  64.         '
  65.         FOR j = numPoints TO kmax + 1 STEP -1
  66.             arr(j + 1).x = arr(j).x
  67.             arr(j + 1).y = arr(j).y
  68.         NEXT
  69.  
  70.         '
  71.         ' Fill the gap with a new point whose position is determined by the average of its neighbors.
  72.         '
  73.         arr(kmax + 1).x = .5 * (arr(kmax).x + arr(kmax + 2).x)
  74.         arr(kmax + 1).y = .5 * (arr(kmax).y + arr(kmax + 2).y)
  75.  
  76.         numPoints = numPoints + 1
  77.     LOOP UNTIL (numPoints = targetPoints)
  78.     '
  79.     ' At this stage, the curve still has all of its sharp edges. Use a `relaxation method' to smooth.
  80.     ' The new position of a point is equal to the average position of its neighboring points.
  81.     '
  82.     FOR j = 1 TO smoothIterations
  83.         FOR k = 2 TO numPoints - 1
  84.             temp(k).x = .5 * (arr(k - 1).x + arr(k + 1).x)
  85.             temp(k).y = .5 * (arr(k - 1).y + arr(k + 1).y)
  86.         NEXT
  87.         FOR k = 2 TO numPoints - 1
  88.             arr(k).x = temp(k).x
  89.             arr(k).y = temp(k).y
  90.         NEXT
  91.     NEXT
  92.  
  93. SUB clean
  94.     CLS
  95.     yCP 10, "Click mouse to hearts content or 500 times. Escape quits testing."
  96.     yCP 30, "Press enter to test curve drawing..."
  97.     yCP 50, "b+ Smooth SUB that uses STxAxTIC's Curve Smoother code."
  98.     yCP 600 - 30, "Curve will disappear in 5 secs or keypress then Click your way to another curve."
  99.  
  100. SUB drawcurve (arr() AS XY, thk, c AS _UNSIGNED LONG)
  101.     DIM k
  102.     FOR k = 1 TO UBOUND(arr) - 1
  103.         thic arr(k).x, arr(k).y, arr(k + 1).x, arr(k + 1).y, thk, c
  104.     NEXT
  105.  
  106. 'update 2020-01-24 to include PD2 inside the sub
  107. SUB thic (x1, y1, x2, y2, thick, K AS _UNSIGNED LONG)
  108.     DIM PD2 AS DOUBLE, t2 AS SINGLE, a AS SINGLE, x3 AS SINGLE, y3 AS SINGLE, x4 AS SINGLE, y4 AS SINGLE
  109.     DIM x5 AS SINGLE, y5 AS SINGLE, x6 AS SINGLE, y6 AS SINGLE
  110.     PD2 = 1.570796326794897
  111.     t2 = thick / 2
  112.     IF t2 < 1 THEN t2 = 1
  113.     a = _ATAN2(y2 - y1, x2 - x1)
  114.     x3 = x1 + t2 * COS(a + PD2)
  115.     y3 = y1 + t2 * SIN(a + PD2)
  116.     x4 = x1 + t2 * COS(a - PD2)
  117.     y4 = y1 + t2 * SIN(a - PD2)
  118.     x5 = x2 + t2 * COS(a + PD2)
  119.     y5 = y2 + t2 * SIN(a + PD2)
  120.     x6 = x2 + t2 * COS(a - PD2)
  121.     y6 = y2 + t2 * SIN(a - PD2)
  122.     ftri x6, y6, x4, y4, x3, y3, K
  123.     ftri x3, y3, x5, y5, x6, y6, K
  124.     fcirc x1, y1, t2, K 'added on for this app
  125.     fcirc x2, y2, t2, K ' ditto
  126.  
  127. '2019-12-16 fix by Steve saves some time with STATIC and saves and restores last dest
  128. SUB ftri (x1, y1, x2, y2, x3, y3, K AS _UNSIGNED LONG)
  129.     DIM D AS LONG
  130.     STATIC a&
  131.     D = _DEST
  132.     IF a& = 0 THEN a& = _NEWIMAGE(1, 1, 32)
  133.     _DEST a&
  134.     _DONTBLEND a& '  '<<<< new 2019-12-16 fix
  135.     PSET (0, 0), K
  136.     _BLEND a& '<<<< new 2019-12-16 fix
  137.     _DEST D
  138.     _MAPTRIANGLE _SEAMLESS(0, 0)-(0, 0)-(0, 0), a& TO(x1, y1)-(x2, y2)-(x3, y3)
  139.  
  140. 'update 2020-01-24 now with _printwidth so can use any FONT
  141. SUB yCP (y, s$) 'for xmax pixel wide graphics screen Center Print at pixel y row
  142.     _PRINTSTRING ((_WIDTH - _PRINTWIDTH(s$)) / 2, y), s$
  143.  
  144. ' modified for XY type
  145. SUB AppendXY (arr() AS XY, addx, addy)
  146.     REDIM _PRESERVE arr(LBOUND(arr) TO UBOUND(arr) + 1) AS XY
  147.     arr(UBOUND(arr)).x = addx
  148.     arr(UBOUND(arr)).y = addy
  149.  
  150. 'getClick returns the mouse x, y position WHEN THE MOUSE WAS RELEASED! or keypress ASC 27 or 32 to 125
  151. '2019-08-06 Test now with new mBox and inputBox procedures
  152. 'found  mBox needed a _KEYCLEAR, how about inputBox?  OK had _KEYCLEAR already
  153. SUB getClick (mx, my, q)
  154.     DIM mb, i
  155.     mb = _MOUSEBUTTON(1)
  156.     WHILE mb
  157.         WHILE _MOUSEINPUT: WEND '<<<<<<<<<<<<<<<<<<<<  clear previous mb
  158.         mb = _MOUSEBUTTON(1)
  159.     WEND
  160.     _KEYCLEAR 'clear previous key presses
  161.     mx = -1: my = -1: q = 0
  162.     DO WHILE mx = -1 AND my = -1
  163.         q = _KEYHIT
  164.         IF q = 27 OR q = 13 THEN _KEYCLEAR: EXIT SUB  'edit 2020-01-25 for this app
  165.         i = _MOUSEINPUT: mb = _MOUSEBUTTON(1)
  166.         'IF mb THEN
  167.         DO WHILE mb 'wait for release
  168.             q = _KEYHIT
  169.             IF q = 27 OR q = 13 THEN EXIT SUB 'edit 2020-01-25 for this app
  170.             i = _MOUSEINPUT: mb = _MOUSEBUTTON(1): mx = _MOUSEX: my = _MOUSEY
  171.             _LIMIT 1000
  172.         LOOP
  173.         _LIMIT 1000
  174.     LOOP
  175.  
  176. 'from Steve Gold standard
  177. SUB fcirc (CX AS INTEGER, CY AS INTEGER, R AS INTEGER, C AS _UNSIGNED LONG)
  178.     DIM Radius AS INTEGER, RadiusError AS INTEGER
  179.     DIM X AS INTEGER, Y AS INTEGER
  180.     Radius = ABS(R): RadiusError = -Radius: X = Radius: Y = 0
  181.     IF Radius = 0 THEN PSET (CX, CY), C: EXIT SUB
  182.     LINE (CX - X, CY)-(CX + X, CY), C, BF
  183.     WHILE X > Y
  184.         RadiusError = RadiusError + Y * 2 + 1
  185.         IF RadiusError >= 0 THEN
  186.             IF X <> Y + 1 THEN
  187.                 LINE (CX - Y, CY - X)-(CX + Y, CY - X), C, BF
  188.                 LINE (CX - Y, CY + X)-(CX + Y, CY + X), C, BF
  189.             END IF
  190.             X = X - 1
  191.             RadiusError = RadiusError - X * 2
  192.         END IF
  193.         Y = Y + 1
  194.         LINE (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
  195.         LINE (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
  196.     WEND
  197.  
  198.  

Note: Smooth SUB name is supposed to be that, not SMOOTH in all caps.

And it's true what STxAxTIC says, the Smoothing effect is really noticeable only when you draw really long scribbles (update) when the targetNumber is high like 500 or 1000.
Smooth SUB test.PNG
* Smooth SUB test.PNG (Filesize: 16.76 KB, Dimensions: 805x622, Views: 242)
« Last Edit: January 25, 2020, 03:33:15 pm by bplus »

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Re: Convert mouse input to DRAW
« Reply #5 on: January 25, 2020, 03:04:55 pm »
...so here is the test code for Toolbox SUB Smooth testing with single mouse clicks

bplus, that was exactly what i meant, but why doesn't your code smooth the curve? Or it does just very slightly and very close to the points itself? Here's  smoothed 4 point curve that is more like rectange. Also, can I make lines thin in your demo?

STxAxTIC, you are right, when I move the mouse very quickly, the points are further from each other and smoothenes is more obvious then. But I was curious about the same as bplus, how would it curve a few separately clicked points.
curve.png
* curve.png (Filesize: 8.47 KB, Dimensions: 799x618, Views: 240)
« Last Edit: January 25, 2020, 03:05:59 pm by dajan »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
    • View Profile
Re: Convert mouse input to DRAW
« Reply #6 on: January 25, 2020, 03:21:05 pm »
Hi dajan, try maxPoints at 20 and interations at 35
4 points mapoints = 20 iiterations = 35.PNG
* 4 points mapoints = 20 iiterations = 35.PNG (Filesize: 13.4 KB, Dimensions: 801x637, Views: 243)

Offline dajan

  • Newbie
  • Posts: 41
    • View Profile
Re: Convert mouse input to DRAW
« Reply #7 on: January 25, 2020, 04:28:30 pm »
bplus, yeah thanks.
the maxpoints=100 and iterations=30 works about how I would expect.