How can I check if a Bitmap Image is empty? - c#

In Form1 I create in the constructor a new Bitmap:
public Form1()
{
InitializeComponent();
de.pb1 = pictureBox1;
de.bmpWithPoints = new Bitmap(pictureBox1.Width, pictureBox1.Height);
de.numberOfPoints = 100;
de.randomPointsColors = false;
de.Init();
}
In the class I check if the bitmap is null :
if (bmpWithPoints == null)
The bitmap is not null but is also not drawn with anything on it.
I check in the class if it's null I want to draw and set points on the bitmap.
if (bmpWithPoints == null)
{
for (int x = 0; x < bmpWithPoints.Width; x++)
{
for (int y = 0; y < bmpWithPoints.Height; y++)
{
bmpWithPoints.SetPixel(x, y, Color.Black);
}
}
Color c = Color.Red;
for (int x = 0; x < numberOfPoints; x++)
{
for (int y = 0; y < numberOfPoints; y++)
{
if (randomPointsColors == true)
{
c = Color.FromArgb(
r.Next(0, 256),
r.Next(0, 256),
r.Next(0, 256));
}
else
{
c = pointsColor;
}
bmpWithPoints.SetPixel(r.Next(0, bmpWithPoints.Width),
r.Next(0, bmpWithPoints.Height), c);
}
}
}
else
{
randomPointsColors = false;
}
Maybe the question should not be if the image is empty or null, I'm not sure how to call it. Maybe just a new image. But i want to check that if the new bitmap is (empty) nothing drawn on it then set the pixels(points).

You can create a method which checks image pixels. As an option, you can use LockBits method to get bitmap bytes into a byte array and use them:
bool IsEmpty(Bitmap image)
{
var data = image.LockBits(new Rectangle(0,0, image.Width,image.Height),
ImageLockMode.ReadOnly, image.PixelFormat);
var bytes = new byte[data.Height * data.Stride];
Marshal.Copy(data.Scan0, bytes, 0, bytes.Length);
image.UnlockBits(data);
return bytes.All(x => x == 0);
}

Related

Best way to speed up CropTransparentPixels method

I have just wrote this method to crop transparent pixels from images.
It seems to work ok but it is very slow because of GetPixel - any ideas on how to make the algorithm logic quicker?
I know I can change the GetPixel for faster (but unsafe) access code and I might do so, however I am after ways to avoid doing a full scan. I want advice on how to make the logic behind this algorithm quicker.
public Bitmap CropTransparentPixels(Bitmap originalBitmap)
{
// Find the min/max transparent pixels
Point min = new Point(int.MaxValue, int.MaxValue);
Point max = new Point(int.MinValue, int.MinValue);
for (int x = 0; x < originalBitmap.Width; ++x)
{
for (int y = 0; y < originalBitmap.Height; ++y)
{
Color pixelColor = originalBitmap.GetPixel(x, y);
if (pixelColor.A == 255)
{
if (x < min.X) min.X = x;
if (y < min.Y) min.Y = y;
if (x > max.X) max.X = x;
if (y > max.Y) max.Y = y;
}
}
}
// Create a new bitmap from the crop rectangle
Rectangle cropRectangle = new Rectangle(min.X, min.Y, max.X - min.X, max.Y - min.Y);
Bitmap newBitmap = new Bitmap(cropRectangle.Width, cropRectangle.Height);
using (Graphics g = Graphics.FromImage(newBitmap))
{
g.DrawImage(originalBitmap, 0, 0, cropRectangle, GraphicsUnit.Pixel);
}
return newBitmap;
}
This is the method I ended up writing and it is much faster.
public static Bitmap CropTransparentPixels(this Bitmap bmp)
{
BitmapData bmData = null;
try
{
bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int scanline = bmData.Stride;
IntPtr Scan0 = bmData.Scan0;
Point top = new Point(), left = new Point(), right = new Point(), bottom = new Point();
bool complete = false;
unsafe
{
byte* p = (byte*)(void*)Scan0;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
if (p[3] != 0)
{
top = new Point(x, y);
complete = true;
break;
}
p += 4;
}
if (complete)
break;
}
p = (byte*)(void*)Scan0;
complete = false;
for (int y = bmp.Height - 1; y >= 0; y--)
{
for (int x = 0; x < bmp.Width; x++)
{
if (p[x * 4 + y * scanline + 3] != 0)
{
bottom = new Point(x + 1, y + 1);
complete = true;
break;
}
}
if (complete)
break;
}
p = (byte*)(void*)Scan0;
complete = false;
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
if (p[x * 4 + y * scanline + 3] != 0)
{
left = new Point(x, y);
complete = true;
break;
}
}
if (complete)
break;
}
p = (byte*)(void*)Scan0;
complete = false;
for (int x = bmp.Width - 1; x >= 0; x--)
{
for (int y = 0; y < bmp.Height; y++)
{
if (p[x * 4 + y * scanline + 3] != 0)
{
right = new Point(x + 1, y + 1);
complete = true;
break;
}
}
if (complete)
break;
}
}
bmp.UnlockBits(bmData);
System.Drawing.Rectangle rectangle = new Rectangle(left.X, top.Y, right.X - left.X, bottom.Y - top.Y);
Bitmap b = new Bitmap(rectangle.Width, rectangle.Height);
Graphics g = Graphics.FromImage(b);
g.DrawImage(bmp, 0, 0, rectangle, GraphicsUnit.Pixel);
g.Dispose();
return b;
}
catch
{
try
{
bmp.UnlockBits(bmData);
}
catch { }
return null;
}
}

Bitmap Screen around Cursor | CopyFromScreen

What?
I have an application that scans an image of my screen by a color code .
Problem!
This process takes too long , because the entire screen is searched.
My Goal
I would like the search to a region around the current mouse position.
But how do i do that?
Code
Here is my Code:
Creates a Screen
private Bitmap CaptureScreen()
{
//Point a = new Point();
//a = Control.MousePosition;
Bitmap b = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
using (Graphics g = Graphics.FromImage(b))
{
g.CopyFromScreen(new Point(0, 0), new Point(0, 0), b.Size);
}
return b;
}
Search for Color Code
public Point GetPixelPosition(Color SearchColor, bool IgnoreAlphaChannel)
{
//Point a = new Point();
//a = Control.MousePosition;
_ColorFound = false;
Point PixelPt = new Point(0, 0);
using (Bitmap b = CaptureScreen())
{
for (int i = 0; i < b.Width; i++)
{
if (this._ColorFound)
break;
for (int j = 0; j < b.Height; j++)
{
if (this._ColorFound)
break;
Color tmpPixelColor = b.GetPixel(i, j);
if (((tmpPixelColor.A == SearchColor.A) || IgnoreAlphaChannel)
&& (tmpPixelColor.R == SearchColor.R)
&& (tmpPixelColor.G == SearchColor.G)
&& (tmpPixelColor.B == SearchColor.B)
)
{
PixelPt.X = i;
PixelPt.Y = j;
this._ColorFound = true;
}
}
}
}
return PixelPt;
}
I don't think your way of scanning is very effective... but in this answer I'm aiming at doing exactly what you want, by using your code (I haven't optimized absolutely anything):
public Point GetPixelPosition(Color SearchColor, bool IgnoreAlphaChannel, int pixelsToSearchAround)
{
Point mousePosition = Cursor.Position;
_ColorFound = false;
Point PixelPt = new Point(0, 0);
using (Bitmap b = CaptureScreen())
{
int minX = mousePosition.X - pixelsToSearchAround;
int maxX = mousePosition.X + pixelsToSearchAround;
int minY = mousePosition.Y - pixelsToSearchAround;
int maxY = mousePosition.Y + pixelsToSearchAround;
if(minX < 0) minX = 0;
if(minY < 0) minY = 0;
if(maxX > b.Width) maxX = b.Width;
if(maxY > b.Height) maxY = b.Height;
for (int i = minX; i < maxX; i++)
{
if (this._ColorFound)
break;
for (int j = minY; j < maxY; j++)
{
if (this._ColorFound)
break;
Color tmpPixelColor = b.GetPixel(i, j);
if (((tmpPixelColor.A == SearchColor.A) || IgnoreAlphaChannel)
&& (tmpPixelColor.R == SearchColor.R)
&& (tmpPixelColor.G == SearchColor.G)
&& (tmpPixelColor.B == SearchColor.B)
)
{
PixelPt.X = i;
PixelPt.Y = j;
this._ColorFound = true;
}
}
}
}
return PixelPt;
}
This should do what you are looking for in a very unoptimized manner: it's not what I'd do to search for a pixel component on screen.
You'd use the third parameter to determine how many pixels around the cursor to search for.
For further optimization, you could only capture the screen region that you are aiming to capture, but I'll leave that up to you (hint: instead of doing it in GetPixelPosition, you could do it in CaptureScreen, modifying the arguments to g.CopyFromScreen, instead of modifying the loop bounds).
Instead of limiting the region, you can improve the performance of the color checking method.
Don't use Bitmap.GetPixel! Use Bitmap.UnlockBits instead.
public static unsafe Point GetPoint (Bitmap bmp, Color c) {
BitmapData bmd = bmp.LockBits (new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try {
int s = bmd.Stride;
int search = (c.A<<0x18)|(c.R<<0x10)|(c.G<<0x08)|c.B;
int* clr = (int*)(void*)bmd.Scan0;
int tmp;
int* row = clr;
for (int i = 0; i < bmp.Height; i++) {
int* col = row;
for (int j = 0; j < bmp.Width; j++) {
tmp = *col;
if(tmp == search) {
return new Point(j,i);
}
col++;
}
row += s>>0x02;
}
return new Point(-1,-1);
} finally {
bmp.UnlockBits (bmd);
}
}
This method returns (-1,-1) if the color cannot be found. You can adapt it to ignore the alpha-channel as well:
public static unsafe Point GetPoint (Bitmap bmp, Color c, bool ignoreAlpha = false) {
BitmapData bmd = bmp.LockBits (new Rectangle(0,0,bmp.Width,bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
try {
int s = bmd.Stride;
int search = (c.A<<0x18)|(c.R<<0x10)|(c.G<<0x08)|c.B;
if(ignoreAlpha) {
search &= 0xffffff;
}
int* clr = (int*)(void*)bmd.Scan0;
int tmp;
int* row = clr;
for (int i = 0; i < bmp.Height; i++) {
int* col = row;
for (int j = 0; j < bmp.Width; j++) {
tmp = *col;
if(ignoreAlpha) {
tmp &= 0xffffff;
}
if(tmp == search) {
return new Point(j,i);
}
col++;
}
row += s>>0x02;
}
return new Point(-1,-1);
} finally {
bmp.UnlockBits (bmd);
}
}
The reason GetPixel is slower is because you don't process them in batch. This is because the method always needs to decode the image and wait until the pixel you are querying walks by. Using UnlockBits you decode only once and then can iterate over all pixels.

Bitmap.GetPixel() returns bad value

I'm having some trouble reading back pixel values from a Bitmap that I'm generating. I first generate a bitmap named maskBitmap in my class using this code:
void generateMaskBitmap()
{
if (inputBitmap != null)
{
Bitmap tempBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(tempBitmap))
{
Brush brush = Brushes.Black;
for (int y = 0; y < tempBitmap.Height; y += circleSpacing)
{
for (int x = 0; x < tempBitmap.Width; x += circleSpacing)
{
g.FillEllipse(brush, x, y, circleDiameter, circleDiameter);
}
}
g.Flush();
}
maskBitmap = (Bitmap)tempBitmap.Clone();
}
}
I then try to apply the mask to my original image using the following code:
void generateOutputBitmap()
{
if (inputBitmap != null && maskBitmap != null)
{
Bitmap tempBitmap = new Bitmap(inputBitmap.Width, inputBitmap.Height);
for (int y = 0; y < tempBitmap.Height; y++)
{
for (int x = 0; x < tempBitmap.Width; x++)
{
Color tempColor = maskBitmap.GetPixel(x, y);
if (tempColor == Color.Black)
{
tempBitmap.SetPixel(x, y, inputBitmap.GetPixel(x, y));
}
else
{
tempBitmap.SetPixel(x, y, Color.White);
}
}
}
outputBitmap = tempBitmap;
}
}
The mask bitmap is successfully generated and visible in a picture box, however the color values for every pixel when testing "tempColor" show empty (A = 0, R = 0, G = 0, B = 0). I am aware of the performance problems with getpixel/setpixel, but this is not an issue for this project. I'm also aware that "tempColor == Color.Black" is not a valid test, but that is just a place holder for my comparison code.
I am unable to reproduce your problem. I copy-and-pasted your code and made some modifications to make it work for me. I am able to confirm that tempColor is sometimes #FF000000.
I suspect you mixed up your bitmap references somewhere. Are you really sure you are never getting any value other than #00000000? Do your circleDiameter and circleSpacing have sensible values? And most importantly: are you absolutely sure that you are reading from the correct bitmap?
Here's my version of your code, which I know works:
using System;
using System.Drawing;
namespace Test
{
class Program
{
static void Main()
{
var bitmap = GenerateMaskBitmap(100, 100);
TestMaskBitmap(bitmap);
}
const int CircleDiameter = 10;
const int CircleSpacing = 10;
static Bitmap GenerateMaskBitmap(int width, int height)
{
Bitmap maskBitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(maskBitmap))
{
Brush brush = Brushes.Black;
for (int y = 0; y < maskBitmap.Height; y += CircleSpacing)
{
for (int x = 0; x < maskBitmap.Width; x += CircleSpacing)
{
g.FillEllipse(brush, x, y, CircleDiameter, CircleDiameter);
}
}
g.Flush();
}
return maskBitmap;
}
static void TestMaskBitmap(Bitmap maskBitmap)
{
for (int y = 0; y < maskBitmap.Height; y++)
{
for (int x = 0; x < maskBitmap.Width; x++)
{
Color tempColor = maskBitmap.GetPixel(x, y);
if (tempColor.ToArgb() != 0)
throw new Exception("It works!");
}
}
}
}
}

c#: Generate a new single image that repeats another image for x times horizontally

I'm looking for sample .NET code (System.Drawing.Image) that does the following:
Load a given image file.
Generate a new single image that repeats the orginal image for x times horizontally.
This creates a new bitmap and draws the source bitmap to it numTimes times.
Bitmap b = Bitmap.FromFile(sourceFilename);
Bitmap output = new Bitmap(b.Width * numTimes, b.Height);
Graphics g = Graphics.FromImage(output);
for (int i = 0; i < numTimes; i++) {
g.DrawImage(b, i * b.Width, 0);
}
// do whatever with the image, here we'll output it
output.Save(outputFilename);
// make sure to clean up too
g.Dispose();
b.Dispose();
output.Dispose();
Here a sample copying each source image pixel on destination bitmap
static void Main(string[] args)
{
ushort nbCopies = 2;
Bitmap srcBitmap = (Bitmap)Image.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\Koala.jpg");
Bitmap dstBitmap = new Bitmap(srcBitmap.Width * nbCopies, srcBitmap.Height, srcBitmap.PixelFormat);
//Slow method
for (int curCopy = 0; curCopy < nbCopies; curCopy++)
{
for (int x = 0; x < srcBitmap.Width; x++)
{
for (int y = 0; y < srcBitmap.Height; y++)
{
Color c = srcBitmap.GetPixel(x, y);
dstBitmap.SetPixel(x + (curCopy * srcBitmap.Width), y, c);
}
}
}
//OR
//Fast method using unsafe code
BitmapData srcBd = srcBitmap.LockBits(new Rectangle(Point.Empty, srcBitmap.Size), ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData dstBd = dstBitmap.LockBits(new Rectangle(Point.Empty, dstBitmap.Size), ImageLockMode.WriteOnly, dstBitmap.PixelFormat);
unsafe
{
for (int curCopy = 0; curCopy < nbCopies; curCopy++)
{
for (int y = 0; y < srcBitmap.Height; y++)
{
byte* srcRow = (byte*)srcBd.Scan0 + (y * srcBd.Stride);
byte* dstRow = (byte*)dstBd.Scan0 + (y * dstBd.Stride) + (curCopy * srcBd.Stride);
for (int x = 0; x < srcBitmap.Width; x++)
{
//Copy each composant value (typically RGB)
for (int comp = 0; comp < (srcBd.Stride / srcBd.Width); comp++)
{
dstRow[x * 3 + comp] = srcRow[x * 3 + comp];
}
}
}
}
}
dstBitmap.UnlockBits(dstBd);
srcBitmap.UnlockBits(srcBd);
dstBitmap.Save(#"C:\Users\Public\Pictures\Sample Pictures\Koala_multiple.jpg");
dstBitmap.Dispose();
srcBitmap.Dispose();
}

Remove surrounding whitespace from an image

I have a block of product images we received from a customer. Each product image is a picture of something and it was taken with a white background. I would like to crop all the surrounding parts of the image but leave only the product in the middle. Is this possible?
As an example: [http://www.5dnet.de/media/catalog/product/d/r/dress_shoes_5.jpg][1]
I don't want all white pixels removed, however I do want the image cropped so that the top-most row of pixels contains one non-white pixel, the left-most vertical row of pixels contains one non-white pixel, bottom-most horizontal row of pixels contains one non-white pixel, etc.
Code in C# or VB.net would be appreciated.
I found I had to adjust Dmitri's answer to ensure it works with images that don't actually need cropping (either horizontally, vertically or both)...
public static Bitmap Crop(Bitmap bmp)
{
int w = bmp.Width;
int h = bmp.Height;
Func<int, bool> allWhiteRow = row =>
{
for (int i = 0; i < w; ++i)
if (bmp.GetPixel(i, row).R != 255)
return false;
return true;
};
Func<int, bool> allWhiteColumn = col =>
{
for (int i = 0; i < h; ++i)
if (bmp.GetPixel(col, i).R != 255)
return false;
return true;
};
int topmost = 0;
for (int row = 0; row < h; ++row)
{
if (allWhiteRow(row))
topmost = row;
else break;
}
int bottommost = 0;
for (int row = h - 1; row >= 0; --row)
{
if (allWhiteRow(row))
bottommost = row;
else break;
}
int leftmost = 0, rightmost = 0;
for (int col = 0; col < w; ++col)
{
if (allWhiteColumn(col))
leftmost = col;
else
break;
}
for (int col = w - 1; col >= 0; --col)
{
if (allWhiteColumn(col))
rightmost = col;
else
break;
}
if (rightmost == 0) rightmost = w; // As reached left
if (bottommost == 0) bottommost = h; // As reached top.
int croppedWidth = rightmost - leftmost;
int croppedHeight = bottommost - topmost;
if (croppedWidth == 0) // No border on left or right
{
leftmost = 0;
croppedWidth = w;
}
if (croppedHeight == 0) // No border on top or bottom
{
topmost = 0;
croppedHeight = h;
}
try
{
var target = new Bitmap(croppedWidth, croppedHeight);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(bmp,
new RectangleF(0, 0, croppedWidth, croppedHeight),
new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
GraphicsUnit.Pixel);
}
return target;
}
catch (Exception ex)
{
throw new Exception(
string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
ex);
}
}
Here's my (rather lengthy) solution:
public Bitmap Crop(Bitmap bmp)
{
int w = bmp.Width, h = bmp.Height;
Func<int, bool> allWhiteRow = row =>
{
for (int i = 0; i < w; ++i)
if (bmp.GetPixel(i, row).R != 255)
return false;
return true;
};
Func<int, bool> allWhiteColumn = col =>
{
for (int i = 0; i < h; ++i)
if (bmp.GetPixel(col, i).R != 255)
return false;
return true;
};
int topmost = 0;
for (int row = 0; row < h; ++row)
{
if (allWhiteRow(row))
topmost = row;
else break;
}
int bottommost = 0;
for (int row = h - 1; row >= 0; --row)
{
if (allWhiteRow(row))
bottommost = row;
else break;
}
int leftmost = 0, rightmost = 0;
for (int col = 0; col < w; ++col)
{
if (allWhiteColumn(col))
leftmost = col;
else
break;
}
for (int col = w-1; col >= 0; --col)
{
if (allWhiteColumn(col))
rightmost = col;
else
break;
}
int croppedWidth = rightmost - leftmost;
int croppedHeight = bottommost - topmost;
try
{
Bitmap target = new Bitmap(croppedWidth, croppedHeight);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(bmp,
new RectangleF(0, 0, croppedWidth, croppedHeight),
new RectangleF(leftmost, topmost, croppedWidth, croppedHeight),
GraphicsUnit.Pixel);
}
return target;
}
catch (Exception ex)
{
throw new Exception(
string.Format("Values are topmost={0} btm={1} left={2} right={3}", topmost, bottommost, leftmost, rightmost),
ex);
}
}
I needed a solution that worked on large images (GetPixel is slow), so I wrote the extension method below. It seems to work well in my limited testing. The drawback is that "Allow Unsafe Code" has to be checked in your project.
public static Image AutoCrop(this Bitmap bmp)
{
if (Image.GetPixelFormatSize(bmp.PixelFormat) != 32)
throw new InvalidOperationException("Autocrop currently only supports 32 bits per pixel images.");
// Initialize variables
var cropColor = Color.White;
var bottom = 0;
var left = bmp.Width; // Set the left crop point to the width so that the logic below will set the left value to the first non crop color pixel it comes across.
var right = 0;
var top = bmp.Height; // Set the top crop point to the height so that the logic below will set the top value to the first non crop color pixel it comes across.
var bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
unsafe
{
var dataPtr = (byte*)bmpData.Scan0;
for (var y = 0; y < bmp.Height; y++)
{
for (var x = 0; x < bmp.Width; x++)
{
var rgbPtr = dataPtr + (x * 4);
var b = rgbPtr[0];
var g = rgbPtr[1];
var r = rgbPtr[2];
var a = rgbPtr[3];
// If any of the pixel RGBA values don't match and the crop color is not transparent, or if the crop color is transparent and the pixel A value is not transparent
if ((cropColor.A > 0 && (b != cropColor.B || g != cropColor.G || r != cropColor.R || a != cropColor.A)) || (cropColor.A == 0 && a != 0))
{
if (x < left)
left = x;
if (x >= right)
right = x + 1;
if (y < top)
top = y;
if (y >= bottom)
bottom = y + 1;
}
}
dataPtr += bmpData.Stride;
}
}
bmp.UnlockBits(bmpData);
if (left < right && top < bottom)
return bmp.Clone(new Rectangle(left, top, right - left, bottom - top), bmp.PixelFormat);
return null; // Entire image should be cropped, so just return null
}
I've written code to do this myself - it's not too difficult to get the basics going.
Essentially, you need to scan pixel rows/columns to check for non-white pixels and isolate the bounds of the product image, then create a new bitmap with just that region.
Note that while the Bitmap.GetPixel() method works, it's relatively slow. If processing time is important, you'll need to use Bitmap.LockBits() to lock the bitmap in memory, and then some simple pointer use inside an unsafe { } block to access the pixels directly.
This article on CodeProject gives some more details that you'll probably find useful.
fix remaining 1px white space at the top and left
public Bitmap Crop(Bitmap bitmap)
{
int w = bitmap.Width;
int h = bitmap.Height;
Func<int, bool> IsAllWhiteRow = row =>
{
for (int i = 0; i < w; i++)
{
if (bitmap.GetPixel(i, row).R != 255)
{
return false;
}
}
return true;
};
Func<int, bool> IsAllWhiteColumn = col =>
{
for (int i = 0; i < h; i++)
{
if (bitmap.GetPixel(col, i).R != 255)
{
return false;
}
}
return true;
};
int leftMost = 0;
for (int col = 0; col < w; col++)
{
if (IsAllWhiteColumn(col)) leftMost = col + 1;
else break;
}
int rightMost = w - 1;
for (int col = rightMost; col > 0; col--)
{
if (IsAllWhiteColumn(col)) rightMost = col - 1;
else break;
}
int topMost = 0;
for (int row = 0; row < h; row++)
{
if (IsAllWhiteRow(row)) topMost = row + 1;
else break;
}
int bottomMost = h - 1;
for (int row = bottomMost; row > 0; row--)
{
if (IsAllWhiteRow(row)) bottomMost = row - 1;
else break;
}
if (rightMost == 0 && bottomMost == 0 && leftMost == w && topMost == h)
{
return bitmap;
}
int croppedWidth = rightMost - leftMost + 1;
int croppedHeight = bottomMost - topMost + 1;
try
{
Bitmap target = new Bitmap(croppedWidth, croppedHeight);
using (Graphics g = Graphics.FromImage(target))
{
g.DrawImage(bitmap,
new RectangleF(0, 0, croppedWidth, croppedHeight),
new RectangleF(leftMost, topMost, croppedWidth, croppedHeight),
GraphicsUnit.Pixel);
}
return target;
}
catch (Exception ex)
{
throw new Exception(string.Format("Values are top={0} bottom={1} left={2} right={3}", topMost, bottomMost, leftMost, rightMost), ex);
}
}
It's certainly possible. In pseudocode:
topmost = 0
for row from 0 to numRows:
if allWhiteRow(row):
topmost = row
else:
# found first non-white row from top
break
botmost = 0
for row from numRows-1 to 0:
if allWhiteRow(row):
botmost = row
else:
# found first non-white row from bottom
break
And similarly for left and right.
The code for allWhiteRow would involve looking at the pixels in that row and making sure they're all close to 255,255,255.
public void TrimImage() {
int threshhold = 250;
int topOffset = 0;
int bottomOffset = 0;
int leftOffset = 0;
int rightOffset = 0;
Bitmap img = new Bitmap(#"e:\Temp\Trim_Blank_Image.png");
bool foundColor = false;
// Get left bounds to crop
for (int x = 1; x < img.Width && foundColor == false; x++)
{
for (int y = 1; y < img.Height && foundColor == false; y++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
leftOffset += 1;
}
foundColor = false;
// Get top bounds to crop
for (int y = 1; y < img.Height && foundColor == false; y++)
{
for (int x = 1; x < img.Width && foundColor == false; x++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
topOffset += 1;
}
foundColor = false;
// Get right bounds to crop
for (int x = img.Width - 1; x >= 1 && foundColor == false; x--)
{
for (int y = 1; y < img.Height && foundColor == false; y++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
rightOffset += 1;
}
foundColor = false;
// Get bottom bounds to crop
for (int y = img.Height - 1; y >= 1 && foundColor == false; y--)
{
for (int x = 1; x < img.Width && foundColor == false; x++)
{
Color color = img.GetPixel(x, y);
if (color.R < threshhold || color.G < threshhold || color.B < threshhold)
foundColor = true;
}
bottomOffset += 1;
}
// Create a new image set to the size of the original minus the white space
//Bitmap newImg = new Bitmap(img.Width - leftOffset - rightOffset, img.Height - topOffset - bottomOffset);
Bitmap croppedBitmap = new Bitmap(img);
croppedBitmap = croppedBitmap.Clone(
new Rectangle(leftOffset - 3, topOffset - 3, img.Width - leftOffset - rightOffset + 6, img.Height - topOffset - bottomOffset + 6),
System.Drawing.Imaging.PixelFormat.DontCare);
// Get a graphics object for the new bitmap, and draw the original bitmap onto it, offsetting it do remove the whitespace
//Graphics g = Graphics.FromImage(croppedBitmap);
//g.DrawImage(img, 1 - leftOffset, 1 - rightOffset);
croppedBitmap.Save(#"e:\Temp\Trim_Blank_Image-crop.png", ImageFormat.Png);
}
I have got code from other post in ms, but that has bugs, I have changed something, now it works good.
The post from http://msm2020-sc.blogspot.com/2013/07/c-crop-white-space-from-around-image.html
The pnmcrop utility from the netpbm graphics utilities library does exactly that.
I suggest looking at their code, available from http://netpbm.sourceforge.net/
#Jonesie works great, but you have a bug with AllWhiteColumn
pixel was wrong calculated var px = i * w + col; is correct.
Also isTransparent should include white color SKColors.White or better compare it using rgb with offset r,g,b >200
I copied to a version that works with SkiaSharp.
using SkiaSharp;
using System;
//
// Based on the original stackoverflow post: https://stackoverflow.com/questions/248141/remove-surrounding-whitespace-from-an-image
//
namespace BlahBlah
{
public static class BitmapExtensions
{
public static SKBitmap TrimWhitespace(this SKBitmap bmp)
{
int w = bmp.Width;
int h = bmp.Height;
// get all the pixels here - this can take a while so dont want it in the loops below
// maybe theres a more efficient way? loading all the pixels could be greedy
var pixels = bmp.Pixels;
bool IsTransparent(SKColor color)
{
return (color.Red == 0 && color.Green == 0 && color.Blue == 0 && color.Alpha == 0) ||
(color == SKColors.Transparent);
}
Func<int, bool> allWhiteRow = row =>
{
for (int i = 0; i < w; ++i)
{
var px = row * w + i;
if (!IsTransparent(pixels[px]))
return false;
}
return true;
};
Func<int, bool> allWhiteColumn = col =>
{
for (int i = 0; i < h; ++i)
{
var px = col * h + i;
if (!IsTransparent(pixels[px]))
return false;
}
return true;
};
int topmost = 0;
for (int row = 0; row < h; ++row)
{
if (allWhiteRow(row))
topmost = row;
else break;
}
int bottommost = 0;
for (int row = h - 1; row >= 0; --row)
{
if (allWhiteRow(row))
bottommost = row;
else break;
}
int leftmost = 0, rightmost = 0;
for (int col = 0; col < w; ++col)
{
if (allWhiteColumn(col))
leftmost = col;
else
break;
}
for (int col = w - 1; col >= 0; --col)
{
if (allWhiteColumn(col))
rightmost = col;
else
break;
}
if (rightmost == 0) rightmost = w; // As reached left
if (bottommost == 0) bottommost = h; // As reached top.
int croppedWidth = rightmost - leftmost;
int croppedHeight = bottommost - topmost;
if (croppedWidth == 0) // No border on left or right
{
leftmost = 0;
croppedWidth = w;
}
if (croppedHeight == 0) // No border on top or bottom
{
topmost = 0;
croppedHeight = h;
}
try
{
var target = new SKBitmap(croppedWidth, croppedHeight);
using var canvas = new SKCanvas(target);
using var img = SKImage.FromBitmap(bmp);
canvas.DrawImage(img,
new SKRect(leftmost, topmost, rightmost, bottommost),
new SKRect(0, 0, croppedWidth, croppedHeight));
return target;
}
catch (Exception ex)
{
throw new Exception(
string.Format("Values are topmost={0} btm={1} left={2} right={3} croppedWidth={4} croppedHeight={5}", topmost, bottommost, leftmost, rightmost, croppedWidth, croppedHeight),
ex);
}
}
}
}

Categories

Resources