i am trying to measure single chars of different fonts from Google fonts.
I tried a lot, but the proportion of height and width was always wrong...
Here are some of my attempts:
TextRenderer
font:= the bloburi of a Google font-example: "https://github.com/FontFaceKit/open-sans/blob/gh-pages/fonts/Bold/OpenSans-Bold.ttf?raw=true"
FontRequestHelper helper = new FontRequestHelper(font);
iFontCollection = helper.iFontCollection;
FontStyle style = iFontCollection.Families[0].IsStyleAvailable(FontStyle.Bold) ? FontStyle.Bold : FontStyle.Regular;
System.Drawing.Font googlefont = new System.Drawing.Font(iFontCollection.Families[0], tSize, FontStyle.Regular);
size = TextRenderer.MeasureText("A", googlefont);
MeasureString
i tried different ones, always not accurated...
SizeF sizeFf = new SizeF();
using (var image = new Bitmap(1, 1))
{
using (var g = Graphics.FromImage(image))
{
sizeFf = g.MeasureString("A", googlefont);
}
}
or
using (Graphics g = Graphics.FromHwnd(IntPtr.Zero))
{
sizeFf = g.MeasureString("A", googlefont);
}
I dont need the exact width and height, the right proportion would be also reasonable. Online i only find this two solutions as good... Why does it not work for me?
Thx a lot
Related
I have some C# code that adds a simple text overlay with a border and semi-transparent background to an image. It works great, but I'm trying to get an equivalent result using Magick.NET. (The straight C# code drops the XMP tags from the original image, and I haven't found a way to deal with that.) Magick.NET handles the XMP tags well, but I'm having trouble replicating the original output.
Original code follows:
using (Image i = Image.FromStream(stream))
{
int width = i.Width;
int height = i.Height;
using (Graphics graphics = Graphics.FromImage(i))
{
string measureString = "my string";
Size stringSize = graphics.MeasureString(measureString, stringFont).ToSize();
Point drawLocation = new Point(width - stringSize.Width - 15, height - stringSize.Height - 15);
Rectangle rect = new Rectangle(drawLocation.X, drawLocation.Y, stringSize.Width, stringSize.Height);
graphics.DrawRectangle(blackPen, rect);
graphics.FillRectangle(fillBrush, rect);
graphics.DrawString(measureString, stringFont, Brushes.Yellow, drawLocation);
}
i.Save(outputFolder + Path.GetFileName(imgFileName));
}
I cobbled this together based on the Magick.NET examples. This get close to what I'm looking for, but adding the border removes the transparency value, and I'm left with a dark gray background, instead of the transparency.
var settings = new MagickReadSettings{
Font = "Calibri",
FillColor=MagickColors.Yellow,
StrokeColor=MagickColors.Black,
TextGravity = Gravity.Center,
BackgroundColor = new MagickColor("#66666699"),
BorderColor = MagickColors.Black,
Height = 250, // height of text box
Width = 680 // width of text box
};
using (var image = new MagickImage(inputFile))
{
using (var caption = new MagickImage($"caption:{myString}", settings))
{
//adding this border removes transparency
// caption.BorderColor = MagickColors.Black;
// caption.Border(1);
image.Composite(caption, Gravity.Southeast, CompositeOperator.Over);
image.Write(outputFile);
}
}
In command line ImageMagick, this seems to work for me in that the background color is transparent gray. The following the result may be what you want:
convert -font ubuntu -fill yellow -stroke black -gravity center -background "#66666699" -bordercolor black -size 250x680 caption:"This Is Some Text" result.png
Note: I used -background, not -backgroundcolor. Also BorderColor is not the color for the outline of the text. That is the stroke. You have not used BorderColor, since you have not specified the Border amount (as in -border in command line), which would outline the image rectangle and not the text.
Due to time constraints with my project, I took a slightly different path to make this work. I wound up creating the transparent overlay using my original .NET drawing code, and passing that as a memory stream to Magick.NET to handle the merge.
Workaround:
string measureString = "build custom string here";
using (var tmpStreamImg = new MemoryStream())
{
// Call custom function to get length of my string
System.Drawing.Size stringSize = MeasureString(measureString, stringFont).ToSize();
Rectangle rect = new Rectangle(0, 0, stringSize.Width, stringSize.Height);
using (Bitmap overlay = new Bitmap(rect.Width, rect.Height))
{
overlay.SetResolution(350, 350);
using (Graphics overlayGraphic = Graphics.FromImage(overlay))
{
overlayGraphic.DrawRectangle(blackPen, rect);
overlayGraphic.FillRectangle(fillBrush, rect);
overlayGraphic.DrawString(measureString, stringFont, Brushes.Yellow, 3, 3);
}
overlay.Save(tmpStreamImg, ImageFormat.Png);
}
tmpStreamImg.Position= 0;
using (var originalImage = new MagickImage(imgFileName))
{
using (var overlayImage = new MagickImage(tmpStreamImg))
{
originalImage.Composite(overlayImage, Gravity.Southeast, CompositeOperator.Over);
originalImage.Write(outputFolder + Path.GetFileName(imgFileName));
}
}
}
I have a custom control that may have user customizable Font in future (the zoom is already implemented). I must fill a rectangle under two digits that form a base-10 number. I have different colors for zero, one or both of the digits.
With the font {Name = Microsoft Sans Serif Size=16} and the following Graphics.MeasureString method calls:
g.MeasureString("00", Font);
g.MeasureString("0", Font);
I get:
The size of "00" is {Width = 31.5486088 Height = 26.8124962}
The size of "0" is {Width = 19.3298588 Height = 26.8124962}
The width of "0" is a lot bigger that half of the width of "00".
I know of the methods Graphics.MeasureString, it has many overloads, and I also know of the StringFormat class. How can I correctly compute the width of the '0' char?
Because the font will be user-customizable, I do not want to solve the problem using a monospace font.
If I use the following calls:
g.MeasureString("00", Font, 999, StringFormat.GenericTypographic);
g.MeasureString("0", Font, 999, StringFormat.GenericTypographic);
The width of "0" seems to be half of the width of "00", but the digits overlap when drawn with a smaller font size:
Update: In the OnPaint method of an UserControl I have this code:
Graphics g = e.Graphics;
int[] indices = { 0, 1 };
CharacterRange[] charRanges = new CharacterRange[indices.Length];
for (int chx = 0; chx < indices.Length; ++chx)
{
charRanges[chx] = new CharacterRange(indices[chx], 1);
}
StringFormat sf = new StringFormat(StringFormat.GenericDefault);
sf.SetMeasurableCharacterRanges(charRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges("01", Font, e.ClipRectangle, sf);
RectangleF[] r = new RectangleF[regions.Length];
int i = 0;
foreach (Region rr in regions)
{
r[i] = rr.GetBounds(g);
g.DrawRectangle(Pens.Blue, r[i].X, r[i].Y, r[i].Width, r[i].Height);
++i;
}
g.DrawString("0", Font, Brushes.Black, r[0], sf);
g.DrawString("1", Font, Brushes.Black, r[1], sf);
The font is {Name = "Microsoft Sans Serif" Size=25}. When running the program, this is what is visible:
I want to make the digits centered in the blue rectangles. The rectangles must be as big as possible in the UserControl but also leaving space for a percent of the Height of the UserControl. The Font should adapt to the rectangles.
Small adjustments are required to make this work as intended:
TextRenderingHint.ClearTypeGridFit gives a better result when rendering the Text.
It's more precise and works well with the grid-fitting nature of Graphics.DrawString.
See the notes you can find in the answer linked below for more informations on this matter.
StringFormat alignment in both horizontal and vertical dimensions.
A modified method that allows to draw strings of any length.
If the string is larger than the container, it will be wrapped, with the current settings.
Irrelevant: Brush and Pen are declared outside the Paint event, to allow their re-definition when required.
Different implementations of MeasureCharacterRanges here:
How to highlight wrapped text in a control
About Graphics.DrawString and TextRenderingHint.ClearTypeGridFit:
Drawing a Long String on to a Bitmap results in Drawing Issues
Font 48em:
Font 16em:
Font 9em:
Pen pen = new Pen(Color.LightGreen, 1);
Brush brush = new SolidBrush(Color.White);
string sourceDigits = "010011001";
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TextRenderingHint = TextRenderingHint.ClearTypeGridFit;
CharacterRange[] charRanges = new CharacterRange[sourceDigits.Length];
for (int chx = 0; chx < sourceDigits.Length; ++chx) {
charRanges[chx] = new CharacterRange(chx, 1);
}
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
sf.SetMeasurableCharacterRanges(charRanges);
Region[] regions = e.Graphics.MeasureCharacterRanges(sourceDigits, Font, e.ClipRectangle, sf);
for (int i = 0; i < regions.Length; i++) {
RectangleF rect = regions[i].GetBounds(e.Graphics);
e.Graphics.DrawRectangle(pen, rect.X, rect.Y, rect.Width, rect.Height);
e.Graphics.DrawString(char.ToString(sourceDigits[i]), Font, brush, rect, sf);
}
}
}
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 trying to get the height of a string. I've come across MeasureString but it's inaccurate for what I need it for - too small for what MS Word is showing (okay, this is probably another topic so if you can point me to the right resources...)
So, I found out about TextMetrics. I want to try it but I couldn't understand how to use it. Here is my sample code:
static public double MeasureGroup2()
{
TextMetrics txt = new TextMetrics();
double height;
height = txt.Height;
return height;
}
Where/how can I set the font and font size, and the string for it to return the values I need?
P.S. My end goal is to measure the height of a string the same way MS Word "measures" or renders it. I'm open to other solutions other than these two.
Additional info:
Code for MeasureString:
static public double MeasureGroup1(string TextFont, int FSize)
{
string Str = "Mgkfps";
Bitmap Bmp = new Bitmap(99,99);
Graphics DrawGraphics = Graphics.FromImage(Bmp);
GraphicsUnit Unit = GraphicsUnit.Point;
DrawGraphics.PageUnit = Unit;
DrawGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
StringFormat format = StringFormat.GenericTypographic;
// Set up string.
System.Drawing.Font stringFont = new System.Drawing.Font(TextFont, FSize, FontStyle.Regular, Unit);
// Measure string.
SizeF stringSize = new SizeF();
stringSize = DrawGraphics.MeasureString(Str, stringFont, 99, format);
DrawGraphics.Dispose();
format.Dispose();
stringFont.Dispose();
return stringSize.Height;
//return TextSize;
}
Notes:
Using Arial 11, and Arial 12, the output of this code needs to be multiplied by 1.0294 to be the same as how MS Word is showing. Physical measurements were made using Word's ruler, Photoshop, and a lot of ratio-and-proportion. (Maybe this is the problem?)
static public double MeasureGroup2(string TextFont, int FSize)
{
string Str = "Mgkfps";
double height;
Bitmap Bmp = new Bitmap(99, 99);
Graphics DrawGraphics = Graphics.FromImage(Bmp);
GraphicsUnit Unit = GraphicsUnit.Point;
DrawGraphics.PageUnit = Unit;
DrawGraphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
StringFormat format = StringFormat.GenericTypographic;
// Set up string.
System.Drawing.Font stringFont = new System.Drawing.Font(TextFont, FSize, FontStyle.Regular, Unit);
Rectangle Rect = new Rectangle(0, 0, 99, 99);
Size sz = new Size(99,99);
sz = TextRenderer.MeasureText(DrawGraphics, Str, stringFont, sz ,TextFormatFlags.NoPadding);
height = sz.Height;
return height;
}
First of all, thank you everyone for helping me out!
When using TextMetrics to get the dimensions of a text, refer to this post here:
https://www.cyotek.com/blog/retrieving-font-and-text-metrics-using-csharp
He has a sample project down at the bottom of the page.
As for where I was hoping to use it, which is getting the height of a text in MS Word:
I assumed that the text height and line spacing, when set to single, is the same. This is not the case. There is a small amount of space, the leading, that is not measured by MeasureString. That is why I was hoping to use the GetTextMetrics function since it returns a struct that includes the leading. Anyway, it appears to be used for something else.
Fortunately, and apparently, there is a method native to the .NET framework that returns the value I need which is FontFamily.GetLineSpacing.
LineHeight = stringFont.Size * FF.GetLineSpacing(FontStyle.Regular)/FF.GetEmHeight(FontStyle.Regular);
Reference:
https://answers.microsoft.com/en-us/office/forum/office_2013_release-word/what-reasons-make-different-line-spacing-between/ca1267f9-0cfe-4f26-8048-bbd7a6a46398?auth=1
https://learn.microsoft.com/en-us/dotnet/framework/winforms/advanced/how-to-obtain-font-metrics
I'm making a batch watermarking tool for myself and some others at work and using the following code allows me to annotate text on the bottom right of the image but I'm not able to make it annotate on the bottom left without manually adjusting the coordinates which differs for any given image. Also changing StringAlignment.Far to StringAlignment.Near etc doesn't do anything but possibly annotate the text outside the image somewhere that doesn't show up.
MSDN has some explanation but it is not helping me. Any help would be great I've been fighting this for some time now.
private void button1_Click(object sender, EventArgs e)
{
foreach (string images in Directory.GetFiles(textBox1.Text))
{
System.Drawing.Image img = System.Drawing.Image.FromFile(images);
Graphics gr = Graphics.FromImage(img);
Font font = new Font("Times New Roman", (float)25,
System.Drawing.FontStyle.Regular);
System.Drawing.Color color = System.Drawing.Color.Red;
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Far;
stringFormat.LineAlignment = StringAlignment.Far;
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.DrawString("WATERMARK GOES HERE"+ images, font,
new System.Drawing.SolidBrush(color),
new System.Drawing.Point(img.Width - 0, img.Height - 0),
stringFormat);
MemoryStream outputStream = new MemoryStream();
img.Save(images+"Stamped.jpg");
}
MessageBox.Show("done");
}
Name your controls. Don't use "button1", "textbox1", etc.
Use the "using" statement. Writing "System.Drawing.Point" and other fully qualified names just increases the size of your code and makes it harder to read.
You are creating a new instance of a SolidBrush class for each image you are watermarking. You should create the brush before the loop and just use it in the loop, then dispose of it afterwards.
Your declaration of a MemoryStream does nothing and is used nowhere.
As for the watermarking itself, you should decide if you want it to scale with image size, or to be a consistent size. Or you can have it have a maximum/minimum size. That's your preference.
private void watermark_btn_Click(object sender, EventArgs e)
{
string watermarkText = "ShowThisWatermark";
using (Font font = new Font("Times New Roman", (float)25, FontStyle.Regular))
using (SolidBrush brush = new SolidBrush(Color.Red))
foreach (string file in Directory.GetFiles(directory_txt.Text))
{
try
{
Bitmap b = new Bitmap(file);
using (Graphics g = Graphics.FromImage(b))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
SizeF measuredSize = g.MeasureString(watermarkText, font);
// Use this to watermark the bottom-left corner
g.DrawString(watermarkText, font, brush, 0, b.Height - measuredSize.Height);
// Use this to watermark the bottom-right corner
g.DrawString(watermarkText, font, brush, b.Width - measuredSize.Width, b.Height - measuredSize.Height);
}
b.Save(Path.GetFileNameWithoutExtension(file) + "_stamped" + Path.GetExtension(file));
}
catch
{
continue;
}
}
}
The try/catch is a lazy way of skipping files which aren't images. Since Directory.GetFiles returns all files in the directory, a non-image file would cause an exception. This could be done in a much neater fashion, but since that was not the nature of your question I kept it simple.