Draw a line code not working

The intention of the code below is to enable the user to free-hand draw (or finger paint) one simple continuous line on the screen. When I run it in the simulator, instead of a continuous line, I get a line that is interrupted even though I keep holding the mouse.

I’m not sure why the code is not working. It’s supposed to simply append the line, once it has been created.


touchListener = function(event)

if(event.phase == “began”) then

lineInitialCoordinatesX = event.x

lineInitialCoordinatesY = event.y

elseif(event.phase == “moved”) then

if lineAlreadyCreated then

line:append(event.x,event.y)

else

line = display.newLine(lineInitialCoordinatesX, lineInitialCoordinatesY, event.x, event.y)

line:setStrokeColor (1,1,1)

line.strokeWidth = 20

lineAlreadyCreated = true

end

elseif(event.phase == “ended”) then

line:append(event.x,event.y)

end

return true

end

lineAlreadyCreated = false

Runtime:addEventListener(“touch”, touchListener)

You’re creating lots of curves in the line, so you need to check the distance between each additional point on the line; If it is too short, don’t add it - i.e.: if the distance between the last added point and the new one is less than, say, 1, don’t add it.

To further improve the code, I would not bother with having variables like ‘lineAlreadyCreated’ but simply use the fact that the ‘line’ variable is either nil or not.

Further, I believe you should be destroying the whole line and recreating it and that each version of the line should be created by passing in a table of x,y values, which is what will be maintained when you’re adding points - not the line itself.

Try this:

local group, pts, line = display.newGroup(), {}, nil local function length( ax, ay, bx, by ) local width, height = bx-ax, by-ay return (width\*width + height\*height)^0.5 end function group:touch(event) if (event.phase == "began") then pts[#pts+1] = event.x pts[#pts+1] = event.y group.hasFocus = true display.currentStage:setFocus( group ) return true elseif (group.hasFocus) then if (event.phase == "moved") then if (length( pts[#pts-1], pts[#pts], event.x, event.y ) \> 1) then if (line) then line:removeSelf() line = nil end pts[#pts+1] = event.x pts[#pts+1] = event.y line = display.newLine( unpack( pts ) ) line:setStrokeColor (1,1,1) line.strokeWidth = 20 end else group.hasFocus = false display.currentStage:setFocus( nil ) end return true end return false end Runtime:addEventListener( "touch", group )

Thank you so much @horacebury! Your code does exactly what I had in mind.

Can I ask for my understanding: What’s the reason why it’s better to destroy the line and recreate it each time, rather than appending to it? Is it because the Corona line:append command is buggy? I would have thought the line:append command is intended for just the kind of use cases that I was trying to implement.

Sorry @horacebury but I have to disagree.

  1. A “moved” event will only fire it the touch point has actually moved from the previous touch point

  2. Agreed

  3. Destroying the line on each “moved” update and redrawing it as that is massively inefficient.  It is like reading a page of text and for each new word you read you start at the beginning of the page again!

FYI, here is a simplified version for you (so you can learn).  Don’t ever have 20+ lines of code when 10 does the job!

local line = nil touchListener = function(event)   if event.phase == "moved" then     if line then       line:append(event.x,event.y)     else       line = display.newLine(event.xStart, event.yStart, event.x, event.y)       line:setStrokeColor (1,1,1)       line.strokeWidth = 20       lineAlreadyCreated = true     end   elseif event.phase == "ended" then     line:append(event.x,event.y)   end   return true end Runtime:addEventListener("touch", touchListener)

And here is the output from the above code

Sorry, SGS, while I totally respect what you do here on the forums and the code you write, I also have to disagree this time. As that is the case, I guess the burden is on me seeing as you’ve provided smaller code.

1. A “moved” event will only fire it the touch point has actually moved from the previous touch point

 

This is true, though the granularity of the points could be almost on top of each other. It is definitely possible to produce touch event values where there two consecutive move phase locations are less than 1 pixel or point apart. This, in my estimation, is causing the ‘folding’ (for want of a better description) in the original code. It does not happen in my code, though I see it happening in your code as well. I’m not sure why it’s not evident in your screenshot, but copy/pasting your code into a brand new main.lua and running it cold produced the same result as Rene found.

 

3. Destroying the line on each “moved” update and redrawing it as that is massively inefficient.  It is like reading a page of text and for each new word you read you start at the beginning of the page again!

 

I do agree here. I was posting code which went quite a bit further than really necessary, I’ll admit. For example, the use of the .hasFocus value is to handle situations where the touch event moved over another object which could handle touch events and was not sensibly filtering input. In that case, you could easily find the touch event being picked up by another touch handler and everything would go wrong. Therefore, I always include that logic, even in the smallest examples.

 

Having said that, the reason to delete the line and recreate it again is because I’m used to thinking that way because in my typical situation it is logical. Without needlessly going into detail, I will say that the number of points used to render various things should be controlled or things will eventually go wrong.

 

I will say that the code could be improved not to delete the line but the problem of one touch being too close to the previous still needs to be handled. I would comment, though, that any decent device these days should be able to delete a line and recreate it without issue. I certainly wouldn’t do it with anything more complex.

 

FYI, here is a simplified version for you (so you can learn).  Don’t ever have 20+ lines of code when 10 does the job!

 

Of course, that’s a great starting point and I kinda wish I’d provided simpler code now. Having said that, here’s a version of my code which does not delete the line. It doesn’t really reduce the number of code lines but it won’t have the performance issue and the points used to build the line will not be excessive (One flaw is that it is no longer possible to retrieve the list of x,y values which build the line, which I consider a requirement - even for learning):

local group, pt, line = display.newGroup(), {}, nil local function length( ax, ay, bx, by ) local width, height = bx-ax, by-ay return (width\*width + height\*height)^0.5 end function group:touch(event) if (event.phase == "began") then group.hasFocus = true display.currentStage:setFocus( group ) pt = { x=event.x, y=event.y } return true elseif (group.hasFocus) then if (event.phase == "moved") then if (length( pt.x, pt.y, event.x, event.y ) \> 1) then pt = { x=event.x, y=event.y } if (line == nil) then line = display.newLine( event.xStart, event.yStart, pt.x, pt.y ) line:setStrokeColor (1,1,1) line.strokeWidth = 20 else line:append( pt.x, pt.y ) end end else group.hasFocus = false display.currentStage:setFocus( nil ) end return true end return false end Runtime:addEventListener( "touch", group )

Always good to debate!  

I don’t get this “folding” you are referring to with my code - as you saw from the screenshot - so I didn’t have to code around it.  The joys of debugging eh?

  1. A lot depends on the canvas resolution and the device resolution if a distance check is required or not.  A 320 x 400 canvas on HD device definitely would but a 640 x 1136 canvas on an iPhone 5 would be pointless as there would be parity between screen and canvas pixels.  Personally, I usually check for a delta movement of more than 5px before acting on “moved” events.

  2. Absolutely, I would always use a focus check and display.currentStage:setFocus() in production code - I was keeping it simple for the OP to concentrate on just the drawing.

  3. If the OP wants to save the drawing as a jpg then storing point data is not required.  But if the drawing was to be editable then I agree that storing the point data has merit.

  4. I wouldn’t use a line to do this if it was me as the result isn’t great - no anti-aliasing for example.  But we could get into much more complicated principles which may be too much.

When answering questions I try and apply the most simple and precise code to achieve what the OP wants. Not necessarily “fully decorated” code which is what you provided.  Neither are necessarily wrong, they are just slightly different approaches.

I think the folding (for want of a better term) is happening when the touch moves relatively slowly. On my machine it happens at any simulator zoom level, at any touch speed. My app is running as a screen size of 720x1024 (or something close to that, I’m not on my machine right now.)

  1. Points for learning vs production. I wonder if I’ve been posting overly-complex code? (Ouch)

  2. True. We don’t know as he didn’t state, but I always assume the data is important. Certainly is in my inputs.

Agreed on posting teaching code. I tend to post the most robust code I can because if anyone can take from it, I want them to be able to reuse it and not have to ask why it breaks in another situation.

@rene.anderson Do you have any more questions about the discussion above?

Having said that, we’re going a long way to describe how to draw a line!

This thread shows everything that is right with coronalabs, thank you guys and everyone else who helps us all out so much, and go so far insanely into detail, that anyone can understand.

Thank you so much horacebury and Sphere Game Studios for your input! Although drawing a line is a very simple task, I feel it is well worth exploring thoroughly for its fundamental nature - and as the debate shows, I think, it’s not quite as straightforward as one might initially think. And keeping in mind, we’re not even talking about beautiful lines here (the kind you see in the game Blek, for instance) but very basic, prototypical (ugly) line drawing.

Sphere Game Studios - thank you so much for your simplified code! On my machine, however, it produces the same strange behaviour as my original code did. Here is a link to a screen video of it (It’s not possible to upload a video directly into this post, is it? Only images?). As the mouse moves faster the line gets more continuous, but at low mouse speeds the line keeps breaking up. I’d like to find the reason for this but it might take me a while to figure it out. Maybe it has to do with configuration?

In regards to the need to store point data - well, it turns out I’d like to be able to determine whether the line intersects with other display objects on the screen, for example a circle. I haven’t worked out the code for this but I did wonder whether I will need to write code that stores the point data in a table as the line is drawn, or whether the Corona engine stores the point data automatically and whether there be an API to read the point data of a line object that has been drawn, or an API that determines whether two display objects intersect.

horacebury, could I ask why you’ve used a “return false” (rather than ‘return true’) at the end of your code (third to last line)? What purpose does the ‘return false’ have?

And horacebuy, I’d love to know more about the advantages you see in the approach of destroying and recreating lines, as opposed to appending. You mentioned it has something to do with retaining control?

Sphere Game Studios - you mentioned if the drawing was to be editable then you wouldn’t use a line to do that. Can I ask what you would use instead? I’d love to find out more.

I used my code on both fast and slow movement and I always get a continual line.  I even built for device and got the same result so I cannot explain your results… as even a slow machine would simply draw a line from the last known point to the latest point (skipping intermediary points) with my code.  i.e. you would lose detail but still would have an continuous line.

I would copy Photoshop and draw an actual brush (i.e. an png) but your project just got way more complicated.

I will need to write code that stores the point data in a table as the line is drawn

This is not done for you so you will need to store the point data and write it to a file.

why you’ve used a “return false”

I should have done this before:

-- always local to avoid global references local group, pt, line = display.newGroup(), {}, nil -- you can get other math functions here: https://gist.github.com/HoraceBury/9431861 local function length( ax, ay, bx, by ) local width, height = bx-ax, by-ay return (width\*width + height\*height)^0.5 end function group:touch(event) if (event.phase == "began") then -- keep track of what has the touch input group.hasFocus = true display.currentStage:setFocus( group ) -- we're not storing every point, but that would be easy (see my first listing) pt = { x=event.x, y=event.y } -- we always report that we capture the began phase return true -- only deal with moved, ended or cancelled phases if this object is capturing the touch input elseif (group.hasFocus) then -- almost every touch will be a moved phase if (event.phase == "moved") then if (length( pt.x, pt.y, event.x, event.y ) \> 1) then -- again, could store every input point pt = { x=event.x, y=event.y } if (line == nil) then line = display.newLine( event.xStart, event.yStart, pt.x, pt.y ) line:setStrokeColor (1,1,1) line.strokeWidth = 20 else line:append( pt.x, pt.y ) end end else -- effectively ended and cancelled phases... -- we no longer want to track which object is taking the input and the stage should allow other objects to get the touch events group.hasFocus = false display.currentStage:setFocus( nil ) end -- moved and cancelled events were handled by this object, so return true return true end -- here we have not handled any phase because hasFocus was false, so this touch event was not ours, so return false return false end Runtime:addEventListener( "touch", group )

I should point out that if this were a multitouch environment, you would want to be identifying the Touch ID at the same time as checking the hasFocus (or instead of) upon the object receiving the event, so as to discard any events the object is not interested in.

TL;DR: The ‘return true’ and ‘return false’ tell the system that we handled or did not handle the event (respectively) and so to stop or continue (respectively) passing the event down the list of objects handling touches.

Redrawing the line completely is necessary when the whole line changes. You might be smoothing the line and so the input points would not even be the points generated from touches, but calculated using a bezier curve (as mine are). Drawing the initial line could be directly rendered, but then remove the line and smooth it when drawing is finished, for example.

I’m still confused as to why SGS is not seeing the breaking lines.

In my simplified code all “moved” is doing is appending points to a line. Logically the only thing that would break this code is a break in the touch event chain causing an “ended” and a new “began”.  This would be simple to test with a print in non “moved” phases.

I was using a 480x800 canvas on an HD device but screen resolution shouldn’t really be the issue.

Hey SGS - I added some print commands to your code and played around with it a bit more on my machine:

[lua]local line = nil

touchListener = function(event)

  if event.phase == “moved” then

    print(“moved”)

    if line then

  print(“append”, event.x, event.y)

      line:append(event.x,event.y)

    else

      print(“began”, event.xStart, event.yStart, event.x, event.y)

      line = display.newLine(event.xStart, event.yStart, event.x, event.y)

      line:setStrokeColor (1,1,1)

      line.strokeWidth = 20

    end

  elseif event.phase == “ended” then

    print(“ended”)

    line:append(event.x,event.y)

  end

  return true

end

Runtime:addEventListener(“touch”, touchListener)[/lua]

I can’t say I’ve figured out what the issue is but I have two observations when I look at what’s been drawn on the simulator screen and what’s been printed to the console. Here is one example where I pressed down the mouse, moved the mouse (pretty quickly) and then released the mouse:

Jun 06 08:57:39.339 moved began 150 86 150 86 Jun 06 08:57:39.363 moved append 150 86 Jun 06 08:57:39.393 moved append 150 86 Jun 06 08:57:39.394 moved append 150 86 moved append 150 86 Jun 06 08:57:39.397 moved append 150 86 Jun 06 08:57:39.425 moved append 150 86 Jun 06 08:57:39.425 moved append 150 86 moved append 150 86 Jun 06 08:57:39.429 moved append 150 86 Jun 06 08:57:39.456 moved append 150 86 Jun 06 08:57:39.456 moved append 150 86 moved append 150 86 Jun 06 08:57:39.462 moved append 150 86 Jun 06 08:57:39.490 moved append 150 86 Jun 06 08:57:39.491 moved append 150 86 moved append 150 86 Jun 06 08:57:39.494 moved append 150 86 Jun 06 08:57:39.515 moved Jun 06 08:57:39.515 append 150 86 moved append 150 86 Jun 06 08:57:39.549 moved append 150 86 Jun 06 08:57:39.549 moved append 150 86 moved append 150 86 Jun 06 08:57:39.553 moved Jun 06 08:57:39.553 append 150 86 Jun 06 08:57:39.560 moved append 150 86 Jun 06 08:57:39.591 moved append 150 86 Jun 06 08:57:39.592 moved append 150 86 moved append 152 88 moved append 152 88 moved append 154 92 Jun 06 08:57:39.596 moved append 154 92 Jun 06 08:57:39.596 moved append 156 96 Jun 06 08:57:39.623 moved append 156 96 Jun 06 08:57:39.624 moved append 158 100 moved append 158 100 moved append 160 102 moved append 160 102 moved append 162 106 Jun 06 08:57:39.628 moved Jun 06 08:57:39.628 append 162 106 moved append 162 110 Jun 06 08:57:39.646 moved append 162 110 Jun 06 08:57:39.646 moved append 164 114 moved append 164 114 moved append 166 118 Jun 06 08:57:39.686 moved append 168 128 Jun 06 08:57:39.686 moved append 168 128 moved append 170 132 moved append 170 132 moved append 170 142 moved append 170 142 moved append 172 156 Jun 06 08:57:39.691 moved append 172 156 Jun 06 08:57:39.691 moved append 172 168 Jun 06 08:57:39.711 moved append 172 168 Jun 06 08:57:39.711 moved append 172 182 moved append 172 182 moved append 174 188 Jun 06 08:57:39.741 moved append 174 188 Jun 06 08:57:39.741 moved append 174 202 moved append 174 202 moved append 174 214 moved append 174 214 moved append 176 228 moved append 176 228 moved append 178 238 Jun 06 08:57:39.747 moved append 178 238 Jun 06 08:57:39.770 moved append 178 244 Jun 06 08:57:39.771 moved append 178 244 moved append 180 250 moved append 180 250 moved append 180 258 Jun 06 08:57:39.775 moved append 180 258 Jun 06 08:57:39.775 moved append 180 262 Jun 06 08:57:39.779 moved append 180 262 Jun 06 08:57:39.798 moved append 180 264 Jun 06 08:57:39.799 moved append 180 264 moved append 180 266 moved append 180 266 moved append 180 266 Jun 06 08:57:39.804 ended

Here is what was drawn on the simulator screen: 

<Hmmm … when I try to insert my screenshot in this post I keep getting the error ‘You are not allowed to use that image extension on this community’ no matter whether I use png or jpeg. Not sure why that is or what to do about it … >

So what was drawn on the screen is just a small triangle, and when I look at the console output I see lots of repetitions, i.e. identical x-y-pairs being appended, starting with the x-y-pair “156 86”.

What this makes me wonder is whether the Corona append function is buggy and doesn’t work well when you keep appending the same point to a line. I realise though that this doesn’t explain why the code seems to be working fine on your machine.

OK there is definitely something with your setup

If I run the same thing I get

22:20:17.032 began 198.33332824707 342.5 198.33332824707 340.83331298828 22:20:17.079 append 199.16665649414 339.16665649414 22:20:17.095 append 199.16665649414 338.33331298828 22:20:17.111 append 199.16665649414 337.5 22:20:17.111 append 199.16665649414 336.66665649414 22:20:17.142 append 200 335.83331298828 22:20:17.142 append 200 334.16665649414 22:20:17.157 append 200.83332824707 333.33331298828

You should never get a moved event if you haven’t moved.  As you can see mine fires an event if either x or y has moved by 1px (which is why IMHO that checking the move is more than 1px is not needed).  Also I am showing fractional whereas you are only showing ints.

What does your config.lua look like?

Also if you just tap the screen at various points my code should just draw a straight line from point to point.  DO you see that behaviour?

To attach screen shots, you have to go to the “More Reply Options” button and upload your image from there.

Rob

what’s your build number?  the fractional touch event x/y was only recently fixed

3079 so recent(ish)

@dave do you get a continuous line with this code?

local line = nil touchListener = function(event) if event.phase == "moved" then if line then line:append(event.x,event.y) else line = display.newLine(event.xStart, event.yStart, event.x, event.y) line:setStrokeColor (1,1,1) line.strokeWidth = 20 end elseif event.phase == "ended" then line:append(event.x,event.y) end return true end Runtime:addEventListener("touch", touchListener)&nbsp;

*update*   latest daily build still has fractional event.x, event.y values.  So I guess not fixed…

23:53:40.804 Copyright (C) 2009-2017 C o r o n a L a b s I n c . 23:53:40.804 Version: 3.0.0 23:53:40.804 Build: 2017.3085 23:53:40.804 Platform: GenericAndroidDevice / x64 / 10.0 / Intel(R) HD Graphics 520 / 4.4.0 - Build 21.20.16.4590 / 2017.3085 / en\_GB | GB | en\_GB | en 23:53:42.360 187.5 408.33331298828 23:53:42.391 183.33332824707 404.16665649414 23:53:42.391 183.33332824707 403.33331298828 23:53:42.406 182.5 400.83331298828 23:53:42.406 181.66665649414 399.16665649414 23:53:42.406 180 398.33331298828 23:53:42.422 180 395 23:53:42.422 179.16665649414 395 23:53:42.437 178.33332824707 390.83331298828

sorry for confusion, my question was for the OP:  what’s THEIR build#?

OP’s simulator output looks like int’s (but could result from just low-res simulator device that matches low-res content)

BUT, if using an old build, then event values can be quantized to content resolution (throwing away some detail, resulting in duplicate coords, etc).  might just be a red herring, but symptoms match.

Your output, with the fractionals, looks like what I’d expect from “random” content size on “random” device.

And yes, your code draws a smooth line for me. :slight_smile:

I will rest my case