Cut transparent parts image - c#

The updated image:
thanks you very much man but i wanted to do somthing little different, to cut each rectangle here as a sperate image.Lets try first to find the blue block Bounds. Sounds hard but its actually simple.
look when i have done so far:
private unsafe Bitmap CodeImage(Bitmap bmp)
{
Bitmap bmpRes = new Bitmap(bmp.Width, bmp.Height);
BitmapData bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr scan0 = bmData.Scan0;
int stride = bmData.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
int minX = 10000 ;
int maxX = -10000;
int minY = 10000;
var maxY = -10000;
bool found = false;
for (int y = 0; y < nHeight; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
for (int x = 0; x < nWidth; x++)
{
if (p[3]!=0) //Check if pixel is transpert;
{
found = true;
if (x < minX)
minX = x;
if (y < minY)
minY = y;
if (x > maxX)
maxX = x;
if (y > maxY)
maxY = y;
}
else
{
if (found)
{
Rectangle temp = new Rectangle(minX, minY, maxX - minX, maxY-minY);
return bmp.Clone(temp, bmp.PixelFormat);
}
}
p += 4;
}
}
return null;
}
you actually was write and i should calculate the width like this :
int width = maxX - minX; and it actually works.. but the height is 0....
try this out man its outputing almost correct rectangle with these bounds:
(200,800,400, and 0 on the height).
i just used parts of your code in my algorithm and yea you were right but now there is a little problem with the height i' would very appreciate if you will have a look

I'll write this in pseudo-ish-code since you should be able then to apply it to any type of image or language...
var minX = 10000
var maxX = -10000
var minY = 10000
var maxY = -10000
for (var y = 0 to height)
{
for (var x = 0 to width)
{
if (pixel(x,y).Color != transparent)
{
if (x < minX) minX = x
if (y < minY) minY = y
if (x > maxX) maxX = x
if (y > maxY) maxY = y
}
}
}
var cropRectangle = (minX, minY, maxX, maxY)
You can now use standard functions on the bitmap to get that area, which should be the area bounded by the non-transparent pixels.

Related

Trimming Bitmap Causes Cutting of Text at Bottom

I'm using the following code to remove whitespace around an image.
static Bitmap TrimBitmap(Bitmap source)
{
Rectangle srcRect = default(Rectangle);
BitmapData data = null;
try
{
data = source.LockBits(new Rectangle(0, 0, source.Width, source.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] buffer = new byte[data.Height * data.Stride];
Marshal.Copy(data.Scan0, buffer, 0, buffer.Length);
int xMin = int.MaxValue,
xMax = int.MinValue,
yMin = int.MaxValue,
yMax = int.MinValue;
bool foundPixel = false;
// Find xMin
for (int x = 0; x < data.Width; x++)
{
bool stop = false;
for (int y = 0; y < data.Height; y++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
xMin = x;
stop = true;
foundPixel = true;
break;
}
}
if (stop)
break;
}
// Image is empty...
if (!foundPixel)
return null;
// Find yMin
for (int y = 0; y < data.Height; y++)
{
bool stop = false;
for (int x = xMin; x < data.Width; x++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
yMin = y;
stop = true;
break;
}
}
if (stop)
break;
}
// Find xMax
for (int x = data.Width - 1; x >= xMin; x--)
{
bool stop = false;
for (int y = yMin; y < data.Height; y++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
xMax = x;
stop = true;
break;
}
}
if (stop)
break;
}
// Find yMax
for (int y = data.Height - 1; y >= yMin; y--)
{
bool stop = false;
for (int x = xMin; x <= xMax; x++)
{
byte alpha = buffer[y * data.Stride + 4 * x + 3];
if (alpha != 0)
{
yMax = y;
stop = true;
break;
}
}
if (stop)
break;
}
srcRect = Rectangle.FromLTRB(xMin, yMin, xMax , yMax);
}
finally
{
if (data != null)
source.UnlockBits(data);
}
Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(dest))
{
graphics.DrawImage(source, destRect, srcRect, GraphicsUnit.Pixel);
}
return dest;
}
I'm trying to trim a Bitmap with Text Drawn on it.The Proper Image should look like this after trimming
But after trimming i get the following result ..the bottom portion clipped off
What i'm i doing wrong? Please advice..
This is actually a problem with Rectangle.FromLTRB !
Looking closer at the images you will find that you have actually lost only one row of pixels. (The strong magnification had me fooled for a while..)
The algorithm to determine the Height (and Width) of the rectangle is basically right, but off by one.
If you use this
srcRect = Rectangle.FromLTRB(xMin, yMin, xMax + 1 , yMax + 1);
or this:
srcRect = new Rectangle(xMin, yMin, xMax - xMin + 1 , yMax - yMin + 1);
it should work.
You can test with pen and paper: Say: 1st pixel row with a color = 4, first from bottom on a 10 pixel square: 8, that makes 5 not 4 net data: 4,5,6,7,8.
Do note that this issue is inherent in FromLTRB:
Rectangle myRectangle = Rectangle.FromLTRB(0, 0, 10, 10);
..results in a Rectangle with Height=10 even though 0..10 should cover 11 pixels rows! So the Right-Bottom coordinate is actually excluded from the result!!
I think the whole issue with rectangles being off by one stems from legacy ways to use a Pen with its alignment. When using the same rectangles to fill with a Brush all works as expected.

C# scanning image code improvement

I'm working on a screen sharing app, which runs a loop and grab fast screenshots using GDI methods . example here
Of course I also use a flood fill algorithm to find the changes areas between 2 images (previous screenshot and current).
I use another small trick - I downscale the snapshot resolution in 10, because processing 1920*1080=2073600 pixels very constantly is not very efficient.
However when I find the rectangle bounds - I apply it on the original full size bitmap and I just multiply by 10 the dimension (including top, left, width, height).
This is the scanning code:
unsafe bool ArePixelsEqual(byte* p1, byte* p2, int bytesPerPixel)
{
for (int i = 0; i < bytesPerPixel; ++i)
if (p1[i] != p2[i])
return false;
return true;
}
private unsafe List<Rectangle> CodeImage(Bitmap bmp, Bitmap bmp2)
{
List<Rectangle> rec = new List<Rectangle>();
var bmData1 = bmp.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
var bmData2 = bmp2.LockBits(new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp2.PixelFormat);
int bytesPerPixel = 4;
IntPtr scan01 = bmData1.Scan0;
IntPtr scan02 = bmData2.Scan0;
int stride1 = bmData1.Stride;
int stride2 = bmData2.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
bool[] visited = new bool[nWidth * nHeight];
byte* base1 = (byte*)scan01.ToPointer();
byte* base2 = (byte*)scan02.ToPointer();
for (int y = 0; y < nHeight; y ++)
{
byte* p1 = base1;
byte* p2 = base2;
for (int x = 0; x < nWidth; ++x)
{
if (!ArePixelsEqual(p1, p2, bytesPerPixel) && !(visited[x + nWidth * y]))
{
// fill the different area
int minX = x;
int maxX = x;
int minY = y;
int maxY = y;
var pt = new Point(x, y);
Stack<Point> toBeProcessed = new Stack<Point>();
visited[x + nWidth * y] = true;
toBeProcessed.Push(pt);
while (toBeProcessed.Count > 0)
{
var process = toBeProcessed.Pop();
var ptr1 = (byte*)scan01.ToPointer() + process.Y * stride1 + process.X * bytesPerPixel;
var ptr2 = (byte*)scan02.ToPointer() + process.Y * stride2 + process.X * bytesPerPixel;
//Check pixel equality
if (ArePixelsEqual(ptr1, ptr2, bytesPerPixel))
continue;
//This pixel is different
//Update the rectangle
if (process.X < minX) minX = process.X;
if (process.X > maxX) maxX = process.X;
if (process.Y < minY) minY = process.Y;
if (process.Y > maxY) maxY = process.Y;
Point n; int idx;
//Put neighbors in stack
if (process.X - 1 >= 0)
{
n = new Point(process.X - 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.X + 1 < nWidth)
{
n = new Point(process.X + 1, process.Y); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y - 1 >= 0)
{
n = new Point(process.X, process.Y - 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
if (process.Y + 1 < nHeight)
{
n = new Point(process.X, process.Y + 1); idx = n.X + nWidth * n.Y;
if (!visited[idx]) { visited[idx] = true; toBeProcessed.Push(n); }
}
}
//finaly set a rectangle.
Rectangle r = new Rectangle(minX * 10, minY * 10, (maxX - minX + 1) * 10, (maxY - minY + 1) * 10);
rec.Add(r);
//got the rectangle now i'll do whatever i want with that.
//notify i scaled everything by x10 becuse i want to apply the changes on the originl 1920x1080 image.
}
p1 += bytesPerPixel;
p2 += bytesPerPixel;
}
base1 += stride1;
base2 += stride2;
}
bmp.UnlockBits(bmData1);
bmp2.UnlockBits(bmData2);
return rec;
}
This is my call:
private void Start()
{
full1 = GetDesktopImage();//the first,intial screen.
while (true)
{
full2 = GetDesktopImage();
a = new Bitmap(full1, 192, 108);//resizing for faster processing the images.
b = new Bitmap(full2, 192, 108); // resizing for faster processing the images.
CodeImage(a, b);
count++; // counter for the performance.
full1 = full2; // assign old to current bitmap.
}
}
However, after all the tricks and techniques I used, the algorithm runs quite slow... on my machine - Intel i5 4670k 3.4ghz - it runs only 20 times (at the maximum! It might get lower)! It maybe sounds fast (don't forget I have to send each changed area over the network after), but I'm looking to achieve more processed image per second. I think the main bottleneck is in the resizing of the 2 images - but I just thought it would be even faster after resizing - because it would have to loop through less pixels... 192*108=200,000 only..
I would appreciate any help, any improvement. Thanks.

Calculating the bounding rectangle on a binary image

I'm attempting to get the bounds of the edges of interest in a binary image using the following method. Sadly my maths seems to be letting me down and I'm only getting a rectangle 2px smaller in each dimension than the original image.
Can someone show me where I have gone wrong?
Note. FastBitmap is a class that allows fast access to pixel data.
private Rectangle FindBox(Bitmap bitmap, byte indexToRemove)
{
int width = bitmap.Width;
int height = bitmap.Height;
int minX = width;
int minY = height;
int maxX = 0;
int maxY = 0;
using (FastBitmap fastBitmap = new FastBitmap(bitmap))
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (fastBitmap.GetPixel(x, y).B == indexToRemove)
{
if (x < minX)
{
minX = x;
}
if (x > maxX)
{
maxX = x;
}
if (y < minY)
{
minY = y;
}
if (y > maxY)
{
maxY = y;
}
}
}
}
}
// check
if ((minX == width) && (minY == height) && (maxX == 0) && (maxY == 0))
{
minX = minY = 0;
}
return new Rectangle(minX, minY, maxX - minX + 1, maxY - minY + 1);
}
The image I'm testing.
You appear to be checking EVERY pixel to see if a match exists in any x and y. Instead, what you want to do is check the minx, maxx, miny, and maxy separately.
For the minY, you want start at the top and check each row down until you hit a y row that has a matching pixel.
For the maxY, you want start at the bottom and check each row up until you hit a y row that has a matching pixel.
For the minX, you want start at the left and check each column until you hit a x column that has a matching pixel.
For the maxX, you want start at the right and check each column until you hit a x column that has a matching pixel.
Something like this:
minY = getMinY(fastBitmap, indexToRemove);
maxY = getMinY(fastBitmap, indexToRemove);
minX = getMinY(fastBitmap, indexToRemove);
maxX = getMinY(fastBitmap, indexToRemove);
int getMinY(Bitmap bitmap, byte indexToRemove)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (fastBitmap.GetPixel(x, y).B == indexToRemove)
{
return y;
}
}
}
return 0;
}
int getMaxY(Bitmap bitmap, byte indexToRemove)
{
for (int y = height; y > 0; y--)
{
for (int x = 0; x < width; x++)
{
if (fastBitmap.GetPixel(x, y).B == indexToRemove)
{
return y;
}
}
}
return height;
}
etc...
You should be able to write the getMinX and getMaxY yourself.

Looping through pixels excluding an area

I have the following code for looping pixels in an image:
Bitmap myBitmap = new Bitmap(pictureBox1.Image);
for (x = 0; x < pictureBox1.Image.Width; x += 1)
{
for (y = 0; y < pictureBox1.Image.Height; y += 1)
{
Color pixelColor = myBitmap.GetPixel(x, y);
aws = pixelColor.GetBrightness();
}
}
The above works. What if i want to exclude an area of the image. The area is an area selected by the user,for example:
from x=200&y=30 to x=250&y=30
from x=200&y=35 to x=250&y=35
from x=200&y=40 to x=250&y=40
from x=200&y=45 to x=250&y=45
How i loop all pixels except the one i wrote above?
Thank you!
I think you can do something like the following
Rectangle rectToExclude = new Rectangle(200, 30, 250, 30);//rectangle to be excluded
Bitmap myBitmap = new Bitmap(pictureBox1.Image);
for (x = 0; x < pictureBox1.Image.Width; x += 1)
{
for (y = 0; y < pictureBox1.Image.Height; y += 1)
{
if (rectToExclude.Contains(x, y)) continue; // if in the exclude rect then continue without doing any effects
Color pixelColor = myBitmap.GetPixel(x, y);
aws = pixelColor.GetBrightness();
}
}
Maybe write a function to test x and y like this:
public static bool IsInArea(int testx, int testy, int x1, int y1, int x2, int y2)
{
if (testx > x1 && testx < x2 && testy > y1 && testy < y2)
{
return true;
}
else
{
return false;
}
}
and then use it like this:
BitmapData bmd = myBitmap.LockBits(new Rectangle(0, 0, myBitmap.Width, myBitmap.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite,
myBitmap.PixelFormat);
int PixelSize = 4;
unsafe
{
for (int y = 0; y < bmd.Height; y++)
{
byte* row = (byte*)bmd.Scan0 + (y * bmd.Stride);
for (int x = 0; x < bmd.Width; x++)
{
if (!IsInArea(x, y, 200, 30, 250, 30))
{
int b = row[x * PixelSize]; //Blue
int g = row[x * PixelSize + 1]; //Green
int r = row[x * PixelSize + 2]; //Red
int a = row[x * PixelSize + 3]; //Alpha
Color c = Color.FromArgb(a, r, g, b);
float brightness = c.GetBrightness();
}
}
}
}
myBitmap.UnlockBits(bmd);

c# auto-crop an image

I have a black and white System.Drawing.Bitmap I need to auto-crop it so that it is only as big as needed to fit the image. This image always starts at the top left (0,0) position but I'm not sure how much height and width is will require. If there any way to auto-crop it to size?
The following is the code I used:
// Figure out the final size
int maxX = 0;
int maxY = 0;
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
System.Drawing.Color c = bitmap.GetPixel(x, y);
System.Drawing.Color w = System.Drawing.Color.White;
if (c.R != w.R || c.G != w.G || c.B != w.B)
{
if (x > maxX)
maxX = x;
if (y > maxY)
maxY = y;
}
}
}
maxX += 2;
If your image does not start at 0,0 like mine, here is a variant that gets you the auto-crop bounds:
public static Rectangle GetAutoCropBounds(Bitmap bitmap)
{
int maxX = 0;
int maxY = 0;
int minX = bitmap.Width;
int minY = bitmap.Height;
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
var c = bitmap.GetPixel(x, y);
var w = Color.White;
if (c.R != w.R || c.G != w.G || c.B != w.B)
{
if (x > maxX)
maxX = x;
if (x < minX)
minX = x;
if (y > maxY)
maxY = y;
if (y < minY)
minY = y;
}
}
}
maxX += 2;
return new Rectangle(minX, minY, maxX - minX, maxY - minY);
}
Note: if you are processing any amount of images in quantity or high-resolution then you'll want to look into faster alternatives than the GetPixel method.

Categories