Centering a canvas on a given point - c#

I am trying to center a rectangle on a point and can't figure out the math needed to do it.
The context this is being used in is a form which paints a bitmap and allows the user to zoom in at a specified point, and pan/scroll while zoomed in.
Here is my code which current works for centering the CanvasBounds on the middle of the ClientRectangle:
private void UpdateCanvas()
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
Point canvasLocation = new Point((ClientRectangle.Width - canvasWidth) / 2, (ClientRectangle.Height - canvasHeight) / 2);
CanvasBounds = new Rectangle(canvasLocation, new Size(canvasWidth, canvasHeight));
}
_zoomRatio is the zoom which adjusts the size of the canvas. 1.0 would be 100%, 2.0 is 200%, etc.
Basically instead I want to feed this function a point taken by mouse input, and use that point as the center for the canvasBounds rectangle. Then when the user manipulates the horizontal and vertical scroll bars, it can change the _centerPoint and update the CanvasBounds.

I think you need to just offset your "point" with your canvas size:
private void UpdateCanvas(Point mousePoint)
{
int canvasWidth = (int)(_bitmap.Width * _zoomRatio);
int canvasHeight = (int)(_bitmap.Height * _zoomRatio);
int canvasX = (mousePoint.X - (canvasWidth / 2));
int canvasY = (mousePoint.Y - (canvasHeight / 2));
CanvasBounds = new Rectangle(canvasX, canvasY, canvasWidth, canvasHeight);
}
If that's not what you are looking for, maybe edit your post with a simple screen shot.

Related

C# winforms unable to paint in middle of screen, even with the correct screen height and width

I'm trying to write a program, that will place a crosshair in the middle of the screen on a transparent background, however the crosshair will always be drawn at an offset even though the coordinates where it's placed is right.
The method which draws the crosshair:
public void drawSquareCrosshair(Graphics gfx, Color color) {
SolidBrush lightChrosshairColor = new SolidBrush(color);
SolidBrush transparentColor = new SolidBrush(Color.Fuchsia);
// Crosshair Bounds
int width = 20;
int height = 20;
int x = (SystemInformation.VirtualScreen.Width / 2)- (width / 2);
int y = (SystemInformation.VirtualScreen.Height / 2) - (height / 2);
Console.WriteLine("X: " + x);
Console.WriteLine("Y: " + y);
gfx.FillRectangle(lightChrosshairColor, x, y, width, height);
}
The logic is, that the width of the screen is divided by two, and minussed by the width of the crosshair divided by two. The same goes for the height. I've set the graphics object to create graphics on a panel which is anchored on all sides within the form, yet the panels height and width is still 1920x1080, just as the form is. The way I've set the form to get maximized is by using form.FormBorderStyle = FormBorderStyle.None; and form.WindowState = FormWindowState.Maximized;
However that doesn't seem to be the case, as the crosshair is still placed at the coordinates that would resemble the middle of the screen (X: 960, Y: 540 on a 1920x1080 screen). I created a desktop background which as a crosshair in the middle of the screen in photoshop (The image is also 1920x1080). Here's how it looks:
The square is the crosshair painted by my application, the red cross is the wallpaper
Has anyone run into this problem before?
This can be fixed by replacing SystemInformation.VirtualScreen with ClientRectangle, see the below code. ClientRectangle returns the bounds of the drawable are of the window.
int x = (ClientRectangle.Width / 2) - (width / 2);
int y = (ClientRectangle.Height / 2) - (height / 2);

MonoGame - proper SpriteFont scaling

I'm making a GUI for my game and now I'm stuck on animations. I need to scale a font up when the mouse hovers over it and scale it down when it's not. Here's my code:
// Update()
if (!IsDisabled)
{
elapsedSecondsFast = (float)gameTime.ElapsedGameTime.TotalSeconds * 3;
if (Size.Contains(InputManager.MouseRect))
{
scale += elapsedSecondsFast;
if (scale >= 1.05f) scale = 1.05f;
}
else
{
scale -= elapsedSecondsFast;
if (scale <= 1.0f) scale = 1.0f;
}
}
// Draw()
if ((PrimaryFont != null) && (SecondaryFont != null) && (!string.IsNullOrEmpty(Text)))
{
if (IsHovered) TextOutliner.DrawBorderedText(spriteBatch, SecondaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2((SecondaryFont.MeasureString(Text).X / 2 * scale - SecondaryFont.MeasureString(Text).X / 2), (SecondaryFont.MeasureString(Text).Y / 2 * scale - SecondaryFont.MeasureString(Text).Y / 2)), scale);
else TextOutliner.DrawBorderedText(spriteBatch, PrimaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2(PrimaryFont.MeasureString(Text).X / 2 * scale - PrimaryFont.MeasureString(Text).X / 2, (PrimaryFont.MeasureString(Text).Y / 2 * scale - PrimaryFont.MeasureString(Text).Y / 2)), scale);
}
The above is a GUIElement class which is inherited by my Button class. Let me explain my code briefly:
PrimaryFont and SecondaryFont are 2 SpriteFonts that use the same
font but a different size. This gives me the scale up/down animation I need without blurring my PrimaryFont.
TextRectangle and Size are 2 different Rectangles. Since my button has a texture and text I decided not to draw text on the texture file but have the game position my text over the texture to "fake" the effect. So TextRectangle is the size and location of button text and Size is size and location of button texture. TextRectangle has its center point in the center of the Button texture. So far I have been using magic numbers to achieve this. This is the core of the problem here.
You can see my origin, I passed it to the DrawBorderedText method of my TextOutliner class. The attributes are in the same order as if it were a spriteBatch.DrawString() call, only without SpriteEffects and layerDepth.
The Problem
Since I'm scaling the font (origin = center I think) it will no longer be in the center of the button. And since I have been using magic numbers to position the un-scaled text over the center of the button texture, I don't want to be forced to do the same thing for scaled text. I'm looking for an algorithm that would always position the text in the middle of my 270x72 texture, no matter if the text is scaled or not, while keeping the scale animation shown above, for each instance of the Button class. Preferably to have its origin point in the center.
Edit
So should I draw like this:
if (IsHovered) TextOutliner.DrawBorderedText(spriteBatch, SecondaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2((SecondaryFont.MeasureString(Text).X / 2), (SecondaryFont.MeasureString(Text).Y / 2)), scale);
else TextOutliner.DrawBorderedText(spriteBatch, PrimaryFont, Text, new Vector2(TextRectangle.X, TextRectangle.Y), ForeColor, 0.0f, new Vector2(PrimaryFont.MeasureString(Text).X / 2, (PrimaryFont.MeasureString(Text).Y / 2)), scale);
and then draw the button's text at btn.Size.Width / 2, btn.Size.Height / 2, (int)MainGame.GameFontLarge.MeasureString("Play").X / 2, (int)MainGame.GameFontLarge.MeasureString("Play").Y / 2
So I eventually found the algorithm by myself and finally eliminated the need of using magic numbers for position of the text. Here's my technique:
TextOutliner.DrawBorderedText(spriteBatch, Font, Text, new Vector2(Size.X + ((Size.Width - Font.MeasureString(Text).X) / 2), Size.Y + ((Size.Height - Font.MeasureString(Text).Y)) / 2), ForeColor, 0.0f, new Vector2((Font.MeasureString(Text).X / 2 * scale - Font.MeasureString(Text).X / 2), (Font.MeasureString(Text).Y / 2 * scale - Font.MeasureString(Text).Y / 2)), scale);
I couldn't take the scale out of the origin equation as #LibertyLocked suggested, because the font was scaling from top-left point upon Mouse Hover and not the center as I want it to.

Create Rect at global coordinate

My scene is 2048 x 1152, and the camera never moves. When I create a rectangle with the following:
timeBarRect = new Rect(220, 185, Screen.width / 3, Screen.height / 50);
Its position changes depending on the resolution of my game, so I can't figure out how to get it to always land where I want it on the screen. To clarify, if I set the resolution to 16:9, and change the size of the preview window, the game will resize at ratios of 16:9, but the bar will move out from where it's supposed to be.
I have two related questions:
Is it possible to place the Rect at a global coordinate? Since the screen is always 2048 x 1152, if I could just place it at a certain coordinate, it'd be perfect.
Is the Rect a UI element? When it's created, I can't find it in the hierarchy. If it's a UI element, I feel like it should be created relative to a canvas/camera, but I can't figure out a way to do that either.
Update:
I am realizing now that I was unclear about what is actually being visualized. Here is that information: Once the Rect is created, I create a texture, update the size of that texture in Update() and draw it to the Rect in OnGui():
timeTexture = new Texture2D (1, 1);
timeTexture.SetPixel(0,0, Color.green);
timeTexture.Apply();
The texture size being changed:
void Update ()
{
if (time < timerMax) {
playerCanAttack = false;
time = time + (10 * Time.deltaTime);
} else {
time = timerMax;
playerCanAttack = true;
}
The actual visualization of the Rect, which is being drawn in a different spot depending on the size of the screen:
void OnGUI(){
float ratio = time / 500;
float rectWidth = ratio * Screen.width / 1.6f;
timeBarRect.width = rectWidth;
GUI.DrawTexture (timeBarRect, timeTexture);
}
I don't know that I completely understand either of the two questions I posed, but I did discover that the way to get the rect's coordinates to match the screen no matter what resolution was not using global coordinates, but using the camera's coordinates, and placing code in Update() such that the rect's coordinates were updated:
timeBarRect.x = cam.pixelWidth / timerWidth;
timeBarRect.y = cam.pixelHeight / timerHeight;

Calculating positions from Manipulation events in c#

I've created one helper app to demonstrate my problem.
I've a rectangle which is filled with an image brush, this brush can be transformed within the rectangle using gesture manipulations.
I'm determining the position of top left corner of the image from the top left corner of the rectangle. I'm getting correct values while (only) translating the image but getting wrong values while using pinch gestures. If you zoom in too much and translate the image, then brush moves in opposite direction.
Below is how you can reproduce my problem with the helper app attached below:
Run the app, get the top left corners of both image and rectangle together by just moving(without pinching) the image until you get the position value as (0,0).
Next Pinch and move the image and get back the top left corners together, now you can see that value is not (0,0).
Download here
Here is my Manipulation Delta Event:
public virtual void Brush_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
if (e.PinchManipulation != null)
{
// Rotate
_currentAngle = previousAngle + AngleOf(e.PinchManipulation.Original) - AngleOf(e.PinchManipulation.Current);
// Scale
_currentScale *= e.PinchManipulation.DeltaScale;
// Translate according to pinch center
double deltaX = (e.PinchManipulation.Current.SecondaryContact.X + e.PinchManipulation.Current.PrimaryContact.X) / 2 -
(e.PinchManipulation.Original.SecondaryContact.X + e.PinchManipulation.Original.PrimaryContact.X) / 2;
double deltaY = (e.PinchManipulation.Current.SecondaryContact.Y + e.PinchManipulation.Current.PrimaryContact.Y) / 2 -
(e.PinchManipulation.Original.SecondaryContact.Y + e.PinchManipulation.Original.PrimaryContact.Y) / 2;
_currentPos.X = previousPos.X + deltaX;
_currentPos.Y = previousPos.Y + deltaY;
}
else
{
// Translate
previousAngle = _currentAngle;
_currentPos.X += e.DeltaManipulation.Translation.X;
_currentPos.Y += e.DeltaManipulation.Translation.Y;
previousPos.X = _currentPos.X;
previousPos.Y = _currentPos.Y;
}
e.Handled = true;
ProcesstTransform();
}
void ProcesstTransform()
{
CompositeTransform gestureTransform = new CompositeTransform();
gestureTransform.CenterX = _currentPos.X;
gestureTransform.CenterY = _currentPos.Y;
gestureTransform.TranslateX = _currentPos.X - outputSize.Width / 2.0;
gestureTransform.TranslateY = _currentPos.Y - outputSize.Height / 2.0;
gestureTransform.Rotation = _currentAngle;
gestureTransform.ScaleX = gestureTransform.ScaleY = _currentScale;
brush.Transform = gestureTransform;
}
First, find the location of the initial upper left corner relative to the center of the transform. This is pretty much straight subtraction. These can be pre-calculated since the before-transformation frame won't change. You do NOT want to pre-scale _brushSize by multiplying in _scale. That will end up scaling the brush twice.
Point origCentre = new Point(ManipulationArea.ActualWidth / 2, ManipulationArea.ActualHeight / 2);
Point origCorner = new Point(origCentre.X - _brushSize.Width / 2, origCentre.Y - _brushSize.Height /2);
Then apply the gestureTransform to the corner point:
Point transCorner = gestureTransform.Transform(origCorner);
XValue.Text = transCorner.X.ToString();
YValue.Text = transCorner.Y.ToString();
This will get things pretty close to accurate, barring some rounding errors and some weirdness from the way the translation is tracked both by changing the position and then by applying the transform. Typically you would only do the latter. I'll leave tracking that down as an exercise for the reader :)
Rob Caplan from Microsoft helped me to solve this issue.

Zoom to Fit to canvas size in C#?

I have a canvas that changes size depending on the users input, i want to hit a button and be able to zoom out to see the whole canvas. Basically i know the height and width of the canvas i just want to zoom in or out so the user can see the whole canvas. I have zoom in and zoom out to work but not sure how do zoom to fit? any thoughts?
February 11th
Thank you for your response, This was great help and i am able to zoom out on the canvas completely. The problem that i am having now is the user has zoom in, and zoom out controls. So if the user zooms in and out and then tries to zoom to fit the canvas the scaling factor will be off, my code is below
This is for the basic zooming in and out on the canvas:
double ScaleRate = 1.2;
public void buttonZoomIn_Click(object sender, RoutedEventArgs e)
{
st.ScaleX *= ScaleRate;
st.ScaleY *= ScaleRate;
}
public void buttonZoomOut_Click(object sender, RoutedEventArgs e)
{
st.ScaleX /= ScaleRate;
st.ScaleY /= ScaleRate;
}
The zoom to fit button that i want to press to zoom out completely on the canvas:
private void zoomToFitBt_Click(object sender, RoutedEventArgs e)
{
float maxWidthScale = (float)ScrollViewerCanvas.Width / (float)Canvas1.Width;
float maxHeightScale = (float)ScrollViewerCanvas.Height / (float)Canvas1.Height;
float scale = Math.Min(maxHeightScale, maxWidthScale);
if (st.ScaleX > scale || st.ScaleY> scale)
{
st.ScaleX *= scale;
st.ScaleY *= scale;
}
}
If i press the zoom to fit button off the start it is fine, but its when the user has done some manual zooming that it messes up. My idea was to maybe every time the user hits the zoom to fit button that it will first go back to its initial state then use the zoom to fit code but not sure about that.
Thanks for your help guys
Based on what little information given, I will simplify your problem into what I gauge you are asking at a most basic mathmatical level. I think you are asking...
"I have 2 rectangles (the viewport,
and the canvas). How do I scale
the canvas such that it is as big as
possible without exceeding the width
or height of the viewport."
this Code will determine how to scale a rectangle in such a way that it will barely fit inside of another rectangle.
Rectangle c = new Rectangle(0, 0, 200, 100); //Canvas Rectancle (assume 200x100)
Rectangle v = new Rectangle(0, 0, 50, 50); //Viewport Rectangle (assume 50x50)
//The maximum scale width we could use
float maxWidthScale = (float)v.Width / (float)c.Width;
//The maximum scale height we could use
float maxHeightScale = (float)v.Height / (float)c.Height;
//Use the smaller of the 2 scales for the scaling
float scale = Math.Min(maxHeightScale, maxWidthScale);
scale = .25 (or 25%) in order to fit using the sample rectangles.

Categories