How to draw a rectangle that looks like CAD elevation drawings - c#

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:

Related

Use graphics.ScaleTransform on Drawing

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:

DrawImage resized image too small

When I draw an image using Graphics.DrawImage and draw it at a bigger size than the original image, it ends up being a bit too small. You can see this in the following picture:
The green lines shouldn't be visible and are not part of the image. Rather they get drawn behind the image and the image should cover them.
How can I draw an image with the exact right size?
EDIT: I draw the green part with the same rectangle I pass into the DrawImage call, with the exact dimensions of how big the image should be. So no flaw in my values (I think).
EDIT 2: I draw the green rectangle using FillRectangle, so no pen calculations need to be done. Also, I logged the values that I pass into the rectangle for both the image and the green fill, and the values are correct. It's just the image that's off. I will post code later, as I'm not at my computer at the moment.
EDIT 3: This is the code I use to render the images:
// This is for zooming
public readonly float[] SCALES = { 0.05f, 0.1f, 0.125f, 0.25f, 0.333f, 0.5f, 0.667f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f, 4.5f, 5.0f, 6.0f, 7.0f, 8.0f, 10.0f, 12.0f, 15.0f, 20.0f, 30.0f, 36.0f };
private int scaleIndex = 8;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
float ScaleFactor = SCALES[scaleIndex];
e.Graphics.InterpolationMode = ScaleFactor < 1 ? InterpolationMode.Bicubic : InterpolationMode.NearestNeighbor;
Image im = Properties.Resources.TSprite0;
for (int y = 0; y < TilesVertical; y++)
{
for (int x = 0; x < TilesHorizontal; x++)
{
float sx = im.Width * ScaleFactor;
float sy = im.Height * ScaleFactor;
Point p = new Point((int)(-scrollPosition.X + sx * x), (int)(-scrollPosition.Y + sy * y));
Size s = new Size((int)Math.Floor(sx), (int)Math.Floor(sy));
// The green rectangle in the background should be the same size as the image
e.Graphics.FillRectangle(Brushes.Lime, new Rectangle(p, s));
e.Graphics.DrawImage(im, new Rectangle(p, s), 0, 0, 16, 16, GraphicsUnit.Pixel);
}
}
im.Dispose();
}
EDIT 4: Also note that the image seems to be cropped on the left and top instead of resized. Take a look at this comparison of the original image upscaled in Photoshop and then how GDI+ renders it:
The issue happens when scaling to 2x or larger.
Looks like the whole problem is caused by the wrong default PixelOffsetMode.
By offsetting pixels during rendering, you can improve render quality
at the cost of render speed.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.Half;
makes it go away for me.
Setting it to
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
also works fine.
Default, None and HighSpeed cause the image to be rendered a little to the left and up.
Often you will also want to set InterpolationMode.NearestNeighbor.

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 to increase the height of curve in windows forms

I want to increase the height of the curve but its left and right position should remain same. Just want to lift up from center to give it a shape like curve as height changes.
Pen blackPen = new Pen(Color.Black, 3);
// Create coordinates of rectangle to bound ellipse.
int x = 93;
int y = 136;
int width = 320;
int height = 50;
// Create start and sweep angles on ellipse.
int startAngle = 0;
int sweepAngle = -180;
// Draw arc to screen.
e.Graphics.DrawArc(blackPen, x, y, width, height, startAngle, sweepAngle);
In the most direct way your problem can be solved like this:
int change = 0;
e.Graphics.DrawArc(blackPen, x, y-change, width, height+change, startAngle, sweepAngle);
By increasing the variable change the ellipse will curve up more and more:
private void button1_Click(object sender, EventArgs e)
{
change += 10;
panel1.Invalidate();
}
But maybe you want more control over the shape? Let's have a look at the options:
Here are examples of the three 'curve' drawing methods:
Curves, Beziers & Ellipses
And here is the code to draw that image.
Please ignore the Graphics.xxxTransform calls! They only are meant to shift the curves a little bit upwards so they don't overlap too much to see them properly.
Also note that the curves in the first image are not completely convex. See the last part of the answer to see a DrawCurve call that avoids the concave segments!
The important part are the Points! And just as the comments suggest, in the third part the ellipses are being changed by making the height larger and moving the top of the Rectangle up by the same amount.
The complexity DrawArc of and DrawCurve is pretty much equal; both are controlled by four integers with a rather clear meaning: They either make one rectangle or the corners of a symmetrical triangle. (Plus one counterpoint for the convex call.)
DrawBezier is more complex, especially since the controls point(s) are not actually on the resulting curve. They can be thought of force vectors that pull the line into a curved shape and are harder to calculate.
private void panel1_Paint(object sender, PaintEventArgs e)
{
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 0; i< 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50 + i * 10);
e.Graphics.DrawCurve(Pens.Maroon, new[] { a, b, c }, 0.7f);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point pc = new Point(350, 200 - i * 10);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, pc, pc, pb);
}
e.Graphics.ResetTransform();
int x = 500;
int y0 = 200;
int w = 200;
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - i * 10, w, 10 + i * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
}
e.Graphics.ResetTransform();
}
Notes:
The Curve (1st image) can be further controlled by the Tension parameter. The lower the tension the more pointed it gets, approaching 1f it makes the curve broader..
The Bezier curve (2nd image) is using only one control point. (Twice.) The curve gets a little pointed this way. You can make it broader and broader by using two different points the move apart little by little..
The Ellipse can't be controlled; it will always fill the bounding Rectangle.
Here is an example of varying the Curves and the Beziers:
The Curves are drawn with varying Tensions. Also I have used an overload that helps to get rid of the concave part at the start and end of the curve. The trick is to add a suitable extra point to the start and end and to tell the DrawCurve to leave out these 1st and last segments.
The simplest point to use (for both ends actually) is the counterpoint of the one at the top.
The Beziers are drawn using two control points, moving out and up a little.
Here is the code for the variations:
Point a = new Point(0, 200);
Point c = new Point(200, 200);
for (int i = 1; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point b = new Point(100, 50);
Point b0 = new Point(b.X, a.Y + (a.Y - b.Y));
e.Graphics.DrawCurve(Pens.Maroon, new[] { b0, a, b, c, b0 }, 1, 2, 0.1f * i);
}
e.Graphics.ResetTransform();
Point pa = new Point(250, 200);
Point pb = new Point(450, 200);
for (int i = 0; i < 10; i++)
{
e.Graphics.TranslateTransform(0, -5);
Point ca = new Point(350 - i * 9, 100 - i * 5);
Point cb = new Point(350 + i * 9, 100 - i * 5);
e.Graphics.DrawBezier(Pens.ForestGreen, pa, ca, cb, pb);
}
e.Graphics.ResetTransform();
here was the solution just increase value of y0 as u increase the value of
y0-i here i=20
int x = 96;
int y0 = 260;
int w = 320;
e.Graphics.TranslateTransform(0, -5);
Rectangle rect = new Rectangle(x, y0 - 20 * 10, w, 10 + 20 * 10);
e.Graphics.DrawArc(Pens.DarkBlue, rect, -0, -180);
e.Graphics.ResetTransform();

Why does DrawString look so crappy?

I am trying to add a text scale to a color image.
The agcScale.jpg image (below) is 2 winform labels on the top and bottom and 2 winform pictureboxes on the left and right.
The exact same code was used to produce the strings in the right and left pictureboxes, the only difference is that pictureBoxAgcVscale contains only the strings.
Why does DrawString in pictureBoxAgc look fine but DrawString in pictureBoxAgcVscale look so bad? I can probably fix pictureBoxAgcVscale by doing a bmp.SetPixel for each pixel but that seems like the wrong way to fix this.
private void DisplayAgcVscale(double min, double max)
{
var bmp = new Bitmap(pictureBoxAgcVscale.Width, pictureBoxAgcVscale.Height);
var c = (max - min) / bmp.Height;
using (var g = Graphics.FromImage(bmp))
{
var font = new Font("Microsoft Sans Serif", 8.25F);
var y1 = bmp.Height / 10;
for (var y = y1; y < bmp.Height; y += y1)
{
var agc = y * c + min;
var text = agc.ToString("#0.000V");
var h = bmp.Height - y - font.Height / 2;
g.DrawString(text, font, Brushes.Black, 0, h);
}
}
pictureBoxAgcVscale.Image = bmp;
}
You are drawing black text on a transparent background. The anti-aliasing pixels are fading from black to black, no choice, turning the letters into blobs. It works for the text on the left because you draw the pixels first.
You forgot g.Clear().
I had a similar issue, but in a listbox, and it wasn't resolved by clearing the rectangle. I had to apply a "TextRenderingHint":
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
e.Graphics.DrawString(listText, myFont, myBrush, e.Bounds, StringFormat.GenericDefault);

Categories

Resources