My code calculates average color of each pixel in an image and returns a new image.
Image calculateAverage(Bitmap image1, Bitmap image2)
{
// Locking the image into memory.
BitmapData sourceData = image1.LockBits(new Rectangle(0, 0, image1.Width, image1.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// All of the pixels will be stored in this array(each 4 numbers represent a pixel).
byte[] sourceBuffer = new byte[sourceData.Stride * sourceData.Height];
// Copying the image pixels into sourceBuffer.
Marshal.Copy(sourceData.Scan0, sourceBuffer, 0, sourceBuffer.Length);
// Don't need the image, unlock memory.
image1.UnlockBits(sourceData);
// --- Same thing for the second image ---
BitmapData blendData = image2.LockBits(new Rectangle(0, 0, image2.Width, image2.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte[] blendBuffer = new byte[blendData.Stride * blendData.Height];
Marshal.Copy(blendData.Scan0, blendBuffer, 0, blendBuffer.Length);
image2.UnlockBits(blendData);
//---
// Calculating average of each color in each pixel.
for (int k = 0; (k + 4 < sourceBuffer.Length) && (k + 4 < blendBuffer.Length); k += 4)
{
sourceBuffer[k] = (byte)calcualteAverage(sourceBuffer[k], blendBuffer[k]);
sourceBuffer[k + 1] = (byte)calcualteAverage(sourceBuffer[k + 1], blendBuffer[k + 1]);
sourceBuffer[k + 2] = (byte)calcualteAverage(sourceBuffer[k + 2], blendBuffer[k + 2]);
// Average of Alpha
sourceBuffer[k + 3] = (byte)calcualteAverage(sourceBuffer[k + 3], sourceBuffer[k + 3]);
}
// Saving the result.
Bitmap resultBitmap = new Bitmap(image1.Width, image1.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle(0, 0,
resultBitmap.Width, resultBitmap.Height),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(sourceBuffer, 0, resultData.Scan0, sourceBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap;
}
For performance reasons I need to rewrite this method using unsafe code though I'm a noob with pointers; don't know how it can improve performance or what will change in the algorithm?
The function can be replaced by this:
public static Bitmap CalculateAverage(
Bitmap sourceBitmap, Bitmap blendBitmap)
{
// Locking the images into the memory.
BitmapData sourceData = sourceBitmap.LockBits(
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData blendData = blendBitmap.LockBits(
new Rectangle(0, 0, blendBitmap.Width, blendBitmap.Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
// Get required variables. This wont work for images with different sizes.
int resultWidth = sourceBitmap.Width;
int resultHeight = sourceBitmap.Height;
int stride = sourceData.Stride;
// result will be stored here.
var resultBitmap = new Bitmap(resultWidth, resultHeight);
BitmapData resultData = resultBitmap.LockBits(
new Rectangle(0, 0, resultWidth, resultHeight),
ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
// Getting address of locked images.
byte* sourceAddress = (byte*)sourceData.Scan0.ToPointer();
byte* blendAddress = (byte*)blendData.Scan0.ToPointer();
byte* resultAddress = (byte*)resultData.Scan0.ToPointer();
// Iterating throgh pixels and storing averages inside the result variable.
for (int y = 0; y < resultHeight; y++)
{
for (int x = 0; x < resultWidth; x++)
{
for (int color = 0; color < 4; color++)
{
int currentByte = (y * stride) + x * 4 + color;
resultAddress[currentByte] = (byte)average(
sourceAddress[currentByte],
blendAddress[currentByte]);
}
}
}
}
// Unlocking the Bits; returning the result bitmap.
sourceBitmap.UnlockBits(sourceData);
blendBitmap.UnlockBits(blendData);
resultBitmap.UnlockBits(resultData);
return resultBitmap;
}
I cannot test it at the moment but this might give a hint in the right direction. If there are any questions please don't hesitate to ask. It's based on this code: How to calculate the average rgb color values of a bitmap.
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 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 am trying to generate 16bit grayscale Bitmap in C# from a random data.But it crashed on Marshal.Copy.
Here is my code:
Bitmap b16bpp;
private void GenerateDummy16bitImage()
{
b16bpp = new Bitmap(IMAGE_WIDTH, IMAGE_HEIGHT, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);
var rect = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
var bitmapData = b16bpp.LockBits(rect, ImageLockMode.WriteOnly, b16bpp.PixelFormat);
// Calculate the number of bytes required and allocate them.
var numberOfBytes = bitmapData.Stride * IMAGE_HEIGHT * 2;
var bitmapBytes = new short[numberOfBytes];
// Fill the bitmap bytes with random data.
var random = new Random();
for (int x = 0; x < IMAGE_WIDTH; x++)
{
for (int y = 0; y < IMAGE_HEIGHT; y++)
{
var i = ((y * IMAGE_WIDTH) + x) * 2; // 16bpp
// Generate the next random pixel color value.
var value = (short)random.Next(5);
bitmapBytes[i] = value; // BLUE
bitmapBytes[i + 1] = value; // GREEN
bitmapBytes[i + 2] = value; // RED
// bitmapBytes[i + 3] = 0xFF; // ALPHA
}
}
// Copy the randomized bits to the bitmap pointer.
var ptr = bitmapData.Scan0;
Marshal.Copy(bitmapBytes, 0, ptr, numberOfBytes);//crashes here
// Unlock the bitmap, we're all done.
b16bpp.UnlockBits(bitmapData);
b16bpp.Save("random.bmp", ImageFormat.Bmp);
Debug.WriteLine("saved");
}
The exception is:
An unhandled exception of type 'System.AccessViolationException' occurred in mscorlib.dll
This is not my code.I found it in relation to 32bit Bitmaps and modified.But I guess I have missed something as I am pretty new to C#.
Basically,all I need is to wrap into BitmapData an arrays of shorts.
This works for System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:
private static void SaveBmp(Bitmap bmp, string path)
{
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bitmapData = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
var pixelFormats = ConvertBmpPixelFormat(bmp.PixelFormat);
BitmapSource source = BitmapSource.Create(bmp.Width,
bmp.Height,
bmp.HorizontalResolution,
bmp.VerticalResolution,
pixelFormats,
null,
bitmapData.Scan0,
bitmapData.Stride * bmp.Height,
bitmapData.Stride);
bmp.UnlockBits(bitmapData);
FileStream stream = new FileStream(path, FileMode.Create);
TiffBitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Compression = TiffCompressOption.Zip;
encoder.Frames.Add(BitmapFrame.Create(source));
encoder.Save(stream);
stream.Close();
}
private static System.Windows.Media.PixelFormat ConvertBmpPixelFormat(System.Drawing.Imaging.PixelFormat pixelformat)
{
System.Windows.Media.PixelFormat pixelFormats = System.Windows.Media.PixelFormats.Default;
switch (pixelformat)
{
case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
pixelFormats = PixelFormats.Bgr32;
break;
case System.Drawing.Imaging.PixelFormat.Format8bppIndexed:
pixelFormats = PixelFormats.Gray8;
break;
case System.Drawing.Imaging.PixelFormat.Format16bppGrayScale:
pixelFormats = PixelFormats.Gray16;
break;
}
return pixelFormats;
}
I have corrected some of your mistakes (mostly wrong sizes). But it will still crash on b16bpp.Save(), because GDI+ does not support saving 16bit grayscale images.
Bitmap b16bpp;
private void GenerateDummy16bitImage()
{
b16bpp = new Bitmap(IMAGE_WIDTH, IMAGE_HEIGHT, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale);
var rect = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
var bitmapData = b16bpp.LockBits(rect, ImageLockMode.WriteOnly, b16bpp.PixelFormat);
// Calculate the number of bytes required and allocate them.
var numberOfBytes = bitmapData.Stride * IMAGE_HEIGHT;
var bitmapBytes = new short[IMAGE_WIDTH * IMAGE_HEIGHT];
// Fill the bitmap bytes with random data.
var random = new Random();
for (int x = 0; x < IMAGE_WIDTH; x++)
{
for (int y = 0; y < IMAGE_HEIGHT; y++)
{
var i = ((y * IMAGE_WIDTH) + x); // 16bpp
// Generate the next random pixel color value.
var value = (short)random.Next(5);
bitmapBytes[i] = value; // GRAY
}
}
// Copy the randomized bits to the bitmap pointer.
var ptr = bitmapData.Scan0;
Marshal.Copy(bitmapBytes, 0, ptr, bitmapBytes.Length);
// Unlock the bitmap, we're all done.
b16bpp.UnlockBits(bitmapData);
b16bpp.Save("random.bmp", ImageFormat.Bmp);
Debug.WriteLine("saved");
}
Explanation of my changes:
bitmapData.Stride is already IMAGE_WIDTH * BytesPerPixel so you don't need to multiply by 2
as you declared bitmapBytes as short[] it has to have the size of the image in pixels not in bytes
that means you also do not need to multiply i by 2
since you have a grayscale image it does not have a blue, green and red channel, but one single 16bit gray channel
Marshal.Copy takes the length in "array units" not in bytes
All in all you tried to copy an array 8 times to large into the bitmap.
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();
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];