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();
}
Related
My problem is that I need to convert an image to a byte array to obtain its pixels.
My image size is 268x188 and when I use the property PixelsFormat it returns Format24bppRgb, so I understand that each pixel contains 3 bytes.
If this is true, the size of the pixels should be 268*188*3 = 151152 bytes, but the byte array that I am creating has a size of 4906 bytes, which is the size of the image file in my computer.
I donĀ“t know if there is another way to obtain these pixels or you can only obtain image file size.
If you want to ignore the header and the compression of the file you can do the following.
var path = ...
using(var image = Image.FromFile(path))
using(var bitmap = new Bitmap(image))
{
var bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
var bytesPerPixel = 4; // bitmapData.PixelFormat (image.PixelFormat and bitmapData.PixelFormat can be different)
var ptr = bitmapData.Scan0;
var imageSize = bitmapData.Width * bitmapData.Height * bytesPerPixel;
var data = new byte[imageSize];
for (int x = 0; x < imageSize; x += bytesPerPixel)
{
for(var y = 0; y < bytesPerPixel; y++)
{
data[x + y] = Marshal.ReadByte(ptr);
ptr += 1;
}
}
bitmap.UnlockBits(bitmapData);
}
To get image pixel try this:
public static byte[] GetImageRaw(Bitmap image)
{
if (image == null)
{
throw new ArgumentNullException(nameof(image));
}
if (image.PixelFormat != PixelFormat.Format24bppRgb)
{
throw new NotSupportedException("Invalid pixel format.");
}
const int PixelSize = 3;
var data = image.LockBits(
new Rectangle(Point.Empty, image.Size),
ImageLockMode.ReadWrite,
image.PixelFormat);
try
{
var bytes = new byte[data.Width * data.Height * PixelSize];
for (var y = 0; y < data.Height; ++y)
{
var source = (IntPtr)((long)data.Scan0 + y * data.Stride);
// copy row without padding
Marshal.Copy(source, bytes, y * data.Width * PixelSize, data.Width * PixelSize);
}
return bytes;
}
finally
{
image.UnlockBits(data);
}
}
Take a look at Bitmap.LockBits
I use this code in ASP.NET application. Very simple:
var imagePath = GetFilePathToYourImage();
using (var img = System.IO.File.OpenRead(imagePath))
{
var imageBytes = new byte[img.Length];
img.Read(imageBytes, 0, (int)img.Length);
}
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);
}
Consider the following two routines.
//Tested
///Working fine.
public static Bitmap ToBitmap(int [,] image)
{
int Width = image.GetLength(0);
int Height = image.GetLength(1);
int i, j;
Bitmap bitmap = new Bitmap(Width, Height);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Width, Height),
ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);
unsafe
{
byte* address = (byte*)bitmapData.Scan0;
for (i = 0; i < bitmapData.Height; i++)
{
for (j = 0; j < bitmapData.Width; j++)
{
// write the logic implementation here
address[0] = (byte)image[j, i];
address[1] = (byte)image[j, i];
address[2] = (byte)image[j, i];
address[3] = (byte)255;
//4 bytes per pixel
address += 4;
}//end for j
//4 bytes per pixel
address += (bitmapData.Stride - (bitmapData.Width * 4));
}//end for i
}//end unsafe
bitmap.UnlockBits(bitmapData);
return bitmap;// col;
}
//Tested
///Working fine.
public static int[,] ToInteger(Bitmap bitmap)
{
int[,] array2D = new int[bitmap.Width, bitmap.Height];
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format32bppRgb);
unsafe
{
byte* address = (byte*)bitmapData.Scan0;
int paddingOffset = bitmapData.Stride - (bitmap.Width * 4);//4 bytes per pixel
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
byte[] temp = new byte[4];
temp[0] = address[0];
temp[1] = address[1];
temp[2] = address[2];
temp[3] = address[3];
array2D[j, i] = BitConverter.ToInt32(temp, 0);
//4-bytes per pixel
address += 4;//4-channels
}
address += paddingOffset;
}
}
bitmap.UnlockBits(bitmapData);
return array2D;
}
These two routines work fine for 32bpp images. These routines only work when pixel format is set to PixelFormat.Format32bpp. If I use PixelFormat.Format8bppIndexed, it generates an exception.
In order to avoid that exception (also, I couldn't achieve seamless conversion between byte and int because of address calculation problem), I need to convert that 32 bit Bitmap to gray-scale every time the int[,] is converted back to a Bitmap. I want to get rid of this problem.
Bitmap grayscale = Grayscale.ToGrayscale(InputImage);
//Here, the Bitmap is treated as a 32bit image
//to avoid the exception eventhough it is already
//an 8bpp grayscale image.
int[,] i1 = ImageDataConverter.ToInteger(grayscale);
Complex[,] comp = ImageDataConverter.ToComplex(i1);
int[,] i2 = ImageDataConverter.ToInteger(comp);
Bitmap b2 = ImageDataConverter.ToBitmap(i2);
//It is already a Grayscale image.
//But, the problem is, b2.PixelFormat is set to
//PixelFormat.Formap32bpp because of those routines.
//Hence the unnecessay conversion.
b2 = Grayscale.ToGrayscale(b2);
I need to modify them to operate on 8bpp indexed (grayscale) images only.
How can I achieve that?
If you want to deal with an indexed bitmap, you need to read each byte of the image, and lookup the color from the palette. When you save the image, you'll need to do the reverse logic:
public static Bitmap ToBitmap(int[,] image)
{
int width = image.GetLength(0);
int height = image.GetLength(1);
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
int stride = bitmapData.Stride;
// A dictionary of colors to their index values
Dictionary<int, int> palette = new Dictionary<int, int>();
// A flat list of colors
List<Color> paletteList = new List<Color>();
unsafe
{
byte* address = (byte*)bitmapData.Scan0;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
// Get the color from the Bitmap
int color = image[x, y];
if (!palette.ContainsKey(color))
{
// This color isn't in the palette, go ahead and add it
palette.Add(color, palette.Count);
paletteList.Add(Color.FromArgb(color));
if (palette.Count >= 256)
{
// The palette is too big. Ideally this function would
// dither some pixels so it could handle this condition
// but that would make this example overly complicated
throw new InvalidOperationException("Too many colors in image");
}
}
// And lookup the index of the color in the palette and
// add it to the BitmapData's memory
address[stride * y + x] = (byte)palette[color];
}
}
}
bitmap.UnlockBits(bitmapData);
// Each time you call Bitmap.Palette it actually returns
// a Clone of the object, so we need to ask for a cloned
// copy here.
var newPalette = bitmap.Palette;
// For each one of our colors, add it to the palette object
for (int i = 0; i < paletteList.Count; i++)
{
newPalette.Entries[i] = paletteList[i];
}
// And since this is a clone, assign it back to the bitmap
// so it'll take effect.
bitmap.Palette = newPalette;
return bitmap;
}
public static int[,] ToInteger(Bitmap bitmap)
{
if (bitmap.Palette.Entries.Length == 0)
{
// This doesn't appear to have a palette, so this operation doesn't
// make sense
throw new InvalidOperationException("bitmap is not an indexed bitmap");
}
int width = bitmap.Width;
int height = bitmap.Height;
int[,] array2D = new int[width, height];
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height),
ImageLockMode.ReadOnly,
PixelFormat.Format8bppIndexed);
unsafe
{
// Pull out the stride to prevent asking for it many times
int stride = bitmapData.Stride;
byte* address = (byte*)bitmapData.Scan0;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
// Lookup the color based off the pixel, and set it's value
// to the return array
array2D[x, y] = bitmap.Palette.Entries[address[stride * y + x]].ToArgb();
}
}
}
bitmap.UnlockBits(bitmapData);
return array2D;
}
public static int[,] ToInteger(Bitmap bitmap)
{
int[,] array2D = new int[bitmap.Width, bitmap.Height];
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),
ImageLockMode.ReadWrite,
PixelFormat.Format8bppIndexed);
int bytesPerPixel = sizeof(byte);
unsafe
{
byte* address = (byte*)bitmapData.Scan0;
int paddingOffset = bitmapData.Stride - (bitmap.Width * bytesPerPixel);
for (int i = 0; i < bitmap.Width; i++)
{
for (int j = 0; j < bitmap.Height; j++)
{
byte[] temp = new byte[bytesPerPixel];
for (int k = 0; k < bytesPerPixel; k++)
{
temp[k] = address[k];
}
int iii = 0;
if (bytesPerPixel >= sizeof(int))
{
iii = BitConverter.ToInt32(temp, 0);
}
else
{
iii = (int)temp[0];
}
array2D[j, i] = iii;
address += bytesPerPixel;
}
address += paddingOffset;
}
}
bitmap.UnlockBits(bitmapData);
return array2D;
}
public static Bitmap ToBitmap(int[,] image)
{
int Width = image.GetLength(0);
int Height = image.GetLength(1);
int i, j;
Bitmap bitmap = new Bitmap(Width, Height, PixelFormat.Format8bppIndexed);
BitmapData bitmapData = bitmap.LockBits(new Rectangle(0, 0, Width, Height),
ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
int bytesPerPixel = sizeof(byte);
unsafe
{
byte* address = (byte*)bitmapData.Scan0;
for (i = 0; i < bitmapData.Height; i++)
{
for (j = 0; j < bitmapData.Width; j++)
{
byte[] bytes = BitConverter.GetBytes(image[j, i]);
for (int k = 0; k < bytesPerPixel; k++)
{
address[k] = bytes[k];
}
address += bytesPerPixel;
}
address += (bitmapData.Stride - (bitmapData.Width * bytesPerPixel));
}
}
bitmap.UnlockBits(bitmapData);
Grayscale.SetGrayscalePalette(bitmap);
return bitmap;
}
hope you all doing well. I did write a bit of codes in C# using Aforge library. I wanted to crop my main image captured from webcam so as to have a nice ROI. When I use threshold value of 0 everything should be in white pixels (total of lets say 26880 pixels) but it seems that I have some black pixels (578 pixels) within my cropped image. any idea of what may caused it? when I don't crop my image everything is fine.
Bitmap img = (Bitmap)eventArgs.Frame.Clone();
Bitmap bmp = new Bitmap(x2box, y2box);
bmp = img.Clone(new Rectangle(x1box, y1box, x2box, y2box), eventArgs.Frame.PixelFormat);
Grayscale filter = new Grayscale(0.2125, 0.7154, 0.0721);
Bitmap img1 = filter.Apply(bmp);
Threshold tresh = new Threshold((int)tresh1); // tresh1 is 0-255 but is set to zero here
tresh.ApplyInPlace(img1);
int iterator = 1; int xrow = 0; // here i use these constant to calculate location of the pixels
byte[] arraybyte = BitmapToByteArray(img1);
for (int i = 0; i < arraybyte.Length; i++)
{
if (i - iterator * img1.Width == 0)
{
xrow++;
iterator++;
}
if (arraybyte[i] == 0) // if pixel is black
{
X_val.Add(i - xrow * img1.Width);
Y_val.Add(iterator);
}
}
for (int i = 0; i < X_val.Count; i++)
{
YAve += Y_val[i];
XAve += X_val[i];
}
MessageBox.Show(X_val.Count.ToString()); // shows non-zero value!
the BitmapToByteArray method is as follow:
public static byte[] BitmapToByteArray(Bitmap bitmap)
{
BitmapData bmpdata = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, bitmap.PixelFormat);
int numbytes = bmpdata.Stride * bitmap.Height;
byte[] bytedata = new byte[numbytes];
IntPtr ptr = bmpdata.Scan0;
Marshal.Copy(ptr, bytedata, 0, numbytes);
bitmap.UnlockBits(bmpdata);
return bytedata;
}
The number of bytes for each row of the Bitmap will be enforced to be a multiple of 4. If roi width * bytes per pixel is not a multiple of 4, you will have padding bytes at the end of each row.
They will not be thresholded as they aren't really part of the Bitmap, so their value may be 0. Your BitmapToByteArray method might not be padding-aware and read every byte.
I'm using libtiff.net to make a tiff from a jpeg.
The problem apears when I try to write, because the tiff.writeScanLine returns false, meaning the image isn't written in the tiff.
Why is this happening? And how can I figure out what's wrong?
Here's the code:
private bool creatingTiff()
{
using (Bitmap bmp = new Bitmap(targetFile))
{
using (Tiff tif = Tiff.Open("BitmapTo24BitColorTiff.tif", "w"))
{
byte[] raster = getImageRasterBytes(bmp, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
tif.SetField(TiffTag.IMAGEWIDTH, bmp.Width);
tif.SetField(TiffTag.IMAGELENGTH, bmp.Height);
tif.SetField(TiffTag.COMPRESSION, Compression.OJPEG);
tif.SetField(TiffTag.PHOTOMETRIC, Photometric.YCBCR);
tif.SetField(TiffTag.SUBFILETYPE, 0);
tif.SetField(TiffTag.ROWSPERSTRIP, bmp.Height);
tif.SetField(TiffTag.ORIENTATION, BitMiracle.LibTiff.Classic.Orientation.TOPLEFT);
tif.SetField(TiffTag.XRESOLUTION, bmp.HorizontalResolution);
tif.SetField(TiffTag.YRESOLUTION, bmp.VerticalResolution);
tif.SetField(TiffTag.RESOLUTIONUNIT, ResUnit.INCH);
tif.SetField(TiffTag.BITSPERSAMPLE, 8);
tif.SetField(TiffTag.SAMPLESPERPIXEL, 3);
tif.SetField(TiffTag.JPEGIFOFFSET, 768);
tif.SetField(TiffTag.PLANARCONFIG, PlanarConfig.CONTIG);
for (int i = 0, offset = 0; i < bmp.Height; i++)
{
bool b = tif.WriteScanline(raster, offset, i, 0);
Console.WriteLine("write succes: " + b);
offset += stride;
}
}
System.Diagnostics.Process.Start("BitmapTo24BitColorTiff.tif");
return true;
}
}
private static byte[] getImageRasterBytes(Bitmap bmp, System.Drawing.Imaging.PixelFormat format)
{
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
byte[] bits = null;
try
{
// Lock the managed memory
BitmapData bmpdata = bmp.LockBits(rect, ImageLockMode.ReadWrite, format);
// Declare an array to hold the bytes of the bitmap.
bits = new byte[bmpdata.Stride * bmpdata.Height];
// Copy the values into the array.
System.Runtime.InteropServices.Marshal.Copy(bmpdata.Scan0, bits, 0, bits.Length);
// Release managed memory
bmp.UnlockBits(bmpdata);
}
catch
{
return null;
}
return bits;
}
private static void convertSamples(byte[] data, int width, int height)
{
int stride = data.Length / height;
const int samplesPerPixel = 3;
for (int y = 0; y < height; y++)
{
int offset = stride * y;
int strideEnd = offset + width * samplesPerPixel;
for (int i = offset; i < strideEnd; i += samplesPerPixel)
{
byte temp = data[i + 2];
data[i + 2] = data[i];
data[i] = temp;
}
}
}
The tif-tags are written, but the image itself isn't. Perhaps if someone can point me in the direction of the library developers blog (BitMiracle), I can direct my problem to them directly.
I think your code has the following errors:
You can not use Compression.OJPEG for new images. Old-JPEGs can only be de-compressed.
You probably should not specify TiffTag.JPEGIFOFFSET value by hand. The library will specify proper value itself.
You are trying to write the whole strip using WriteScanline method. You should use WriteEncodedStrip instead.
It also helps to review warnings emitted by the library (it emits them into console).