I have a very simple method that takes a signature as a list of points and draws them as lines on a bitmap. And I want to take that bitmap and save it to a file, but when I call the Save method Im getting "A generic error occured in GDI+" and no Inner Exception.
This seems like pretty straight forward code so Im not sure what the problem is.
using (var b = new Bitmap(width, height))
{
var g = Graphics.FromImage(b);
var lastPoint = points[0];
for (int i = 1; i < points.Length; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
g.Flush();
pictureBox.Image = b;
b.Save("C:\\test.bmp");
}
I've tried saving with all the possible ImageFormats, put the graphics object in a using statement, and as a long shot I even tried:
using (var ms = new MemoryStream())
{
b.Save(ms, ImageFormats.Bmp); // And b.Save(ms, ImageFormats.MemoryBmp);
Image.FromStream(ms).Save("C:\\test.bmp");
}
The strange thing is, if I remove the b.Save (or ignore the exception), the pictureBox displays the image perfectly.
Any help would be appreciated.
I would draw to the PictureBox using the corresponding Graphics from the Paint event and then save to a bitmap:
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
MyDrawing(e.Graphics);
Bitmap b = new Bitmap(pictureBox1.Width, pictureBox1.Height);
pictureBox1.DrawToBitmap(b, pictureBox1.ClientRectangle);
b.Save(#"C:\test.bmp");
}
private void MyDrawing(Graphics g)
{
var lastPoint = points[0];
for (int i = 1; i < points.Count; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
g.Flush();
}
Saved BMP:
Your code has two issues, one hiding the other:
Your application probably doesn't have writing rights on the root of C:, so the Save fails.
You also can't have a PictureBox display a Bitmap which you then destroy in the using block.
So you should change the code to something like this:
var b = new Bitmap(width, height);
using (Graphics g = Graphics.FromImage(b))
{
var lastPoint = points[0];
for (int i = 1; i < points.Length; i++)
{
var p = points[i];
// When the user takes the pen off the device, X/Y is set to -1
if ((lastPoint.X >= 0 || lastPoint.Y >= 0) && (p.X >= 0 || p.Y >= 0))
g.DrawLine(Pens.Black, lastPoint.X, lastPoint.Y, p.X, p.Y);
lastPoint = p;
}
// g.Flush(); not necessary
pictureBox1.Image = b;
b.Save("C:\\temp\\test.bmp");
}
You also should include a check on the Length of the array and consider using a List<Points> instead, also checking its Count and using DrawLines on points.ToArray() for better line joins, especially with thick, semi-transparent lines!
Related
I am getting
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.
every time I want to remove an item from the array, so I could draw (fill) another rectangle at that place.
Everything is happening on MouseDown event on right click black rectangle should be drawn (filled) and on left white one.
I made 2 lists of rectangles, one for white ones, and one for black ones.
I am getting exception thrown when I try to draw (fill) rectangle of opposite color of the one that is.
Everything is written into a bitmap and later bitmap is draw as an image in Paint event.
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
using (Graphics rectGraphics = Graphics.FromImage(rectBitmap))
{
rBlack = new Rectangle((e.X / 20) * 20, (e.Y / 20) * 20, 20, 20);
rectGraphics.SmoothingMode = SmoothingMode.HighSpeed;
foreach (Rectangle r in whiteRectangles) // place where exception is thrown if I want to fill black rectangle on the place where white is
{
if (r.X - 1 == rBlack.X && r.Y - 1 == rBlack.Y)
{
int index = whiteRectangles.IndexOf(r);
whiteRectangles.RemoveAt(index);
}
rectGraphics.FillRectangle(brushWhite, r);
}
blackRectangles.Add(rBlack);
foreach (Rectangle r in blackRectangles)
{
rectGraphics.FillRectangle(brushBlack, r);
}
}
}
if (e.Button == MouseButtons.Left)
{
using (Graphics rectGraphics = Graphics.FromImage(rectBitmap))
{
rWhite = new Rectangle((e.X / 20) * 20 +1, (e.Y / 20) * 20 +1, 19, 19);
rectGraphics.SmoothingMode = SmoothingMode.HighSpeed;
foreach (Rectangle r in blackRectangles) // place where exception is thrown if I try to fill white rectangle on the place where black is
{
if (r.X + 1 == rWhite.X && r.Y + 1 == rWhite.Y)
{
int index = blackRectangles.IndexOf(r);
blackRectangles.RemoveAt(index);
}
rectGraphics.FillRectangle(brushBlack, r);
}
whiteRectangles.Add(rWhite);
foreach (Rectangle r in whiteRectangles)
{
rectGraphics.FillRectangle(brushWhite, r);
}
}
}
this.Refresh();
}
You probably need to use a for loop instead of a foreach. Set your upper limit using the Count of the list, then delete by index.
for (int i = 0; i < list.Count; i++)
{
//Delete list[i] here
}
Or, in reverse
for (int i = list.Count - 1; i >= 0; i--)
{
//Delete list[i] here
}
In my previous question, I transformed this image:
into this:
which Tesseract OCR interprets as this:
1O351
Putting a frame around the image
actually improves the OCR result.
1CB51
However, I need all 5 characters to OCR correctly, so as an experiment I used Paint.NET to rotate and align each individual letter into its proper orientation:
Resulting in the correct answer:
1CB52
How would I go about performing this correction in C#?
I've done a bit of research on various text alignment algorithms, but they all assume the existence of lines of text in the source image, lines from which you can derive a rotation angle, but which already contain the proper spacing and orientation relationships between the letters.
You can use the code in the following code project article to segment each individual character. However, when trying to deskew these characters individually any result you get is not going to be very good because there isn't very much information to go off of.
I tried using AForge.NETs HoughLineTransformation class and I got angles in the range of 80 - 90 degrees. So I tried using the following code to deskew them:
private static Bitmap DeskewImageByIndividualChars(Bitmap targetBitmap)
{
IDictionary<Rectangle, Bitmap> characters = new CCL().Process(targetBitmap);
using (Graphics g = Graphics.FromImage(targetBitmap))
{
foreach (var character in characters)
{
double angle;
BitmapData bitmapData = character.Value.LockBits(new Rectangle(Point.Empty, character.Value.Size), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
try
{
HoughLineTransformation hlt = new HoughLineTransformation();
hlt.ProcessImage(bitmapData);
angle = hlt.GetLinesByRelativeIntensity(0.5).Average(l => l.Theta);
}
finally
{
character.Value.UnlockBits(bitmapData);
}
using (Bitmap bitmap = RotateImage(character.Value, 90 - angle, Color.White))
{
g.DrawImage(bitmap, character.Key.Location);
}
}
}
return targetBitmap;
}
With the RotateImage method taken from here. However, the results didn't seem to be the best. Maybe you can try and make them better.
Here is the code from the code project article for your reference. I have made a few changes to it so that it behaves a bit safer, such as adding try-finally around the LockBits and disposing of objects properly using the using statement etc.
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
namespace ConnectedComponentLabeling
{
public class CCL
{
private Bitmap _input;
private int[,] _board;
public IDictionary<Rectangle, Bitmap> Process(Bitmap input)
{
_input = input;
_board = new int[_input.Width, _input.Height];
Dictionary<int, List<Pixel>> patterns = Find();
var images = new Dictionary<Rectangle, Bitmap>();
foreach (KeyValuePair<int, List<Pixel>> pattern in patterns)
{
using (Bitmap bmp = CreateBitmap(pattern.Value))
{
images.Add(GetBounds(pattern.Value), (Bitmap)bmp.Clone());
}
}
return images;
}
protected virtual bool CheckIsBackGround(Pixel currentPixel)
{
return currentPixel.color.A == 255 && currentPixel.color.R == 255 && currentPixel.color.G == 255 && currentPixel.color.B == 255;
}
private unsafe Dictionary<int, List<Pixel>> Find()
{
int labelCount = 1;
var allLabels = new Dictionary<int, Label>();
BitmapData imageData = _input.LockBits(new Rectangle(0, 0, _input.Width, _input.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
int bytesPerPixel = 3;
byte* scan0 = (byte*)imageData.Scan0.ToPointer();
int stride = imageData.Stride;
for (int i = 0; i < _input.Height; i++)
{
byte* row = scan0 + (i * stride);
for (int j = 0; j < _input.Width; j++)
{
int bIndex = j * bytesPerPixel;
int gIndex = bIndex + 1;
int rIndex = bIndex + 2;
byte pixelR = row[rIndex];
byte pixelG = row[gIndex];
byte pixelB = row[bIndex];
Pixel currentPixel = new Pixel(new Point(j, i), Color.FromArgb(pixelR, pixelG, pixelB));
if (CheckIsBackGround(currentPixel))
{
continue;
}
IEnumerable<int> neighboringLabels = GetNeighboringLabels(currentPixel);
int currentLabel;
if (!neighboringLabels.Any())
{
currentLabel = labelCount;
allLabels.Add(currentLabel, new Label(currentLabel));
labelCount++;
}
else
{
currentLabel = neighboringLabels.Min(n => allLabels[n].GetRoot().Name);
Label root = allLabels[currentLabel].GetRoot();
foreach (var neighbor in neighboringLabels)
{
if (root.Name != allLabels[neighbor].GetRoot().Name)
{
allLabels[neighbor].Join(allLabels[currentLabel]);
}
}
}
_board[j, i] = currentLabel;
}
}
}
finally
{
_input.UnlockBits(imageData);
}
Dictionary<int, List<Pixel>> patterns = AggregatePatterns(allLabels);
patterns = RemoveIntrusions(patterns, _input.Width, _input.Height);
return patterns;
}
private Dictionary<int, List<Pixel>> RemoveIntrusions(Dictionary<int, List<Pixel>> patterns, int width, int height)
{
var patternsCleaned = new Dictionary<int, List<Pixel>>();
foreach (var pattern in patterns)
{
bool bad = false;
foreach (Pixel item in pattern.Value)
{
//Horiz
if (item.Position.X == 0)
bad = true;
else if (item.Position.Y == width - 1)
bad = true;
//Vert
else if (item.Position.Y == 0)
bad = true;
else if (item.Position.Y == height - 1)
bad = true;
}
if (!bad)
patternsCleaned.Add(pattern.Key, pattern.Value);
}
return patternsCleaned;
}
private IEnumerable<int> GetNeighboringLabels(Pixel pix)
{
var neighboringLabels = new List<int>();
for (int i = pix.Position.Y - 1; i <= pix.Position.Y + 2 && i < _input.Height - 1; i++)
{
for (int j = pix.Position.X - 1; j <= pix.Position.X + 2 && j < _input.Width - 1; j++)
{
if (i > -1 && j > -1 && _board[j, i] != 0)
{
neighboringLabels.Add(_board[j, i]);
}
}
}
return neighboringLabels;
}
private Dictionary<int, List<Pixel>> AggregatePatterns(Dictionary<int, Label> allLabels)
{
var patterns = new Dictionary<int, List<Pixel>>();
for (int i = 0; i < _input.Height; i++)
{
for (int j = 0; j < _input.Width; j++)
{
int patternNumber = _board[j, i];
if (patternNumber != 0)
{
patternNumber = allLabels[patternNumber].GetRoot().Name;
if (!patterns.ContainsKey(patternNumber))
{
patterns[patternNumber] = new List<Pixel>();
}
patterns[patternNumber].Add(new Pixel(new Point(j, i), Color.Black));
}
}
}
return patterns;
}
private unsafe Bitmap CreateBitmap(List<Pixel> pattern)
{
int minX = pattern.Min(p => p.Position.X);
int maxX = pattern.Max(p => p.Position.X);
int minY = pattern.Min(p => p.Position.Y);
int maxY = pattern.Max(p => p.Position.Y);
int width = maxX + 1 - minX;
int height = maxY + 1 - minY;
Bitmap bmp = DrawFilledRectangle(width, height);
BitmapData imageData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
try
{
byte* scan0 = (byte*)imageData.Scan0.ToPointer();
int stride = imageData.Stride;
foreach (Pixel pix in pattern)
{
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride] = pix.color.B;
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride + 1] = pix.color.G;
scan0[((pix.Position.X - minX) * 3) + (pix.Position.Y - minY) * stride + 2] = pix.color.R;
}
}
finally
{
bmp.UnlockBits(imageData);
}
return bmp;
}
private Bitmap DrawFilledRectangle(int x, int y)
{
Bitmap bmp = new Bitmap(x, y);
using (Graphics graph = Graphics.FromImage(bmp))
{
Rectangle ImageSize = new Rectangle(0, 0, x, y);
graph.FillRectangle(Brushes.White, ImageSize);
}
return bmp;
}
private Rectangle GetBounds(List<Pixel> pattern)
{
var points = pattern.Select(x => x.Position);
var x_query = points.Select(p => p.X);
int xmin = x_query.Min();
int xmax = x_query.Max();
var y_query = points.Select(p => p.Y);
int ymin = y_query.Min();
int ymax = y_query.Max();
return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
}
}
}
With the above code I got the following input/output:
As you can see the B has rotated quite well but the others aren't as good.
An alternative to trying to deskew the individual characters is to find there location using the segmentation routine above. Then passing each individual character through to your recognition engine separately and seeing if this improves your results.
I have used the following method to find the angle of the character using the List<Pixel> from inside the CCL class. It works by finding the angle between the "bottom left" and "bottom right" points. I haven't tested if it works if the character is rotated the other way around.
private double GetAngle(List<Pixel> pattern)
{
var pixels = pattern.Select(p => p.Position).ToArray();
Point bottomLeft = pixels.OrderByDescending(p => p.Y).ThenBy(p => p.X).First();
Point rightBottom = pixels.OrderByDescending(p => p.X).ThenByDescending(p => p.Y).First();
int xDiff = rightBottom.X - bottomLeft.X;
int yDiff = rightBottom.Y - bottomLeft.Y;
double angle = Math.Atan2(yDiff, xDiff) * 180 / Math.PI;
return -angle;
}
Note my drawing code is a bit broken so that is why the 5 is cut off on the right but this code produces the following output:
Note that the B and the 5 are rotated further than you'd expect because of their curvature.
Using the following code by getting the angle from the left and right edges and then choosing the best one, the rotations seems to be better. Note I have only tested it with letters that need rotating clockwise so if they need to go the opposite way it might not work too well.
This also "quadrants" the pixels so that each pixel is chosen from it's own quadrant as not to get two that are too nearby.
The idea in selecting the best angle is if they are similar, at the moment within 1.5 degrees of each other but can easily be updated, average them. Else we pick the one that is closest to zero.
private double GetAngle(List<Pixel> pattern, Rectangle bounds)
{
int halfWidth = bounds.X + (bounds.Width / 2);
int halfHeight = bounds.Y + (bounds.Height / 2);
double leftEdgeAngle = GetAngleLeftEdge(pattern, halfWidth, halfHeight);
double rightEdgeAngle = GetAngleRightEdge(pattern, halfWidth, halfHeight);
if (Math.Abs(leftEdgeAngle - rightEdgeAngle) <= 1.5)
{
return (leftEdgeAngle + rightEdgeAngle) / 2d;
}
if (Math.Abs(leftEdgeAngle) > Math.Abs(rightEdgeAngle))
{
return rightEdgeAngle;
}
else
{
return leftEdgeAngle;
}
}
private double GetAngleLeftEdge(List<Pixel> pattern, double halfWidth, double halfHeight)
{
var topLeftPixels = pattern.Select(p => p.Position).Where(p => p.Y < halfHeight && p.X < halfWidth).ToArray();
var bottomLeftPixels = pattern.Select(p => p.Position).Where(p => p.Y > halfHeight && p.X < halfWidth).ToArray();
Point topLeft = topLeftPixels.OrderBy(p => p.X).ThenBy(p => p.Y).First();
Point bottomLeft = bottomLeftPixels.OrderByDescending(p => p.Y).ThenBy(p => p.X).First();
int xDiff = bottomLeft.X - topLeft.X;
int yDiff = bottomLeft.Y - topLeft.Y;
double angle = Math.Atan2(yDiff, xDiff) * 180 / Math.PI;
return 90 - angle;
}
private double GetAngleRightEdge(List<Pixel> pattern, double halfWidth, double halfHeight)
{
var topRightPixels = pattern.Select(p => p.Position).Where(p => p.Y < halfHeight && p.X > halfWidth).ToArray();
var bottomRightPixels = pattern.Select(p => p.Position).Where(p => p.Y > halfHeight && p.X > halfWidth).ToArray();
Point topRight = topRightPixels.OrderBy(p => p.Y).ThenByDescending(p => p.X).First();
Point bottomRight = bottomRightPixels.OrderByDescending(p => p.X).ThenByDescending(p => p.Y).First();
int xDiff = bottomRight.X - topRight.X;
int yDiff = bottomRight.Y - topRight.Y;
double angle = Math.Atan2(xDiff, yDiff) * 180 / Math.PI;
return Math.Abs(angle);
}
This now produces the following output, again my drawing code is slightly broken. Note that the C looks to not have deskewed very well but looking closely it is just the shape of it that has caused this to happen.
I improved the drawing code and also attempted to get the characters onto the same baseline:
private static Bitmap DeskewImageByIndividualChars(Bitmap bitmap)
{
IDictionary<Rectangle, Tuple<Bitmap, double>> characters = new CCL().Process(bitmap);
Bitmap deskewedBitmap = new Bitmap(bitmap.Width, bitmap.Height, bitmap.PixelFormat);
deskewedBitmap.SetResolution(bitmap.HorizontalResolution, bitmap.VerticalResolution);
using (Graphics g = Graphics.FromImage(deskewedBitmap))
{
g.FillRectangle(Brushes.White, new Rectangle(Point.Empty, deskewedBitmap.Size));
int baseLine = characters.Max(c => c.Key.Bottom);
foreach (var character in characters)
{
int y = character.Key.Y;
if (character.Key.Bottom != baseLine)
{
y += (baseLine - character.Key.Bottom - 1);
}
using (Bitmap characterBitmap = RotateImage(character.Value.Item1, character.Value.Item2, Color.White))
{
g.DrawImage(characterBitmap, new Point(character.Key.X, y));
}
}
}
return deskewedBitmap;
}
This then produces the following output. Note each character isn't on the exact same baseline due to the pre rotation bottom being taken to work it out. To improve the code using the baseline from post rotation would be needed. Also thresholding the image before doing the baseline would help.
Another improvement would be to calculate the Right of each of the rotated characters locations so when drawing the next one it doesn't overlap the previous and cut bits off. Because as you can see in the output the 2 is slightly cutting into the 5.
The output is now very similar to the manually created one in the OP.
how would I go about filling the space between two rectangles when they are not vertically aligned. So if one rectangle moves up the gap between it and the next rectangle (In a chain which all follow the same path) looks smooth.
Example:
Current:
Desired:
My current drawing code:
public override void Draw(Graphics g)
{
for (int i = 0; i < this._Children.Count; i++)
{
this._Children[i].Draw(g);
if (i != 0 && i + 1 < this._Children.Count)
{
if (this._Children[i].Y != this._Children[i + 1].Y)
{
//Draw circle in gap between?
}
}
}
base.Draw(g);
}
Underlying draw:
g.FillRectangle(this.Color, this.X, this.Y, this.Size.Width, this.Size.Height);
g.DrawRectangle(this.Outline, this.X, this.Y, this.Size.Width, this.Size.Height);
Edit:
After following Jim and commenter's advice, I came up with the following. The maths seems to be wrong and it doesn't really get the effect I wanted at all though.
GraphicsPath path = new GraphicsPath();
for (int i = 0; i < this._Children.Count; i++)
{
path.AddRectangle(this._Children[i].GetBoundingBox());
if (i < this._Children.Count)
{
Block block = i == 0 ? (Block)this : this._Children[i - 1];
float x = this._Children[i].X < block.X ? this._Children[i].X : block.X;
float y = this._Children[i].Y > block.X ? this._Children[i].Y : block.Y;
float width = System.Math.Abs(this._Children[i].X - block.X);
float height = System.Math.Abs(this._Children[i].Y - block.Y);
path.AddEllipse(x, y, width, height);
}
}
g.FillPath(this._Children[0].Color, path);
g.DrawPath(this._Children[0].Outline, path);
base.Draw(g);
Edit 2: Been following everyone's advice and editing as I go along. Jim's works now but it only draws at all if you are moving up and you start moving right.
And now with TAW's advice I get ArguementInvalid, I assume this is because the rGap rectangle's height is 0.
My implementation of TAW's:
for (int i = 0; i < this._Children.Count; i++)
{
this._Children[i].Draw(g);
if (i + 1 < this._Children.Count)
{
Block block = i == 0 ? (Block)this : this._Children[i + 1];
Rectangle rec = this._Children[i].GetBoundingBox();
Rectangle rec2 = block.GetBoundingBox();
Rectangle rGap = new Rectangle(Math.Min(rec.X, rec2.X), Math.Min(rec.Y, rec2.Y), 2 * Math.Abs(rec.Left - rec2.Left), 2 * Math.Abs(rec2.Top - rec.Top));
GraphicsPath gp = new GraphicsPath();
gp.AddRectangle(rec);
gp.AddRectangle(rec2);
gp.AddArc(rGap, 0, 360);
gp.FillMode = FillMode.Winding;
g.DrawPath(this._Children[i].Outline, gp);
g.FillPath(this._Children[i].Color, gp);
}
}
base.Draw(g);
Edit 3:
I have developed my own solution after studying the problem a little more, it's not the solution I wanted but hopefully it should help someone else. Now it just needs converting to rounded corners.
Code:
for (int i = 0; i < this._Children.Count; i++)
{
this._Children[i].Draw(g);
Block block = i - 1 < 0 ? (Block)this : this._Children[i - 1];
Rectangle rec = this._Children[i].GetBoundingBox();
Rectangle rec2 = block.GetBoundingBox();
Direction dir = this._Children[i].GetDirection(true);
Direction dir2 = block.GetDirection(true);
int minX = Math.Min(rec.X, rec2.X);
int minY = Math.Min(rec.Y, rec2.Y);
int maxX = Math.Max(rec.X, rec2.X);
int maxY = Math.Max(rec.Y, rec2.Y);
int diffX = maxX - minX;
int diffY = maxY - minY;
int width = this._Children[i].Size.Width;
int height = this._Children[i].Size.Height;
Rectangle fillRec = default(Rectangle);
if ((dir == Direction.Right && dir2 == Direction.Down) || (dir == Direction.Up && dir2 == Direction.Left))
{
fillRec = new Rectangle(minX + width, minY, diffX, diffY);
}
else if ((dir == Direction.Down && dir2 == Direction.Left) || (dir == Direction.Right && dir2 == Direction.Up))
{
fillRec = new Rectangle(minX + width, (maxY + height) - diffY, diffX, diffY);
}
else if ((dir == Direction.Up && dir2 == Direction.Right) || (dir == Direction.Left && dir2 == Direction.Down))
{
fillRec = new Rectangle(minX, minY, diffX, diffY);
}
else if ((dir == Direction.Left && dir2 == Direction.Up) || (dir == Direction.Down && dir2 == Direction.Right))
{
fillRec = new Rectangle(minX, (maxY + height) - diffY, diffX, diffY);
}
if (fillRec != default(Rectangle))
{
g.FillRectangle(this._Children[i].Color, fillRec);
g.DrawRectangle(this._Children[i].Outline, fillRec);
}
}
base.Draw(g);
Produces:
Here is a solution that draws all four rounded corneres for four rectangles.
I create four rectangles overlapping with various gaps and add them and the arcs to fill the gaps to a GraphicsPath.
GraphicsPath GP = new GraphicsPath();
Rectangle fillGap(Rectangle R1, Rectangle R2, bool isTop, bool isLeft )
{
int LeftMin = Math.Min(R1.Left, R2.Left);
int RightMax = Math.Max(R1.Right, R2.Right);
int TopMin = Math.Min(R1.Top, R2.Top);
int BotMax = Math.Max(R1.Bottom, R2.Bottom);
int RightGap = 2 * Math.Abs(R1.Right - R2.Right);
int LeftGap = 2 * Math.Abs(R1.Left - R2.Left);
int TopGap = 2 * Math.Abs(R1.Top - R2.Top);
int BotGap = 2 * Math.Abs(R1.Bottom - R2.Bottom);
Rectangle R = Rectangle.Empty;
if (isTop && isLeft) R = new Rectangle(LeftMin, TopMin, LeftGap, TopGap);
if (isTop && !isLeft)
R = new Rectangle(RightMax - RightGap, TopMin, RightGap, TopGap);
if (!isTop && !isLeft)
R = new Rectangle(RightMax - RightGap, BotMax - BotGap , RightGap, BotGap );
if (!isTop && isLeft)
R = new Rectangle(LeftMin, BotMax - BotGap , LeftGap, BotGap );
return R;
}
private void button1_Click(object sender, EventArgs e)
{
Rectangle Rtop = new Rectangle(20, 10, 200, 40);
Rectangle Rbottom = new Rectangle(20, 200, 200, 40);
Rectangle Rleft = new Rectangle(10, 20, 40, 200);
Rectangle Rright = new Rectangle(210, 20, 40, 200);
GP = new GraphicsPath();
GP.FillMode = FillMode.Winding;
GP.AddRectangle(Rtop);
GP.AddRectangle(Rleft);
GP.AddRectangle(Rbottom);
GP.AddRectangle(Rright);
GP.AddArc(fillGap(Rtop, Rleft, true, true), 0, 360);
GP.AddArc(fillGap(Rtop, Rright, true, false), 0, 360);
GP.AddArc(fillGap(Rbottom, Rleft, false, true), 0, 360);
GP.AddArc(fillGap(Rbottom, Rright, false, false), 0, 360);
panel1.Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Black, 1.5f))
{
e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
e.Graphics.DrawPath(pen, GP);
if(checkBox1.Checked) e.Graphics.FillPath(Brushes.Red, GP);
}
}
The code now assumes that your gaps are not wider than the rectanlges.
The Pen for the outline should not be smaller that 1.5f or the filling will overlap too much.
Also the Smoothing mode should be high quality, so no pixels get lost.
Here is how it looks : Drawn & filled:
You can't necessarily draw a circle in the space unless the space is square. But you can draw an ellipse.
What you need to do:
Determine extents of the rectangle in question. The center point is the point where the two rectangles meet (the bottom right part of the blank space you want to fill. The top left is the X coordinate of the left rectangle and the Y coordinate of the top rectangle. The width would be 2*(center.X - left) and the height would be 2*(center.Y - top).
Fill an ellipse in that rectangle.
Note that the above will have the effect of drawing the top-left curved portion. It won't fully fill the overlapping rectangle below.
If you want to get rid of the lines, draw a filled rectangle without a border (actually, with the border the same color as the fill color) in the overlapping space. Then draw the filled ellipse as described above, again without a border.
To draw the border of the ellipse, look into DrawArc.
I have this image:
and I wrote a code that supposed to crop only the part with the black dots(the code is built for 1-color images only),
without all of the transparent pixels around the dots,
and then return the image after the crop,
but for some reason, when it gets to a black pixel,
it does not recognize that this is a black pixel,
and because of that, it skips the 'if' statement.
Here is the code:
private Image cropTransparency(Image image)
{
Bitmap imageCrop = new Bitmap(image);
imageCrop.Save(#"C:\Users\Nehoray\Desktop\Test.png");
Point min = new Point(imageCrop.Width, imageCrop.Height);
Point max = new Point(imageCrop.Width, imageCrop.Height);
for (int w = 0; w < imageCrop.Width; w++)
{
//'w' stands for Width
for (int h = 0; h < imageCrop.Height; h++)
{
//'h' stands for Height
Color check = imageCrop.GetPixel(w, h);
if (check == Color.Black)
{
MessageBox.Show("Found a white pixel!");
if (w < min.X)
{
min.X = w;
}
if (h < min.Y)
{
min.Y = h;
}
if (w > max.X)
{
max.X = w;
}
if (h > max.Y)
{
max.Y = h;
}
}
}
}
imageCrop = new Bitmap(max.X - min.X, max.Y - min.Y);
Graphics g = Graphics.FromImage(imageCrop);
Rectangle cropRect = new Rectangle(new Point(0, 0), new Size(max.X - min.X, max.Y - min.Y));
g.DrawImage(image, new Rectangle(0, 0, max.X - min.X, max.Y - min.Y), cropRect, GraphicsUnit.Pixel);
g.Save();
return imageCrop;
}
If you find out why it does not recognize when there is a black pixel, please let me know..
thanks anyway :)
There are quite a few issues with this code:
Point max = new Point(imageCrop.Width, imageCrop.Height);
How will a point ever be greater than the max, when the max is initialised to the maximum value? This should be (0,0)
Color check = imageCrop.GetPixel(w, h);
if (check == Color.Black)
I'm not sure this does what you think it will. You have a 32-bit image, with an alpha channel, so you need to take the alpha values into account. Also, you're comparing against a predefined colour which has a reference that won't match your pixel even if all 4 channels are a match. You possibly just want to check for the alpha component being non-zero. If you only compare the colour channels, be aware that transparent pixels may well have a matching colour, producing unexpected results.
Rectangle cropRect = new Rectangle(new Point(0, 0), new Size(max.X - min.X, max.Y - min.Y));
Why are you cropping from 0,0? Your rectangle should begin at min.X, min.Y
g.Save();
This doesn't save the image, you know that right? You save the image, unmodified at the start of your code, and then never re-save it once you've cropped it (I assume this stuff, including the hard-coded path, is for debug, but even then it seems you probably meant to write the image here)
You are comparing: (check == Color.Black) which means: is the reference check pointing to the same instance as the reference Color.Black --> this will never be true.
you have to compare the actual color:
(check.ToArgb() == Color.Black.ToArgb())
private Image cropTransparency(Image image)
{
Bitmap imageCrop = new Bitmap(image); // aca paso la imagen original a Bitmap
//imageCrop.Save(#"tmp.png");
Point min = new Point(imageCrop.Width, imageCrop.Height);
Point max = new Point(0, 0);
for (int w = 0; w < imageCrop.Width; w++)
{
//'w' stands for Width
for (int h = 0; h < imageCrop.Height; h++)
{
//'h' stands for Height
Color check = imageCrop.GetPixel(w, h);
if (check == Color.FromArgb(255, 0, 0, 0))
{
Console.WriteLine("Found a white pixel!");
if (w < min.X)
{
min.X = w;
}
if (h < min.Y)
{
min.Y = h;
}
if (w > max.X)
{
max.X = w;
}
if (h > max.Y)
{
max.Y = h;
}
}
}
}
imageCrop = new Bitmap(max.X - min.X, max.Y - min.Y);
Graphics g = Graphics.FromImage(imageCrop);
Rectangle cropRect = new Rectangle(new Point(min.X,min.Y), new Size(max.X - min.X, max.Y - min.Y));
g.DrawImage(image, new Rectangle(0, 0, max.X - min.X, max.Y - min.Y), cropRect, GraphicsUnit.Pixel);
g.Save();
return imageCrop;
}
Scenario:
1) Program going to draw a string (commonly a single character) on a bitmap:
protected void DrawCharacter(string character, Font font)
{
if(string.IsNullOrEmpty(character))
character = ".";
FontFamily f = new FontFamily(FontName);
bitmap = new Bitmap((int)(font.Size * 2f), (int)font.Height);
Graphics g = Graphics.FromImage(bitmap);
g.Clear(Color.White);
g.DrawString(character, font, Brushes.Black, DrawPoint);
}
2) Using following algorithm we get all black pixels position:
public Int16[] GetColoredPixcels(Bitmap bmp, bool useGeneric)
{
List<short> pixels = new List<short>();
int x = 0, y = 0;
do
{
Color c = bmp.GetPixel(x, y);
if (c.R == 0 && c.G == 0 && c.B == 0)
pixels.Add((Int16)(x + (y + 1) * bmp.Width));
if (x == bmp.Width - 1)
{
x = 0;
y++;
}
else
x++;
} while (y < bmp.Height);
return pixels.ToArray();
}
Problem occurs when input character is a single point (.). I don't know what's happening in bitmap object while processing function bmp.GetPixel(x, y), because it can't find point position! Output array claims bitmap has no black point! But when input string is (:) program can find pixels position properly!
Any suggestion or guid?
thanks in advance...
I suspect that anti-aliasing means that the pixel for "." isn't completely black. Why not change your condition to just pick "very dark" pixels?
private const int Threshold = 10;
...
if (c.R < Threshold && c.G < Threshold && c.B < Threshold)