I'm making a map drawing program that behaves a lot like a typical "Paint" program. I'd like for my coastlines to be prominent, so adding a border/outline around my painted path like in the following picture is what I'm aiming for:
The border should appear real-time as the user paints the "land".
Right now, my software behaves like this:
At the moment my painting works with Graphics.DrawLine(), but I'm guessing I need to add the lines to a GraphicsPath and trace the outline on the path somehow to get the desired effect.
Here's the important part of my code. All drawing is being done in a Canvas object I've made, which is just a PictureBox with some custom logic:
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if (isMouseDown) // if a mouse button is being pressed
{
if (currentTool == Tools.LandWater) // if the current tool is the land placing tool
{
SetupGraphics(); // pretty much just gr = this.CreateGraphics();
Brush penBrush = null;
if (IsTextureBrush) // if the canvas object is going to use a texture brush or solid brush
{
Image texture = this.PaperTexture;
if (e.Button == MouseButtons.Left) texture = this.LandTexture1;
else if (e.Button == MouseButtons.Right) texture = this.WaterTexture; // if user right clicks, water is placed
penBrush = new TextureBrush(texture);
}
else
{
penBrush = new SolidBrush(BrushColour);
}
using (Pen p = new Pen(penBrush, this.BrushSize))
{
p.StartCap = System.Drawing.Drawing2D.LineCap.Round;
p.EndCap = System.Drawing.Drawing2D.LineCap.Round;
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
gr.DrawLine(p, lastMouseLocation, e.Location);
}
lastMouseLocation = e.Location; // originally set in MouseDown as e.Location
}
}
}
Does anyone have a clue as to where I could even start? I looked around at some Win32 API calls and stuff but none of them work as intended.
Related
I was looking for a solution to transparently add panning & zooming capability to a WPF Image control and I have found the solution https://stackoverflow.com/a/6782715/584180, developed by Wiesław Šoltés and Konrad Viltersten, which is outstanding.
Now I would like to add a 'mouse click' event to the control so that I can the coordinates of the clicked point in the original image coordinate system, so I can use them to retrieve the pixel color.
I understand there will be some rounding and if the image is zoomed out the color will not correspond to the actual one displayed on screen. I also understand that the user may click outside the image borders, in that case I expect a null Point or negative coords to be returned.
I am not an expert of the C# way of doing transforms and at the moment I am stuck with this (to be added inside the ZoomBorder.cs class):
public Point GetImageCoordsAt(MouseButtonEventArgs e)
{
if (child != null)
{
var tt = GetTranslateTransform(child);
var mousePos = e.GetPosition(this);
var transformOrigin = new Point(tt.X, tt.Y);
return mousePos; // Wrong: how do I transform this?
}
return null;
}
For future reference, what I was trying to achieve is this:
public Point GetImageCoordsAt(MouseButtonEventArgs e)
{
if (child != null && child.IsMouseOver)
{
var controlSpacePosition = e.GetPosition(child);
var imageControl = this.Child as Image;
var mainViewModel = ((MainViewModel)base.DataContext);
if (imageControl != null && imageControl.Source != null)
{
// Convert from control space to image space
var x = Math.Floor(controlSpacePosition.X * imageControl.Source.Width / imageControl.ActualWidth);
var y = Math.Floor(controlSpacePosition.Y * imageControl.Source.Height / imageControl.ActualHeight);
return new Point(x, y);
}
}
return new Point(-1, -1);
}
Which returns the coordinates of the mouse pointer in the original image system.
As mm8 suggests you can get the location you want using e.GetPosition(child);
, there's no need to perform any transformations. For testing purposes I've overwritten the reset behaviour. Using the code from the link you provided, change
void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
this.Reset();
}
to
public Point GetImageCoordsAt(MouseButtonEventArgs e)
{
if (child != null && child.IsMouseOver)
{
return e.GetPosition(child);
}
return new Point(-1, -1);
}
void child_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
MessageBox.Show(GetImageCoordsAt(e).ToString());
}
If you rightclick at the same location in the image, you'll get (approximately) the same coordinates, regardless of the pan and zoom.
I have one Flow Layout Panel with some User Controls in it.
I Want to Select these controls using rectangle selection using Mouse,like one used in windows file explorer .
I have tried these : https://support.microsoft.com/en-us/kb/314945
But it was very flickering and not useful (I might be wrong,please correct me).
Any good examples please.
Drawing the rubber-band rectangle is done like this:
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
panel1.Refresh();
using (Graphics g = panel1.CreateGraphics())
{
Rectangle rect = GetRectangle(mdown, e.Location);
g.DrawRectangle(Pens.Red, rect);
}
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
mdown = e.Location;
}
It uses a helper function:
static public Rectangle GetRectangle(Point p1, Point p2)
{
return new Rectangle(Math.Min(p1.X, p2.X), Math.Min(p1.Y, p2.Y),
Math.Abs(p1.X - p2.X), Math.Abs(p1.Y - p2.Y));
}
I don't get any flicker from it. If you do, maybe you have coded the Paint event, you may want to use a double-buffered Panel:
class DrawPanel : Panel
{
public DrawPanel()
{
DoubleBuffered = true;
}
}
Update: Instead of a Panel, which is a Container control and not really meant to draw onto, you can use a Picturebox or a Label (with Autosize=false); both have the DoubleBuffered property turned on out of the box and support drawing better than Panels do.
If the problem is the flickering only. You might want to set the Forms double buffer property to true.
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.
Latest
//combination of pictureBox1.Invalidate(); and pictureBox1.Update();
pictureBox1.Refresh();
After pictureBox1.Refresh(); , it will trigger pictureBox1_Paint
Following code will have the icon remain on the picture box.
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (checkBox1.Checked)
{
e.Graphics.DrawIcon(IconCamera, rect);
}
}
Following code NOT! it will draw the icon and clear it soon after it was drawn in Paint (that why have my old question)
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics graphic = Graphics.FromHwnd(pictureBox1.Handle);
if (checkBox1.Checked)
{
graphic.DrawIcon(IconCamera, rect);
}
}
I thought these two graphics are same but it seem like they are not same.
May I know what is the difference of Graphics of PaintEventArgs of
pictureBox1 and Graphics from handle of pictureBox1?
Why DrawIcon on pictureBox1 Paint event with Graphics from handle of
pictureBox1 will disappear in Paint event but not in the function I
declared? thanks
Old Question
When I checked the checkbox then I will draw icon on the picture box,
Graphics graphic = Graphics.FromHwnd(pictureBox1.Handle);
if (checkBox1.CheckState.ToString() == "Checked")
{
Icon IconCamera = new Icon("cam.ico");
Rectangle rect = new Rectangle(100, 100, 32, 32);
graphic.DrawIcon(IconCamera, rect);
}
BUT After the picture box refresh,
pictureBox1.Refresh();
the drawn icon will disappear.
May I know what picturebox event OR else can I make use to put in the drawing icon code to maintain or always auto redraw the icon even after the picture box refresh()? thanks. I tried Paint and Validating of picturebox1 cannot work.
The best is to use two calls one after each other:
pictureBox1.Invalidate();
pictureBox1.Update();
This will force the picturebox to be redrawn.
Then I subscribe for Paint event, which is signalled when previous two lines of code are called (in case of redrawing the picturebox). Here I do the all the drawings.
In your case it would be something like this:
public void Paint(PaintEventArgs e)
{
if (checkBox1.Checked)
{
Rectangle rect = new Rectangle(100, 100, 32, 32);
Icon IconCamera = new Icon("cam.ico");
e.Graphics.DrawIcon(IconCamera, rect);
}
}
Then, subscribe for event CheckedChanged of checkBox1, and call there that two redrawing commands from the begin of this post.
It causes, that when user change the checked state of checkBox1, pictureBox1 will be forced to be redrawn and the icon will be drawn according to checkBox1 state.
You can avoid using a Graphics object and find another way. I don't know if you are forced in using an icon, but you can try to use a Label this way.
label1.Image = "yourImage.bmp" // you can use a bitmap
There here come your statement.
if(checkBox1.Checked) {
label1.Visible = true;
label1.Location = new Point(x, y); // your coordinate
}
Then you can handle checkBox1_CheckedChanged and code:
Void checkBox1_CheckedChanged(object sender, EventArgs e) {
if(!checkBox1.Checked) {
label1.Visible = false;
}
}
Hope this could help.
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....
}