I try to draw a string (single character) in C# into a Bitmap at an exact position with:
Bitmap bmp = new Bitmap(64, 64);
Graphics g = Graphics.FromImage(bmp);
g.DrawString("W", font1, new SolidBrush(myColor), new Point(32,32);
There is so much empty space rendered around a single letter, that I can not guess the "needed" position to draw the character to have it at the correct position at the end.
By now I have the pixel exact dimension of the character (looking at bits in a separately rendered bitmap). But this information is useless, if I cannot draw the character at an exact position (e.g. center or top right corner or ....).
Are there other methods to draw text in C# on a bitmap? Or are there any converting methods to convert the real pixel position in something DrawString needs?
No need to look at the pixels or start working with your own font..
You can use a GraphicsPath instead of DrawString or TextRenderer, as it will let you know its net bounds rectangle with GraphicsPath.GetBounds() .
When you know it, you can calculate how to move the Graphics object using TranslateTransform:
private void button1_Click(object sender, EventArgs e)
{
string text = "Y"; // whatever
Bitmap bmp = new Bitmap(64, 64); // whatever
bmp.SetResolution(96, 96); // whatever
float fontSize = 32f; // whatever
using ( Graphics g = Graphics.FromImage(bmp))
using ( GraphicsPath GP = new GraphicsPath())
using ( FontFamily fontF = new FontFamily("Arial"))
{
testPattern(g, bmp.Size); // optional
GP.AddString(text, fontF, 0, fontSize, Point.Empty,
StringFormat.GenericTypographic);
// this is the net bounds without any whitespace:
Rectangle br = Rectangle.Round(GP.GetBounds());
g.DrawRectangle(Pens.Red,br); // just for testing
// now we center:
g.TranslateTransform( (bmp.Width - br.Width ) / 2 - br.X,
(bmp.Height - br.Height )/ 2 - br.Y);
// and fill
g.FillPath(Brushes.Black, GP);
g.ResetTransform();
}
// whatever you want to do..
pictureBox1.Image = bmp;
bmp.Save("D:\\__test.png", ImageFormat.Png);
}
A small test routine to let us see the centering better:
void testPattern(Graphics g, Size sz)
{
List<Brush> brushes = new List<Brush>()
{ Brushes.SlateBlue, Brushes.Yellow,
Brushes.DarkGoldenrod, Brushes.Lavender };
int bw2 = sz.Width / 2;
int bh2 = sz.Height / 2;
for (int i = bw2; i > 0; i--)
g.FillRectangle(brushes[i%4],bw2 - i, bh2 - i, i + i, i + i );
}
The GetBounds method returns a RectangleF; in my example it is {X=0.09375, Y=6.0625, Width=21, Height=22.90625}. Do note that due to rounding things can always be off by one..
You may or may not want to change the Graphics setting to special Smoothingmodes etc..
Also it should be noted that this will do automatic ie mechanical centering by the bounds rectangle. This may be quite different from 'optical or visual centering', which is rather hard to code and to some extent a matter of personal taste. But typography is as much an art as a profession..
Related
I'm trying to generate a custom Bitmap through code at a very small size and display it to a PictureBox, upscaled to fit said PictureBox. I am using the graphics object to do this in order to use NearestNeighbor interpolation to upscale single pixels perfectly.
I'm using the graphics object of a temporary default image that is in the PictureBoxs "Image" component on Form.Load, which is sized to be the perfect width and height to maintain the correct aspect ratio from the original Bitmap.
Here is the relevant code:
private void Form1_Load(object sender, EventArgs e)
{
bmp = new Bitmap(16, 9, PixelFormat.Format24bppRgb);
rnd = new Random();
GenerateImage();
}
private void GenerateImage()
{
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
int num = rnd.Next(2);
if (num == 0)
{
bmp.SetPixel(x, y, Color.White);
}
else
{
bmp.SetPixel(x, y, Color.Gold);
}
}
}
Bitmap image = new Bitmap(picOutput.Image);
grp = Graphics.FromImage(image);
grp.InterpolationMode = InterpolationMode.NearestNeighbor;
grp.DrawImage(
bmp,
new Rectangle(0, 0, image.Width, image.Height),
0,
0,
bmp.Width,
bmp.Height,
GraphicsUnit.Pixel
);
grp.Dispose();
picOutput.Image = image;
}
The problem is that the Bitmap seems to be drawn incorrectly. About half a pixel from the original Bitmap is cut off on the left and top edges of the Bitmap when displayed through the PictureBox, and that roughly half a pixel shows up as the original default image on the right and bottom edges. It's almost like the Bitmap was offset up and to the left while being drawn by the graphics object, it doesn't perfectly cover up the original default image like it was supposed to.
My first thought was the PictureBoxs SizeMode, which is still set to "Normal," but none of them change the problem at all. Here is a picture of the problem. The black edges on the right and bottom are part of the temporary default image (the image I used graphics from), which is completely black and covers the entire PictureBox area.
Can anyone offer some insight?
As user Jimi pointed out in a comment, grp.PixelOffsetMode = PixelOffsetMode.Half from this post solved the issue.
At first, I used drawString and GraphicsPath.AddString to draw outlined/solid text in the pictureBox. I can change it's font size, style and font-family but I realized that I won't be able to resized/stretch the text since the font size is proportionally distributed to the string. So the solution I was told was this:
I have been advised that in order to scale a Text (from a Draw String), I need to use a rectangle on which the text will depend on. In that way, I can resize the whole text (width, height, both). But I have no idea how to do it.
PS. If there are other ways, you can tell me. Thanks all.
Here's my TextDrawing Method:
public void DrawRects(Font f, string text, Graphics g, RectangleF rect)
{
List<RectangleF> list = new List<RectangleF>();
using (StringFormat format = new StringFormat())
{
int i;
format.Alignment = StringAlignment.Near;
format.LineAlignment = StringAlignment.Center;
format.Trimming = StringTrimming.None;
format.FormatFlags = StringFormatFlags.MeasureTrailingSpaces;
CharacterRange[] ranges = new CharacterRange[text.Length];
for (i = 0; i < text.Length; i++)
{
ranges[i] = new CharacterRange(i, 1);
}
format.SetMeasurableCharacterRanges(ranges);
Region[] regionArray = g.MeasureCharacterRanges(text, f, rect, format);
for (i = 0; i < regionArray.Length; i++)
{
list.Add(regionArray[i].GetBounds(g));
}
foreach (RectangleF r in list)
{
//g.SmoothingMode = SmoothingMode.AntiAlias;
//g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
//g.InterpolationMode = InterpolationMode.High;
g.DrawRectangle(Pens.LightBlue, Rectangle.Round(r));
}
using (GraphicsPath path = new GraphicsPath())
{
path.AddString(text, f.FontFamily, Convert.ToInt32(f.Style), g.DpiY * rect.Height/72f, rect.Location, format);
RectangleF text_rectf = path.GetBounds();
PointF[] target_pts = {
new PointF(rect.Left, rect.Top),
new PointF(rect.Right, rect.Top),
new PointF(rect.Left, rect.Bottom)};
g.Transform = new Matrix(text_rectf, target_pts);
g.FillPath(Brushes.Red, path);
g.DrawPath(Pens.Red, path);
g.ResetTransform();
}
//g.SmoothingMode = SmoothingMode.AntiAlias;
//g.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
//g.InterpolationMode = InterpolationMode.High;
//g.DrawString(text, f, Brushes.Red, rect, format);
}
}
And my UI for your reference:
Result I need:
Edit: I changed the code on my text drawing, what I still can't do is to create different rectangles on each letters that is able o be resized using trackbar.
I had a go and I couldn't work out the rectangles relationship to the letters...
Never the less I propose a more elegant, time-tested and mathematically correct solution.
Alex Fr provided an excellent set of drawing tools in his DrawTools article and this project serves as a basis for Draw Tool Redux.
The original project by Alex Fr was based on a Microsoft C++ MFC sample project which developers may learn from DRAWCLI. The DrawTools C# program reproduces some of DRAWCLI functionality and uses some design decisions from this sample. These days the only way to see it is via WayBack machine link: https://web.archive.org/web/20120814082327/https://www.codeproject.com/Articles/8494/DrawTools
I'd recommend you swap drawing libraries and start off with a really well designed solution. The Draw Tool Redux has most of the functionality I see you need. With the exception of the Rotation Offset which I believe I've seen an example of in Rod Stephens book, here it is on WayBack Machine again: Interactively rotate images by an arbitrary angle in C#.
I'm wondering if that's possible:
I got a c# application with something like a display consisting of about 11000 circles drawn on the Form.
What I want to achieve is to be able to draw text on that display, but not using the "real" pixels, but using the circles (rectangles) drawn on the form as pixels.
Edit 1:
When drawing text in c#, you would i.e. use something like Graphics.DrawString(...), giving the method a rectangle (so coordinates) in which the text should be drawn in. That text then is drawn in that rectangle using the screen pixels. What I want to do is draw text as well but not using the screen pixels but my custom pixels of which my display consists.
Edit 2
Method used to draw the circles on the Form; Circles is a list consisting of Circle objects, where circleRectangle returns the coordinates in which the circle should be drawn and Filled tells the method if the circle should be filled or not.
public void DrawCircles(Graphics g)
{
graphics = g;
graphics.SmoothingMode =System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Pen pen = new Pen(Color.Black, penthickness);
SolidBrush brush = new SolidBrush(Color.White);
for (int j = 0; j < Circles.Count;j++ )
{
graphics.DrawEllipse(pen, Circles[j].CircleRectangle);
if (Circles[j].Filled)
brush.Color = fillColor;
else
brush.Color = Color.White;
graphics.FillEllipse(brush, Circles[j].CircleRectangle);
}
}
Is this possible and if yes, how would I do that?
You could write on an invisible BitMap with the DrawText method and then scan the bitmap's pixels and turn the corresponding circles on.
Did that last week with the cells of DataGridView. Real easy.
Here is some code:
public void drawText(string text, Font drawFont)
{
Bitmap bmp = new Bitmap(canvasWidth, canvasHeight);
Graphics G = Graphics.FromImage(bmp);
SolidBrush brush = new SolidBrush(paintColor);
Point point = new Point( yourOriginX, yourOriginY );
G.DrawString(text, drawFont, brush, point);
for (int x = 0; x < canvasWidth; x++)
for (int y = 0; y < canvasHeight; y++)
{
Color pix = bmp.GetPixel(x, y);
setCell(x, y, pix); //< -- set your custom pixels here!
}
bmp.Dispose();
brush.Dispose();
G.Dispose();
}
Edit: You would use your dimensions and your origin for the DrawString, of course
I already have this code but it gives me the wrong result.
private void document_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
int charPerLine = e.MarginBounds.Width / (int)e.Graphics.MeasureString("m", txtMain.Font).Width;
}
The txtMain is a textbox.
This should do the trick. Be careful when dividing by a variable cast to an integer. You are leaving yourself open to a divide-by-zero here in the event that the Width property is less than one, which will be truncated to zero. It may be unlikely that you will have such a small font in your application, but it is still good practice.
private void document_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
if( (int)e.Graphics.MeasureString("m", txtMain.Font).Width > 0 )
{
int charPerLine =
e.MarginBounds.Width / (int)e.Graphics.MeasureString("m", txtMain.Font).Width;
}
}
The real issue though is why do you even need to know the number of characters per line. Unless you are trying to do some sort of ASCII art, you can use the different overloads of Graphics.DrawString to have GDI+ layout the text for you inside a bounding rectangle without needing to know how many characters fit on a line.
This sample from MSDN shows you how to do this:
// Create string to draw.
String drawString = "Sample Text";
// Create font and brush.
Font drawFont = new Font("Arial", 16);
SolidBrush drawBrush = new SolidBrush(Color.Black);
// Create rectangle for drawing.
float x = 150.0F;
float y = 150.0F;
float width = 200.0F;
float height = 50.0F;
RectangleF drawRect = new RectangleF(x, y, width, height);
// Draw rectangle to screen.
Pen blackPen = new Pen(Color.Black);
e.Graphics.DrawRectangle(blackPen, x, y, width, height);
// Set format of string.
StringFormat drawFormat = new StringFormat();
drawFormat.Alignment = StringAlignment.Center;
// Draw string to screen.
e.Graphics.DrawString(drawString, drawFont, drawBrush, drawRect, drawFormat);
So if you are trying to print a page of text, you can just set the drawRect to the e.MarginBounds and plug a page worth of text in for drawString.
Another thing, if you are trying to print tabular data, you can just partition the page into rectangles - one for each column/row (however you need it), and use e.Graphics.DrawLine overloads to print the table borders.
If you post more details on what you are actually trying to achieve we can help more.
I have an image with a certain pattern. How do I repeat it in another image using GDI?
Is there any method to do it in GDI?
In C#, you can create a TextureBrush that'll tile your image wherever you use it, and then fill an area with it. Something like this (an example that fills the whole image)...
// Use `using` blocks for GDI objects you create, so they'll be released
// quickly when you're done with them.
using (TextureBrush brush = new TextureBrush(yourImage, WrapMode.Tile))
using (Graphics g = Graphics.FromImage(destImage))
{
// Do your painting in here
g.FillRectangle(brush, 0, 0, destImage.Width, destImage.Height);
}
Note, if you want some control over how the image is tiled, you're going to need to learn a bit about transforms.
I almost forgot (actually I did forget for a bit): You'll need to import System.Drawing (for Graphics and TextureBrush) and System.Drawing.Drawing2D (for WrapMode) in order for the code above to work as is.
There's no function to paint a particular image as a "pattern" (painting it repeatedly), but it should be pretty simple to do:
public static void FillPattern(Graphics g, Image image, Rectangle rect)
{
Rectangle imageRect;
Rectangle drawRect;
for (int x = rect.X; x < rect.Right; x += image.Width)
{
for (int y = rect.Y; y < rect.Bottom; y += image.Height)
{
drawRect = new Rectangle(x, y, Math.Min(image.Width, rect.Right - x),
Math.Min(image.Height, rect.Bottom - y));
imageRect = new Rectangle(0, 0, drawRect.Width, drawRect.Height);
g.DrawImage(image, drawRect, imageRect, GraphicsUnit.Pixel);
}
}
}