In my aplication I need to plot an equation. The plotted equation will be composed of many small linear lines. When I plot it using the DrawLine method inside a for I get higher quality than when using the DrawLines method.
Graphics canvas = pnlCanvas.CreateGraphics();
canvas.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
//High Quality
for (int i = 0; i < plot_points.Length - 1; i++)
{
canvas.DrawLine(penKat, plot_points[i], plot_points[i + 1]);
}
//Low Quality
canvas.DrawLines(penKat, plot_points);
I need to plot it using the DrawLines method because of some issues. Is there a way to get high quality using that method?
Try:
penKat.EndCap = System.Drawing.Drawing2D.LineCap.Round;
penKat.StartCap = System.Drawing.Drawing2D.LineCap.Round;
penKat.LineJoin = LineJoin.Round;
MiterLimit might help, if your lines are thicker than a few pixels..
Edit:
For crisp joins you may want to experiment with other LineJoin values:
penKat.LineJoin = LineJoin.MiterClipped;
penKat.MiterLimit = 1.5f;
Or
penKat.LineJoin = LineJoin.Miter;
penKat.MiterLimit = 1.5f;
Do try out other MiteLimit values until you're happy!
Or post an example image with the two versions..
For stroke widths of 2-4 pixels the difference between the LineJoins will not be very visible. This changes dramatically with growing stroke widths; so remember this property for those thicker lines!
Related
It seems like this should be simple enough, but I'm really struggling with finding any documentation on how I can do this. I'm simply looking to crop an image to turn a square into a circle.
There is a lot of discussion about it, but I can't seem to find a good example of how to do this using UWP/Win2D.
Here is a bit of code to illustrate the issue I was trying to describe in my comments:
// draw a 10x10 grid of circles
var bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/ice.png"); // hex-shaped image is 250x220 pixels
var brush = new CanvasImageBrush(sender, bitmap);
for (var i = 0; i < 10; i++)
{
for (var j = 0; j < 10; j++)
{
//_drawingSession.FillCircle(new Vector2(i * 50, j * 50), (float)(25), Colors.Blue);
_drawingSession.FillCircle(new Vector2(i * 50, j * 50), (float)(25), brush);
}
}
The image below shows how the brush is being cut from the same x/y coordinates based on the vector where the target circle is to be drawn.
Note: the same effect occurs with FillEllipse().
You can try to use CanvasImageBrush and CanvasDrawingSession.FillEllipse Method achieve it.
private async void canvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/image.jpg"))
{
CanvasImageBrush canvasImageBrush = new CanvasImageBrush(sender, bitmap);
args.DrawingSession.FillEllipse(new System.Numerics.Vector2(100f), 100, 100, canvasImageBrush);
}
}
------------ Update -------------
If you want to cut a circle out of the image source, you can configure the CanvasImageBrush.Transform property to scale the image, then cut the circle and display it on the canvas.
private async void canvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/image.jpg"))
{
CanvasImageBrush canvasImageBrush = new CanvasImageBrush(sender, bitmap);
System.Numerics.Vector2 center = new System.Numerics.Vector2((float)(bitmap.Size.Width / 2),
(float)(bitmap.Size.Height / 2));
canvasImageBrush.Transform = System.Numerics.Matrix3x2.CreateScale(0.5F, center);
args.DrawingSession.FillEllipse(center, 160, 160, canvasImageBrush);
}
}
You should change some parameters in my above code to satisfy your requirement, such as the scale in the Matrix3x2.CreateScale method.
Okay, after chatting with one of the fellows on the GitHub Win2D project, I finally have a clear answer on how this works - and it works nothing like I would have expected it to work.
First, the bitmap brush image is by default being positioned at 0,0 on the canvas.
In my case, I wanted to cut a circle from the image and draw it someplace else on the canvas. This requires 2 separate bits of math.
First, you need to position the bitmap's top-left-corner (TLC) to where you want the circle to be drawn. This is done by setting the brush's Transform property. In my example, I'm setting the image TLC to 300/300;
// create the brush
var brush = new CanvasImageBrush(sender, _tiles[1]);
brush.Transform = Matrix3x2.CreateTranslation(300, 300);
Now, to cut/draw the circle using the brush image, I have to describe where the center of the image is to be on the canvas. My image is 250x220.
// 300+250/2, 300+220/2 = 425, 410
_args.DrawingSession.FillCircle(new Vector2(425, 410), (float)(110), brush);
This gives the effect of cutting a circle out of my original bitmap and drawing it on the canvas at the desired location.
Hopefully this is clear enough. I know I certainly struggled to find the answer.
I am playing around with the Microsoft Vision API and learning C# as I go, and one of the properties of a Vision object is an "Accent Color" of the image.
From a series of images analysed, I want to show those colors ordered in a Linear Gradient -- because that will be able to show the user that most pictures are (for example) blue, because Blue colors take up half of the gradient etc.
I have this working, in that I am ordering the Colors by Hue, and able to produce a Linear Gradient I am filling into a Bitmap.
BUT, the gradient by default is Horizontal, and I need Vertical -- so I've used LinearGradientBrush.RotateTransform(90) which rotates that actual gradient fine, but doesn't seem to fill the entire Rectangle, and it repeats. This is what I'm getting as a result:
How do I create a Vertical LinearGradient that fills up the entire Height of the Rectangle object for my Bitmap?
Here is my code:
private Bitmap CreateColorGradient(System.Drawing.Rectangle rect, System.Drawing.Color[] colors)
{
Bitmap gradient = new Bitmap(rect.Width, rect.Height);
LinearGradientBrush br = new LinearGradientBrush(rect, System.Drawing.Color.White, System.Drawing.Color.White, 0, false);
ColorBlend cb = new ColorBlend();
// Positions
List<float> positions = new List<float>();
for (int i = 0; i < colors.Length; i++) positions.Add((float)i / (colors.Length - 1));
cb.Positions = positions.ToArray();
cb.Colors = colors;
br.InterpolationColors = cb;
br.RotateTransform(90);
using (Graphics g = Graphics.FromImage(gradient))
g.FillRectangle(br, rect);
return gradient;
}
Thanks for reading and any help -- also if you see something in my code that could be done better please point it out it helps me learn :)
You are ignoring the angle parameter in the constructor. And as you instead do a rotation on the Grahics object your brush rectangle is no longer correctly fits the target bitmap and the gradient can't fill it; so it repeats.
To correct
simply set the angle to 90 and
remove the br.RotateTransform(90); call.
Here this changes the result from the left to the middle version:
While we're looking at it, do take note of the WrapMode property of LinearGradientBrush. What you see in the first image is the default WrapMode.Clamp. Often a changing to one of the Flip mode helps.. So lets have a look at the impact of it one the first version at the right position.
It looks like WrapMode.TileFlipY but since I have brought back the rotation it actually takes a value WrapMode.TileFlipX or WrapMode.TileFlipXY:
br.WrapMode = WrapMode.TileFlipX;
I'm trying to make a timer that mimics a clock, with a ticking hand. I have no problem drawing a clock texture and then a line for the hand, but I also want the space behind the clock hand to have a fill. So as time goes on, I want the clock to "fill up" starting at the origin (0:00) all the way up to the clock hand.
I basically want to do this:
What's the best way for me to do this? I have the foundation, just don't know how to add the fill part.
You should aproximate it building a triangle fan.
int n=0;
VertexPostionColor[] V = new VertexPositionColor[num_triangles+2]
V[0] = Center;
for (var angle = start ;angle<=end; angle += (end - start) / num_triangles)
{
V[++N].Position = new Vector3( Math.Cos(angle), Math.Sin(angle)) * radius + Center;
V[N].Color = CircleColor;
}
Short[] Index = new Short[num_triangles*3];
for (int i = 0; i< num_triangles; i++)
{
Index[i*3] = 0;
Index[i*3+1] = i+1;
Index[i*3+2] = i+2;
}
GraphicsDevice.DrawUserIndexedPrimitives(...);
If you want to get complicated using a spritebatch, you have to use a small sector texture, and draw it multiple times rotating it about the center.
this is an example, it need to be tuned to be precise.
float SectorAngle = Mathhelper.ToRadians(10);
Texture2D SectorTex;
Vector2 Origin = new Vector2(SectorTex.Width/2, SectorTex.Height);
for (var angle=start; angle<=end; angle+=SectorAngle) {
spriteBatch.Draw(SectorTex, Center, null, Color.White, Origin, angle, scale,...)
}
If you want to do it using textures, you should be able to manage it with two simple textures: a semi-circle (exactly half a circle), and a full circle.
First, draw the full circle white. Then it's just a matter of calculating how much of the circle needs to be filled.
If it's less than half, draw the half circle blue, rotated to match the "minute hand". Then draw another half circle white to cover the left side.
If it's more than half, draw the half circle blue, covering the entire right side. Then draw another half circle blue, rotated to match the "minute hand".
Once the fill is complete, you just need to draw the other clock components; the hands and border.
I'm trying to detect rectangles on this image:
with this code:
static void Main(string[] args)
{
// Open your image
string path = "test.png";
Bitmap image = (Bitmap)Bitmap.FromFile(path);
// locating objects
BlobCounter blobCounter = new BlobCounter();
blobCounter.FilterBlobs = true;
blobCounter.MinHeight = 5;
blobCounter.MinWidth = 5;
blobCounter.ProcessImage(image);
Blob[] blobs = blobCounter.GetObjectsInformation();
// check for rectangles
SimpleShapeChecker shapeChecker = new SimpleShapeChecker();
foreach (var blob in blobs)
{
List<IntPoint> edgePoints = blobCounter.GetBlobsEdgePoints(blob);
List<IntPoint> cornerPoints;
// use the shape checker to extract the corner points
if (shapeChecker.IsQuadrilateral(edgePoints, out cornerPoints))
{
// only do things if the corners form a rectangle
if (shapeChecker.CheckPolygonSubType(cornerPoints) == PolygonSubType.Rectangle)
{
// here i use the graphics class to draw an overlay, but you
// could also just use the cornerPoints list to calculate your
// x, y, width, height values.
List<Point> Points = new List<Point>();
foreach (var point in cornerPoints)
{
Points.Add(new Point(point.X, point.Y));
}
Graphics g = Graphics.FromImage(image);
g.DrawPolygon(new Pen(Color.Red, 5.0f), Points.ToArray());
image.Save("result.png");
}
}
}
}
but it dont recognize the rectangles (walls). It just recognize the big square, and when I reduce the minHeight and minWidth, it recognize trapezoids on the writing..
I propose a different algorithm approach, after working almost a year with image processing algorithms what I can tell is that to create an efficient algorithm, you have to "reflect" how you, as a human would do that, here is the proposed approach:
We don't really care about the textures, we care about the edges (rectangles are edges), therefore we will apply an Edge-detection>Difference (http://www.aforgenet.com/framework/docs/html/d0eb5827-33e6-c8bb-8a62-d6dd3634b0c9.htm), this gives us:
We want to exaggerate the walls, as humans we know that we are looking for the walls, but the computer does not know this, therefore, apply two rounds of Morphology>Dilatation (http://www.aforgenet.com/framework/docs/html/88f713d4-a469-30d2-dc57-5ceb33210723.htm), this gives us:
We care only about the what is wall and what is not, apply a Binarization>Threshold (http://www.aforgenet.com/framework/docs/html/503a43b9-d98b-a19f-b74e-44767916ad65.htm), we get:
(Optional) We can apply a blob extraction to erase the labels ("QUARTO, BANHEIRO", etc)
We apply a Color>Invert, this is just done because the next step detects the white color not black.
Apply a Blob>Processing>Connected Components Labeling (http://www.aforgenet.com/framework/docs/html/240525ea-c114-8b0a-f294-508aae3e95eb.htm), this will give us all the rectangles, like this:
Note that for each colored box you have its coordinates, center, width and height. So you can extract a snip from the real image with that coordinates.
PS: Using the program AForge Image Processing Lab is highly recommended to test your algos.
Each time a rectangle is found, the polygon is drawn on Graphics and the file is saved only for THAT rectangle. This means that result.png will only contain a single rectangle at a time.
Try first saving all the rectangles in a List<List<Points>> and then going over it and add ALL the rectangles to the image. Something like this (Pseudo):
var image..
var rectangles..
var blobs..
foreach (blob in blobs)
{
if (blob is rectangle)
{
rectangles.add(blob);
}
}
foreach (r in rectangles)
{
image.draw(r.points);
}
image.save("result.png");
If your problem now is to avoid noise due to writings on the image, use FillHoles with widht and height of holes smaller than the smallest rectangle but larger than any of the writings.
If the quality of image is good and no text is touching the border of the image, Invert the image and FillHoles will remove most of the stuff.
Hope I understood your problem correctly.
We are trying to detect rectangles in so many rectangles (considering gray rectangles of grid). Almost all algorithms will get confused here. You're not eliminating externals from input image. Why not replace grid line color with background color or use threshold above to eliminate all grids first.
Then grow all pixels equal to the width of wall, Find all horizontal and vertical lines thereafter use maths to find rectangles using detected lines. Uncontrolled filling will be risky as when boundries are not closed fill will make two rooms as one rectangle.
What I am looking to do sounds really simple, but no where on the Internet so far have I found a way to do this in DotNet nor found a 3rd party component that does this either (without spending thousands on completely unnecessary features).
Here goes:
I have a jpeg of a floor tile (actual photo) that I create a checkerboard pattern with.
In dotnet, it is easy to rotate and stitch photos together and save the final image as a jpeg.
Next, I want to take that final picture and make it appear as if the "tiles" are laying on a floor for a generic "room scene". Basically adding a 3D perspective to make it appear as if it is actually in the room scene.
Heres a website that is doing something similar with carpeting, however I need to do this in a WinForms application:
Flor Website
Basically, I need to create a 3D perspective of a jpeg, then save it as a new jpeg (then I can put an overlay of the generic room scene).
Anyone have any idea on where to get a 3rd party DotNet image processing module that can do this seemingly simple task?
It is not so simple because you need a 3D transformation, which is more complicated and computationally expensive than a simple 2D transformation such as rotation, scaling or shearing. For you to have an idea of the difference in the math, 2D transformations require 2 by 2 matrices, whereas a projection transformation (which is more complicated than other 3D transforms) requires a 4 by 4 matrix...
What you need is some 3D rendering engine in which you can draw polygons (in a perspective view) and them cover them with a texture (like a carpet). For .Net 2.0, I'd recommend using SlimDX which is a port of DirectX that would allow you to render polygons, but there is some learning curve. If you are using WPF (.Net 3.0 and up), there is a built in 3D canvas that allows you to draw textured polygons in perspective. That might be easier/better to learn than SlimDX for your purposes. I'm sure that there is a way to redirect the output of the 3D canvas towards a jpeg...
You might simplify the problem a lot if you don't require great performance and if you restrict the orientation of the texture (eg. always a horizontal floor or always a vertical wall). If so, you could probably render it yourself with a simple drawing loop in .Net 2.0.
If you just want a plain floor, your code would look like this. WARNING: Obtaining your desired results will take some significant time and refinement, specially if you don't know the math very well. But on the other hand, it is always fun to play with code of this type... (:
Find some sample images below.
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
namespace floorDrawer
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
ResizeRedraw = DoubleBuffered = true;
Width = 800;
Height = 600;
Paint += new PaintEventHandler(Form1_Paint);
}
void Form1_Paint(object sender, PaintEventArgs e)
{
// a few parameters that control the projection transform
// these are the parameters that you can modify to change
// the output
double cz = 10; // distortion
double m = 1000; // magnification, usually around 1000 (the pixel width of the monitor)
double y0 = -100; // floor height
string texturePath = #"c:\pj\Hydrangeas.jpg";//#"c:\pj\Chrysanthemum.jpg";
// screen size
int height = ClientSize.Height;
int width = ClientSize.Width;
// center of screen
double cx = width / 2;
double cy = height / 2;
// render destination
var dst = new Bitmap(width, height);
// source texture
var src = Bitmap.FromFile(texturePath) as Bitmap;
// texture dimensions
int tw = src.Width;
int th = src.Height;
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
double v = m * y0 / (y - cy) - cz;
double u = (x - cx) * (v + cz) / m;
int uu = ((int)u % tw + tw) % tw;
int vv = ((int)v % th + th) % th;
// The following .SetPixel() and .GetPixel() are painfully slow
// You can replace this whole loop with an equivalent implementation
// using pointers inside unsafe{} code to make it much faster.
// Note that by casting u and v into integers, we are performing
// a nearest pixel interpolation... It's sloppy but effective.
dst.SetPixel(x, y, src.GetPixel(uu, vv));
}
// draw result on the form
e.Graphics.DrawImage(dst, 0, 0);
}
}
}