How to create a bitmap with 16 bits per pixel on MacOS? - c#

I want to create a bitmap that has 16 bits per pixel, but when I specify any of the pixel formats:
PixelFormat.Format16bppArgb1555, PixelFormat.Format16bppGrayScale, PixelFormat.Format16bppRgb555, PixelFormat.Format16bppRgb565
They all throw a System.NotImplementedException with the error message "Not Implemented". I can't allocate 16 bits per pixel manually because Bitmap.SetPixel() only allows you to set a byte to alpha, red, green, or blue (so I need to use marshalling which requires a particular pixel format). Rather than setting a byte for each color value, I'm trying to distribute 16 bits between red, green, and blue. For example 5 bits for red, 5 bits for green, and 5 bits for blue.
Below is an example of one of the 16 bits per pixel pixel formats that throws a System.NotImplementedException:
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format16bppArgb1555);
My system is MacOS.

System.Drawing does not support every pixel format on all platforms. Once I collected the differences (see the Restrictions of Possible Pixel Formats on Different Platforms part). I think MacOS uses the same package as the Linux version.
As Bitmap encoders cannot save images preserving the Format16bppArgb1555 pixel format anyway I suggest you to create a Bitmap instance using the closest compatible format (for example, Format32bppArgb instead of Format16bppArgb1555), and if you really want to transform the colors into the ARGB1555 color space, you can use the Quantize method with PredefinedColorsQuantizer.Argb1555 using the same library I linked for the comparison. You can download it from NuGet.
Disclaimer: I dit not test it on MacOS but it works in Linux (Ubuntu).
Rather than setting a byte for each color value, I'm trying to distribute 16 bits between red, green, and blue. For example 5 bits for red, 5 bits for green, and 5 bits for blue.
Edit: I'm already working on the next version, which will allow using a managed bitmap data with any PixelFormat on any platform. See the public BitmapDataFactory.CreateBitmapData method (not available in a public release yet but you can already play with it if you check out the Development branch).
Update:
I have image bytes (i.e. bytes representing only an image, no header), and I'm testing to see if the format of the image the image bytes decode to is 16 bits per pixel.
Now I see why you need to set pixels in a 16-bit raw format. As in Linux/MacOS the System.Drawing.Bitmap has a limited support the easiest solution if you create managed bitmap data as I mentioned above.
Note: I published a pre-release, which makes the BitmapDataFactory.CreateBitmapData method available without checking out my development branch. And now you can turn your raw bytes of any PixelFormat to a Bitmap like this:
(of course, if pfGuess is not the actual format the result can be messy)
public Bitmap ToBitmap(byte[] rawData, PixelFormat pfGuess, Size size)
{
// the result bitmap will be ARGB32, which works on every platform
Bitmap result = new Bitmap(size.Width, size.Height);
// we create an accessor for the result so we can quickly copy the translated pixels
using var bitmapDataDst = result.GetWritableBitmapData();
// For the managed source any pixel format will work.
// Please note though that unlike System.Drawing.Bitmap images,
// managed bitmap data never has padding bytes at the end of each line.
// So if your rawData has padding (eg. when size has odd width and pf is 16bpp),
// then maybe the best if you include the padding in width.
// See https://learn.microsoft.com/en-us/windows/win32/medfound/image-stride for details
using var bitmapDataSrc = BitmapDataFactory.CreateBitmapData(size, pfGuess);
// Checking if rawData is long enough. RowSize is the width in bytes.
if (rawData.Length < bitmapDataSrc.RowSize * size.Height)
throw new ArgumentException("rawData is too short");
int currentByte = 0;
// iterating through rows
for (int y = 0; y < size.Height; y++)
{
var rowSrc = bitmapDataSrc[y]; // has the specified pixel format
var rowDst = bitmapDataDst[y]; // has ARGB32 pixel format
// writing raw pixels
for (int x = 0; x < bitmapDataSrc.RowSize; x++)
rowSrc.WriteRaw<byte>(x, rawData[currentByte++]);
// copying the pixels interpreted as pfGuess to result
for (int x = 0; x < size.Width; x++)
rowDst[x] = rowSrc[x];
}
// The result bitmap is now populated from the arbitrarily interpreted raw data.
// As it has ARGB32 format you can save it on any platform without any issues.
return result;
}

Related

Structure for holding a 16bits grayscale image

I need to mask image from memory buffers (rectangle area filled with black). So I naively re-use the Bitmap class with ImageFormat.MemoryBmp for my API. This works quite well on my local machine:
public static void MaskBitmap(Bitmap input, Rectangle maskRegion)
{
var bytesPerPixel = Image.GetPixelFormatSize(input.PixelFormat) / 8;
var row = new byte[maskRegion.Width * bytesPerPixel];
var maskData = input.LockBits(maskRegion, ImageLockMode.WriteOnly, input.PixelFormat);
for (var i = 0; i < maskData.Height; ++i)
{
Marshal.Copy(row, 0, maskData.Scan0 + (i * maskData.Stride), row.Length);
}
input.UnlockBits(maskData);
}
However when deploying to production it turns out that the following throw a NotImplementedException:
var image16 = new Bitmap(512, 512, PixelFormat.Format16bppGrayScale);
I eventually tracked it down to here:
https://github.com/mono/libgdiplus/blob/6.0.4/src/bitmap.c#L848
So my question is: is there any existing class in c# that I can re-use to hold images of pixelformat type:
PixelFormat.Format8bppIndexed:
PixelFormat.Format16bppGrayScale:
PixelFormat.Format24bppRgb:
I know GDI+ does not support saving/displaying 16bits image, I simply need a memory structure with image-style access.
Just for reference, I tried the following hack:
var image = new Bitmap(512,512,PixelFormat.Format24bppRgb);
image.Flags = ImageFlags.ColorSpaceGray;
But Flags is read-only.
As you could see, the GDI+ Bitmap does not support the 16bpp grayscale pixel format on Linux at all, and actually its support is quite limited on Windows, too. Once I collected the limitations for both platforms, see the table under the Restrictions of Possible Pixel Formats on Different Platforms section here.
I need to mask image from memory buffers
To use completely managed in-memory representation of a bitmap both on Linux and Windows, you can use this library (disclaimer: written by me). You can create a 16bpp grayscale bitmap data by the BitmapDataFactory.CreateBitmapData method, which returns an IReadWriteBitmapData that allows a lot of managed operations (see the link that enlists the usable extension methods). You can even convert it to an actual Bitmap by the ToBitmap extension, but on Linux this converts the result to a Bitmap with 24bpp RGB pixel format.
Example:
var image16 = BitmapDataFactory.CreateBitmapData(new Size(512, 512), PixelFormat.Format16bppGrayScale);
var row = image16.FirstRow;
do
{
for (int x = 0; x < image16.Width; x++)
{
// row[x] uses a Color32 structure (8 bit per color) but raw access
// enables you to use the full 16-bit color range:
row.WriteRaw<ushort>(x, 32768); // 50% gray
}
} while (row.MoveNextRow());
As for the 8bpp indexed and 24bpp RGB formats, these are supported by the native Bitmap also on Linux, but please note that starting with version .NET 6 System.Drawing will be supported only on Windows by default. Microsoft recommends using other libraries instead, but you can still enable the Unix support by adding "System.Drawing.EnableUnixSupport": true to runtimeconfig.json. Or, if you decide to use my library I mentioned above, just call DrawingModule.Initialize() before anything else, which enables the Unix support without editing any config files.
I think the Wpf bitmaps should support 16-bit grayscale.
However, most systems I have worked on that uses 16-bit grayscale images uses a custom data type. Something like:
public class My16BitImage{
public ushort[] Data {get;}
public int Width {get;}
public int Height {get;}
}
Note that for displaying the image you most probably need to convert it to an 8-bit image anyway, and you probably need to scale the values to make the max/min values map to the largest/smallest 8 bit values.

Confused about LockBits, BitmapData and PixelFormat.Format48bppRgb

Consider the following code:
public static Bitmap Create3x3Bitmap(PixelFormat pixelFormat)
{
var bmp = new Bitmap(3, 3, pixelFormat);
// I know SetPixel does not perform well, this is strictly for learning purposes
bmp.SetPixel(0, 0, Color.Red);
bmp.SetPixel(1, 0, Color.Lime);
bmp.SetPixel(2, 0, Color.Blue);
bmp.SetPixel(0, 1, Color.White);
bmp.SetPixel(1, 1, Color.Gray);
bmp.SetPixel(2, 1, Color.Black);
bmp.SetPixel(0, 2, Color.Cyan);
bmp.SetPixel(1, 2, Color.Fuchsia);
bmp.SetPixel(2, 2, Color.Yellow);
return bmp;
}
The code above should produce a 3 x 3 Bitmap instance which, if magnified, should look like this:
I figured out that I could "scan" the bitmap's pixels information using the Bitmap.LockBits method. I succeeded doing just that using the 24 and 32 bits based PixelFormat values as the pixelFormat argument.
However, I have yet to understand how to work out pixel informations when PixelFormat.Format48bppRgb is used instead:
public void Test()
{
using (var bmp = Create3x3Bitmap(PixelFormat.Format48bppRgb))
{
var lockRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
var data = bmp.LockBits(lockRect, ImageLockMode.ReadWrite, bmp.PixelFormat);
var absStride = Math.Abs(data.Stride); // will be equal to 20
var size = absStride * data.Height; // will be equal to 60
byte[] scanData = new byte[size];
Marshal.Copy(data.Scan0, scanData, 0, size);
// ...
// more stuff here, irrelevant for the actual question
// ...
bmp.UnlockBits(bitmapData);
}
}
If I run the code above using the debugger and break right after the call to Marshal.Copy, I can see that the scanData byte array contains 60 bytes. My understanding is that for each of the three RGB channels, two bytes are required. This means 6 bytes per pixel. There's also two additional unused bytes for each "row" on the y-axis, which in this case is 3 rows.
So if I'm getting this right, here is how I should interpret the array's content for the first row:
Now what confuses me is how I'm supposed to interpret each channel's pair of bytes and translate that back into the original color. It makes sense that for the first pixel (which is red), a pair of 0s would be found for both the blue and green channel. I wasn't so sure what to make of 0 and 32 as a pair that's supposed to mean "full-on red", but looking around the net I came to understand that the range is 0 to 8192 rather than 0 to 65535, due to a limitation of GDI+.
And sure enough, using BitConverter.ToUInt16 got me a value of 8192 for that pair of bytes. So the result for the red pixel makes sense.
However, it also gives 1768 for the "232, 6" pairs found on each channel of the gray pixel (indexes 26 to 31 on the image above). And that's where I am confused. Since gray color's 8 bits channels are midway in the range of 0 to 255, I would have expected something like 4095 or 4096, which is midway between 0 to 8192. 🤷‍♂️
Is my understanding on byte pairs representing channels even correct? If so then how come I got these channel values for gray color?
Minor nit: you wrote this:
There's also two additional empty pixels for each "row" on the y-axis, which in this case is 3 rows.
There are two additional bytes for each row, making the stride 20 bytes instead of the 18 bytes that would be needed for three 6-byte pixel values. This fulfills the GDI+ requirement that the bitmap stride be a multiple of 4.
As far as the question itself goes…
As noted in a comment, the Q&A Why are RGB values of a PixelFormat.Format48bppRgb image only from 0 to 255? should be helpful to you. Indeed, this statement has at least half of the answer to your puzzle:
GDI+ allows only values from 0 to 8192 in the color channel.
This means that when you set a pixel to, for example, red, which has a 24-bit RGB value of (255,0,0), this is extended to 48-bits by creating a value of (8192,0,0). This is different from the (65535,0,0) one might expect with 16 bits per pixel.
The other part is that the pixel channels are stored as two-byte, little-endian, 16-bit values. When you see a byte 0 (0x00), followed by a byte 32 (0x20), the way to interpret that is to store the first byte in a ushort variable, then shift the second byte left by 8 bits and combine that with the first byte in the same variable. E.g.:
byte[] redChannelBytes = { 0x00, 0x20 };
ushort redChannel = redChannelBytes[0] | (redChannelBytes[1] << 8);
Or you can just call BitConverter.ToUInt16(byte[], int)
byte[] redChannelBytes = { 0x00, 0x20 };
ushort redChannel = BitConverter.ToUInt16(redChannelBytes, 0);
Maybe I'm a bit late but I think I can add some useful info to the accepted answer.
Since gray color's 8 bits channels are midway in the range of 0 to 255, I would have expected something like 4095 or 4096, which is midway between 0 to 8192.
It's because the transformation between 'ordinary' and 'wide' pixel formats is not linear. The regular pixel formats (and the Color struct as well) represent colors with gamma correction γ = 2.2, whereas the wide pixel formats have no gamma correction (γ = 1.0).
To get the correct 13bpp level, you can use the following formula:
outputLevel = 8192 * Math.Pow(inputLevel / 255d, 1d / gamma)
which gives you 1770 for inputLevel = 128 and gamma = 0.45 (= 1 / 2.2), which is very close to 1768.
Actually Windows is cheating a bit as you can notice for low inputLevel values: the formula above gives you 0 for input levels < 5, though try SetPixel with 48/64 bpp format and low RGB color values and you can see that the raw RGB components are never zero and they are always different. So using a proper gamma correction may ridiculously cause loss of information when widening the pixel format... That's why (besides performance) I use lookup tables instead when translating colors. The linked source points to the 48-bit color transformations.
To make things even more complicated all these apply only when using GDI+ on Windows. The ReactOS implementation uses a simple linear transformation for wide formats, and also uses the full 16 bit range as opposed to the Windows' 13 bpp range. And in Mono the libgdiplus implementation simply does not support the wide formats at all.
That's why the linked source firstly checks whether to use the lookup tables. It always respects the actual underlying behavior and can also use the full 16bpp range depending on the actual GDI+ implementation. Feel free to use the library if you wish. One of the main motivations was to simplify the complexity and also to provide fast solutions for manipulating bitmaps of any PixelFormat. It also allows to access the actual underlying raw data in a managed way (see the ReadRaw/WriteRaw examples under the last link).

c# faster way of setting pixel colours

I've picked up a project that creates a noise map using setPixel(). The majority of the runtime of this application is spent within the setPixel() function so I was looking to increase the speed at which the function executes.
I have done some research into this and found that this:
int index = x + (y * Width);
int col = color.ToArgb();
if (this.Bits == null)
{
this.Bits = new Int32[Width * Height];
}
Bits[index] = col;
has been recommended as a quicker approach. However, this is generating a completely black image.
I don't understand 100% how image manipulation and memory pointers work to be able to understand the code completely and refactor it to something better.
Here's the original code the person before implemented:
unsafe
{
var scan0 = (byte*)Iptr;
int bitmapStride = Stride;
int bitmapPixelFormatSize = Depth / 8;
index = (bitmapStride * y) + (x * bitmapPixelFormatSize);
if (bitmapPixelFormatSize == 4)
{
scan0[index + 3] = color.A;
scan0[index + 2] = color.R;
scan0[index + 1] = color.G;
scan0[index] = color.B;
}
else if (bitmapPixelFormatSize == 1)
{
scan0[index] = color.R;
}
else
{
scan0[index + 2] = color.R;
scan0[index + 1] = color.G;
scan0[index] = color.B;
}
}
Iptr is just an IntPtr
Stride is an int, the only place I can find this being set is Stride = (PixelCount/Height) * (Depth / 8)
x is width
y is height
Would I be able to get an explanation of what is happening in the original block of code and possibly some help in understanding how to convert that to something that executes quicker, currently it takes around 500,000ms to finish this function due to a nested for loop of width * height.
Note: The following information was originally created by Bob Powell. The original link is no longer functional, so I have copied this information from the Internet Archive at https://web.archive.org/web/20120330012542/http://bobpowell.net/lockingbits.htm. It's a bit long, but I think it's worth preserving.
I'm not sure if this will serve as a direct answer to your question, but perhaps it will help you in finding a solution.
Using the LockBits method to access image data
Many image processing tasks and even file type conversions, say from 32 bit-per-pixel to 8 bit-per-pixel can be speeded up by accessing the pixel data array directly, rather than relying on GetPixel and SetPixel or other methods.
You will be aware that .NET is a managed code system which most often uses managed data so it's not often that we need to gain access to bytes stored in memory anymore however, image manipulation is one of the few times when managed data access is just too slow and so we need to delve once again into the knotty problems of finding the data and manipulating it.
Before I start on the subject in hand, I'll just remind you that the methods used to access any unmanaged data will be different depending on the language in which your program is written. C# developers have the opportunity, via the unsafe keyword and use of pointers, to access data in memory directly. Visual basic programmers should access such data through the Marshal class methods which may also show a small performance loss.
Lock up your bits
The Bitmap class provides the LockBits and corresponding UnlockBits methods which enable you to fix a portion of the bitmap pixel data array in memory, access it directly and finally replace the bits in the bitmap with the modified data. LockBits returns a BitmapData class that describes the layout and position of the data in the locked array.
The BitmapData class contains the following important properties;
Scan0 The address in memory of the fixed data array
Stride The width, in bytes, of a single row of pixel data. This width
is a multiple, or possibly sub-multiple, of the pixel dimensions of
the image and may be padded out to include a few more bytes. I'll
explain why shortly.
PixelFormat The actual pixel format of the data. This is important
for finding the right bytes
Width The width of the locked image
Height The height of the locked image
The relationship of Scan0 and Stride to the array in memory is shown in figure1.
The Stride property, as shown in figure 1, holds the width of one row in bytes. The size of a row however may not be an exact multiple of the pixel size because for efficiency, the system ensures that the data is packed into rows that begin on a four byte boundary and are padded out to a multiple of four bytes. This means for example that a 24 bit per pixel image 17 pixels wide would have a stride of 52. The used data in each row would take up 317 = 51 bytes and the padding of 1 byte would expand each row to 52 bytes or 134 bytes. A 4BppIndexed image of 17 pixels wide would have a stride of 12. Nine of the bytes, or more properly eight and a half, would contain data and the row would be padded out with a further 3 bytes to a 4 byte boundary.
The data carrying portion of the row, as has been suggested above, is laid out according to the pixel format. A 24 bit per pixel image containing RGB data would have a new pixel every 3 bytes, a 32 bit per pixel RGBA every four bytes. Pixel formats that contain more than one pixel per byte, such as the 4 bit per pixel Indexed and 1 bit per pixel indexed, have to be processed carefully so that the pixel required is not confused with it's neigbour pixels in the same byte.
Finding the right byte.
Because the stride is the width of a row, to index any given row or Y coordinate you can multiply the stride by the Y coordinate to get the beginning of a particular row. Finding the correct pixel within the row is possibly more difficult and depends on knowing the layout of the pixel formats. The following examples show how to access a particular pixel for a given pixel format.
Format32BppArgb Given X and Y coordinates, the address of the first
element in the pixel is Scan0+(y * stride)+(x*4). This Points to the
blue byte. The following three bytes contain the green, red and alpha
bytes.
Format24BppRgb Given X and Y coordinates, the address of the first
element in the pixel is Scan0+(yStride)+(x3). This points to the
blue byte which is followed by the green and the red.
Format8BppIndexed Given the X and Y coordinates the address of the
byte is Scan0+(y*Stride)+x. This byte is the index into the image
palette.
Format4BppIndexed Given X and Y coordinates the byte containing the
pixel data is calculated as Scan0+(y*Stride)+(x/2). The corresponding
byte contains two pixels, the upper nibble is the leftmost and the
lower nibble is the rightmost of two pixels. The four bits of the
upper and lower nibble are used to select the colour from the 16
colour palette.
Format1BppIndexed Given the X and Y coordinates, the byte containing
the pixel is calculated by Scan0+(y*Stride)+(x/8). The byte contains
8 bits, each bit is one pixel with the leftmost pixel in bit 8 and
the rightmost pixel in bit 0. The bits select from the two entry
colour palette.
Iterating through the pixels
For pixel formats with one or more bytes per pixel, the formula is simple and can be accomplished by looping through all Y and X values in order. The code in the following listings sets the blue component of a 32 bit per pixel image to 255. In both cases bm is a bitmap previously created.
BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat);
int PixelSize=4;
for(int y=0; y<bmd.Height; y++)
{
byte* row = (byte *)bmd.Scan0+(y*bmd.Stride);
for(int x = 0; x<bmd.Width; x++)
{
row[x * PixelSize] = 255;
}
}
In VB this operation would be treated a little differently because VB has no knowledge of pointers and requires the use of the marshal class to access unmanaged data.
Dim x As Integer
Dim y As Integer
Dim PixelSize As Integer = 4
Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat)
For y = 0 To bmd.Height - 1
For x = 0 To bmd.Width - 1
Marshal.WriteByte(bmd.Scan0, (bmd.Stride * y) + (4 * x) , 255)
Next
Next
Sub-byte pixels.
The Format4BppIndexed and Format1BppIndexed pixel formats mentioned earlier both have more than one pixel stored in a byte. In such cases, it's up to you to ensure that changing the data for one pixel does not effect the other pixel or pixels held in that byte.
The method for indexing a 1 bit per pixel image relies on using bitwise logical operations And and Or to reset or set specific bits in the byte. After using the formula shown above for 1 bit per pixel images, the lower 3 bits of the X coordinate is used to select the bit required. The listings below show this process in C# and VB. In both examples bmd is a bitmap data extracted from a 1 bit per pixel image.
C# code uses pointers and requires compiling with unsafe code
byte* p=(byte*)bmd.Scan0.ToPointer();
int index=y*bmd.Stride+(x>>3);
byte mask=(byte)(0x80>>(x&0x7));
if(pixel)
p[index]|=mask;
else
p[index]&=(byte)(mask^0xff);
VB code uses the marshal class
Dim mask As Byte = 128 >> (x And 7)
Dim offset As Integer = (y * bmd.Stride) + (x >> 3)
Dim currentPixel As Byte = Marshal.ReadByte(bmd.Scan0, offset)
If pixel = True Then
Marshal.WriteByte(bmd.Scan0, offset, currentPixel Or mask)
Else
Marshal.WriteByte(bmd.Scan0, offset, CByte(currentPixel And (mask Xor 255)))
End If
Note, it's quite valid to use the Marshal class from C# code. I used pointers because it offers the best performance.
Accessing individual pixels in a 4 bit per pixel image is handled in a similar manner. The upper and lower nibble of the byte must be dealt with separately and changing the contents of the odd X pixels should not effect the even X pixels. The code below shows how to perform this in C# and VB.
C#
int offset = (y * bmd.Stride) + (x >> 1);
byte currentByte = ((byte *)bmd.Scan0)[offset];
if((x&1) == 1)
{
currentByte &= 0xF0;
currentByte |= (byte)(colorIndex & 0x0F);
}
else
{
currentByte &= 0x0F;
currentByte |= (byte)(colorIndex << 4);
}
((byte *)bmd.Scan0)[offset]=currentByte;
VB
Dim offset As Integer = (y * bmd.Stride) + (x >> 1)
Dim currentByte As Byte = Marshal.ReadByte(bmd.Scan0, offset)
If (x And 1) = 1 Then
currentByte = currentByte And &HF0
currentByte = currentByte Or (colorIndex And &HF)
Else
currentByte = currentByte And &HF
currentByte = currentByte Or (colorIndex << 4)
End If
Marshal.WriteByte(bmd.Scan0, offset, currentByte)
Using LockBits and UnlockBits
The LockBits method takes a rectangle which may be the same size or smaller than the image being processed, a PixelFormat which is usually the same as that of the image being processed and a ImageLockMode value that specifies whether the data is read-only, write-only, read-write or a user allocated buffer. This last option cannot be used from C# or VB because the method overload for LockBits that specifies a user buffer is not included in the GDI+ managed wrapper.
It is very important that when all operations are complete the BitmapData is put back into the bitmap with the UnlockBits method. The snippet of code below illustrates this.
Dim bmd As BitmapData = bm.LockBits(New Rectangle(0, 0, 10, 10), ImageLockMode.ReadWrite, bm.PixelFormat)
' do operations here
bm.UnlockBits(bmd)
Summary
That just about covers the aspects of accessing the most popular and most difficult pixel formats directly. Using these techniques instead of the GetPixel and SetPixel methods provided by Bitmap will show a marked performance boost to your image processing and image format conversion routines.

What Does the Byte[] Representation of an Image Actually Mean?

I loaded a 1 pixel image into a bitmap and then converted it to a byte[]
_Image = "test.jpg";
Bitmap testImage = new Bitmap(_Image);
ImageConverter converter = new ImageConverter();
byte[] byteTestImage = (byte[])converter.ConvertTo(testImage,typeof(byte[]));
The single pixel has RGB values (255, 116, 25). Each of these can be represented by a byte,
so I assumed that byteTestImage would correspond to this. But, byteTestImage is 635 elements in total.
What is the relationship between those bytes and the 1 pixel image?
The file you loaded is a JPG. It has certain additional information (width, height, EXIF data) not just colors. Look at https://en.wikipedia.org/wiki/JPEG
Try opening it in a hex editor. You might even be able to read info about the camera used to take it.
There is no always RGB format for single pixel in Bitmap. It all depends on format. You can have a alfa component, you can have a palette to which martix of pixels refers to and more...
Check out: Bitmap format

C# Converting 32bpp image to 8bpp

I'm trying to convert a 32bpp screenshot image to an 8bpp (or 4bpp, or 1bpp) format using C#. I've already looked at several stackoverflow answers on similar subjects and most suggest variations using the following code:
public static Bitmap Convert(Bitmap oldbmp)
{
Bitmap newbmp = new Bitmap(oldbmp.Width, oldbmp.Height, PixelFormat.Format8bppIndexed);
Graphics gr = Graphics.FromImage(newbmp);
gr.PageUnit = GraphicsUnit.Pixel;
gr.DrawImageUnscaled(oldbmp, 0, 0);
return newbmp;
}
However, when this executes, I get a the exception: A graphics object cannot be created from an image that has an indexed pixel format. I understand that 8, 4 and 1bpp images have colour table mappings rather than the actual colour pixels themselves (as in 32 or 16bpp images) so I assume I'm missing some conversion step somewhere, but I'm fairly new to C# (coming from a C++ background) and would prefer to be able do this using native C# calls rather than resorting to PInvoking BitBlt and GetDIBits etc. Anybody able to help me solve this? Thanks.
EDIT: I should point out that I need this to be backwardly compatible to .NET framework 2.0
GDI+ in general has very poor support for indexed pixel formats. There is no simple way to convert an image with 65536 or 16 million colors into one that only has 2, 16 or 256. Colors have to be removed from the source image and that is a lossy conversion that can have very poor results. There are multiple algorithms available to accomplish this, none of them are perfect for every kind of image. This is a job for a graphics editor.
There is one trick I found. GDI+ has an image encoder for GIF files. That's a graphics format that has only 256 colors, the encoder must limit the number of colors. It uses a dithering algorithm that's suitable for photos. It does have a knack for generating a grid pattern, you'll be less than thrilled when it does. Use it like this:
public static Image Convert(Bitmap oldbmp) {
using (var ms = new MemoryStream()) {
oldbmp.Save(ms, ImageFormat.Gif);
ms.Position = 0;
return Image.FromStream(ms);
}
}
The returned image has a 8bpp pixel format with the Palette entries calculated by the encoder. You can cast it to Bitmap if necessary. By far the best thing to do is to simply not bother with indexed formats. They date from the stone age of computing back when memory was severely constrained. Or use a professional graphics editor.
AForge library is doing it perfectly using Grayscale.
var bmp8bpp = Grayscale.CommonAlgorithms.BT709.Apply(bmp);
This class is the base class for image grayscaling [...]
The filter accepts 24, 32, 48 and 64 bpp color images and produces 8
(if source is 24 or 32 bpp image) or 16 (if source is 48 or 64 bpp
image) bpp grayscale image.
Negative stride signifies the image is bottom-up (inverted). Just use the absolute of the stride if you dont care. I know that works for 24bpp images, unaware if it works for others.
You can use System.Windows.Media.Imaging in PresentationCore Assembly take a look at here for more information

Categories