I am trying to find an image within a screenshot and draw a rectangle around it. What I don't understand it how to interpret my result matrix to identify the area containing the image.
The below code will draw a rectangle, but it's not really in the right place and I don't know if that's because I'm not using my result correctly or something else.
using (Mat templateImage = CvInvoke.Imread("\\top_1.png", Emgu.CV.CvEnum.ImreadModes.AnyColor))
using (Mat inputImage = CvInvoke.Imread(AppDomain.CurrentDomain.BaseDirectory + "\\currentScreen.png", Emgu.CV.CvEnum.ImreadModes.AnyColor))
{
Mat result = new Mat();
CvInvoke.MatchTemplate(inputImage, templateImage, result, Emgu.CV.CvEnum.TemplateMatchingType.SqdiffNormed);
result.MinMax(out double[] minVal, out double[] maxVal, out Point[] minLoc, out Point[] maxLoc);
int x = minLoc[0].X;
int y = minLoc[0].Y;
int w = maxLoc[0].X - minLoc[0].X;
int h = maxLoc[0].Y - minLoc[0].Y;
Form f = new Form
{
BackColor = Color.Red,
//TransparencyKey = Color.Red,
FormBorderStyle = FormBorderStyle.None,
TopMost = true,
Location = new Point(x, y),
Size = new Size(w, h)
};
Application.EnableVisualStyles();
Application.Run(f);
}
The only thing I see is the position of the form, you must set the StarPosition to Manual
Form f = new Form
{
StartPosition = FormStartPosition.Manual,
BackColor = Color.Red,
//TransparencyKey = Color.Red,
FormBorderStyle = FormBorderStyle.None,
TopMost = true,
Location = new Point(x, y),
Size = new Size(w, h)
};
This is the screen Image
This is the template
This is the result
This is with out StartPosition
I realised my error after reading and re-reading the documentation a few times. I ended up not using this as I found a much simpler and more reliable method for my use case, but for the benefit of others:
The function MinMax() gives you both minimum and maximum because which one you use depends on the matching type used. For example Emgu.CV.CvEnum.TemplateMatchingType.SqdiffNormed returns results where the min is the greatest match.
The location returned in minLoc is just the top left co-ordinate of the templateImage and so it matches on the same size area as templateImage meaning I just had to do:
int x = minLoc[0].X;
int y = minLoc[0].Y;
int w = maxLoc[0].X + templateImage.Width;
int h = maxLoc[0].Y + templateImage.Height;
I got caught out because I didn't assume the templateImage found within inputImage had be the same size.
Related
I am capturing InkStrokes and have a need to create a scaled bitmap image of the strokes in the background. The captured images need to be of uniform size regardless of how big the bounding box of the ink.
For example, if original ink stroke is drawn and the bounding box top/left is 100,100 and size is 200,200 on the ink canvas, I want the ink to start at 0,0 of the new rendered bitmap that is 50,50 size (ignore impact of stroke width right now).
I have figured out how to scale the ink strokes (thanks StackOverflow) but not how to move the strokes. Right now, it seems I have to create a bitmap the size of the InkCanvas, render the scaled ink, then crop bigger image to the correct size.
I've tried using the InkStroke.PointTranslate via
var scaleMatrix = Matrix3x2.CreateScale(scale);
scaleMatrix.Translation = -offset; // top/left of ink stroke bounding box
stroke.PointTransform = scaleMatrix;
But the coordinates do not come out correct.
Any help much appreciated.
You can combine transformations by multiplying matrices. This works for me
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
var boundingBox = inkCanvas.InkPresenter.StrokeContainer.BoundingRect;
var matrix1 = Matrix3x2.CreateTranslation((float)-boundingBox.X, (float)-boundingBox.Y);
var matrix2 = Matrix3x2.CreateScale(0.5f);
var builder = new InkStrokeBuilder();
var newStrokeList = new List<InkStroke>();
foreach (var stroke in strokes)
{
newStrokeList.Add(builder.CreateStrokeFromInkPoints
(stroke.GetInkPoints(), matrix1 * matrix2));
}
//Add the translated and scaled strokes to the inkcanvas
inkCanvas.InkPresenter.StrokeContainer.AddStrokes(newStrokeList);
Maybe I was still doing something wrong, but it appears you cannot use InkStrokeBuilder.CreateStrokeFromInkPoints with more than one kind of transform. I tried all kinds of combinations/approaches, and just could not get it to work.
Here is my solution...
private static IList<InkStroke> GetScaledAndTransformedStrokes(IList<InkStroke> strokeList, float scale)
{
var builder = new InkStrokeBuilder();
var newStrokeList = new List<InkStroke>();
var boundingBox = strokeList.GetBoundingBox();
foreach (var singleStroke in strokeList)
{
var translateMatrix = new Matrix(1, 0, 0, 1, -boundingBox.X, -boundingBox.Y);
var newInkPoints = new List<InkPoint>();
var originalInkPoints = singleStroke.GetInkPoints();
foreach (var point in originalInkPoints)
{
var newPosition = translateMatrix.Transform(point.Position);
var newInkPoint = new InkPoint(newPosition, point.Pressure, point.TiltX, point.TiltY, point.Timestamp);
newInkPoints.Add(newInkPoint);
}
var newStroke = builder.CreateStrokeFromInkPoints(newInkPoints, new Matrix3x2(scale, 0, 0, scale, 0, 0));
newStrokeList.Add(newStroke);
}
return newStrokeList;
}
I ended up having to apply my own translate transform then use the builder.CreateStrokeFromInkPoints with a scale matrix applied to get the results I wanted. GetBoundingBox is my own extension:
public static class RectExtensions
{
public static Rect CombineWith(this Rect r, Rect rect)
{
var top = (r.Top < rect.Top) ? r.Top : rect.Top;
var left = (r.Left < rect.Left) ? r.Left : rect.Left;
var bottom = (r.Bottom < rect.Bottom) ? rect.Bottom : r.Bottom;
var right = (r.Right < rect.Right) ? rect.Right : r.Right;
var newRect = new Rect(new Point(left, top), new Point(right, bottom));
return newRect;
}
}
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
I have a windows forms project written in C#. The main form has a TabControl on it and there is a requirement for one of the users to be able to print one of the TabPages. The form is very long and I use a vertical scroll bar. The whole of the form needs to be able to be printed.
I have tried using the DrawToBitmap method to convert to a bitmap first, but this will only include the portion of the form that the user can see. Some other solutions I have tried involve screen capturing, which has the same issue.
How can I print out, or get an image, of the whole of the tab page, including the parts the user only sees when they scroll down?
This is rather simple for any control including TabControls and TabPages but not Forms.
All you need to do is enlarge the relevant controls enough to show all their content. (They don't have to be actually visible on screen.)
Here is an example:
tabControl1.Height = 10080;
tabPage2.Height = 10050;
dataGridView1.Height = 10000;
dataGridView1.Rows.Add(3000);
for (int i = 0; i < dataGridView1.Rows.Count; i++) dataGridView1[0, i].Value = i;
using (Bitmap bmp = new Bitmap(tabControl1.Width , tabControl1.Height ))
{
tabControl1.DrawToBitmap(bmp, tabControl1.ClientRectangle);
bmp.Save("D:\\xxxx.png", ImageFormat.Png);
}
This saves the full content of the DataGridView, the TabPage and the TabControl..
Note: that this will not work with forms, which can't much exceed the screen dimensions..
Update: Here is code that saves a form with vertical scrolling by patching several bitmaps together. It can, of course be expanded to include horizontal scrolling as well. I have coded a similar solution for larger Panels here.
static void saveLargeForm(Form form, string fileName)
{
// yes it may take a while
form.Cursor = Cursors.WaitCursor;
// allocate target bitmap and a buffer bitmap
Bitmap target = new Bitmap(form.DisplayRectangle.Width, form.DisplayRectangle.Height);
Bitmap buffer = new Bitmap(form.Width, form.Height);
// the vertical pointer
int y = 0;
var vsc = form.VerticalScroll;
vsc.Value = 0;
form.AutoScrollPosition = new Point(0, 0);
// the scroll amount
int l = vsc.LargeChange;
Rectangle srcRect = ClientBounds(form);
Rectangle destRect = Rectangle.Empty;
bool done = false;
// we'll draw onto the large bitmap with G
using (Graphics G = Graphics.FromImage(target))
{
while (!done)
{
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
int v = vsc.Value;
vsc.Value = vsc.Value + l;
form.AutoScrollPosition = new Point(form.AutoScrollPosition.X, vsc.Value + l);
int delta = vsc.Value - v;
done = delta < l;
y += delta;
}
destRect = new Rectangle(0, y, srcRect.Width, srcRect.Height);
form.DrawToBitmap(buffer, new Rectangle(0, 0, form.Width, form.Height));
G.DrawImage(buffer, destRect, srcRect, GraphicsUnit.Pixel);
}
// write result to disc and clean up
target.Save(fileName, System.Drawing.Imaging.ImageFormat.Png);
target.Dispose();
buffer.Dispose();
GC.Collect(); // not sure why, but it helped
form.Cursor = Cursors.Default;
}
It makes use of a helper function to determine the the net size of the virtual client rectangle, ie excluding borders, title and scrollbar:
static Rectangle ClientBounds(Form f)
{
Rectangle rc = f.ClientRectangle;
Rectangle rb = f.Bounds;
int sw = SystemInformation.VerticalScrollBarWidth;
var vsc = f.VerticalScroll;
int bw = (rb.Width - rc.Width - (vsc.Visible ? sw : 0) ) / 2;
int th = (rb.Height - rc.Height) - bw * 2;
return new Rectangle(bw, th + bw, rc.Width, rc.Height );
}
Basically I have a form and am trying to "dim" areas of it to draw focus to a certain part of the form. To do this I'm using a Form with no border and 50% opacity, aligned with the actual form. The area I am trying to mask is the dark gray area, roughly, as pictured:
To get the "U"-shaped form, I'm using a GraphicsPath with AddPolygon, calculating the points of each vertex:
var p = new GraphicsPath();
var origin = new Point(Top, Left);
var maxExtentPt = new Point(origin.X + Width, origin.Y + Height);
Point[] points = {
origin,
new Point(origin.X + leftPanel.Width, origin.Y),
new Point(origin.X + leftPanel.Width, maxExtentPt.Y - bottomPanel.Height),
new Point(maxExtentPt.X - rightPanel.Width, maxExtentPt.Y- bottomPanel.Height),
new Point(maxExtentPt.X - rightPanel.Width, origin.Y),
new Point(maxExtentPt.X, origin.Y),
maxExtentPt,
new Point(origin.X, maxExtentPt.Y),
origin
};
p.AddPolygon(points);
overlayForm.Region = new Region(p);
overlayForm.Location = PointToScreen(Point.Empty);
The three panels in the code are what I am masking, so I am using their dimensions to calculate the points. Instead of getting my expected result, the mask looks like this, with its size changing as I resize the main form (I recalculate the region on Move and Resize):
Is there some limitation of GraphicsPath.AddPolygon that I'm not aware of? I double-checked (quadruple-checked, really) the results of my calculations, including taking the coordinates for each point and plugging them into Ipe to see if the shape was actually correct... It was. But not in my program!
Edit: Here are the values of each point, when I hit a breakpoint at p.AddPolygon(points); I'm starting in the upper left-hand corner and going around clockwise:
Looks like your points are wrong after all.
Everything ought to be in the coordinates of the ClientRectangle, so
Origin should not be new Point(Top, Left) which is the Location of the Form. It should be Point.Empty or (0,0). Or you could use the leftPanel.Location.
And
maxExtentPt = new Point(origin.X + Width, origin.Y + Height);
should read:
var maxExtentPt = new Point(origin.X + ClientSize.Width, origin.Y + ClientSize.Height);
(The difference is the size of border+title..)
Let me know if that works better!
You could also try doing it this way, basing everything off the Panels:
Form overlayForm = new Form();
overlayForm.Opacity = .5;
overlayForm.BackColor = Color.DarkGray;
overlayForm.StartPosition = FormStartPosition.Manual;
overlayForm.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
var p = new GraphicsPath();
var origin = new Point(0, 0);
var maxExtentPt = new Point(this.Width, this.Height);
Point[] points = {
origin,
new Point(leftPanel.Right, origin.Y),
new Point(leftPanel.Right, bottomPanel.Top),
new Point(rightPanel.Left, bottomPanel.Top),
new Point(rightPanel.Left, origin.Y),
new Point(rightPanel.Right, origin.Y),
maxExtentPt,
new Point(origin.X, maxExtentPt.Y),
origin
};
p.AddPolygon(points);
overlayForm.Region = new Region(p);
overlayForm.Location = this.PointToScreen(new Point(0, 0));
overlayForm.Show();
I have a canvas with a background set to be lineargradientbrush....how do I then extract the color from this background at a particular mouse point (x,y)?
I can do this with a BitmappedImage fine...as this deals with pixels, not sure about a canvas though...
Thanks greatly in advance,
U.
The code posted by Ray Burns didn't work for me but it did lead me down the right path. After some research and experimentation I located the problems to be the bitmap.Render(...) implementation and the Viewbox it uses.
Note: I'm using .Net 3.5 and WPF so maybe his code works in other versions of .Net.
The comments were left here intentionally to help explain the code.
As you can see the Viewbox needs to be normalized with respect to the source Visual Height and Width.
The DrawingVisual needs to be drawn using the DrawingContext before it can be rendered.
In the RenderTargetBitmap method I tried both PixelFormats.Default and PixelFormats.Pbgra32. My testing results were the same with both of them.
Here is the code.
public static Color GetPixelColor(Visual visual, Point pt)
{
Point ptDpi = getScreenDPI(visual);
Size srcSize = VisualTreeHelper.GetDescendantBounds(visual).Size;
//Viewbox uses values between 0 & 1 so normalize the Rect with respect to the visual's Height & Width
Rect percentSrcRec = new Rect(pt.X / srcSize.Width, pt.Y / srcSize.Height,
1 / srcSize.Width, 1 / srcSize.Height);
//var bmpOut = new RenderTargetBitmap(1, 1, 96d, 96d, PixelFormats.Pbgra32); //assumes 96 dpi
var bmpOut = new RenderTargetBitmap((int)(ptDpi.X / 96d),
(int)(ptDpi.Y / 96d),
ptDpi.X, ptDpi.Y, PixelFormats.Default); //generalized for monitors with different dpi
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawRectangle(new VisualBrush { Visual = visual, Viewbox = percentSrcRec },
null, //no Pen
new Rect(0, 0, 1d, 1d) );
}
bmpOut.Render(dv);
var bytes = new byte[4];
int iStride = 4; // = 4 * bmpOut.Width (for 32 bit graphics with 4 bytes per pixel -- 4 * 8 bits per byte = 32)
bmpOut.CopyPixels(bytes, iStride, 0);
return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}
If you are interested in the getScreenDPI() function the code is:
public static Point getScreenDPI(Visual v)
{
//System.Windows.SystemParameters
PresentationSource source = PresentationSource.FromVisual( v );
Point ptDpi;
if (source != null)
{
ptDpi = new Point( 96.0 * source.CompositionTarget.TransformToDevice.M11,
96.0 * source.CompositionTarget.TransformToDevice.M22 );
}
else
ptDpi = new Point(96d, 96d); //default value.
return ptDpi;
}
And the usage is similar to Ray's. I show it here for a MouseDown on a canvas.
private void cvsTest_MouseDown(object sender, MouseButtonEventArgs e)
{
Point ptClicked = e.GetPosition(cvsTest);
if (e.LeftButton.Equals(MouseButtonState.Pressed))
{
Color pxlColor = ImagingTools.GetPixelColor(cvsTest, ptClicked);
MessageBox.Show("Color String = " + pxlColor.ToString());
}
}
FYI, ImagingTools is the class where I keep static methods related to imaging.
WPF is vector based so it doesn't really have any concept of a "pixel" except within a bitmap data structure. However you can determine the average color of a rectangular area, including a 1x1 rectangular area (which generally comes out as a single pixel on the physical screen).
Here's how to do this:
public Color GetPixelColor(Visual visual, int x, int y)
{
return GetAverageColor(visual, new Rect(x,y,1,1));
}
public Color GetAverageColor(Visual visual, Rect area)
{
var bitmap = new RenderTargetBitmap(1,1,96,96,PixelFormats.Pbgra32);
bitmap.Render(
new Rectangle
{
Width = 1, Height = 1,
Fill = new VisualBrush { Visual = visual, Viewbox = area }
});
var bytes = new byte[4];
bitmap.CopyPixels(bytes, 1, 0);
return Color.FromArgb(bytes[0], bytes[1], bytes[2], bytes[3]);
}
Here is how you would use it:
Color pixelColor = GetPixelColor(canvas, x, y);
The way this code works is:
It fills a 1x1 Rectangle using a VisualBrush that shows the selected area of the canvas
It renders this Rectangle on to a 1-pixel bitmap
It gets the pixel color from the rendered bitmap
On Microsoft Support, there is this article about finding the color of the pixel at the mouse cursor:
http://support.microsoft.com/kb/892462