Problem with CanvasDrawDrag Example

As suggested in another thread, I’ve based my current project on the CanvasDrawDrag Example. However it doesn’t seem to be working as intended. The problem is in the PicHit function.

Private Function PicHit(x As Integer, y As Integer) as DragPic
  // Find the DragPic hit by the point x,y (if any).
  
  For i As Integer = UBound(mPics) DownTo 0
    Dim pic As DragPic = mPics(i)
    
    // Are we within the bounds of this picture?
    Dim picX As Integer = x - pic.x
    Dim picY As Integer = y - pic.y
    Dim inPicture As Boolean = _
    0 <= picX And picX < pic.Image.Graphics.Width And _
    0 <= picY And picY < pic.Image.Graphics.Height
    
    If inPicture Then
      // So we're in the picture, but is this pixel opaque?
      Dim pixel As Color = pic.Image.Graphics.Pixel(picX, picY) 
      If pixel.Alpha < 255 Then Return pic
    End If
  Next
  
  Return Nil
End Function

This is supposed to check to see which item the user clicked on. The items are all ovals with an alpha value of 66. The first part that checks to see if the the mouse click is inside the picture’s bounding box is okay, but the second part that checks the pixel alpha and rejects the picture if the user has clicked outside the oval doesn’t work. The picture is selected regardless of where in the bounding box user has clicked either inside or outside the oval. I’m not familiar enough with how alpha channels work. Could someone have a look at this and advise what needs to be fixed?

FYI, I added some code to display the alpha value of the last pixel that was clicked, and with hiDPI disabled it returns 66 in all cases except when you click exactly on the oval border, in which case it returns 0. When HiDPI is enabled, behaviour is even stranger. The mouse coordinates don’t appear to be exactly at the point of the arrow cursor, but slightly above and to the left. (I’m running this on a MacBook Pro with Retina display and OSX 10.10.5.)

regardless of this example… This is NOT how I would suggest you do it. But my suggestion may be a bit more advanced than the level you are currently at (no offense if I’m wrong about that :slight_smile: )

  • create a class that represents your shape (be it square, rectangle, circle, whatever), this includes it color attributes
  • create an array (collection/dictionary/database) of the instances of all the shapes
  • in the PAINT event, cycle thru that collection and draw the shapes

Here is the “advanced” part
the class will need attributes similar to these

  • shape type (is it a circle? rectangle? star?)
  • the points the define the bounding box (left, top, width, height) - you “move” it by changing x and/or y
  • color information for fill, border, alpha etc
  • a METHOD that given (g as graphics) draws ITSELF
  • a METHOD that given (x as Integer,y as Integer) return TRUE if X,Y are inside the defined border (which maybe an oval inside the bounding box

This way, each shape takes care of itself… it knows its own attributes (what do I look like), it knows how to behave (how am I drawn), and (is a given coordinate inside my borders)

Once you grasp this design concept and make it work for you, you can extend the shapes to anything you want, and draw them however you need (including scaling them, rotating them etc)

I created a clone similar to Visio using the concepts described above (and it had hundreds of pre-defined polygon shapes), but they all mapped to a single custom class… So I had ONE piece of code that could handle ANY shape I wanted, and I didn’t have to write special cases to handle special shapes.

Hope this was educational, and not off-putting… But I’d hate to see you invest a ton of time, and realize the road you took can’t get you to where you want to go (trust me, been there, done that and have the T-Shirt to prove it :slight_smile: )

Hi Dave. Thanks for your comments.

Actually I have created a class that does essentially what you’ve described, then replaced the original dragPic code to work with the new class. I haven’t yet implemented the method for determining if the user has clicked on the visible part of the object. I was hoping I could use the alpha value technique as was used in the original PicHit function. However, when I couldn’t get my version of the PicHit function to work, I went back to the original example to see how it behaved, and discovered that it doesn’t seem to work at all.

I now have the example working correctly. There were two bugs in the PicHit function. Corrected code is as follows:

Private Function PicHit(x As Integer, y As Integer) as DragPic
  // Find the DragPic hit by the point x,y (if any).
  dim pixelOffsetCorrection As Integer
  if app.SupportsHiDPI then
    pixelOffsetCorrection=4
  else
    pixelOffsetCorrection=0
  end if
  For i As Integer = UBound(mPics) DownTo 0
    Dim pic As DragPic = mPics(i)
    
    // Are we within the bounds of this picture?
    Dim picX As Integer = x - pic.x
    Dim picY As Integer = y - pic.y
    Dim inPicture As Boolean = _
    0 <= picX And picX < pic.Image.Graphics.Width And _
    0 <= picY And picY < pic.Image.Graphics.Height
    
    If inPicture Then
      // So we're in the picture, but is this pixel opaque?
      Dim pixel As Color = pic.Image.Graphics.Pixel(picX*pic.Image.Graphics.ScaleX,_
       picY*pic.Image.Graphics.ScaleY)    '****** added scale factor
      PixAlpha=pixel.Alpha
      PixColor=pixel
      If pixel.Alpha >0  Then Return pic   '****** Changed from pixel.Alpha < 255...
    End If
  Next
  
  Return Nil
End Function
  1. The scale factor needs to be included when looking at the pixel, or else it won’t work in HiDPI mode.
  2. The alpha value that is returned will be zero if when clicking outside the oval. Changing the condition in the if statement to >0 seems to resolve the issue, though I’m still not entirely comfortable with what’s going on with respect to the alpha channel.

It appears that when the example was updated for compatibility with a new version of Xojo, it must have been given just a cursory test, and these problems were missed.

Giving this problem further consideration, the fix I mentioned above will work fine in this specific example, because the objects have a non-zero alpha. So, this would not work in a more general case.

I notice that when clicking inside the bounding box, but outside of the visible object, the color that is returned from the clicked pixel is &c00000000 (opaque black). If one were to make sure that this color is never used anywhere else in the object, then this could be used as the basis of the test. But again, this example code appears to be suggesting a method that is not good programming practice.

Looking though the old examples supplied with RealBasic2009 (the most recent one that I have prior to Xojo2016) I see the old example project was called DragPics and the PicHit function naturally worked with masks. This is the old PicHit code:

Function PicHit(x As Integer, y As Integer) As DragPic
  // Find the DragPic hit by the point x,y (if any).
  
  Dim dp As DragPic
  Dim i, halfw, halfh As Integer
  
  for i = UBound(mPics) downTo 0
    dp = mPics(i)
    halfw = dp.image.width / 2
    halfh = dp.image.height / 2
    // Are we within the bounds of this picture?
    if Abs(x - dp.x) < halfw and Abs(y - dp.y) < halfh then
      // we're in the bounds of this picture, but 
      // does it hit the mask?
      if dp.image.mask.graphics.Pixel(x-dp.x+halfw, y-dp.y+halfh).red <= maskHitValue then
        return dp
      end if
    end if
  next
  
  return nil
End Function

So, as I suspected, it was given a rudimentary update from the old code, and never tested properly.

I just ported the example to iOS and it should be included in a future release.

Basically, PicHit simply verifies the picture is not transparent. That leaves a world of different colors. And frankly, I would not see the point of moving around invisible pictures.

I have used that as a basis for a layout editor, and was happy to find that example to save me hours of work.

I think that the reasoning behind testing the alpha is not to prevent moving invisible pictures, but to prevent selecting an object whose visible part is only a small fraction of the area of its bounding box, when the user clicks a long distance away from the visible part. For example, a line drawn at an angle of 45° will have a bounding box much larger than the area of the line itself. If the line runs from lower left to upper right of its bounding box, then you wouldn’t want to be selecting the line by clicking near the top left or bottom right corners of the bounding box because this is a very long way away from the line. In a similar situation, I’m working with rotated text, and 45° rotated text suffers from exactly this same problem.

BTW, if you run the old RB DragPics example, you’ll see that it behaves as I suggested it should: If you click inside the oval’s bounding box but outside of the oval itself, then it isn’t selected. You have to click inside the visible part to select it. The current CanvasDrawDrag example doesn’t do this. Clicking anywhere in the bounding box will select the object.

You know what ? Any one of us can improve on an example project, or submit a new one, as I did for the iOS version.

If you correct the current project, send it to Paul Lefebvre, he will be glad to put an improved version up.

Robert… might I ask exactly what you end result is going to be?
Or is this simply a educational exercise?

What I’m working on is a fairly simple application where I can import an image into a canvas and then add some annotations to it: primarily text, but also some other basic objects such as lines, rectangles, circles, etc., with the ability to reposition them by dragging them around. This is partly a learning exercise, but the end result will be very useful for some other work I’m doing.

At this point my project no longer bears much resemblance to the CanvasDrawDrag example, because I have created a general graphics object class (much like you suggested) that will eventually contain the various properties for tracking the object type and associated data, and methods for drawing and detecting if the object has been clicked on.

At this point I’m ready to abandon the technique used in the PicHit function, because I’ve concluded that although it is simple, it has very limited use.

The only reason I keep talking about the CanvasDrawDrag example is because I was trying to use it as a way to learn how the alpha channel could be used to detect mouse clicks on the visible part of the object. In the end, I’ve concluded that it’s just a very bad example, though it was probably okay in its earlier incarnation when it used masks. In it’s current form, I don’t see any way that it could be made to work reliably, but perhaps one of the graphics gurus can demonstrate otherwise.

On that note, I’ll abandon CanvasDrawDrag, and develop a different hit detection method. I have lots of experience with analytical geometry, so the code won’t be a problem.

comparing the X,Y coordinates of the mouse to the geometry of the object is your best bet.

PointInRectangle is easy (it was in the original example, just not named that)
PointInOval should be easy… compare the distance/angle of X,Y to the center of the object, an oval will have a varying radius, a circle will have a constant radius
any “etc.” shapes will need to be handled accordingly :slight_smile:

To avoid dragging the boundaries is fairly easy : don’t return the object if pixel is the color of the transparent part of the picture (&h00000000):

[code]Private Function PicHit(x As Integer, y As Integer) as DragPic
// Find the DragPic hit by the point x,y (if any).

For i As Integer = UBound(mPics) DownTo 0
Dim pic As DragPic = mPics(i)

// Are we within the bounds of this picture?
Dim picX As Integer = x - pic.x
Dim picY As Integer = y - pic.y
Dim inPicture As Boolean = _
0 <= picX And picX < pic.Image.Graphics.Width And _
0 <= picY And picY < pic.Image.Graphics.Height

If inPicture Then
  // So we're in the picture, but is this pixel opaque?
  Dim pixel As Color = pic.Image.Graphics.Pixel(picX, picY) 
  If pixel.Alpha < 255 and pixel <> &c00000000 Then Return pic
End If

Next

Return Nil
End Function
[/code]

I am going to send the corrected project to Paul. That will be way more useful for other members than sterile criticism.

Michel, while I agree that I was being critical, it was (I had hoped) an attempt at constructive criticism. I would like to see the example working properly, but I wanted to discuss it before submitting a bug report, to make sure that I wasn’t misunderstanding something.

Regarding your correction to the PicHit code, I think you meant
If pixel.Alpha < 255 and pixel <> &c00000000 Then Return pic
rather than
If pixel.Alpha < 255 and pixel <> &h00000000 Then Return pic

While this works for the example, it won’t work in the general case where the object could be any color, because &c00000000 is opaque black and could appear in a visible part of the image. In that case the object would not be selected. I had already tried this. That is why I said earlier that I don’t see any simple fix for the example.

Also the dimension statement for Pixel needs to be changed in order to be compatible with HiDPI platforms:
Dim pixel As Color = pic.Image.Graphics.Pixel(picXpic.Image.Graphics.ScaleX, picYpic.Image.Graphics.ScaleY)

Robert, I feel you may be too demanding for a simple example.

I may be mistaken, but for me, the purpose of all these examples is to give a starting point for users to understand better an aspect of programming.

In that respect, CanvasDrawDrag seems to me to be a valid element along the path of a Xojo user, to discover a technique for moving objects onto a canvas.

There are many aspects to programming, and each of us brings in his forte and failings. Some will find examples too elementary, other too difficult. They have the advantage to be simple enough to understand for beginners, which is far more than I could say for the VB.NET example projects provided by Microsoft BTW.

About &c00000000 you are right. I had corrected that before sending to Paul.

I agree that I am very demanding when it comes to examples. It doesn’t matter to me whether the examples are simple or complex, but in any event they MUST work correctly or else they do more harm than good. The point of an example is to show the proper way to accomplish something. How can a beginning programmer learn from an incorrect example?

I am an experienced programmer, though my exposure to Xojo/RealBasic is fairly sporadic and not especially graphics related; so I make use of examples to get up to speed on Xojo specific things such as this. Because of this, it didn’t take long for me to realize that the problem was not in my own code, but in the example. A beginning programmer would be more likely to assume they’d done something wrong rather than blame the example.

When I write documentation for my own products, I create my own examples, I go through them with a fine toothed comb and test them every way possible, because I understand that people rely on the examples being correct. I’m perhaps somewhat obsessive about this, but I find that one simple example usually does far more good than many pages of description. To me, an incorrect example is nearly unforgivable.

Though my comments have probably come across as being unnecessarily surly, I don’t mean to insult the people responsible for these examples. The problem is that this particular example is an old one that was written using language features that are now deprecated. Going into ancient code (most often written by someone else) to fix it is not a job that anyone likes to do (certainly not me). It’s fraught with danger, trying to catch all of the subtle changes. Still, it has to be done properly, or else people will stop trusting the examples.

You really should have a look at the SimpleDraw article in xDev 12.5 - it does exactly what you want to do.

See http://www.xdevmag.com

[quote=294753:@Robert Weaver]Also the dimension statement for Pixel needs to be changed in order to be compatible with HiDPI platforms:
Dim pixel As Color = pic.Image.Graphics.Pixel(picXpic.Image.Graphics.ScaleX, picYpic.Image.Graphics.ScaleY)[/quote]

Nope. Not necessary. This project works perfectly with HiDPI enabled.

In spite of its name inherited from the non HiDPI era, Pixel does not report the color of an actual pixel, but of a point. So no change is necessary.

Xojo community, IMHO is not about simple consumption of goods. It is also the result of a collective effort. Most of the examples have been written by Xojo users, to the best of their abilities, usually for their own, and they decided to share them for the benefit of others. As such, they are not perfect, but a good example of what ordinary people can achieve. Stress on ordinary. Meaning not masters of the discipline.

You may have expected too much. As I said above, it is each one decision to make these examples better, for the common good. If an example does not suit you, you are free to move on, or, if you feel it can be improved, to do so and share.

[quote=294857:@Michel Bujardet]Nope. Not necessary. This project works perfectly with HiDPI enabled.

In spite of its name inherited from the non HiDPI era, Pixel does not report the color of an actual pixel, but of a point. So no change is necessary.
[/quote]
That’s curious. I couldn’t get it to work properly on my MacBook Pro Retina until I made that change. I had added some diagnostic code to display info about the color that was returned, and it was always in a position above and to the left of the cursor hotspot. I guess I’ll have to wait and see how the final example code comes out.