Drawing Circles In Xamarin.iOS (Xamarin Monotouch) for Showing Progress Graphically - c#

As I'm very new to Xamarin World and new to its controls. I would like to add Circles to Show Work Progress in my mono touch app. For showing Progress I have to mark an Arc in the circle. And if possible any one can help with me a sample code. Awaiting an answer, Thanks a lot in advance.

using System;
using UIKit;
using CoreGraphics;
namespace CircleTest.Touch
{
public class CircleGraph : UIView
{
int _radius = 10;
int _lineWidth = 10;
nfloat _degrees = 0.0f;
UIColor _backColor = UIColor.FromRGB(46, 60, 76);
UIColor _frontColor = UIColor.FromRGB(234, 105, 92);
//FromRGB (234, 105, 92);
public CircleGraph (CGRect frame, int lineWidth, nfloat degrees)
{
_lineWidth = lineWidth;
_degrees = degrees;
this.Frame = new CGRect(frame.X, frame.Y, frame.Width, frame.Height);
this.BackgroundColor = UIColor.Clear;
}
public CircleGraph (CGRect frame, int lineWidth, UIColor backColor, UIColor frontColor)
{
_lineWidth = lineWidth;
this.Frame = new CGRect(frame.X, frame.Y, frame.Width, frame.Height);
this.BackgroundColor = UIColor.Clear;
}
public override void Draw (CoreGraphics.CGRect rect)
{
base.Draw (rect);
using (CGContext g = UIGraphics.GetCurrentContext ()) {
_radius = (int)( (this.Bounds.Width) / 3) - 8;
DrawGraph(g, this.Bounds.GetMidX(), this.Bounds.GetMidY());
};
}
public void DrawGraph(CGContext g,nfloat x0,nfloat y0) {
g.SetLineWidth (_lineWidth);
// Draw background circle
CGPath path = new CGPath ();
_backColor.SetStroke ();
path.AddArc (x0, y0, _radius, 0, 2.0f * (float)Math.PI, true);
g.AddPath (path);
g.DrawPath (CGPathDrawingMode.Stroke);
// Draw overlay circle
var pathStatus = new CGPath ();
_frontColor.SetStroke ();
pathStatus.AddArc(x0, y0, _radius, 0, _degrees * (float)Math.PI, false);
g.AddPath (pathStatus);
g.DrawPath (CGPathDrawingMode.Stroke);
}
}
}
Actually this is what iam actually supposed to do. Its working for me
This is how it look like. U can create it like a class file and simply u can assign to a UIView.
For more reference you can use this sample project Pi Graph
[EDIT]: The Draw method originally passed the View.Frame x,y to the DrawGraph method. This should be View.Bounds (modified above to reflect this). Remember that the frame x,y is in reference to the containing superview and bounds is referenced to the current view. This would have worked if the view was added at 0,0 but once you start moving around the UI it disappears. When the arcs are drawn the values for x,y passed to AddArc need to be in reference to the current view not the parent superview.

Drawing a circle on a GLContext isn't that hard to do and is the same as you would do in Objective-C or Swift.
I assume you want to create your own view which you can reuse. To do so, simply inherit from UIView:
public class CircleView : UIView
{
}
Now to draw anything in your new custom view you want to override the Draw method:
public override void Draw(RectangleF rect)
{
base.Draw(rect);
// draw stuff in here
}
To draw stuff you need to get hold of the current context from UIGraphics, which can be done like so:
using (var gctx = UIGraphics.GetCurrentContext())
{
// use gctx to draw stuff
}
The CGContext you get back, is very similar to Canvas on Android for instance. It has helper methods to draw arcs, circles, rectangles, points and much more.
So to draw a simple circle in that context, you do:
gctx.SetFillColor(UIColor.Cyan.CGColor);
gctx.AddEllipseInRect(rect);
So combine everything you get:
public class CircleView : UIView
{
public override Draw(RectangleF rect)
{
base.Draw(rect);
using (var gctx = UIGraphics.GetCurrentContext())
{
gctx.SetFillColor(UIColor.Cyan.CGColor);
gctx.AddEllipseInRect(rect);
}
}
}
That is it! Well, not exactly, this is where you need to start think of how you want to draw your progress indicator. What I think would probably work is:
Draw the back ground
Draw the borders
Calculate the degrees from the progress in percent
Use the degrees to create an arc using gctx.AddArc(), which can take an angle and draw an arc.
Draw the percentage as a string in the middle
To draw a string you will need to convert your string to a NSAttributedString then use CTLine to draw the text like:
using(var line = new CTLine(nsAttrString))
line.Draw(gctx);

Minor alterations to the answer provided by #SARATH as copying and pasting did not yield the desired result.
Changed _degrees to _percentComplete
Fixed overload constructor for changing colors by adding a param for percentComplete and added missing member variable assignments for _backColor and _frontColor
Added constant float value for drawing a full circle (FULL_CIRCLE)
Multiply _percentComplete by FULL_CIRCLE to get the end angle for both arcs (with different directions)
Calculation of the radius
public class CircleGraph : UIView
{
const float FULL_CIRCLE = 2 * (float)Math.PI;
int _radius = 10;
int _lineWidth = 10;
nfloat _percentComplete = 0.0f;
UIColor _backColor = UIColor.LightGray; //UIColor.FromRGB(46, 60, 76);
UIColor _frontColor = UIColor.Green; //UIColor.FromRGB(234, 105, 92);
public CircleGraph(CGRect frame, int lineWidth, nfloat percentComplete)
{
_lineWidth = lineWidth;
_percentComplete = percentComplete;
this.Frame = new CGRect(frame.X, frame.Y, frame.Width, frame.Height);
this.BackgroundColor = UIColor.Clear;
}
public CircleGraph(CGRect frame, int lineWidth, nfloat percentComplete, UIColor backColor, UIColor frontColor)
{
_lineWidth = lineWidth;
_percentComplete = percentComplete;
this.Frame = new CGRect(frame.X, frame.Y, frame.Width, frame.Height);
this.BackgroundColor = UIColor.Clear;
_backColor = backColor;
_frontColor = frontColor;
}
public override void Draw(CoreGraphics.CGRect rect)
{
base.Draw(rect);
using (CGContext g = UIGraphics.GetCurrentContext())
{
var diameter = Math.Min(this.Bounds.Width, this.Bounds.Height);
_radius = (int)(diameter / 2) - _lineWidth;
DrawGraph(g, this.Bounds.GetMidX(), this.Bounds.GetMidY());
};
}
public void DrawGraph(CGContext g, nfloat x, nfloat y)
{
g.SetLineWidth(_lineWidth);
// Draw background circle
CGPath path = new CGPath();
_backColor.SetStroke();
path.AddArc(x, y, _radius, 0, _percentComplete * FULL_CIRCLE, true);
g.AddPath(path);
g.DrawPath(CGPathDrawingMode.Stroke);
// Draw overlay circle
var pathStatus = new CGPath();
_frontColor.SetStroke();
// Same Arc params except direction so colors don't overlap
pathStatus.AddArc(x, y, _radius, 0, _percentComplete * FULL_CIRCLE, false);
g.AddPath(pathStatus);
g.DrawPath(CGPathDrawingMode.Stroke);
}
}
Example
var circleGraph = new CircleGraph(circleGraphView.Frame, 20, 0.75f);
CircleGraph at 75%

Related

Replacement for CSS3 function repeating-linear-gradient() in .NET (WinForms)

Is there any replacement (analogue) for CSS3 function repeating-linear-gradient() in .NET (WinForms, not WPF)?
I need to paint repeating "zebra stripes" (e.g. red, blue, green, red, blue, green, ...) at an angle 45 degrees.
UPD:
Following Jimi's advice I managed to solve the problem only partially:
private void DrawRepeatingStripes(int degree, int stripeWidth, Color[] colors, Rectangle rect, Graphics graphics)
{
using (var img = new Bitmap(colors.Length * stripeWidth, rect.Height))
{
using (var g = Graphics.FromImage(img))
{
for (int i = 0; i < colors.Length; i++)
{
// TODO: cache SolidBrush
g.FillRectangle(new SolidBrush(colors[i]), stripeWidth * i, 0, stripeWidth, rect.Height);
}
}
using (var tb = new TextureBrush(img, WrapMode.Tile))
{
using (var myMatrix = new Matrix())
{
myMatrix.Rotate(degree);
graphics.Transform = myMatrix;
graphics.FillRectangle(tb, rect);
graphics.ResetTransform();
}
}
}
}
Usage (in some form's code):
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
DrawRepeatingStripes(45, 10, new Color[] { Color.Red, Color.Yellow, Color.Green }, e.ClipRectangle, e.Graphics);
}
The problem is that rotation is... well, a rotation, so part of rect is filled with stripes and part is empty. Have no idea how to solve it :(
An example about using a TextureBrush to fill the surface of a Control used as canvas.
The LinearRepeatingGradient class exposes a bindable ColorBands Property (of Type BindingList<ColorBand>) that allows to add or remove ColorBand objects, a record that defines the Color and size of each band you want to generate.
The RotationAngle Property specifies the rotation to apply to the rendering.
In the Paint event of the Control used as canvas, call the Fill(Graphics g) method, passing the e.Graphics object provided by the PaintEventArgs argument.
A new Bitmap is generated, based on the content of the ColorBands Property.
When the rotation angle cannot be exactly divided by 90, the canvas' dimensions are inflated by a third of its diagonal (as the maximum distance from the non-rotated rectangle).
The TextureBrush fills this inflated surface, so no blank space is left on the sides of the canvas.
Since this test sample is built with .NET 7, I'm using record to store the color bands' settings. You can replace it with a class object without changes to the rest of the code.
public record ColorBand(Color Color, int Size) {
public override string ToString() => $"Color: {Color.Name} Size: {Size}";
}
Same as above: using declaration instead of using statements
using System.Drawing;
using System.Drawing.Drawing2D;
public class LinearRepeatingGradient
{
public LinearRepeatingGradient(float rotation = .0f)
{
ColorBands = new BindingList<ColorBand>();
RotationAngle = rotation;
}
public float RotationAngle { get; set; }
[Bindable(true), ListBindable(BindableSupport.Default)]
public BindingList<ColorBand> ColorBands { get; }
public void Fill(Graphics g) => Fill(g, g.ClipBounds);
public void Fill(Graphics g, Rectangle fillArea) => Fill(g, new RectangleF(fillArea.Location, fillArea.Size));
protected virtual void Fill(Graphics g, RectangleF display)
{
if (ColorBands is null || ColorBands.Count == 0 || g.Clip.IsInfinite(g)) return;
var canvas = InflateCanvas(display);
var centerPoint = new PointF(canvas.X + canvas.Width / 2, canvas.Y + canvas.Height / 2);
using var texture = GetTexture(canvas.Width);
if (texture is null) return;
using var brush = new TextureBrush(texture, WrapMode.Tile);
using var mx = new Matrix();
mx.RotateAt(RotationAngle, centerPoint);
g.Transform = mx;
g.FillRectangle(brush, canvas);
g.ResetTransform();
}
private RectangleF InflateCanvas(RectangleF rect)
{
if (RotationAngle % 90.0f == 0) return rect;
float maxInflate = (float)Math.Sqrt(Math.Pow(rect.X - rect.Right, 2) +
Math.Pow(rect.Y - rect.Bottom, 2)) / 3.0f;
var canvas = rect;
canvas.Inflate(maxInflate, maxInflate);
return canvas;
}
private Bitmap? GetTexture(float width)
{
int height = ColorBands!.Sum(c => c.Size);
if (height <= 0) return null;
var texture = new Bitmap((int)(width + .5f), height);
int startPosition = 0;
using var g = Graphics.FromImage(texture);
for (int i = 0; i < ColorBands!.Count; i++) {
var rect = new Rectangle(0, startPosition, texture.Width, ColorBands![i].Size);
using var brush = new SolidBrush(ColorBands![i].Color);
g.FillRectangle(brush, rect);
startPosition += ColorBands![i].Size;
}
return texture;
}
}
This is how it works:
Since the ColorBands property is bindable, you can use data bindings to perform actions, when a ColorBand object is added or removed and also bind the ColorBands collection to Controls, as shown in the animation:
public partial class SomeForm : Form {
LinearRepeatingGradient gradient = new();
public SomeForm()
{
InitializeComponent();
[DataGridView].DataSource = gradient.ColorBands;
gradient.ColorBands.ListChanged += (s, e) => someControl.Invalidate();
}
private void someControl_Paint(object sender, PaintEventArgs e) => gradient.Fill(e.Graphics);
As a consequence, when you add a new ColorBand (or remove it), the internal collection changes and the Control used as canvas is invalidated, showing the new fill:
gradient.ColorBands.Add(new ColorBand(Color.Red, 45f));
The RotationAngle property doesn't use data bindings, so you have to invalidate the canvas manually when you change it. You can of course change that and make this property bindable:
gradient.RotationAngle = 215f;
someControl.Invalidate();

How to draw a rounded checker on a checkerboard with C#?

I have an online checkers game project and I have a problem with drawing the checkers on the panel array elements. The checkerboard pattern is drawn in the form's onload event. I have a method to draw a circle and fill the same circle to create a round shape on each checkerboard square but my form is just showing the squares with no checker pieces on them.
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Checkers
{
public partial class Form1 : Form
{
private Panel[,] _chessBoardPanels;
public Form1()
{
InitializeComponent();
}
/*This method is supposed to draw a circle which later should be
filled by the method that follows*/
public static void drawCircle(Graphics g,Pen pen,float centerX,float centerY, float radius)
{
g.DrawEllipse(pen, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
/*This method fills a circle drawn above on the panel with the desired color
*/
public static void fillCircle(Graphics g, Brush b, float centerX, float centerY, float radius)
{
g.FillEllipse(b, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
private void Form1_Load(object sender, EventArgs e)
{
//controls the size of a single square on the checkerboard
const int tileSize = 100;
//controls the number of squares on either side of the board
const int gridSize = 8;
//this is fr applying different colors to the board so the pattern is realized
var clr1 = Color.DarkGray;
var clr2 = Color.White;
// initialize the "chess board"
_chessBoardPanels = new Panel[gridSize, gridSize];
// double for loop to handle all rows and columns
for (var n = 0; n < gridSize; n++)
{
for (var m = 0; m < gridSize; m++)
{
// create new Panel control which will be one
// chess board tile
var newPanel = new Panel
{
Size = new Size(tileSize, tileSize),
Location = new Point(tileSize * n, tileSize * m)
};
//get the graphics context from each panel and store in g
Graphics g = newPanel.CreateGraphics();
//instantiate a pen
Pen penner = new Pen(Brushes.Black, 3);
//defining the center of the circle
float cx = newPanel.Width / 2;
float cy = newPanel.Height / 2;
//defining the radius of the circle
float radius = cx / 2;
// add to Form's Controls so that they show up
Controls.Add(newPanel);
// add to our 2d array of panels for future use
_chessBoardPanels[n, m] = newPanel;
// color the backgrounds
if (n % 2 == 0)
newPanel.BackColor = m % 2 != 0 ? clr1 : clr2;
else
newPanel.BackColor = m % 2 != 0 ? clr2 : clr1;
//draw a new checker piece if this is true
if (n % 2 == 0)
drawCircle(g, penner, cx, cy, radius);
else
;
}
}
}
}
}
Any help to help me draw a circle and fill it so that the image of a checker piece on the board is realized is welcome.
Object oriented solutions are also welcome involving the class Checkers, This is because it would be easier to write methods to move the checker piece to a certain panel on the board with ease.
Following helpful comments on the original answer, I realized you need to use a Control.Paint event to draw the circles on the squares.
Code
public class Checkerpiece
{
//colors of the rounded pieces
Color color;
//specify where the checker is drawn
Panel target_square;
//specify the center of the circle
float center_x;
float center_y;
//specify the radius of the checker piece
float radii;
//fill the details inside the constructor
public Checkerpiece(Panel mypanel,Color color)
{
this.color = color;
this.target_square = mypanel;
this.center_x = mypanel.Width / 2;
this.center_y = mypanel.Height / 2;
this.radii = mypanel.Width / 2;
}
//this method draws the checkerpiece on the target panel
public void draw()
{
this.target_square.Paint += Target_square_Paint;
}
//this event will redraw the circles as needed
private void Target_square_Paint(object sender, PaintEventArgs e)
{
Pen mypen = new Pen(color, 3);
drawCircle(e.Graphics, mypen, this.center_x, this.center_y, this.radii);
}
public static void drawCircle(Graphics g, Pen pen, float centerX, float centerY, float radius)
{
g.DrawEllipse(pen, centerX - radius, centerY - radius,
radius + radius, radius + radius);
}
}
Output

Is there a fast way to manipulate and buffer a screen in Windows Forms?

I am working on a game for learning purposes, I want to make it only with the .NET-Framework and a Windows Forms project in C#.
I want to get the 'screen' (Something that can be displayed on the window) as an int[]. Modify the array and reapply the altered array to the 'screen' in a buffered manner (So that it doesn't flicker).
I am currently using a Panel, which I draw a Bitmap on with Graphics. The Bitmap is converted to an int[] which I then can modify and reapply to the Bitmap and redraw. It works, but is very slow, especially because I have to scale up the image every frame because my game is only 300x160 and the screen 900x500.
Build up:
// Renders 1 frame
private void Render()
{
// Buffer setup
_bufferedContext = BufferedGraphicsManager.Current;
_buffer = _bufferedContext.Allocate(panel_canvas.CreateGraphics(), new Rectangle(0, 0, _scaledWidth, _scaledHeight));
_screen.clear();
// Get position of player on map
_xScroll = _player._xMap - _screen._width / 2;
_yScroll = _player._yMap - _screen._height / 2;
// Indirectly modifies the int[] '_pixels'
_level.render(_xScroll, _yScroll, _screen);
_player.render(_screen);
// Converts the int[] into a Bitmap (unsafe method is faster)
unsafe
{
fixed (int* intPtr = &_screen._pixels[0])
{
_screenImage = new Bitmap(_trueWidth, _trueHeight, _trueWidth * 4, PixelFormat.Format32bppRgb, new IntPtr(intPtr));
}
}
// Draw generated image on buffer
Graphics g = _buffer.Graphics;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506));
// Update panel buffered
_buffer.Render();
}
Is there a faster way without external libraries to make this work?
I'm not to sure about the unsafe code , But I do know about the buffered graphics manager. I think you should create a class for it instead of creating a new one every time.As well as having all of your sprites widths and heights be determined at the load instead of scaling them. That sped up my small game engine a good bit.
class Spritebatch
{
private Graphics Gfx;
private BufferedGraphics bfgfx;
private BufferedGraphicsContext cntxt = BufferedGraphicsManager.Current;
public Spritebatch(Size clientsize, Graphics gfx)
{
cntxt.MaximumBuffer = new Size(clientsize.Width + 1, clientsize.Height + 1);
bfgfx = cntxt.Allocate(gfx, new Rectangle(Point.Empty, clientsize));
Gfx = gfx;
}
public void Begin()
{
bfgfx.Graphics.Clear(Color.Black);
}
public void Draw(Sprite s)
{
bfgfx.Graphics.DrawImageUnscaled(s.Texture, new Rectangle(s.toRec.X - s.rotationOffset.Width,s.toRec.Y - s.rotationOffset.Height,s.toRec.Width,s.toRec.Height));
}
public void drawImage(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaled(b, rec);
}
public void drawImageClipped(Bitmap b, Rectangle rec)
{
bfgfx.Graphics.DrawImageUnscaledAndClipped(b, rec);
}
public void drawRectangle(Pen p, Rectangle rec)
{
bfgfx.Graphics.DrawRectangle(p, rec);
}
public void End()
{
bfgfx.Render(Gfx);
}
}
This is a example of what I used. It's set up to mimic the Spritebatch in Xna. Drawing the images Unscaled will really increase the speed of it.Also creating one instance of the buffered graphics and Context will be faster then creating a new one every time you have to render. So I would advise you to change the line g.DrawImage(_screenImage, new Rectangle(0, 0, 900, 506)); to DrawImageUnscaled(_screenImage, new Rectangle(0, 0, 900, 506));
Edited : Example of how to scale code on sprite load
public Sprite(Bitmap texture, float x, float y, int width, int height)
{
//texture is the image you originally start with.
Bitmap b = new Bitmap(width, height);
// Create a bitmap with the desired width and height
using (Graphics g = Graphics.FromImage(b))
{
g.DrawImage(texture, 0, 0, width, height);
}
// get the graphics from the new image and draw the old image to it
//scaling it to the proper width and height
Texture = b;
//set Texture which is the final picture to the sprite.
//Uppercase Texture is different from lowercase
Scaling of the image is expensive enough, even when is done without any interpolation. To speed up the things, you should minimize memory allocations: when you create brand new Bitmap every frame, it leads to object creation and pixmap buffer allocation. This fact negates all the benefits you get from BufferedGraphics. I advise you to do the following:
Create the Bitmap instance of required size (equal to screen size) only once, outside of Render method.
Use direct access to bitmap data through LockBits method, and try to implement the scaling be hand using nearest pixel.
Of course, using some sort of hardware acceleration for scaling operation is the most preferred option (for example, in opengl all images are usually drawn using textured rectangles, and rendering such rectangles implicitly involves the process of "scaling" when texture sampling is performed).
I'm wondering why do you call this "very slow", because I did some tests and the performance doesn't seem bad. Also have you measured the performance of your rendering code into int[] '_pixels' (unfortunately you haven't provided that code) separately from the bitmap operations, because it might be the slow part.
About your concrete question. As others mentioned, using preallocated buffered graphics and bitmap objects would speed up it a bit.
But do you really need that int[] buffer? BufferedGraphics is already backed internally with a bitmap, so what really happens is:
(1) You fill the int[] buffer
(2) int[] buffer is copied to the new/preallocated Bitmap
(3) Bitmap from step 2 is copied (applying scale) to the BufferedGraphics internal bitmap (via DrawImage)
(4) BufferedGraphics internal bitmap is copied to the screen (via Render)
As you can see, there are a lot of copy operations. The intended usage of BufferedGraphics is:
(1) You fill the BufferedGraphics internal bitmap via drawing methods of the BufferedGraphics.Graphics property. If setup, the Graphics will do the scaling (as well other transformations) for you.
(2) BufferedGraphics internal bitmap is copied to the screen (via Render)
I don't know what your drawing code is doing, but if you can afford it, this definitely should provide the best performance.
Here is my quick and dirty test in case you are interested in:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
namespace Test
{
enum RenderMode { NewBitmap, PreallocatedBitmap, Graphics }
class Screen
{
Control canvas;
public Rectangle area;
int[,] pixels;
BitmapData info;
Bitmap bitmap;
BufferedGraphics buffer;
float scaleX, scaleY;
public RenderMode mode = RenderMode.NewBitmap;
public Screen(Control canvas, Size size)
{
this.canvas = canvas;
var bounds = canvas.DisplayRectangle;
scaleX = (float)bounds.Width / size.Width;
scaleY = (float)bounds.Height / size.Height;
area.Size = size;
info = new BitmapData { Width = size.Width, Height = size.Height, PixelFormat = PixelFormat.Format32bppRgb, Stride = size.Width * 4 };
pixels = new int[size.Height, size.Width];
bitmap = new Bitmap(size.Width, size.Height, info.PixelFormat);
buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds);
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
ApplyMode();
}
public void ApplyMode()
{
buffer.Graphics.ResetTransform();
if (mode == RenderMode.Graphics)
buffer.Graphics.ScaleTransform(scaleX, scaleY);
}
public void FillRectangle(Color color, Rectangle rect)
{
if (mode == RenderMode.Graphics)
{
using (var brush = new SolidBrush(color))
buffer.Graphics.FillRectangle(brush, rect);
}
else
{
rect.Intersect(area);
if (rect.IsEmpty) return;
int colorData = color.ToArgb();
var pixels = this.pixels;
for (int y = rect.Y; y < rect.Bottom; y++)
for (int x = rect.X; x < rect.Right; x++)
pixels[y, x] = colorData;
}
}
public unsafe void Render()
{
if (mode == RenderMode.NewBitmap)
{
var bounds = canvas.DisplayRectangle;
using (var buffer = BufferedGraphicsManager.Current.Allocate(canvas.CreateGraphics(), bounds))
{
Bitmap bitmap;
fixed (int* pixels = &this.pixels[0, 0])
bitmap = new Bitmap(info.Width, info.Height, info.Stride, info.PixelFormat, new IntPtr(pixels));
buffer.Graphics.InterpolationMode = InterpolationMode.NearestNeighbor;
buffer.Graphics.DrawImage(bitmap, bounds);
buffer.Render();
}
}
else
{
if (mode == RenderMode.PreallocatedBitmap)
{
fixed (int* pixels = &this.pixels[0, 0])
{
info.Scan0 = new IntPtr(pixels); info.Reserved = 0;
bitmap.LockBits(area, ImageLockMode.WriteOnly | ImageLockMode.UserInputBuffer, info.PixelFormat, info);
bitmap.UnlockBits(info);
}
buffer.Graphics.DrawImage(bitmap, canvas.DisplayRectangle);
}
buffer.Render();
}
}
}
class Game
{
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var game = new Game();
game.Run();
}
Form form;
Control canvas;
Screen screen;
Level level;
Player player;
private Game()
{
form = new Form();
canvas = new Control { Parent = form, Bounds = new Rectangle(0, 0, 900, 506) };
form.ClientSize = canvas.Size;
screen = new Screen(canvas, new Size(300, 160));
level = new Level { game = this };
player = new Player { game = this };
}
private void Run()
{
bool toggleModeRequest = false;
canvas.MouseClick += (sender, e) => toggleModeRequest = true;
var worker = new Thread(() =>
{
int frameCount = 0;
Stopwatch drawT = new Stopwatch(), applyT = new Stopwatch(), advanceT = Stopwatch.StartNew(), renderT = Stopwatch.StartNew(), infoT = Stopwatch.StartNew();
while (true)
{
if (advanceT.ElapsedMilliseconds >= 3)
{
level.Advance(); player.Advance();
advanceT.Restart();
}
if (renderT.ElapsedMilliseconds >= 8)
{
frameCount++;
drawT.Start(); level.Render(); player.Render(); drawT.Stop();
applyT.Start(); screen.Render(); applyT.Stop();
renderT.Restart();
}
if (infoT.ElapsedMilliseconds >= 1000)
{
double drawS = drawT.ElapsedMilliseconds / 1000.0, applyS = applyT.ElapsedMilliseconds / 1000.0, totalS = drawS + applyS;
var info = string.Format("Render using {0} - Frames:{1:n0} FPS:{2:n0} Draw:{3:p2} Apply:{4:p2}",
screen.mode, frameCount, frameCount / totalS, drawS / totalS, applyS / totalS);
form.BeginInvoke(new Action(() => form.Text = info));
infoT.Restart();
}
if (toggleModeRequest)
{
toggleModeRequest = false;
screen.mode = (RenderMode)(((int)screen.mode + 1) % 3);
screen.ApplyMode();
frameCount = 0; drawT.Reset(); applyT.Reset();
}
}
});
worker.IsBackground = true;
worker.Start();
Application.Run(form);
}
class Level
{
public Game game;
public int pos = 0; bool right = true;
public void Advance() { Game.Advance(ref pos, ref right, 0, game.screen.area.Right - 1); }
public void Render()
{
game.screen.FillRectangle(Color.SaddleBrown, new Rectangle(0, 0, pos, game.screen.area.Height));
game.screen.FillRectangle(Color.DarkGreen, new Rectangle(pos, 0, game.screen.area.Right, game.screen.area.Height));
}
}
class Player
{
public Game game;
public int x = 0, y = 0;
public bool right = true, down = true;
public void Advance()
{
Game.Advance(ref x, ref right, game.level.pos, game.screen.area.Right - 5, 2);
Game.Advance(ref y, ref down, 0, game.screen.area.Bottom - 1, 2);
}
public void Render() { game.screen.FillRectangle(Color.Yellow, new Rectangle(x, y, 4, 4)); }
}
static void Advance(ref int pos, ref bool forward, int minPos, int maxPos, int delta = 1)
{
if (forward) { pos += delta; if (pos < minPos) pos = minPos; else if (pos > maxPos) { pos = maxPos; forward = false; } }
else { pos -= delta; if (pos > maxPos) pos = maxPos; else if (pos < minPos) { pos = minPos; forward = true; } }
}
}
}

Incorrect scaling of Pen when using Graphics.ScaleTransform

I'm seeing strange behaviour when drawing a line with a scale transform (Graphics.ScaleTransform() - see MSDN) in my OnPaint() method.
When using a large y-scale factor for the ScaleTransform method, then if the x-scale is set above 1x, the line suddenly becomes much larger.
Setting the width of pen with which the line is drawn to -1 seems to get round the problem, but I do not want to draw a very thin line (the line must be printed later, 1px is too thin).
Here's some sample code to demonstrate the problem:
public class GraphicsTestForm : Form
{
private readonly float _lineLength = 300;
private readonly Pen _whitePen;
private Label _debugLabel;
public GraphicsTestForm()
{
ClientSize = new Size(300, 300);
Text = #"GraphicsTest";
SetStyle(ControlStyles.ResizeRedraw, true);
_debugLabel = new Label
{
ForeColor = Color.Yellow,
BackColor = Color.Transparent
};
Controls.Add(_debugLabel);
_lineLength = ClientSize.Width;
_whitePen = new Pen(Color.White, 1f); // can change pen width to -1
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float scaleX = ClientSize.Width / _lineLength;
const int ScaleY = 100;
e.Graphics.Clear(Color.Black);
_debugLabel.Text = #"x-scale: " + scaleX;
// scale the X-axis so the line exactly fits the graphics area
// scale the Y-axis by scale factor
e.Graphics.ScaleTransform(scaleX, ScaleY);
float y = ClientSize.Height / (ScaleY * 2f);
e.Graphics.DrawLine(_whitePen, 0, y, _lineLength, y);
e.Graphics.ResetTransform();
}
}
I would like the line/pen to scale gracefully, without jumping in size so dramatically.
(Additionally, I noticed that when the line is very large, it is not drawn continuously across multiple monitors. Perhaps this is related?)
Try to change the pen width according to the scale:
_whitePen = new Pen(Color.White, 1f / ScaleY);
e.Graphics.DrawLine(_whitePen, 0, y, _lineLength, y);
I just compensated for the overall scaling in the pens line geometry;-
m_Pen->SetWidth(1.0f);
m_Pen->ScaleTransform(1.0f / ZoomX, 1.0f / ZoomY);

Animation in XNA

I have this code:
public class Area
{
Texture2D point;
Rectangle rect;
SpriteBatch _sB;
GameTimer _gt;
int xo, yo, xt, yt;
//List<Card> _cards;
public Area(Texture2D point, SpriteBatch sB)
{
this.point = point;
this._sB = sB;
xt = 660;
yt = 180;
xo = 260;
yo = 90;
}
public void Draw(SpriteBatch spriteBatch)
{
rect = new Rectangle(660, 180, 80, 120);
spriteBatch.Draw(point, rect, Color.White);
_gt = new GameTimer();
_gt.UpdateInterval = TimeSpan.FromSeconds(0.1);
_gt.Draw += OnDraw;
}
private void OnDraw(object sender, GameTimerEventArgs e)
{
this.pass(xo, yo);
if (xo != xt) xo += (xt > xo) ? 10 : -10;
if (yo != yt) yo += (yt > yo) ? 10 : -10;
}
public void pass(int x, int y)
{
rect = new Rectangle(x, y, 80, 120);
_sB.Draw(point, rect, Color.Black);
}
}
So, I can't understand what's wrong. And It's my first project with XNA, and because of it there can be stupid mistake :)
P.S. Sorry. There is a rectangle with coordinates (xt,yt), and I need the animation to move the rectangle to (xo,yo)
P.P.S. I added the full class with corrections, because I don't understand my mistake.
You are drawing the entire animation in one frame.. .you should call Pass with diferent x,y from OnDraw...
EDITED:
1) You don't need the timer, the draw method in game class is by default called 60 frames per second...
2) The Seconds parameter should be calculated as (float) gametime.ElapsedTime.TotalSeconds;
float time;
int xt=660, yt=180;
int xo=260, yo=90;
public void Draw(SpriteBatch spriteBatch, float Seconds)
{
rect = new Rectangle(660, 180, 80, 120);
spriteBatch.Draw(point, rect, Color.White);
this.pass(xo, yo, spriteBatch);
time+= Seconds;
if (time>0.3)
{
if (xo!=xt) xo+= (xt>xo) ? 10 : -10;
if (yo!=yt) yo+= (yt>yo) ? 10 : -10;
time = 0;
}
}
public void pass(int x, int y, spritebatch sb)
{
rect = new Rectangle(x, y, 80, 120);
sb.Draw(point, rect, Color.Red);
}
As you should know this animation will move in a rough mode... if you want to move your sprite smoothly... you can use a Vector2 for your positions and a float for your speed;
Vector2 Origin = new Vector2(260, 90);
Vector2 Target = new Vector2(660, 180);
Vector2 Forward = Vector2.Normalize(Target-Source);
float Speed = 100; // Pixels per second
float Duration = (Target - Origin).Length() / Speed;
float Time = 0;
public void Update(float ElapsedSecondsPerFrame)
{
if (Time<Duration)
{
Time+=Duration;
if (Time > Duration) {
Time = Duration;
Origin = Target;
}
else Origin += Forward * Speed * ElapsedSecondsPerFrame;
}
}
public void Draw()
{
rect = new Rectangle((int) Origin.X, (int) Origin.Y, 80, 120);
sb.Draw(point, rect, Color.Red);
}
If you are willing to use Sprite Vortex to make your animations (a specific version actually) you can use the following class. You have to use Sprite Vortex 1.2.2 because in the newer versions the XML format is changed. Make sure that the XML file you add the property is changed to "Do not compile".
If you need a working example I can send you a very simple one.
p.s. Sprite Vortex should do the same thing you use the other program for, however v 1.2.2 is pretty buggy but not too bad.
the class is here : http://pastebin.com/sNSa7xgQ
Use Sprite Vortex (make sure it's 1.2.2) to select a spritesheet and select the sub images you want to animate. export the XML code.
add the class to your project, it reads the XML and adds automatically creates the frames for the animation for you.

Categories