Get the bounding rectangle of multiple rectangles - c#

I have up to 4 rectangles on an image, for each of these rectangles I know their top left X,Y coordinate and their width,height. I want to create an Int32Rect with dimensions from most top left rectangle to the most bottom right rectangle. The main issue is that you can only create a System.Windows.Int32Rect with x,y,width,height parameters. Any ideas how I can accomplish this with my currently known values?
EDIT:
Trying to clarify, I want to create a Int32Rect that is the dimensions of all other "rectangles" on my image. So one large Int32Rect that starts from the "rectangle" at the top-left portion of the image and stretches to the "rectangle" that is at the bottom-right portion of the image.
Here's some code that does this for a single rectangle:
var topLeftOfBox = new Point(centerOfBoxRelativeToImage.X - Box.Width/2,
centerOfBoxRelativeToImage.Y - Box.Height/2);
return new CroppedBitmap(originalBitmap, new Int32Rect(topLeftOfBox.X, topLeftOfBox.Y, Box.Width, Box.Height));
Thanks for the help and ideas everyone, I found Aybe's answer to work the best for me.

You need to grab x/y mins/maxs for each rectangle and build a rectangle out of these values:
using System.Linq;
using System.Windows;
internal class Class1
{
public Class1()
{
var rect1 = new Int32Rect(10, 10, 10, 10);
var rect2 = new Int32Rect(30, 30, 10, 10);
var rect3 = new Int32Rect(50, 50, 10, 10);
var rect4 = new Int32Rect(70, 70, 10, 10);
var int32Rects = new[] { rect1, rect2, rect3, rect4 };
var int32Rect = GetValue(int32Rects);
}
private static Int32Rect GetValue(Int32Rect[] int32Rects)
{
int xMin = int32Rects.Min(s => s.X);
int yMin = int32Rects.Min(s => s.Y);
int xMax = int32Rects.Max(s => s.X + s.Width);
int yMax = int32Rects.Max(s => s.Y + s.Height);
var int32Rect = new Int32Rect(xMin, yMin, xMax - xMin, yMax - yMin);
return int32Rect;
}
}
Result:
{10,10,70,70}

The question is somewhat unclear and so I will start of by stating my understanding of what you are looking for. As I read your question, you have a collection of rectangles and are looking to find the smallest rectangle that contains all rectangles in your collection.
Here is what you need to know in order to do this:
The top left of a rect is (X, Y) and the bottom right is (X+Width, Y+Height).
The width of a rectangle is given by Right - Left. The height is given by Bottom - Top.
Now that you can convert between these quantities the rest is easy. Find the minimum top value, the minimum left value, the maximum right value and the maximum bottom value. Feed those values into the equations in the second bullet point above and you are done.

You can save some effort, and avoid being concerned about having "world knowledge" of which Rectangle is bottom-most/top.most left-most/right-most:
public static class RectangleExtensions
{
public static Rectangle RectsGetBounds(this Rectangle rect, params Rectangle[] rects)
{
Rectangle rbase = rect;
for (int i = 0; i < rects.Length; i++)
{
rbase = Rectangle.Union(rbase, rects[i]);
}
return rbase;
}
public static Rectangle ControlsGetBounds(this Control cntrl, params Control[] cntrls)
{
return RectsGetBounds(cntrl.Bounds, cntrls.Select(c => c.Bounds).ToArray());
}
}

Related

How to display draw rectangle from OpenCV.MatchTemplate

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.

Moving UWP InkStrokes for Offscreen Rendering

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;
}
}

Drawing sets of coordinates to pixels so they mutually scale

So I have a List<object> of longitude and latitude coordinates of two points, and I need to connect the line between them. The trick is to display all of the lines within a panel so that they are scaled within the panel's dimensions (converting coordinate numbers to match the pixels) and I almost got it. However I'm confounded by some unknown problem. The code is:
int canvasWidth = panel1.Width,
canvasHeight = panel1.Height;
var minX1 = tockeKoordinate.Min(x => x.startX);
var minX2 = tockeKoordinate.Min(x => x.endX);
var minX = Math.Min(minX1, minX2);
var maxX1 = tockeKoordinate.Max(x => x.startX);
var maxX2 = tockeKoordinate.Max(x => x.endX);
var maxX = Math.Max(maxX1, maxX2);
var maxY1 = tockeKoordinate.Max(x => x.startY);
var maxY2 = tockeKoordinate.Max(x => x.endY);
var maxY = Math.Max(maxY1, maxY2);
var minY1 = tockeKoordinate.Min(x => x.startY);
var minY2 = tockeKoordinate.Min(x => x.endY);
var minY = Math.Min(minY1, minY2);
double coordinatesWidth = Math.Abs(maxX - minX),
coordinatesHeight = Math.Abs(maxY - minY);
float coefWidth = (float)coordinatesWidth / canvasWidth,
coefHeight = (float)coordinatesHeight / canvasHeight;
Basically I check the List for minimum and maximum XY coordinates, so I know what the extreme values are. Then I use a coeficient value to recalculate the coords in pixels so that are within the panel. When I use this:
drawLine(Math.Abs((float)(line.startX - minX) / coefWidth),
Math.Abs((float)(line.startY - minY) / coefHeight),
Math.Abs((float)(line.endX - maxX) / coefWidth),
Math.Abs((float)(line.endY - maxY) / coefHeight));
which is in foreach loop that iterates trough all the elements from the List . The drawline() method is as follows:
private void drawLine(float startX, float startY, float endX, float endY)
{
PointF[] points =
{
new PointF(startX, startY),
new PointF(endX, endY),
};
g.DrawLine(myPen, points[0], points[1]);
}
WHen all of this is put together, I get this picture:
I know for a fact that the "lines" should be connected and form shapes, in this case they represent roads in a suburban area.
I figured that it treats every coordinate set like it is the only one and then scales it to the panel dimensions. Actually it should scale it in reference to all of the other coordinates
It should "zoom" them out and connect with each other, because that is the way I defined the panel dimensions and everything else.
EDIT: ToW's solution did the trick, with this line of code changed to use my List:
foreach (var line in tockeKoordinate)
{
gp.AddLine((float)(line.startX), (float)(line.startY), (float)(line.endX), (float)(line.endY));
gp.CloseFigure();
}
End result when working properly:
As far as I can see your best bet would be to add all those lines to a GraphicsPath.
After it is complete you can look at its bounding rectangle and compare it to the size your Panel offers.
Then you can calculate a scale for the Graphics object to draw with and also a translation.
Finally you draw the lines with Graphics.DrawPath.
All with just 2 division on your side :-)
Here is an example:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics G = e.Graphics;
Random R = new Random(13);
GraphicsPath gp = new GraphicsPath();
for (int i = 0; i < 23; i++)
{
gp.AddLine(R.Next(1234), R.Next(1234), R.Next(1234), R.Next(1234));
gp.CloseFigure(); // disconnect lines
}
RectangleF rect = gp.GetBounds();
float scale = Math.Min(1f * panel1.Width / rect.Width,
1f * panel1.Height / rect.Height);
using (Pen penUnscaled = new Pen(Color.Blue, 4f))
using (Pen penScaled = new Pen(Color.Red, 4f))
{
G.Clear(Color.White);
G.DrawPath(penUnscaled, gp);
G.ScaleTransform(scale, scale);
G.TranslateTransform(-rect.X, -rect.Y);
G.DrawPath(penScaled, gp);
}
}
A few notes:
The blue lines do not fit onto the panel
The red lines are scaled down to fit
The Pen is scaled along with the rest of the Graphics but won't go under 1f.
To create connected lines do add a PointF[] or, more convenient a List<PointF>.ToArray().
I really should have used panel1.ClientSize.Width instead of panel1.Width etc..; now it is off a tiny bit at the bottom; bad boy me ;-)

How to use Clipper library to enlarge and fill paths

I am trying to use the Clipper library to modify a graphics path.
I have list of widths that represent outlines / strokes. I want to start with the largest first and work my way down to the smallest.
For this example, we will add 2 strokes with widths of 20 and 10.
I want to take take my graphics path, and expand / offset it by 20 pixels into a new graphics path. I do not want to alter the original path. Then I want to fill the new graphics path with a solid color.
Next, I want to take my original graphics path, and expand / offset it by 10 pixels into a new graphics path. I want to fill this new path with a different color.
Then I want to fill my original path with a different color.
What is the proper way to do this. I have the following method that I created to try and do this, but it is not working properly.
private void createImage(Graphics g, GraphicsPath gp, List<int> strokeWidths)
{
ClipperOffset pathConverter = new ClipperOffset();
Clipper c = new Clipper();
gp.Flatten();
foreach(int strokeSize in strokeWidths)
{
g.clear();
ClipperPolygons polyList = new ClipperPolygons();
GraphicsPath gpTest = (GraphicsPath)gp.Clone();
PathToPolygon(gpTest, polyList, 100);
gpTest.Reset();
c.Execute(ClipType.ctUnion, polyList, PolyFillType.pftPositive, PolyFillType.pftEvenOdd);
pathConverter.AddPaths(polyList, JoinType.jtMiter, EndType.etClosedPolygon);
pathConverter.Execute(ref polyList, strokeSize * 100);
for (int i = 0; i < polyList.Count; i++)
{
// reverses scaling
PointF[] pts2 = PolygonToPointFArray(polyList[i], 100);
gpTest.AddPolygon(pts2);
}
g.FillPath(new SolidBrush(Color.Red), gpTest);
}
}
private void PathToPolygon(GraphicsPath path, ClipperPolygons polys, Single scale)
{
GraphicsPathIterator pathIterator = new GraphicsPathIterator(path);
pathIterator.Rewind();
polys.Clear();
PointF[] points = new PointF[pathIterator.Count];
byte[] types = new byte[pathIterator.Count];
pathIterator.Enumerate(ref points, ref types);
int i = 0;
while (i < pathIterator.Count)
{
ClipperPolygon pg = new ClipperPolygon();
polys.Add(pg);
do
{
IntPoint pt = new IntPoint((int)(points[i].X * scale), (int)(points[i].Y * scale));
pg.Add(pt);
i++;
}
while (i < pathIterator.Count && types[i] != 0);
}
}
private PointF[] PolygonToPointFArray(ClipperPolygon pg, float scale)
{
PointF[] result = new PointF[pg.Count];
for (int i = 0; i < pg.Count; ++i)
{
result[i].X = (float)pg[i].X / scale;
result[i].Y = (float)pg[i].Y / scale;
}
return result;
}
While you've made a pretty reasonable start, you seem to be getting muddled in your createImage() function. You mention wanting different colors with the different offsets and so you're missing a colors array to match your strokeWidths array. Also, it's unclear to me what you're doing with the clipping (union) stuff, but it's probably unnecessary.
So in pseudo-code I suggest something like the following ....
static bool CreateImage(Graphics g, GraphicsPath gp,
List<int> offsets, List<Color> colors)
{
const scale = 100;
if (colors.Count < offsets.Count) return false;
//convert GraphicsPath path to Clipper paths ...
Clipper.Paths cpaths = GPathToCPaths(gp.Flatten(), scale);
//setup the ClipperOffset object ...
ClipperOffset co = new ClipperOffsets();
co.AddPaths(cpaths, JoinType.jtMiter, EndType.etClosedPolygon);
//now loop through each offset ...
foreach(offset in offsets, color in colors)
{
Clipper.Paths csolution = new Clipper.Paths();
co.Execute(csolution, offset);
if (csolution.IsEmpty) break; //useful for negative offsets
//now convert back to floating point coordinate array ...
PointF[] solution = CPathToPointFArray(csolution, scale);
DrawMyPaths(Graphics g, solution, color);
}
}
And something to watch for if you were to use increasingly larger offsets, each polygon drawn in the 'foreach' loop would hide previously drawn polygons.

Canvas background for retrieving color

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

Categories