Compare two image data using unsafe method - c#

I am writing a function to get difference between two bitmap images in visual studio 2010. I have a function that takes two bitmap images as parameters, I use unlock bits to get data of each pixel,both the images are of equal resolution and dimensions.
When I use unlock bits for only one image it works well, but when I use it for both simultaneously in the same function it gives an exception
BITMAP REGION IS ALREADY LOCKED
code:
public Bitmap Invert(Bitmap b,Bitmap c)
{
BitmapData bmData =
b.LockBits(new System.Drawing.Rectangle(0, 0, b.Width, b.Height),
ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int stride = bmData.Stride;
System.IntPtr Scan0 = bmData.Scan0;
// for image 2
BitmapData data2 =
c.LockBits(new System.Drawing.Rectangle(0,
0,
c.Width,
c.Height),
ImageLockMode.ReadWrite,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);
int stride1 = data2.Stride;
System.IntPtr Scan1 = data2.Scan0;
unsafe
{
byte* p = (byte*)(void*)Scan0;
byte* q = (byte*)(void*)Scan1;
nOffset = stride - b.Width * 3;
nWidth = b.Width * 3;
for (y = 0; y < b.Height; ++y)
{
for (x = 0; x < nWidth; ++x)
{
p[0] = (byte)(p[0]-q[0]);
++p;
++q;
}
p += nOffset;
q += nOffset;
}
}
b.UnlockBits(bmData);
c.UnlockBits(data2);
return b;
}

Becuase you only need to compare images , I would suggesst opening them in read mode only i.e:
BitmapData data2 = c.LockBits(new System.Drawing.Rectangle(0,
0,
c.Width,
c.Height),
ImageLockMode.ReadOnly,
System.Drawing.Imaging.PixelFormat.Format24bppRgb);

Related

Saving a single channel of a bitmap image to a file [duplicate]

This question already has answers here:
Split PNG into RGB and Alpha Channels
(2 answers)
Closed 3 years ago.
I'm using this code to save a bitmap as binary data.
Bitmap bmp = new Bitmap(screenWidth, position);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.BitmapData bmpData = bmp.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
File.WriteAllBytes(filename, bmp);
g.Dispose();
As I only need the first channel's values, is it possible to retrieve that from the bitmap? Performance is essential.
You're almost there, but there are a few key details missing:
Instead of using bmp.PixelFormat, force the pixel format for the BitmapData object to PixelFormat.Format32BppArgb, then you're 100% sure what structure you will get, and in 32-bit mode, the stride will always exactly match a predictable width * 4. If you don't do this, you may get unexpected results if the read image happens to be paletted or some sort of 16bpp format where each pixel can't be divided into simple colour component bytes.
Loop over the data and extract the channel. The order of the letters 'ARGB' refers to the a hexadecimal value 0xAARRGGBB (like, for example, 0xFF428ED0), which is a little-endian Uint32 value, meaning the actual order of the colour component bytes is the reverse: { BB, GG, RR, AA }.
So, to extract your channel:
// Channels are: B=0, G=1, R=2, A=3
Int32 channel = 1 // for this example, extract the Green channel.
Int32 width;
Int32 height;
Byte[] rgbaValues;
using (Bitmap bmp = new Bitmap(screenWidth, position))
using (Graphics g = Graphics.FromImage(bmp))
{
width = bmp.Width
height = bmp.Height;
g.CopyFromScreen(screenLeft, screenTop, 0, 0, bmp.Size);
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Int32 bytes = bmpData.Stride * bmp.Height;
rgbaValues = new byte[bytes];
Marshal.Copy(bmpData.Scan0, rgbValues, 0, bytes);
bmp.UnlockBits(bmpData);
g.Dispose();
}
Byte[] channelValues = new byte[width * height];
Int32 lineStart = 0;
Int32 lineStartChannel = 0;
for (Int32 y = 0; y < height; ++y)
{
Int32 offset = lineStart;
Int32 offsetChannel = lineStartChannel;
for (Int32 x = 0; x < width; ++x)
{
// For reference:
//Byte blue = rgbaValues[offset + 0];
//Byte green = rgbaValues[offset + 1];
//Byte red = rgbaValues[offset + 2];
//Byte alpha = rgbaValues[offset + 3];
channelValues[offsetChannel] = rgbaValues[offset + channel];
offset += 4;
offsetChannel++;
}
lineStart += stride;
lineStartChannel += width;
}
File.WriteAllBytes(filename, channelValues);
This just saves the data as byte array. If you want to write it as image, the simplest way is probably to make an 8-bit bitmap, open a BitmapData object on it, and write the lines into it one by one, and then set its colour palette to a generated range from 0,0,0 to 255,255,255.
I posted a function that takes a byte array, image dimensions and a palette and makes an image out of it in this answer.

Color to Monochrome conversion

See: Save a 32-bit Bitmap as 1-bit .bmp file in C#
Listing #1
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
byte[] destBytes = new byte[(Width + 7) / 8];//19 bytes
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x / 8] |= (byte)(0x80 >> (x % 8));
}
}
Marshal.Copy(destBytes, 0, (IntPtr)((long)destBmpData.Scan0 + destBmpData.Stride * y), destBytes.Length);
}
dest.UnlockBits(destBmpData);
return dest;
}
Listing #2
public static Bitmap BitmapTo1Bpp222(Bitmap source)
{
int Width = source.Width;
int Height = source.Height;
Bitmap dest = new Bitmap(Width, Height, PixelFormat.Format1bppIndexed);
BitmapData destBmpData = dest.LockBits(new Rectangle(0, 0, Width, Height), ImageLockMode.ReadWrite, PixelFormat.Format1bppIndexed);
int destStride = destBmpData.Stride;
int destSize = Math.Abs(destStride) * Height;
byte[] destBytes = new byte[destSize];
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
Color c = source.GetPixel(x, y);
if (x % 8 == 0)
{
destBytes[x*y / 8] = 0;
}
if (c.GetBrightness() >= 0.5)
{
destBytes[x*y / 8] |= (byte)(0x80 >> (x % 8));
}
}
}
Marshal.Copy(destBytes, 0, destBmpData.Scan0, destBytes.Length);
dest.UnlockBits(destBmpData);
return dest;
}
See the position of Marshal.Copy().
Why does the Listing #1 work, but Listing #2 doesn't?
What modification can make the Listing #2 work?
Both of these are overly complicated. LockBits can convert data to 1bpp. Just open the source as 1bpp, copy its data into the new 1bpp image, and you're done.
I'm also quite baffled by the combination of GetPixel and LockBits. Usually, using LockBits means you realized that GetPixel is a horribly slow waste of time that performs a LockBits internally on every call.
public static Bitmap BitmapTo1Bpp(Bitmap source)
{
Rectangle rect = new Rectangle(0, 0, source.Width, source.Height);
Bitmap dest = new Bitmap(rect.Width, rect.Height, PixelFormat.Format1bppIndexed);
dest.SetResolution(source.HorizontalResolution, source.VerticalResolution);
BitmapData sourceData = source.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed);
BitmapData targetData = dest.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
Int32 actualDataWidth = (rect.Width + 7) / 8;
Int32 h = source.Height;
Int32 origStride = sourceData.Stride;
Int32 targetStride = targetData.Stride;
// buffer for one line of image data.
Byte[] imageData = new Byte[actualDataWidth];
Int64 sourcePos = sourceData.Scan0.ToInt64();
Int64 destPos = targetData.Scan0.ToInt64();
// Copy line by line, skipping by stride but copying actual data width
for (Int32 y = 0; y < h; y++)
{
Marshal.Copy(new IntPtr(sourcePos), imageData, 0, actualDataWidth);
Marshal.Copy(imageData, 0, new IntPtr(destPos), actualDataWidth);
sourcePos += origStride;
destPos += targetStride;
}
dest.UnlockBits(targetData);
source.UnlockBits(sourceData);
return dest;
}
Do note that conversion of data to indexed formats should be avoided in cases where your result is not 1bpp for pure black and white. Indexed formats are paletted, and doing it this way will not do any kind of reduction to an optimised palette approaching the image colours; it will just change the colours on the image to their closest match on the standard palette for this bit depth. For 1bpp this is just black and white, which is perfect, but for 4bpp and 8bpp it will give pretty bad results.
Also note that for some reason you can't convert from a higher to a lower indexed pixel format; it will throw an exception. Since you can convert a bitmap to 32-bit using the new Bitmap(Bitmap) constructor, this problem can easily be avoided by calling the code like this:
public static Bitmap ConvertTo1Bpp(Bitmap source)
{
PixelFormat sourcePf = source.PixelFormat;
if ((sourcePf & PixelFormat.Indexed) == 0 || Image.GetPixelFormatSize(sourcePf) == 1)
return BitmapTo1Bpp(source);
using (Bitmap bm32 = new Bitmap(source))
return BitmapTo1Bpp(bm32);
}

Extracting Bitmap Data to an array of byte

Let's say, I have an array of byte containing raw bitmap data without headers.
However the bitmap data is a bit weird, I'm not quite sure but it seems the bitmap data is not correctly aligned if the width is NPOT (Not Power of Two)
I use following codes to construct the bmp from such bitmap data:
public Bitmap GetBitmap(byte[] bitmapData, int width, int height)
{
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format16bppRgb555);
Rectangle rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
BitmapData bmpData = bitmap.LockBits(rect, ImageLockMode.ReadWrite, bitmap.PixelFormat);
unsafe
{
byte* ptr = (byte*)bmpData.Scan0;
for (int i = 0; i < bitmapData.Length; i++)
{
*ptr = bitmapData[i];
ptr++;
if (width % 2 != 0)
{
if ((i + 1) % (width * 2) == 0 && (i + 1) * 2 % width < width - 1)
{
ptr += 2;
}
}
}
}
bitmap.UnlockBits(bmpData);
return bitmap;
}
The code works fine so far. But for some reasons, I need to implement "Import Bitmap", which mean I need to get the "weird" bitmap data from an instance of bitmap.
How do I do this?
Finally, I figure out how to do this.
I decide to copy the data first to an array of byte via Marshal.Copy and then copy it to another array of bytes while skip some point if the width is NPOT:
public byte[] ImportBitmap(Bitmap bitmap)
{
int width = bitmap.Width, height = bitmap.Height;
var bmpArea = new Rectangle(0, 0, width, height);
var bmpData = bitmap.LockBits(bmpArea, ImageLockMode.ReadWrite, PixelFormat.Format16bppRgb555);
var data = new byte[bmpData.Stride * Height];
Marshal.Copy(bmpData.Scan0, data, 0, data.Length);
bitmap.UnlockBits(bmpData);
bitmap.Dispose(); // bitmap is no longer required
var destination = new List<byte>();
int leapPoint = width * 2;
for (int i = 0; i < data.Length; i++)
{
if (width % 2 != 0)
{
// Skip at some point
if (i == leapPoint)
{
// Skip 2 bytes since it's 16 bit pixel
i += 1;
leapPoint += (width * 2) + 2;
continue;
}
}
destination.Add(data[i]);
}
return destination.ToArray();
}

cropping an area from BitmapData with C#

I have a bitmap sourceImage.bmp
locking it's bits:
BitmapData dataOriginal = sourceImage.LockBits(new Rectangle(0, 0, sourceImage.Width, sourceImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Do analysis, get a clone:
Bitmap originalClone = AForge.Imaging.Image.Clone(dataOriginal);
unlocking bits:
sourceImage.UnlockBits(dataOriginal);
is it possible to specify which part of "dataOriginal" to copy (x,y,w,h)? or to create new data from the dataOriginal, specifying X and Y coordinates as well as H and W?
The aim is to copy a small area from this image. This method might be faster than DrawImage, that's why I don't use the latter.
Edit:
So I took 29 Mb bitmap and did some hardcore testing! Full-size crop (basically a copy) + 100 iterations.
Code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using AForge;
using AForge.Imaging;
using System.Diagnostics;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace testCropClone
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private unsafe Bitmap Clone(Bitmap bmp, int startX, int startY, int width, int height)
{
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];
Marshal.Copy(rawOriginal.Scan0, origBytes, 0, origByteCount);
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);
Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
return croppedBitmap;
}
private Bitmap cloneBitmap(Bitmap bmp, int startX, int startY, int width, int height)
{
Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
Bitmap cloneBitmap = bmp.Clone(srcRect, bmp.PixelFormat);
return cloneBitmap;
}
private Bitmap cloneRectangle(Bitmap bmp, int startX, int startY, int width, int height)
{
Rectangle srcRect = Rectangle.FromLTRB(startX, startY, width, height);
Bitmap dest = new Bitmap(srcRect.Width, srcRect.Height);
Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
using (Graphics graphics = Graphics.FromImage(dest))
{
graphics.DrawImage(bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
return dest;
}
private Bitmap cloneAforge(Bitmap bmp, int startX, int startY, int width, int height)
{
BitmapData rawOriginal = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
Bitmap cloneBitmap = AForge.Imaging.Image.Clone(rawOriginal);
bmp.UnlockBits(rawOriginal);
return cloneBitmap;
}
private void button1_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneAforge(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_aforge.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox1.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button2_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneBitmap(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_bitmap.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox2.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button3_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = Clone(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_bits.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox3.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
private void button4_Click(object sender, EventArgs e)
{
Bitmap source = new Bitmap(#"C:\9\01.bmp");
Stopwatch s1 = Stopwatch.StartNew();
for (int i = 0; i < 100; i++)
{
Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
Clone1.Dispose();
}
/*Bitmap Clone1 = cloneRectangle(source, 0, 0, source.Width, source.Height);
Clone1.Save(#"C:\9\01_rect.bmp");
Clone1.Dispose();*/
s1.Stop();
source.Dispose();
textBox4.Text = ("" + s1.ElapsedMilliseconds / 100 + " ms");
}
}
}
Edit2: (Aforge full-size Crop..) method Nr. 2
for (int i = 0; i < 100; i++)
{
Crop crop = new Crop(new Rectangle(0, 0, source.Width, source.Height));
var source2 = crop.Apply(source);
source2.Dispose();
}
Average = 62ms (40ms less that 1st Aforge approach)
Results:
BitmapClone (0 ms) ?? (cheating, isn't it?)
Aforge #2 (65 ms)
Aforge #1 (105 ms)
Rectangle (170 ms)
Lock Bits (803 ms) (waiting for fixes/new test results..)
I whipped up a quick (and admittedly rough) manual solution that demonstrates how to do this using locked bitmaps. It should be considerably faster than the alternative methods, but does involve a lot more code.
Bitmap bmp = new Bitmap(#"C:\original.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];
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);
Marshal.Copy(croppedBytes, 0, croppedData.Scan0, croppedBytes.Length);
bmp.UnlockBits(rawOriginal);
croppedBitmap.UnlockBits(croppedData);
croppedBitmap.Save(#"C:\test.bmp");
I used this original image:
To output this image, cropped to 100x100 # 15,15:
Obviously if you use this code, you'll want to clean it up a bit and add error handling. If I understand your question correctly, doing things this way should eliminate the need to use AForge at all.
Fopedush's answer benefits greatly when we subsitute Marshal.copy with memcpy, because that way we don't have to copy it through a byte[] array. That way the memory gets copied only once, instead of three times!
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
static unsafe extern int memcpy(byte* dest, byte* src, long count);
static public Bitmap cropBitmap(Bitmap sourceImage, Rectangle rectangle)
{
const int BPP = 4; //4 Bpp = 32 bits; argb
var sourceBitmapdata = sourceImage.LockBits(rectangle, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
var croppedImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppArgb);
var croppedBitmapData = croppedImage.LockBits(new Rectangle(0, 0, rectangle.Width, rectangle.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
unsafe
{
croppedBitmapData.Stride = sourceBitmapdata.Stride;
byte* sourceImagePointer = (byte*)sourceBitmapdata.Scan0.ToPointer();
byte* croppedImagePointer = (byte*)croppedBitmapData.Scan0.ToPointer();
memcpy(croppedImagePointer, sourceImagePointer,
Math.Abs(croppedBitmapData.Stride) * rectangle.Height);
}
sourceImage.UnlockBits(sourceBitmapdata);
croppedImage.UnlockBits(croppedBitmapData);
return croppedImage;
}
My results are:
BitmapClone: 1823 ms
LockBits: 4857 ms
Rectangle: 1479 ms
My method: 559 ms
My method with LockBits on source image done only once (before loop): 160 ms
I don't have AForge so I haven't included that, but by looking on op's results it would be slower than this. I was testing cropping the image in half.
Please note, that if we would exchange memcpy with:
for (int k = 0; k < Math.Abs(croppedBitmapData.Stride) * rectangle.Height; k++)
*(croppedImagePointer++) = *(sourceImagePointer++);
it gets 10x slower!
You can try something like this:
public static Bitmap CropBitmap(Bitmap bitmap, int x, int y, int w, int h)
{
Rectangle rect = new Rectangle(x, y, w, h);
Bitmap cropped = bitmap.Clone(rect, bitmap.PixelFormat);
return cropped;
}
And do something like this in yout code (sample):
var croppedImagem = CropBitmap(dataOriginal, 0, 0, 100, 100);
I hope it helps!
I am a new user and can't vote yet, otherwise I would have upvoted Korwin80's answer as it provides the most efficient working solution, in my opinion. trakos' solution may execute faster but yields scrambled images, at least for me. Here is how I applied Korwin80's solution, with some minor improvements, in my own code:
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private unsafe static extern int memcpy(byte* dest, byte* src, long count);
private unsafe Bitmap Crop(Bitmap srcImg, Rectangle rectangle)
{
if ((srcImg.Width == rectangle.Width) && (srcImg.Height == rectangle.Height))
return srcImg;
var srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
var bpp = srcImgBitmapData.Stride / srcImgBitmapData.Width; // 3 or 4
var srcPtr = (byte*)srcImgBitmapData.Scan0.ToPointer() + rectangle.Y * srcImgBitmapData.Stride + rectangle.X * bpp;
var srcStride = srcImgBitmapData.Stride;
var dstImg = new Bitmap(rectangle.Width, rectangle.Height, srcImg.PixelFormat);
var dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
var dstPtr = (byte*)dstImgBitmapData.Scan0.ToPointer();
var dstStride = dstImgBitmapData.Stride;
for (int y = 0; y < rectangle.Height; y++)
{
memcpy(dstPtr, srcPtr, dstStride);
srcPtr += srcStride;
dstPtr += dstStride;
}
srcImg.UnlockBits(srcImgBitmapData);
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
this class gets your bitmap obj . then lockbits. in ctor.
When you call crop method, it uses memcpy to copy the desired region to new bmp.
lockbits: tells the garbage collector to NOT move my bits anywhere, cuz im gonna modify it by pointers (scan0).
memcpy : fastest copy. can copy memory blocks. optimized by some experts.
why memcpy fast?
instead of copying byte by byte, (widthheight) times memory access .
memcpy does it block by block, much more less than wh times .
internal unsafe sealed class FastImageCroper : IDisposable
{
private readonly Bitmap _srcImg;
private readonly BitmapData _srcImgBitmapData;
private readonly int _bpp;
private readonly byte* _srtPrt;
public FastImageCroper(Bitmap srcImg)
{
_srcImg = srcImg;
_srcImgBitmapData = srcImg.LockBits(new Rectangle(0, 0, srcImg.Width, srcImg.Height), ImageLockMode.ReadOnly, srcImg.PixelFormat);
_bpp = _srcImgBitmapData.Stride / _srcImgBitmapData.Width; // == 4
_srtPrt = (byte*)_srcImgBitmapData.Scan0.ToPointer();
}
public Bitmap Crop(Rectangle rectangle)
{
Bitmap dstImg = new Bitmap(rectangle.Width, rectangle.Height, _srcImg.PixelFormat);
BitmapData dstImgBitmapData = dstImg.LockBits(new Rectangle(0, 0, dstImg.Width, dstImg.Height), ImageLockMode.WriteOnly, dstImg.PixelFormat);
byte* dstPrt = (byte*)dstImgBitmapData.Scan0.ToPointer();
byte* srcPrt = _srtPrt + rectangle.Y*_srcImgBitmapData.Stride + rectangle.X*_bpp;
for (int y = 0; y < rectangle.Height; y++)
{
int srcIndex = y * _srcImgBitmapData.Stride;
int croppedIndex = y * dstImgBitmapData.Stride;
memcpy(dstPrt + croppedIndex, srcPrt + srcIndex, dstImgBitmapData.Stride);
}
dstImg.UnlockBits(dstImgBitmapData);
return dstImg;
}
public void Dispose()
{
_srcImg.UnlockBits(_srcImgBitmapData);
}
[DllImport("msvcrt.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern int memcpy(byte* dest, byte* src, long count);
}

Improving Image compositing Algorithm c# .NET

I was wondering if anyone could shed some light on improvements I can do in making this compositing algorithm faster. What is does is takes 3 images splits them up to get the 1st Images Red Channel, 2nd Images Green channel and the 3rd Images Blue channel and composites them together into 1 new image. Now it works but at an excruciatingly slow pace. The reason i think down to the pixel by pixel processing it has to do on all image components.
The process is to :
For all images:
Extract respective R G and B values -> composite into 1 image -> Save new Image.
foreach (Image[] QRE2ImgComp in QRE2IMGArray)
{
Globals.updProgress = "Processing frames: " + k + " of " + QRE2IMGArray.Count + " frames done.";
QRMProgressUpd(EventArgs.Empty);
Image RedLayer = GetRedImage(QRE2ImgComp[0]);
QRE2ImgComp[0] = RedLayer;
Image GreenLayer = GetGreenImage(QRE2ImgComp[1]);
QRE2ImgComp[1] = GreenLayer;
Image BlueLayer = GetBlueImage(QRE2ImgComp[2]);
QRE2ImgComp[2] = BlueLayer;
Bitmap composite = new Bitmap(QRE2ImgComp[0].Height, QRE2ImgComp[0].Width);
Color Rlayer,Glayer,Blayer;
byte R, G, B;
for (int y = 0; y < composite.Height; y++)
{
for (int x = 0; x < composite.Width; x++)
{
//pixelColorAlpha = composite.GetPixel(x, y);
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
Rlayer = Rcomp.GetPixel(x, y);
Glayer = Gcomp.GetPixel(x, y);
Blayer = Bcomp.GetPixel(x, y);
R = (byte)(Rlayer.R);
G = (byte)(Glayer.G);
B = (byte)(Blayer.B);
composite.SetPixel(x, y, Color.FromArgb((int)R, (int)G, (int)B));
}
}
Globals.updProgress = "Saving frame...";
QRMProgressUpd(EventArgs.Empty);
Image tosave = composite;
Globals.QRFrame = tosave;
tosave.Save("C:\\QRItest\\E" + k + ".png", ImageFormat.Png);
k++;
}
For reference here is the red channel filter method relatively the same for blue and green:
public Image GetRedImage(Image sourceImage)
{
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(sourceImage.Width, sourceImage.Height);
for (int x = 0; x < bmp.Width; x++)
{
for (int y = 0; y < bmp.Height; y++)
{
Color pxl = bmp.GetPixel(x, y);
Color redPxl = Color.FromArgb((int)pxl.R, 0, 0);
redBmp.SetPixel(x, y, redPxl);
}
}
Image tout = (Image)redBmp;
return tout;
}
Move these
Bitmap Rcomp = new Bitmap(QRE2ImgComp[0]);
Bitmap Gcomp = new Bitmap(QRE2ImgComp[1]);
Bitmap Bcomp = new Bitmap(QRE2ImgComp[2]);
outside the for-loops!
Other very important points:
avoid using GetPixel - it is VERY SLOW!
Checkout LockBits etc. - this is how pixel-level access is usually done in .NET
Consider using a 3rd-party library (free or commercial)... several have some optimized method built-in to do what you are trying to achieve...
I totally agree with the points Yahia listed in his answer to improve performance. I'd like to add one more point regarding performance. You could use the Parallel class of the .Net Framework to parallelize the execution of your for loops. The following example makes use of the LockBits method and the Parallel class to improve performance (assuming 32 bits per pixel (PixelFormat.Format32bppArgb)):
public unsafe static Bitmap GetBlueImagePerf(Image sourceImage)
{
int width = sourceImage.Width;
int height = sourceImage.Height;
Bitmap bmp = new Bitmap(sourceImage);
Bitmap redBmp = new Bitmap(width, height, bmp.PixelFormat);
BitmapData bd = bmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, PixelFormat.Format32bppRgb);
BitmapData bd2 = redBmp.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, PixelFormat.Format32bppRgb);
byte* source = (byte*)bd.Scan0.ToPointer();
byte* target = (byte*)bd2.Scan0.ToPointer();
int stride = bd.Stride;
Parallel.For(0, height, (y1) =>
{
byte* s = source + (y1 * stride);
byte* t = target + (y1 * stride);
for (int x = 0; x < width; x++)
{
// use t[1], s[1] to access green channel
// use t[2], s[2] to access red channel
t[0] = s[0];
t += 4; // Add bytes per pixel to current position.
s += 4; // For other pixel formats this value is different.
}
});
bmp.UnlockBits(bd);
redBmp.UnlockBits(bd2);
return redBmp;
}
public unsafe static void DoImageConversion()
{
Bitmap RedLayer = GetRedImagePerf(Image.FromFile("image_path1"));
Bitmap GreenLayer = GetGreenImagePerf(Image.FromFile("image_path2"));
Bitmap BlueLayer = GetBlueImagePerf(Image.FromFile("image_path3"));
Bitmap composite =
new Bitmap(RedLayer.Width, RedLayer.Height, RedLayer.PixelFormat);
BitmapData bd = composite.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
byte* comp = (byte*)bd.Scan0.ToPointer();
BitmapData bdRed = RedLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdGreen = GreenLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
BitmapData bdBlue = BlueLayer.LockBits(new Rectangle(0, 0, RedLayer.Width, RedLayer.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
byte* red = (byte*)bdRed.Scan0.ToPointer();
byte* green = (byte*)bdGreen.Scan0.ToPointer();
byte* blue = (byte*)bdBlue.Scan0.ToPointer();
int stride = bdRed.Stride;
Parallel.For(0, bdRed.Height, (y1) =>
{
byte* r = red + (y1 * stride);
byte* g = green + (y1 * stride);
byte* b = blue + (y1 * stride);
byte* c = comp + (y1 * stride);
for (int x = 0; x < bdRed.Width; x++)
{
c[0] = b[0];
c[1] = g[1];
c[2] = r[2];
r += 4; // Add bytes per pixel to current position.
g += 4; // For other pixel formats this value is different.
b += 4; // Use Image.GetPixelFormatSize to get number of bits per pixel
c += 4;
}
});
composite.Save("save_image_path", ImageFormat.Jpeg);
}
Hope, this answer gives you a starting point for improving your code.

Categories