Use graphics.ScaleTransform on Drawing - c#

At the moment I develope a ChartControl and it works just pretty well in my opinion,
but now I'm at a point where it would be nice to have the ability to zoom the drawed signal for better analyzing.
At the moment I calculate the needed points like this:
for (int i = 0; i < PointsCount; i++){
xAxisPoint = xAxisOP.X + i * (xAxisWidth / PointsCount);
yAxisPoint = yAxisHeight * data[i].Point / Divisor;
if(yAxisPoint > yAxisHeight){
yAxisPoint = yAxisHeight;
}
if(yAxisPoint < -yAxisHeight){
yAxisPoint = -yAxisHeight;
}
Points[i] = new PointF(xAxisPoint, yAxisOP.Y + yAxisPoint);
}
if(zoom){
graphics.ScaleTransform(0.2f*ZoomFactor, 0.2f*ZoomFactor);
}
using (Pen plotPen = new Pen(plotColor, 1)){
graphics.DrawLines(plotPen, Points);
}
But the problem is: When it zooms in, the zoom is way too big and is drawn outside the bounds of my control.
Is there a way to specify an area in which it should be Scaled (zoomed)?

For the final question: Is there a way to specify an area in which it should be scaled/zoomed? you need a combination of SetClip, TranslateTransform and ScaleTransform.
Here is an example.
It uses a
target rectangle zoomTgtArea where the zoomed graphics are displayed,
a mouse location zoomOrigin where the zoom origin is,
a float zoomFactor, a positive float.
Initial values:
Rectangle zoomTgtArea = new Rectangle(300, 500, 200, 200);
Point zoomOrigin = Point.Empty; // updated in MouseMove when button is pressed
float zoomFactor = 2f;
The trick to zoom in on only a part of the graphics is to display the graphics twice, once normally and once with the transformations of the Graphics object.
Let's try:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
// normal drawing
DrawStuff(e.Graphics);
// for the movable zoom we want a small correction
Rectangle cr = pictureBox.ClientRectangle;
float pcw = cr.Width / (cr.Width - ZoomTgtArea.Width / 2f) ;
float pch = cr.Height / (cr.Height - ZoomTgtArea.Height / 2f) ;
// now we prepare the graphics object; note: order matters!
e.Graphics.SetClip(zoomTgtArea );
// we can either follow the mouse or keep the output area fixed:
if (cbx_fixed.Checked)
e.Graphics.TranslateTransform( ZoomTgtArea.X - zoomCenter.X * zoomFactor,
ZoomTgtArea.Y - zoomCenter.Y * zoomFactor);
else
e.Graphics.TranslateTransform( - zoomCenter.X * zoomFactor * pcw,
- zoomCenter.Y * zoomFactor * pch);
// finally zoom
e.Graphics.ScaleTransform(zoomFactor, zoomFactor);
// and display zoomed
DrawStuff(e.Graphics);
}
The DrawStuff I used is simple:
void DrawStuff(Graphics g)
{
bool isZoomed = g.Transform.Elements[0]!= 1
|| g.Transform.OffsetX != 0 | g.Transform.OffsetY != 0;
if (isZoomed) g.Clear(Color.Gainsboro); // pick your back color
// all your drawing here!
Rectangle r = new Rectangle(10, 10, 500, 800); // some size
using (Font f = new Font("Tahoma", 11f))
g.DrawString(text, f, Brushes.DarkSlateBlue, r);
}
Its only extra is clearing the background so the normal drawing won't shine through the zoomed version..
Let's see:

Related

How to rotate 2d object in C#

Basically i have a windows form that user will be able to draw different shapes(e.g square, circle and triangle), user will be able to highlight any of these shapes after drawing and then control that highlighted shape by moving or rotating it, i don't know how to rotate the shape. any one can help, this is my code (only to draw a square)
PS: user need to click twice on the form to draw the shape between those 2 points as shown below also i know i should be using onPaint method but this is the requirements of the task
Thanks
public Square(Point keyPt, Point oppPt) // constructor
{
this.keyPt = keyPt;
this.oppPt = oppPt;
}
// You will need a different draw method for each kind of shape. Note the square is drawn
// from first principles. All other shapes should similarly be drawn from first principles.
// Ideally no C# standard library class or method should be used to create, draw or transform a shape
// and instead should utilse user-developed code.
public void draw(Graphics g, Pen blackPen)
{
// This method draws the square by calculating the positions of the other 2 corners
double xDiff, yDiff, xMid, yMid; // range and mid points of x & y
// calculate ranges and mid points
xDiff = oppPt.X - keyPt.X;
yDiff = oppPt.Y - keyPt.Y;
xMid = (oppPt.X + keyPt.X) / 2;
yMid = (oppPt.Y + keyPt.Y) / 2;
// draw square
g.DrawLine(blackPen, (int)keyPt.X, (int)keyPt.Y, (int)(xMid + yDiff / 2), (int)(yMid - xDiff / 2));
g.DrawLine(blackPen, (int)(xMid + yDiff / 2), (int)(yMid - xDiff / 2), (int)oppPt.X, (int)oppPt.Y);
g.DrawLine(blackPen, (int)oppPt.X, (int)oppPt.Y, (int)(xMid - yDiff / 2), (int)(yMid + xDiff / 2));
g.DrawLine(blackPen, (int)(xMid - yDiff / 2), (int)(yMid + xDiff / 2), (int)keyPt.X, (int)keyPt.Y);
}
public void fillSquare(Graphics g, Brush redBrush)
{
float xDiff = oppPt.X - keyPt.X;
float yDiff = oppPt.Y - keyPt.Y;
float xMid = (oppPt.X + keyPt.X) / 2;
float yMid = (oppPt.Y + keyPt.Y) / 2;
var path = new GraphicsPath();
path.AddLines(new PointF[] {
keyPt,
new PointF(xMid + yDiff/2, yMid-xDiff/2),
oppPt
});
path.AddLines(new PointF[] {
keyPt,
new PointF(xMid - yDiff/2, yMid + xDiff/2),
oppPt
});
path.CloseFigure();
// Fill Triangle
g.FillPath(redBrush, path);
}
}
}
i have tried this method but something is missing i don't know what is it
private void itemRotation(PaintEventArgs e)
{
Pen blackpen = new Pen(Color.Black);
Graphics g = e.Graphics;
Font myFont = new System.Drawing.Font("Helvetica", 9);
Brush blackwriter = new SolidBrush(System.Drawing.Color.Black);
if (rotateItem)
{
for (int i = 0; i < shapes.Count; i++)
{
if (shapes[i].Selected)
{
if (shapes[i].ShapeType == (int)ShapeTypes.Square)
{
PointF center = new PointF(shapes[i].keyPt.X + (shapes[i].oppPt.X / 2.0F), shapes[i].keyPt.Y + (shapes[i].oppPt.Y / 2.0F));
shapes[i].keyPt = new Point(shapes[i].keyPt.X, shapes[i].keyPt.Y);
shapes[i].oppPt = new Point(shapes[i].oppPt.X, shapes[i].oppPt.Y);
Matrix myMatrix = new Matrix();
myMatrix.Rotate(30);
g.Transform = myMatrix;
((Square)shapes[i]).draw(g, blackpen);
g.DrawString("2nd pos", myFont, blackwriter, shapes[i].keyPt.X, shapes[i].oppPt.X);
}
}
}
}
}
Below is an example of how to draw the same shape (a GraphicsPath) into various locations and rotations.
The key here is the following two commands
e.Graphics.TranslateTransform(x, y);
e.Graphics.RotateTransform(-angle);
See results below:
and the code used to generate it:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// This code defines a graphics shape using a GraphicsPath
// and draws multiple copies along a grid and with various
// rotation angle angles.
e.Graphics.SmoothingMode=SmoothingMode.AntiAlias;
var target = sender as PictureBox;
// Step 1 - Define a rectangle 20 by 12 pixels, center at origin.
var gp = new GraphicsPath();
gp.AddLines(new PointF[] {
new PointF(-10, -6),
new PointF( 10, -6),
new PointF( 10, 6),
new PointF(-10, 6) });
gp.CloseFigure();
// Step 2 - Define a 10×9 grid with two loops
float angle = 0;
for (int i = 0; i<9; i++)
{
// divide the control height into 10 divisions
float y = (i+1)*target.Height/10;
for (int j = 0; j<10; j++)
{
// divide the control width into 11 divisions
float x = (j+1)*target.Width/11;
// Save the default transformation state
var state = e.Graphics.Save();
// Traslate the origin to (x,y), each grid point
e.Graphics.TranslateTransform(x, y);
// Rotate shape by an angle (negative = CCW)
e.Graphics.RotateTransform(-angle);
// Draw the shape
e.Graphics.FillPath(Brushes.LightSeaGreen, gp);
e.Graphics.DrawPath(Pens.Black, gp);
// Restore the default transformation state
e.Graphics.Restore(state);
// Increment the angle by one degree.
// The idea is to show all 90 degrees of rotation in a 10×9 grid.
angle++;
}
}
}

Zooming graphics without scrolling

I have a user control and I'm using ScaleTransform() to implement zoom.
However, in order to keep the center content in the center after the zoom, it is also necessary to scroll. For example, if I zoom in (make things bigger), the X and Y origin should increase so that most of the content does not move down and to the right. (That is, as I zoom in, some of the content should disappear to the left and top.)
Has anyone worked out the calculations of how much to scroll in the X and Y direction in response to a zoom?
For example:
e.Graphics.ScaleTransform(2.0F, 2.0F);
e.Graphics.TranslateTransform(?, ?);
What would be my arguments to TranslateTransform() be so that the center part of the content remains at the center?
Note: I am not displaying an image. I am drawing the graphic content to the surface of my user control.
Or perhaps there's an even easier way?
This should work and I can't imagine any easier way; it assumes you have decided on the center of the zooming. I have chosen to draw centered on the panel:
float zoom = 1f;
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
Point c = new Point(drawPanel1.ClientSize.Width / 2, drawPanel1.ClientSize.Height / 2);
// a blue sanity check for testing
e.Graphics.FillEllipse(Brushes.DodgerBlue, c.X - 3, c.Y - 3, 6, 6);
// the offsets you were looking for:
float ox = c.X * ( zoom - 1f);
float oy = c.Y * ( zoom - 1f);
// first move and then scale
e.Graphics.TranslateTransform(-ox, -oy);
e.Graphics.ScaleTransform(zoom, zoom);
// now we can draw centered around our point c
Size sz = new Size(300, 400);
int count = 10;
int wx = sz.Width / count;
int wy = sz.Height / count;
for (int i = 0; i < count; i++)
{
Rectangle r = new Rectangle(c.X - i * wx / 2 , c.Y - i * wy / 2, i * wx, i * wy );
e.Graphics.DrawRectangle(Pens.Red, r );
}
}
Note the order of moving and scaling!
I guess you are using some differet interface, but in my case, that's what got the job done (for leaving the mouse in it's original location on the draw after the mouse wheel event):
private void DrawPb_MouseWheel(object sender, MouseEventArgs e)
{
// e contains current mouse location and the wheel direction
int wheelDirection = e.Delta / Math.Abs(e.Delta); // is 'in' or 'out' (1 or -1).
double factor = Math.Exp(wheelDirection * Constants.ZoomFactor); // divide or multiply
double newX = e.X - e.X / factor; // what used to be x is now newX
double newY = e.Y - e.Y / factor; // same for y
Point offset = new Point((int)(-newX), (int)(-newY)); // the offset of the old point to it's new location
Graph.AddOffset(offset); // apply offset
}

Fill texture brush using image, not tile

I have a texture brush that uses a certain image to make the texture to be displayed like this:
Image image = new Bitmap("Untitled.png");
for (int i = 0; i < points.Count; i++)
{
using (TextureBrush tbr = new TextureBrush(image))
{
tbr.RotateTransform(i * 4);
var p = PointToClient(Cursor.Position);
tbr.Transform = new Matrix(
75.0f / 640.0f,
0.0f,
0.0f,
75.0f / 480.0f,
0.0f,
0.0f);
e.Graphics.FillEllipse(tbr, p.X - 50, p.Y - 50, 100, 100);
Pen p3 = new Pen(tbr);
e.Graphics.DrawEllipse(Pens.DeepSkyBlue, p.X - 50, p.Y - 50, 100, 100);
}
}
and here's the image it is using:
This is how it turns out:
I want the image to fill the circle so that it looks like this(edited image):
Any Help would be appreciated.
You need to scale using the correct numbers.
If you want an image with a size = width * height pixels to fill a circle of diameter you should scale like this:
int diameter = 100;
Image image = new Bitmap(yourImage);
float scaleX = 1f * diameter / image.Size.Width;
float scaleY = 1f * diameter / image.Size.Height;
Note however that your TextureBrush will always reveal a tiling made from your image. This seemed ok for your original question, especially when rotating the images in the tail to get rid of any artifacts.
But here it may simply not be what you want.
If you want the image to follow the mouse you need to draw it.
Here is an example, that uses a checkbox to switch between tiling and drawing. The animation uses only one frame:
for (int i = 0; i < points.Count; i++)
{
using (TextureBrush tbr = new TextureBrush(image))
{
tbr.RotateTransform(i * 4); // optional
var p = PointToClient(Cursor.Position);
tbr.Transform = new Matrix(
scaleX,
0.0f,
0.0f,
scaleY,
0.0f,
0.0f);
// any tile mode will work, though not all the same way
tbr.WrapMode = WrapMode.TileFlipXY;
if (cbx_tileFunny.Checked)
e.Graphics.FillEllipse(tbr, p.X - diameter/2,
p.Y - diameter/2, diameter, diameter);
else
{
((Bitmap)image).SetResolution(e.Graphics.DpiX, e.Graphics.DpiY); // (**)
e.Graphics.ScaleTransform(scaleX, scaleY);
e.Graphics.DrawImage( image, (p.X - diameter/2) / scaleX,
(p.Y - diameter/2 ) / scaleY);
e.Graphics.ResetTransform();
}
/// ? Pen p3 = new Pen(tbr);
e.Graphics.DrawEllipse(Pens.DeepSkyBlue, p.X - diameter/2,
p.Y - diameter/2, diameter, diameter);
}
}
Do note the extra scaling needed here (**) if the image has a different dpi setting than your screen.
Also: While it is usually a good idea to create and dispose Pens and Brushes quickly, when so much effort is put into creating the Brush and/or Image caching them or even a series of them seems rather preferrable, imo.

How do I draw a Drop Shadow at any Location on a Form?

Using GDI+, how do I draw a Border Shadow or a Drop Shadow at specified coordinates? I'm not trying to attach the shadow to anything, I just need to draw a shadow from x40,0px to x140px,0px. I've not been able to find any information about this and I'm beginning to think it isn't possible.
My intention is to draw a shadow at certain location at bottom of control but I don't want it to be the entire width of the control, which is why I've asked specifically about only drawing at specified locations.
Here is a piece of code that could get you started.
The drawShadow method draws a shadow of given color and depth along a GraphicsPath.
The use of GraphicsPath allows you to draw shadows of more complex shapes than mere Rectangles.
The shadow is drawn with a vector of colors that gradually goes from the shadow to the background color and is moving to the right and down. (You can change the direction by changing the shadow vector. Values greater 1 will need an larger Pen width! (*) )
To demonstrate the routine I have added a getRectPath function that creates a GraphicsPath from a Rectangle and a Button click that calls the drawing routine.
Of course in production code you must attach it to the Paint event instead!
void drawShadow(Graphics G, Color c, GraphicsPath GP, int d)
{
Color[] colors = getColorVector(c, this.BackColor, d).ToArray();
for (int i = 0; i < d; i++)
{
G.TranslateTransform(1f, 0.75f); // <== shadow vector!
using (Pen pen = new Pen(colors[i], 1.75f ) ) // <== pen width (*)
G.DrawPath(pen, GP);
}
G.ResetTransform();
}
List<Color> getColorVector(Color fc, Color bc, int depth)
{
List<Color> cv = new List<Color>();
float dRed = 1f * (bc.R - fc.R) / depth;
float dGreen = 1f * (bc.G - fc.G) / depth;
float dBlue = 1f * (bc.B - fc.B) / depth;
for (int d = 1; d <= depth; d++)
cv.Add(Color.FromArgb(255, (int) (fc.R + dRed * d),
(int) (fc.G + dGreen * d), (int) (fc.B + dBlue * d) ));
return cv;
}
GraphicsPath getRectPath(Rectangle R)
{
byte[] fm = new byte[3];
for (int b = 0; b < 3; b++) fm[b] = 1;
List<Point> points = new List<Point>();
points.Add(new Point(R.Left, R.Bottom));
points.Add(new Point(R.Right, R.Bottom));
points.Add(new Point(R.Right, R.Top));
return new GraphicsPath(points.ToArray(), fm);
}
private void button1_Click(object sender, EventArgs e)
{
using (Graphics G = this.CreateGraphics())
drawShadow(G, Color.Black, getRectPath(new Rectangle(111, 111, 222, 222)), 17);
}
Edit: I have changed the solution to allow for complex shadows and arbitrary shadow vectors without the alpha channel's overlapping creating ugly artifacts. This assumes that the background has a uniform color!

How to draw a rectangle that looks like CAD elevation drawings

What brush should i use to draw rectangles with white interior of the line and lines for the perimeter of the rectangle like the elevations below.
The form1 winform is what i am working on and the image behind the winform is how i need to the rectangles to look in my winform.
To make the question easier, how can i fill the interior portion of the rectangles with white?
How do i fill the LINES of the rectangle with white? I do not need to fill the inside of the rectangle, I need to fill a portion of the 4 lines that make up the rectangle with white.
void BuildShopDrawing(ElevationResponse elevation)
{
float penWidth = (float)((2f / 12f) * PIXELS_PER_FOOT);
Pen blackPen = new Pen(Color.FromArgb(40, 84, 149), penWidth);
Bitmap canvas = new Bitmap((((int)elevation.TotalWidthFeet) * PIXELS_PER_FOOT) + 55, (((int)elevation.TotalHeightFeet) * PIXELS_PER_FOOT) + 25);
Graphics dc = Graphics.FromImage(canvas);
RectangleF[] bays = new RectangleF[elevation.Bays.Count];
float x = 10F;
float width = 0F;
float height = 0F;
for (int i = 0; i < elevation.Bays.Count; i++)
{
if (i > 0)
{
x += (float)((elevation.Bays[i - 1].WidthInches / 12) * PIXELS_PER_FOOT);
}
width = (float)(elevation.Bays[i].WidthInches / 12) * PIXELS_PER_FOOT;
height = (float)(elevation.Bays[i].HeightInches / 12) * PIXELS_PER_FOOT;
bays[i] =
new RectangleF(new PointF(x, 10),
new SizeF(width, height));
}
dc.DrawRectangles(blackPen, bays);
this.picBx.Image = canvas;
this.Size = new System.Drawing.Size(canvas.Width + 10, canvas.Height + 50);
}
You need to look a bit more thoroughly at the Pen Class more specifically the CompoundArray Property, it will give you something like you are wanting, You will need to play around some other of the Pen Class properties to get your transitions right. And as a side note when you post example code that depends on external custom classes you make it harder for someone to help, it is always best to make sure that the code can run by itself.
Try adding this after you declare your pen.
float[] cmpArray = new float[4]{0.0F, 0.2F, 0.7F, 1.0F};
blackPen.CompoundArray = cmpArray;
It looks something like this:

Categories

Resources