I am working on a program that takes a Bitmap and converts it into circular form. The code is as follows:
public static Image CropToCircle(Image srcImage, Color backGround)
{
Image dstImage = new Bitmap(srcImage.Width, srcImage.Height, srcImage.PixelFormat);
Graphics g = Graphics.FromImage(dstImage);
using (Brush br = new SolidBrush(backGround)) {
g.FillRectangle(br, 0, 0, dstImage.Width, dstImage.Height);
}
GraphicsPath path = new GraphicsPath();
path.AddEllipse(0, 0, dstImage.Width, dstImage.Height);
g.SetClip(path);
g.DrawImage(srcImage, 0, 0);
return dstImage;
}
It returns the image in circular shape; however I need to read an image wedge in the form of degrees; that is, the circle has 360 degrees and I am trying to write a function that will accept a degree (e.g. 10) and will return the pixels of the image that fall in 10th degree. Such that entire image will be readable in 1 to 360 degrees.
Since my hint was actually rather misleading, let me make up by giving you a working code:
// collect a list of colors from a bitmap with a cetner c and radius r
List<Color> getColorsByAngle(Bitmap bmp, Point c, int r, float angle)
{
List<Color> colors = new List<Color>();
for (int i = 0; i < r; i++)
{
int x = (int)(Math.Sin(angle / 180f * Math.PI) * i);
int y = (int)(Math.Cos(angle / 180f * Math.PI) * i);
colors.Add(bmp.GetPixel(c.X + x, c.Y + y));
}
return colors;
}
Here it is at work:
(The gif is rather quantized for size..)
Note that
Pixels close to the center will be read multiple time, the center itself even each time
To collect all outer pixels you need to read as many angles as the circumference of the circle has pixels, ie 2 * PI * radius. So for a circle with a radius of 300 pixels you need to step the angle in 360° / (600 * 3.14) or about 0.2°..
Also note the the coordinate systems in GDI and in geometry are not the same, neither in the direction of the axes nor the angles. Adapting this is left for you..
The original version didn't mention a 'wedge area'. To read an area or the whole image simply loop over an angle range in suitable steps!
Related
I'm currently trying to scale a texture to the given size in pixels via SharpDX.Direct3D9.
I have the following code which draws a texture on the screen (2D)
public static bool DrawTexture(IntPtr device, IntPtr txt, RectangleF rect, float rotation, Color tint)
{
try {
Texture texture = (Texture)txt;
Matrix m = Matrix.Identity * Matrix.Translation(-0.5f, -0.5f, 0.0f) * Matrix.Scaling(rect.Width, rect.Height, 1.0f) * Matrix.RotationZ(rotation) * Matrix.Translation(rect.X, rect.Y, 0.0f);
using (Sprite s = new Sprite((Device)device)) {
s.Begin();
s.Transform = m;
s.Draw(texture, tint.ToRawColorBGRA());
s.End();
}
return true;
}
catch (Exception ex) {
Main.managerInstance.console.PrintError(string.Format("[Direct3D9] An error occured while trying to draw texture. Details: {0}", ex.ToString()));
}
return false;
}
Matrix.Scaling(rect.Width, rect.Height, 1.0f) is responsible for scaling my texture to the given size (128x128 pixel).
But as far as i understand, the Matrix.Scaling function takes in a float from 0 - 1 where 1 is the full texture size and 2 would be double the texture size. But i would like to enter the size in pixel and not in units(?).
So i tried the following:
Size res = CGame.Resolution;
float cW = rect.Width / res.Width;
float cH = rect.Height / res.Height;
Matrix m = Matrix.Identity * Matrix.Translation(-0.5f, -0.5f, 0.0f) * Matrix.Scaling(cW, cH, 1.0f) * Matrix.RotationZ(rotation) * Matrix.Translation(rect.X, rect.Y, 0.0f);
I divide the given texture Width and Height (which is 128x128 pixel) by the Width and Height of the current screen resolution (Which in my case is 1920x1080).
This leaves me with the following:
The result of the division by the screen resolution
As you can see there is a red rectangle in the texture, which is actually 128x128 pixel in size, and in the background, there is my texture, which is supposed to be scaled to 128x128 but as you can see, it clearly is larger then the red rectangle which is 128x128.
Here is how i load my texture
// D3DX.DefaultNonPowerOf2 = -2
Texture t = Texture.FromFile(device, filePath, D3DX.DefaultNonPowerOf2, D3DX.DefaultNonPowerOf2, 1, Usage.None, Format.Unknown, Pool.Managed, Filter.None, Filter.None, 0);
If someone could help me out with this problem i would be really grateful!
Got it working!
Needed to divide the target Size of the texture by the actual texture size like so:
SurfaceDescription sd = texture.GetLevelDescription(0);
float cW = rect.Width / sd.Width;
float cH = rect.Height / sd.Height;
I am generating dynamic textures in monogame for simple shapes. Yes I know the disadvantages to this system, but I am just experimenting with building my own physics engine. I am trying to generate the texture for an ellipse as is described here.
I have a function PaintDescriptor that takes an x and y pixel coordinate and gives back what color it should be. Red is just while I am debugging, and normally it would be Color.Transparent.
public override Color PaintDescriptor(int x, int y)
{
float c = (float)Width / 2;
float d = (float)Height / 2;
return pow((x - c) / c, 2) + pow((y - d) / d, 2) <= 1 ? BackgroundColor : Color.Red;
}
Now this works if Width == Height, so, a circle. However, if they are not equal, it generates a texture with some ellipse like shapes, but also with banding/striping.
I have tried seeing if my width and height were switched, and ive tried several other things. One thing to note is that where in the normal coordinate system on desmos I have (y + d) / d, but since the screen's y axis is flipped, I have to flip the y offset in the code: (y - d) / d. The rest of the relating code for texture generation and drawing is here:
public Texture2D GenerateTexture(GraphicsDevice device, Func<int, int, Color> paint)
{
Texture2D texture = new Texture2D(device, Width, Height);
Color[] data = new Color[Width * Height];
for (int pixel = 0; pixel < data.Count(); pixel++)
data[pixel] = paint(pixel / Width, pixel % Height);
texture.SetData(data);
return texture;
}
public void Draw(float scale = 1, float layerdepth = 0, SpriteEffects se = SpriteEffects.None)
{
if (SBRef == null)
throw new Exception("No reference to spritebatch object");
SBRef.Draw(Texture, new Vector2(X, Y), null, null, null, 0, new Vector2(scale, scale), Color.White, se, layerdepth);
}
public float pow(float num, float power) //this is a redirect of math.pow to make code shorter and more readable
{
return (float)Math.Pow(num, power);
}
Why doesnt this match desmos? Why does it not make an ellipse?
EDIT: I forgot to mention, but one possible solution I have come across is to always draw a circle, and then scale it to the desired width and height. This is not acceptable for me for one because of some possible blurriness in drawing, or other artifacts, but more mainly because I want to understand whatever im not currently getting with this solution.
After sleeping and coming back with a fresh mindset for like the 10th time, I found the answer. in the function GenerateTexture:
data[pixel] = paint(pixel / Width, pixel % Height);
should be
data[pixel] = paint(pixel % Width, pixel / Height);
I'm using the following code to Transform a small rectangle coordinates to a larger one ie: A rectangle position on a small image to the same position on the larger resolution of the same image
Rectangle ConvertToLargeRect(Rectangle smallRect, Size largeImageSize, Size smallImageSize)
{
double xScale = (double)largeImageSize.Width / smallImageSize.Width;
double yScale = (double)largeImageSize.Height / smallImageSize.Height;
int x = (int)(smallRect.X * xScale + 0.5);
int y = (int)(smallRect.Y * yScale + 0.5);
int right = (int)(smallRect.Right * xScale + 0.5);
int bottom = (int)(smallRect.Bottom * yScale + 0.5);
return new Rectangle(x, y, right - x, bottom - y);
}
But there seems to be a problem with some images.The transformed rectangle coordinates seems to be off the image.
UPDATE:
img.Draw(rect, new Bgr(232, 3, 3), 2);
Rectangle transret= ConvertToLargeRect(rect, orgbitmap.Size, bit.Size);
target = new Bitmap(transret.Width, transret.Height);
using (Graphics g = Graphics.FromImage(target))
{
g.SmoothingMode = SmoothingMode.HighQuality;
g.DrawImage(orgbitmap, new Rectangle(0, 0, target.Width, target.Height),
transret, GraphicsUnit.Pixel);
}
Rectangle Drawn on small resolution Image
{X=190,Y=2,Width=226,Height=286}
Rectangle Transformed into Orginal Large Resolution Image {X=698,Y=7,Width=830,Height=931}
Original Image
First of all, if you resize the shape it shouldn't move position. That's not what one would expect out of enlarging a shape. This means the X,Y point of the top-left corner shouldn't be transformed.
Second, you shouldn't be adding 0.5 manually to operations, that's not a clean way to proceed. Use the ceiling function as suggested by #RezaAghaei
Third, you should not substract X/Y from the height/width, your calculations should be done as width * scale.
Please correct those mistakes, and if it doesn't work I'll update the answer with extra steps.
Please Note - This is a Math question essentially. However, i have also tagged C#
as this is the language i am working in
Summary
I'm looking for an algorithm (or name thereof) that can find the Negative Space (or space) in an image. The closest i have found Dijkstra's algorithm (which is seemingly close), yet its actually a subset of the actual problem. Namely, to walk through a Cartesian Plane traversing every coordinate that isn't filled (or black in my case) to find a mask. Example below
Example of Dijkstra's Algorithm
The background
I need to tidy up 10's of thousands of images that have artefacts in them. By cleaning up i mean these things specifically :
Using Edge Detection to find the edges of the objects in the images
Masking the Negative Space so i can covert the image backgrounds to plain white
Cropping the images to their optimal size.
Currently i'm using Canny Edge Detection to find the most important part of the image. I can crop the image fairly well (shown below), and also find all the images that have the problem. However i am having trouble locating the best algorithm (or name thereof) to find the negative space.
Example of the original image
As you can see the image looks pretty clean, however its not
Example of the accentuated problem
The image has lots of artefacts in the background and they need to be removed
Example of Canny Edge Detection
This does a wonderful job of cleaning up the image
The Problem
Dijkstra's algorithms premise is it looks for all the possible paths, its basically a solves the Travelling Sales man problem
The problems is; The algorithm actually does much more than i need to do with regards to the weighing and the distance measures , and it stops when it has the shortest path (where i need it to complete the image).
The pseudo code
1 function Dijkstra(Graph, source):
2
3 create vertex set Q
4
5 for each vertex v in Graph: // Initialization
6 dist[v] ← INFINITY // Unknown distance from source to v
7 prev[v] ← UNDEFINED // Previous node in optimal path from source
8 add v to Q // All nodes initially in Q (unvisited nodes)
9
10 dist[source] ← 0 // Distance from source to source
11
12 while Q is not empty:
13 u ← vertex in Q with min dist[u] // Node with the least distance
14 // will be selected first
15 remove u from Q
16
17 for each neighbor v of u: // where v is still in Q.
18 alt ← dist[u] + length(u, v)
19 if alt < dist[v]: // A shorter path to v has been found
20 dist[v] ← alt
21 prev[v] ← u
22
23 return dist[], prev[]
Can anyone suggest an Algorithm or modify the Pseudo Code to Dijkstra's Algorithms to achieve this?
The answer to the question was simply the Flood-fill Algorithm.
However, to solve the entire problem of cleaning subtle artefacts from images, the total solution was as follows.
Use Canny Edge Detection with appropriate thresholds to get the outline of objects in the image
Use a Gaussian Blur to Blur the Canny results enough so the flood full wont bleed
Use a flood fill to create the Mask and apply it back to the original image
Some traps for your for young players.
PixelFormats, you need to make sure everything is talking the same format
Not editing the bitmap directly by using scanlines or locked pixels
paralleling algorithms where possible, in this case the flood fill and Blur where good candiates
Update
Even a faster method was just to use Parallel FloodFill with a Color Threshold value
Color Threshold
public static bool IsSimilarColor(this Color source, Color target, int threshold)
{
int r = source.R - target.R, g = source.G - target.G, b = source.B - target.B;
return (r * r + g * g + b * b) <= threshold * threshold;
}
Parallel FloodFill
public static Bitmap ToWhiteCorrection(this Bitmap source, Color sourceColor, Color targetColor, Color maskColor, int threshold, Size tableSize, int cpu = 0)
{
using (var dbMask = new DirectBitmap(source))
{
using (var dbDest = new DirectBitmap(source))
{
var options = new ParallelOptions
{
MaxDegreeOfParallelism = cpu <= 0 ? Environment.ProcessorCount : cpu
};
// Divide the image up
var rects = dbMask.Bounds.GetSubRects(tableSize);
Parallel.ForEach(rects, options, rect => ProcessWhiteCorrection(dbMask, dbDest, rect, sourceColor, targetColor, maskColor, threshold));
return dbDest.CloneBitmap();
}
}
}
private static void ProcessWhiteCorrection(this DirectBitmap dbMask, DirectBitmap dbDest, Rectangle rect, Color sourceColor, Color targetColor, Color maskColor, int threshold)
{
var pixels = new Stack<Point>();
AddStartLocations(dbMask, rect, pixels, sourceColor, threshold);
while (pixels.Count > 0)
{
var point = pixels.Pop();
if (!rect.Contains(point))
{
continue;
}
if (!dbMask[point]
.IsSimilarColor(sourceColor, threshold))
{
continue;
}
dbMask[point] = maskColor;
dbDest[point] = targetColor;
pixels.Push(new Point(point.X - 1, point.Y));
pixels.Push(new Point(point.X + 1, point.Y));
pixels.Push(new Point(point.X, point.Y - 1));
pixels.Push(new Point(point.X, point.Y + 1));
}
}
Worker
private static void ProcessWhiteCorrection(this DirectBitmap dbMask, DirectBitmap dbDest, Rectangle rect, Color sourceColor, Color targetColor, Color maskColor, int threshold)
{
var pixels = new Stack<Point>();
// this basically looks at a 5 by 5 rectangle in all 4 corners of the current rect
// and looks to see if we are all the source color
// basically it just picks good places to start the fill
AddStartLocations(dbMask, rect, pixels, sourceColor, threshold);
while (pixels.Count > 0)
{
var point = pixels.Pop();
if (!rect.Contains(point))
{
continue;
}
if (!dbMask[point].IsSimilarColor(sourceColor, threshold))
{
continue;
}
dbMask[point] = maskColor;
dbDest[point] = targetColor;
pixels.Push(new Point(point.X - 1, point.Y));
pixels.Push(new Point(point.X + 1, point.Y));
pixels.Push(new Point(point.X, point.Y - 1));
pixels.Push(new Point(point.X, point.Y + 1));
}
}
Direct bitmap
public class DirectBitmap : IDisposable
{
public DirectBitmap(int width, int height, PixelFormat pixelFormat = PixelFormat.Format32bppPArgb)
{
Width = width;
Height = height;
Bounds = new Rectangle(0, 0, Width, Height);
Bits = new int[width * height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Bitmap = new Bitmap(width, height, width * 4, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
using (var g = Graphics.FromImage(Bitmap))
{
g.Clear(Color.White);
}
}
public DirectBitmap(Bitmap source)
{
Width = source.Width;
Height = source.Height;
Bounds = new Rectangle(0, 0, Width, Height);
Bits = new int[source.Width * source.Height];
BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
Stride = (int)GetStride(PixelFormat, Width);
Bitmap = new Bitmap(source.Width, source.Height, Stride, PixelFormat.Format32bppPArgb, BitsHandle.AddrOfPinnedObject());
using (var g = Graphics.FromImage(Bitmap))
{
g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height));
}
}
...
I want to crop from an image using user-drawn rectangles on a canvas. The rectangles can be moved, re-sized, and rotated.
When the user selects "Get Cropped Image", the area inside the rectangle should be saved in a second image location on the page, which I can do perfectly well, so long as the rectangle is not rotated. (Straight-forward use of CroppedBitmap.) However, when the rectangle is at an angle I do not know how to perform the crop.
This is what I want to do (forgive my poor MS Paint skills):
My questions are:
1) How do I correctly track or calculate the points of the rectangle?
and,
2) Once I have the points, how do I crop the rotated rectangle?
EDIT:
Thanks to user Rotem, I believe that I have the answer to the second question. Using code modified from the following answers: Answer 1, Answer 2, I am seeing good results. Unfortunately, I am still unable to track the correct location points for the rectangle, so I cannot fully test this as of yet.
public static Bitmap CropRotatedRect(Bitmap source, System.Drawing.Rectangle rect, float angle, bool HighQuality)
{
Bitmap result = new Bitmap((int)rect.Width, (int)rect.Height);
using (Graphics g = Graphics.FromImage(result))
{
g.InterpolationMode = HighQuality ? InterpolationMode.HighQualityBicubic : InterpolationMode.Default;
using (Matrix mat = new Matrix())
{
mat.Translate(-rect.Location.X, -rect.Location.Y);
mat.RotateAt(-(angle), rect.Location);
g.Transform = mat;
g.DrawImage(source, new System.Drawing.Point(0, 0));
}
}
return result;
}
EDIT:
The answer to the first point is much easier than I had originally thought. You can always get the top-left corner of the rectangle by calling—
double top = Canvas.GetTop(rect);
double left = Canvas.GetLeft(rect);
You can then calculate the rest of the points using the width and the height—
Point topLeft = new Point(left, top);
Point topRight = new Point(left + rect.Width, top);
Point bottomLeft = new Point(left, top + rect.Height);
Point bottomRight = new Point(left + rect.Width, top + rect.Height);
Point centerPoint = new Point(left + (rect.Width / 2), top + (rect.Height / 2));
If your rectangle is rotated, then you have to translate these points to determine where they truly lie on the canvas—
public Point TranslatePoint(Point center, Point p, double angle)
{
// get the point relative to (0, 0) by subtracting the center of the rotated shape.
Point relToOrig = new Point(p.X - center.X, p.Y - center.Y);
double angleInRadians = angle * Math.PI / 180;
double sinOfA = Math.Sin(angleInRadians);
double cosOfA = Math.Cos(angleInRadians);
Point translatedPoint = new Point(relToOrig.X * cosOfA - relToOrig.Y * sinOfA,
relToOrig.X * sinOfA + relToOrig.Y * cosOfA);
return new Point(translatedPoint.X + center.X, translatedPoint.Y + center.Y);
}
Once you are able to translate the top-left corner, you can use Rotem's cropping method. You can also calculate the position of the rest of the rectangle, so you are able to determine if the rectangle is within the bounds of the image, if it is touching an edge, or any other thing that you might want to do in regards to the position.
I discovered the answer to my own question(s), and made the appropriate edits along the way. Please see above for the answer.