WriteableBitmap set pixels - c#

I am using WPF and i would like to manipulate some pixels of my image. I am using WritableBitmap because i can use it indirect from source. When i try to set some pixels RGB values (in order), the result is not that i expected.
I use this extension method to read source:
public static PixelColor[,] CopyPixels(this BitmapSource source,out int stride)
{
if (source.Format != PixelFormats.Bgra32)
source = new FormatConvertedBitmap(source, PixelFormats.Bgra32, null, 0);
PixelColor[,] pixels = new PixelColor[source.PixelWidth, source.PixelHeight];
stride = source.PixelWidth * ((source.Format.BitsPerPixel + 7) / 8);
GCHandle pinnedPixels = GCHandle.Alloc(pixels, GCHandleType.Pinned);
source.CopyPixels(
new Int32Rect(0, 0, source.PixelWidth, source.PixelHeight),
pinnedPixels.AddrOfPinnedObject(),
pixels.GetLength(0) * pixels.GetLength(1) * 4, stride);
pinnedPixels.Free();
return pixels;
}
The output struct is
[StructLayout(LayoutKind.Sequential)]
public struct PixelColor
{
public byte Blue;
public byte Green;
public byte Red;
public byte Alpha;
}
this is the simple example code to modify pixels (between 200x200 and 300x300) to black:
int stride = 0;
PixelColor[,] PixelData = wBitmap.CopyPixels(out stride);
for (int i = 0; i < PixelData.GetLength(0); i++)
{
for (int j = 0; j < PixelData.GetLength(1); j++)
{
if ((200 < i && 300 > i) && (200 < j && 300 > j))
{
PixelData[i, j].Blue = 0;
PixelData[i, j].Red = 0;
PixelData[i, j].Green = 0;
}
}
}
wBitmap.WritePixels(new Int32Rect(0, 0, wBitmap.PixelWidth, wBitmap.PixelHeight), PixelData, stride,0);
The surprising result is
This is an image with 500x500 parameters. I expected that the result will be an black filled square in the middle of the image instead of black vertical lines. What is the problem with my example code? (The colorful dots behind the lines are part of the original image.)

Just change the order of the dimensions in the 2-dimensional array declaration in your CopyPixels methods:
var pixels = new PixelColor[source.PixelHeight, source.PixelWidth];

Related

Replace colored pixels of bitmap image from another image

I have a performance problem.
For a insole model configurator, we have a piece to upload and many material images to fusion with the piece image.
I should replace every white pixel on the piece image by the corresponding pixel on the material image.
As the material image is not a mono color, I cant replace simply all white by another mono color.
Image sizes are the same. So I simply take a pixel if the color is not transparent from the piece image and with the same X and Z coordinates on the material images, I take a pixel and I set the pixel of the piece image.
But as there are many materials, it takes 5 minutes today.
Is there a mor optimised way to do this ?
Here is my method :
//For every material image, calls the fusion method below.
foreach (string material in System.IO.Directory.GetFiles(materialsPath))
{
var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), material);
}
private static void FusionWhiteImagesWithMaterials(string whiteImageFolder, string file, string materialImageFile)
{
if (file.ToLower().EndsWith(".db") || materialImageFile.ToLower().EndsWith(".db"))
return;
List<CustomPixel> lstColoredPixels = new List<CustomPixel>();
try
{
Bitmap image = new Bitmap(file);
for (int y = 0; y < image.Height; ++y)
{
for (int x = 0; x < image.Width; ++x)
{
if (image.GetPixel(x, y).A > 0)
{
lstColoredPixels.Add(new CustomPixel(x, y));
}
}
}
Bitmap bmpTemp = new Bitmap(materialImageFile);
Bitmap target = new Bitmap(bmpTemp, new Size(image.Size.Width, image.Size.Height));
for (int y = 0; y < target.Height; y++)
{
for (int x = 0; x < target.Width; x++)
{
Color clr = image.GetPixel(x, y);
if (clr.A > 0)
{
if (clr.R > 200 && clr.G > 200 && clr.B > 200)
image.SetPixel(x, y, target.GetPixel(x, y));
else
image.SetPixel(x, y, Color.Gray);
}
}
}
...
image.Save(...);
}
catch (Exception ex)
{
}
}
//I reduced image sizes to keep on the screen. Real image sizes are 500x1240 px.
Replacing the white is one possibility, but it's not a very pretty one. Based on the images you have there, the ideal solution for this is to get the pattern with the correct alpha applied, and then paint the visible black lines over it. This is actually a process with some more steps:
Extract the alpha from the foot shape image
Extract the black lines from the foot shape image
Apply the alpha to the pattern image
Paint the black lines over the alpha-adjusted pattern image
The way I'd approach this is to extract the data of both images as ARGB byte arrays, meaning, each pixel is four bytes, in the order B, G, R, A. Then, for each pixel, we simply copy the alpha byte from the foot shape image into the alpha byte of the pattern image, so you end up with the pattern image, with the transparency of the foot shape applied to it.
Now, in a new byte array of the same size, which starts with pure 00 bytes (meaning, since A,R,G and B are all zero, transparent black), we construct the black line. Pixels can be considered "black" if they're both not white, and visible. So the ideal result, including smooth fades, is to adjust the alpha of this new image to the minimum value of the alpha and the inverse of the brightness. Since it's grayscale, any of the R, G, B will do for brightness. To get the inverse as byte value, we just take (255 - brightness).
Note, if you need to apply this to a load of images, you probably want to extract the bytes, dimensions and stride of the foot pattern image only once in advance, and keep them in variables to give to the alpha-replacing process. In fact, since the black lines image won't change either, a preprocessing step to generate that should speed things up even more.
public static void BakeImages(String whiteFilePath, String materialsFolder, String resultFolder)
{
Int32 width;
Int32 height;
Int32 stride;
// extract bytes of shape & alpha image
Byte[] shapeImageBytes;
using (Bitmap shapeImage = new Bitmap(whiteFilePath))
{
width = shapeImage.Width;
height = shapeImage.Height;
// extract bytes of shape & alpha image
shapeImageBytes = GetImageData(shapeImage, out stride, PixelFormat.Format32bppArgb);
}
using (Bitmap blackImage = ExtractBlackImage(shapeImageBytes, width, height, stride))
{
//For every material image, calls the fusion method below.
foreach (String materialImagePath in Directory.GetFiles(materialsFolder))
{
using (Bitmap patternImage = new Bitmap(materialImagePath))
using (Bitmap result = ApplyAlphaToImage(shapeImageBytes, width, height, stride, patternImage))
{
if (result == null)
continue;
// paint black lines image onto alpha-adjusted pattern image.
using (Graphics g = Graphics.FromImage(result))
g.DrawImage(blackImage, 0, 0);
result.Save(Path.Combine(resultFolder, Path.GetFileNameWithoutExtension(materialImagePath) + ".png"), ImageFormat.Png);
}
}
}
}
The black lines image:
public static Bitmap ExtractBlackImage(Byte[] shapeImageBytes, Int32 width, Int32 height, Int32 stride)
{
// Create black lines image.
Byte[] imageBytesBlack = new Byte[shapeImageBytes.Length];
// Line start offset is set to 3 to immediately get the alpha component.
Int32 lineOffsImg = 3;
for (Int32 y = 0; y < height; y++)
{
Int32 curOffs = lineOffsImg;
for (Int32 x = 0; x < width; x++)
{
// copy either alpha or inverted brightness (whichever is lowest)
// from the shape image onto black lines image as alpha, effectively
// only retaining the visible black lines from the shape image.
// I use curOffs - 1 (red) because it's the simplest operation.
Byte alpha = shapeImageBytes[curOffs];
Byte invBri = (Byte) (255 - shapeImageBytes[curOffs - 1]);
imageBytesBlack[curOffs] = Math.Min(alpha, invBri);
// Adjust offset to next pixel.
curOffs += 4;
}
// Adjust line offset to next line.
lineOffsImg += stride;
}
// Make the black lines images out of the byte array.
return BuildImage(imageBytesBlack, width, height, stride, PixelFormat.Format32bppArgb);
}
The processing to apply the foot image's transparency to the pattern image:
public static Bitmap ApplyAlphaToImage(Byte[] alphaImageBytes, Int32 width, Int32 height, Int32 stride, Bitmap texture)
{
Byte[] imageBytesPattern;
if (texture.Width != width || texture.Height != height)
return null;
// extract bytes of pattern image. Stride should be the same.
Int32 patternStride;
imageBytesPattern = ImageUtils.GetImageData(texture, out patternStride, PixelFormat.Format32bppArgb);
if (patternStride != stride)
return null;
// Line start offset is set to 3 to immediately get the alpha component.
Int32 lineOffsImg = 3;
for (Int32 y = 0; y < height; y++)
{
Int32 curOffs = lineOffsImg;
for (Int32 x = 0; x < width; x++)
{
// copy alpha from shape image onto pattern image.
imageBytesPattern[curOffs] = alphaImageBytes[curOffs];
// Adjust offset to next pixel.
curOffs += 4;
}
// Adjust line offset to next line.
lineOffsImg += stride;
}
// Make a image out of the byte array, and return it.
return BuildImage(imageBytesPattern, width, height, stride, PixelFormat.Format32bppArgb);
}
The helper function to extract the bytes from an image:
public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride, PixelFormat desiredPixelFormat)
{
Int32 width = sourceImage.Width;
Int32 height = sourceImage.Height;
BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, desiredPixelFormat);
stride = sourceData.Stride;
Byte[] data = new Byte[stride * height];
Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
sourceImage.UnlockBits(sourceData);
return data;
}
The helper function to make a new image from a byte array:
public static Bitmap BuildImage(Byte[] sourceData, Int32 width, Int32 height, Int32 stride, PixelFormat pixelFormat)
{
Bitmap newImage = new Bitmap(width, height, pixelFormat);
BitmapData targetData = newImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, newImage.PixelFormat);
// Get actual data width.
Int32 newDataWidth = ((Image.GetPixelFormatSize(pixelFormat) * width) + 7) / 8;
Int32 targetStride = targetData.Stride;
Int64 scan0 = targetData.Scan0.ToInt64();
// Copy per line, copying only data and ignoring any possible padding.
for (Int32 y = 0; y < height; ++y)
Marshal.Copy(sourceData, y * stride, new IntPtr(scan0 + y * targetStride), newDataWidth);
newImage.UnlockBits(targetData);
return newImage;
}
The result in my test tool:
As you see, the black lines are preserved on top of the pattern.
GetPixel/SetPixel are notoriously slow due to locking and other overhead accessing the pixels. To improve performance you will need to use some unmanaged coding to access the data directly.
This answer should shows an example on how to improve speed when working with bitmaps.
Here is some (untested!) code adapted from that anwer:
public static unsafe Image MergeBitmaps(Bitmap mask, Bitmap background)
{
Debug.Assert(mask.PixelFormat == PixelFormat.Format32bppArgb);
BitmapData maskData = mask.LockBits(new Rectangle(0, 0, mask.Width, mask.Height),
ImageLockMode.ReadWrite, mask.PixelFormat);
BitmapData backgroundData = background.LockBits(new Rectangle(0, 0, background.Width, background.Height),
ImageLockMode.ReadWrite, background.PixelFormat);
try
{
byte bytesPerPixel = 4;
/*This time we convert the IntPtr to a ptr*/
byte* maskScan0 = (byte*)maskData.Scan0.ToPointer();
byte* backgroundScan0 = (byte*)backgroundData.Scan0.ToPointer();
for (int i = 0; i < maskData.Height; ++i)
{
for (int j = 0; j < maskData.Width; ++j)
{
byte* maskPtr = maskScan0 + i * maskData.Stride + j * bytesPerPixel;
byte* backPtr = backgroundScan0 + i * backgroundData.Stride + j * bytesPerPixel;
//maskPtr is a pointer to the first byte of the 4-byte color data
//maskPtr[0] = blueComponent;
//maskPtr[1] = greenComponent;
//maskPtr[2] = redComponent;
//maskPtr[3] = alphaComponent;
if (maskPtr[3] > 0 )
{
if (maskPtr[2] > 200 &&
maskPtr[1] > 200 &&
maskPtr[0] > 200)
{
maskPtr[3] = 255;
maskPtr[2] = backPtr[2];
maskPtr[1] = backPtr[1];
maskPtr[0] = backPtr[0];
}
else
{
maskPtr[3] = 255;
maskPtr[2] = 128;
maskPtr[1] = 128;
maskPtr[0] = 128;
}
}
}
}
return mask;
}
finally
{
mask.UnlockBits(maskData);
background.UnlockBits(backgroundData);
}
}
}
I found this solution, it is much more faster.
But it uses too much resources.
Parallel programing in C# came to my help :
//I called my method in a parallel foreach
Parallel.ForEach(System.IO.Directory.GetFiles(materialsPath), filling =>
{
var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), filling);
});
//Instead of a classic foreach loop like this.
foreach (string material in System.IO.Directory.GetFiles(materialsPath))
{
var result = FillWhiteImages(whiteImagesFolder, whiteImagesFolder + "\\" + System.IO.Path.GetFileName(whiteFilePath), material);
}

Convert ARGB to PARGB

I've been looking for a fast alternative method of SetPixel() and I have found this link : C# - Faster Alternatives to SetPixel and GetPixel for Bitmaps for Windows Forms App
So my problem is that I've an image and I want to create a copy as a DirectBitmap object but first I need to convert ARGB to PARGB so I used this code:
public static Color PremultiplyAlpha(Color pixel)
{
return Color.FromArgb(
pixel.A,
PremultiplyAlpha_Component(pixel.R, pixel.A),
PremultiplyAlpha_Component(pixel.G, pixel.A),
PremultiplyAlpha_Component(pixel.B, pixel.A));
}
private static byte PremultiplyAlpha_Component(byte source, byte alpha)
{
return (byte)((float)source * (float)alpha / (float)byte.MaxValue + 0.5f);
}
and Here's my copy code:
DirectBitmap DBMP = new DirectBitmap(img.Width, img.Height);
MyImage myImg = new MyImage(img as Bitmap);
for (int i = 0; i < img.Width; i++)
{
for (int j = 0; j < img.Height; j++)
{
Color PARGB = NativeWin32.PremultiplyAlpha(Color.FromArgb(myImg.RGB[i, j].Alpha,
myImg.RGB[i, j].R, myImg.RGB[i, j].G, myImg.RGB[i, j].B));
byte[] bitMapData = new byte[4];
bitMapData[3] = (byte)PARGB.A;
bitMapData[2] = (byte)PARGB.R;
bitMapData[1] = (byte)PARGB.G;
bitMapData[0] = (byte)PARGB.B;
DBMP.Bits[(i * img.Height) + j] = BitConverter.ToInt32(bitMapData, 0);
}
}
MyImage : a class containing a Bitmap object along with an array of struct RGB storing the colors of each pixel
However, this code gives me a messed up image. what am I doing wrong?
Bitmap data is organized horizontal line after horizontal line. Therefore, your last line should be:
DBMP.Bits[j * img.Width + i] = BitConverter.ToInt32(bitMapData, 0);

What is the WPF equivalent of displaying an Image on a Canvas using ImageData in Java SWT

What is the WPF equivalent of the following Java SWT code? I want to create an Image from a list of RGBA values and display on a Canvas.
private Image GetImage()
{
ImageData imageData = new ImageData(imageWidth, imageHeight,32,palette);
int pixelVecLoc=0;
for (int h = 0; h<imageHeight && (pixelVecLoc < currentImagePixelVec.size()); h++)
{
for (int w = 0; w<imageWidth && (pixelVecLoc < currentImagePixelVec.size()); w++)
{
int p = 0;
Pixel pixel = currentImagePixelVec.get(pixelVecLoc);
p = (pixel.Alpha<<24) | (pixel.Red<<16) | (pixel.Green<<8) | pixel.Blue;
imageData.setPixel(w, h, p);
pixelVecLoc++;
}
}
imageData = imageData.scaledTo(imageScaleWidth, imageScaleHeight);
Image image = ImageDescriptor.createFromImageData(imageData).createImage();
return image;
}
Then draw it on a Canvas:
gc.drawImage(image, 0, 0);
This is a short snippet showing how you can create a custom RGBA buffer and write pixel data to it (based on this example):
int width = 512;
int height = 256;
int stride = width * 4 + (width % 4);
int pixelWidth = 4; // RGBA (BGRA)
byte[] imageData = new byte[width * stride]; // raw byte buffer
for (int y = 0; y < height; y++)
{
int yPos = y * stride;
for (int x = 0; x < width; x++)
{
int xPos = yPos + x * pixelWidth;
imageData[xPos + 2] = (byte) (RedValue); // replace *Value with source data
imageData[xPos + 1] = (byte) (GreenValue);
imageData[xPos ] = (byte) (BlueValue);
imageData[xPos + 3] = (byte) (AlphaValue);
}
}
Then use the BitmapSource.Create Method (Int32, Int32, Double, Double, PixelFormat, BitmapPalette, IntPtr, Int32, Int32) method together with a PixelFormats:
BitmapSource bmp =
BitmapSource.Create(
width,
height,
96, // Horizontal DPI
96, // Vertical DPI
PixelFormats.Bgra32, // 32-bit BGRA
null, // no palette
imageData, // byte buffer
imageData.Length, // buffer size
stride); // stride
Note that the byte-order is reverse except the alpha component (BGRA) as shown in the snippet.
To transfer the result to canvas you can first create an Image, set the BitmapSource as Source and finally add that to the canvas:
// create image and set image as source
Image BmpImg = New Image();
BmpImg.Width = width;
BmpImg.Height = height;
BmpImg.Source = bmp;
// add image to canvas
canvas.Children.Add(BmpImg);
Canvas.SetLeft(BmpImg, 0); // to set position (x,y)
Canvas.SetTop(BmpImg, 0);

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;
}

How can I speed up this histogram class?

This is supposed to calculate the histogram of an 8-bit grayscale image. With a 1024x770 test bitmap, CreateTime ends up at around 890ms. How can I make this go (way, way) faster?
EDIT: I should mention that this doesn't actually compute the histogram yet, it only gets the values out of the bitmap. So I really should have asked, what is the fastest way to retrieve all pixel values from an 8-bit grayscale image?
public class Histogram {
private static int[,] values;
public Histogram(Bitmap b) {
var sw = Stopwatch.StartNew();
values = new int[b.Width, b.Height];
for (int w = 0; w < b.Width; ++w) {
for (int h = 0; h < b.Height; ++h) {
values[w, h] = b.GetPixel(w, h).R;
}
}
sw.Stop();
CreateTime = (sw.ElapsedTicks /
(double)Stopwatch.Frequency) * 1000;
}
public double CreateTime { get; set; }
}
The basic histogram algorithm is something like:
int[] hist = new hist[256];
//at this point dont forget to initialize your vector with 0s.
for(int i = 0; i < height; ++i)
{
for(int j = 0 ; j < widthl ++j)
{
hist[ image[i,j] ]++;
}
}
The algorithm sums how many pixels with value 0 you have, how many with value=1 and so on.
The basic idea is to use the pixel value as the index to the position of the histogram where you will count.
I have one version of this algorithm written for C# using unmanaged code (which is fast) I dont know if is faster than your but feel free to take it and test, here is the code:
public void Histogram(double[] histogram, Rectangle roi)
{
BitmapData data = Util.SetImageToProcess(image, roi);
if (image.PixelFormat != PixelFormat.Format8bppIndexed)
return;
if (histogram.Length < Util.GrayLevels)
return;
histogram.Initialize();
int width = data.Width;
int height = data.Height;
int offset = data.Stride - width;
unsafe
{
byte* ptr = (byte*)data.Scan0;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x, ++ptr)
histogram[ptr[0]]++;
ptr += offset;
}
}
image.UnlockBits(data);
}
static public BitmapData SetImageToProcess(Bitmap image, Rectangle roi)
{
if (image != null)
return image.LockBits(
roi,
ImageLockMode.ReadWrite,
image.PixelFormat);
return null;
}
I hope I could help you.
You'll want to use the Bitmap.LockBits method to access the pixel data. This is a good reference on the process. Essentially, you're going to need to use unsafe code to iterate over the bitmap data.
Here's a copy/pastable version of the function I've come up w/ based on on this thread.
The unsafe code expects the bitmap to be Format24bppRgb, and if it's not, it'll convert the bitmap to that format and operate on the cloned version.
Note that the call to image.Clone() will throw if you pass in a bitmap using an indexed pixel format, such as Format4bppIndexed.
Takes ~200ms to get a histogram from an image 9100x2048 on my dev machine.
private long[] GetHistogram(Bitmap image)
{
var histogram = new long[256];
bool imageWasCloned = false;
if (image.PixelFormat != PixelFormat.Format24bppRgb)
{
//the unsafe code expects Format24bppRgb, so convert the image...
image = image.Clone(new Rectangle(0, 0, image.Width, image.Height), PixelFormat.Format24bppRgb);
imageWasCloned = true;
}
BitmapData bmd = null;
try
{
bmd = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly,
PixelFormat.Format24bppRgb);
const int pixelSize = 3; //pixels are 3 bytes each w/ Format24bppRgb
//For info on locking the bitmap bits and finding the
//pixels using unsafe code, see http://www.bobpowell.net/lockingbits.htm
int height = bmd.Height;
int width = bmd.Width;
int rowPadding = bmd.Stride - (width * pixelSize);
unsafe
{
byte* pixelPtr = (byte*)bmd.Scan0;//starts on the first row
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{
histogram[(pixelPtr[0] + pixelPtr[1] + pixelPtr[2]) / 3]++;
pixelPtr += pixelSize;//advance to next pixel in the row
}
pixelPtr += rowPadding;//advance ptr to the next pixel row by skipping the padding # the end of each row.
}
}
}
finally
{
if (bmd != null)
image.UnlockBits(bmd);
if (imageWasCloned)
image.Dispose();
}
return histogram;
}

Categories

Resources