I need to convert a text containing Unicode chars into a Bitmap that could have a transparent background as well.
I found and tried different posts like this, this, or this, but no one seems to work for me.
I found also this post that suggests using TextRenderer.DrawText() method instead of System.Drawing.Graphics.DrawString() but the final result is not good anyway.
That's a code snippet:
private static Bitmap ImageFromText(string text, Font font, Color textColor, Color fillColor, System.Drawing.Graphics graphics)
{
Bitmap bmpOut;
SizeF sz = TextRenderer.MeasureText(graphics, text, font);
//SizeF sz = g.MeasureString(text, font);
if (sz.IsEmpty)
sz = new SizeF(1, 1);
bmpOut = new Bitmap((int) Math.Ceiling(sz.Width), (int) Math.Ceiling(sz.Height), PixelFormat.Format32bppArgb);
using (System.Drawing.Graphics gBmp = System.Drawing.Graphics.FromImage(bmpOut))
{
gBmp.Clear(fillColor);
gBmp.SmoothingMode = SmoothingMode.HighQuality;
gBmp.InterpolationMode = InterpolationMode.HighQualityBilinear;
gBmp.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
//gBmp.DrawString(text, font, brFore, 0, 0); // Unicode chars are not supported
TextRenderer.DrawText(gBmp, text, font, new Point(0, 0), textColor);
}
return bmpOut;
}
Here is my test code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
string testString = "Unicode Title \uD83E\uDC25";
var font = new Font(new FontFamily("Microsoft Sans Serif"), 12.375f);
// e.Graphics.DrawString(testString, new Font(font.FontFamily, 20), new SolidBrush(Color.Black), 0, 0);
TextRenderer.DrawText(e.Graphics, testString, new Font ( font.FontFamily, 20 ), new Point(0, 50), Color.Black);
Bitmap bitmap = ImageFromText(testString, new Font(font.FontFamily, 20), Color.Black, this.BackColor, e.Graphics);
e.Graphics.DrawImage(bitmap, new Point(0, 100));
//Clipboard.SetImage(bitmap);
}
}
And this is the poor result:
How can I improve the bitmap result?
EDIT
Short recap:
I can't use System.Drawing.Graphics.DrawString() method because it does not support Unicode chars
I can't use TextRenderer.DrawText() method because TextRenderer class uses GDI and it isn't alpha aware, so it does not support a transparent background.
Is there some other way to reach my goal?
Related
I'm trying to draw a box around a label which has been aligned using StringAlignment.Far for example. I can find the Size of text using g.MeasureString but I can't find a method to translate the origin point such that I can find a Rectangle which bounds the text.
Say I have a Point origin at which to draw from, and a StringFormat format with what alignment I wish my string to have. I can find the Size of the string using g.MeasureString(text, font). How do I translate this Point/Size pair into a rectangle which overlaps the g.DrawString(text, font, brush, origin, format) call.
It's difficult to convert c to managed code. You should use .Net code directly if it's available.
For MeasureString, see link Graphics.MeasureString Method
Example:
using System.Diagnostics;
...
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
Font font = new Font("Arial", 16);
SizeF sz = g.MeasureString("Text...", font);
Rectangle rc = new Rectangle(0,0, (int)sz.Width, (int)sz.Height);
Debug.WriteLine(rc.Width.ToString());
Debug.WriteLine(rc.Height.ToString());
//change top/left origin of rectangle
rc.X = 10;
rc.Y = 20;
}
You just need the width and height of text. You can change left/top corner of rectangle.
By the way, the C method gives a rectangle with top/left coordinates at zero, so it's the same information as Size
Edit
This will fit text with word-break flag in to a rectangle whose width is 100. The height of the rectangle is not known. TextRenderer.MeasureText will tell us the height of the rectangle. Top/left corner can be changed, alignment can be changed.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Font font = new Font("Arial", 10);
string text = "I'm trying to draw a box around a label which has been aligned.";
Size layout = new Size(100, 0);
Size sz = TextRenderer.MeasureText(e.Graphics, text, font, layout,
TextFormatFlags.WordBreak);
Rectangle rc = new Rectangle(new Point(0,0), sz);
e.Graphics.DrawRectangle(Pens.Black, rc);
TextRenderer.DrawText(e.Graphics, text, font, rc,
SystemColors.ControlText, SystemColors.Control, TextFormatFlags.WordBreak);
}
My way is use SetMeasurableCharacterRanges to obtain the region of the whole text.
Consider into OnPaint:
protected override void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
Font font = new Font("Arial", 16);
string text = "Border of this text";
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
RectangleF area = new RectangleF(0, 0, 246, 84);
sf.SetMeasurableCharacterRanges(new CharacterRange[] { new CharacterRange(0, text.Length) });
Region[] r = g.MeasureCharacterRanges(text, font, area, sf);
Rectangle rf = new Rectangle((int)r[0].GetBounds(g).X, (int)r[0].GetBounds(g).Y, (int)r[0].GetBounds(g).Width, (int)r[0].GetBounds(g).Height);
g.DrawString(text, font, Brushes.Black, area, sf);
g.DrawRectangle(new Pen(Color.Red, 1), rf);
}
On a single bitmap I need to display graphs and text values. So what I did is create a bitmap with points and creating a another bitmap with the text and place on the large bitmap.
I tried using the brush to write the text, but I am not able to see the underlying graphics even though trasparency is set.
Instead I thought to set the transparency for the text bitmap, but the bitmap which I have created are 24 bit rgb. So can we set the transparency for the 24 bit map.
Bitmap textBitmap = null;
textBitmap = new Bitmap(10, 10, PixelFormat.Format24bppRgb);
using (Graphics memoryGrahics =
Graphics.FromImage(textBitmap))
{
memoryGrahics.FillRectangle(Brushes.Black, new Rectangle(0, 0, 100, 100));
memoryGrahics.DrawString(result, f, Brushes.White, x, y);
}
//placing the text bitmap on the graphbitmap
using (Graphics g = Graphics.FromImage(GraphBitmap))
{
g.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;
textBitmap.MakeTransparent();
g.DrawImage(textBitmap, 0, 0);
return GraphBitmap;
}
well.. it seems like you are using 2 different Graphical objects... although 1 Graphics objects with 1 bitmap can handle multiple layouts of custom drawings, like so:
int width = 800, height = 600;
var bit = new Bitmap(width, height);
var g = Graphics.FromImage(bit);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
var area = new Rectangle(0, 0, width, height);
g.FillRectangle(new LinearGradientBrush(area, Color.PaleGoldenrod, Color.OrangeRed, 45), area);
g.DrawImage(Image.FromFile(#"your image"), new Point(10, 10));
g.DrawString("sample", new System.Drawing.Font("Tahoma", 56), new SolidBrush(Color.Black), new PointF(50, 50));
pictureBox1.Image = bit;
note that g.DrawImage method can be used to load other bitmaps as well
Building a little paint program and am trying to incorporate the concept of layers.
I'm using a PictureBox control to display the image, and getting the Graphics object from the image being displayed by the PictureBox and drawing to that.
My problem is I'm trying to figure out how to draw to a new Graphics object that is overlayed on top of the picture box, and be able to get the newly drawn image without the original image absorbed into the graphic.
If I do something like:
Graphics gr = Graphics.FromImage(myPictureBox.image);
gr.DrawRectangle(blah blah)
...I am editing the original image in the picture box. I want a way to only capture the new stuff being drawn as a separate image, but still have it displayed as an overlay over top of what was already there.
Anyone able to point me in the right direction? Thanks!
I would reckon to use the transparent control and do some modification so it can be used as image layers:
http://www.codeproject.com/Articles/26878/Making-Transparent-Controls-No-Flickering
Probably something like this (make any modification as necessary).
class LayerControl : UserControl
{
private Image image;
private Graphics graphics;
public LayerControl(int width, int height)
{
this.Width = width;
this.Height = height;
image = new Bitmap(width, height);
graphics = Graphics.FromImage(image);
// Set style for control
SetStyle(ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint |
ControlStyles.UserPaint, true);
}
// this function will draw your image
protected override void OnPaint(PaintEventArgs e)
{
var bitMap = new Bitmap(image);
// by default the background color for bitmap is white
// you can modify this to follow your image background
// or create a new Property so it can dynamically assigned
bitMap.MakeTransparent(Color.White);
image = bitMap;
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.PixelOffsetMode = PixelOffsetMode.HighQuality;
g.CompositingQuality = CompositingQuality.GammaCorrected;
float[][] mtxItens = {
new float[] {1,0,0,0,0},
new float[] {0,1,0,0,0},
new float[] {0,0,1,0,0},
new float[] {0,0,0,1,0},
new float[] {0,0,0,0,1}};
ColorMatrix colorMatrix = new ColorMatrix(mtxItens);
ImageAttributes imgAtb = new ImageAttributes();
imgAtb.SetColorMatrix(
colorMatrix,
ColorMatrixFlag.Default,
ColorAdjustType.Bitmap);
g.DrawImage(image,
ClientRectangle,
0.0f,
0.0f,
image.Width,
image.Height,
GraphicsUnit.Pixel,
imgAtb);
}
// this function will grab the background image to the control it self
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
Graphics g = e.Graphics;
if (Parent != null)
{
BackColor = Color.Transparent;
int index = Parent.Controls.GetChildIndex(this);
for (int i = Parent.Controls.Count - 1; i > index; i--)
{
Control c = Parent.Controls[i];
if (c.Bounds.IntersectsWith(Bounds) && c.Visible)
{
Bitmap bmp = new Bitmap(c.Width, c.Height, g);
c.DrawToBitmap(bmp, c.ClientRectangle);
g.TranslateTransform(c.Left - Left, c.Top - Top);
g.DrawImageUnscaled(bmp, Point.Empty);
g.TranslateTransform(Left - c.Left, Top - c.Top);
bmp.Dispose();
}
}
}
else
{
g.Clear(Parent.BackColor);
g.FillRectangle(new SolidBrush(Color.FromArgb(255, Color.Transparent)), this.ClientRectangle);
}
}
// simple drawing circle function
public void DrawCircles()
{
using (Brush b = new SolidBrush(Color.Red))
{
using (Pen p = new Pen(Color.Green, 3))
{
this.graphics.DrawEllipse(p, 25, 25, 20, 20);
}
}
}
// simple drawing rectable function
public void DrawRectangle()
{
using (Brush b = new SolidBrush(Color.Red))
{
using (Pen p = new Pen(Color.Red, 3))
{
this.graphics.DrawRectangle(p, 50, 50, 40, 40);
}
}
}
// Layer control image property
public Image Image
{
get
{
return image;
}
set
{
image = value;
// this will make the control to be redrawn
this.Invalidate();
}
}
}
Example how to use it:
LayerControl lc = new LayerControl(100, 100);
lc.Location = new Point(0, 0);
lc.DrawRectangle();
LayerControl lc2 = new LayerControl(100, 100);
lc2.Location = new Point(0, 0);
lc2.DrawCircles();
LayerControl lc3 = new LayerControl(100, 100);
lc3.Location = new Point(0, 0);
lc3.Image = new Bitmap(#"<Image Path>");
// adding control
this.Controls.Add(dc);
this.Controls.Add(dc2);
this.Controls.Add(dc3);
With this method you can have multiple layers that can put overlapping each other (due to the transparency feature it has).
If you want to add it in top of your PictureBox make sure to re-order the control. The Layer Control should be added before your PictureBox control.
// adding control
this.Controls.Clear();
this.Controls.Add(dc);
this.Controls.Add(dc2);
this.Controls.Add(dc3);
this.Controls.Add(PictureBox1);
Hopefully it help.
example code which working fine - take dummy image and layered the original image with custom text
public void LayerImage(System.Drawing.Image Current, int LayerOpacity)
{
Bitmap bitmap = new Bitmap(Current);
int h = bitmap.Height;
int w = bitmap.Width;
Bitmap backg = new Bitmap(w, h + 20);
Graphics g = null;
try
{
g = Graphics.FromImage(backg);
g.Clear(Color.White);
Font font = new Font("Arial", 12, FontStyle.Bold, GraphicsUnit.Pixel);
RectangleF rectf = new RectangleF(70, 90, 90, 50);
Color color = Color.FromArgb(255, 128, 128, 128);
Point atpoint = new Point(backg.Width / 2, backg.Height - 10);
SolidBrush brush = new SolidBrush(color);
StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Center;
g.DrawString("BRAND AMBASSADOR", font, brush, atpoint, sf);
g.Dispose();
MemoryStream m = new MemoryStream();
backg.Save(m, System.Drawing.Imaging.ImageFormat.Jpeg);
}
catch { }
Color pixel = new Color();
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
pixel = bitmap.GetPixel(x, y);
backg.SetPixel(x, y, Color.FromArgb(LayerOpacity, pixel));
}
}
MemoryStream m1 = new MemoryStream();
backg.Save(m1, System.Drawing.Imaging.ImageFormat.Jpeg);
m1.WriteTo(Response.OutputStream);
m1.Dispose();
base.Dispose();
}
Got it working, perhaps I wasn't clear enough in my original question.
Essentially what I ended up doing was storing each layer as a separate Image object, then just hooking into the OnPaint method of my control and manually drawing the graphics in order, instead of just drawing to PictureBox.Image. Works like a charm!
The graphics capabilities of .NET drawing libraries are simple. Their main purpose is direct drawing of GUI. If you want to have layering, alpha transparency or advanced filters, then you should either use 3rd party library or roll your own drawing code.
Is it possible to apply a gradient to label text?
Right now I am taking over a controls OnPaint and drawing the string of text that I want; however, this is to specific. I really want to make it so that the label itself gets applied the gradient colors I want. So in turn each character would have the gradient specified as the text has changed.
So instead of using the ForeColor I would apply a LinearGradientBrush. I am using WinForms at the moment.
EDIT 1
Here is the code that I am currently using. However, this only applies the gradient to all of the characters. I would like to change it so that each character in the string is applied.
// Draw the formatted text string to the DrawingContext of the control.
Font font = new Font("BankGothic Md BT", 48f, FontStyle.Bold);
LinearGradientBrush brush = new LinearGradientBrush(label1.Location, new Point(label1.Width, label1.Height), Color.Goldenrod, Color.Black);
e.Graphics.DrawString(label1.Text, font, brush, 0,0);
Edit 2
Here is what I did. I just extended the Label class and inherited OnPaint.
public partial class LabelEx : Label {
public LabelEx() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
// Draw the formatted text string to the DrawingContext of the control.
//base.OnPaint(e);
Font font = new Font("Tahoma", 48f, FontStyle.Bold);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, Width, Height + 5), Color.Gold, Color.Black, LinearGradientMode.Vertical);
e.Graphics.DrawString(Text, font, brush, 0, 0);
}
}
Which gives me a nice gradient text label.
Thanks!
Here is what I did. I just extended the Label class and inherited OnPaint.
public partial class LabelEx : Label {
public LabelEx() {
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e) {
// Draw the formatted text string to the DrawingContext of the control.
//base.OnPaint(e);
Font font = new Font("Tahoma", 48f, FontStyle.Bold);
LinearGradientBrush brush = new LinearGradientBrush(new Rectangle(0, 0, Width, Height + 5), Color.Gold, Color.Black, LinearGradientMode.Vertical);
e.Graphics.DrawString(Text, font, brush, 0, 0);
}
}
I'm creating a custom control, part of which is using the Graphics class to draw text to the form. Currently I'm using the following code to display it:
private float _lineHeight { get { return this.Font.Size + 5; } }
private void Control_Paint(object sender, PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Brush b = new SolidBrush(Colors[7]);
g.DrawString("Hello World!", this.Font, b, 0, 2);
g.DrawString("This has been a test of the emergency drawing system!",
this.Font, b, 0, 2 + _lineHeight);
}
I'm currently using fixedwidth fonts, and I'd like to know how wide the font will display, but there doesn't appear to be any properties for this sort of information. Is there some way of obtaining it? I want it so I can wrap lines properly when displayed.
Yes, you can use MeasureString from the Graphics class
This method returns a SizeF structure that represents
the size, in the units specified by
the PageUnit property, of the string
specified by the text parameter as
drawn with the font parameter.
private void MeasureStringMin(PaintEventArgs e)
{
// Set up string.
string measureString = "Measure String";
Font stringFont = new Font("Arial", 16);
// Measure string.
SizeF stringSize = new SizeF();
stringSize = e.Graphics.MeasureString(measureString, stringFont);
// Draw rectangle representing size of string.
e.Graphics.DrawRectangle(new Pen(Color.Red, 1), 0.0F, 0.0F, stringSize.Width, stringSize.Height);
// Draw string to screen.
e.Graphics.DrawString(measureString, stringFont, Brushes.Black, new PointF(0, 0));
}