I am doing custom drawing using the GDI+.
Normally if I want to fit whatever I am drawing to the window, I calculate the appropriate ratio and I ScaleTransform everything by that ratio:
e.Graphics.ScaleTransform(ratio, ratio);
The problem with ScaleTransform is that it scales everything including pen strokes and brushes.
Hoe do I scale all of the pixel coordinates of what I'm drawing? Every line, rectangle, or path is basically a series of points. So I can multiply all of those points by the ratio manually, but is there an easy alternative to do this more seamlessly?
Try putting all your objects in a GraphicsPath instance first. It doesn't have a ScaleTransform method but you can transform the objects with GraphicsPath.Transform. You can pass a scaling matrix via Matrix.Scale.
You can wrap the GDI graphics object and store the scale factor
interface IDrawing
{
void Scale(float sx, float sy);
void Translate(float dx, float dy);
void SetPen(Color col, float thickness);
void DrawLine(Point from, Point to);
// ... more methods
}
class GdiPlusDrawing : IDrawing
{
private float scale;
private Graphics graphics;
private Pen pen;
public GdiPlusDrawing(Graphics g)
{
float scale = 1.0f;
}
public void Scale(float s)
{
scale *= s;
graphics.ScaleTransform(s,s);
}
public void SetPen(Color color, float thickness)
{
// Use scale to compensate pen thickness.
float penThickness = thickness/scale;
pen = new Pen(color, penThickness); // Note, need to dispose.
}
// Implement rest of IDrawing
}
I think ScaleTransform works on every numeric value that the GDI context is concerned with, so you can't just use it for coordinates, unfortunately. WPF has a GeometryTransform but I don't know of an equivalent to it in GDI+.
If you're concerned about code duplication you could always write a utility method to draw the shapes with a certain scale level applied to their points.
You could also try manually reversing the ScaleTransform by applying the inverse of it to any objects you don't want scaled; I know some brushes expose this method.
Fortunately, Pen has a local ScaleTransform by which inverse rescaling can be done to compensate for the global transform.
Pen.ResetTransform after using each rescaling before the next, or the current pen scaling (independent of graphics context) can shrink to nearly nothing (actually, one pixel), shoot to the moon, or points midway.
Related
This question is asked before but since it doesn't work and my lack of reputation point(I tried to comment at question but I couldn't) I had to ask this question again.
This is the link of the quustion asked before;
How to zoom at a point in picturebox
I used the code which is shown in the link but when I run it the point or shape disappear.
here is my code;
public partial class Form1 : Form
{
private Matrix transform = new Matrix();
private double m_dZoomscale = 1.0;
public static double s_dScrollValue = .1;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Transform = transform;
Pen mypen = new Pen(Color.Red,5);
Rectangle rect = new Rectangle(10, 10, 30, 30);
e.Graphics.DrawRectangle(mypen, rect);
}
protected override void OnMouseWheel(MouseEventArgs mea)
{
pictureBox1.Focus();
if (pictureBox1.Focused == true && mea.Delta != 0)
{
ZoomScroll(mea.Location, mea.Delta > 0);
}
}
private void ZoomScroll(Point location, bool zoomIn)
{
transform.Translate(-location.X, -location.Y);
if (zoomIn)
transform.Scale((float)s_dScrollValue, (float)s_dScrollValue);
else
transform.Scale((float)-s_dScrollValue, (float)-s_dScrollValue);
transform.Translate(location.X, location.Y);
pictureBox1.Invalidate();
}
The answer you are referencing cannot possibly work. I have no idea why it was accepted, nor up-voted. Except that at some time in the past, I apparently up-voted it as well. I don't know what I was thinking.
Anyway, that code has some problems:
It uses the mouse coordinates passed in directly, rather than converting them to the coordinate system for the PictureBox control. The coordinates passed to the OnMouseWheel() method are relative to the Form itself, so only if the PictureBox top-left coincides with the Form's upper-left corner would that work.
More problematically, the code is completely misusing the Matrix.Scale() method, passing a value that seems intended to be a delta for the scale, when in fact the Scale() method accepts a factor for the scale. This has two implications:
Passing a negative value is wrong, because negative values flip the coordinate system, rather than reducing the scale, and
Passing an increment value is wrong, because the value passed will be multiplied with the current scaling to get the new scaling.
Also problematic is that the code applies the matrix transformations in the wrong order, because the default order is "prepend", not "append" (I find the latter more natural to work with, but I assume there's some reason known to those who specialize in matrix math that explains why the default is the former).
There is also the relatively minor issue that, even ignoring the above, allowing the user to adjust the scale factor arbitrarily will eventually lead to an out-of-range value. It would be better for the code to limit the scale to something reasonable.
Here is a version of your code, modified so that it addresses all of these issues:
private Matrix transform = new Matrix();
private float m_dZoomscale = 1.0f;
public const float s_dScrollValue = 0.1f;
public Form1()
{
InitializeComponent();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.Transform = transform;
Pen mypen = new Pen(Color.Red, 5);
Rectangle rect = new Rectangle(10, 10, 30, 30);
e.Graphics.DrawRectangle(mypen, rect);
}
protected override void OnMouseWheel(MouseEventArgs mea)
{
pictureBox1.Focus();
if (pictureBox1.Focused == true && mea.Delta != 0)
{
// Map the Form-centric mouse location to the PictureBox client coordinate system
Point pictureBoxPoint = pictureBox1.PointToClient(this.PointToScreen(mea.Location));
ZoomScroll(pictureBoxPoint, mea.Delta > 0);
}
}
private void ZoomScroll(Point location, bool zoomIn)
{
// Figure out what the new scale will be. Ensure the scale factor remains between
// 1% and 1000%
float newScale = Math.Min(Math.Max(m_dZoomscale + (zoomIn ? s_dScrollValue : -s_dScrollValue), 0.1f), 10);
if (newScale != m_dZoomscale)
{
float adjust = newScale / m_dZoomscale;
m_dZoomscale = newScale;
// Translate mouse point to origin
transform.Translate(-location.X, -location.Y, MatrixOrder.Append);
// Scale view
transform.Scale(adjust, adjust, MatrixOrder.Append);
// Translate origin back to original mouse point.
transform.Translate(location.X, location.Y, MatrixOrder.Append);
pictureBox1.Invalidate();
}
}
With this code, you will find that no matter where you place the mouse before adjusting the mouse wheel, the rendered image will scale while keeping the point under the mouse fixed in place.
Note:
I took a look at some of the similar questions on Stack Overflow, and there are a few that might also be useful to you. Some of the answers overcomplicate things, in my opinion, but all should work. See:
Zoom To Point Not Working As Expected
Zoom in on a fixed point using matrices
Zooming graphics without scrolling
I'm making a game in C# and XNA 4.0. It uses multiple objects (such as a player character, enemies, platforms, etc.), each with their own texture and hitbox. The objects are created and drawn using code similar to the following:
class Object
{
Texture2D m_texture;
Rectangle m_hitbox;
public Object(Texture2D texture, Vector2 position)
{
m_texture = texture;
m_hitbox = new Rectangle((int)position.X, (int)position.Y, texture.Width, texture.Height);
}
public void Draw(SpriteBatch spriteBatch)
{
spriteBatch.Draw(texture, m_hitbox, Color.White);
}
}
Everything works properly, but I also want to allow the player to resize the game window. The main game class uses the following code to do so:
private void Update(GameTime gameTime)
{
if (playerChangedWindowSize == true)
{
graphics.PreferredBackBufferHeight = newHeight;
graphics.PreferredBackBufferWidth = newWidth;
graphics.ApplyChanges();
}
}
This will inevitably cause the positions and hitboxes of the objects to become inaccurate whenever the window size is changed. Is there an easy way for me to change the positions and hitboxes based on a new window size? If the new window width was twice as big as it was before I could probably just double the width of every object's hitbox, but I'm sure that's a terrible way of doing it.
Consider normalizing your coordinate system to view space {0...1} and only apply the window dimensions scalar at the point of rendering.
View Space to Screen Space Conversion
Pseudo code for co-ordinates:
x' = x * screenResX
y' = y * screenResY
Similarly for dimensions. Let's say you have a 32x32 sprite originally designed for 1920x1080 and wish to scale so that it fits the same logical space on screen (so it doesn't appear unnaturally small):
r = 32 * screenResX' / screenResY
width' = width * r
height' = height * r
Then it won't matter what resolution the user has set.
If you are concerned over performance this may impose, then you can perform the above at screen resolution change time for a one-off computation. However you should still always keep the original viewspace {0...1}.
Collision Detection
It's arguably more efficient to perform CD on screen space coordinates
Hope this helps
I am trying to make a retro game engine in C#. I want to use a resolution of 320x200, but the screen does not natively support that, so I'm trying to decide what is the most efficient method of emulating that. Do I create a Bitmap object and then use SetPixel and create methods for drawing basic shapes? Then scale the image to the size of the screen. Should I draw little Rectangle objects instead to mimic the pixels? What do you think would be the most efficient? Also any other ideas?
You could just use the DrawImage of the Graphics object to paint your 320x200 bitmap on a rectangle of any size.
By setting the interpolation mode on the graphics object first, you can control the way the image is painted when resized. Different interpolation modes should give different visual results and chances are you will be satisfied with one of built-in modes so that you don't have to provide any custom implementation of the streching algorithm.
On the other hand, have you considered OpenGL/DirectX rather than GDI+?
If you must use GDI+ then let it handle the scaling using this method can leverage hardware acceleration if available as opposed to drawing into bitmaps. But agreed with other poster there are better frameworks for this have a look at XNA.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var gameTick = new Timer {Interval = 10};
gameTick.Tick += (s, e) => BeginInvoke((Action)(Invalidate));
gameTick.Start();
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
//calculate the scale ratio to fit a 320x200 box in the form
var width = g.VisibleClipBounds.Width;
var height = g.VisibleClipBounds.Height;
var widthRatio = width / 320f;
var heightRatio = height / 200f;
var scaleRatio = Math.Min(widthRatio, heightRatio);
g.ScaleTransform(scaleRatio, scaleRatio);
//draw a 320x200 rectangle (because of scale transform this always fills form)
g.FillRectangle(Brushes.Gray, 0, 0, 320, 200);
}
}
I need to render a sprite in a texture2d so that this texture can later be render on the screen, but at the same time I need to access the pixels of this modified texture so, if I add let's say a sprite in the texture and I call a get pixel function in a coordinate where the sprite was then it should give me the new pixel values that correspond to the sprite (that has been blended with the texture2d).
I am using xna 4.0 not 3.5 or less.
thanks.
the equivalent of Graphics.FromImage(img).DrawImage(... in GDI
I tried this and failed
public static Texture2D DrawSomething(Texture2D old, int X, int Y, int radius) {
var pp = Res.game.GraphicsDevice.PresentationParameters;
var r = new RenderTarget2D(Res.game.GraphicsDevice, old.Width, old.Height, false, pp.BackBufferFormat, pp.DepthStencilFormat,
pp.MultiSampleCount, RenderTargetUsage.DiscardContents);
Res.game.GraphicsDevice.SetRenderTarget(r);
var s = new SpriteBatch(r.GraphicsDevice);
s.Begin();
s.Draw(old, new Vector2(0, 0), Color.White);
s.Draw(Res.picture, new Rectangle(X - radius / 2, Y - radius / 2, radius, radius), Color.White);
s.End();
Res.game.GraphicsDevice.SetRenderTarget(null);
return r;
}
Res.game is basically a pointer to the main game form and Res.picture is a random texture2d
Use a RenderTarget2D: http://msdn.microsoft.com/en-us/library/microsoft.xna.framework.graphics.rendertarget2d.aspx
If possible, avoid creating a new render target every time. Create it outside of the method and reuse it for best performance.
Here some pseudo-code:
public Texture2D DrawOnTop(RenderTarget2D target, Texture2D oldTexture, Texture2D picture)
{
SetRenderTarget(target);
Draw(oldTexture);
Draw(picture);
SetRenderTarget(null);
return target;
}
If the size changes frequently and you cannot reuse the target, at least dispose the previous one, like annonymously suggested in the comments. Each new target will consume memory, unless you release the resource in time. But dispose it after you used it in a shader or did whatever you wanted to do with it. Once disposed it is gone.
i draw the circle in c# using directx.i like to draw the circle with same dimensions in c# using GDI.It means i like to convert that circle from directx to GDI. Is any body help for me.plz provide the answer for me.how can i do it.Is any algorithm available for that........
And also i give the input for center of the circle is (x,y)in this point format.but in gdi it is pixel format .so how can i convert the directx points to gdi+ pixels
Here is a link from MSDN that introduces Graphics and Drawing in Windows Forms. And it's likely that you will need something similar to:
public Form1()
{
InitializeComponent();
this.Paint += new PaintEventHandler(Form1_Paint);
// This works too
//this.Paint += (_, args) => DrawCircle(args.Graphics);
}
void Form1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(e.Graphics);
}
private void DrawCircle(Graphics g)
{
int x = 0;
int y = 0;
int radius = 50;
// The x,y coordinates here represent the upper left corner
// so if you have the center coordinates (cenX, cenY), you will have to
// substract radius from both cenX and cenY in order to represent the
// upper left corner.
// The width and height represents that of the bounding rectangle of the circle
g.DrawEllipse(Pens.Black, x, y, radius * 2, radius * 2);
// Use this instead if you need a filled circle
//g.FillEllipse(Brushes.Black, x, y, radius * 2, radius * 2);
}
After that you might want to look into double-buffering techniques, a few links :
[SO] How to double buffer .NET controls on a form?
[MSDN] Double Buffered Graphics
[MSDN] Using Double Buffering