Today I wanted to try sth new in image processing in C#.
Szenario as following: I have two black and white images. Now I want to get all white pixels (x,y) from both images and put them into a return image. So in the end my image3 contains all white pixels from image1 and image2.
I'm using unsafe pointers as they are faster.
So in the code I check if image1 in (x,y) == image2 in (x,y) as it is very unlikely that both pictures have a white pixel at the same spot
My approach right now:
private unsafe static Bitmap combine_img(Bitmap img1, Bitmap img2)
{
Bitmap retBitmap = img1;
int width = img1.Width;
int height = img1.Height;
BitmapData image1 = retBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData image2 = img2.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb); // here an error
byte* scan1 = (byte*)image1.Scan0.ToPointer();
int stride1 = image1.Stride;
byte* scan2 = (byte*)image2.Scan0.ToPointer();
int stride2 = image2.Stride;
for (int y = 0; y < height; y++)
{
byte* row1 = scan1 + (y * stride1);
byte* row2 = scan2 + (y * stride2);
for (int x = 0; x < width; x++)
{
if (row1[x] == row2[x])
row1[x] = 255;
}
}
img1.UnlockBits(image1);
img2.UnlockBits(image2);
return retBitmap;
}
unfortunately it returns an error when trying to lock the second image, saying the image has already been locked!
The problem was, that strangely I did pass the same image, here the corrected Code:
private unsafe static void combine_img(Bitmap img1, Bitmap img2)
{
BitmapData image1 = img1.LockBits(new Rectangle(0, 0, img1.Width, img1.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
BitmapData image2 = img2.LockBits(new Rectangle(0, 0, img2.Width, img2.Height), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);
int bytesPerPixel = 3;
byte* scan1 = (byte*)image1.Scan0.ToPointer();
int stride1 = image1.Stride;
byte* scan2 = (byte*)image2.Scan0.ToPointer();
int stride2 = image2.Stride;
for (int y = 0; y < img1.Height; y++)
{
byte* row1 = scan1 + (y * stride1);
byte* row2 = scan2 + (y * stride2);
for (int x = 0; x < img1.Width; x++)
{
if (row2[x * bytesPerPixel] == 255)
row1[x * bytesPerPixel] = row1[x * bytesPerPixel - 1] = row1[x * bytesPerPixel-2] = 255;
}
}
img1.UnlockBits(image1);
img2.UnlockBits(image2);
}
I guess your second image is not 24 bit image. Mayber try something like:
BitmapData image2 = img2.LockBits(new Rectangle(0, 0, img2.Width, img2.Height), ImageLockMode.ReadWrite, img2.PixelFormat);
In this case you will always pass this line (I assume), but problem is you wont know you are actually dealing with 24 bits image or 32 bits image.
Related
When using DiffSetPixel it's adding the different pixels to the new bitmap diffBM when i'm using the second method DiffLockBits it's adding the different pixels to the existing bitmap.
what i want to do is in the first method the DiffSetPixel to be able to add the different pixels to existing bitmap any existing bitmap and not only to the diffBM and in the DiffLockBits i want to add it also to the diffBM.
and in both methods i want to be able to change the different pixels colors for example to red or green or any color and not only grayscale.
DiffSetPixel method
public static Bitmap DiffSetPixel(Bitmap src1, Bitmap src2, int x1, int y1, int x2, int y2, int width, int height)
{
Bitmap diffBM = new Bitmap(width, height, PixelFormat.Format24bppRgb);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
//Get Both Colours at the pixel point
Color col1 = src1.GetPixel(x1 + x, y1 + y);
Color col2 = src2.GetPixel(x2 + x, y2 + y);
// Get the difference RGB
int r = 0, g = 0, b = 0;
r = Math.Abs(col1.R - col2.R);
g = Math.Abs(col1.G - col2.G);
b = Math.Abs(col1.B - col2.B);
// Invert the difference average
int dif = 255 - ((r + g + b) / 3);
// Create new grayscale RGB colour
Color newcol = Color.FromArgb(dif,dif,dif);
diffBM.SetPixel(x, y, newcol);
}
}
return diffBM;
}
DiffLockBits method
public static Bitmap DiffLockBits(Bitmap bmp1 , Bitmap bmp2)
{
Bitmap diffBM = new Bitmap(bmp1.Width, bmp1.Height, PixelFormat.Format24bppRgb);
//Lock bitmap's bits to system memory
Rectangle rect = new Rectangle(0, 0, bmp1.Width, bmp1.Height);
BitmapData bmpData = bmp1.LockBits(rect, ImageLockMode.ReadWrite, bmp1.PixelFormat);
Rectangle rect1 = new Rectangle(0, 0, bmp2.Width, bmp2.Height);
BitmapData bmpData1 = bmp2.LockBits(rect1, ImageLockMode.ReadWrite, bmp2.PixelFormat);
//Scan for the first line
IntPtr ptr = bmpData.Scan0;
IntPtr ptr1 = bmpData1.Scan0;
//Declare an array in which your RGB values will be stored
int bytes = Math.Abs(bmpData.Stride) * bmp1.Height;
byte[] rgbValues = new byte[bytes];
int bytes1 = Math.Abs(bmpData1.Stride) * bmp2.Height;
byte[] rgbValues1 = new byte[bytes1];
//Copy RGB values in that array
Marshal.Copy(ptr, rgbValues, 0, bytes);
Marshal.Copy(ptr1, rgbValues1, 0, bytes1);
for (int i = 0; i < rgbValues.Length; i += 3)
{
if (rgbValues[i] != rgbValues1[i])
{
//Set RGB values in a Array where all RGB values are stored
byte gray = (byte)(rgbValues[i] * .21 + rgbValues[i + 1] * .71 + rgbValues[i + 2] * .071);
rgbValues[i] = rgbValues[i + 1] = rgbValues[i + 2] = gray;
}
}
//Copy changed RGB values back to bitmap
Marshal.Copy(rgbValues, 0, ptr, bytes);
//Unlock the bits
bmp1.UnlockBits(bmpData);
bmp2.UnlockBits(bmpData1);
return bmp1;
}
I am trying to extract a specific area from a bitmap for further processing. In rare cases an error occurs when Marshal.Copy is called. This can be reproduced with the following example code:
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int x = 168;
int y = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(x, y, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int byteCount = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] pixels = new byte[byteCount];
Marshal.Copy(bitmapData.Scan0, pixels, 0, byteCount);
// some further processing
bitmap.UnlockBits(bitmapData);
In other posts I have read that Stride can be negative. That is not the case here.
Why does the error occur and how can I prevent it?
Edit 1:
I have implemented the second suggestion of JonasH. But that also fails with the AccessViolationException. Probably I did not do that correctly.
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int zoneX = 168;
int zoneY = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int rowSize = Math.Abs(bitmapData.Stride);
byte[] pixels = new byte[bitmapData.Height * rowSize];
IntPtr iptr = bitmapData.Scan0;
for (int y = 0; y < bitmapData.Height; y++)
{
Marshal.Copy(IntPtr.Add(iptr, y * rowSize),
pixels,
y * rowSize,
rowSize);
}
bitmap.UnlockBits(bitmapData);
This is probably because you are only locking part of the bitmap. Replace the zone with new Rectangle(0, 0, bitmap.Width , bitmap.Height); and I would expect your problem to disappear.
The alternative would be to restrict the copy-operation to the locked part of the bitmap, but that would require copying row by row and not the entire bitmap at once.
Copying row by row needs careful usage of offsets, you need to keep track of both the horizontal and vertical offsets of both the source and target data. I think something like this should work:
for (int y = 0; y < zoneHeight ; y++)
{
Marshal.Copy(
IntPtr.Add(iptr,(y + zoneY ) * bitmapData.Stride + zoneX)
pixels,
y * zoneWidth,
zoneWidth );
}
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;
I want to compare two images, where only "date of print" is different, I wanted to crop 'date' area only. But I wanted to show full image without crop area, (not the crop area only)
Code I used for cropping
static void Main(string[] args)
{
Bitmap bmp = new Bitmap(#"C:\Users\Public\Pictures\Sample Pictures\1546.jpg");
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
int origByteCount = rawOriginal.Stride * rawOriginal.Height;
byte[] origBytes = new Byte[origByteCount];
System.Runtime.InteropServices.Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);
//I want to crop a 100x100 section starting at 15, 15.
int startX = 15;
int startY = 15;
int width = 100;
int height = 100;
int BPP = 4; //4 Bpp = 32 bits, 3 = 24, etc.
byte[] croppedBytes = new Byte[width * height * BPP];
//Iterate the selected area of the original image, and the full area of the new image
for (int i = 0; i < height; i++)
{
for (int j = 0; j < width * BPP; j += BPP)
{
int origIndex = (startX * rawOriginal.Stride) + (i * rawOriginal.Stride) + (startY * BPP) + (j);
int croppedIndex = (i * width * BPP) + (j);
//copy data: once for each channel
for (int k = 0; k < BPP; k++)
{
croppedBytes[croppedIndex + k] = origBytes[origIndex + k];
}
}
}
//copy new data into a bitmap
Bitmap croppedBitmap = new Bitmap(width, height);
BitmapData croppedData = croppedBitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
System.Runtime.InteropServices.Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
croppedBitmap.Save(#"C:\Users\Public\Pictures\Sample Pictures\AFTERCROP_CROP.jpg");
bmp.Save(#"C:\Users\Public\Pictures\Sample Pictures\AFTERCROP-ORIG.jpg");
}
Your code is a bit overcomplicated, and you seem to be confused about what cropping is - cropping means taking a part of the original picture. What you seem to want instead is to black out some part of the original picture:
The easiest way to accomplish this is by drawing a simple filled rectangle over the original image:
var bmp = Bitmap.FromFile(#"C:\Users\Public\Pictures\Sample Pictures\Chrysanthemum.jpg");
using (var gr = Graphics.FromImage(bmp))
{
gr.FillRectangle(Brushes.Black, 50, 50, 200, 200);
}
If you also want to preserve the original bitmap, you can just copy it over.
I have 3 System.Drawing.Bitmap objects. A RGB foreground, RGB background, and a single byte per pixel mask image where 0 means take the background pixel and 1 means take the foreground pixel. All three are the same dimensions.
I chose to use the Bitmap objects because I need to eventually run this code in MonoTouch and MonoDroid. I can rethink that if needed.
The code I have is optimized but still seems slow. This is also the slowest part of the whole operation so I would like to optimize it more. I also can't help but think there is some secret method that will do all this for me.
If it helps. Prior to this point all 3 are byte[]'s which I turn into images and re-size to uniform dimensions. If I could do things better one step back then let me know.
The code below is what I am currently using and essentially updates the background image with the appropriate foreground pixels.
BitmapData backgroundData = background.LockBits(new System.Drawing.Rectangle(0, 0, background.Width, background.Height), ImageLockMode.ReadOnly, background.PixelFormat);
int backgroundPixelSize = GetPixelSize(backgroundData);
BitmapData foregroundData = foreground.LockBits(new System.Drawing.Rectangle(0, 0, foreground.Width, foreground.Height), ImageLockMode.ReadOnly, foreground.PixelFormat);
int foregroundPixelSize = GetPixelSize(foregroundData);
BitmapData maskData = mask.LockBits(new System.Drawing.Rectangle(0, 0, mask.Width, mask.Height), ImageLockMode.ReadOnly, mask.PixelFormat);
//int maskPixelSize = GetPixelSize(maskData);
for (int y = 0; y < background.Height; y++)
{
byte* backgroundRow = (byte*)backgroundData.Scan0 + (y * backgroundData.Stride);
byte* foregroundRow = (byte*)foregroundData.Scan0 + (y * foregroundData.Stride);
byte* maskRow = (byte*)maskData.Scan0 + (y * maskData.Stride);
for (int x = 0; x < background.Width; x++)
{
// Check if the mask byte is set
if (maskRow[x] > 0)
{
// Copy the bytes over
for (int p = 0; p < backgroundPixelSize; p++)
{
backgroundRow[x * backgroundPixelSize + p] = foregroundRow[x * foregroundPixelSize + p];
}
}
}
}
Update:
The two images are 3 bytes per pixel and the mask image is 1 byte per pixel.
You can try this code. I think it's more fastly and clear (in case of all three images have same sizes).
BitmapData backgroundData = background.LockBits(
new System.Drawing.Rectangle(0, 0, background.Width, background.Height),
ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
BitmapData foregroundData = foreground.LockBits(
new System.Drawing.Rectangle(0, 0, foreground.Width, foreground.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData maskData = mask.LockBits(
new System.Drawing.Rectangle(0, 0, mask.Width, mask.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
uint* backgroundPtr = (uint*)backgroundData.Scan0;
uint* foregroundPtr = (uint*)foregroundData.Scan0;
uint* maskPtr = (uint*)maskData.Scan0;
int dataLength = backgroundData.Height * backgroundData.Width;
for (int i = 0; i < dataLength; i++)
if (maskPtr[i] > 0)
backgroundPtr[i] = foregroundPtr[i];
UPDATE
Also it is possible to use mask PixelFormat:
BitmapData maskData = mask.LockBits(
new System.Drawing.Rectangle(0, 0, mask.Width, mask.Height),
ImageLockMode.ReadOnly, mask.PixelFormat);
byte* maskPtr = (byte*)maskData.Scan0;
int dataLength = backgroundData.Height * backgroundData.Width;
for (int i = 0; i < dataLength; i++)
if (maskPtr[i] > 0)
backgroundPtr[i] = foregroundPtr[i];