Graphics.DrawPath and LinearGradientBrush issue - c#

In relation to this question that I asked a few weeks ago now
LinearGradientBrush does not render correctly
Consider the following code:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Rectangle rect = new Rectangle(100, 100, 200, 100);
using(LinearGradientBrush brush = new LinearGradientBrush(rect, Color.Red, Color.Blue, 90))
{
using(Pen pen = new Pen(brush, 1))
{
pen.Alignment = PenAlignment.Inset;
e.Graphics.DrawRectangle(pen, rect);
}
}
}
Produces this result...
Why is there a red line where there should be a blue line, and how do I fix this?
Pen.Alignment = PenAlignment.Inset didn't work!

Pens (or the Graphics DrawMethods) have a tendency to draw outside their boundaries. I think Microsoft considered that a feature, but I never understood it.
Try using a smaller rectangle for the pen:
using (var brush = new LinearGradientBrush(rect, Color.Red, Color.Blue, 90)) {
using (Pen pen = new Pen(brush, 1)) {
e.Graphics.DrawRectangle(pen, new Rectangle(rect.X, rect.Y,
rect.Width - 1, rect.Height - 1));
}
}

OK, to sum it up: DrawRectangle always overdraws by 1 pixel (*) and the LinearGradientBrush therefore restarts with the 1st color.
Yes, Lars got one solution, but here is an interesting alternative:
brush.WrapMode = WrapMode.TileFlipXY;
This makes sure that the gradient doesn't start at the beginning when it has to overdraw but reverses in both directions, so the extra pixel is drawn in the right Color and you don't need to manipulate one of the two the Rectangle Sizes..
Well ok, if you need total precision it is probably second best, as the last row and column will have the same color as their neighbours..but not having to deal with two sizes may be worth it..
(*) Which is why you can't use it to draw a 1x1 square/pixel. Instead you need to use FillRectangle..

Related

Graphics.DrawImage() clips image when using RotateTransform()

I have following code to draw my border2.bmp in 4 direction
private void Form1_Paint(object sender, PaintEventArgs e)
{
Bitmap border = new Bitmap("border2.bmp");
int borderThick = border.Height;
Graphics g = e.Graphics;
Size region = g.VisibleClipBounds.Size.ToSize();
Rectangle desRectW = new Rectangle(0, 0, region.Width - borderThick, borderThick);
// 1. LEFT - RIGHT border
g.TranslateTransform(30, 30);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 2. UP - BUTTOM border
g.ResetTransform();
g.TranslateTransform(50, 50);
g.RotateTransform(90);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 3. RIGHT-LEFT border
g.ResetTransform();
g.TranslateTransform(100, 100);
g.RotateTransform(180);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
// 4. BOTTOM - UP border
g.ResetTransform();
g.TranslateTransform(150, 150);
g.RotateTransform(270);
g.DrawImage(border, desRectW, desRectW, GraphicsUnit.Pixel);
}
My original image is:
But the result of rotations are not exactly as I expected. 90 degrees is missing the first red line, 270 degrees is missing first black column, and 180 degrees is missing both.
Like image I attached:
PS: you can get border2.bmp at: http://i.imgur.com/pzonx3i.png
Edit:
I tried g.PixelOffsetMode = PixelOffsetMode.HighQuality; as #Peter Duniho comment, but I found it also does't draw correctly.
Example: 4 line is not starting at same position as we expect.
g.TranslateTransform(50, 50);
// LEFT - RIGHT border
g.DrawLine(Pens.Red, 0, 0, 100, 0);
// UP - BOTTOM border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Blue)), 0, 0, 100, 0);
// RIGHT-LEFT border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Green)), 0, 0, 100, 0);
// BOTTOM - UP border
g.RotateTransform(90);
g.DrawLine(new Pen(Color.FromArgb(128, Color.Gray)), 0, 0, 100, 0);
I can't really explain why this happens, except that any graphics API is necessarily going to include optimizations which may lead to imprecise behaviors at times and it seems that you are running into such a situation here.
In your particular example, the problem can be corrected by adding the following statement to the code, before you draw the images:
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
Setting it to Half will also work. It is equivalent to HighQuality (or technically, HighQuality is equivalent to it…but I find HighQuality more descriptive in the code :) ).
This will slow the rendering of the bitmap down somewhat, but probably not in a way that is perceptible to your users.
While the .NET documentation isn't very helpful in terms of describing this setting, the native Win32 docs for the same feature has slightly more detail:
PixelOffsetMode enumeration. From the description, one can infer that with the logical center of the pixel at (0,0), it's possible to lose a pixel on one edge when rotating (and/or gain a pixel on another edge). Switching to Half fixes this.
You need to account some things.
(1) RotateTransforms applies rotation at the current origin (as explained in the Matrix.Rotate documentation
(2) The default MatrixOrder (when not specified by using the overrides with the additional argument) is Prepend. Which means in your case the resulting transformation is rotate, then translate.
For instance, if you put this code inside the paint event:
var g = e.Graphics;
var loc = new Point(128, 128);
var rect = new Rectangle(0, 0, 64, 16);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.FillRectangle(Brushes.Blue, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(90);
g.FillRectangle(Brushes.Red, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(180);
g.FillRectangle(Brushes.Green, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.RotateTransform(270);
g.FillRectangle(Brushes.Magenta, rect);
you'll get this
The blue rectangle is not rotated. Now pin virtually the origin (the upper left point) and start rotating clockwise. You'll see that it will exactly match the Red (90), Green (180) and Magenta (270) rectangles.
What all that means is that if you want to form a rectangle, you need to apply additional offset (translation) to the rotated rectangles. It depends how you want to handle the overlapping areas, but for the sample if we want to concat the Red rectangle right to the Blue one, we need to add the Blue rectangle Width + the original rectangle Height in the X direction. For other rotated rectangles you can apply similar additional offset to X, Y or both.
To complete the sample, if we modify the code like this
var g = e.Graphics;
var loc = new Point(128, 128);
var rect = new Rectangle(0, 0, 64, 16);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y);
g.FillRectangle(Brushes.Blue, rect);
g.ResetTransform();
g.TranslateTransform(loc.X + rect.Width + rect.Height, loc.Y);
g.RotateTransform(90);
g.FillRectangle(Brushes.Red, rect);
g.ResetTransform();
g.TranslateTransform(loc.X + rect.Width + rect.Height, loc.Y + rect.Width + rect.Height);
g.RotateTransform(180);
g.FillRectangle(Brushes.Green, rect);
g.ResetTransform();
g.TranslateTransform(loc.X, loc.Y + rect.Width + rect.Height);
g.RotateTransform(270);
g.FillRectangle(Brushes.Magenta, rect);
the new result will be
Once you understand all that, hope you can apply the required corrections to your concrete code.

How can I scale up the hatchstyle drawn in a rectangle? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 7 years ago.
Improve this question
I'm using .Net drawing to draw a diagram. It is essentially a stacked bar chart.
The issue I have is that I want to reduce the amount of lines in the hatch style so in a way scale it up to make it clearer. I've looked around but didn't come across anything that could help me.
I draw a rectangle and then use a hatchbrush to fill it but due to image size the hatchfill becomes less clearer. Thank you for any suggestions.
The hatchStyles and brush types are stored in the db and I use a helper function to return them. So I draw the rectangle and after getting the brush I fill the rectangle. Essentially I want to scale up the hatch fill if that can be done.
g.DrawRectangle(gridpen, startX, startY, BOREHOLE_RECT_WIDTH, layerRectHeight);
brush = GetBoreholeBrush(l.SoilTypeMatrixLevel1Id.PrimaryBrushType,
l.SoilTypeMatrixLevel1Id.PrimaryFillStyle,
l.SoilTypeMatrixLevel1Id.PrimaryColour);
g.FillRectangle(brush, startX, startY, BOREHOLE_RECT_WIDTH, layerRectHeight);
And the getBrush function; the brush type, hatch style and colour are stored in the db and used to create the returned brush:
//===================================
private Brush GetBoreholeBrush(string BrushType, string HatchStyle, string Colour)
//===================================
{
//Decide on what brush type has been chosen.
Brush brush;
if (BrushType.ToLower() == BrushTypes.HatchBrush.ToString().ToLower())
{
brush = new HatchBrush(GetHatchStyle(HatchStyle),
Color.Black, ColorTranslator.FromHtml(Colour));
}
else if (BrushType.ToLower() == BrushTypes.SolidBrush.ToString().ToLower())
{
brush = new HatchBrush(GetHatchStyle(HatchStyle),
Color.Black, ColorTranslator.FromHtml(Colour));
}
else if (BrushType.ToLower() == BrushTypes.TextureBrush.ToString().ToLower())
{
brush = new HatchBrush(GetHatchStyle(HatchStyle),
Color.Black, ColorTranslator.FromHtml(Colour));
}
else
{
brush = new HatchBrush(GetHatchStyle(HatchStyle),
Color.Black, ColorTranslator.FromHtml(Colour));
}
return brush;
}
Function to return the hatch style:
//===================================
private HatchStyle GetHatchStyle(string FillStyle)
//===================================
{
//Loop through each hatch tyle and return the correct one.
foreach (HatchStyle style in Enum.GetValues(typeof(HatchStyle)))
{
if (style.ToString().ToLower() == FillStyle.ToLower())
{
return style;
}
}
return HatchStyle.Vertical;
}
As you can see in the image below the hatch style is not clear.
The most direct but probably not very helpful answer is : No you can't scale the hatch pattern of a HatchBrush.
It is meant to always look sharp at the pixel level and is not even affected by scaling the Graphics object.
Looking at your question I wonder: Are you sure you are really using a HatchBrush? You get the brush from a function GetBoreholeBrush. If you really have stored indices into the 50 HatchStyle then I guess you really use a HatchBrush.
Now as using a HatchBrush won't work I guess you could use a TextureBrush instead..
You could transform the hatch patterns to larger versions by scaling them up; this is not exactly a simple conversion. The direct approach of drawing the larger by an integer factor and without anti-aliasing is simple and may be good enough.
But you may need to fine-tune them, as this way all pixels, that is both line pixels and background pixels get enlarged and also all diagonals will look jagged.
So you would need to balance the hatch size and the stroke width and recreate all patterns you need from scratch in larger sizes.
Here is an example that illustrates the problems with the simple solution; the first row is the original hatch pattern the others are simple texture brush results, scaled by 1x, 2x and 3x..:
First a function to transform a HatchBrush to a TextureBrush
TextureBrush TBrush(HatchBrush HBrush)
{
using (Bitmap bmp = new Bitmap(8,8))
using (Graphics G = Graphics.FromImage(bmp))
{
G.FillRectangle(HBrush, 0, 0, 8, 8);
TextureBrush tb = new TextureBrush(bmp);
return tb;
}
}
Note that the hatch pattern is 8x8 pixels.
Now the Paint code used for the above image:
private void panel1_Paint(object sender, PaintEventArgs e)
{
var hs = (HatchStyle[])Enum.GetValues(typeof(HatchStyle));
for (int i = 0; i < hs.Length; i++)
using (HatchBrush hbr = new HatchBrush(hs[i], Color.GreenYellow))
using (HatchBrush hbr2 = new HatchBrush(hs[i], Color.LightCyan))
{
e.Graphics.FillRectangle(hbr, new Rectangle(i * 20, 10,16,60));
using (TextureBrush tbr = TBrush(hbr2))
{
e.Graphics.FillRectangle(tbr, new Rectangle(i * 20, 80, 16, 60));
tbr.ScaleTransform(2, 2);
e.Graphics.FillRectangle(tbr, new Rectangle(i * 20, 150, 16, 60));
tbr.ResetTransform();
tbr.ScaleTransform(3,3);
e.Graphics.FillRectangle(tbr, new Rectangle(i * 20, 220, 16, 60));
}
}
}
Note that while the TextureBrush has nice methods to modify the texture, the HatchBrush has nothing like that at all..

Pen.Alignment is not working while drawing line

I want to draw a thick line and a thin line. The thick line should be positioned inside of the theoretical line. So I set the pen alignment as below.
Pen blackPen = new Pen(Color.FromArgb(255, 0, 0, 0), 1);
Pen greenPen = new Pen(Color.FromArgb(255, 0, 255, 0), 10);
greenPen.Alignment = PenAlignment.Inset;
e.Graphics.DrawLine(greenPen, 10, 100, 100, 50);
e.Graphics.DrawLine(blackPen, 10, 100, 100, 50);
But the idea is not get worked. Any better idea to do the same ???
Pen.Alignment is partially implemented:
This property determines how the Pen draws closed curves and polygons. The PenAlignment enumeration specifies five values; however, only two values—Center and Inset—will change the appearance of a drawn line. Center is the default value for this property and specifies that the width of the pen is centered on the outline of the curve or polygon. A value of Inset for this property specifies that the width of the pen is inside the outline of the curve or polygon. The other three values, Right, Left, and Outset, will result in a pen that is centered.
So, Inset will work only on polygons or curves (e.g. Graphics.DrawRectangle).

System.Drawing.Bitmap GetBounds GraphicsUnit.Inch

Can anybody tell me how to get a rectangle back from GetBounds in any units OTHER than pixels? The following code - lifted directly off the MSDN documentation for this function - returns a rectangle that is pretty obviously in pixels rather than points (1/72 of an inch). (Unless icons come in a size of 32/72"x32/72" rather than 32x32 pixels like I think). I am most interested in working with a rectangle in inches, but I would settle for simply seeing the GetBounds pageUnit parameter cause a change in the returned rectangle.
Bitmap bitmap1 = Bitmap.FromHicon(SystemIcons.Hand.Handle);
Graphics formGraphics = this.CreateGraphics();
GraphicsUnit units = GraphicsUnit.Point;
RectangleF bmpRectangleF = bitmap1.GetBounds(ref units);
Rectangle bmpRectangle = Rectangle.Round(bmpRectangleF);
formGraphics.DrawRectangle(Pens.Blue, bmpRectangle);
formGraphics.Dispose();
The Information is a little sparse on this, I was able to find this MSDN Forum posting that suggests since the Bitmap is already created the units have already been set and are not changable. Since the GraphicsUnit is being passed by a reference, it you look at it after the call you will find it set back to Pixel from Inch. If you actually want to change the size that the rectangle is drawn at set the Graphics.PageUnit Property on formGraphics to the GraphicsUnit you want to draw the Rectangle at.
From above Link:
In this sample, the parameters of Image.GetBounds method don’t change the result, because the bound of Bitmap has been decided. The parameters only determine the unit length to deal with the range, inch by inch or point by point. But the parameters will not influence the result.
emphasis mine
A bit late answering this one, but I thought I would do so because I found it in Google when trying to answer the question "how many mm can I fit in my picture box?", it would have saved me a lot of time not having to work out how to do it!. GetBounds is useless (if you wanted it in pixels...) but it is possible to find the relation between drawing units and display pixels using the Graphics.TransformPoints method:
private void Form1_Load(object sender, EventArgs e)
{
Bitmap b;
Graphics g;
Size s = pictureBox1.Size;
b = new Bitmap(s.Width, s.Height);
g = Graphics.FromImage(b);
PointF[] points = new PointF[2];
g.PageUnit = GraphicsUnit.Millimeter;
g.PageScale = 1.0f;
g.ScaleTransform(1.0f, 1.0f);
points[0] = new PointF(0, 0);
points[1] = new PointF(1, 1);
g.TransformPoints(CoordinateSpace.Device, CoordinateSpace.Page, points);
MessageBox.Show(String.Format("1 page unit in {0} is {1} pixels",g.PageUnit.ToString(),points[1].X));
points[0] = new PointF(0, 0);
points[1] = new PointF(1, 1);
g.TransformPoints(CoordinateSpace.Page, CoordinateSpace.World, points);
MessageBox.Show(String.Format("1 page unit in {0} is {1} pixels",g.PageUnit.ToString(),points[1].X));
g.ResetTransform();
pictureBox1.Image = b;
SolidBrush brush = new SolidBrush(Color.FromArgb(120, Color.Azure));
Rectangle rectangle = new Rectangle(10, 10, 50, 50);
// Fill in the rectangle with a semi-transparent color.
g.FillRectangle(brush, rectangle);
pictureBox1.Invalidate();
}
This will display the basic mm to display pixels (3.779527 in my case) - the world coordinates are 1 mm per pixel, this would change if you applied graphics.ScaleTransform.
Edit: Of course, it helps if you assign the bitmap to the pictureBox image property (and keep the Graphics object to allow changes as required).
Add label
In class Form1 Add field
PointF[] cooridates;
Form1.cs [design] look for lighting bolt in properties double click Paint create handler
Form1_Paint(object sender,PaintEventArgs)
{
e.Graphics.PageUnit = GraphicsUnit.Inch;
if (cooridates != null)
e.Graphics.TransformPoints(CoorinateSpace.World,
CoorinateSpace.Device,cooridates);
}
Create handler again for Form1.MouseMove
Form1_MouseMove(object sender,MouseEventArgs e
{
cooridates[0].X = e.Location.X;
cooridates[0].Y = e.Location.Y;
this.Refresh();
label1.Text = $"X = {cooridates[0].X} Y = {
{ cooridates[0].Y } ";
}
Form1_Load(object sender,MouseEventArgs)
{
cooridates = new PointF[1] { new PointF(0f,0f) };
}
Move mouse to get cooridates in Inches

Border in DrawRectangle

Well, I'm coding the OnPaint event for my own control and it is very nescessary for me to make it pixel-accurate.
I've got a little problem with borders of rectangles.
See picture:
removed dead ImageShack link
These two rectangles were drawn with the same location and size parameters, but using different size of the pen. See what happend? When border became larger it has eaten the free space before the rectangle (on the left).
I wonder if there is some kind of property which makes border be drawn inside of the rectangle, so that the distance to rectangle will always be the same. Thanks.
You can do this by specifying PenAlignment
Pen pen = new Pen(Color.Black, 2);
pen.Alignment = PenAlignment.Inset; //<-- this
g.DrawRectangle(pen, rect);
If you want the outer bounds of the rectangle to be constrained in all directions you will need to recalculate it in relation to the pen width:
private void DrawRectangle(Graphics g, Rectangle rect, float penWidth)
{
using (Pen pen = new Pen(SystemColors.ControlDark, penWidth))
{
float shrinkAmount = pen.Width / 2;
g.DrawRectangle(
pen,
rect.X + shrinkAmount, // move half a pen-width to the right
rect.Y + shrinkAmount, // move half a pen-width to the down
rect.Width - penWidth, // shrink width with one pen-width
rect.Height - penWidth); // shrink height with one pen-width
}
}
This isn't a direct answer to the question, but you might want to consider using the ControlPaint.DrawBorder method. You can specify the border style, colour, and various other properties. I also believe it handles adjusting the margins for you.
I guess not... but you may move the drawing position half the pen size to the bottom right

Categories