GraphicsPath.IsVisible() doesn't match up with actual path? - c#

I'm experiencing a discrepancy between a GraphicsPath drawn in World coordinates on a UserControl and the results of GraphicsPath.IsVisible() to Hit Test the shape with the mouse.
I performed a little test that made a map of where IsVisible() returned true, relative to the GraphicsPath shape that was drawn. The results show a very "low resolution" version of the shape I'm drawing.
Link to shared Google Drive image showing the results:
http://goo.gl/zd6xiM
Is there something I'm doing or not doing correctly that's causing this?
Thanks!
Here's the majority of my OnMouseMove() event handler:
protected override void OnMouseMove(MouseEventArgs e)
{
//base.OnMouseMove(e);
debugPixel = Point.Empty;
PointF worldPosition = ScreenToWorld(PointToClient(Cursor.Position));
if (_mouseStart == Point.Empty) // Just moving mouse around, no buttons pressed
{
_objectUnderMouse = null;
// Hit test mouse position against each canvas object to see if we're overtop of anything
for (int index = 0; index < _canvasObjects.Count; index++) // Uses front to back order
{
NPCanvasObject canvasObject = _canvasObjects[index];
if (canvasObject is NPCanvasPart)
{
NPCanvasPart canvasPart = (canvasObject as NPCanvasPart);
NPPart part = canvasPart.Part;
GraphicsPath gp = canvasPart.GraphicsPath;
// Set the object under the mouse cursor, and move it to the "front" so it draws on top of everythign else
if (gp.IsVisible(worldPosition))
{
// DEBUG
debugPixel.X = e.X;
debugPixel.Y = e.Y;
_objectUnderMouse = canvasObject;
_canvasObjects.MoveItemAtIndexToFront(_canvasObjects.IndexOf(canvasObject));
break; // Since we're modifying the collection we're iterating through, we can't reliably continue past this point
}
}
}
}
else
{
...
}
}
Later in my drawing code I draw a pixel whenever debugPixel != Point.Empty . I temporarily suppressed clearing before drawing so I could see them all.
Some other info that may be asked, or could be helpful to troubleshoot:
I've tried different Graphics.InterpolationMode settings but that doesn't seem to have any effect
I've applied a TranslateTransform and ScaleTransform to the main drawing Graphics but the underlying HitTest map seems to scale and translate equal to the GraphicsPath
For my main drawing canvas, Graphics.PageUnit = GraphicsUnit.Inch, except when I'm doing pixel-based overlay stuff

I thought I had researched this thoroughly enough, but apparently not. Shortly after posting this question I did another search with slightly different terms and found this:
http://vbcity.com/forums/t/72877.aspx
...which was enough to clue me in that the GraphicsPath and my main drawing Graphics were not the same. Using the overloaded GraphicsPath.IsVisible(PointF, Graphics) solved this problem very nicely.
Essentially it was trying to check against a very aliased (pixelated) version of my shape that had been scaled to the same size but not smoothed.

Related

SkiaSharp Touch Bitmap Image

In the app I'm trying to develop a key part is getting the position of where the user has touched. First I thought of using a tap gesture recognizer but after a quick google search I learned that was useless (See here for an example).
Then I believe I discovered SkiaSharp and after learning how to use it, at least somewhat, I'm still not sure how I get the proper coordinates of a touch. Here are sections of the code in my project that are relevant to the problem.
Canvas Touch Function
private void canvasView_Touch(object sender, SKTouchEventArgs e)
{
// Only carry on with this function if the image is already on screen.
if(m_isImageDisplayed)
{
// Use switch to get what type of action occurred.
switch (e.ActionType)
{
case SKTouchAction.Pressed:
TouchImage(e.Location);
// Update simply tries to draw a small square using double for loops.
m_editedBm = Update(sender);
// Refresh screen.
(sender as SKCanvasView).InvalidateSurface();
break;
default:
break;
}
}
}
Touch Image
private void TouchImage(SKPoint point)
{
// Is the point in range of the canvas?
if(point.X >= m_x && point.X <= (m_editedCanvasSize.Width + m_x) &&
point.Y >= m_y && point.Y <= (m_editedCanvasSize.Height + m_y))
{
// Save the point for later and set the boolean to true so the algorithm can begin.
m_clickPoint = point;
m_updateAlgorithm = true;
}
}
Here I'm just seeing or TRYING to see if the point clicked was in range of the image and I made a different SkSize variable to help. Ignore the boolean, not that important.
Update function (function that attempts to draw ON the point pressed so it's the most important)
public SKBitmap Update(object sender)
{
// Create the default test color to replace current pixel colors in the bitmap.
SKColor color = new SKColor(255, 255, 255);
// Create a new surface with the current bitmap.
using (var surface = new SKCanvas(m_editedBm))
{
/* According to this: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/finger-paint ,
the points I have to start are in Xamarin forms coordinates, but I need to translate them to SkiaSharp coordinates which are in
pixels. */
Point pt = new Point((double)m_touchPoint.X, (double)m_touchPoint.Y);
SKPoint newPoint = ConvertToPixel(pt);
// Loop over the touch point start, then go to a certain value (like x + 100) just to get a "block" that's been altered for pixels.
for (int x = (int)newPoint.X; x < (int)newPoint.X + 200.0f; ++x)
{
for (int y = (int)newPoint.Y; y < (int)newPoint.Y + 200.0f; ++y)
{
// According to the x and y, change the color.
m_editedBm.SetPixel(x, y, color);
}
}
return m_editedBm;
}
}
Here I'm THINKING that it'll start, you know, at the coordinate I pressed (and these coordinates have been confirmed to be within the range of the image thanks to the function "TouchImage". And when it does get the correct coordinates (or at least it SHOULD of done that) the square will be drawn one "line" at a time. I have a game programming background so this kind of sounds simple but I can't believe I didn't get this right the first time.
Also I have another function, it MIGHT prove worthwhile because the original image is rotated and then put on screen. Why? Well by default the image, after taking the picture, and then displayed, is rotated to the left. I had no idea why but I corrected it with the following function:
// Just rotate the image because for some reason it's titled 90 degrees to the left.
public static SKBitmap Rotate()
{
using (var bitmap = m_bm)
{
// The new ones width IS the old ones height.
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0.0f);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
}
I'll keep reading and looking up stuff on what I'm doing wrong, but if any help is given I'm grateful.

C#-WinForms || Multiple instances of Graphics for rotation

im currently working on a school project which is basically a game that involves GDI-Winforms animations.
I chose my game to be something like prison break where you are trying to escape the prison while guards with light-torches are walking around the room.
So, i have the Guard class which represents the guard and has A Path to walk on. Furthermore, the guard can rotate in 90deg. angles. (animated to a certain degree)
When i rotate the guard, i actually rotate the Graphics object that was passed through the Form_Paint event:
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
When i was dealing with only 1 guard, it was working great. Smooth and did the exactly what he was supposed to do.
When i tried to add 1 more guard, they started to freak out. Soon enough i figured out that its because im sending both guards to draw the same instance of Graphics. Which also means, both of them are rotating it.
Lets say one is rotating by -90, the other will too, but his angle class member variable will not be -90, then all hell breaks loose.
Rotation of graphics method (sits inside the guard class):
public Graphics RotateGuard(Graphics g, Point pivot, float angle) // returns the rotated Graphics object
{
if (!float.IsNaN(angle))
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, pivot);
g.Transform = m;
}
}
return g;
}
The next thing i did was to give each of them this.CreateGraphics().
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
Then everything worked just fine. The only thing is, it seemed like it was really heavy for the GPU to process or something. It drew the guard once about every 5 frames less than he supposed to be drawn.
I googled but couldn't find anything but people saying that "There is no need to clone a Graphics object", but still, i can't think of any better WORKING solution.
How can i solve this problem nicely?
Thanks in advanced.
CreateGraphics() is a really bad idea. You should do all the drawing to the Graphics passed by PaintEventArgs. So your initial code is just fine. However, what you need to do is to ensure that every object that receives a Graphics in its Draw method leaves it unchanged after doing its job. This can be achieved by using Graphics.Save and Graphics.Restore like this
class Guard
{
public void Draw(Graphics g)
{
var state = g.Save();
try
{
// The actual drawing code
}
finally
{
g.Restore(state);
}
}
}
An efficient way is to maintain one overall transform and have a matrix for each thing you wish to draw. Prior to drawing you multiply the current transform with the object's transform. Afterwards you reset the transform before drawing next.
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var g = e.Graphics;
g.ResetTransform();
g.MultiplyTransform (playerMatrix);
ply.Draw(e.Graphics); // Draws the player
g.ResetTransform();
foreach (Guard grd in this.guards )
{
g.MultiplyTransform (grd.Matrix);
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
g.ResetTransform();
}
}
Such concepts are similar to how things are done in 3D graphics such as Direct3D; XNA; OpenGL and Unity3D.
After drawing with the rotated Graphics tool object simply call e.Graphics.ResetTransform().
Also you may want to look into Graphics.Save() and Graphics.Restore() if you have made a few settings you want to return to.. It can save few states and when done with them bring them back up. Very nice, at least if you keep count of what you are doing.
And, of course you could undo the Translation/Rotation by doing the reverse calls in the reverse order, but the other methods are simpler and made for just your case.
Note that Graphics doesn't contain any graphics, it is a tool used to draw into an associated Bitmap or onto a control's surface..
Finally: Never, never use CreateGraphics !!! Its results are non-persistent, which you want only very rarely..
I figured out what it was, then i used what #Ivan did.
void Game_Paint(object sender, PaintEventArgs e)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var saved = e.Graphics.Save();
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.Restore(saved);
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
All i had to do is instead of using this.DoubleBuffered i used SetStyle(ControlStyles.OptimizedDoubleBuffer, true); which at first i thought both does the same, but evidently it does not.
Then, i saved the current graphics state and re-drew it.
Thanks a lot for everyone!

Moving a transparent image in a picturebox

For a project, I'm making a game and in it I have a scrolling map. The map moves left and right and is redrawn in a picturebox so that I can have a large map in a small picturebox. The top portion of the map is transparent so that I can change the sky colour later on. However when I move the map, the transparent part glitches out.
Original map before moving
After moving the map a bit
As you can see, everything above the tree line gets stretched, that is because that is where the transparency starts. The picturebox's parent is the form and the form is light blue, which is why the background is light blue.
Here is my code for moving the picture/redrawing it onto the picturebox:
private void timerTick_Tick(object sender, EventArgs e)
{
move();
//Draws new portion of the map
g.DrawImage(image, new Rectangle(0, 0, pbMap.Width, pbMap.Height), new Rectangle(imageX, imageY, pbMap.Width, pbMap.Height), GraphicsUnit.Pixel);
//Refreshes
pbMap.Image = bmp;
}
private void move()
{
//Right arrow events
if (right)
{
imageX += mapSpeed;
//Makes sure the picture stays within borders
if (imageX >= (imageWidth - pbMap.Width))
{
imageX = imageWidth - pbMap.Width;
}
}
//Left arrow events
if (left)
{
imageX -= mapSpeed;
//Makes sure the picture stays within borders
if (imageX <= 0)
{
imageX = 0;
}
}
}
Can anyone help explain the glitching?
Try calling g.Clear() with your sky color before the g.DrawImage() call. I think it's just drawing on top of itself and that's causing the smearing.
To me it seems like you are redrawing over and over without clearing the display from the previous draw! What type of framework are you using to develop that? Does it have a custom drawing class? As tesserex suggested more specifically call g.Clear() and u will be fine.
You shouldn't need g.Clear in this case because you're Re-Drawing a new image every time.
My bet is that imageX is greater then (imageWidth - pbMap.Width) so it will not enter the IF, therefore it will redraw the same as before.
Note: I don't know how you create g but if you use .CreateGraphics() don't forget to Dispose()
Cheers

Graphics.DrawImage speed

In my program, I'm coding a basic image editor. Part of this allows the user to draw a rectangular region and I pop up a display that shows that region zoomed by 3x or so (which they can adjust further with the mouse wheel). If they right click and drag this image, it will move the zoom region around on the original image, basically acting as a magnifying glass.
The problem is, I'm seeing some serious performance issues even on relatively small bitmaps. If the bitmap showing the zoomed region is around 400x400 it's still updating as fast as mouse can move and is perfectly smooth, but if I mouse wheel the zoom up to around 450x450, it immediately starts chunking, only down to around 2 updates per second, if that. I don't understand why such a small increase incurs such an enormous performance problem... it's like I've hit some internal memory limit or something. It doesn't seem to matter the size of the source bitmap that is being zoomed, just the size of the zoomed bitmap.
The problem is that I'm using Graphics.DrawImage and a PictureBox. Reading around this site, I see that the performance for both of these is typically not very good, but I don't know enough about the inner workings of GDI to improve my speed. I was hoping some of you might know where my bottlenecks are, as I'm likely just using these tools in poor ways or don't know of a better tool to use in its place.
Here are some snippets of my mouse events and related functions.
private void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
// slide the zoomed part to look at a different area of the original image
if (zoomFactor > 1)
{
isMovingZoom = true;
// try saving the graphics object?? are these settings helping at all??
zoomingGraphics = Graphics.FromImage(displayImage);
zoomingGraphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
zoomingGraphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.Low;
zoomingGraphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighSpeed;
zoomingGraphics.PixelOffsetMode = PixelOffsetMode.HighSpeed;
}
}
}
private void pictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (isMovingZoom)
{
// some computation on where they moved mouse ommitted here
zoomRegion.X = originalZoomRegion.X + delta.X;
zoomRegion.Y = originalZoomRegion.Y + delta.Y;
zoomRegionEnlarged = scaleToOriginal(zoomRegion);
// overwrite the existing displayImage to prevent more Bitmaps being allocated
createZoomedImage(image.Bitmap, zoomRegionEnlarged, zoomFactor, displayImage, zoomingGraphics);
}
}
private void createZoomedImage(Bitmap source, Rectangle srcRegion, float zoom, Bitmap output, Graphics outputGraphics)
{
Rectangle destRect = new Rectangle(0, 0, (int)(srcRegion.Width * zoom), (int)(srcRegion.Height * zoom));
outputGraphics.DrawImage(source, destRect, srcRegion, GraphicsUnit.Pixel);
if (displayImage != originalDisplayImage && displayImage != output)
displayImage.Dispose();
setImageInBox(output);
}
// sets the picture box image, as well as resizes the window to fit
void setImageInBox(Bitmap bmp)
{
pictureBox.Image = bmp;
displayImage = bmp;
this.Width = pictureBox.Width + okButton.Width + SystemInformation.FrameBorderSize.Width * 2 + 25;
this.Height = Math.Max(450, pictureBox.Height) + SystemInformation.CaptionHeight + SystemInformation.FrameBorderSize.Height * 2 + 20;
}
private void pictureBox_MouseUp(object sender, MouseEventArgs e)
{
else if (e.Button == System.Windows.Forms.MouseButtons.Right)
{
if (isMovingZoom)
{
isMovingZoom = false;
zoomingGraphics.Dispose();
}
}
}
As you can see, I'm not declaring a new Bitmap every time I want to draw something, I'm reusing an old Bitmap (and the Bitmap's graphics object, though I don't know if there is much cost with calling Graphics.FromImage repeatedly). I tried adding Stopwatches around to benchmark my code, but I think DrawImage passes functionality to another thread so the function claims to be done relatively quickly. I'm trying to Dispose all my Bitmap and Graphics objects when I'm not using them, and avoid repeated calls to allocate/deallocate resources during the MouseMove event. I'm using a PictureBox but I don't think that's the problem here.
Any help to speed up this code or teach me what's happening in DrawImage is appreciated! I've trimmed some excess code to make it more presentable, but if I've accidentally trimmed something important, or don't show how I'm using something which may be causing problems, please let me know and I'll revise the post.
The way I handle issues like that is when receiving the Paint event, I draw the whole image to a memory bitmap, and then BLT it to the window.
That way, all visual flash is eliminated, and it looks fast, even if it actually is not.
To be more clear, I don't do any painting from within the mouse event handlers.
I just set up what's needed for the main Paint handler, and then do Invalidate.
So the painting happens after the mouse event completes.
ADDED: To answer Tom's question in a comment, here's how I do it. Remember, I don't claim it's fast, only that it looks fast, because the _e.Graphics.DrawImage(bmToDrawOn, new Point(0,0)); appears instantaneous. It just bips from one image to the next.
The user doesn't see the window being cleared and then repainted, thing by thing.
It gives the same effect as double-buffering.
Graphics grToDrawOn = null;
Bitmap bmToDrawOn = null;
private void DgmWin_Paint(object sender, PaintEventArgs _e){
int w = ClientRectangle.Width;
int h = ClientRectangle.Height;
Graphics gr = _e.Graphics;
// if the bitmap needs to be made, do so
if (bmToDrawOn == null) bmToDrawOn = new Bitmap(w, h, gr);
// if the bitmap needs to be changed in size, do so
if (bmToDrawOn.Width != w || bmToDrawOn.Height != h){
bmToDrawOn = new Bitmap(w, h, gr);
}
// hook the bitmap into the graphics object
grToDrawOn = Graphics.FromImage(bmToDrawOn);
// clear the graphics object before drawing
grToDrawOn.Clear(Color.White);
// paint everything
DoPainting();
// copy the bitmap onto the real screen
_e.Graphics.DrawImage(bmToDrawOn, new Point(0,0));
}
private void DoPainting(){
grToDrawOn.blahblah....
}

Weird shape resizing behavior in WPF

I am making a WPF application that will let the user draw and resize shapes.
The resizing part is done using adorners, and the shapes are my own classes derived from Shape.
For example I have a Polyline, and for each of it's points I am adorning a Thumb with a handler on it's DragDelta event:
void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
{
PolylineEx polyline = this.AdornedElement as PolylineEx;
ResizingThumb thumb = sender as ResizingThumb;
int index = (int)thumb.Tag;
Point newPoint = new Point(
polyline.Points[index].X + e.HorizontalChange,
polyline.Points[index].Y + e.VerticalChange);
Trace.WriteLine(String.Format("Arranging point {0}:{1},{2}", index, newPoint.X, newPoint.Y));
polyline.Points[index] = newPoint;
}
Well, the problem is that sometimes (often), dragging a thumb is not smooth at all. First I thought it was a performance issue, because of a new Point being created every time (and other things), but then I noticed that sometimes the point is set up at weird coordinates that have nothing to do with the position of the mouse.
I've also uploaded a sample project HERE, could someone who knows his way around wpf take a look and enlighten me of what is happening?
Also, if anyone thinks of a better way for doing this, any feedback would be highly appreciated.
Thank you.
Edit: HERE is a new link to the project files.
I'm ending up answering my own question :)
I think the problem was here:
protected override System.Windows.Media.Geometry DefiningGeometry
{
get
{
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext context = geometry.Open())
{
context.BeginFigure(Points[0], false, true);
foreach (Point pt in Points)
{
context.LineTo(pt, true, true);
}
geometry.Freeze();
return geometry;
}
}
}
I switched to using PathGeometry & LineSegments and it works fine now.

Categories