Character appears like white box when calling method DrawString - c#

I have a great question for you. I searched all the Google and MSDN and didn't find anything.
I'm trying to do a program that exports a font to single PNG images per character. I'm currently testing it with new Windows font, Segoe UI Symbol. Please note I know the font license terms and I won't distribute that font in the internet.
Well, the real problem is happening when I call the method DrawString, member of Graphics. I convert the unicode integer value to a char then to a string. I already tried to convert the integer to char with char.ConvertFromUtf32() and with Convert.ToChar().
The program is working good during 26 characters (starting from 57344 = 0xE000), the problem doesn't appear when I use numeric values until 57370. After this, there is not a single number that is not written with a white box char.
After some search, I found an overload to Font constructor, with the attribute gdiCharset and tried to use its value as 2, but nothing was happened.
I'm showing the source code for you below. Please, if there is someone who can help me, I will be happy.
UPDATE
When I use escape sequences (like "\uE1FF" instead of char conversion it works! But I don't know how to make escape sequences within a for loop.
Font segoe = new Font("Segoe UI Symbol", 800, FontStyle.Regular, GraphicsUnit.Pixel, 2);
Bitmap bmedidor = new Bitmap(1000, 1000, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics gmedidor = Graphics.FromImage(bmedidor);
// This line below doesn't matter, see the method DrawString
Size tamanho = gmedidor.MeasureString(char.ConvertFromUtf32(57344).ToString(), segoe).ToSize();
int[] reducoes = new int[6] {512, 256, 128, 64, 32, 16};
string caminho = "C:\\InoMetro";
for (int u = 57344; u < 57896; u++)
{
Bitmap caractere = new Bitmap(tamanho.Width, tamanho.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics criador = Graphics.FromImage(caractere);
criador.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
criador.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
criador.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
criador.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
// Here we have the problem
criador.DrawString(Convert.ToChar(u).ToString(), segoe, new SolidBrush(Color.Black), new PointF(0, 0));
for (int r = 0; r < reducoes.Length; r++)
{
int taR = reducoes[r];
Bitmap reducao = new Bitmap(taR, taR, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
Graphics redutor = Graphics.FromImage(reducao);
redutor.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
redutor.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
redutor.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
redutor.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
redutor.DrawImage(caractere, 0, 0, taR, taR);
reducao.Save(caminho + "\\" + taR.ToString() + "\\" + u.ToString() + ".png", System.Drawing.Imaging.ImageFormat.Png);
}
}

U+E000-U+F8FF (57344-63743 decimal) are Private Use Area characters.
Most fonts (including Segoe UI Symbol) don't provide glyphs for any code points in this range, so the typical fallback behavior is for the font renderer to display a white box () or a question mark in a black diamond (�) when asked to draw one of these code points.
Segoe UI Symbol on Windows 8 only defines glyphs for U+E000-U+E019, then provides no glyphs for U+E01A-U+E051; that's why white boxes are displayed for 57370 (0xE01A) through 57425 (0xE051).

Related

How to measure string width and specify language script

I've been successfully measuring strings with System.Drawing.Graphics with this (simplified) code snippet:
var cachedFont = cachedFont = new System.Drawing.Font(pfc.Families[0], line.FontSize, fontStyle, GraphicsUnit.Pixel);
format.SetMeasurableCharacterRanges(new[] { new CharacterRange(0, line.Text.Length) });
var regions = graphics.Value.MeasureCharacterRanges(line.Text, cachedFont, new Rectangle(0, 0, 10000, 0), format);
double result = 0;
for (var i = 0; i < regions.Length; i++)
{
var region = regions[i];
result += region.GetBounds(graphics.Value).Width;
region.Dispose();
}
return result;
However, I need to support OpenType layout which might have multiple language variations for a specific character, depending on the Language script (e.g. Arabic, Thai, CJK scripts, etc.)
It's safe to assume that the measurements are incorrect when a glyph substitution takes place when measuring strings with the system's default script.
Does System.Drawing.Graphics supports different scripts? How do I define them?

Find / replace colors in (i.e. recolor) a picture

I am trying to recreate the Recolor Picture dialog that Microsoft unfortunately discontinued in the transition from Office 2003 to 2007. This was very useful for replacing colors in a picture (see http://www.indezine.com/products/powerpoint/learn/picturesandvisuals/recolor-pictures-ppt2003.html for full description of dialog).
I am mostly interested in doing this for images in the metafile format (EMF or WMF), which tend to have fewer colors than other picture formats, in my experience. The picture below is an example of an enhanced metafile picture pasted from Excel into PowerPoint that appears to contain just 6 colors:
If I was able to use the legacy Office dialog pictured above, I would see my 6 colors on the left in the "Original" column, and I could easily change the blue font (and border) color to black. The problem here is that if I use GetPixel() to programmatically inventory the colors in the image, I get dozens of colors due to anti-aliasing of the fonts, and it isn't practical to show the user all these recoloring options (which would effectively require the user to manually recreate the proper anti-aliasing effect). The snippet of code below illustrates how I have tried to inventory the colors:
Dim listColors as New List(Of Color)
Dim shp as PowerPoint.Shape = [a metafile picture in PowerPoint]
Dim strTemp as String = Path.Combine(Environ("temp"), "temp_img.emf")
shp.Export(strTemp, PowerPoint.PpShapeFormat.ppShapeFormatEMF, 0, 0)
Using bmp As New Bitmap(strTemp)
For x As Integer = 0 To bmp.Width - 1
For y As Integer = 0 To bmp.Height - 1
listColors.Add(bmp.GetPixel(x, y))
Next
Next
End Using
I see that there is an optional Palette property for metafiles, which I thought could provide an answer, but an exception is thrown when I try to access it, so that was a dead end. I also see that there are headers for metafile images, but I cannot decipher what to do with them from the limited documentation on the Internet, and I am not even sure that that would get me to the right answer (i.e. 6 colors).
To summarize, part 1 of the question is how to inventory (i.e. identify) the 6 "core" colors in the image above, and part 2 is how to replace one of these 6 colors with another. VB.NET solutions are preferred, although I can probably translate C# code if not too complex.
If needed, you can download the EMF version of the image above at https://www.dropbox.com/s/n03ys3dh9pcd0xu/temp_img.emf?dl=0.
EDIT: To be clear, I am not interested in "computing" the six "core" colors in the image above. I believe, perhaps incorrectly, that these six colors are explicit properties of the image, and my first objective is to figure out how to access them. Indeed, if you simply ungroup the metafile picture twice in PowerPoint, you can loop through the resulting shapes to get these six colors. That would address part 1 of the question, although it seems a bit sloppy, works only for metafiles (which may be fine, actually), and I doubt that is how the legacy Recolor Picture dialog worked. To address part 2, I could regroup the metafile picture shapes after swapping colors but, again, that seems sloppy and modifies the picture in ways other than what is intended. So, how can I explicitly retrieve / modify / set these "core" colors in a [metafile] picture?
So I've had a quick stab at implementing one of my ideas to use a dictionary of colours and what not. The code isn't working properly just yet but I figured I show it here so that you can have a quick look into how it works and develop on it from there.
using (Bitmap bitmap = new Bitmap(#"InputPath"))
{
Dictionary<Color, List<Color>> colourDictionary = new Dictionary<Color, List<Color>>();
int nTolerance = 60;
int nBytesPerPixel = Bitmap.GetPixelFormatSize(bitmap.PixelFormat) / 8;
System.Drawing.Imaging.BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bitmap.PixelFormat);
try
{
int nByteCount = bitmapData.Stride * bitmap.Height;
byte[] _baPixels = new byte[nByteCount];
System.Runtime.InteropServices.Marshal.Copy(bitmapData.Scan0, _baPixels, 0, _baPixels.Length);
int _nStride = bitmapData.Stride;
for (int h = 0; h < bitmap.Height; h++)
{
int nCurrentLine = h * _nStride;
for (int w = 0; w < (bitmap.Width * nBytesPerPixel); w += nBytesPerPixel)
{
int nBlue = _baPixels[nCurrentLine + w];
int nGreen = _baPixels[nCurrentLine + w + 1];
int nRed = _baPixels[nCurrentLine + w + 2];
if (colourDictionary.Keys.Count > 0)
{
Color[] caNearbyColours = colourDictionary.Keys.Select(c => c)
.Where(c => (int)c.B <= (nBlue + nTolerance) && (int)c.B >= (nBlue - nTolerance)
&& (int)c.G <= (nGreen + nTolerance) && (int)c.G >= (nGreen - nTolerance)
&& (int)c.R <= (nRed + nTolerance) && (int)c.R >= (nRed - nTolerance)).ToArray();
if (caNearbyColours.Length > 0)
{
if (!colourDictionary[caNearbyColours.FirstOrDefault()].Any(c => c.R == nRed && c.G == nGreen && c.B == nBlue))
colourDictionary[caNearbyColours.FirstOrDefault()].Add(Color.FromArgb(255, nRed, nGreen, nBlue));
}
else
colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>());
}
else
colourDictionary.Add(Color.FromArgb(255, nRed, nGreen, nBlue), new List<Color>());
}
}
}
finally
{
bitmap.UnlockBits(bitmapData);
}
using (Bitmap colourBitmap = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat))
{
using (Graphics g = Graphics.FromImage(colourBitmap))
{
for (int h = 0; h < colourBitmap.Height; h++)
{
for (int w = 0; w < colourBitmap.Width; w++)
{
Color colour = bitmap.GetPixel(w, h);
if (!colourDictionary.ContainsKey(colour))
{
Color keyColour = colourDictionary.Keys.FirstOrDefault(k => colourDictionary[k].Any(v => v == colour));
colourBitmap.SetPixel(w, h, keyColour);
}
else
colourBitmap.SetPixel(w, h, colour);
}
}
}
colourBitmap.Save(#"OutputPath", System.Drawing.Imaging.ImageFormat.Png);
}
}
Notice how the top section uses Lockbits for better performance. This can easily be transferred onto the bottom section. Also note that the lock bits code is set to work for images with a bytes per pixel of 3 or 4.
Now onto how the code is attempting to work:
It starts off by looping over the initial image and finding colours. If the colour is found already it skips it, however if the colour is not in the dictionary it will add it and also if the colour has a similar colour in the dictionaries keys (will need changing to look at values too) it will add it to it's values.
This then loops through the pixels in the output image setting them according to the keys in the dictionary, thus creating a 'blocked' image.
Now as I said it isn't working just yet, so here are some improvements needed to be made:
As said above checking the colour to those in the values
Adding lock bits to the bottom code for better performance
Tweaking the nTolerance value for better results
Keep track of counts of colours and at the end of looping set the key to that with the largest count
And of course anything else that I have not thought of

How to estimate the length of a to-be-printed string?

I am trying to estimate the length of a printed string.
Font newFont = new Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Point);
label1.Font = newFont;
labe1.Text = "300028";
Graphics g = Graphics.FromHwnd(label1.Handle);
SizeF txtSize = g.MeasureString(label1.Text, label1.Font);
txtSize is {Width=60.3177, Height=19.875} points.
The actual width should be 60.3177 * 0.353 = 21.29 mm
where (1 point = 1/72 inch = 0.353 mm)
On paper (printed with Word) the width is about 13.5 mm
Why do we get such a big difference between the value computed with MeasureString (21.29 mm) and the real one (13.5 mm)?
I am aware of the limitations of the MeasureString method but I do not think this cannot justify such a big difference.
What I am missing?
Because you initialize your Graphics object wrong. You are using a display handle, not a print handle.
According to this post your Graphics object should be obtained using the PrinterSettings.CreateMeasurementGraphics method on a PrintDocument:
Graphics g = pd.PrinterSettings.CreateMeasurementGraphics();
Printing units are by default in hundredths of an inch, not 72ths of an inch.
As the other answer mentions, you need to use PrinterSettings.CreateMeasurementGraphics to get a graphics object that will be configured the right way to measure text for printing.

C# Pen.DashPattern

I want to draw a Line between 2 rows while using drag and drop. The function of this is simply visual, so that the user knows, where he is dropping the row. The line should look like the excel onces. Here my code:
Pen _marqueePen = new Pen(Color.Gray, 2);
float[] dashValues = {1f,1f};
_marqueePen.DashPattern = dashValues;
But this looks like that
I want to look it like that:
I'm WinForms and the C1 Flexgrid control.
You can use a Custom Pen like this:
using (Pen pen = new Pen(Color.Gray, 4f) )
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
pen.DashPattern = new float[] { 0.25F, 0.25F };
// now draw your stuff..
}
Note the doc on MSDN:
The elements in the dashArray array set the length of each dash
and space in the dash pattern. The first element sets the length of a dash,
the second element sets the length of a space, the third element sets
the length of a dash, and so on. Consequently, each element should be a
non-zero positive number.
The length of each dash and space in the dash pattern is the product
of the element value in the array and the width of the Pen.
You can pick any pen width and any dash&gap lengths as long as you keep their relation in mind.. So if you want the finest dashes, make sure they multiply to 1.0 pixels!
Here is the resulting line:
Some options:
You could use a PNG graphic that mimics that excel behaviour and then draw it on the control (you'll have to tile your image vertically).
Draw three lines with your code, with offset of y-axis & x-axis one pixel.
That looks to me more like a rectangle filed with HatchBrush having HatchStyle.Percent50 and height of 3.
You could try
Rectangle rect = new Rectangle(0, 0, 500, 3) //you will use the values here from your cursor but height will be 3
HatchBrush brush = new HatchBrush(HatchStyle.Percent50, Color.Black);
g.FillRectangle(brush, rect);

Why do my images seem to be in the format of Bgra instead of Argb?

So, I am very confused over a quick test that I just ran. I am doing some image processing in C#. Get/SetPixel() have proven to be too slow, so I am using LockBits to get at the raw data.
However, I seem to have hit a situation which I can't figure out. While scanning the image, it seems that each pixel is laid out as Bgra, that is, blue byte, green byte, red byte, and alpha, in that order. I was under the impression that they would be laid out in Argb order. here is a sample of the code that I am using.
BitmapData baseData =
m_baseImage.LockBits(new Rectangle(new Point(0, 0), m_baseImage.Size),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Bitmap test = new Bitmap(m_baseImage.Width, m_baseImage.Height);
byte* ptr = (byte*)baseData.Scan0;
for (int y = 0; y < m_baseImage.Height; ++y)
{
for (int x = 0; x < m_baseImage.Width; ++x)
{
// this works, image is copied correctly
Color c1 = Color.FromArgb(*(ptr + 3), *(ptr + 2), *(ptr + 1), *ptr);
// below does not work! Bytes are reversed.
//Color c1 = Color.FromArgb(*ptr, *(ptr + 1), *(ptr + 2), *(ptr + 3));
test.SetPixel(x, y, c1);
ptr += 4;
}
}
m_baseImage.UnlockBits(baseData);
pictureBox1.Image = m_baseImage;
pictureBox2.Image = test;
The first line which grabs the color of the base image works, the second does not. I am pretty sure that I am missing something very obvious here.
Not only are the colors reversed BGRA, but the rows are reversed as well - the bottom of the image is the first in memory. It's just the way Windows has always worked.
The little-endian explanation seems obvious, but I don't think it's the truth. If you look at the definition of COLORREF in the Windows API, you'll notice that Red is the low order byte and Blue is the higher order; if you stored this as a single integer value, it would be RGB0.
ARGB refers to the byte order in words fetched as words. If you fetch the bytes one at a time, you'll receive em low to hi as IBM PC's are little-endian

Categories

Resources