Calculate x offset on FormattedText instance with TextAlignment.Center - c#

I've a class which inherits from Shape and also needs to precisely draw some multiline text within the OnRender(DrawingContect drawingContext) method.
I can fill a rectangle which exactly fills the rectangular size of the text:
And the relevant simplified code snippet:
protected override void OnRender(DrawingContext drawingContext)
{
...
var formattedText = new FormattedText(
Text,
CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.TextInfo.IsRightToLeft
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight,
TypeFace,
FontSize,
TextBrush
);
formattedText.TextAlignment = TextAlignment.Left;
formattedText.Trimming = TextTrimming.CharacterEllipsis;
formattedText.SetFontWeight(FontWeight);
formattedText.MaxTextWidth = Width;
formattedText.MaxTextHeight = Height;
...
DrawShape(
drawingContext,
new List<Point>
{
new Point(0, 0),
new Point(formattedText.Width, 0),
new Point(formattedText.Width, formattedText.Height),
new Point(0, formattedText.Height)
},
brush,
pen
);
drawingContext.DrawText(formattedText, new Point(0, 0));
...
}
void DrawShape(DrawingContext dc, List<Point> points, Brush fill, Pen pen)
{
var streamGeometry = new StreamGeometry();
using (var ctx = streamGeometry.Open())
{
ctx.BeginFigure(points[0], true, true);
foreach (var point in points)
{
ctx.LineTo(point, true, true);
}
}
streamGeometry.Freeze();
dc.DrawGeometry(fill, pen, streamGeometry);
}
My problem is when I try to use the same code above but with TextAlignment.Center I'm unable to correctly position that rectangle behind the text:
How can I get the x offset to correctly draw that rectangle?
This isn't what I'm trying to achieve, but is a simplified example which highlights the issue.

There are two properties, OverhandLeading and OverhandTrailing which provide this information:
I overlooked these originally as they weren't giving me the expected behaviour, but turns out I had some incorrect logic to calculate my rotation centre point on my real shape.
So, for the example above, the points would be:
new Point(formattedText.OverhangLeading, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, formattedText.Height),
new Point(formattedText.OverhangLeading, formattedText.Height)

Related

How to make a diagonal line divide two panels in C# WinForms?

this is my form and I'm trying to make it so the diagonal line in the middle divides the left and the right parts of my form evenly. The line is drawn in a separate panel, with a script instructing it where to position the line (also, the background of this panel was set to transparent). The left part of my form is another panel as well as the black part in the upper right corner. The login elements (the email and password fields, the register and sign-in buttons, etc) are attached to the form itself.
Image of the form when I run it
Image of the form in the editor
I tried adding other lines in the same place in the other panels but it still didn't look as I wanted it to because those panels were still overlapping despite the transparent background of the original panel with the line.
I don't know what to do, so help would be much appreciated ;)
If you want to divide the left and the right parts of the form evenly with the diagonal line in the middle, you can refer to the following code:
private void panel3_Paint(object sender, PaintEventArgs e)
{
base.OnPaint(e);
using (Graphics g = e.Graphics)
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
var p = new Pen(Color.Red, 3);
var point1 = new Point(0, 0);
var point2 = new Point(panel3.Width, panel3.Height);
g.DrawLine(p, point1, point2);
}
}
You can change the color and size of the diagonal line in the code.
I would put this into an own user control. Afterwards you could set everything through ForeColor, BackgroundColor, Thickness and RightToLeft:
public class DiagonalSeparator : UserControl
{
private int thickness = 3;
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (Graphics g = e.Graphics)
{
g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
var p = new Pen(ForeColor, thickness);
var point1 = RightToLeft == RightToLeft.No ? new Point(0, 0) : new Point(Width, 0);
var point2 = RightToLeft == RightToLeft.No ? new Point(Width, Height) : new Point(0, Height);
g.DrawLine(p, point1, point2);
}
}
[DefaultValue(3)]
[Description("The thickness of the drawn line"), Category("Appearance")]
[Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public int Thickness
{
get
{
return thickness;
}
set
{
thickness = value;
Invalidate();
}
}
}
This control can then be used as any other control within the designer and you can check if the visualization works as expected.

WinForms: Measure Text With No Padding

In a WinForms app, I am trying to measure the size of some text I want to draw with no padding. Here's the closest I've gotten...
protected override void OnPaint(PaintEventArgs e) {
DrawIt(e.Graphics);
}
private void DrawIt(Graphics graphics) {
var text = "123";
var font = new Font("Arial", 32);
var proposedSize = new Size(int.MaxValue, int.MaxValue);
var measuredSize = TextRenderer.MeasureText(graphics, text, font, proposedSize, TextFormatFlags.NoPadding);
var rect = new Rectangle(100, 100, measuredSize.Width, measuredSize.Height);
graphics.DrawRectangle(Pens.Blue, rect);
TextRenderer.DrawText(graphics, text, font, rect, Color.Black, TextFormatFlags.NoPadding);
}
... but as you can see from the results ...
... there is still a considerable amount of padding, particularly on the top and bottom. Is there any way to measure the actual bounds of the drawn characters (with something really awful like printing to an image and then looking for painted pixels)?
Thanks in advance.
(I've marked this answer as "the" answer just so people know it was answered, but #TaW actually provided the solution -- see his link above.)
#TaW - That was the trick. I'm still struggling to get the text to go where I want it to, but I'm over the hump. Here's the code I ended out with...
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
DrawIt(e.Graphics);
}
private void DrawIt(Graphics graphics) {
var text = "123";
var font = new Font("Arial", 40);
// Build a path containing the text in the desired font, and get its bounds.
GraphicsPath path = new GraphicsPath();
path.AddString(text, font.FontFamily, (int)font.Style, font.SizeInPoints, new Point(0, 0), StringFormat.GenericDefault);
var bounds = path.GetBounds();
// Move it where I want it.
var xlate = new Matrix();
xlate.Translate(100, 100);
path.Transform(xlate);
// Draw the path (and a bounding rectangle).
graphics.DrawPath(Pens.Black, path);
bounds = path.GetBounds();
graphics.DrawRectangle(Pens.Blue, bounds.Left, bounds.Top, bounds.Width, bounds.Height);
}
... and here is the result (notice the nice, tight bounding box) ...
Have you tried
Graphics.MeasureString("myString", myFont, int.MaxValue, StringFormat.GenericTypographic)

Use croppic to rotate images

I am using croppic to crop images. Is there any possibility to extend its functionality to rotate the image, before sending it to crop. Something like this
var angle = 0;
$('#button').on('click', function() {
angle += 90;
$("#image").rotate(angle);
});
Here is the demo
I can manage to to rotate the image, but the cropped image is not in rotated position. Any idea?
Update:
Now the image is getting rotated. I have managed to keep rotated image by the same handler that uses crop method. But the cropped image in rotated position occurs only when the rotated image is in 180 rotation. When its in the 90/270, the image, obviously don't fit in the bounding box (if you have seen croppic) and I get a MemoryOutOfBound exception.
Here is the code:
public static Bitmap RotateImage(Bitmap bmpSrc, float theta)
{
Matrix mRotate = new Matrix();
mRotate.Translate(bmpSrc.Width / -2, bmpSrc.Height / -2, MatrixOrder.Append);
mRotate.RotateAt(theta, new System.Drawing.Point(0, 0), MatrixOrder.Append);
using (GraphicsPath gp = new GraphicsPath())
{ // transform image points by rotation matrix
gp.AddPolygon(new System.Drawing.Point[] { new System.Drawing.Point(0, 0), new System.Drawing.Point(bmpSrc.Width, 0), new System.Drawing.Point(0, bmpSrc.Height) });
gp.Transform(mRotate);
System.Drawing.PointF[] pts = gp.PathPoints;
// create destination bitmap sized to contain rotated source image
Rectangle bbox = boundingBox(bmpSrc, mRotate);
Bitmap bmpDest = new Bitmap(bbox.Width, bbox.Height);
using (Graphics gDest = Graphics.FromImage(bmpDest))
{ // draw source into dest
Matrix mDest = new Matrix();
mDest.Translate(bmpDest.Width / 2, bmpDest.Height / 2, MatrixOrder.Append);
gDest.Transform = mDest;
gDest.DrawImage(bmpSrc, pts);
return bmpDest;
}
}
}
private static Rectangle boundingBox(Image img, Matrix matrix)
{
GraphicsUnit gu = new GraphicsUnit();
Rectangle rImg = Rectangle.Round(img.GetBounds(ref gu));
// Transform the four points of the image, to get the resized bounding box.
System.Drawing.Point topLeft = new System.Drawing.Point(rImg.Left, rImg.Top);
System.Drawing.Point topRight = new System.Drawing.Point(rImg.Right, rImg.Top);
System.Drawing.Point bottomRight = new System.Drawing.Point(rImg.Right, rImg.Bottom);
System.Drawing.Point bottomLeft = new System.Drawing.Point(rImg.Left, rImg.Bottom);
System.Drawing.Point[] points = new System.Drawing.Point[] { topLeft, topRight, bottomRight, bottomLeft };
GraphicsPath gp = new GraphicsPath(points,new byte[] { (byte)PathPointType.Start,
(byte)PathPointType.Line, (byte)PathPointType.Line,
(byte)PathPointType.Line });
gp.Transform(matrix);
return Rectangle.Round(gp.GetBounds());
}
I think main issue is in the bounding box.
using (Bitmap rotate = new Bitmap(RotateImage(bmp, rotateAngle)))
{
using (Bitmap cloneRotate = (Bitmap)rotate.Clone()) //getting error here
{
//some stuffs
cloneRotate.save(mycroppedimageurl);
}
}

Rendering only part of a Visual

I have a large DrawingVisual that is added to a Canvas and I want to get the color value of a pixel when I click the mouse. I tried rendering the entire Visaul to a RenderTargetBitmap but got an "Out of memory" exception. then using an example I found here How to render (bitmap) only part of a Visual?
I tried rendering only part of the Visual but I dont get the correct pixel value. currently my code looks like this
private void OnMouseDown(object sender, System.Windows.Input.MouseEventArgs e)
{
// Retreive the coordinates of the mouse button event.
Point pt = e.GetPosition((UIElement)sender);
VisualBrush vb = new VisualBrush(myDrawingVisual);
vb.ViewboxUnits = BrushMappingMode.Absolute;
// create rect from the position in the canvas - starting point of the DrawingVisual
vb.Viewbox = new Rect(point.X - myDrawingVisual.ContentBounds.Left,point.Y - myDrawingVisual.ContentBounds.Top, 1, 1);
vb.ViewportUnits = BrushMappingMode.Absolute;
vb.Viewport = new Rect(point, new Size(1, 1));
System.Windows.Shapes.Rectangle r = new System.Windows.Shapes.Rectangle();
r.Width = 1;
r.Height = 1;
r.Fill = vb;
// Use RenderTargetBitmap to get the visual, in case the image has been transformed.
var renderTargetBitmap = new RenderTargetBitmap(1,
1,
96, 96, PixelFormats.Default);
renderTargetBitmap.Render(r);
var pixels = new byte[4];
renderTargetBitmap.CopyPixels(pixels, 4, 0);
}
You haven't laid out the Rectangle. For details refer to the Layout article on MSDN.
Add the following lines:
r.Measure(new Size(1, 1));
r.Arrange(new Rect(0, 0, 1, 1));
var renderTargetBitmap = ...
renderTargetBitmap.Render(r);

StreamGeometry not getting rasterized when the Pen has DashStyle other than Null

I was trying to create a brush that draws a geometry. Everything worked fine until I tried to add Dashing to the shape.
I found that when I create the geometry using Geometry.Parse, the dashed line appears correctly, but when I create it directly using StreamGeometryContext, nothing gets rendered.
This is the code I'm using:
private void RenderGeometryAndSetAsBackground()
{
Point startPoint = new Point(3505961.52400725, 3281436.57325874);
Point[] points = new Point[] {
new Point(3503831.75515445,3278705.9649394),
new Point(3503905.74802898,3278449.37713916),
new Point(3507242.57331039,3276518.41148474),
new Point(3507700.6914325,3276536.23547958),
new Point(3510146.73449577,3277964.12812859),
new Point(3509498.96473447,3278678.60178448),
new Point(3507412.1889951,3277215.64022219),
new Point(3504326.22698001,3278682.85514017),
new Point(3506053.34789057,3281390.66371786)};
string geom = "M3505961.52400725,3281436.57325874L3503831.75515445,3278705.9649394 3503905.74802898,3278449.37713916 3507242.57331039,3276518.41148474 3507700.6914325,3276536.23547958 3510146.73449577,3277964.12812859 3509498.96473447,3278678.60178448 3507412.1889951,3277215.64022219 3504326.22698001,3278682.85514017 3506053.34789057,3281390.66371786";
//Geometry geometry = StreamGeometry.Parse(geom);
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext sgc = geometry.Open())
{
sgc.BeginFigure(startPoint, false, true);
foreach (Point p in points)
{
sgc.LineTo(p, true, true);
}
}
Pen pen = new Pen(Brushes.Yellow, 3);
pen.DashStyle = new DashStyle(new double[] { 30, 30 }, 0);
//GeometryDrawing gd = new GeometryDrawing(null, pen, path.RenderedGeometry);
GeometryDrawing gd = new GeometryDrawing(null, pen, geometry);
DrawingBrush drawingBrush = new DrawingBrush(gd);
DrawingBrush tile = drawingBrush.Clone();
tile.Viewbox = new Rect(0.5, 0, 0.25, 0.25);
RenderTargetBitmap rtb = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
context.DrawRectangle(tile, null, new Rect(0, 0, 256, 256));
}
rtb.Render(drawingVisual);
ImageBrush bgBrush = new ImageBrush(rtb);
Background = bgBrush;
}
When done that way, nothing is getting drawn. If I don't use dashing (or set the dashing to null) it works fine. It also works if I use the StreamGeometry.Parse(geom) and keeps the dashing.
Trying to call sgc.Close() didn't help. Currently my only workaround is to call:
geometry = Geometry.Parse(geometry.ToString());
which is not very nice...
What am I missing?
That's a pretty fascinating bug you got there, I can confirm it. Some ILSpy digging revealed the cause: the implicit BeginFigure call that is generated by Geometry.Parse sets the isFilled parameter to true, whereas you set it to false in your explicit StreamGeometryContext call. Change the second parameter in sgc.BeginFigure from false to true, and the dashed lines will render.
The WPF path markup syntax does not allow specifying whether any individual figure should be filled or not, so I suppose that's why the parser defaults to filling them all, just to be sure. But I can't see any good reason why dashed lines would require filled figures, that has to be a WPF bug.

Categories

Resources