System.Drawing.Graphics.DrawString on top of another. Shadow effect problem - c#

What I am trying to achieve is - I want to draw a text watermark on an image using GDI. This text watermark should be white and semi-transparent, shadow - grey semi-transparent.
I tried this solution but obviously, when I draw a white semi-transparent text on top of a grey semi-transparent text, I see no white text at all, because grey pops up.
Would be very helpful if anyone could guide me through this, as I have no experience with graphics at all.
What I also tried, except of this:
drawing.DrawString(text, font, shadowBrush, x + shadowOffset.Width, y + shadowOffset.Height);
drawing.DrawString(text, font, textBrush, x, y);
Is, I tried to apply the outlining here. I thought that, maybe it is possible to apply the outlining at only one side so it would look like a shadow, but I failed on it either.
Here's the code I have so far:
var scalingRatio = GetWatermarkScalingRatio(image);
var scaledFontSize = (int)Math.Ceiling(scalingRatio * textProperties.Size);
var fontFamily = textProperties.Font ?? DefaultFont;
var alpha = (int)(255.0f * textProperties.Opacity / 100.0f);
var color = Color.FromArgb(alpha, Color.White);
using (var gr = Graphics.FromImage(image))
using (var font = new Font(fontFamily, scaledFontSize, DefaultFontStyle, GraphicsUnit.Pixel))
using (var semiTransparentBrush = new SolidBrush(color))
using (var shadowBrush = new SolidBrush(_shadowBrushColor))
{
var textSize = gr.MeasureString(textProperties.Message, font);
double wmWidth = textSize.Width;
double wmHeight = textSize.Height;
double angleRadian = (DefaultTextAngle % 360 / 180.0) * Math.PI;
var offset = GetTxtWatermarkOffset(textProperties.Position, image, wmWidth, wmHeight, angleRadian);
using var m = gr.Transform;
m.RotateAt(DefaultTextAngle, new PointF(offset.X, offset.Y), MatrixOrder.Append);
gr.Transform = m;
gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
gr.DrawString(textProperties.Message, font, shadowBrush, offset.X, offset.Y + DefaultShadowOffset);
gr.DrawString(textProperties.Message, font, semiTransparentBrush, offset.X, offset.Y);
gr.ResetTransform();
}

Related

How to fill text with 2 different color/texture

I draw text on top of a image using Path.Addstring and filling it with a color and it works perfectly. Now I would like to split(bisect) the text vertically and have 2 different colors or textures. For eg. the top half of the text with a solid brush and the bottom half with hatch brush. I'd like to know if this is possible and which way should I implement it.
Reference image created using paint.net software. I drew a line to split the text and filled the bottom part with a different texture.
*I don't want the line to be visible in the final output.
Possible.
Fill the path with the solid brush.
Get the rectangle that bounds the path through the GraphicsPath.GetBounds method.
Call the Graphics.SetClip method to exclude the top half of the rectangle.
Fill the path with a TextureBrush or HatchBrush.
An example that uses a HatchBrush to fill the second vertical half of the path.
private void SomeControl_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
var r = (sender as Control).ClientRectangle;
using (var gp = new GraphicsPath())
using (var sf = new StringFormat())
using (var fnt = new Font("Blackoak Std", 72))
using (var hbr = new HatchBrush(HatchStyle.Percent25, Color.White, Color.Red))
{
sf.Alignment = sf.LineAlignment = StringAlignment.Center;
gp.AddString("RED", fnt.FontFamily, (int)fnt.Style, GetEmFontSize(fnt), r, sf);
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillPath(Brushes.Red, gp);
var rf = gp.GetBounds();
rf.Height /= 2f;
g.SetClip(rf, CombineMode.Exclude);
g.FillPath(hbr, gp);
g.ResetClip();
g.SmoothingMode = SmoothingMode.None;
}
}
private float GetEmFontSize(Font fnt) =>
fnt.SizeInPoints * (fnt.FontFamily.GetCellAscent(fnt.Style) +
fnt.FontFamily.GetCellDescent(fnt.Style)) / fnt.FontFamily.GetEmHeight(fnt.Style);
See also the other HatchStyle values.

C# DrawString with StringFormatFlags: How to Flip Vertical Orientation?

I'm drawing strings on an image and saving it as a TIFF. I need to orient the text like this:
desired output
I'm using this code to create the string format:
formatFlags = (StringFormatFlags.NoClip | StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft);
And this is the output:
actual output
How can I simply flip the orientation around?
Update: Based on the comment, I tried this, but it is place my text way up in the corner instead of where it would be if I didn't add in that sample code. I commented out my original DrawString.
protected virtual void AddTextToBackground(Graphics backgroundGfx, FeatureLocation featureLocation, TextFeature textFeature, int equator) {
Font font = CreateFont(textFeature);
Color fontColor = ColorTranslator.FromHtml(textFeature.FontColor);
Brush textBrush = new SolidBrush(fontColor);
// Determine postion of text box
int xPos = featureLocation.XPos - TEXT_RECT_WIDTH / 2;
int adjustedEquatorOffset = CalculateTextEquatorOffset(featureLocation.EquatorOffset);
//int adjustedEquatorOffset = featureLocation.EquatorOffset;
int yPos = CalculateYPos(equator, adjustedEquatorOffset, TEXT_RECT_WIDTH);
// Rectangle is necessary to create centered text using StringFormat in the DrawString call
Rectangle rect = new Rectangle(xPos, yPos, TEXT_RECT_WIDTH, TEXT_RECT_WIDTH);
// Set up alignment
StringFormat stringFormat = new StringFormat {
Alignment = StringAlignment.Center,
LineAlignment = FindStringAlignment(featureLocation.EquatorOffset)
};
// Determine rotation and set format flags
stringFormat.FormatFlags = GetTextFormatFlags(featureLocation.DefaultRotation, textFeature.Rotation);
// Draw text
SizeF sz = backgroundGfx.VisibleClipBounds.Size;
backgroundGfx.TranslateTransform(sz.Width / 2, sz.Height / 2);
backgroundGfx.RotateTransform(45);
//backgroundGfx.DrawString(textFeature.Text, font, textBrush, rect, stringFormat);
backgroundGfx.DrawString(textFeature.Text, font, textBrush, -(sz.Width/2), -(sz.Height/2), stringFormat);
backgroundGfx.ResetTransform();
}

Arc graphic quality

Back here. Is there any way to improve the quality of the Arc?
I'm using e.Graphics.SmoothingMode = SmoothingMode.AntiAlias
This is the piece of code that creates the arc:
using (GraphicsPath gp = new GraphicsPath())
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
gp.Reset();
gp.AddPie(_OuterRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
gp.AddPie(_InnerRectangle, (float)_Properties.Origin, (float)_Properties.GaugeType);
gp.Reverse();
pArea.SetClip(gp);
using (Pen oPen = new Pen(this.ForeColor, 2f))
{
e.Graphics.DrawPath(oPen, gp);
}
e.Graphics.SetClip(ClientRectangle);
}
Thanks in advance.
EDIT:
I've did what LarsTech proposed and now the quality is perfect, but I'm not having the figure I need:
OuterRectangle: is the ClientRectangle area, that I'm manipulating it to make Width and Height the same lenght;
InnerRectangle: is 2/3ths of the ClientRectangle area, ergo, of the OuterRectangle;
Properties.Origin: is the angle where the arc starts. I have it in an enumerator as Cardinal Points, where North is 270, East is 0,
and so. In case of the figure, is SouthWest, 135 degrees;
Properties.GaugeType: is another enumerator that says if is Complete = 360, Half = 180, Quarter = 90, so with that I can determine the sweep angle. In case of the figure is ThreeQuarter, 270 degrees.
The problem:
When clipping a region of the current Graphics (Graphics.SetClip method), the resulting drawing loses quality, because the antialiasing effect generated by Graphics.SmoothingMode = SmoothingMode.AntiAlias is lost.
A possible solution is to avoid clipping the region defined by the GraphicsPath used to design the arcs (GraphicsPath.AddPie method); this, however, leaves the lines of the Pie visible, compromising the shape.
Another solution is to draw an ellipsis in the center of the arcs using the background color of the Canvas. Since the arcs are drawn using two rectangles, we can use the inner rectagle, inflate it (Rectangle.Inflate method) as needed (a fraction - Pen.Width / 2 - of the Pen size used for the ouline, usually).
This allows to delete the artifacts generated by the GraphicsPath shapes and to draw some other graphics content in the center of the shapes.
For example, using different Brushes:
LinearGradientBrush HatchBrush TextureBrush
Of course there are other methods to achieve the same result. We could draw the Arcs using the GraphicsPath.AddArc method, extract or calculate the first and last points of the Arcs and use them to draw two lines (GraphicsPath.AddLine) that will close the figures.
But, since we want to draw different graphics objects in the center of the arcs, these objects will cover the center area anyway.
How to use this code:
In a Form, add a TrackBar (named tbarSpeed, here)
Add a PictureBox (named Canvas), with Size (200, 200).
Wire up the TrackBar tbarSpeed_Scroll event and the Panel Canvas_Paint event.
using System.Drawing;
using System.Drawing.Drawing2D;
float GaugeValue = 88.0f;
float GaugeSweepAngle = 270.0f;
float GaugeStartAngle = 135.0F;
private void Canvas_Paint(object sender, PaintEventArgs e)
{
var canvas = sender as Control;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
var outerRectangle = new Rectangle(10, 10, 180, 180);
var innerRectangle = new Rectangle(30, 30, 140, 140);
var blendRectangle = new Rectangle(10, 10, 180, 160);
var innerCenter = new PointF(outerRectangle.Left + (outerRectangle.Width / 2),
outerRectangle.Top + (outerRectangle.Height / 2));
float gaugeLength = (outerRectangle.Width / 2) - 2;
using (var path = new GraphicsPath())
{
path.AddPie(outerRectangle, GaugeStartAngle, GaugeSweepAngle);
path.AddPie(innerRectangle, GaugeStartAngle, GaugeSweepAngle);
innerRectangle.Inflate(-1, -1);
using (var pen = new Pen(Color.White, 3f))
using (var backgroundbrush = new SolidBrush(canvas.BackColor))
using (var gradientBrush = new LinearGradientBrush(blendRectangle,
Color.Green, Color.Red, LinearGradientMode.ForwardDiagonal))
{
var blend = new Blend()
{
Factors = new[] { 0.0f, 0.0f, 0.1f, 0.3f, 0.7f, 1.0f },
Positions = new[] { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0f }
};
gradientBrush.Blend = blend;
e.Graphics.FillPath(gradientBrush, path);
e.Graphics.DrawPath(pen, path);
e.Graphics.FillEllipse(backgroundbrush, innerRectangle);
using (var format = new StringFormat())
{
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
innerRectangle.Location = new Point(innerRectangle.X, innerRectangle.Y + canvas.Font.Height);
e.Graphics.DrawString(GaugeValue.ToString() + "%", canvas.Font, Brushes.White, innerRectangle, format);
}
using (var mx = new Matrix())
{
mx.RotateAt(GaugeStartAngle + 90 + (GaugeValue * (GaugeSweepAngle / 100)), innerCenter);
e.Graphics.Transform = mx;
e.Graphics.DrawLine(pen, innerCenter, new PointF(innerCenter.X, innerCenter.Y - gaugeLength));
e.Graphics.ResetTransform();
}
}
}
}
private void tbarSpeed_Scroll(object sender, EventArgs e)
{
GaugeValue = tbarSpeed.Value;
Canvas.Invalidate();
}
Sample code on PasteBin

drawstring rotate long text 180

I use drawstring method to draw text inside rectangle ,.. i wanted to rotate the text 180 degree so i used this code
Rectangle displayRectangleh =
new Rectangle(new Point((int)posh.X, (int)posh.Y), new Size(rectwdh, recth));
StringFormat format1h = new StringFormat(StringFormatFlags.DirectionVertical);
format1h.LineAlignment = StringAlignment.Center;
format1h.Alignment = StringAlignment.Center;
b = new SolidBrush(Color.Black);
e.ChartGraphics.Graphics.TranslateTransform((float)rectwdh / 2 + posh.X, recth / 2 + posh.Y);
e.ChartGraphics.Graphics.RotateTransform(180);
e.ChartGraphics.Graphics.TranslateTransform(-((float)rectwdh / 2 + posh.X), -(recth / 2 + posh.Y));
Font boldFonth = new Font(FontFamily.GenericSansSerif, 16, FontStyle.Bold, GraphicsUnit.Pixel, 1, true);
e.ChartGraphics.Graphics.DrawRectangle(new Pen(Color.Black, 2), displayRectangleh);
e.ChartGraphics.Graphics.DrawString("This is rotated long text test ", boldFonth, b, (RectangleF)displayRectangleh, format1h);
e.ChartGraphics.Graphics.ResetTransform();
where posh.X and posh.Y (recatangle position) , rectwdh (rectangle width) and recth (rectangle height)
It works perfect for short text but for long text the new line will be above my old line see the next pic :
I even tried to add new line character \n but same result.
my question : how to rotate the text 180 inside rectangle and end up with proper alignment ??
Never having even used the StringFormat APIs before, I pounded at it randomly until I found this:
StringFormat format1h = new StringFormat(StringFormatFlags.DirectionVertical | StringFormatFlags.DirectionRightToLeft);

Center text output from Graphics.DrawString()

I'm using the .NETCF (Windows Mobile) Graphics class and the DrawString() method to render a single character to the screen.
The problem is that I can't seem to get it centred properly. No matter what I set for the Y coordinate of the location of the string render, it always comes out lower than that and the larger the text size the greater the Y offset.
For example, at text size 12, the offset is about 4, but at 32 the offset is about 10.
I want the character to vertically take up most of the rectangle it's being drawn in and be centred horizontally. Here's my basic code. this is referencing the user control it's being drawn in.
Graphics g = this.CreateGraphics();
float padx = ((float)this.Size.Width) * (0.05F);
float pady = ((float)this.Size.Height) * (0.05F);
float width = ((float)this.Size.Width) - 2 * padx;
float height = ((float)this.Size.Height) - 2 * pady;
float emSize = height;
g.DrawString(letter, new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular),
new SolidBrush(Color.Black), padx, pady);
Yes, I know there is the label control that I could use instead and set the centring with that, but I actually do need to do this manually with the Graphics class.
I'd like to add another vote for the StringFormat object.
You can use this simply to specify "center, center" and the text will be drawn centrally in the rectangle or points provided:
StringFormat format = new StringFormat();
format.LineAlignment = StringAlignment.Center;
format.Alignment = StringAlignment.Center;
However there is one issue with this in CF. If you use Center for both values then it turns TextWrapping off. No idea why this happens, it appears to be a bug with the CF.
To align a text use the following:
StringFormat sf = new StringFormat();
sf.LineAlignment = StringAlignment.Center;
sf.Alignment = StringAlignment.Center;
e.Graphics.DrawString("My String", this.Font, Brushes.Black, ClientRectangle, sf);
Please note that the text here is aligned in the given bounds. In this sample this is the ClientRectangle.
Through a combination of the suggestions I got, I came up with this:
private void DrawLetter()
{
Graphics g = this.CreateGraphics();
float width = ((float)this.ClientRectangle.Width);
float height = ((float)this.ClientRectangle.Width);
float emSize = height;
Font font = new Font(FontFamily.GenericSansSerif, emSize, FontStyle.Regular);
font = FindBestFitFont(g, letter.ToString(), font, this.ClientRectangle.Size);
SizeF size = g.MeasureString(letter.ToString(), font);
g.DrawString(letter, font, new SolidBrush(Color.Black), (width-size.Width)/2, 0);
}
private Font FindBestFitFont(Graphics g, String text, Font font, Size proposedSize)
{
// Compute actual size, shrink if needed
while (true)
{
SizeF size = g.MeasureString(text, font);
// It fits, back out
if (size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.Name, (float)(font.Size * .9), font.Style);
oldFont.Dispose();
}
}
So far, this works flawlessly.
The only thing I would change is to move the FindBestFitFont() call to the OnResize() event so that I'm not calling it every time I draw a letter. It only needs to be called when the control size changes. I just included it in the function for completeness.
To draw a centered text:
TextRenderer.DrawText(g, "my text", Font, Bounds, ForeColor, BackColor,
TextFormatFlags.HorizontalCenter |
TextFormatFlags.VerticalCenter |
TextFormatFlags.GlyphOverhangPadding);
Determining optimal font size to fill an area is a bit more difficult. One working soultion I found is trial-and-error: start with a big font, then repeatedly measure the string and shrink the font until it fits.
Font FindBestFitFont(Graphics g, String text, Font font,
Size proposedSize, TextFormatFlags flags)
{
// Compute actual size, shrink if needed
while (true)
{
Size size = TextRenderer.MeasureText(g, text, font, proposedSize, flags);
// It fits, back out
if ( size.Height <= proposedSize.Height &&
size.Width <= proposedSize.Width) { return font; }
// Try a smaller font (90% of old size)
Font oldFont = font;
font = new Font(font.FontFamily, (float)(font.Size * .9));
oldFont.Dispose();
}
}
You'd use this as:
Font bestFitFont = FindBestFitFont(g, text, someBigFont, sizeToFitIn, flags);
// Then do your drawing using the bestFitFont
// Don't forget to dispose the font (if/when needed)
Here's some code. This assumes you are doing this on a form, or a UserControl.
Graphics g = this.CreateGraphics();
SizeF size = g.MeasureString("string to measure");
int nLeft = Convert.ToInt32((this.ClientRectangle.Width / 2) - (size.Width / 2));
int nTop = Convert.ToInt32((this.ClientRectangle.Height / 2) - (size.Height / 2));
From your post, it sounds like the ClientRectangle part (as in, you're not using it) is what's giving you difficulty.
You can use an instance of the StringFormat object passed into the DrawString method to center the text.
See Graphics.DrawString Method and StringFormat Class.

Categories