I can't quite seem to wrap my brain around images and converting them from a byte[] of raw RGB colors to a BitMap. I found one solution that allows me to convert an RGB 24bpp byte[] to a BitMap using SetPixel, but I have read that using LockBits is much faster, so I am trying to figure out how to do it that way.
Using the SetPixel method, I am getting an inverted image using:
public static Bitmap CreateBitmap24bppRgb(byte[] bmpData, int width, int height)
{
var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
var pos = 0;
for (var y = 0; y < height; y++)
{
for (var x = 0; x < width; x++)
{
bmp.SetPixel(x, y, Color.FromArgb(bmpData[pos], bmpData[pos + 1], bmpData[pos + 2]));
pos += 3;
}
}
return bmp;
}
Which I can't quite seem to figure out how to invert. But when I try to use LockBits, the image is just black, and I am unsure what I am doing wrong, it seems quite straight forward.
public static Bitmap CreateBitmap24bppRgb(byte[] data, int width, int height)
{
var bmp = new Bitmap(width, height, PixelFormat.Format24bppRgb);
//Create a BitmapData and Lock all pixels to be written
var bmpData = bmp.LockBits(
new Rectangle(0, 0, bmp.Width, bmp.Height),
ImageLockMode.WriteOnly, bmp.PixelFormat);
//Copy the data from the byte array into BitmapData.Scan0
for (int y = 0; y < bmp.Height - 1; y++)
{
Marshal.Copy(data, y * bmp.Width, bmpData.Scan0 bmpData.Stride);
}
//Unlock the pixels
bmp.UnlockBits(bmpData);
return bmp;
}
I'm just curious what is going wrong here?
If you're creating a new bitmap, instead of modifying an existing one, there's no reason to use LockBits or Marshal.Copy.
Just go with the Bitmap constructor that takes a pointer to pixel data.
public static Bitmap CreateBitmap24bppRgb(byte[] data, int width, int height)
{
GCHandle pin = GCHandle.Alloc(data, GCHandleType.Pinned);
var bmp = new Bitmap(width, height,
(width * 3 + 3) / 4 * 4,
PixelFormat.Format24bppRgb,
Marshal.UnsafeAddrOfPinnedArrayElement(data, 0));
bmp = (Bitmap)bmp.Clone(); // workaround the requirement that the memory address stay valid
// the clone step can also crop and/or change PixelFormat, if desired
GCHandle.Free(pin);
return bmp;
}
(or use an unsafe block, pinned keyword, and a pointer)
Related
I am trying to extract a specific area from a bitmap for further processing. In rare cases an error occurs when Marshal.Copy is called. This can be reproduced with the following example code:
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int x = 168;
int y = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(x, y, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int byteCount = Math.Abs(bitmapData.Stride) * bitmapData.Height;
byte[] pixels = new byte[byteCount];
Marshal.Copy(bitmapData.Scan0, pixels, 0, byteCount);
// some further processing
bitmap.UnlockBits(bitmapData);
In other posts I have read that Stride can be negative. That is not the case here.
Why does the error occur and how can I prevent it?
Edit 1:
I have implemented the second suggestion of JonasH. But that also fails with the AccessViolationException. Probably I did not do that correctly.
Bitmap bitmap = new Bitmap(1741, 2141, PixelFormat.Format1bppIndexed);
int zoneWidth = 50;
int zoneHeight = 50;
int zoneX = 168;
int zoneY = bitmap.Height - zoneHeight;
Rectangle zone = new Rectangle(zoneX, zoneY, zoneWidth, zoneHeight);
BitmapData bitmapData = bitmap.LockBits(zone, ImageLockMode.ReadOnly, bitmap.PixelFormat);
int rowSize = Math.Abs(bitmapData.Stride);
byte[] pixels = new byte[bitmapData.Height * rowSize];
IntPtr iptr = bitmapData.Scan0;
for (int y = 0; y < bitmapData.Height; y++)
{
Marshal.Copy(IntPtr.Add(iptr, y * rowSize),
pixels,
y * rowSize,
rowSize);
}
bitmap.UnlockBits(bitmapData);
This is probably because you are only locking part of the bitmap. Replace the zone with new Rectangle(0, 0, bitmap.Width , bitmap.Height); and I would expect your problem to disappear.
The alternative would be to restrict the copy-operation to the locked part of the bitmap, but that would require copying row by row and not the entire bitmap at once.
Copying row by row needs careful usage of offsets, you need to keep track of both the horizontal and vertical offsets of both the source and target data. I think something like this should work:
for (int y = 0; y < zoneHeight ; y++)
{
Marshal.Copy(
IntPtr.Add(iptr,(y + zoneY ) * bitmapData.Stride + zoneX)
pixels,
y * zoneWidth,
zoneWidth );
}
I'm trying to use the SDK of a sensor for intraoral x-ray. I managed to save the image, but the image doubled.
This is the function to save the image.
private void SaveImage(short[] data, int widht, int height)
{
Bitmap pic = new Bitmap(widht, height);
Rectangle dimension = new Rectangle(0, 0, pic.Width, pic.Height);
BitmapData picData = pic.LockBits(dimension, ImageLockMode.ReadWrite, pic.PixelFormat);
IntPtr pixelStartAddress = picData.Scan0;
Marshal.Copy(data, 0, pixelStartAddress, data.Length);
pic.UnlockBits(picData);
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
}
And this is the image saved
I'm not used to coding in C#. So I'm not sure if I do this right, and if the problem is on the function where i save the image or on the way as populated short array (short[] data);
[Update]
As suggested, I added PixelFormat.Format16bppGrayScale. and the function was like this:
private void SaveImage(short[] data, int widht, int height)
{
Bitmap pic = new Bitmap(widht, height, PixelFormat.Format16bppGrayScale);
Rectangle dimension = new Rectangle(0, 0, pic.Width, pic.Height);
BitmapData picData = pic.LockBits(dimension, ImageLockMode.ReadWrite, pic.PixelFormat) ;
IntPtr pixelStartAddress = picData.Scan0;
Marshal.Copy(data, 0, pixelStartAddress, data.Length);
pic.UnlockBits(picData);
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
}
And I got this error:
System.Runtime.InteropServices.ExternalException: 'GDI+ generic error.'
In this line:
pic.Save(#"C:\Users\WIM\Desktop\teste\teste\teste.jpeg", ImageFormat.Jpeg);
[Update]
I tried to modify the pixel format, and I tested all the other 16bpp. This is the result:
Format16bpp565
Format16bpp555
And for Format16bppArgb1555 result a transparent image.
The pixel format Format16bppGrayScale exists in System.Drawing, but it is not actually implemented or supported. That's why it gives an error when trying to save it.
If this is indeed 16 bpp greyscale data, the only way to solve this in System.Drawing namespace is to reduce your data from 16-bit to 8-bit, treat it as an indexed (paletted) image, and just give it a grey fade from [0,0,0] to [255,255,255] as colour palette.
public static void SaveImage(short[] data, int width, int height, string path)
{
int dataLen = data.Length;
// This might not equal the width.
int inputStride = dataLen / height;
byte[] sourceData = new byte[dataLen];
// Reduce Int16 to bytes by downshifting the data by 8 bits.
// Effectively this divides the number by 256.
for (int i = 0; i < dataLen; ++i)
sourceData[i] = (Byte) (data[i] >> 8);
// Make an 8bpp image
using (Bitmap pic = new Bitmap(width, height, PixelFormat.Format8bppIndexed))
{
BitmapData picData = pic.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, pic.PixelFormat);
// Get actual stride from BitmapData
Int32 targetStride = picData.Stride;
Int64 scan0 = picData.Scan0.ToInt64();
// Stride is ALWAYS rounded up to the next multiple of 4, so copy line by line to avoid issues.
for (int y = 0; y < height; ++y)
Marshal.Copy(sourceData, y * inputStride, new IntPtr(scan0 + y * targetStride), width);
pic.UnlockBits(picData);
// Requesting Bitmap.Palette makes a COPY, so get it out, edit it, and then reassign it.
ColorPalette pal = pic.Palette;
for (int i = 0; i < 256; ++i)
pal.Entries[i] = Color.FromArgb(i, i, i);
pic.Palette = pal;
pic.Save(path, ImageFormat.Png);
}
}
I need to create an image in memory (can be huge image!) and to extract from it byte array in the size of width x height. Each byte must have value of 0-255 (256 gray scale values: 0 for white and 255 for black).
The part of creating the image is easy, here is a simple example of my code:
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
drawing.Clear(Color.Black);// paint the background
drawing.DrawString(text, font, Brushes.White, 0, 0);
Problem is to convert it to "my" special gray scale byte array. When I'm using any pixel format other then Format8bppIndexed, the byte array I'm getting from the bitmap is not in the size I need (width*length) so I need a conversion that takes too much time. When I'm using Format8bppIndexed I'm getting the byte array very fast and in the right size, but each byte/pixel is 0-15.
Changing the bitmap palette has no affect:
var pal = img.Palette;
for (int i = 1; i < 256; i++){
pal.Entries[i] = Color.FromArgb(255, 255, 255);
}
img.Palette = pal;
Any idea how to do it?
Edit: Full code:
// assume font can be Times New Roman, size 7500!
static private Bitmap DrawText(String text, Font font)
{
//first, create a dummy bitmap just to get a graphics object
var img = new Bitmap(1, 1);
var drawing = Graphics.FromImage(img);
//measure the string to see how big the image needs to be
var textSize = drawing.MeasureString(text, font);
//free up the dummy image and old graphics object
img.Dispose();
drawing.Dispose();
//create a new image of the right size (must be multiple of 4)
int width = (int) (textSize.Width/4) * 4;
int height = (int)(textSize.Height / 4) * 4;
img = new Bitmap(width, height);
drawing = Graphics.FromImage(img);
// paint the background
drawing.Clear(Color.Black);
drawing.DrawString(text, font, Brushes.White, 0, 0);
var bmpData = img.LockBits(new Rectangle(0, 0, img.Width, img.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
var newBitmap = new Bitmap(width, height, bmpData.Stride, PixelFormat.Format8bppIndexed, bmpData.Scan0);
drawing.Dispose();
return newBitmap;
}
private static byte[] GetGrayscleBytesFastest(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;
}
You probably want to do this in two steps. First, create a 16bpp grayscale copy of your original image as described in Convert an image to grayscale.
Then, create your 8bpp image with the appropriate color table and draw the 16bpp grayscale image onto that image. That will do the conversion for you, converting the 16-bit grayscale values to your 256 different colors.
You should then have an 8bpp image with your 256 different shades of gray. You can then call LockBits to get access to the bitmap bits, which will be index values in the range 0 to 255.
I have solved this problem with ImageSharp
I calculate the gray value from the rgb values and then add it to the array.
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
private static byte[] GetImageData(byte[] imageData)
{
using (var image = Image.Load<Rgba32>(imageData))
{
var buffer = new byte[image.Width * image.Height];
var index = 0;
image.ProcessPixelRows(accessor =>
{
for (int y = 0; y < accessor.Height; y++)
{
Span<Rgba32> pixelRow = accessor.GetRowSpan(y);
for (int x = 0; x < pixelRow.Length; x++)
{
ref Rgba32 pixel = ref pixelRow[x];
buffer[index] = (byte)((pixel.R + pixel.G + pixel.B) / 3);
index++;
}
}
});
return buffer;
}
}
For Bitmap, there is a MakeTransparent method, is there one similar for changing one color to another?
// This sets Color.White to transparent
Bitmap myBitmap = new Bitmap(sr.Stream);
myBitmap.MakeTransparent(System.Drawing.Color.White);
Is there something that can do something like this?
Bitmap myBitmap = new Bitmap(sr.Stream);
myBitmap.ChangeColor(System.Drawing.Color.Black, System.Drawing.Color.Gray);
Through curiosity to Yorye Nathan's comment, This is an extension that I created by modifying http://msdn.microsoft.com/en-GB/library/ms229672(v=vs.90).aspx.
It can turn all pixels in a bitmap from one colour to another.
public static class BitmapExt
{
public static void ChangeColour(this Bitmap bmp, byte inColourR, byte inColourG, byte inColourB, byte outColourR, byte outColourG, byte outColourB)
{
// Specify a pixel format.
PixelFormat pxf = PixelFormat.Format24bppRgb;
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData =
bmp.LockBits(rect, ImageLockMode.ReadWrite,
pxf);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
// int numBytes = bmp.Width * bmp.Height * 3;
int numBytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[numBytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, numBytes);
// Manipulate the bitmap
for (int counter = 0; counter < rgbValues.Length; counter += 3)
{
if (rgbValues[counter] == inColourR &&
rgbValues[counter + 1] == inColourG &&
rgbValues[counter + 2] == inColourB)
{
rgbValues[counter] = outColourR;
rgbValues[counter + 1] = outColourG;
rgbValues[counter + 2] = outColourB;
}
}
// Copy the RGB values back to the bitmap
Marshal.Copy(rgbValues, 0, ptr, numBytes);
// Unlock the bits.
bmp.UnlockBits(bmpData);
}
}
called by bmp.ChangeColour(0,128,0,0,0,0);
Lifting the code from this answer:
public static class BitmapExtensions
{
public static Bitmap ChangeColor(this Bitmap image, Color fromColor, Color toColor)
{
ImageAttributes attributes = new ImageAttributes();
attributes.SetRemapTable(new ColorMap[]
{
new ColorMap()
{
OldColor = fromColor,
NewColor = toColor,
}
}, ColorAdjustType.Bitmap);
using (Graphics g = Graphics.FromImage(image))
{
g.DrawImage(
image,
new Rectangle(Point.Empty, image.Size),
0, 0, image.Width, image.Height,
GraphicsUnit.Pixel,
attributes);
}
return image;
}
}
While I haven't benchmarked it, this should be faster than any solution that's doing GetPixel/SetPixel in a loop. It's also a bit more straightforward.
You can use SetPixel for that:
private void ChangeColor(Bitmap s, System.Drawing.Color source, System.Drawing.Color target)
{
for (int x = 0; x < s.Width; x++)
{
for (int y = 0; y < s.Height; y++)
{
if (s.GetPixel(x, y) == source)
s.SetPixel(x, y, target);
}
}
}
GetPixel and SetPixel are wrappers around gdiplus.dll functions GdipBitmapGetPixel and GdipBitmapSetPixel accordingly
Remarks:
Depending on the format of the bitmap, GdipBitmapGetPixel might not
return the same value as was set by GdipBitmapSetPixel. For example,
if you call GdipBitmapSetPixel on a Bitmap object whose pixel format
is 32bppPARGB, the pixel's RGB components are premultiplied. A
subsequent call to GdipBitmapGetPixel might return a different value
because of rounding. Also, if you call GdipBitmapSetPixel on a Bitmap
object whose color depth is 16 bits per pixel, information could be
lost during the conversion from 32 to 16 bits, and a subsequent call
to GdipBitmapGetPixel might return a different value.
You need a library that provides a way to modify the color space of an image without having to work with pixels. LeadTools has a pretty extensive image library that you can use that supports color space modifications, including swapping colors.
i am creating png image which painted on my base, from the base i can save a png image, for your reference
Graphics g = e.Graphics;
....
g.DrawLine(pen, new Point(x, y), new Point(x1, y1));
.....
base.OnPaint(e);
using (var bmp = new Bitmap(500, 50))
{
base.DrawToBitmap(bmp, new Rectangle(0, 0, 500, 50));
bmp.Save(outPath);
}
this is single color transparency image, now how do i can inverse this image like png filled with any color and the real image portion should be transparent, is there any possibilities?
bit detail : so transparent will go nontransparent and where there is fill will go to transparent
There's a faster way if you're willing to use unsafe code:
private unsafe void Invert(Bitmap bmp)
{
int w = bmp.Width, h = bmp.Height;
BitmapData data = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
int* bytes = (int*)data.Scan0;
for ( int i = w*h-1; i >= 0; i-- )
bytes[i] = ~bytes[i];
bmp.UnlockBits(data);
}
Note that this doesn't care about the colors and will invert those as well. If you wish to use a specific color, then the code will have to be modified a bit.
EDIT (thanks for Thomas notation)
public void ApplyInvert()
{
byte A, R, G, B;
Color pixelColor;
for (int y = 0; y < bitmapImage.Height; y++)
{
for (int x = 0; x < bitmapImage.Width; x++)
{
pixelColor = bitmapImage.GetPixel(x, y);
A = (byte)(255 - pixelColor.A);
R = pixelColor.R;
G = pixelColor.G;
B = pixelColor.B;
bitmapImage.SetPixel(x, y, Color.FromArgb((int)A, (int)R, (int)G, (int)B));
}
}
}
from here : Image Processing in C#: Inverting an image
For anyone who wants a fast method for inverting Bitmap colors without using unsafe:
public static void BitmapInvertColors(Bitmap bitmapImage)
{
var bitmapRead = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
var bitmapLength = bitmapRead.Stride * bitmapRead.Height;
var bitmapBGRA = new byte[bitmapLength];
Marshal.Copy(bitmapRead.Scan0, bitmapBGRA, 0, bitmapLength);
bitmapImage.UnlockBits(bitmapRead);
for (int i = 0; i < bitmapLength; i += 4)
{
bitmapBGRA[i] = (byte)(255 - bitmapBGRA[i]);
bitmapBGRA[i + 1] = (byte)(255 - bitmapBGRA[i + 1]);
bitmapBGRA[i + 2] = (byte)(255 - bitmapBGRA[i + 2]);
// [i + 3] = ALPHA.
}
var bitmapWrite = bitmapImage.LockBits(new Rectangle(0, 0, bitmapImage.Width, bitmapImage.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppPArgb);
Marshal.Copy(bitmapBGRA, 0, bitmapWrite.Scan0, bitmapLength);
bitmapImage.UnlockBits(bitmapWrite);
}
Bitmap GetPixel and SetPixel are extremly slow, this method works by copying the Bitmap pixels into a byte array, which you can then loop through and change, before finally copying the pixels back.
When you say invert the transparent sections into color, are you storing the real colors in the PNG image just set to full transparency? A lot of programs will optimize a png by removing the color data from transparency so you can't reverse it.
Colors can be converted to transparency
But transparency (without underlying colors) cannot be converted to color.
If your lucky your PNG will be non optimized and still have the original color data intact, but if your doing this from user input then it won't work for a high percentage of cases.