GDI+ gradient effect - c#

In the code below:
void f13(Graphics g)
{
g.FillRectangle(new SolidBrush(Color.Black), pictureBox1.ClientRectangle);
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var zf = .0143;
const int w = 6000, h = 10, margin = 40;
var bmp = new Bitmap(w + 2 * margin, h + 2 * margin);
var bmpG = Graphics.FromImage(bmp);
bmpG.FillRectangle(new SolidBrush(Color.White), 0, 0, bmp.Width, bmp.Height);
var srcRect = new RectangleF(margin - .5f, margin - .5f, w, h);
zf = (float)Convert.ToInt32(w * zf) / w;
var destRect = new Rectangle(0, 0, Convert.ToInt32(w * zf), Convert.ToInt32(w * zf));
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
destRect.X += destRect.Width;
g.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
f13(e.Graphics);
}
I get a gap between two rectangles:
micro http://www.uploadup.com/di-0HXM.png
macro http://www.uploadup.com/di-G1O5.png
why is that?
If the gap line is not so clear, you may decrease margin. if you set it to 10 you'll get:
macro, less margin http://www.uploadup.com/di-P2ZT.png

That'll happen if your rectangles' boundaries aren't integers. Gradient has nothing to do with it.
Consider: Let's say you're drawing a rectangle whose right side is at X=100.5, and you're filling it with white (with the existing background being black). So the graphics library (this isn't specific to GDI+) will "half-fill" those rightmost pixels (at X=100) with white, meaning they blend the existing black with a 50% mix of white, for a result of gray.
Then you draw another rectangle whose left side is at X=100.5. Now you're once again filling the pixels at X=100 halfway with white, so the graphics library will take the existing color (gray) and blend it with a 50% white, leaving you with 75% white.
If you don't want this kind of seam, you have to either (a) make sure your rectangles overlap a little bit, or (b) manually round your coordinates to the nearest pixel, so all the pixels are getting completely written instead of blended with what's already there.

Related

DrawEllipse: Ellipse goes outside of the Bitmap size

I want to draw a circle with DrawEllipse on a specified Bitmap, with the same size of the Bitmap, but the result is that the circle appears clipped at the edges.
Why this problem?
Bitmap layer = new Bitmap(80, 80);
using (Graphics g = Graphics.FromImage(layer))
{
using (Pen p = new Pen(Color.Black, 4))
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
}
pictureBox3.Size = new Size(100, 100);
pictureBox3.Image = layer;
By default a Pen has a PenAlignment.Center.
This means that half of its widh will draw outside the bounding rectangle.
You can simply avoid the issue by changing it to PenAlignment.Inset:
using (Pen p = new Pen(Color.Black, 4) { Alignment = PenAlignment.Inset})
{
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
Update: If you want to turn on smoothing for the Graphics object you will need 1 or 2 extra pixels on both sides of the pen stroke for the anti-aliasing pixels. Using a smaller bounding rectanlge can't be avoided now. But..:
Rectangle rect = new Rectangle(Point.Empty, layer.Size);
rect.Inflate(-1, -1); // or -2
..should do..

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.

DrawEllipse: Antialiasing broken with PenAlignment.Inset

As suggested by #TaW on this previous question, I setted PenAlignment.Inset to draw the circle inside the Bitmap, but this caused another problem.
I want to draw a circle on a specified Bitmap with antialiasing.
SmoothingMode.AntiAlias
The problem is that, when I use PenAlignment.Inset, the antialiasing doesn't work correctly!
Instead, with PenAlignment.Center, it works correctly...
Any suggestion to resolve this problem?
Bitmap layer = new Bitmap(80, 80);
using (Graphics g = Graphics.FromImage(layer))
{
using (Pen p = new Pen(Color.Black, 4))
{
p.Alignment = PenAlignment.Inset;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.DrawEllipse(p, new Rectangle(0, 0, layer.Width, layer.Height));
}
}
pictureBox3.Size = new Size(100, 100);
pictureBox3.Image = layer;
(Note the bugs on the left image)
Deflating the bounding rectangle by 1/2 of the pen's stroke width should solve this problem. By "deflate", I mean pull in all 4 sides towards the rectangle's center by 1/2 pen width:
float halfPenWidth = p.Width*0.5f;
g.DrawEllipse(p, new RectangleF(halfPenWidth, halfPenWidth, layer.Width - p.Width, layer.Height - p.Width));
or plugging in a hardcoded pen width of 4:
g.DrawEllipse(p, new Rectangle(2, 2, layer.Width - 4, layer.Height - 4));
Note that the full pen width must be subtracted from the rectangle's width and height in order to pull the right and bottom sides in by 1/2 pen width while keeping the rectangle centered on the same point.
Using this code with pen alignment centered, 1/2 of the stroke width will be drawn outside of the rectangle at the points where the ellipse touches the rectangle, but it will still be drawn inside the bitmap.

Drawing Colors in a picturebox?

In C# i have a picturebox. i would like to draw 4 colors. The default will be white, red, green, blue. How do i draw these 4 colors stritched in this picbox? or should i have 4 picbox? in that case how do i set the rgb color?
You need to specify what it is you would specifically like to draw. You can't draw a red - that makes no sense. You can, however, draw a red rectangle at location (0,0) which is 100 pixels tall and 100 wide. I will answer what I can, however.
If you want to set the outline of a shape to a specific color, you would create a Pen object. If you want to fill a shape with a color, however, then you would use a Brush object. Here's an example of how you would draw a rectangle filled with red, and a rectangle outlined in green:
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = e.Graphics;
Brush brush = new SolidBrush(Color.Red);
graphics.FillRectangle(brush, new Rectangle(10, 10, 100, 100));
Pen pen = new Pen(Color.Green);
graphics.DrawRectangle(pen, new Rectangle(5, 5, 100, 100));
}
Add a PictureBox to the form, create an event handler for the paint event, and make it look like this:
private void PictureBox_Paint(object sender, PaintEventArgs e)
{
int width = myPictureBox.ClientSize.Width / 2;
int height = myPictureBox.ClientSize.Height / 2;
Rectangle rect = new Rectangle(0, 0, width, height);
e.Graphics.FillRectangle(Brushes.White, rect);
rect = new Rectangle(width, 0, width, height);
e.Graphics.FillRectangle(Brushes.Red, rect);
rect = new Rectangle(0, height, width, height);
e.Graphics.FillRectangle(Brushes.Green, rect);
rect = new Rectangle(width, height, width, height);
e.Graphics.FillRectangle(Brushes.Blue, rect);
}
This will divide the surface into 4 rectangles and paint each of them in the colors White, Red, Green and Blue.
If you want to use non-predefined colors, then you need to get a Color object from the static method Color.FromArgb().
int r = 100;
int g = 200;
int b = 50;
Color c = Color.FromArgb(r, g, b);
Brush brush = new SolidBrush(c);
//...
Best RegardsOliver Hanappi

(C#) How to draw an "a" with Tahoma with height = 6

I've been trying to do this, but for some reason this is just giving me weird results:
int bpp = Screen.PrimaryScreen.BitsPerPixel;
string fontName = "Tahoma";
Font font = new Font(fontName, 10 * bpp, GraphicsUnit.Point);
Bitmap bm = new Bitmap(20 * bpp, 20 * bpp);
Graphics g = Graphics.FromImage(bm);
TextRenderer.DrawText(g, "a", font, new Rectangle(0, 0, 5 * bpp, 6 * bpp), Color.Black);
g.Flush();
pictureBox1.Image = bm;
What am I doing wrong here? I don't see anything printed on the picture. If I remove all bpp references, I can see it, but it's pretty small.
You are aware that BitsPerPixel describes the color depth (the number of memory bits that are used to describe the color of a pixel), and has nothing to do with resolution?
I assume that what you want to do is to draw the text in a size that is related to the resolution, which you can do by referring to the DpiX and DpiY properties of the Graphics object.
Update
I am not sure if yo need to bring Dpi into the calculation for this. All you need to do is to create a Rectangle that defines the desired size of your text, and then calculate the correct font size to make the text fit inside the rectangle. The following does that (but maximizes the text size both vertical and horizontal direction). It might give you some pointers to solve your problem:
Bitmap bm = new Bitmap(50, 50);
using (Font font = new Font(fontName, 10, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
// measure how large the text is on the Graphics object with the current font size
SizeF s = g.MeasureString(text, font);
// calculate how to scale the font to make the text fit
float fontScale = Math.Max(s.Width / rect.Width, s.Height / rect.Height);
using (Font fontForDrawing = new Font(font.FontFamily, font.SizeInPoints / fontScale, GraphicsUnit.Point))
{
g.DrawString(text, fontForDrawing, Brushes.Black, rect, stringFormat);
}
}
And if you want to print the text with a given point size, you don't need to go about measuring; just set the font size:
Bitmap bm = new Bitmap(20, 20);
using (Font font = new Font(fontName, 6, GraphicsUnit.Point))
using (Graphics g = Graphics.FromImage(bm))
{
g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat stringFormat = new StringFormat()
{
Alignment = StringAlignment.Center,
LineAlignment = StringAlignment.Near
};
Rectangle rect = new Rectangle(0, 0, bm.Width, bm.Height);
g.DrawString(text, font, Brushes.Black, rect, stringFormat);
}
I would try:
int ImgQual = 600;
int Width = 50;
int Height = 50;
Font TextFont = New Font("Tahoma", 14, FontStyle.Bold)
Bitmap bmp = New Bitmap(Width, Height);
bmp.SetResolution(ImgQual, ImgQual);
System.Drawing.Graphics g = Graphics.FromImage(bmp);
System.Drawing.StringFormat sf = New System.Drawing.StringFormat();
sf.Alignment = StringAlignment.Center;
g.DrawString("a", NumberTextFont, Brushes.Black, New RectangleF(0, 0, Width, Height), sf);
return bmp;
There are two primary reasons the "a" is small:
The height of a font is measured using a point size (which is 1/72nd of a inch, quite different from a number of pixels because different computer screens have different numbers of pixels per inch).
Font height is measured from the top of a line of text to the bottom. As some letters have ascenders (the tall bar in "h") and others have descenders (the dangly bit of a "g"), an "a" will occupy only about a third of the height of the font.
If you wish your "a" to fill the image, then you will need to draw it into a larger rectangle and "clip off" the empty regions above and below the "a". If you're not too worried about screen DPI on different computers, then you can just experiment until you find a font size (and position to draw at) that works.

Categories

Resources