Finding the smallest possible Rectangle for a Difference between Bitmaps - c#

I'm trying to optimize the performance on a VNC-type app I'm writing, and I'm having a bit of difficulty getting it truly optimized.
Given two bitmaps, I'd like to return a third bitmap, of the smallest possible size, which fully contains the differences between the bitmaps.
I am able to return a bitmap with the same size as my Bitmap A / Bitmap B, where the differences have color, and the rest is transparent, but this doesn't save me any actual data space, and doesn't actually optimize anything (Unless Bitmaps do something special with completely transparent pixels, but I feel as though they do not.)
Anyway, here's what I hve now, and I could potentially keep track of the pointer where the first change is found, and then keep track of the pointer where the last change is found, but how could I convert these pointers into a rectangle? And how could I crop my full-size difference bitmap in to that rectangle?
Here's the code I'm using so far:
var a = previous.Screen;
var b = next.Screen;
Bitmap output = new Bitmap(
Math.Max(a.Width, b.Width),
Math.Max(a.Height, b.Height),
PixelFormat.Format32bppArgb);
Rectangle recta = new Rectangle(Point.Empty, a.Size);
Rectangle rectb = new Rectangle(Point.Empty, b.Size);
Rectangle rectOutput = new Rectangle(Point.Empty, output.Size);
BitmapData aData = a.LockBits(recta, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bData = b.LockBits(rectb, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData outputData = output.LockBits(rectOutput, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
try
{
unsafe
{
byte* aPtr = (byte*) aData.Scan0;
byte* bPtr = (byte*) bData.Scan0;
byte* outputPtr = (byte*) outputData.Scan0;
int h = Math.Min(a.Height, b.Height);
int w = Math.Min(a.Width, b.Width);
for (int y = 0; y < h; y++)
{
aPtr = (byte*) aData.Scan0;
bPtr = (byte*) bData.Scan0;
outputPtr = (byte*) outputData.Scan0;
aPtr += y*aData.Stride;
bPtr += y*bData.Stride;
outputPtr += y*outputData.Stride;
for (int x = 0; x < w; x++)
{
bool bl = false;
//the three color channels
for (int j = 0; j < 3; j++)
{
if (*aPtr != *bPtr)
{
bl = true;
}
*outputPtr = (byte) *bPtr;
outputPtr++;
aPtr++;
bPtr++;
}
//alpha, when one or mre color channels are different
if (bl)
*outputPtr = (byte) ((*aPtr + *bPtr)/2);
outputPtr++;
aPtr++;
bPtr++;
}
}
}
}
catch
{
if (output != null)
{
output.UnlockBits(outputData);
output.Dispose();
output = null;
}
}
finally
{
a.UnlockBits(aData);
b.UnlockBits(bData);
if (output != null)
output.UnlockBits(outputData);
}

Related

c# crop image on specific pixel

i would like to crop an image from a full image only at a specific part this is my code
private unsafe Bitmap GetDiffBitmap(Bitmap bmp)
{
BitmapData bmDataRes;
bmpRes = new Bitmap(bmp.Width, bmp.Height);
bmData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
bmDataRes = bmpRes.LockBits(new Rectangle(0, 0, bmpRes.Width, bmpRes.Height), System.Drawing.Imaging.ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
IntPtr scan0 = bmData.Scan0;
IntPtr scan0Res = bmDataRes.Scan0;
int stride = bmData.Stride;
int strideRes = bmDataRes.Stride;
int nWidth = bmp.Width;
int nHeight = bmp.Height;
for(int y = 0; y < nHeight; y++)
{
byte* p = (byte*)scan0.ToPointer();
p += y * stride;
byte* pRes = (byte*)scan0Res.ToPointer();
pRes += y * strideRes;
for (int x = 0; x < nWidth; x++)
{
//always get the complete pixel when differences are found
if (p[0] ==255)
{
pRes[0] = p[0];
pRes[1] = p[1];
pRes[2] = p[2];
//alpha (opacity)
pRes[3] = p[3];
}
else
{
bmpRes.UnlockBits(bmDataRes);
break;
}
p += 4;
pRes += 4;
}
}
return bmpRes;
}
as you can see i just want to copy the pixels when the red byte is 255 and if its not,immediently to stop and break. i want to find continuous red parts.
but im getting a weird exception on that line when i release the bitmap
bmpRes.UnlockBits(bmDataRes); the exception -A generic error occurred in GDI+ any idea why is it throwing that error?
You should break the outter for loop as well.
What happens is that you're trying to UnlockBits more than once, actually.
Or more simply you can change
bmpRes.UnlockBits(bmDataRes);
break;
into
bmpRes.UnlockBits(bmDataRes);
return bmpRes;

C# Creating PixelFormat.Format32bppArgb skewing image

I am trying to combine 3 grayscale bitmaps into one color bitmap. All three grayscale images are the same size (this is based off of data from the Hubble). My logic is:
Load "blue" image and convert to PixelFormat.Format24bppRgb. Based off of that create a new byte array that is 4 times as large as the blue data array length/3 (so it will be one byte for blue, one byte for green, one byte for red, one byte for alpha per pixel since my system is little endian). Populate the "blue" bytes of the array from the "blue" bytes of the blue image (and in this first loop set the alpha byte to 255). I then load the green and red bitmaps, convert them to PixelFormat.Format24bppRgb, and pull the g/r value and add it to the correct place in the data array. The final data array then has the bgra bytes set correctly from what I can tell.
When I have the data array populated, I have used it to:
Create a PixelFormats.Bgra32 BitmapSource then convert that to a Bitmap.
Create a PixelFormat.Format32bppArgb Bitmap using the Bitmap constructor (width, height, stride, PixelForma, IntPtr)
Create a PixelFormat.Format32bppArgb Bitmap using pointers
All three ways of creating a return bitmap result in the image being "skewed" (sorry, I don't know of a better word).
The actual output (of all three ways of generating the final bitmap) is: Actual output
The desired output is something like (this was done in photoshop so it is slightly different): Desired output
The three file names (_blueFileName, _greenFileName, _redFileName) are set in the constructor and I check to make sure the files exist before creating the class. I can post that code if anyone wants it.
Can anyone tell me what I am doing wrong? I am guessing that is is due to the stride or something like that?
Note: I can't post the links to the images I am using as input as I don't have 10 reputation points. Maybe I could send the links via email or something if someone wants them as well.
Here is my code (with some stuff commented out, the comments describe what happens if each commented out block is used instead):
public Bitmap Merge()
{
// Load original "blue" bitmap.
Bitmap tblueBitmap = (Bitmap)Image.FromFile(_blueFileName);
int width = tblueBitmap.Width;
int height = tblueBitmap.Height;
// Convert to 24 bpp rgb (which is bgr on little endian machines)
Bitmap blueBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(blueBitmap))
{
gr.DrawImage(tblueBitmap, 0, 0, width, height);
}
tblueBitmap.Dispose();
// Lock and copy to byte array.
BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly,
blueBitmap.PixelFormat);
int numbBytes = blueData.Stride*blueBitmap.Height;
byte[] blueBytes = new byte[numbBytes];
Marshal.Copy(blueData.Scan0, blueBytes, 0, numbBytes);
blueBitmap.UnlockBits(blueData);
blueData = null;
blueBitmap.Dispose();
int mult = 4;
byte[] data = new byte[(numbBytes/3)*mult];
int count = 0;
// Copy every third byte starting at 0 to the final data array (data).
for (int i = 0; i < data.Length / mult; i++)
{
// Check for overflow
if (blueBytes.Length <= count*3 + 2)
{
continue;
}
// First pass, set Alpha channel.
data[i * mult + 3] = 255;
// Set blue byte.
data[i*mult] = blueBytes[count*3];
count++;
}
// Cleanup.
blueBytes = null;
int generation = GC.GetGeneration(this);
GC.Collect(generation);
Bitmap tgreenBitmap = (Bitmap)Image.FromFile(_greenFileName);
Bitmap greenBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(greenBitmap))
{
gr.DrawImage(tgreenBitmap, 0, 0, width, height);
}
tgreenBitmap.Dispose();
BitmapData greenData = greenBitmap.LockBits(new Rectangle(0, 0, greenBitmap.Width, greenBitmap.Height), ImageLockMode.ReadOnly,
greenBitmap.PixelFormat);
numbBytes = greenData.Stride * greenBitmap.Height;
byte[] greenBytes = new byte[numbBytes];
Marshal.Copy(greenData.Scan0, greenBytes, 0, numbBytes);
greenBitmap.UnlockBits(greenData);
greenData = null;
greenBitmap.Dispose();
count = 0;
for (int i = 0; i < data.Length / mult; i++)
{
if (greenBytes.Length <= count * 3 + 1)
{
continue;
}
// Set green byte
data[i * mult + 1] = greenBytes[count * 3 + 1];
count++;
}
greenBytes = null;
generation = GC.GetGeneration(this);
GC.Collect(generation);
Bitmap tredBitmap = (Bitmap)Image.FromFile(_redFileName);
Bitmap redBitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics gr = Graphics.FromImage(redBitmap))
{
gr.DrawImage(tredBitmap, 0, 0, width, height);
}
tredBitmap.Dispose();
BitmapData redData = redBitmap.LockBits(new Rectangle(0, 0, redBitmap.Width, redBitmap.Height), ImageLockMode.ReadOnly,
redBitmap.PixelFormat);
numbBytes = redData.Stride * redBitmap.Height;
byte[] redBytes = new byte[numbBytes];
Marshal.Copy(redData.Scan0, redBytes, 0, numbBytes);
redBitmap.UnlockBits(redData);
redData = null;
redBitmap.Dispose();
count = 0;
for (int i = 0; i < data.Length / mult; i++)
{
if (redBytes.Length <= count * 3+2)
{
count++;
continue;
}
// set red byte
data[i * mult + 2] = redBytes[count * 3 + 2];
count++;
}
redBytes = null;
generation = GC.GetGeneration(this);
GC.Collect(generation);
int stride = (width*32 + 7)/8;
var bi = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgra32, null, data, stride);
// uncomment out below to see what a bitmap source to bitmap does. So far, it is exactly the same as
// the uncommented out lines below.
// ---------------------------------------------------------------------------------------------------
//return BitmapImage2Bitmap(bi);
unsafe
{
fixed (byte* p = data)
{
IntPtr ptr = (IntPtr)p;
// Trying the commented out lines returns the same bitmap as the uncommented out lines.
// ------------------------------------------------------------------------------------
byte* p2 = (byte*)ptr;
Bitmap retBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
BitmapData fData = retBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
unsafe
{
for (int i = 0; i < fData.Height; i++)
{
byte* imgPtr = (byte*)(fData.Scan0 + (fData.Stride * i));
for (int x = 0; x < fData.Width; x++)
{
for (int ii = 0; ii < 4; ii++)
{
*imgPtr++ = *p2++;
}
//*imgPtr++ = 255;
}
}
}
retBitmap.UnlockBits(fData);
//Bitmap retBitmap = new Bitmap(width, height, GetStride(width, PixelFormat.Format32bppArgb),
// PixelFormat.Format32bppArgb, ptr);
return retBitmap;
}
}
}
private Bitmap BitmapImage2Bitmap(BitmapSource bitmapSrc)
{
using (MemoryStream outStream = new MemoryStream())
{
BitmapEncoder enc = new BmpBitmapEncoder();
enc.Frames.Add(BitmapFrame.Create(bitmapSrc));
enc.Save(outStream);
Bitmap bitmap = new Bitmap(outStream);
return new Bitmap(bitmap);
}
}
private int GetStride(int width, PixelFormat pxFormat)
{
int bitsPerPixel = ((int)pxFormat >> 8) & 0xFF;
int validBitsPerLine = width * bitsPerPixel;
int stride = ((validBitsPerLine + 31) / 32) * 4;
return stride;
}
You are missing the gap between the lines. The Stride value is not the amount of data in a line, it's the distance between the start of one line to the next. There may be a gap at the end of each line to align the next line on an even address boundary.
The Stride value can even be negative, then the image is stored upside down in memory. To get the data without the gaps and to handle all cases you need to copy one line at a time:
BitmapData blueData = blueBitmap.LockBits(new Rectangle(0, 0, blueBitmap.Width, blueBitmap.Height), ImageLockMode.ReadOnly, blueBitmap.PixelFormat);
int lineBytes = blueBitmap.Width * 3;
int numbBytes = lineBytes * blueBitmap.Height;
byte[] blueBytes = new byte[numbBytes];
for (int y = 0; y < blueBitmap.Height; y++) {
Marshal.Copy(blueData.Scan0 + y * blueData.Stride, blueBytes, y * lineBytes, lineBytes);
}
blueBitmap.UnlockBits(blueData);
blueBitmap.Dispose();

How do i resize the Bitmap in this function?

All the time the size is 1920x1080 but i want it to be for example 10x10 so the process in the function will be faster. I dont mind the size of the bitmap or its quality i just want it to be faster.
In this function im creating an histogram of each Bitmap. The problem is that the FOR loops take very long.
public static long[] GetHistogram(Bitmap b)
{
long[] myHistogram = new long[256]; // histogram סופר כמה יש מכל גוון
BitmapData bmData = null;
//b = new Bitmap(100, 100);
//b.SetPixel(0,0,Color.FromArgb(10,30,40,50)); //run and make it come to here
try
{
ResizeBitmap(b, 10, 10);
//Lock it fixed with 32bpp
bmData = b.LockBits(new Rectangle(0, 0, b.Width, b.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int scanline = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
int nWidth = b.Width;
int nHeight = b.Height;
for (int y = 0; y < nHeight; y++)
{
for (int x = 0; x < nWidth; x++)
{
long Temp = 0;
Temp += p[0]; // p[0] - blue, p[1] - green , p[2]-red
Temp += p[1];
Temp += p[2];
Temp = (int)Temp / 3;
myHistogram[Temp]++;
//we do not need to use any offset, we always can increment by pixelsize when
//locking in 32bppArgb - mode
p += 4;
}
}
}
b.UnlockBits(bmData);
}
catch
{
try
{
b.UnlockBits(bmData);
}
catch
{
}
}
return myHistogram; // to save to a file the histogram of all the bitmaps/frames each line contain 0 to 256 values of a frame.
}
So im calling this function: ResizeBitmap(b, 10, 10);
And try to resize each bitmap but after this line i see that the bitmap size is still 1920x1080
This is the ResizeBitmap function:
public static Bitmap ResizeBitmap(Bitmap b, int nWidth, int nHeight)
{
Bitmap result = new Bitmap(nWidth, nHeight);
using (Graphics g = Graphics.FromImage((Image)result))
g.DrawImage(b, 0, 0, nWidth, nHeight);
return result;
}
Maybe there is another way to make the GetHistogram function to work faster ?
The method returns the new bitmap, so you should use the return value:
Bitmap newBitmap = ResizeBitmap(b, 10, 10);
b.Dispose();
b = newBitmap;
Side note: You are ignoring the stride of the image, which means that the pointer can go outside the image data. Consider that the stride is negative if the bitmap is stored upside down.

What is the best and fast way to get pixels' column of bitmap?

What is the fastest way to get column of pixels from bitmap?
I don't consider two loops because it could take to long. I will appreciate for any idea. Thanks!
I would suggest to convert your bitmap into a vector of bytes using the following code:
Bitmpa myImage=new Bitmap("myImage.bmp");
BitmapData bmpData1=myImage.LockBits(new Rectangle(0,0,myImage.Width,myImage.Height),
System.Drawing.Imaging.ImageLockMode.ReadWrite, myImage.PixelFormat);
byte[] myImageData = new byte[bmpData1.Stride * bmpData1.Height];
System.Runtime.InteropServices.Marshal.Copy(bmpData1.Scan0,myImageData,0
,myImageData.Length);
myImgage.UnlockBits(bmpData1);
Then it becomes easy to retrieve the pixels on the column you want. Just loop through the vector myImageData with a step equals to the number of pixels per rows * the number of bytes per pixel.
Just be careful about the number of bytes per pixel are used for the representation of your bitmap.
This depends on the PixelFormat .
Assuming you only need a single column, the following routines should help you.
First here is the standard solution using verifiable C# code
static Color[] GetPixelColumn(Bitmap bmp, int x)
{
Color[] pixelColumn = new Color[bmp.Height];
for (int i = 0; i < bmp.Height; ++i)
{
pixelColumn[i] = bmp.GetPixel(x, i);
}
return pixelColumn;
}
Here is a faster alternative, but this requires that you enable unsafe support for the C# compiler
static Color[] GetPixelColumnFast(Bitmap bmp, int x)
{
Color[] pixelColumn = new Color[bmp.Height];
BitmapData pixelData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
unsafe
{
int* pData = (int*)pixelData.Scan0.ToPointer();
pData += x;
for (int i = 0; i < bmp.Height; ++i)
{
pixelColumn[i] = Color.FromArgb(*pData);
pData += bmp.Width;
}
}
bmp.UnlockBits(pixelData);
return pixelColumn;
}
Both functions will return a Color array with the colors of the pixels for a specific column in the bitmap.
Convert it to a array of bytes
private static byte[, ,] BitmapToBytes(Bitmap bitmap)
{
BitmapData bitmapData =
bitmap.LockBits(new Rectangle(new Point(), bitmap.Size), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
byte[] bitmapBytes;
var stride = bitmapData.Stride;
try
{
int byteCount = bitmapData.Stride * bitmap.Height;
bitmapBytes = new byte[byteCount];
Marshal.Copy(bitmapData.Scan0, bitmapBytes, 0, byteCount);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
byte[, ,] result = new byte[3, bitmap.Width, bitmap.Height];
for (int k = 0; k < 3; k++)
{
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
result[k, i, j] = bitmapBytes[j * stride + i * 3 + k];
}
}
}
return result;
}

Reducing Bitmap bit-size in C#

I am using C#, and having an image stored in the object Bitmap.
Now I would like to convert this image into 8bit greyscale, then into a 4-bit greyscale image.
Do you have any tips how this can be made?
In the .NET Bitmap formats, there are no such thing as a 8 or 4 bit grayscale image. The supported formats are enumerated by the PixelFormat enumeration. You can, however, create a 4 or 8 bit image by creating a indexed image (8bppIndexed or 4bppIndexed), where each entry in the palette is a greyscale value.
This code takes a Bitmap and creates a copy as a 8bpp indexed image with greyscale values:
public static Bitmap BitmapToGrayscale(Bitmap source)
{
// Create target image.
int width = source.Width;
int height = source.Height;
Bitmap target = new Bitmap(width,height,PixelFormat.Format8bppIndexed);
// Set the palette to discrete shades of gray
ColorPalette palette = target.Palette;
for(int i = 0 ; i < palette.Entries.Length ; i++)
{
palette.Entries[i] = Color.FromArgb(0,i,i,i);
}
target.Palette = palette;
// Lock bits so we have direct access to bitmap data
BitmapData targetData = target.LockBits(new Rectangle(0, 0, width,height),
ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
BitmapData sourceData = source.LockBits(new Rectangle(0, 0, width,height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
for(int r = 0 ; r < height ; r++)
{
byte* pTarget = (byte*) (targetData.Scan0 + r*targetData.Stride);
byte* pSource = (byte*) (sourceData.Scan0 + r*sourceData.Stride);
for(int c = 0 ; c < width ; c++)
{
byte colorIndex = (byte) (((*pSource)*0.3 + *(pSource + 1)*0.59 + *(pSource + 2)*0.11));
*pTarget = colorIndex;
pTarget++;
pSource += 3;
}
}
}
target.UnlockBits(targetData);
source.UnlockBits(sourceData);
return target;
}
In order to make a 4Bpp image instead, you would need to create the target with PixelFormat.Format4bppIndexed, and then set the ColorPalette to 16 discrete shades of gray. Finally, in the loop you should normalize values 2 be between 0-15 and pack each 2 pixel values into a single byte.
This is the modified code to make a 4bpp greyscale image:
public static Bitmap BitmapToGrayscale4bpp(Bitmap source)
{
// Create target image.
int width = source.Width;
int height = source.Height;
Bitmap target = new Bitmap(width,height,PixelFormat.Format4bppIndexed);
// Set the palette to discrete shades of gray
ColorPalette palette = target.Palette;
for(int i = 0 ; i < palette.Entries.Length ; i++)
{
int cval = 17*i;
palette.Entries[i] = Color.FromArgb(0,cval,cval,cval);
}
target.Palette = palette;
// Lock bits so we have direct access to bitmap data
BitmapData targetData = target.LockBits(new Rectangle(0, 0, width,height),
ImageLockMode.ReadWrite, PixelFormat.Format4bppIndexed);
BitmapData sourceData = source.LockBits(new Rectangle(0, 0, width,height),
ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
unsafe
{
for(int r = 0 ; r < height ; r++)
{
byte* pTarget = (byte*) (targetData.Scan0 + r*targetData.Stride);
byte* pSource = (byte*) (sourceData.Scan0 + r*sourceData.Stride);
byte prevValue = 0;
for(int c = 0 ; c < width ; c++)
{
byte colorIndex = (byte) ((((*pSource)*0.3 + *(pSource + 1)*0.59 + *(pSource + 2)*0.11)) / 16);
if (c % 2 == 0)
prevValue = colorIndex;
else
*(pTarget++) = (byte)(prevValue | colorIndex << 4);
pSource += 3;
}
}
}
target.UnlockBits(targetData);
source.UnlockBits(sourceData);
return target;
}

Categories

Resources