I want to divide my game grid into an array of rectangles. Each rectangle is 40x40 and there are 14 rectangles in every column, with a total of 25 columns. This covers a game area of 560x1000.
This is the code I have set up to make the first column of rectangles on the game grid:
Rectangle[] gameTiles = new Rectangle[15];
for (int i = 0; i <= 15; i++)
{
gameTiles[i] = new Rectangle(0, i * 40, 40, 40);
}
I'm pretty sure this works, but of course I cannot confirm it because rectangles do not render on the screen for me to physically see them. What I would like to do for debugging purposes is to render a border, or fill the rectangle with color so I can see it on the game itself, just to make sure this works.
Is there a way to make this happen? Or any relatively simple way I can just make sure that this works?
Thank you very much.
First, make a 1x1 pixel texture of white for the rectangle:
var t = new Texture2D(GraphicsDevice, 1, 1);
t.SetData(new[] { Color.White });
Now, you need to render the rectangle - assume the Rectangle is called rectangle. For a rendering a filled block, it is very simple - make sure to set the tint Color to be the colour you want. Just use this code:
spriteBatch.Draw(t, rectangle, Color.Black);
For a border, is it more complex. You have to draw the 4 lines that make up the outline (the rectangle here is r):
int bw = 2; // Border width
spriteBatch.Draw(t, new Rectangle(r.Left, r.Top, bw, r.Height), Color.Black); // Left
spriteBatch.Draw(t, new Rectangle(r.Right, r.Top, bw, r.Height), Color.Black); // Right
spriteBatch.Draw(t, new Rectangle(r.Left, r.Top, r.Width , bw), Color.Black); // Top
spriteBatch.Draw(t, new Rectangle(r.Left, r.Bottom, r.Width, bw), Color.Black); // Bottom
Hope it helps!
This worked perfect if you want to draw rectangles over your existing textures. Great when you want to test/see for collisions
http://bluelinegamestudios.com/blog/posts/drawing-a-hollow-rectangle-border-in-xna-4-0/
-----From Site-----
The basic trick to drawing shapes is to make a single-pixel texture which is White, which you can then mix with other colors and display in solid shapes.
// At the top of your class:
Texture2D pixel;
// Somewhere in your LoadContent() method:
pixel = new Texture2D(GameBase.GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
pixel.SetData(new[] { Color.White }); // so that we can draw whatever color we want on top of it
Then in your Draw() method do something like:
spriteBatch.Begin();
// Create any rectangle you want. Here we'll use the TitleSafeArea for fun.
Rectangle titleSafeRectangle = GraphicsDevice.Viewport.TitleSafeArea;
// Call our method (also defined in this blog-post)
DrawBorder(titleSafeRectangle, 5, Color.Red);
spriteBatch.End();
And the actual method that does the drawing:
private void DrawBorder(Rectangle rectangleToDraw, int thicknessOfBorder, Color borderColor)
{
// Draw top line
spriteBatch.Draw(pixel, new Rectangle(rectangleToDraw.X, rectangleToDraw.Y, rectangleToDraw.Width, thicknessOfBorder), borderColor);
// Draw left line
spriteBatch.Draw(pixel, new Rectangle(rectangleToDraw.X, rectangleToDraw.Y, thicknessOfBorder, rectangleToDraw.Height), borderColor);
// Draw right line
spriteBatch.Draw(pixel, new Rectangle((rectangleToDraw.X + rectangleToDraw.Width - thicknessOfBorder),
rectangleToDraw.Y,
thicknessOfBorder,
rectangleToDraw.Height), borderColor);
// Draw bottom line
spriteBatch.Draw(pixel, new Rectangle(rectangleToDraw.X,
rectangleToDraw.Y + rectangleToDraw.Height - thicknessOfBorder,
rectangleToDraw.Width,
thicknessOfBorder), borderColor);
}
Related
I am playing around with the Microsoft Vision API and learning C# as I go, and one of the properties of a Vision object is an "Accent Color" of the image.
From a series of images analysed, I want to show those colors ordered in a Linear Gradient -- because that will be able to show the user that most pictures are (for example) blue, because Blue colors take up half of the gradient etc.
I have this working, in that I am ordering the Colors by Hue, and able to produce a Linear Gradient I am filling into a Bitmap.
BUT, the gradient by default is Horizontal, and I need Vertical -- so I've used LinearGradientBrush.RotateTransform(90) which rotates that actual gradient fine, but doesn't seem to fill the entire Rectangle, and it repeats. This is what I'm getting as a result:
How do I create a Vertical LinearGradient that fills up the entire Height of the Rectangle object for my Bitmap?
Here is my code:
private Bitmap CreateColorGradient(System.Drawing.Rectangle rect, System.Drawing.Color[] colors)
{
Bitmap gradient = new Bitmap(rect.Width, rect.Height);
LinearGradientBrush br = new LinearGradientBrush(rect, System.Drawing.Color.White, System.Drawing.Color.White, 0, false);
ColorBlend cb = new ColorBlend();
// Positions
List<float> positions = new List<float>();
for (int i = 0; i < colors.Length; i++) positions.Add((float)i / (colors.Length - 1));
cb.Positions = positions.ToArray();
cb.Colors = colors;
br.InterpolationColors = cb;
br.RotateTransform(90);
using (Graphics g = Graphics.FromImage(gradient))
g.FillRectangle(br, rect);
return gradient;
}
Thanks for reading and any help -- also if you see something in my code that could be done better please point it out it helps me learn :)
You are ignoring the angle parameter in the constructor. And as you instead do a rotation on the Grahics object your brush rectangle is no longer correctly fits the target bitmap and the gradient can't fill it; so it repeats.
To correct
simply set the angle to 90 and
remove the br.RotateTransform(90); call.
Here this changes the result from the left to the middle version:
While we're looking at it, do take note of the WrapMode property of LinearGradientBrush. What you see in the first image is the default WrapMode.Clamp. Often a changing to one of the Flip mode helps.. So lets have a look at the impact of it one the first version at the right position.
It looks like WrapMode.TileFlipY but since I have brought back the rotation it actually takes a value WrapMode.TileFlipX or WrapMode.TileFlipXY:
br.WrapMode = WrapMode.TileFlipX;
I am trying to using antialiasing but I don't why it isn't working:
{
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 0, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
}
First it should be noted that the Graphics object does not contain any graphics; it is a tool that lets you draw onto a related bitmap, including a control's surface. Therefore changing any of its properties, like the SmoothingMode only influences graphics you draw from then on, not anything you have drawn before..
The circle certainly would have antialised pixels if you would draw it after setting the SmoothingMode from its default None to AntiAlias.
The Line is vertical, so it doesn't need antialiasing except at its ends, where there is some. But if you tilt it or move it to a non-integer position anti-aliasing will show!
Let's modify your code a little and look closely at the result:
Pen pen = new Pen(Color.Black, 3);
Pen r = new Pen(Color.YellowGreen, 3);
Graphics b = panel2.CreateGraphics();
b.DrawEllipse(pen, 6, 6, 90, 90);
b.SmoothingMode = SmoothingMode.AntiAlias;
b.DrawLine(r, new Point(50, 90), new Point(50, 0));
b.DrawLine(r, new Point(60, 90), new Point(70, 0));
b.DrawLine(r, new PointF(40.5f, 90), new PointF(40.5f, 0));
b.DrawEllipse(pen, 6, 6, 30, 30);
The smaller circle has many gray pixels and even the original green line has a lighter top end. The two new lines are fully anti-aliased now, one because it is tilted, the other because it sits 'between' pixels.
Btw: If it is turned on you will also see anti-alising when your Pen.Width is even or when it is a non-integer number. The reason for the latter should be obvious; the former comes from the PenAlignment property. Its default Center tries to center the pen, but not at the pixel boundary but at the center of the coordinate pixels. Therefore only an uneven width will completely fill the pixels and not cause anti-aliasing. For closed shapes you can change this behaviour by changing the Pen.Alignment to Inset:
This property determines how the Pen draws closed curves and
polygons. The PenAlignment enumeration specifies five values;
however, only two values—Center and Inset—will change the appearance
of a drawn line. Center is the default value for this property and
specifies that the width of the pen is centered on the outline of the
curve or polygon. A value of Inset for this property specifies that the
width of the pen is inside the outline of the curve or polygon. The
other three values, Right, Left, and Outset, will result in a pen that
is centered.
A Pen that has its alignment set to Inset will yield unreliable
results, sometimes drawing in the inset position and sometimes in the
centered position.Also, an inset pen cannot be used to draw compound
lines and cannot draw dashed lines with Triangle dash caps.
PS: The question was not about how to draw properly, so let me just note that you never ought to do it using control.CreateGraphics as this will always only result in non-persistent graphics. Instead you need to use the Paint event and its e.Graphics object..
I've been fighting with this problem for a few days now and have had zero luck getting it resolved or finding any support on the web. Basically, I am trying to create a VisualBrush with a canvas as the visual element. I want to be able to draw several lines on this canvas and will eventually be mapping it to a 3D surface (2 parallel triangles forming a rectangle). I've set up the texture coordinates and can confirm everything works by simply using an ImageBrush or something of that nature. The problem I am having is that the canvas refuses to maintain its size and is constantly scaled based on the content that is inside it. So for example, if the canvas is 100x100 and I have a single line inside it from (0, 0) to (50, 50), the canvas would be scaled such that only the 50x50 portion with content inside is mapped to the texture coordinates and onto the 3D surface. I have tried many combinations of Viewport/Viewbox/StretchMode/Alignment/etc and can't find anything that stops this from happening. I am setting the Width and Height properties of the canvas so I can't see why it would perform differently in the VisualBrush versus if I simply added it into a grid or some other layout container.
Here's some code to help illustrate the problem.
// create the canvas for the VisualBrush
Canvas drawCanvas = new Canvas();
drawCanvas.Background = Brushes.Transparent; // transparent canvas background so only content is rendered
drawCanvas.Width = 100;
drawCanvas.Height = 100;
// add a line to the canvas
Line l = new Line();
l.X1 = 0;
l.Y1 = 0;
l.X2 = 50;
l.Y2 = 50;
l.Stroke = Brushes.Red;
l.StrokeThickness = 2;
drawCanvas.Children.Add(l);
// create rectangular surface mesh
MeshGeometry3D TableMesh = new MeshGeometry3D();
CreateRectangleModel(
new Point3D(0, 0, 0),
new Point3D(0, 0, 100),
new Point3D(100, 0, 100),
TableMesh);
// need to include texture coordinates for our table mesh to map canvas to 3D model
TableMesh.TextureCoordinates = new PointCollection {
new Point(0, 0), new Point(0, 1), new Point(1, 1),
new Point(1, 1), new Point(0, 1), new Point(0, 0),
new Point(1, 1), new Point(1, 0), new Point(0, 0),
new Point(0, 0), new Point(1, 0), new Point(1, 1)
};
// finally make our visual brush with the canvas we have created
VisualBrush vb = new VisualBrush();
vb.Visual = drawCanvas;
Material TableMaterial = new DiffuseMaterial(vb);
// add our new model to the scene
GeometryModel3D geometry = new GeometryModel3D(TableMesh, TableMaterial);
Model3DGroup group = new Model3DGroup();
group.Children.Add(geometry);
ModelVisual3D previewTable = new ModelVisual3D();
previewTable.Content = group;
MainViewport.Children.Add(previewTable);
And the one function I referenced is
private void CreateRectangleModel(Point3D pStart, Point3D pCorner1, Point3D pEnd, MeshGeometry3D mesh)
{
// pCorner -> O--O <- pEnd
// | /|
// |/ |
// pStart -> O--O <- pCorner2
// find the remaining point for our rectangle
Point3D pCorner2 = new Point3D(
pStart.X + (pEnd.X - pCorner1.X),
pStart.Y + (pEnd.Y - pCorner1.Y),
pStart.Z + (pEnd.Z - pCorner1.Z));
// add necessary triangles to our group (we create 2 facing up, 2 facing down)
CreateTriangleModel(pStart, pCorner1, pEnd, mesh);
CreateTriangleModel(pEnd, pCorner1, pStart, mesh);
CreateTriangleModel(pEnd, pCorner2, pStart, mesh);
CreateTriangleModel(pStart, pCorner2, pEnd, mesh);
}
I can't get the canvas to maintain its set size of 100x100 unless the children content goes all the way to these extremes. For my purposes, the line coordinates could be anywhere within the canvas and I need to have it maintain its desired shape regardless of the content.
Any advice would be greatly appreciated!
Some things to try:
Create an image file of 100x100 pixels by hand and then put it into an ImageBrush, and use that brush in your DiffuseMaterial. This is to confirm that your texture coords, etc are being set up properly.
Instead of using a Transparent Background colour on your Canvas, try Green (this is to see if the texture is being cropped based on transparent pixels)
If the cropping is being done of your texture (due to transparency)...then try putting a rectangle on the Canvas at the dimensions you want the texture to be...maybe that will change something.
Or try using RenderTargetBitmap to create an image from your Canvas visual...then use that image within an ImageBrush. (you can then confirm that the image is of your expected area of 100x100).
Finally, instead of using a Canvas+VisualBrush to create your texture try creating the Brush by using a DrawingBrush+DrawingGroup+GeometryDrawing
What I want to do is basically cropping a rectangle from an image. However, it should satisfy some special cases:
I want to crop an angled rectangle on image.
I don't want to rotate the image and crop a rectangle :)
If cropping exceeds the image size, I don't want to crop an empty background color.
I want to crop from back of the starting point, that will end at starting point when rectangle size completed. I know I couldn't explain well so if I show what I want visually:
The blue dot is the starting point there, and the arrow shows cropping direction. When cropping exceeds image borders, it will go back to the back of the starting point as much as, when the rectangle width and height finished the end of the rectangle will be at starting point.
Besides this is the previous question I asked:
How to crop a cross rectangle from an image using c#?
In this question, I couldn't predict that a problem can occur about image dimensions so I didn't ask for it. But now there is case 3. Except case three, this is exactly same question. How can I do this, any suggestions?
What needs to be done is to add offsets to the matrix alignment. In this case I am taking one extra length of the rectangle from each side (total 9 rectangles) and offsetting the matrix each time.
Notice that it is necessary to place offset 0 (the original crop) last, otherwise you will get the wrong result.
Also note that if you specify a rectangle that is bigger than the rotated picture you will still get empty areas.
public static Bitmap CropRotatedRect(Bitmap source, Rectangle rect, float angle, bool HighQuality)
{
int[] offsets = { -1, 1, 0 }; //place 0 last!
Bitmap result = new Bitmap(rect.Width, rect.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = HighQuality ? InterpolationMode.HighQualityBicubic : InterpolationMode.Default;
foreach (int x in offsets)
{
foreach (int y in offsets)
{
using (Matrix mat = new Matrix())
{
//create the appropriate filler offset according to x,y
//resulting in offsets (-1,-1), (-1, 0), (-1,1) ... (0,0)
mat.Translate(-rect.Location.X - rect.Width * x, -rect.Location.Y - rect.Height * y);
mat.RotateAt(angle, rect.Location);
g.Transform = mat;
g.DrawImage(source, new Point(0, 0));
}
}
}
}
return result;
}
To recreate your example:
Bitmap source = new Bitmap("C:\\mjexample.jpg");
Bitmap dest = CropRotatedRect(source, new Rectangle(86, 182, 87, 228), -45, true);
I'm drawing directly onto a form with two FillPolygon statements to create two arrows - one black, one white. (The white arrow is slightly smaller and drawn over the black arrow.)
Here's the code in the form's OnPaint.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillPolygon(brushBlack, travelArrow);
g.FillPolygon(brushWhite, featureArrow);
}
Works great. Now since the white arrow is going to be drawn several times in different rotations, I decided to use double-buffering to avoid as much flicker as possible.
I first created a DrawFeatureArrow method that I call in OnPaint.
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.FillPolygon(brushBlack, travelArrow);
DrawFeatureArrow(this, e);
}
Note: There's no rotation of the white arrow coded yet, I'm just trying to get the double-buffering set up.
DrawFeatureArrow looks like this.
private void DrawFeatureArrow(object sender, PaintEventArgs e)
{
Bitmap buffer = new Bitmap(60, 159);
Graphics gOff = Graphics.FromImage(buffer);
gOff.FillRectangle(brushGreen, 0, 0, buffer.Width, buffer.Height);
gOff.FillPolygon(brushWhite, featureArrow);
ImageAttributes attr = new ImageAttributes();
attr.SetColorKey(Color.Green, Color.Green);
Rectangle srcRect = new Rectangle(0, 0, bugger.Width, buffer.Height);
Rectangle destRect = new Rectangle(90, 66, 60, 159);
Graphics f = e.Graphics;
// Should draw green rectangle and white arrow
f.DrawImage(buffer, 90, 66); // Draws just a green rectangle
// If uncommented, should draw just white arrow (green rectangle hidden by SetColorKey)
// f.DrawImage(buffer, destRect, 0, 0, buffer.Width, buffer.Height, GraphicsUnit.Pixel, attr);
f.Dispose();
gOff.Dispose();
buffer.Dispose();
}
When run, the green rectangle is drawn but not the white arrow.
Strangely enough, in DrawFeatureArrow, if you replace this
gOff.FillPolygon(brushWhite, featureArrow);
With this
gOff.FillRectangle(brushWhite, 10, 10, 20, 20);
You get a tiny white rectangle in the upper left of the green rectangle.
Very strange behavior. Hope someone can point out what I'm doing incorrectly.
Thanks in advance for the help.
You're drawing your feature arrow over buffer that has dimensions of 60, 159. Maybe the location of the feature arrow is outside of those dimensions and therefore did not end in the bitmap.