i'm trying to do method that should return difference between two images in byte array(rgb values) and send it via UDP. Code: (there's no part with sending this array because it doesn't matter now)
public void getdiff(Bitmap lol,Bitmap lol2)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
Rectangle rect = new Rectangle(0, 0, lol.Width, lol.Height);
BitmapData bmpData = lol.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
IntPtr ptr = bmpData.Scan0;
Rectangle rect2 = new Rectangle(0, 0, lol2.Width, lol2.Height);
BitmapData bmpData2 = lol2.LockBits(rect2, ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
IntPtr ptr2 = bmpData2.Scan0;
int numBytes = bmpData.Stride * lol.Height;
byte[] rgbValues = new byte[numBytes];
int numBytes2 = bmpData2.Stride * lol2.Height;
byte[] rgbValues2 = new byte[numBytes2];
byte[] difference = new byte[numBytes];
Marshal.Copy(ptr, rgbValues, 0, numBytes);
Marshal.Copy(ptr2, rgbValues2, 0, numBytes2);
for (int counter = 0; counter < rgbValues.Length; counter++)
{
if (rgbValues[counter] != rgbValues2[counter])
{
difference[counter] = rgbValues[counter];
}
}
Marshal.Copy(rgbValues, 0, ptr, numBytes);
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
textBox1.Text = ts.Milliseconds.ToString();
lol.UnlockBits(bmpData);
lol2.UnlockBits(bmpData2);
}
Right now I have 2 byte arrays from 2 images, and I just compare them, when images are different, I write adequate RGB values from chosen image to difference[]. Problem is, when RGB values are equal in both images, adequate places in this difference array are filled with 0(triple 0 represent White colour). So problem is, if i would "impose" this difference[] on to target image it will be probably mostly white. I really want to use Marshal.Copy and Lockbits because it's really efficient.
Question is: How can I store this image difference to avoid for multiplying "for" loops at reading/imposing it? Maybe I'm missing some methods? I would like to stay with LockBits and Marshal.Copy, but if you have better ideas - please share with me.
Related
In the code below I want to convert an image to a byte array, modify the value of some pixels, and convert it back to an image.
Everything works fine if I don't modify any pixel value. The two conversions work. But if I try to modify a pixel value between the two conversions I get an exception. Can you tell me what I am doing wrong ?
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.ShowDialog();
FileStream bmpstream = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read);
Bitmap spotImage = new Bitmap(bmpstream);
ImageConverter imgCon = new ImageConverter();
byte[] rgbValues = (byte[])imgCon.ConvertTo(spotImage, typeof(byte[]));
for (int counter = 2; counter < rgbValues.Length; counter += 3)
{
Buffer.SetByte(rgbValues, counter, 0x00);
//rgbValues[counter] = 0x00; <--I tried also this (not sure of the difference) and I get the same result
}
Image image2 = null;
using (MemoryStream mStream = new MemoryStream(rgbValues, 0, rgbValues.Length))
{
image2 = Image.FromStream(mStream, true); //this line gives the exception
}
pictureBox2.Image = image2;
I also tried to open the file using Bitmap.LockBits and got the same result. Here is the second version of the code (again, it worked as long as I didn't modify the byte array):
Rectangle rect = new Rectangle(0, 0, spotImage.Width, spotImage.Height);
BitmapData bmpData = spotImage.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, spotImage.PixelFormat);
IntPtr pointeur = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmpData.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(pointeur, rgbValues, 0, bytes);
for (int counter = 2; counter < rgbValues.Length; counter += 3)
{
Buffer.SetByte(rgbValues, counter, 0x00);
//rgbValues[counter] = 0x00;
}
spotImage.UnlockBits(bmpData);
Image image2 = null;
using (MemoryStream mStream = new MemoryStream(rgbValues, 0, rgbValues.Length))
{
image2 = Image.FromStream(mStream, true); //this line gives the exception
}
pictureBox2.Image = image2;
Your both attempts failed, because as in the comments:
First attempt - you mess up bytes in file header
Second attempt - you try to decode raw pixel data as Image
Your second attempt is much closer to correct solution. Lock bits with Write enabled and stay with the image you already open - no need to rebuild new bitmap (if there is actually such need in your specific case then I leave this to you as an exercise):
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.ShowDialog();
FileStream bmpstream = new FileStream(openFileDialog.FileName, FileMode.Open, FileAccess.Read);
Bitmap spotImage = new Bitmap(bmpstream);
Rectangle rect = new Rectangle(0, 0, spotImage.Width, spotImage.Height);
BitmapData bmpData = spotImage.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite, spotImage.PixelFormat);
IntPtr pointeur = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * bmpData.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(pointeur, rgbValues, 0, bytes);
var pixelSize = Image.GetPixelFormatSize(bmpData.PixelFormat);
for (int counter = 2; counter < rgbValues.Length; counter += 3)
{
rgbValues[counter] = 0x00;
}
// copy buffer back to image
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, pointeur, bytes);
spotImage.UnlockBits(bmpData);
pictureBox2.Image = spotImage;
This should work. I don't know what effect on the image you wanted to achieve though.
I am receiving the following exception:
Exception thrown:
'System.AccessViolationException' in System.Drawing.dll
When calling the Save function of a Bitmap. The procedure works fine the first time around, but subsequent calls throw this exception.
My application takes a single long image and vertically tiles it out into several separate images. I do this by first breaking out the whole image into bytes, then in a Parallel.For loop I generate the bitmap from a subset byte array.
// Generate Bitmap from width, height and bytes
private Bitmap GenerateBitmap(int width, int height, byte[] bytes)
{
Bitmap bmp = new Bitmap(width, height, Stride(width),
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0));
bmp.SetPalette();
return bmp;
}
That is the bitmap generation routine.
Here is the loop body that calls it.
Parallel.For(0, tileCount, i =>
{
byte[] bytes = new byte[imageWidth * tileHeight];
for (int j = 0; j < bytes.Length; j++)
{
bytes[j] = imageBytes[j + (imageWidth * (tileHeight * i))];
}
arr[i] = GenerateBitmap(imageWidth, tileHeight, bytes);
});
And here is the code elsewhere that the exception is thrown.
foreach(Bitmap tile in pattern.Tiles)
{
Console.WriteLine("Creating Tile " + count);
using (Bitmap bmp = new Bitmap(tile))
{
bmp.Save(Globals.patternOutputPath + "tile_" + count + ".png");
}
count += 1;
}
Where Tiles is a property of the pattern that calls the for loop function (which returns a list of Bitmaps).
I am assuming I'm missing some clean up somewhere in here.
Additional info: all images (input and output) are 256 (index) color format.
Edit: The comments below address the problem at hand, and I THINK I've solved the problem. I changed the GenerateBitmap routine to the following and am no longer getting this exception, but I have some more testing to do.
private Bitmap GenerateBitmap(int width, int height, byte[] bytes)
{
Bitmap bmp = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bmpData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, bmp.PixelFormat);
Marshal.Copy(bytes, 0, bmpData.Scan0, bytes.Length);
bmp.UnlockBits(bmpData);
return bmp;
/*Bitmap bmp = new Bitmap(width, height, Stride(width),
PixelFormat.Format8bppIndexed,
Marshal.UnsafeAddrOfPinnedArrayElement(bytes, 0));
bmp.SetPalette();
return bmp;*/
}
I'm using a C# library for reading of QRCodes. A lot of the samples I've found are based off the old version of zxing where the RGBLuminanceSource constructor still takes in bitmap. In the latest version RGBLuminanceSource only takes byte[]. I've tried to convert bitmap to byte[], but the decode result is always null.
Here's the code I used for conversion:
private byte[] GetRGBValues(Bitmap bmp)
{
// Lock the bitmap's bits.
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
return rgbValues;
}
and for decode:
Bitmap bitmap = Bitmap.FromFile(#"C:\QRimages.jpg") as Bitmap;
LuminanceSource source = new RGBLuminanceSource(GetRGBValues(bitmap), bitmap.Width, bitmap.Height);
var binarizer = new HybridBinarizer(source);
var binBitmap = new BinaryBitmap(binarizer);
QRCodeReader reader = new QRCodeReader();
var result = reader.decode(binBitmap);
Somehow the result is always null.
Also, our requirement is that we have to use image taken by camera. I've tried this:
Bitmap bitmap = Bitmap.FromFile(#"C:\QRimages.jpg") as Bitmap;
BarcodeReader reader = new BarcodeReader { AutoRotate = true, TryHarder = true };
Result result = reader.Decode(bitmap);
It only works for the QR image I download online, but if I print out that image and take a picture of that with my phone, then try to process that image, the result is back to null.
Any suggestions would be appreciated.
Here's the image I'm using:
I'm trying to refactor this unsafe code to copy a single ARGB channel from one image to another using System.Runtime.InteropServices.Marshal.Copy as per this example on MSDN but I'm totally lost.
Could anyone walk me through how I would go about it?
public enum ChannelARGB
{
Blue = 0,
Green = 1,
Red = 2,
Alpha = 3
}
public static void transferOneARGBChannelFromOneBitmapToAnother(
Bitmap source,
Bitmap dest,
ChannelARGB sourceChannel,
ChannelARGB destChannel )
{
if ( source.Size!=dest.Size )
throw new ArgumentException();
Rectangle r = new Rectangle( Point.Empty, source.Size );
BitmapData bdSrc = source.LockBits( r,
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb );
BitmapData bdDst = dest.LockBits( r,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb );
unsafe
{
byte* bpSrc = (byte*)bdSrc.Scan0.ToPointer();
byte* bpDst = (byte*)bdDst.Scan0.ToPointer();
bpSrc += (int)sourceChannel;
bpDst += (int)destChannel;
for ( int i = r.Height * r.Width; i > 0; i-- )
{
*bpDst = *bpSrc;
bpSrc += 4;
bpDst += 4;
}
}
source.UnlockBits( bdSrc );
dest.UnlockBits( bdDst );
}
Edit
In an attempt to work through #Ben Voigt walk though I have come up with this so far. Unfortunately I am now getting the following error:
Attempted to read or write protected memory. This is often an
indication that other memory is corrupt.
private static void TransferOneArgbChannelFromOneBitmapToAnother(
Bitmap source,
Bitmap destination,
ChannelARGB sourceChannel,
ChannelARGB destinationChannel)
{
if (source.Size != destination.Size)
{
throw new ArgumentException();
}
Rectangle rectangle = new Rectangle(Point.Empty, source.Size);
// Lockbits the source.
BitmapData bitmapDataSource = source.LockBits(rectangle,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
// Declare an array to hold the bytes of the bitmap.
int bytes = bitmapDataSource.Stride * bitmapDataSource.Height;
// Allocate a buffer for the source image
byte[] sourceRgbValues = new byte[bytes];
// Get the address of the first line.
IntPtr ptrSource = bitmapDataSource.Scan0;
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptrSource,
sourceRgbValues,
0,
bytes);
// Unlockbits the source.
source.UnlockBits(bitmapDataSource);
// Lockbits the destination.
BitmapData bitmapDataDestination = destination.LockBits(rectangle,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
// Allocate a buffer for image
byte[] destinationRgbValues = new byte[bytes];
IntPtr ptrDestination = bitmapDataDestination.Scan0;
// Copy the RGB values into the array.
System.Runtime.InteropServices.Marshal.Copy(ptrDestination,
destinationRgbValues,
0,
bytes);
ptrSource += (int)sourceChannel;
ptrDestination += (int)destinationChannel;
for (int i = rectangle.Height * rectangle.Width; i > 0; i--)
{
destinationRgbValues[i] = sourceRgbValues[i];
ptrSource += 4;
ptrDestination += 4;
}
// Copy the RGB values back to the bitmap
// ******This is where I am getting the exception*******.
System.Runtime.InteropServices.Marshal.Copy(destinationRgbValues,
0,
ptrDestination,
bytes);
// Unlock bits the destination.
destination.UnlockBits(bitmapDataDestination);
}
Can anyone see what I have done wrong? This is all a bit over my head to be honest. I think I should buy some books.
LockBits the source.
Marshal.Copy the source BitmapData to a byte[] buffer.
UnlockBits the source.
LockBits the destination.
Marshal.Copy the destination BitmapData to a byte[] buffer.
Loop through and copy that channel from the source byte[] to the destination byte[] (note, use arithmetic on indexes instead of on pointers)
Marshal.Copy the destination byte[] back to the BitmapData.
UnlockBits the destination.
I'm not sure what the point is, though. Code that uses Marshal.Copy is just as dangerous as code that uses the unsafe keyword, and should require similar code security permission.
A potentially more efficient way would be to use ImageAttributes.SetColorMatrix to remove the desired channel from the destination image, remove all other channels from the source image, and then blend. See the example for ColorMatrix
Or use DirectX (or OpenGL) and a shader that just transfers the one channel.
You could use my simple LINQ based image processing framework from Nuget or Codeplex and write a simple query that swaps the channels around.
You could also use a ColorMatrix to perform the channel swap like in this code.
Unfortunately, a ColorMatrix won't work if you want to combine channels from two separate images. You would need an additive (or bitwise or) blending method, and the only blending provided by GDI+ is Over and Copy. It also looks to me like any methods that would allow you to access the bits directly, including LockBits, are locked down.
I think the only option is to use GetPixel and SetPixel on each pixel, something like this:
Color dstColor = bpDst.GetPixel(x, y);
Color srcColor = bpSrc.GetPixel(x, y);
int srcValue = (srcColor.ToArgb() >> (sourceChannel * 8)) & 0xff;
int dstArgb = (dstColor.ToArgb() & ~(0xff << (destChannel * 8))) | (srcValue << (destChannel * 8));
bpDst.SetPixel(x, y, Color.FromArgb(dstArgb));
Could some rewrite the following function to use any optimized mechanism? I'm pretty sure that this is not the way to proceed, copying pixel by pixel.
I have read about AlphaBlend, or BitBlt, but I'm not used to native code.
public static Bitmap GetAlphaBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
try
{
for (int y = 0; y <= srcData.Height - 1; y++)
{
for (int x = 0; x <= srcData.Width - 1; x++)
{
Color pixelColor = Color.FromArgb(
Marshal.ReadInt32(srcData.Scan0, (srcData.Stride * y) + (4 * x)));
result.SetPixel(x, y, pixelColor);
}
}
}
finally
{
srcBitmap.UnlockBits(srcData);
}
return result;
}
IMPORTANT NOTE: The source image has a wrong pixel format (Format32bppRgb), so I need to adjust the alpha channel. This is the only mechanism that works for me.
The reason why the src image has a wrong pixel format is explained here.
I tried the following options without luck:
Creating a new image and draw the src image using the Graphics.DrawImage from src. Did not preserve the alpha.
Creating a new image using the Scan0 form src. Works fine, but has a problem when the GC dispose the src image (explained in this other post);
This solution is the only that really works, but I know that is not optimal. I need to know how to do it using the WinAPI or other optimal mechanism.
Thank you very much!
Assuming the source image does infact have 32 bits per pixel, this should be a fast enough implementation using unsafe code and pointers. The same can be achieved using marshalling, though at a performance loss of around 10%-20% if I remember correctly.
Using native methods will most likely be faster but this should already be orders of magnitude faster than SetPixel.
public unsafe static Bitmap Clone32BPPBitmap(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
int* srcScan0 = (int*)srcData.Scan0;
int* resScan0 = (int*)resData.Scan0;
int numPixels = srcData.Stride / 4 * srcData.Height;
try
{
for (int p = 0; p < numPixels; p++)
{
resScan0[p] = srcScan0[p];
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Here is the safe version of this method using marshalling:
public static Bitmap Copy32BPPBitmapSafe(Bitmap srcBitmap)
{
Bitmap result = new Bitmap(srcBitmap.Width, srcBitmap.Height, PixelFormat.Format32bppArgb);
Rectangle bmpBounds = new Rectangle(0, 0, srcBitmap.Width, srcBitmap.Height);
BitmapData srcData = srcBitmap.LockBits(bmpBounds, ImageLockMode.ReadOnly, srcBitmap.PixelFormat);
BitmapData resData = result.LockBits(bmpBounds, ImageLockMode.WriteOnly, result.PixelFormat);
Int64 srcScan0 = srcData.Scan0.ToInt64();
Int64 resScan0 = resData.Scan0.ToInt64();
int srcStride = srcData.Stride;
int resStride = resData.Stride;
int rowLength = Math.Abs(srcData.Stride);
try
{
byte[] buffer = new byte[rowLength];
for (int y = 0; y < srcData.Height; y++)
{
Marshal.Copy(new IntPtr(srcScan0 + y * srcStride), buffer, 0, rowLength);
Marshal.Copy(buffer, 0, new IntPtr(resScan0 + y * resStride), rowLength);
}
}
finally
{
srcBitmap.UnlockBits(srcData);
result.UnlockBits(resData);
}
return result;
}
Edit: Your source image has a negative stride, which means the scanlines are stored upside-down in memory (only on the y axis, rows still go from left to right). This effectively means that .Scan0 returns the first pixel of the last row of the bitmap.
As such I modified the code to copy one row at a time.
notice: I've only modified the safe code. The unsafe code still assumes positive strides for both images!
Try the Bitmap Clone method.
A utility class in my Codeblocks library http://codeblocks.codeplex.com allows you to transform a source image to any other image using LINQ.
See this sample here: http://codeblocks.codeplex.com/wikipage?title=Linq%20Image%20Processing%20sample&referringTitle=Home
While the sample transforms the same image format between source and destination, you could change things around, as well.
Note that I have clocked this code and it is much faster than even unsafe code for large images because it uses cached full-row read ahead.