Is it possible to get a scaled/translated graphics object? - c#

I'm using WinForms to write a visualizer for a simulation. The visualization involves various objects moving around a grid.
So far, I'm using a custom control that extends Panel and doing the custom drawing with the Graphics class during Paint events. However, one irritation is that I'm constantly having to scale things from grid coordinates to the control.DisplayRectangle coordinates (in other words, an object that takes up 2 cells in the grid would take up 2 * (control.DisplayRectangle.Width / horizontalGridWidth) pixels when drawn).
I'm wondering, is there a way to get the Graphics object to do these translations for me so that I can express my drawing in grid coordinates and have it automatically be mapped to the physical coordinates?
It turns out that Matrix was indeed the key (see accepted answer). Here's the code that got it working:
public SimulationPanel() {
this.DoubleBuffered = true;
this.SizeChanged += (o, e) => this.Invalidate();
this.Paint += this.PaintPanel;
}
private void Paint(object sender, PaintEventArgs e) {
e.Graphics.Clear(Color.Black);
var fromRectangle = GetSimulationWorldCoordinates();
var toRectangle = ScaleToFit(fromRectangle, this.DisplayRectangle);
using (var matrix = new Matrix(
fromRectangle,
new[] {
toRectangle.Location,
new Point(toRectangle.Right, toRectangle.Top),
new Point(toRectangle.Left, toRectangle.Bottom),
}))
{
// draw the simulation stuff here using simulation coordinates
e.Graphics.Transform = matrix;
e.Graphics.FillRectangle(Brushes.LightBlue, toRectangle);
e.Graphics.DrawLine(Pens.Red, toRectangle.Location, new Point(toRectangle.Right, toRectangle.Bottom));
}
}

How about this code ?
I use Labels instead of grids because I cannot know your grids.
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
GraphicsPath path = new GraphicsPath();
RectangleF pathRect;
public Form1()
{
InitializeComponent();
Location = new Point(0, 0);
Size = new System.Drawing.Size(500, 500);
Label lbl1 = new Label();
lbl1.Location = new Point(100, 100);
lbl1.Size = new System.Drawing.Size(200, 100);
lbl1.BorderStyle = BorderStyle.FixedSingle;
lbl1.Paint += new PaintEventHandler(lbl_Paint);
Label lbl2 = new Label();
lbl2.Location = new Point(300, 200);
lbl2.Size = new System.Drawing.Size(100, 200);
lbl2.BorderStyle = BorderStyle.FixedSingle;
lbl2.Paint += new PaintEventHandler(lbl_Paint);
Controls.Add(lbl1);
Controls.Add(lbl2);
path.AddRectangle(new Rectangle(50, 50, 150, 150));
path.AddEllipse(new Rectangle(25, 50, 25, 50));
pathRect = path.GetBounds();
}
void lbl_Paint(object sender, PaintEventArgs e)
{
var rect = ((Control)sender).DisplayRectangle;
PointF[] plgpts = new PointF[] {
new PointF(rect.Left, rect.Top),
new PointF(rect.Right - 1, rect.Top),
new PointF(rect.Left, rect.Bottom - 1),
};
Graphics g = e.Graphics;
g.Clear(SystemColors.Control);
using (Matrix matrix = new Matrix(path.GetBounds(), plgpts))
{
g.Transform = matrix;
g.DrawPath(Pens.Red, path);
}
}
}
}

Related

Using Matrix.Rotate more than once C# Winforms

I've been trying to solve a problem I'm having with rotating an oval. The code that I'm posting works, but it's mimicking the kind of code I have to work with for real. Meaning, I can't use the OnPaint override and I'm limited to what I can do in the main code. I'm adding an oval to a graphic layer, and I need to be able to rotate, move and resize the oval. Move and resizing work flawlessly, but rotating doesn't work as I need it to.
If you click in the small box at the 9 oclock position,
the oval will rotate as expected:
The required behavior is to be able to click in the small box at the 12 oclock position, and have the oval rotate again. This does not occur. In order to get the oval to rotate again you need to click in the original 9 oclock position. What I'm really, really trying to find out is how to get the coordinates of the box at the 12 oclock position (or what ever position or location that box ends up after rotating) so that I can rotate it again. Thus far, I have been unable to figure it out. Please try and understand, I'm working with old code that was poorly written and I'm not allowed to change very much of it. What I write must integrate with what's already there. Below is the code snippet that demonstrates what I'm doing. I know I'm missing something obvious, just don't know what. Thanks.
public partial class Form1 : Form
{
int theAngle = 0;
Pen pen2 = new Pen(Color.FromArgb(255, 68, 125, 255), 4);
Pen pen3 = new Pen(Color.Green, 4);
Pen smallPen = new Pen(Color.Black, 1);
PointF center = new PointF(0, 0);
Rectangle rectangle = new Rectangle(20, 20, 100, 30);
Rectangle myRect2 = new Rectangle();
bool mouseBtnDown = false;
Graphics gw;
public Form1()
{
InitializeComponent();
this.MouseDown += mouseDown;
gw = this.CreateGraphics();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (myRect2.Contains(e.Location))
theAngle+=90;
rotate();
}
if (e.Button == MouseButtons.Right)
{
//oval = false;
//mouseBtnDown = false;
theAngle = 0;
rectangle.X = e.X - 50;
rectangle.Y = e.Y - 15;
rectangle.Width = 100;
rectangle.Height = 30;
center.X = rectangle.Left + (0.5f * rectangle.Width);
center.Y = rectangle.Top + (0.5f * rectangle.Height);
myRect2.Size = new Size(15, 15);
myRect2.Location = new Point(rectangle.Left - 15, (rectangle.Top - (rectangle.Height / 2)) + 15);
drawstuff();
// Invalidate();
}
}
public void rotate()
{
var matrix = new Matrix();
matrix.RotateAt(theAngle, center);
gw.Transform = matrix;
drawstuff();
}
public void drawstuff()
{
gw.SmoothingMode = SmoothingMode.AntiAlias; // Creates smooth lines.
gw.DrawEllipse(pen2, rectangle);
gw.DrawRectangle(smallPen, myRect2);
}
}
You can use the Transform matrix to rotate points. So what I've done is get the 4 corner points of myRect2, rotate them using the same Transform matrix, then assign these to a rectangle. You can then use this new rectangle to check the location. I also changed the call to drawstuff() at the end of the right button click to call rotate(), this way the rotated rectangle can get updated when the ellipse is first placed, and the graphics transform angle gets updated to 0.
public partial class Form1 : Form
{
int theAngle = 0;
Pen pen2 = new Pen(Color.FromArgb(255, 68, 125, 255), 4);
Pen pen3 = new Pen(Color.Green, 4);
Pen smallPen = new Pen(Color.Black, 1);
PointF center = new PointF(0, 0);
Rectangle rectangle = new Rectangle(20, 20, 100, 30);
Rectangle myRect2 = new Rectangle();
Rectangle rotatedRect2 = new Rectangle();
bool mouseBtnDown = false;
Graphics gw;
public Form1()
{
InitializeComponent();
this.MouseDown += mouseDown;
gw = this.CreateGraphics();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (rotatedRect2.Contains(e.Location))
theAngle += 90;
rotate();
}
if (e.Button == MouseButtons.Right)
{
//oval = false;
//mouseBtnDown = false;
theAngle = 0;
rectangle.X = e.X - 50;
rectangle.Y = e.Y - 15;
rectangle.Width = 100;
rectangle.Height = 30;
center.X = rectangle.Left + (0.5f * rectangle.Width);
center.Y = rectangle.Top + (0.5f * rectangle.Height);
myRect2.Size = new Size(15, 15);
myRect2.Location = new Point(rectangle.Left - 15, (rectangle.Top - (rectangle.Height / 2)) + 15);
rotate();
//drawstuff();
// Invalidate();
}
}
public void rotate()
{
var matrix = new Matrix();
matrix.RotateAt(theAngle, center);
gw.Transform = matrix;
// Get the 4 corner points of myRect2.
Point p1 = new Point(myRect2.X, myRect2.Y),
p2 = new Point(myRect2.X + myRect2.Width, myRect2.Y),
p3 = new Point(myRect2.X, myRect2.Y + myRect2.Height),
p4 = new Point(myRect2.X + myRect2.Width, myRect2.Y + myRect2.Height);
Point[] pts = new Point[] { p1, p2, p3, p4 };
// Rotate the 4 points.
gw.Transform.TransformPoints(pts);
// Update rotatedRect2 with those rotated points.
rotatedRect2.X = pts.Min(pt => pt.X);
rotatedRect2.Y = pts.Min(pt => pt.Y);
rotatedRect2.Width = pts.Max(pt => pt.X) - pts.Min(pt => pt.X);
rotatedRect2.Height = pts.Max(pt => pt.Y) - pts.Min(pt => pt.Y);
drawstuff();
}
public void drawstuff()
{
gw.SmoothingMode = SmoothingMode.AntiAlias; // Creates smooth lines.
gw.DrawEllipse(pen2, rectangle);
gw.DrawRectangle(smallPen, myRect2);
}
}
Something to note. Drawing to the screen like this (using the form's created graphics and drawing using your own draw function) means the ellipses/rectangles are only drawn once. i.e. if you draw a few then minimize your form, when you bring it back up the ellipses will be gone. Not sure if this is what you're after or not. One way to fix this would be to draw the ellipses to a Bitmap and then in the form's Paint event this bitmap is draw to the form.

Keeping Track of multiple objects that used Matrix Transform C# Winforms

I posted a question about using Graphics Transform and got a great answer. What I needed was to have a spot on an oval where, if the mouse was in it, the oval would rotate. The code below includes that. However, now I ALSO need a region where, if the mouse is clicked inside it, I can resize the oval.
The problem I'm having isn't with the resize, it's with the region detection because the code that's transforming the resize rectangle isn't working. That same code works great when applied to the small, moving circle, which is leaving me more than a little confused.
When you start out, you have everything correctly placed and working. The oval, the small circle that will rotate it when you click in it, and the green box (Currently hidden under a red one) where if you click in it, you can resize the oval.
However, if you begin to rotate everything, the red rectangle is what you get. But what I need is that red rectangle to be the exact same size and shape as the green one and in the same place as the green one so that I can use the rectange.Contains(mousepointer) methods.
I know that I'm missing something basic here, but I have yet to be able to isolate it. Any helpful ideas would be appreciated. Here's the code I'm using:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace OvalTestV2
{
public partial class Form1 : Form
{
int theAngle = 0;
Pen pen2 = new Pen(Color.FromArgb(255, 68, 125, 255), 4);
Pen pen3 = new Pen(Color.Green, 4);
Pen smallPen = new Pen(Color.Black, 1);
bool testspot = false;
PointF center = new PointF(0, 0);
Rectangle rectangle = new Rectangle(20, 20, 100, 30);
Rectangle myRect2 = new Rectangle();
Rectangle rotatedRect2 = new Rectangle();
Rectangle resizeRect = new Rectangle();
Rectangle resizeRect2 = new Rectangle();
bool mouseBtnDown = false;
Graphics gw;
public Form1()
{
InitializeComponent();
this.MouseDown += mouseDown;
gw = this.CreateGraphics();
}
private void mouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (resizeRect.Contains(e.Location))
{
testspot = true;
}
if (resizeRect2.Contains(e.Location))
testspot = true;
if (rotatedRect2.Contains(e.Location))
{
gw.Clear(SystemColors.Control);
theAngle += 5;
rotate();
}
}
if (e.Button == MouseButtons.Right)
{
//oval = false;
//mouseBtnDown = false;
theAngle = 0;
rectangle.X = e.X - 50;
rectangle.Y = e.Y - 15;
rectangle.Width = 100;
rectangle.Height = 30;
center.X = rectangle.Left + (0.5f * rectangle.Width);
center.Y = rectangle.Top + (0.5f * rectangle.Height);
myRect2.Size = new Size(15, 15);
myRect2.Location = new Point(rectangle.Left - 15, (rectangle.Top - (rectangle.Height / 2)) + 15);
resizeRect.X = rectangle.X;
resizeRect.Y = rectangle.Y;
resizeRect.Size = rectangle.Size;
rectangle.Location = rectangle.Location;
rotate();
//drawstuff();
}
}
public void rotate()
{
mouseDnX_lbl.Text = myRect2.X.ToString();
mouseDnY_lbl.Text = myRect2.Y.ToString();
var matrix = new Matrix();
matrix.RotateAt(theAngle, center);
gw.Transform = matrix;
//For the Rotation area
// Get the 4 corner points of myRect2.
Point p1 = new Point(myRect2.X, myRect2.Y),
p2 = new Point(myRect2.X + myRect2.Width, myRect2.Y),
p3 = new Point(myRect2.X, myRect2.Y + myRect2.Height),
p4 = new Point(myRect2.X + myRect2.Width, myRect2.Y + myRect2.Height);
Point[] pts = new Point[] { p1, p2, p3, p4 };
// Rotate the 4 points.
gw.Transform.TransformPoints(pts);
// Update rotatedRect2 with those rotated points.
rotatedRect2.X = pts.Min(pt => pt.X);
rotatedRect2.Y = pts.Min(pt => pt.Y);
rotatedRect2.Width = pts.Max(pt => pt.X) - pts.Min(pt => pt.X);
rotatedRect2.Height = pts.Max(pt => pt.Y) - pts.Min(pt => pt.Y);
//End of Rotation Area
//For the Resize area
Point rp1 = new Point(resizeRect.X, resizeRect.Y),
rp2 = new Point(resizeRect.X + resizeRect.Width, resizeRect.Y),
rp3 = new Point(resizeRect.X, resizeRect.Y + resizeRect.Height),
rp4 = new Point(resizeRect.X + resizeRect.Width, resizeRect.Y + resizeRect.Height);
Point[] rpts = new Point[] { rp1, rp2, rp3,rp4 };
// Rotate the 4 points.
gw.Transform.TransformPoints(rpts);
// Update rotatedRect2 with those rotated points.
resizeRect2.X = rpts.Min(pt => pt.X);
resizeRect2.Y = rpts.Min(pt => pt.Y);
resizeRect2.Width = rpts.Max(pt => pt.X) - rpts.Min(pt => pt.X);
resizeRect2.Height = rpts.Max(pt => pt.Y) - rpts.Min(pt => pt.Y);
//End of Resize area
drawstuff();
}
Pen pen4 = new Pen(Color.Red, 4);
public void drawstuff()
{
gw.SmoothingMode = SmoothingMode.AntiAlias; // Creates smooth lines.
gw.DrawEllipse(pen2, rectangle);
gw.DrawEllipse(smallPen, myRect2);
gw.DrawRectangle(pen3, resizeRect);
gw.DrawRectangle(pen4, resizeRect2);
}

Print Text with DrawString() next to an existing bitmap

Greetings fellow users,
A virgin post on my end since its the first time i am abusing stack overflow with a question! I have been trying to get a bitmap print along with a String to print. Basically the view i want to achieve is the Image and the text to the right of the image as we see the printout. Below is the code I am using
Bitmap qrCodeImage = qrCode.GetGraphic(20);
senderQR = qrCodeImage;
PrintDocument pd = new PrintDocument();
Margins margins = new Margins(10, 10, 10, 10);
pd.DefaultPageSettings.Margins = margins;
pd.PrintPage += PrintPage;
pd.Print();
Here is the PrintPage method
private void PrintPage(object sender, PrintPageEventArgs e)
{
System.Drawing.Image img = senderQR;
Bitmap batchCode = new Bitmap(80, 700);
Rectangle m = e.MarginBounds;
RectangleF batch1 = new RectangleF(80, 700, 650, 1000);
m.Width = img.Width / 5;
m.Height = img.Height / 5;
Graphics g = Graphics.FromImage(batchCode);
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.DrawString(batch, new Font("Arial", 40), Brushes.Black, batch1);
g.Flush();
e.Graphics.DrawImage(img, m);
}
What am i doing wrong? what seems to be the issue? I have been struggling a whole lot to achieve this but no luck!
Additional Notes:
I want the text on the right to Wrap under itself and not under or on top of the existing bitmap within a size of 3,5 x 2 (inches) (label printing).
This is the printout i get with the existing code;
https://prnt.sc/h1ecb0
https://prnt.sc/h1edex
The image you're drawing on (batchCode) is 80 pixels wide and 700 high. When you write your text over it, you set the top-left point of your writing to 80,700 - exactly to the bottom-right corner of your picture. Basically, you write your text outside of the picture.
Update
I've created a small example to make it reproducible, below is a form class for a basic WinForms application:
public partial class Form1 : Form
{
private PictureBox pictureBox2;
public Form1()
{
InitializeComponent();
pictureBox2 = new PictureBox();
pictureBox2.Size = ClientSize;
pictureBox2.SizeMode = PictureBoxSizeMode.AutoSize;
this.Click += Form1_Click;
pictureBox2.Click += Form1_Click;
Controls.Add(pictureBox2);
}
private void Form1_Click(object sender, EventArgs e)
{
var batch = "hello there!";
Bitmap batchCode = new Bitmap(1000, 1000);
var batch1 = new RectangleF(150, 150, 850, 850);
using (Graphics g = Graphics.FromImage(batchCode))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.DrawString(batch, new Font("Arial", 40), Brushes.Black, batch1);
}
pictureBox2.Image = batchCode;
}
}

C# Non-rectangular pictureboxes

Is there a way to create non-rectangular pictureBoxes. I have round shapes that should overlap and if possible should be in different pictureboxes..
I tried the this here, but you could not see both pictureboxes that are overlapping at a time, but just one..
Here the picture that resulted from my tests:
You can make the PictureBoxes circular by adding an Ellipse to a GraphicsPath and then building a new Region from it.
Here's a quick example:
public class Target : PictureBox
{
public Target()
{
this.Size = new Size(100, 100);
this.Paint += Target_Paint;
Rectangle rc = this.ClientRectangle;
rc.Inflate(-10, -10);
System.Drawing.Drawing2D.GraphicsPath gp = new System.Drawing.Drawing2D.GraphicsPath();
gp.AddEllipse(rc);
this.Region = new Region(gp);
}
void Target_Paint(object sender, PaintEventArgs e)
{
Rectangle rc = this.ClientRectangle;
rc.Inflate(-10, -10);
using (Pen pen = new Pen(Color.Blue, 5))
{
e.Graphics.DrawEllipse(pen, rc);
}
rc = new Rectangle(new Point(this.Size.Width / 2, this.Size.Height / 2), new Size(1, 1));
rc.Inflate(9, 9);
e.Graphics.FillEllipse(Brushes.Red, rc);
}
}
After compiling, the new control appears at the top of your ToolBox.
Here's a screenshot with three of them overlapping each other:

How to fill color in a shape drawn by pen

I am drawing a shape with drawcurve with pen.
I need to fill the color in that graphics how can I do that?
This is my code:
Pen p1 = new Pen(Color.Red);
Graphics g1 = panel1.CreateGraphics();
g1.DrawCurve(p1, new Point[] { new Point(470, 470), new Point(430, 440), new Point(400, 480), new Point(470, 560), });
Graphics g2 = panel1.CreateGraphics();
g2.DrawCurve(p1, new Point[] { new Point(470, 470), new Point(510, 440), new Point(540, 480), new Point(470, 560), });
I found fill path but I don't know how to use that.
Use the GraphicsPath class. You can draw it filled with Graphics.FillPath and draw the outline, if necessary, with Graphics.DrawPath. And be sure to only ever draw in the Paint event handler, whatever you draw with CreateGraphics() is not going to last long when the panel redraws itself.
using System.Drawing.Drawing2D;
...
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
panelPath = new GraphicsPath();
panelPath.AddCurve(new Point[] { new Point(470, 470), new Point(430, 440), new Point(400, 480), new Point(470, 560), });
panelPath.AddCurve(new Point[] { new Point(470, 470), new Point(510, 440), new Point(540, 480), new Point(470, 560), });
panel1.Paint += new PaintEventHandler(panel1_Paint);
}
void panel1_Paint(object sender, PaintEventArgs e) {
e.Graphics.TranslateTransform(-360, -400);
e.Graphics.FillPath(Brushes.Green, panelPath);
e.Graphics.DrawPath(Pens.Red, panelPath);
}
GraphicsPath panelPath;
}
Produces:

Categories