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!");
}
}
}
}
}
Related
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;
}
}
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);
}
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.
I am trying to write a program that clicks on the first pixel it finds which has a certain color. Unfortunately, it appears that sometimes my program is unable to detect that there is actually the color on the screen. I am taking a screenshot of the screen and then using the GetPixel() method to find the color of every pixel.
Here is my method I use:
private static Point FindFirstColor(Color color)
{
int searchValue = color.ToArgb();
Point location = Point.Empty;
using (Bitmap bmp = GetScreenShot())
{
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
if (searchValue.Equals(bmp.GetPixel(x, y).ToArgb()))
{
location = new Point(x, y);
}
}
}
}
return location;
}
In order to take a screenshot of my screen, I use:
private static Bitmap GetScreenShot()
{
Bitmap result = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height, PixelFormat.Format32bppArgb);
{
using (Graphics gfx = Graphics.FromImage(result))
{
gfx.CopyFromScreen(Screen.PrimaryScreen.Bounds.X, Screen.PrimaryScreen.Bounds.Y, 0, 0, Screen.PrimaryScreen.Bounds.Size, CopyPixelOperation.SourceCopy);
}
}
return result;
}
Even when I use a color which I know is on the screen, it still returns Point.Empty. What is the reason for this?
Just copied your method and used as color the find Color.Black and it worked without any problems.
The only thing that's currently maybe not correctly in your code is that you don't immediately return after finding the first matching point. Instead you simply continue to iterate over all points thus leading to the fact that you'll going to return the last occurrence of a matching color.
To avoid this you can change your code into:
private static Point FindFirstColor(Color color)
{
int searchValue = color.ToArgb();
using (Bitmap bmp = GetScreenShot())
{
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
if (searchValue.Equals(bmp.GetPixel(x, y).ToArgb()))
{
return new Point(x, y);
}
}
}
}
return Point.Empty;
}
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();
}