Use DrawingBrush to create two Vertical Lines - c#

I am trying to create a 50 x 50 square that has half of the rectangle white and the other half black (the lines going vertically instead of horizontally). I have the following code, but it is not filling the rectangle as expected. How do I make it 50% white and 50% black?
System.Windows.Shapes.Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
DrawingBrush blackBrush = new DrawingBrush();
GeometryDrawing backgroundSquare = new GeometryDrawing(System.Windows.Media.Brushes.White,null,new RectangleGeometry(new Rect(25, 0, 50, 50)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(25, 0, 100, 100)));
GeometryDrawing checkers = new GeometryDrawing(new SolidColorBrush(Colors.Black), null, gGroup);
DrawingGroup checkersDrawingGroup = new DrawingGroup();
checkersDrawingGroup.Children.Add(backgroundSquare);
checkersDrawingGroup.Children.Add(checkers);
blackBrush.Drawing = checkersDrawingGroup;
blackBrush.Viewport = new Rect(0, 0, 0.25, 0.25);
blackBrush.TileMode = TileMode.Tile;
swatch.Fill = blackBrush;
sp_Thumbnails.Children.Add(swatch);

Its simple to have three sections just have one more GeometryDrawing object within your drawingGroup.
you can also configure the number of GeometryDrawing will be there within your drawingGroup as below.
Please see the generic solution to your problem that will display horizontal sections as per the groupCount value.
public void CreateRectangle(int groupCount)
{
Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
double groupsize = 100 / groupCount;
DrawingBrush blackBrush = new DrawingBrush();
DrawingGroup checkersDrawingGroup = new DrawingGroup();
//Considering 3 as groupCount
List<SolidColorBrush> brushes = new List<SolidColorBrush>() { Brushes.Black, Brushes.White,Brushes.Red };
double location = 0;
for (int i = 0; i < groupCount; i++)
{
GeometryDrawing drawing = new GeometryDrawing(brushes[i] , null,
new RectangleGeometry(new Rect(0, location,groupsize,groupsize)));
checkersDrawingGroup.Children.Add(drawing);
location += groupsize;
}
blackBrush.Drawing = checkersDrawingGroup;
swatch.Fill = blackBrush;
brdrect.Children.Add(swatch);
}

To fill your rectangle half with black and half with white. I modified your code as below. This will create a rectangle with lines separating two sections vertically.
Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
DrawingBrush blackBrush = new DrawingBrush();
GeometryDrawing backgroundSquare = new GeometryDrawing(Brushes.White, null,
new RectangleGeometry(new Rect(0, 0, 25, 25)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(25, 0, 25, 25)));
GeometryDrawing checkers = new GeometryDrawing(Brushes.Black, null, gGroup);
DrawingGroup checkersDrawingGroup = new DrawingGroup();
checkersDrawingGroup.Children.Add(backgroundSquare);
checkersDrawingGroup.Children.Add(checkers);
blackBrush.Drawing = checkersDrawingGroup;
swatch.Fill = blackBrush;
brdrect.Children.Add(swatch);
If you want your sections to be spliced horizontally then you will need few changes in above code.
just modify the rectangle drawing creation section as below.
GeometryDrawing backgroundSquare = new GeometryDrawing(Brushes.White, null,
new RectangleGeometry(new Rect(0, 0, 25, 25)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(0, 25, 25, 25)));
GeometryDrawing checkers = new GeometryDrawing(Brushes.Black, null, gGroup);

Related

System.Drawing: Texts are not top-aligned with DrawString

In windows application(C#), When draw string using DrawString function of System.Drawing.Graphics class. But values are not properly aligned with other values though X and Y coordinates are the same. It behaves differently for different fonts with different font sizes. I have considered an internal leading and deduct it from Y coordinate but still is not working for all font types.
In my example, I want all text part to be top-aligned with the horizontal line. But for different fonts and font size, It is not aligned properly. It is working for first font(Avenir Black) but not for others.
Below is the code I am using to generate this printed document:
static void PrintDocument(string filePath)
{
// Create new document
PrintDocument pd = new PrintDocument();
pd.PrintPage += new PrintPageEventHandler(delegate (Object sender, PrintPageEventArgs e)
{
//e.Graphics.Clear(Color.White);
var Canvas = e.Graphics;
var rectF = new RectangleF(0f, 100, 1000, 78.74016f);
Pen p = new Pen(Color.Black);
p.Width = 1 / 2;
p.DashStyle = DashStyle.Solid;
Canvas.DrawLine(p, rectF.X, rectF.Y, rectF.X + rectF.Width, rectF.Y);
rectF = new RectangleF(0f, 100, 250, 78.74016f);
DrawPrice(Canvas, "Avenir Black", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Arial", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Calibri", ref rectF);
rectF.X += 20;
rectF.Y = 100;
DrawPrice(Canvas, "Times New Roman", ref rectF);
});
pd.Print();
Process.Start(filePath);
}
private static void DrawPrice(Graphics Canvas, string fontName, ref RectangleF rectF)
{
StringFormat format = new StringFormat();
format.Alignment = StringAlignment.Center;
format.LineAlignment = StringAlignment.Center;
var superFont = new Font(fontName, 20, new FontStyle());
var font = new Font(fontName, 61, new FontStyle());
Brush brush = new SolidBrush(Color.FromName("black"));
var currencySymbol = "$";
var mainPrice = "29";
var cents = "50";
var superFontMetrics = FontInfo(Canvas, superFont, GraphicsUnit.Point);
var fontMetrics = FontInfo(Canvas, font, GraphicsUnit.Point);
var symbolSize = Canvas.MeasureString(currencySymbol, superFont, rectF.Size, format);
var centsSize = Canvas.MeasureString(cents, superFont, rectF.Size, format);
var dollarSize = Canvas.MeasureString(mainPrice, font, rectF.Size, format);
Canvas.DrawString(currencySymbol, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, symbolSize.Width, symbolSize.Height), format);
rectF.X += symbolSize.Width;
Canvas.DrawString(mainPrice, font, brush, new RectangleF(rectF.X, rectF.Y, dollarSize.Width, rectF.Height), format);
var mainPriceX = rectF.X + (dollarSize.Width / 2);
rectF.X += dollarSize.Width;
Canvas.DrawString(cents, superFont, brush, new RectangleF(rectF.X, rectF.Y - superFontMetrics.InternalLeading, centsSize.Width, centsSize.Height), format);
rectF.Y += rectF.Height;
rectF.X += centsSize.Width;
var titleFont = new Font(fontName, 5, new FontStyle());
var titleTextSize = Canvas.MeasureString(fontName, titleFont, rectF.Size, format);
Canvas.DrawString(fontName, titleFont, brush, new RectangleF(mainPriceX, rectF.Y, titleTextSize.Width, titleTextSize.Height), format);
}
public static FontMetrics FontInfo(Graphics gr, Font font, GraphicsUnit toUnit)
{
FontMetrics metrics = new FontMetrics();
float em_height = font.FontFamily.GetEmHeight(font.Style);
metrics.EmHeight = ConvertUnits(gr, font.Size, font.Unit, toUnit);
float design_to_points = metrics.EmHeight / em_height;
metrics.Ascent = design_to_points * font.FontFamily.GetCellAscent(font.Style);
metrics.Descent = design_to_points * font.FontFamily.GetCellDescent(font.Style);
metrics.CellHeight = metrics.Ascent + metrics.Descent;
metrics.InternalLeading = metrics.CellHeight - metrics.EmHeight;
metrics.LineSpacing = design_to_points * font.FontFamily.GetLineSpacing(font.Style);
metrics.ExternalLeading = metrics.LineSpacing - metrics.CellHeight;
return metrics;
}
After using GraphicsPath >> AddString method to print string, I got the result I want i.e. printed texts are top aligned for all type of fonts. Here is the output:
Below is the code I used:
private void DrawStringByGraphicsPath(Graphics g, Font font, Brush brush, ref RectangleF rectF, RectangleF elementRectF, StringFormat format, string text)
{
if (!string.IsNullOrEmpty(text))
{
using (GraphicsPath graphicsPath = new GraphicsPath())
{
graphicsPath.AddString(text, font.FontFamily, (int)font.Style, GetEMSize(g, font), elementRectF.Location, format);
// this is the net bounds without any whitespace:
RectangleF br = graphicsPath.GetBounds();
// Transform it for top alignment
g.TranslateTransform(elementRectF.X - br.X, (elementRectF.Y - br.Y));
g.FillPath(brush, graphicsPath);
g.ResetTransform();
}
}
}
private float GetEMSize(Graphics canvas, Font font)
{
return (font.SizeInPoints * (font.FontFamily.GetCellAscent(font.Style) + font.FontFamily.GetCellDescent(font.Style))) / font.FontFamily.GetEmHeight(font.Style);
}

How to make an image(or a rectangle) follow a Spline

I have a project that I need to make an image follow a spline.
I build the spline using Graphics.DrawCurve through an array of Points.
I'm trying to use PointAnimationUsingPath but I can't seem to get it to work. Apparently it doesn't work in C# with Windows form.
Can someone give me a light on how to do this?
Thank you All.
-----EDIT-----
Change to a WPF UserControl as recommend in comments.
Still need some help as the shape does not move exactly following the dots, below my code:
public partial class SplineBox : UserControl
{
Point[] finalPoint;
public SplineBox()
{
InitializeComponent();
}
public void MoveShape(Point[] _path)
{
// Create a NameScope for the page so that
// we can use Storyboards.
NameScope.SetNameScope(this, new NameScope());
// Create the EllipseGeometry to animate.
EllipseGeometry animatedEllipseGeometry =
new EllipseGeometry(new Point(10, 100), 15, 15);
// Register the EllipseGeometry's name with
// the page so that it can be targeted by a
// storyboard.
this.RegisterName("AnimatedEllipseGeometry", animatedEllipseGeometry);
// Create a Path element to display the geometry.
Path ellipsePath = new Path();
ellipsePath.Data = animatedEllipseGeometry;
ellipsePath.Fill = Brushes.Blue;
ellipsePath.Margin = new Thickness(15);
SplineCanvas.Children.Add(ellipsePath);
this.Content = SplineCanvas;
// Create the animation path.
PathGeometry animationPath = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = _path[0];
PolyBezierSegment pBezierSegment = new PolyBezierSegment();
for (int p = 1; p < _path.Length; p++)
{
pBezierSegment.Points.Add(_path[p]);
}
pFigure.Segments.Add(pBezierSegment);
animationPath.Figures.Add(pFigure);
// Freeze the PathGeometry for performance benefits.
animationPath.Freeze();
// Create a PointAnimationgUsingPath to move
// the EllipseGeometry along the animation path.
PointAnimationUsingPath centerPointAnimation = new PointAnimationUsingPath();
centerPointAnimation.PathGeometry = animationPath;
centerPointAnimation.Duration = TimeSpan.FromSeconds(5);
centerPointAnimation.RepeatBehavior = RepeatBehavior.Forever;
// Set the animation to target the Center property
// of the EllipseGeometry named "AnimatedEllipseGeometry".
Storyboard.SetTargetName(centerPointAnimation, "AnimatedEllipseGeometry");
Storyboard.SetTargetProperty(centerPointAnimation,
new PropertyPath(EllipseGeometry.CenterProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.RepeatBehavior = RepeatBehavior.Forever;
pathAnimationStoryboard.AutoReverse = true;
pathAnimationStoryboard.Children.Add(centerPointAnimation);
// Start the Storyboard when ellipsePath is loaded.
ellipsePath.Loaded += delegate (object sender, RoutedEventArgs e)
{
// Start the storyboard.
pathAnimationStoryboard.Begin(this);
};
}
public void Paint(ScreenObject _spline)
{
List<Point> points = new List<Point>();
if (true)
{
var spline = _spline;
foreach (System.Windows.Point point in spline.SplineAnchors)
{
Point tempP = new Point((int)point.X, (int)point.Y);
points.Add(tempP);
}
finalPoint = points.ToArray();
//Pen pen = new Pen(Color.FromArgb(255, 0, 0, 255), 1);
//e.Graphics.DrawCurve(pen, finalPoint);
foreach (Point p in finalPoint)
{
// Create a red Ellipse.
Ellipse myEllipse = new Ellipse();
// Create a SolidColorBrush with a red color to fill the
// Ellipse with.
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
// Describes the brush's color using RGB values.
// Each value has a range of 0-255.
mySolidColorBrush.Color = Color.FromArgb(255, 100, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.Black;
// Set the width and height of the Ellipse.
myEllipse.Width = 10;
myEllipse.Height = 10;
myEllipse.Margin = new Thickness(p.X - 5, p.Y - 5, 0, 0);
//e.Graphics.DrawRectangle(pen, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
//e.Graphics.FillRectangle(Brushes.Red, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
SplineCanvas.Children.Add(myEllipse);
}
}
}
}

Dynamically Combining Rectangles

I'm trying to combine two rectangles that I need to create dynamically but I can't figure out how to draw them using .Data and I don't know how to convert from Windows.Shapes.Rectangle to Windows.Media.Geometry.
Rectangle Cross1 = new Rectangle();
Cross1.Margin = new Thickness(465, -140, 0, 0);
Cross1.Height = 110;
Cross1.Width = 15;
Cross1.RenderTransform = rotateTransform1;
Rectangle Cross2 = new Rectangle();
Cross2.HorizontalAlignment = HorizontalAlignment.Left;
Cross2.VerticalAlignment = VerticalAlignment.Top;
Cross2.Margin = new Thickness(362, -103, 0, 0);
Cross2.Height = 110;
Cross2.Width = 15;
Cross2.RenderTransform = rotateTransform2;
CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, Cross1, Cross2);
The CombinedGeometry class only works with other System.Windows.Media.Geometry objects, not System.Windows.Shapes. You need to use the equivalent RectangleGeometry class instead.
Something such as:
RectangleGeometry Cross1 = new RectangleGeometry(new Rect(0, 0, 15, 110));
Cross1.Transform = rotateTransform1;
RectangleGeometry Cross2 = new RectangleGeometry(new Rect(0, 0, 15, 110));
Cross2.Transform = rotateTransform2;
CombinedGeometry c1 = new CombinedGeometry(GeometryCombineMode.Union, Cross1, Cross2);

StreamGeometry combine

I have complex StreamGeometry and I want to clip it. Unfortunately, it looks like StreamGeometry doesn't supports combine.
Here is a test.
Xaml:
<Path x:Name="path" Stroke="Red"/>
Code:
var clip = new RectangleGeometry(new Rect(50, 50, 10, 10));
var rect = new RectangleGeometry(new Rect(0, 0, 100, 100));
var stream = new StreamGeometry();
using (var geometry = stream.Open())
{
geometry.BeginFigure(new Point(0, 0), false, true);
geometry.LineTo(new Point(0, 100), true, false);
geometry.LineTo(new Point(100, 100), true, false);
geometry.LineTo(new Point(100, 0), true, false);
}
//path.Data = rect;
//path.Data = stream;
//path.Data = clip;
//path.Data = Geometry.Combine(rect, clip, GeometryCombineMode.Intersect, null);
//path.Data = Geometry.Combine(stream, clip, GeometryCombineMode.Intersect, null);
Uncommenting first line (to show rect) or second line (to show stream) produces same visual result:
Uncommenting third line (to show clip) or fourth line (to show intersection of clip and rect) will produce:
While uncommenting last line (to show intersection of clip and geometry) produce blank screen.
My question is: how to combine (clip) StreamGeometry?
I know there is UIElement.Clip, but:
// blank screen
path.Data = stream;
path.Clip = clip;
// blank screen as well
path.Data = stream;
path.Clip = clip;
You can definitely combine StreamGeometry objects. For this, you want to use a CombinedGeometry object. Here's a simple extension method for drawing polygons with holes:
public static void DrawPolygon(this DrawingContext context, Brush brush, Pen pen, IEnumerable<Point> exteriorRing, IEnumerable<IEnumerable<Point>> interiorRings = null)
{
StreamGeometry geo = new StreamGeometry();
Geometry finalGeometry = geo;
using (StreamGeometryContext ctxExterior = geo.Open())
{
ctxExterior.BeginFigure(exteriorRing.First(), (brush != null), true);
ctxExterior.PolyLineTo(exteriorRing.Skip(1).ToArray(), (pen != null), false);
if (interiorRings != null)
{
foreach (var ring in interiorRings)
{
var interiorGeometry = new StreamGeometry();
using (var ctxInterior = interiorGeometry.Open())
{
ctxInterior.BeginFigure(ring.First(), true, true);
ctxInterior.PolyLineTo(ring.Skip(1).ToArray(), (pen != null), false);
}
finalGeometry = new CombinedGeometry(GeometryCombineMode.Exclude, finalGeometry, interiorGeometry);
}
}
}
context.DrawGeometry(brush, pen, finalGeometry);
}
Solution is simple: do not use StreamGeometry.
To example, this will work (using PathGeometry instead):
var clip = new RectangleGeometry(new Rect(50, 50, 10, 10));
var geometry = new PathGeometry(new[] { new PathFigure(new Point(0, 0), new[] {
new LineSegment(new Point(0, 100), true),
new LineSegment(new Point(100, 100), true),
new LineSegment(new Point(100, 0), true),
}, true) });
path.Data = Geometry.Combine(clip, geometry, GeometryCombineMode.Intersect, null);
Result:
Very important!
It looks like UIElement.Clip still render invisible parts (mayhap only with StreamGeometry) ! Never use it! Clip geometry before assigning it.

C# Windows Phone - Creating a round mask

I need to mask dynamically created images, so that they will be shown as circles.
Pictures can be square, but are usually rectangles... so the circle that will be shown can be taken from the center of it...so the shown circle must be inscribed in the picture and centered in the center of it.
This is the code I'm using right now:
//Setting up the image
Image image = new Image();
image.Height = 70;
image.Width = 70;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.UriSource = new Uri("http://url-of-the-image", UriKind.Absolute);
image.CacheMode = new BitmapCache();
image.Source = bitmapImage;
image.Stretch = Stretch.UniformToFill;
image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
//Setting up the mask
RadialGradientBrush opacityMask = new RadialGradientBrush();
GradientStop gs1 = new GradientStop();
GradientStop gs2 = new GradientStop();
GradientStop gs3 = new GradientStop();
gs1.Color = Color.FromArgb(255, 0, 0, 0);
gs1.Offset = 0.0;
gs2.Color = Color.FromArgb(255, 0, 0, 0);
gs2.Offset = 0.999;
gs3.Color = Color.FromArgb(0, 0, 0, 0);
gs3.Offset = 1.0;
opacityMask.GradientStops.Add(gs1);
opacityMask.GradientStops.Add(gs2);
opacityMask.GradientStops.Add(gs3);
image.OpacityMask = opacityMask;
//Showing the image
panel.Children.Add(image);
This all works fine, but when the pictures are rectangular and not square, this creates an ellipse instead of a circle... any idea on how can I force it to create a circle?
I also tried to specify some more parameters, but doesn't seem to help:
opacityMask.Center = new Point(0.5, 0.5);
opacityMask.RadiusX = 0.5;
opacityMask.RadiusY = 0.5;
Okay, today i tried again to fix this, and i came out with a solution.
It's not the best, more clean solution ever...but works :)
I basically wrapped the picture (not masked) into a StackPanel and then applied the mask to the StackPanel instead ;)
This is how it looks like (the only lines that change from the original are the the last few ones):
//Setting up the image
Image image = new Image();
image.Height = 70;
image.Width = 70;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.UriSource = new Uri("http://url-of-the-image", UriKind.Absolute);
image.CacheMode = new BitmapCache();
image.Source = bitmapImage;
image.Stretch = Stretch.UniformToFill;
image.VerticalAlignment = System.Windows.VerticalAlignment.Center;
//Setting up the mask
RadialGradientBrush opacityMask = new RadialGradientBrush();
GradientStop gs1 = new GradientStop();
GradientStop gs2 = new GradientStop();
GradientStop gs3 = new GradientStop();
gs1.Color = Color.FromArgb(255, 0, 0, 0);
gs1.Offset = 0.0;
gs2.Color = Color.FromArgb(255, 0, 0, 0);
gs2.Offset = 0.999;
gs3.Color = Color.FromArgb(0, 0, 0, 0);
gs3.Offset = 1.0;
opacityMask.GradientStops.Add(gs1);
opacityMask.GradientStops.Add(gs2);
opacityMask.GradientStops.Add(gs3);
//Setting up the StackPanel
StackPanel sp = new StackPanel();
sp.OpacityMask = opacityMask;
//Showing the image
sp.Children.Add(image);
panel.Children.Add(sp);

Categories

Resources